From b6949b9169f92ad71b32f320119c28a61894fb71 Mon Sep 17 00:00:00 2001 From: Brent Swisher Date: Mon, 10 Dec 2018 02:15:40 -0500 Subject: [PATCH 001/691] Add an error state to the image block to allow upload errors to display (#10224) --- packages/block-library/src/image/edit.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index 551e3dadda255..f7fdd73a9aaa5 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -121,7 +121,7 @@ class ImageEdit extends Component { } componentDidMount() { - const { attributes, setAttributes } = this.props; + const { attributes, setAttributes, noticeOperations } = this.props; const { id, url = '' } = attributes; if ( isTemporaryImage( id, url ) ) { @@ -134,6 +134,10 @@ class ImageEdit extends Component { setAttributes( pickRelevantMediaFiles( image ) ); }, allowedTypes: ALLOWED_MEDIA_TYPES, + onError: ( message ) => { + noticeOperations.createErrorNotice( message ); + this.setState( { isEditing: true } ); + }, } ); } } From 40d1282a992702e1933118ef626f0666692baf2b Mon Sep 17 00:00:00 2001 From: Joen Asmussen Date: Mon, 10 Dec 2018 08:26:23 +0100 Subject: [PATCH 002/691] Try: JS Console warning for when in Quirks Mode (#12575) * Try: JS Console warning for when in Quirks Mode This PR detects whether the browser is in Quirks Mode. Quirks Mode is a rendering method used when the doctype definition is missing or incorrectly placed in the HTML source, causing the browser to have difficulty detecting the type of document it is to render. This is usually caused by a PHP error, or even just a style tag that is output incorrectly on the page. See discussion in https://github.com/WordPress/gutenberg/pull/12455 and https://github.com/WordPress/gutenberg/issues/11378. The usual result is Gutenberg rendering incorrectly, notably with metaboxes overlapping content. The purpose of this PR is to help developers debug the issue and fix it at the root. As such, it adds a console warning, props @nickcernis for the text: ``` [Warning] Your browser is using Quirks Mode. This can cause rendering issues such as blocks overlaying meta boxes in the editor. Quirks Mode can be triggered by PHP errors or HTML code appearing before the opening . Try checking the raw page source or your site's PHP error log and resolving errors there, removing any HTML before the doctype, or disabling plugins. ``` It also augments the documentation to add a note about this. * Move warning to index.js * Remove try/catch. * Tweak: Remove redundant [warning] in warn call --- .../developers/backwards-compatibility/meta-box.md | 2 ++ packages/edit-post/src/index.js | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/docs/designers-developers/developers/backwards-compatibility/meta-box.md b/docs/designers-developers/developers/backwards-compatibility/meta-box.md index eb96bfc38e0ff..2f28f48ed77cb 100644 --- a/docs/designers-developers/developers/backwards-compatibility/meta-box.md +++ b/docs/designers-developers/developers/backwards-compatibility/meta-box.md @@ -82,3 +82,5 @@ Most PHP meta boxes should continue to work in Gutenberg, but some meta boxes th - Plugins relying on selectors that target the post title, post content fields, and other metaboxes (of the old editor). - Plugins relying on TinyMCE's API because there's no longer a single TinyMCE instance to talk to in Gutenberg. - Plugins making updates to their DOM on "submit" or on "save". + +Please also note that if your plugin triggers a PHP warning or notice to be output on the page, this will cause the HTML document type (``) to be output incorrectly. This will cause the browser to render using "Quirks Mode", which is a compatibility layer that gets enabled when the browser doesn't know what type of document it is parsing. The block editor is not meant to work in this mode, but it can _appear_ to be working just fine. If you encounter issues such as *meta boxes overlaying the editor* or other layout issues, please check the raw page source of your document to see that the document type definition is the first thing output on the page. There will also be a warning in the JavaScript console, noting the issue. diff --git a/packages/edit-post/src/index.js b/packages/edit-post/src/index.js index bce8285f72b0b..59e870e7929da 100644 --- a/packages/edit-post/src/index.js +++ b/packages/edit-post/src/index.js @@ -68,6 +68,13 @@ export function initializeEditor( id, postType, postId, settings, initialEdits ) registerCoreBlocks(); + // Show a console log warning if the browser is not in Standards rendering mode. + const documentMode = document.compatMode === 'CSS1Compat' ? 'Standards' : 'Quirks'; + if ( documentMode !== 'Standards' ) { + // eslint-disable-next-line no-console + console.warn( "Your browser is using Quirks Mode. \nThis can cause rendering issues such as blocks overlaying meta boxes in the editor. Quirks Mode can be triggered by PHP errors or HTML code appearing before the opening . Try checking the raw page source or your site's PHP error log and resolving errors there, removing any HTML before the doctype, or disabling plugins." ); + } + dispatch( 'core/nux' ).triggerGuide( [ 'core/editor.inserter', 'core/editor.settings', From b7e343d892fbcbe471e0438de64c3b9b373a44b0 Mon Sep 17 00:00:00 2001 From: Terri Ann Date: Mon, 10 Dec 2018 09:48:41 -0600 Subject: [PATCH 003/691] Organizing screenshot assets for the block tutorial inside the designers-developers directory in the repo (#12745) --- .../block-tutorial => assets}/inspector.png | Bin .../assets/toolbar-text.png | Bin 0 -> 22683 bytes .../block-controls-toolbars-and-inspector.md | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) rename docs/designers-developers/{developers/tutorials/block-tutorial => assets}/inspector.png (100%) create mode 100644 docs/designers-developers/assets/toolbar-text.png diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/inspector.png b/docs/designers-developers/assets/inspector.png similarity index 100% rename from docs/designers-developers/developers/tutorials/block-tutorial/inspector.png rename to docs/designers-developers/assets/inspector.png diff --git a/docs/designers-developers/assets/toolbar-text.png b/docs/designers-developers/assets/toolbar-text.png new file mode 100644 index 0000000000000000000000000000000000000000..76b18c6b8f368f3abd5d2559e5a6f71c75913ed4 GIT binary patch literal 22683 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 +![Screenshot of the rich text toolbar applied to a paragraph block inside the block editor](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/assets/toolbar-text.png) When the user selects a block, a number of control buttons may be shown in a toolbar above the selected block. Some of these block-level controls are included automatically if the editor is able to transform the block to another type, or if the focused element is an RichText component. @@ -171,7 +171,7 @@ Note that `BlockControls` is only visible when the block is currently selected a ## Inspector -inspector +![Screenshot of the inspector panel focused on the settings for a paragraph block](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/assets/inspector.png) The inspector is used to display less-often-used settings or settings that require more screen space. The inspector should be used for **block-level settings only**. From 8790bce7ae56191d00fe7384ea1832df10624961 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak Date: Mon, 10 Dec 2018 12:55:19 -0800 Subject: [PATCH 004/691] Rename backwards compatiblity to backward compatibility (#12751) * Rename backwards compatiblity to backward compatibility * Remove package-lock from commit * Update CONTRIBUTING.md Co-Authored-By: mkaz * Update CONTRIBUTING.md Co-Authored-By: mkaz * Whitespace in manifest --- CONTRIBUTING.md | 6 +++--- README.md | 4 ++-- docs/designers-developers/developers/README.md | 2 +- .../developers/backward-compatibility/README.md | 1 + .../deprecations.md | 2 +- .../meta-box.md | 6 +++--- .../developers/backwards-compatibility/README.md | 1 - docs/manifest.json | 14 +++++++------- docs/toc.json | 6 +++--- lib/client-assets.php | 2 +- .../components/src/server-side-render/README.md | 4 ++-- packages/edit-post/src/store/effects.js | 2 +- 12 files changed, 25 insertions(+), 25 deletions(-) create mode 100644 docs/designers-developers/developers/backward-compatibility/README.md rename docs/designers-developers/developers/{backwards-compatibility => backward-compatibility}/deprecations.md (97%) rename docs/designers-developers/developers/{backwards-compatibility => backward-compatibility}/meta-box.md (95%) delete mode 100644 docs/designers-developers/developers/backwards-compatibility/README.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4ccb59aa2df44..e9a47c7322e47 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -166,9 +166,9 @@ Maintaining dozens of npm packages is difficult—it can be tough to keep track The developer who proposes a change (pull request) is responsible to choose the correct version increment (`major`, `minor`, or `patch`) according to the following guidelines: -- Major version X (X.y.z | X > 0) should be changed with any backwards-incompatible/"breaking" change. This will usually occur at the final stage of deprecating and removing of a feature. -- Minor version Y (x.Y.z | x > 0) should be changed when you add functionality or change functionality in a backwards-compatible manner. It must be incremented if any public API functionality is marked as deprecated. -- Patch version Z (x.y.Z | x > 0) should be incremented when you make backwards-compatible bug fixes. +- Major version X (X.y.z | X > 0) should be changed with any backward incompatible/"breaking" change. This will usually occur at the final stage of deprecating and removing of a feature. +- Minor version Y (x.Y.z | x > 0) should be changed when you add functionality or change functionality in a backward compatible manner. It must be incremented if any public API functionality is marked as deprecated. +- Patch version Z (x.y.Z | x > 0) should be incremented when you make backward compatible bug fixes. When in doubt, refer to [Semantic Versioning specification](https://semver.org/). diff --git a/README.md b/README.md index 03251ceba2160..855958ef9635e 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ This repo is the development hub for the editor focus in WordPress Core. `Gutenberg` is the project name. ## Getting started -- **Download:** If you want to use the latest release with your WordPress site, download the latest release from the WordPress.org plugins repository. +- **Download:** If you want to use the latest release with your WordPress site, download the latest release from the WordPress.org plugins repository. - **Discuss:** Conversations and discussions take place in `#core-editor` channel on the Making WordPress Slack. - **Contribute:** Development of Gutenberg happens in this GitHub repo. Get started by reading the contributing guidelines. - **Learn:** Discover more about the project on WordPress.org. @@ -44,7 +44,7 @@ Check out the ( block="core/archives" attributes={ { showPostCounts: true, - displayAsDropdown: false, + displayAsDropdown: false, } } /> ); diff --git a/packages/edit-post/src/store/effects.js b/packages/edit-post/src/store/effects.js index 70ebd8cbb5305..a177c36c1b761 100644 --- a/packages/edit-post/src/store/effects.js +++ b/packages/edit-post/src/store/effects.js @@ -79,7 +79,7 @@ const effects = { const state = store.getState(); - // Additional data needed for backwards compatibility. + // Additional data needed for backward compatibility. // If we do not provide this data, the post will be overridden with the default values. const post = select( 'core/editor' ).getCurrentPost( state ); const additionalData = [ From 1a1dc7c15f556a5298c2549e219fdc79fb951304 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Tue, 11 Dec 2018 03:21:35 -0600 Subject: [PATCH 005/691] Update node-sass to 4.11.0 to support Node.js 11 (#12541) ## Description Fixes #12539 by updating node-sass to support Node.js 11. ## How has this been tested? Running `npm install` on macOS 10.14 with Node.js 11.2 without problems. ## Types of changes Minor dependency bump to support Node.js 11. ## Checklist: - [x] My code is tested. - [x] My code follows the WordPress code style. - [x] My code follows the accessibility standards. - [x] My code has proper inline documentation. --- package-lock.json | 158 +++++++++++++++++++++++++++++++++------------- package.json | 2 +- 2 files changed, 115 insertions(+), 45 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8ced31ee5d85a..347cbcd533748 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15030,9 +15030,9 @@ } }, "node-sass": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.9.2.tgz", - "integrity": "sha512-LdxoJLZutx0aQXHtWIYwJKMj+9pTjneTcLWJgzf2XbGu0q5pRNqW5QvFCEdm3mc5rJOdru/mzln5d0EZLacf6g==", + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.11.0.tgz", + "integrity": "sha512-bHUdHTphgQJZaF1LASx0kAviPH7sGlcyNhWade4eVIpFp6tsn7SV8xNMTbsQFpEV9VXpnwTTnNYlfsZXgGgmkA==", "dev": true, "requires": { "async-foreach": "^0.1.3", @@ -15048,14 +15048,26 @@ "meow": "^3.7.0", "mkdirp": "^0.5.1", "nan": "^2.10.0", - "node-gyp": "^3.3.1", + "node-gyp": "^3.8.0", "npmlog": "^4.0.0", - "request": "2.87.0", + "request": "^2.88.0", "sass-graph": "^2.2.4", "stdout-stream": "^1.4.0", "true-case-path": "^1.0.2" }, "dependencies": { + "ajv": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.1.tgz", + "integrity": "sha512-ZoJjft5B+EJBjUyu9C9Hc0OZyPZSSlOF+plzouTrg6UlA8f+e/n8NIgBFG/9tppJtpPWfthHakK7juJdNDODww==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", @@ -15068,6 +15080,12 @@ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", "dev": true }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "dev": true + }, "camelcase": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", @@ -15076,7 +15094,7 @@ }, "camelcase-keys": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", "dev": true, "requires": { @@ -15086,7 +15104,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -15107,6 +15125,28 @@ "which": "^1.2.9" } }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "dev": true, + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, "indent-string": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", @@ -15116,15 +15156,11 @@ "repeating": "^2.0.0" } }, - "lru-cache": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", - "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", - "dev": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true }, "map-obj": { "version": "1.0.1", @@ -15134,7 +15170,7 @@ }, "meow": { "version": "3.7.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz", "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", "dev": true, "requires": { @@ -15150,12 +15186,33 @@ "trim-newlines": "^1.0.0" } }, + "mime-db": { + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", + "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==", + "dev": true + }, + "mime-types": { + "version": "2.1.21", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", + "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", + "dev": true, + "requires": { + "mime-db": "~1.37.0" + } + }, "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, "redent": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", @@ -15166,9 +15223,37 @@ "strip-indent": "^1.0.1" } }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, "strip-ansi": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { @@ -18883,7 +18968,7 @@ }, "os-locale": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", "dev": true, "requires": { @@ -18903,7 +18988,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { @@ -19038,7 +19123,7 @@ "dependencies": { "source-map": { "version": "0.4.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "resolved": "http://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", "dev": true, "requires": { @@ -19674,9 +19759,9 @@ "dev": true }, "stdout-stream": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.0.tgz", - "integrity": "sha1-osfIWH5U2UJ+qe2zrD8s1SLfN4s=", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz", + "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==", "dev": true, "requires": { "readable-stream": "^2.0.1" @@ -20723,27 +20808,12 @@ "dev": true }, "true-case-path": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.2.tgz", - "integrity": "sha1-fskRMJJHZsf1c74wIMNPj9/QDWI=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz", + "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==", "dev": true, "requires": { - "glob": "^6.0.4" - }, - "dependencies": { - "glob": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", - "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", - "dev": true, - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } + "glob": "^7.1.2" } }, "tryer": { diff --git a/package.json b/package.json index 7351c13255f05..432662628f9e2 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ "lint-staged": "7.2.0", "lodash": "4.17.10", "mkdirp": "0.5.1", - "node-sass": "4.9.2", + "node-sass": "4.11.0", "path-type": "3.0.0", "pegjs": "0.10.0", "phpegjs": "1.0.0-beta7", From 0fba129ab9dd7755d4e94a275f738657470fc170 Mon Sep 17 00:00:00 2001 From: Nate Wright Date: Tue, 11 Dec 2018 18:14:51 +0000 Subject: [PATCH 006/691] Add attributes to ServerSideRender readme (#12793) * Add attributes to ServerSideRender readme Adds a code example demonstrating how to define attributes when registering a block that will use attributes in a ServerSideRender component. * Add whitespace and inline code markup to ServerSideRender readme Implements requested changes from code review. --- .../src/server-side-render/README.md | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/components/src/server-side-render/README.md b/packages/components/src/server-side-render/README.md index ee18a7f4294ed..55b0be7630dab 100644 --- a/packages/components/src/server-side-render/README.md +++ b/packages/components/src/server-side-render/README.md @@ -32,5 +32,25 @@ Output uses the block's `render_callback` function, set when defining the block. ## API Endpoint -The API endpoint for getting the output for ServerSideRender is `/wp/v2/block-renderer/:block`. It accepts any params, which are used as `attributes` for the block's `render_callback` method. - +The API endpoint for getting the output for ServerSideRender is `/wp/v2/block-renderer/:block`. It will use the block's `render_callback` method. + +If you pass `attributes` to `ServerSideRender`, the block must also be registered and have its attributes defined in PHP. + +```php +register_block_type( + 'core/archives', + array( + 'attributes' => array( + 'showPostCounts' => array( + 'type' => 'boolean', + 'default' => false, + ), + 'displayAsDropdown' => array( + 'type' => 'boolean', + 'default' => false, + ), + ), + 'render_callback' => 'render_block_core_archives', + ) +); +``` From 70a48ea2b25c3af621563b42c32dc936a0a945f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= Date: Wed, 12 Dec 2018 03:44:54 -0500 Subject: [PATCH 007/691] Scripts: Add check-engines script to the package (#12721) * Scripts: Add check-engines script to the package * Update packages/scripts/CHANGELOG.md Co-Authored-By: gziolo * Update packages/scripts/README.md Co-Authored-By: gziolo * Update minimal node version to 10.x Co-Authored-By: gziolo --- package-lock.json | 1 + package.json | 7 +---- packages/scripts/CHANGELOG.md | 6 ++++ packages/scripts/README.md | 19 +++++++++++++ packages/scripts/package.json | 1 + packages/scripts/scripts/check-engines.js | 34 +++++++++++++++++++++++ 6 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 packages/scripts/scripts/check-engines.js diff --git a/package-lock.json b/package-lock.json index 347cbcd533748..161b9cdfaf0be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2763,6 +2763,7 @@ "@wordpress/npm-package-json-lint-config": "file:packages/npm-package-json-lint-config", "babel-eslint": "8.0.3", "chalk": "^2.4.1", + "check-node-version": "^3.1.1", "cross-spawn": "^5.1.0", "eslint": "^4.19.1", "jest": "^23.6.0", diff --git a/package.json b/package.json index 432662628f9e2..b5be65bc97bcb 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,6 @@ "WordPress", "editor" ], - "engines": { - "node": ">=8.0.0", - "npm": ">=6.0.0" - }, "dependencies": { "@wordpress/a11y": "file:packages/a11y", "@wordpress/annotations": "file:packages/annotations", @@ -72,7 +68,6 @@ "autoprefixer": "8.2.0", "babel-loader": "8.0.0", "chalk": "2.4.1", - "check-node-version": "3.1.1", "concurrently": "3.5.0", "copy-webpack-plugin": "4.5.2", "core-js": "2.5.7", @@ -145,7 +140,7 @@ "prebuild:packages": "npm run clean:packages && lerna run build && cross-env INCLUDE_PACKAGES=babel-plugin-import-jsx-pragma,postcss-themes,jest-console SKIP_JSX_PRAGMA_TRANSFORM=1 node ./bin/packages/build.js", "build:packages": "cross-env EXCLUDE_PACKAGES=babel-plugin-import-jsx-pragma,jest-console,postcss-themes node ./bin/packages/build.js", "build": "npm run build:packages && cross-env NODE_ENV=production webpack", - "check-engines": "check-node-version --package", + "check-engines": "wp-scripts check-engines", "check-licenses": "concurrently \"wp-scripts check-licenses --prod --gpl2\" \"wp-scripts check-licenses --dev\"", "precheck-local-changes": "npm run docs:build", "check-local-changes": "( git diff -U0 | xargs -0 node bin/process-git-diff ) || ( echo \"There are local uncommitted changes after one or both of 'npm install' or 'npm run docs:build'!\" && exit 1 );", diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index c07fe9a2e6d56..6f2977dc07f90 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.5.0 (Unreleased) + +### New Feature + +- Added support for `check-engines` script ([#12721](https://github.com/WordPress/gutenberg/pull/12721)) + ## 2.4.4 (2018-11-20) ## 2.4.3 (2018-11-09) diff --git a/packages/scripts/README.md b/packages/scripts/README.md index 4126cdd34cc9b..04ddf958782e0 100644 --- a/packages/scripts/README.md +++ b/packages/scripts/README.md @@ -19,6 +19,7 @@ _Example:_ ```json { "scripts": { + "check-engines": "wp-scripts check-engines", "lint:pkg-json": "wp-scripts lint-pkg-json .", "test": "wp-scripts test-unit-js" } @@ -27,6 +28,24 @@ _Example:_ ## Available Scripts +### `check-engines` + +Check if the current `node`, `npm` (or `yarn`) versions match the given [semantic version](https://semver.org/) ranges. If the given version is not satisfied, information about installing the needed version is printed and the program exits with an error code. It uses [check-node-version](https://www.npmjs.com/package/check-node-version) behind the scenes with the default configuration provided. You can specify your own ranges as described in [check-node-version docs](https://www.npmjs.com/package/check-node-version). + +_Example:_ + +```json +{ + "scripts": { + "check-engines": "wp-scripts check-engines" + } +} +``` + +This is how you execute the script with presented setup: +* `npm run check-engines` - checks installed version of `node` and `npm`. + + ### `wp-scripts lint-js` Helps enforce coding style guidelines for your JavaScript files. It uses [eslint](https://eslint.org/) with no rules provided (we plan to add zero config support in the near future). You can specify your own rules as described in [eslint docs](https://eslint.org/docs/rules/). diff --git a/packages/scripts/package.json b/packages/scripts/package.json index fbbbff832e736..6a0bc5e1986a9 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -36,6 +36,7 @@ "@wordpress/npm-package-json-lint-config": "file:../npm-package-json-lint-config", "babel-eslint": "8.0.3", "chalk": "^2.4.1", + "check-node-version": "^3.1.1", "cross-spawn": "^5.1.0", "eslint": "^4.19.1", "jest": "^23.6.0", diff --git a/packages/scripts/scripts/check-engines.js b/packages/scripts/scripts/check-engines.js new file mode 100644 index 0000000000000..5440e27ccaffb --- /dev/null +++ b/packages/scripts/scripts/check-engines.js @@ -0,0 +1,34 @@ +/** + * External dependencies + */ +const { sync: spawn } = require( 'cross-spawn' ); +const { sync: resolveBin } = require( 'resolve-bin' ); + +/** + * Internal dependencies + */ +const { + getCliArgs, + hasCliArg, +} = require( '../utils' ); + +const args = getCliArgs(); + +const hasConfig = hasCliArg( '--package' ) || + hasCliArg( '--node' ) || + hasCliArg( '--npm' ) || + hasCliArg( '--yarn' ); +const config = ! hasConfig ? + [ + '--node', '>=10.0.0', + '--npm', '>=6.0.0', + ] : + []; + +const result = spawn( + resolveBin( 'check-node-version' ), + [ ...config, ...args ], + { stdio: 'inherit' } +); + +process.exit( result.status ); From 0416bae17c52b0a11ec1075c0928f879264b7d75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= Date: Wed, 12 Dec 2018 04:21:31 -0500 Subject: [PATCH 008/691] Move devDependencies to root package.json file (#12720) * Chore: Remove unused npm dependencies from the root package.json file * Move devDependencies to root package.json file --- package-lock.json | 60 ++++++++++++++----- package.json | 20 +++++-- .../package.json | 4 -- packages/babel-plugin-makepot/package.json | 4 -- packages/block-library/package.json | 5 -- .../package.json | 3 - .../package.json | 3 - packages/blocks/package.json | 3 - packages/browserslist-config/package.json | 3 - packages/components/package.json | 5 -- packages/compose/package.json | 5 -- packages/core-data/package.json | 3 - .../package.json | 3 - packages/data/package.json | 5 -- packages/edit-post/package.json | 4 -- packages/editor/package.json | 6 -- packages/element/package.json | 3 - packages/hooks/package.json | 3 - packages/i18n/package.json | 3 - packages/is-shallow-equal/package.json | 8 --- .../package.json | 4 -- packages/notices/package.json | 3 - .../npm-package-json-lint-config/package.json | 3 - packages/nux/package.json | 3 - packages/postcss-themes/package.json | 4 +- packages/redux-routine/package.json | 3 - packages/rich-text/package.json | 4 -- packages/viewport/package.json | 4 -- 28 files changed, 63 insertions(+), 118 deletions(-) diff --git a/package-lock.json b/package-lock.json index 161b9cdfaf0be..086978a9ea5c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2732,7 +2732,9 @@ "dev": true, "requires": { "@babel/runtime": "^7.0.0", - "postcss": "^6.0.16" + "autoprefixer": "^8.2.0", + "postcss": "^6.0.16", + "postcss-color-function": "^4.0.1" } }, "@wordpress/redux-routine": { @@ -4503,6 +4505,16 @@ "tweetnacl": "^0.14.3" } }, + "benchmark": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/benchmark/-/benchmark-2.1.4.tgz", + "integrity": "sha1-CfPeMckWQl1JjMLuVloOvzwqVik=", + "dev": true, + "requires": { + "lodash": "^4.17.4", + "platform": "^1.3.3" + } + }, "bfj": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/bfj/-/bfj-6.1.1.tgz", @@ -15095,7 +15107,7 @@ }, "camelcase-keys": { "version": "2.1.0", - "resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", "dev": true, "requires": { @@ -15105,7 +15117,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -15171,7 +15183,7 @@ }, "meow": { "version": "3.7.0", - "resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", "dev": true, "requires": { @@ -15204,7 +15216,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, @@ -15254,7 +15266,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { @@ -16371,6 +16383,12 @@ "find-up": "^2.1.0" } }, + "platform": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.5.tgz", + "integrity": "sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q==", + "dev": true + }, "please-upgrade-node": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.1.1.tgz", @@ -18969,7 +18987,7 @@ }, "os-locale": { "version": "1.4.0", - "resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", "dev": true, "requires": { @@ -18989,7 +19007,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { @@ -19124,7 +19142,7 @@ "dependencies": { "source-map": { "version": "0.4.4", - "resolved": "http://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", "dev": true, "requires": { @@ -19280,6 +19298,24 @@ } } }, + "shallow-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.0.0.tgz", + "integrity": "sha1-UI0YOLPeWQq4dXsBGyXkMJAJRfc=", + "dev": true + }, + "shallow-equals": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shallow-equals/-/shallow-equals-1.0.0.tgz", + "integrity": "sha1-JLdL8cY0wR7Uxxgqbfb7MA3OQ5A=", + "dev": true + }, + "shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "dev": true + }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -20436,12 +20472,6 @@ "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=", "dev": true }, - "symlink-or-copy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/symlink-or-copy/-/symlink-or-copy-1.2.0.tgz", - "integrity": "sha512-W31+GLiBmU/ZR02Ii0mVZICuNEN9daZ63xZMPDsYgPgNjMtg+atqLEGI7PPI936jYSQZxoLb/63xos8Adrx4Eg==", - "dev": true - }, "table": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/table/-/table-4.0.3.tgz", diff --git a/package.json b/package.json index b5be65bc97bcb..c9a2723cb9008 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,9 @@ }, "devDependencies": { "@babel/core": "7.0.0", + "@babel/plugin-syntax-jsx": "7.0.0", "@babel/runtime-corejs2": "7.0.0", + "@babel/traverse": "7.0.0", "@wordpress/babel-plugin-import-jsx-pragma": "file:packages/babel-plugin-import-jsx-pragma", "@wordpress/babel-plugin-makepot": "file:packages/babel-plugin-makepot", "@wordpress/babel-preset-default": "file:packages/babel-preset-default", @@ -65,41 +67,49 @@ "@wordpress/npm-package-json-lint-config": "file:packages/npm-package-json-lint-config", "@wordpress/postcss-themes": "file:packages/postcss-themes", "@wordpress/scripts": "file:packages/scripts", - "autoprefixer": "8.2.0", "babel-loader": "8.0.0", + "benchmark": "2.1.4", + "browserslist": "3.2.8", "chalk": "2.4.1", "concurrently": "3.5.0", "copy-webpack-plugin": "4.5.2", "core-js": "2.5.7", "cross-env": "3.2.4", "cssnano": "4.0.3", + "enzyme": "3.7.0", "deasync": "0.1.13", "deep-freeze": "0.0.1", "doctrine": "2.1.0", "eslint-plugin-jest": "21.5.0", "espree": "3.5.4", + "fbjs": "0.8.17", "glob": "7.1.2", "husky": "0.14.3", + "is-plain-obj": "1.1.0", + "is-equal-shallow": "0.1.3", "jest-puppeteer": "3.2.1", + "jsdom": "11.12.0", "lerna": "3.4.3", "lint-staged": "7.2.0", "lodash": "4.17.10", "mkdirp": "0.5.1", "node-sass": "4.11.0", - "path-type": "3.0.0", "pegjs": "0.10.0", "phpegjs": "1.0.0-beta7", - "postcss-color-function": "4.0.1", "puppeteer": "1.6.1", + "react-dom": "16.6.3", "react-test-renderer": "16.6.3", + "redux": "4.0.0", "rimraf": "2.6.2", "rtlcss": "2.4.0", "sass-loader": "6.0.7", - "source-map-loader": "0.2.3", + "shallow-equal": "1.0.0", + "shallow-equals": "1.0.0", + "shallowequal": "1.1.0", "sprintf-js": "1.1.1", + "source-map-loader": "0.2.3", "stylelint": "9.5.0", "stylelint-config-wordpress": "13.1.0", - "symlink-or-copy": "1.2.0", "uuid": "3.3.2", "webpack": "4.8.3", "webpack-bundle-analyzer": "3.0.2", diff --git a/packages/babel-plugin-import-jsx-pragma/package.json b/packages/babel-plugin-import-jsx-pragma/package.json index 5cd00bf163f86..1c2bb7de9bb95 100644 --- a/packages/babel-plugin-import-jsx-pragma/package.json +++ b/packages/babel-plugin-import-jsx-pragma/package.json @@ -28,10 +28,6 @@ "dependencies": { "@babel/runtime": "^7.0.0" }, - "devDependencies": { - "@babel/core": "^7.0.0", - "@babel/plugin-syntax-jsx": "^7.0.0" - }, "peerDependencies": { "@babel/core": "^7.0.0" }, diff --git a/packages/babel-plugin-makepot/package.json b/packages/babel-plugin-makepot/package.json index e21a023be7d3d..819415f3b9732 100644 --- a/packages/babel-plugin-makepot/package.json +++ b/packages/babel-plugin-makepot/package.json @@ -29,10 +29,6 @@ "gettext-parser": "^1.3.1", "lodash": "^4.17.10" }, - "devDependencies": { - "@babel/core": "^7.0.0", - "@babel/traverse": "^7.0.0" - }, "peerDependencies": { "@babel/core": "^7.0.0" }, diff --git a/packages/block-library/package.json b/packages/block-library/package.json index ad9c4514349cc..5d3e510bbfe93 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -40,11 +40,6 @@ "memize": "^1.0.5", "url": "^0.11.0" }, - "devDependencies": { - "deep-freeze": "^0.0.1", - "enzyme": "^3.7.0", - "react-test-renderer": "^16.6.3" - }, "publishConfig": { "access": "public" } diff --git a/packages/block-serialization-default-parser/package.json b/packages/block-serialization-default-parser/package.json index 1a235b320cd06..8cf5725dfd2d2 100644 --- a/packages/block-serialization-default-parser/package.json +++ b/packages/block-serialization-default-parser/package.json @@ -23,9 +23,6 @@ "dependencies": { "@babel/runtime": "^7.0.0" }, - "devDependencies": { - "@wordpress/block-serialization-spec-parser": "file:../block-serialization-spec-parser" - }, "publishConfig": { "access": "public" } diff --git a/packages/block-serialization-spec-parser/package.json b/packages/block-serialization-spec-parser/package.json index 251a28045fb95..7739f0bf04177 100644 --- a/packages/block-serialization-spec-parser/package.json +++ b/packages/block-serialization-spec-parser/package.json @@ -18,9 +18,6 @@ "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" }, - "devDependencies": { - "pegjs": "0.10.0" - }, "publishConfig": { "access": "public" }, diff --git a/packages/blocks/package.json b/packages/blocks/package.json index 1a662f7a28626..af7151b58d806 100644 --- a/packages/blocks/package.json +++ b/packages/blocks/package.json @@ -41,9 +41,6 @@ "tinycolor2": "^1.4.1", "uuid": "^3.3.2" }, - "devDependencies": { - "deep-freeze": "^0.0.1" - }, "publishConfig": { "access": "public" } diff --git a/packages/browserslist-config/package.json b/packages/browserslist-config/package.json index a554d875997e8..928f2c2dffb23 100644 --- a/packages/browserslist-config/package.json +++ b/packages/browserslist-config/package.json @@ -21,9 +21,6 @@ "node": ">=8" }, "main": "index.js", - "devDependencies": { - "browserslist": "^3.1.0" - }, "publishConfig": { "access": "public" } diff --git a/packages/components/package.json b/packages/components/package.json index 6657f37b182b6..9ad1244df0dff 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -47,11 +47,6 @@ "tinycolor2": "^1.4.1", "uuid": "^3.3.2" }, - "devDependencies": { - "@wordpress/token-list": "file:../token-list", - "enzyme": "^3.7.0", - "react-test-renderer": "^16.6.3" - }, "publishConfig": { "access": "public" } diff --git a/packages/compose/package.json b/packages/compose/package.json index dc7a4ea89316e..ace35bcce8ba3 100644 --- a/packages/compose/package.json +++ b/packages/compose/package.json @@ -26,11 +26,6 @@ "@wordpress/is-shallow-equal": "file:../is-shallow-equal", "lodash": "^4.17.10" }, - "devDependencies": { - "enzyme": "^3.7.0", - "react-dom": "^16.6.3", - "react-test-renderer": "^16.6.3" - }, "publishConfig": { "access": "public" } diff --git a/packages/core-data/package.json b/packages/core-data/package.json index ad9917d785bad..4431099461900 100644 --- a/packages/core-data/package.json +++ b/packages/core-data/package.json @@ -29,9 +29,6 @@ "lodash": "^4.17.10", "rememo": "^3.0.0" }, - "devDependencies": { - "deep-freeze": "^0.0.1" - }, "publishConfig": { "access": "public" } diff --git a/packages/custom-templated-path-webpack-plugin/package.json b/packages/custom-templated-path-webpack-plugin/package.json index 8cf29e724b187..f1d922ffd5949 100644 --- a/packages/custom-templated-path-webpack-plugin/package.json +++ b/packages/custom-templated-path-webpack-plugin/package.json @@ -27,9 +27,6 @@ "@babel/runtime": "^7.0.0", "escape-string-regexp": "^1.0.5" }, - "devDependencies": { - "webpack": "^4.8.3" - }, "peerDependencies": { "webpack": "^4.0.0" }, diff --git a/packages/data/package.json b/packages/data/package.json index 493c9af3bd7be..a547a316872d2 100644 --- a/packages/data/package.json +++ b/packages/data/package.json @@ -32,11 +32,6 @@ "redux": "^4.0.0", "turbo-combine-reducers": "^1.0.2" }, - "devDependencies": { - "deep-freeze": "^0.0.1", - "enzyme": "^3.7.0", - "react-test-renderer": "^16.6.3" - }, "publishConfig": { "access": "public" } diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json index 6c98e2826302c..d8d3f4802aa39 100644 --- a/packages/edit-post/package.json +++ b/packages/edit-post/package.json @@ -42,10 +42,6 @@ "lodash": "^4.17.10", "refx": "^3.0.0" }, - "devDependencies": { - "deep-freeze": "^0.0.1", - "enzyme": "^3.7.0" - }, "publishConfig": { "access": "public" } diff --git a/packages/editor/package.json b/packages/editor/package.json index dfe9ac51ed3fd..2f2676ee36853 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -59,12 +59,6 @@ "tinymce": "^4.7.2", "traverse": "^0.6.6" }, - "devDependencies": { - "deep-freeze": "^0.0.1", - "enzyme": "^3.7.0", - "react-dom": "^16.6.3", - "react-test-renderer": "^16.6.3" - }, "publishConfig": { "access": "public" } diff --git a/packages/element/package.json b/packages/element/package.json index 5e80ae9220c0d..19707938c6a67 100644 --- a/packages/element/package.json +++ b/packages/element/package.json @@ -27,9 +27,6 @@ "react": "^16.6.3", "react-dom": "^16.6.3" }, - "devDependencies": { - "enzyme": "^3.7.0" - }, "publishConfig": { "access": "public" } diff --git a/packages/hooks/package.json b/packages/hooks/package.json index 8f1eb12b94ab1..cef3b444b83d7 100644 --- a/packages/hooks/package.json +++ b/packages/hooks/package.json @@ -22,9 +22,6 @@ "dependencies": { "@babel/runtime": "^7.0.0" }, - "devDependencies": { - "benchmark": "^2.1.4" - }, "publishConfig": { "access": "public" } diff --git a/packages/i18n/package.json b/packages/i18n/package.json index 18158a6f0162e..a1005eac73e6e 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -30,9 +30,6 @@ "sprintf-js": "^1.1.1", "tannin": "^1.0.1" }, - "devDependencies": { - "benchmark": "^2.1.4" - }, "publishConfig": { "access": "public" } diff --git a/packages/is-shallow-equal/package.json b/packages/is-shallow-equal/package.json index 290b2810f4551..f5553ceb41a47 100644 --- a/packages/is-shallow-equal/package.json +++ b/packages/is-shallow-equal/package.json @@ -26,14 +26,6 @@ "dependencies": { "@babel/runtime": "^7.0.0" }, - "devDependencies": { - "benchmark": "^2.1.4", - "fbjs": "^0.8.16", - "is-equal-shallow": "^0.1.3", - "shallow-equal": "^1.0.0", - "shallow-equals": "^1.0.0", - "shallowequal": "^1.0.2" - }, "publishConfig": { "access": "public" } diff --git a/packages/library-export-default-webpack-plugin/package.json b/packages/library-export-default-webpack-plugin/package.json index 00b91c6dbeb44..52d28f6e9e184 100644 --- a/packages/library-export-default-webpack-plugin/package.json +++ b/packages/library-export-default-webpack-plugin/package.json @@ -27,10 +27,6 @@ "lodash": "^4.17.10", "webpack-sources": "^1.1.0" }, - "devDependencies": { - "rimraf": "^2.6.2", - "webpack": "^4.8.3" - }, "peerDependencies": { "webpack": "^4.0.0" }, diff --git a/packages/notices/package.json b/packages/notices/package.json index f7ae5027587cd..5e642a6d7f273 100644 --- a/packages/notices/package.json +++ b/packages/notices/package.json @@ -24,9 +24,6 @@ "@wordpress/data": "file:../data", "lodash": "^4.17.10" }, - "devDependencies": { - "deep-freeze": "^0.0.1" - }, "publishConfig": { "access": "public" } diff --git a/packages/npm-package-json-lint-config/package.json b/packages/npm-package-json-lint-config/package.json index e7912c2c2f2cd..ddf65a0f15c8c 100644 --- a/packages/npm-package-json-lint-config/package.json +++ b/packages/npm-package-json-lint-config/package.json @@ -18,9 +18,6 @@ "url": "https://github.com/WordPress/gutenberg/issues" }, "main": "index.js", - "devDependencies": { - "is-plain-obj": "^1.1.0" - }, "peerDependencies": { "npm-package-json-lint": ">= 3.3.1" }, diff --git a/packages/nux/package.json b/packages/nux/package.json index d754dc9f891f5..28dd725e40fae 100644 --- a/packages/nux/package.json +++ b/packages/nux/package.json @@ -29,9 +29,6 @@ "lodash": "^4.17.10", "rememo": "^3.0.0" }, - "devDependencies": { - "enzyme": "^3.7.0" - }, "publishConfig": { "access": "public" } diff --git a/packages/postcss-themes/package.json b/packages/postcss-themes/package.json index 947039a78a946..88727a2f2fe09 100644 --- a/packages/postcss-themes/package.json +++ b/packages/postcss-themes/package.json @@ -27,7 +27,9 @@ "main": "build/index.js", "dependencies": { "@babel/runtime": "^7.0.0", - "postcss": "^6.0.16" + "autoprefixer": "^8.2.0", + "postcss": "^6.0.16", + "postcss-color-function": "^4.0.1" }, "publishConfig": { "access": "public" diff --git a/packages/redux-routine/package.json b/packages/redux-routine/package.json index 5eef2653d0782..bd28bcde065c7 100644 --- a/packages/redux-routine/package.json +++ b/packages/redux-routine/package.json @@ -26,9 +26,6 @@ "is-promise": "^2.1.0", "rungen": "^0.3.2" }, - "devDependencies": { - "redux": "^4.0.0" - }, "publishConfig": { "access": "public" } diff --git a/packages/rich-text/package.json b/packages/rich-text/package.json index aeb67dbfa3506..4ae221a90812f 100644 --- a/packages/rich-text/package.json +++ b/packages/rich-text/package.json @@ -27,10 +27,6 @@ "lodash": "^4.17.10", "rememo": "^3.0.0" }, - "devDependencies": { - "deep-freeze": "^0.0.1", - "jsdom": "^11.12.0" - }, "publishConfig": { "access": "public" } diff --git a/packages/viewport/package.json b/packages/viewport/package.json index 11da1c485c9d7..527fca472cd3d 100644 --- a/packages/viewport/package.json +++ b/packages/viewport/package.json @@ -26,10 +26,6 @@ "@wordpress/element": "file:../element", "lodash": "^4.17.10" }, - "devDependencies": { - "deep-freeze": "^0.0.1", - "react-test-renderer": "^16.6.3" - }, "publishConfig": { "access": "public" } From 51583b83b66ee86201b36c70028640ffdbeece43 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Wed, 12 Dec 2018 16:27:11 +0100 Subject: [PATCH 009/691] Fix php notice from the recent comments block (#12812) --- lib/load.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/load.php b/lib/load.php index 0c55db3a37f6c..ee1c973f99568 100644 --- a/lib/load.php +++ b/lib/load.php @@ -62,7 +62,10 @@ if ( ! function_exists( 'render_block_core_categories' ) ) { require dirname( __FILE__ ) . '/../packages/block-library/src/categories/index.php'; } -if ( ! function_exists( 'render_block_core_latest_comments' ) ) { +// Currently merged in core as `gutenberg_render_block_core_latest_comments`, +// expected to change soon. +if ( ! function_exists( 'render_block_core_latest_comments' ) + && ! function_exists( 'gutenberg_render_block_core_latest_comments' ) ) { require dirname( __FILE__ ) . '/../packages/block-library/src/latest-comments/index.php'; } if ( ! function_exists( 'render_block_core_latest_posts' ) ) { From 6c688159babfbea1adfa2eda536066cd19762813 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Wed, 12 Dec 2018 17:57:26 +0100 Subject: [PATCH 010/691] RichText: Fix React warning shown when unmounting a currently selected RichText. (#12817) --- packages/editor/src/components/rich-text/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/editor/src/components/rich-text/index.js b/packages/editor/src/components/rich-text/index.js index 0731916d6a1a7..bc814d8ebff9c 100644 --- a/packages/editor/src/components/rich-text/index.js +++ b/packages/editor/src/components/rich-text/index.js @@ -129,6 +129,10 @@ export class RichText extends Component { this.lastHistoryValue = value; } + componentWillUnmount() { + document.removeEventListener( 'selectionchange', this.onSelectionChange ); + } + setRef( node ) { this.editableRef = node; } From 4dae3bc56cca220b916d2e2bfeaecc9da79e3779 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Wed, 12 Dec 2018 13:16:52 -0500 Subject: [PATCH 011/691] Packages: Reimplement ESLint config as plugin (#12763) * Packages: Move eslint-config to eslint-plugin (Fails pre-commit, but in effort to ensure history preservation) * eslint-plugin: Add npmrc to avoid package-lock.json * Framework: Update path references for eslint-config to -plugin * eslint-plugin: Reimplement ESLint config as plugin * eslint-plugin: Unmark as private * eslint-plugin: Undocument custom ruleset --- .eslintrc.js | 2 +- docs/manifest.json | 6 +- package-lock.json | 13 ++- package.json | 2 +- packages/eslint-config/README.md | 24 ----- packages/eslint-config/configs/es5.js | 12 --- packages/eslint-config/configs/esnext.js | 8 -- packages/eslint-config/configs/rules/es5.js | 84 ------------------ .../eslint-config/configs/rules/esnext.js | 66 -------------- packages/eslint-plugin/.npmrc | 1 + packages/eslint-plugin/README.md | 48 ++++++++++ packages/eslint-plugin/configs/custom.js | 19 ++++ .../index.js => eslint-plugin/configs/es5.js} | 87 ++++--------------- packages/eslint-plugin/configs/esnext.js | 36 ++++++++ packages/eslint-plugin/configs/index.js | 1 + packages/eslint-plugin/configs/jsx-a11y.js | 17 ++++ packages/eslint-plugin/configs/react.js | 28 ++++++ packages/eslint-plugin/configs/recommended.js | 16 ++++ packages/eslint-plugin/index.js | 3 + .../package.json | 10 +-- 20 files changed, 206 insertions(+), 277 deletions(-) delete mode 100644 packages/eslint-config/README.md delete mode 100644 packages/eslint-config/configs/es5.js delete mode 100644 packages/eslint-config/configs/esnext.js delete mode 100644 packages/eslint-config/configs/rules/es5.js delete mode 100644 packages/eslint-config/configs/rules/esnext.js create mode 100644 packages/eslint-plugin/.npmrc create mode 100644 packages/eslint-plugin/README.md create mode 100644 packages/eslint-plugin/configs/custom.js rename packages/{eslint-config/index.js => eslint-plugin/configs/es5.js} (52%) create mode 100644 packages/eslint-plugin/configs/esnext.js create mode 100644 packages/eslint-plugin/configs/index.js create mode 100644 packages/eslint-plugin/configs/jsx-a11y.js create mode 100644 packages/eslint-plugin/configs/react.js create mode 100644 packages/eslint-plugin/configs/recommended.js create mode 100644 packages/eslint-plugin/index.js rename packages/{eslint-config => eslint-plugin}/package.json (72%) diff --git a/.eslintrc.js b/.eslintrc.js index feced45620657..af4bda32427a9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -19,7 +19,7 @@ const majorMinorRegExp = escapeRegExp( version.replace( /\.\d+$/, '' ) ) + '(\\. module.exports = { root: true, extends: [ - '@wordpress/eslint-config', + 'plugin:@wordpress/eslint-plugin/recommended', 'plugin:jest/recommended', ], rules: { diff --git a/docs/manifest.json b/docs/manifest.json index e8925eec9fb9c..dbf955bd2d247 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -498,9 +498,9 @@ "parent": "packages" }, { - "title": "@wordpress/eslint-config", - "slug": "packages-eslint-config", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/eslint-config/README.md", + "title": "@wordpress/eslint-plugin", + "slug": "packages-eslint-plugin", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/eslint-plugin/README.md", "parent": "packages" }, { diff --git a/package-lock.json b/package-lock.json index 086978a9ea5c9..ea80146bc3857 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2590,13 +2590,14 @@ "@babel/runtime": "^7.0.0" } }, - "@wordpress/eslint-config": { - "version": "file:packages/eslint-config", + "@wordpress/eslint-plugin": { + "version": "file:packages/eslint-plugin", "dev": true, "requires": { "babel-eslint": "^8.0.3", "eslint-plugin-jsx-a11y": "6.0.2", - "eslint-plugin-react": "7.7.0" + "eslint-plugin-react": "7.7.0", + "requireindex": "^1.2.0" } }, "@wordpress/format-library": { @@ -18668,6 +18669,12 @@ } } }, + "requireindex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", + "integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==", + "dev": true + }, "resolve": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", diff --git a/package.json b/package.json index c9a2723cb9008..6493f7d59e1d2 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "@wordpress/babel-preset-default": "file:packages/babel-preset-default", "@wordpress/browserslist-config": "file:packages/browserslist-config", "@wordpress/custom-templated-path-webpack-plugin": "file:packages/custom-templated-path-webpack-plugin", - "@wordpress/eslint-config": "file:packages/eslint-config", + "@wordpress/eslint-plugin": "file:packages/eslint-plugin", "@wordpress/jest-console": "file:packages/jest-console", "@wordpress/jest-preset-default": "file:packages/jest-preset-default", "@wordpress/library-export-default-webpack-plugin": "file:packages/library-export-default-webpack-plugin", diff --git a/packages/eslint-config/README.md b/packages/eslint-config/README.md deleted file mode 100644 index 5f2b003e765c5..0000000000000 --- a/packages/eslint-config/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# ESLint Config - -[ESLint](https://eslint.org/) config for WordPress development. - -## Installation - -Install the module - -```bash -npm install @wordpress/eslint-config --save-dev -``` - -### Usage - -Next, extend the configuration from your project's `.eslintrc` file: - -```json -"extends": "@wordpress/eslint-config" -``` - -Refer to the [ESLint documentation on Shareable Configs](http://eslint.org/docs/developer-guide/shareable-configs) for more information. - - -

Code is Poetry.

diff --git a/packages/eslint-config/configs/es5.js b/packages/eslint-config/configs/es5.js deleted file mode 100644 index 2e56e7a2f5fcd..0000000000000 --- a/packages/eslint-config/configs/es5.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * The original version of this file is based on WordPress ESLint rules and shared configs: - * https://github.com/WordPress-Coding-Standards/eslint-plugin-wordpress. - */ - -module.exports = { - env: { - es6: true, - }, - - rules: require( './rules/esnext' ), -}; diff --git a/packages/eslint-config/configs/esnext.js b/packages/eslint-config/configs/esnext.js deleted file mode 100644 index 3b3a80d7d4cd8..0000000000000 --- a/packages/eslint-config/configs/esnext.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * The original version of this file is based on WordPress ESLint rules and shared configs: - * https://github.com/WordPress-Coding-Standards/eslint-plugin-wordpress. - */ - -module.exports = { - rules: require( './rules/es5' ), -}; diff --git a/packages/eslint-config/configs/rules/es5.js b/packages/eslint-config/configs/rules/es5.js deleted file mode 100644 index 61e3e01c343f1..0000000000000 --- a/packages/eslint-config/configs/rules/es5.js +++ /dev/null @@ -1,84 +0,0 @@ -module.exports = { - // Possible Errors - // Disallow assignment in conditional expressions - 'no-cond-assign': [ 'error', 'except-parens' ], - // Disallow irregular whitespace outside of strings and comments - 'no-irregular-whitespace': 'error', - // Best Practices - // Specify curly brace conventions for all control statements - curly: [ 'error', 'all' ], - // Encourages use of dot notation whenever possible - 'dot-notation': [ 'error', { - allowKeywords: true, - allowPattern: '^[a-z]+(_[a-z]+)+$', - } ], - // Disallow use of multiline strings - 'no-multi-str': 'error', - // Disallow use of the with statement - 'no-with': 'error', - // Requires to declare all vars on top of their containing scope - 'vars-on-top': 'error', - // Require immediate function invocation to be wrapped in parentheses - 'wrap-iife': 'error', - // Require or disallow Yoda conditions - yoda: [ 'error', 'always' ], - // Strict Mode - // Variables - // Stylistic Issues - // Enforce spacing inside array brackets - 'array-bracket-spacing': [ 'error', 'always' ], - // Enforce one true brace style - 'brace-style': 'error', - // Require camel case names - camelcase: [ 'error', { - properties: 'always', - } ], - // Disallow or enforce trailing commas - 'comma-dangle': [ 'error', 'never' ], - // Enforce spacing before and after comma - 'comma-spacing': 'error', - // Enforce one true comma style - 'comma-style': [ 'error', 'last' ], - // Enforce newline at the end of file, with no multiple empty lines - 'eol-last': 'error', - // Enforces spacing between keys and values in object literal properties - 'key-spacing': [ 'error', { - beforeColon: false, - afterColon: true, - } ], - // Enforce spacing before and after keywords - 'keyword-spacing': 'error', - // Disallow mixed "LF" and "CRLF" as linebreaks - 'linebreak-style': [ 'error', 'unix' ], - // Enforces empty lines around comments - 'lines-around-comment': [ 'error', { - beforeLineComment: true, - } ], - // Disallow mixed spaces and tabs for indentation - 'no-mixed-spaces-and-tabs': 'error', - // Disallow multiple empty lines - 'no-multiple-empty-lines': 'error', - // Disallow trailing whitespace at the end of lines - 'no-trailing-spaces': 'error', - // Require or disallow an newline around variable declarations - 'one-var-declaration-per-line': [ 'error', 'initializations' ], - // Enforce operators to be placed before or after line breaks - 'operator-linebreak': [ 'error', 'after' ], - // Specify whether backticks, double or single quotes should be used - quotes: [ 'error', 'single' ], - // Require or disallow use of semicolons instead of ASI - semi: [ 'error', 'always' ], - // Require or disallow space before blocks - 'space-before-blocks': [ 'error', 'always' ], - // Require or disallow space before function opening parenthesis - 'space-before-function-paren': [ 'error', 'never' ], - // Require or disallow space before blocks - 'space-in-parens': [ 'error', 'always', { exceptions: [ '{}', '[]' ] } ], - // Require spaces around operators - 'space-infix-ops': 'error', - // Require or disallow spaces before/after unary operators (words on by default, nonwords) - 'space-unary-ops': [ 'error', { - overrides: { '!': true }, - } ], - // Legacy -}; diff --git a/packages/eslint-config/configs/rules/esnext.js b/packages/eslint-config/configs/rules/esnext.js deleted file mode 100644 index dcfc27f06554f..0000000000000 --- a/packages/eslint-config/configs/rules/esnext.js +++ /dev/null @@ -1,66 +0,0 @@ -// see https://eslint.org/docs/rules/#ecmascript-6 -// -module.exports = { - // require braces around arrow function bodies - 'arrow-body-style': 'off', - // require parentheses around arrow function arguments - 'arrow-parens': 'off', - // enforce consistent spacing before and after the arrow in arrow functions - 'arrow-spacing': 'off', - // require super() calls in constructors - 'constructor-super': 'error', - // enforce consistent spacing around * operators in generator functions - 'generator-star-spacing': 'off', - // disallow reassigning class members - 'no-class-assign': 'off', - // disallow arrow functions where they could be confused with comparisons - 'no-confusing-arrow': 'off', - // disallow reassigning `const` variables - 'no-const-assign': 'error', - // disallow duplicate class members - 'no-dupe-class-members': 'error', - // disallow duplicate module imports - 'no-duplicate-imports': 'error', - // disallow `new` operators with the `Symbol` object - 'no-new-symbol': 'off', - // disallow specified modules when loaded by `import` - 'no-restricted-imports': 'off', - // disallow `this`/`super` before calling `super()` in constructors - 'no-this-before-super': 'off', - // disallow unnecessary computed property keys in object literals - 'no-useless-computed-key': 'error', - // disallow unnecessary constructors - 'no-useless-constructor': 'error', - // disallow renaming import, export, and destructured assignments to the same name - 'no-useless-rename': 'off', - // require `let` or `const` instead of `var` - 'no-var': 'error', - // require or disallow method and property shorthand syntax for object literals - 'object-shorthand': 'off', - // require arrow functions as callbacks - 'prefer-arrow-callback': 'off', - // require `const` declarations for variables that are never reassigned after declared - 'prefer-const': 'error', - // require destructuring from arrays and/or objects - 'prefer-destructuring': 'off', - // disallow `parseInt()` and `Number.parseInt()` in favor of binary, octal, and hexadecimal literals - 'prefer-numeric-literals': 'off', - // require rest parameters instead of `arguments` - 'prefer-rest-params': 'off', - // require spread operators instead of `.apply()` - 'prefer-spread': 'off', - // require template literals instead of string concatenation - 'prefer-template': 'off', - // require generator functions to contain `yield` - 'require-yield': 'off', - // enforce spacing between rest and spread operators and their expressions - 'rest-spread-spacing': 'off', - // enforce sorted import declarations within modules - 'sort-imports': 'off', - // require symbol descriptions - 'symbol-description': 'off', - // require or disallow spacing around embedded expressions of template strings - 'template-curly-spacing': [ 'error', 'always' ], - // require or disallow spacing around the `*` in `yield*` expressions - 'yield-star-spacing': 'off', -}; diff --git a/packages/eslint-plugin/.npmrc b/packages/eslint-plugin/.npmrc new file mode 100644 index 0000000000000..43c97e719a5a8 --- /dev/null +++ b/packages/eslint-plugin/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md new file mode 100644 index 0000000000000..fd516bbf622b4 --- /dev/null +++ b/packages/eslint-plugin/README.md @@ -0,0 +1,48 @@ +# ESLint Plugin + +[ESLint](https://eslint.org/) plugin including configurations and custom rules for WordPress development. + +## Installation + +Install the module + +```bash +npm install @wordpress/eslint-plugin --save-dev +``` + +### Usage + +To opt-in to the default configuration, extend your own project's `.eslintrc` file: + +```json +{ + "extends": [ "plugin:@wordpress/eslint-plugin/recommended" ] +} +``` + +Refer to the [ESLint documentation on Shareable Configs](http://eslint.org/docs/developer-guide/shareable-configs) for more information. + +The `recommended` preset will include rules governing an ES2015+ environment, and includes rules from the [`eslint-plugin-jsx-a11y`](https://github.com/evcohen/eslint-plugin-jsx-a11y) and [`eslint-plugin-react`](https://github.com/yannickcr/eslint-plugin-react) projects. + +#### Rulesets + +Alternatively, you can opt-in to only the more granular rulesets offered by the plugin. These include: + +- `es5` +- `esnext` +- `jsx-a11y` +- `react` + +For example, if your project does not use React, you could consider extending including only the ESNext rules in your project using the following `extends` definition: + +```json +{ + "extends": [ "plugin:@wordpress/eslint-plugin/esnext" ] +} +``` + +These rules can be used additively, so you could extend both `esnext` and `custom` rulesets, but omit the `react` and `jsx-a11y` configurations. + +The granular rulesets will not define any environment globals. As such, if they are required for your project, you will need to define them yourself. + +

Code is Poetry.

diff --git a/packages/eslint-plugin/configs/custom.js b/packages/eslint-plugin/configs/custom.js new file mode 100644 index 0000000000000..0318c85c324c9 --- /dev/null +++ b/packages/eslint-plugin/configs/custom.js @@ -0,0 +1,19 @@ +module.exports = { + rules: { + 'no-restricted-syntax': [ + 'error', + { + selector: 'CallExpression[callee.name=/^__|_n|_x$/]:not([arguments.0.type=/^Literal|BinaryExpression$/])', + message: 'Translate function arguments must be string literals.', + }, + { + selector: 'CallExpression[callee.name=/^_n|_x$/]:not([arguments.1.type=/^Literal|BinaryExpression$/])', + message: 'Translate function arguments must be string literals.', + }, + { + selector: 'CallExpression[callee.name=_nx]:not([arguments.2.type=/^Literal|BinaryExpression$/])', + message: 'Translate function arguments must be string literals.', + }, + ], + }, +}; diff --git a/packages/eslint-config/index.js b/packages/eslint-plugin/configs/es5.js similarity index 52% rename from packages/eslint-config/index.js rename to packages/eslint-plugin/configs/es5.js index aa02fdc33ff9d..167e542ef69a6 100644 --- a/packages/eslint-config/index.js +++ b/packages/eslint-plugin/configs/es5.js @@ -1,56 +1,26 @@ module.exports = { - parser: 'babel-eslint', - extends: [ - './configs/es5.js', - './configs/esnext.js', - 'plugin:react/recommended', - 'plugin:jsx-a11y/recommended', - ], - env: { - node: true, - }, - parserOptions: { - sourceType: 'module', - ecmaFeatures: { - jsx: true, - }, - }, - globals: { - window: true, - document: true, - }, - plugins: [ - 'react', - 'jsx-a11y', - ], rules: { 'array-bracket-spacing': [ 'error', 'always' ], - 'arrow-parens': [ 'error', 'always' ], - 'arrow-spacing': 'error', 'brace-style': [ 'error', '1tbs' ], - camelcase: [ 'error', { properties: 'never' } ], + camelcase: [ 'error', { + properties: 'never', + } ], 'comma-dangle': [ 'error', 'always-multiline' ], 'comma-spacing': 'error', - 'comma-style': 'error', - 'computed-property-spacing': [ 'error', 'always' ], + 'comma-style': [ 'error', 'last' ], + curly: [ 'error', 'all' ], 'dot-notation': 'error', 'eol-last': 'error', eqeqeq: 'error', 'func-call-spacing': 'error', indent: [ 'error', 'tab', { SwitchCase: 1 } ], - 'jsx-a11y/label-has-for': [ 'error', { - required: 'id', - } ], - 'jsx-a11y/media-has-caption': 'off', - 'jsx-a11y/no-noninteractive-tabindex': 'off', - 'jsx-a11y/role-has-required-aria-props': 'off', - 'jsx-quotes': 'error', 'key-spacing': 'error', 'keyword-spacing': 'error', - 'lines-around-comment': 'off', + 'linebreak-style': [ 'error', 'unix' ], 'no-alert': 'error', 'no-bitwise': 'error', 'no-caller': 'error', + 'no-cond-assign': [ 'error', 'except-parens' ], 'no-console': 'error', 'no-debugger': 'error', 'no-dupe-args': 'error', @@ -60,31 +30,18 @@ module.exports = { 'no-eval': 'error', 'no-extra-semi': 'error', 'no-fallthrough': 'error', + 'no-irregular-whitespace': 'error', 'no-lonely-if': 'error', + 'no-multi-str': 'error', 'no-mixed-operators': 'error', 'no-mixed-spaces-and-tabs': 'error', 'no-multiple-empty-lines': [ 'error', { max: 1 } ], 'no-multi-spaces': 'error', - 'no-multi-str': 'off', 'no-negated-in-lhs': 'error', 'no-nested-ternary': 'error', 'no-redeclare': 'error', - 'no-restricted-syntax': [ - 'error', - { - selector: 'CallExpression[callee.name=/^__|_n|_x$/]:not([arguments.0.type=/^Literal|BinaryExpression$/])', - message: 'Translate function arguments must be string literals.', - }, - { - selector: 'CallExpression[callee.name=/^_n|_x$/]:not([arguments.1.type=/^Literal|BinaryExpression$/])', - message: 'Translate function arguments must be string literals.', - }, - { - selector: 'CallExpression[callee.name=_nx]:not([arguments.2.type=/^Literal|BinaryExpression$/])', - message: 'Translate function arguments must be string literals.', - }, - ], 'no-shadow': 'error', + 'no-trailing-spaces': 'error', 'no-undef': 'error', 'no-undef-init': 'error', 'no-unreachable': 'error', @@ -93,23 +50,13 @@ module.exports = { 'no-unused-vars': 'error', 'no-useless-return': 'error', 'no-whitespace-before-property': 'error', + 'no-with': 'error', 'object-curly-spacing': [ 'error', 'always' ], + 'one-var-declaration-per-line': [ 'error', 'initializations' ], + 'operator-linebreak': [ 'error', 'after' ], 'padded-blocks': [ 'error', 'never' ], - quotes: [ 'error', 'single', { allowTemplateLiterals: true, avoidEscape: true } ], 'quote-props': [ 'error', 'as-needed' ], - 'react/display-name': 'off', - 'react/jsx-curly-spacing': [ 'error', { - when: 'always', - children: true, - } ], - 'react/jsx-equals-spacing': 'error', - 'react/jsx-indent': [ 'error', 'tab' ], - 'react/jsx-indent-props': [ 'error', 'tab' ], - 'react/jsx-key': 'error', - 'react/jsx-tag-spacing': 'error', - 'react/no-children-prop': 'off', - 'react/prop-types': 'off', - 'react/react-in-jsx-scope': 'off', + quotes: [ 'error', 'single', { avoidEscape: true } ], semi: 'error', 'semi-spacing': 'error', 'space-before-blocks': [ 'error', 'always' ], @@ -119,11 +66,10 @@ module.exports = { asyncArrow: 'always', } ], 'space-in-parens': [ 'error', 'always' ], - 'space-infix-ops': [ 'error', { int32Hint: false } ], + 'space-infix-ops': 'error', 'space-unary-ops': [ 'error', { overrides: { '!': true, - yield: true, }, } ], 'valid-jsdoc': [ 'error', { @@ -151,6 +97,7 @@ module.exports = { requireReturn: false, } ], 'valid-typeof': 'error', - yoda: 'off', + 'vars-on-top': 'error', + 'wrap-iife': 'error', }, }; diff --git a/packages/eslint-plugin/configs/esnext.js b/packages/eslint-plugin/configs/esnext.js new file mode 100644 index 0000000000000..7e01f959fde72 --- /dev/null +++ b/packages/eslint-plugin/configs/esnext.js @@ -0,0 +1,36 @@ +module.exports = { + env: { + es6: true, + }, + extends: [ + require.resolve( './es5.js' ), + ], + parserOptions: { + sourceType: 'module', + }, + rules: { + // Disable ES5-specific (extended from ES5) + 'vars-on-top': 'off', + + // Enable ESNext-specific + 'arrow-parens': [ 'error', 'always' ], + 'arrow-spacing': 'error', + 'computed-property-spacing': [ 'error', 'always' ], + 'constructor-super': 'error', + 'no-const-assign': 'error', + 'no-dupe-class-members': 'error', + 'no-duplicate-imports': 'error', + 'no-useless-computed-key': 'error', + 'no-useless-constructor': 'error', + 'no-var': 'error', + 'prefer-const': 'error', + quotes: [ 'error', 'single', { allowTemplateLiterals: true, avoidEscape: true } ], + 'space-unary-ops': [ 'error', { + overrides: { + '!': true, + yield: true, + }, + } ], + 'template-curly-spacing': [ 'error', 'always' ], + }, +}; diff --git a/packages/eslint-plugin/configs/index.js b/packages/eslint-plugin/configs/index.js new file mode 100644 index 0000000000000..035c09a8fa767 --- /dev/null +++ b/packages/eslint-plugin/configs/index.js @@ -0,0 +1 @@ +module.exports = require( 'requireindex' )( __dirname ); diff --git a/packages/eslint-plugin/configs/jsx-a11y.js b/packages/eslint-plugin/configs/jsx-a11y.js new file mode 100644 index 0000000000000..38dd0ed2a3bf0 --- /dev/null +++ b/packages/eslint-plugin/configs/jsx-a11y.js @@ -0,0 +1,17 @@ +module.exports = { + extends: [ + 'plugin:jsx-a11y/recommended', + ], + plugins: [ + 'jsx-a11y', + ], + rules: { + 'jsx-a11y/label-has-for': [ 'error', { + required: 'id', + } ], + 'jsx-a11y/media-has-caption': 'off', + 'jsx-a11y/no-noninteractive-tabindex': 'off', + 'jsx-a11y/role-has-required-aria-props': 'off', + 'jsx-quotes': 'error', + }, +}; diff --git a/packages/eslint-plugin/configs/react.js b/packages/eslint-plugin/configs/react.js new file mode 100644 index 0000000000000..05c09b7e16809 --- /dev/null +++ b/packages/eslint-plugin/configs/react.js @@ -0,0 +1,28 @@ +module.exports = { + extends: [ + 'plugin:react/recommended', + ], + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + plugins: [ + 'react', + ], + rules: { + 'react/display-name': 'off', + 'react/jsx-curly-spacing': [ 'error', { + when: 'always', + children: true, + } ], + 'react/jsx-equals-spacing': 'error', + 'react/jsx-indent': [ 'error', 'tab' ], + 'react/jsx-indent-props': [ 'error', 'tab' ], + 'react/jsx-key': 'error', + 'react/jsx-tag-spacing': 'error', + 'react/no-children-prop': 'off', + 'react/prop-types': 'off', + 'react/react-in-jsx-scope': 'off', + }, +}; diff --git a/packages/eslint-plugin/configs/recommended.js b/packages/eslint-plugin/configs/recommended.js new file mode 100644 index 0000000000000..370355bd1d6ad --- /dev/null +++ b/packages/eslint-plugin/configs/recommended.js @@ -0,0 +1,16 @@ +module.exports = { + parser: 'babel-eslint', + extends: [ + require.resolve( './jsx-a11y.js' ), + require.resolve( './react.js' ), + require.resolve( './custom.js' ), + require.resolve( './esnext.js' ), + ], + env: { + node: true, + }, + globals: { + window: true, + document: true, + }, +}; diff --git a/packages/eslint-plugin/index.js b/packages/eslint-plugin/index.js new file mode 100644 index 0000000000000..0933aba1cc826 --- /dev/null +++ b/packages/eslint-plugin/index.js @@ -0,0 +1,3 @@ +module.exports = { + configs: require( './configs' ), +}; diff --git a/packages/eslint-config/package.json b/packages/eslint-plugin/package.json similarity index 72% rename from packages/eslint-config/package.json rename to packages/eslint-plugin/package.json index 8d5c6c93c35ee..7bce174d05838 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-plugin/package.json @@ -1,15 +1,14 @@ { - "name": "@wordpress/eslint-config", - "private": true, + "name": "@wordpress/eslint-plugin", "version": "1.0.0-alpha.0", - "description": "ESLint config for WordPress development.", + "description": "ESLint plugin for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", "keywords": [ "wordpress", "eslint" ], - "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/eslint-config/README.md", + "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/eslint-plugin/README.md", "repository": { "type": "git", "url": "https://github.com/WordPress/gutenberg.git" @@ -20,7 +19,8 @@ "dependencies": { "babel-eslint": "^8.0.3", "eslint-plugin-jsx-a11y": "6.0.2", - "eslint-plugin-react": "7.7.0" + "eslint-plugin-react": "7.7.0", + "requireindex": "^1.2.0" }, "publishConfig": { "access": "public" From 64045784bac1b29a243ba8beae33c31e887c2fe3 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Wed, 12 Dec 2018 19:30:32 +0100 Subject: [PATCH 012/691] 4.7 (#12819) * Bump plugin version to 4.7.0 * chore(release): publish - @wordpress/annotations@1.0.4 - @wordpress/api-fetch@2.2.6 - @wordpress/block-library@2.2.10 - @wordpress/block-serialization-default-parser@2.0.2 - @wordpress/block-serialization-spec-parser@2.0.2 - @wordpress/blocks@6.0.4 - @wordpress/components@7.0.4 - @wordpress/core-data@2.0.15 - @wordpress/data@4.1.0 - @wordpress/date@3.0.1 - @wordpress/edit-post@3.1.5 - @wordpress/editor@9.0.5 - @wordpress/eslint-plugin@1.0.0 - @wordpress/format-library@1.2.8 - @wordpress/html-entities@2.0.4 - @wordpress/list-reusable-blocks@1.1.17 - @wordpress/notices@1.1.1 - @wordpress/nux@3.0.5 - @wordpress/rich-text@3.0.3 - @wordpress/url@2.3.2 - @wordpress/viewport@2.0.13 * Update changelogs after 4.7 package releases --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- packages/annotations/CHANGELOG.md | 2 ++ packages/annotations/package.json | 2 +- packages/api-fetch/CHANGELOG.md | 2 ++ packages/api-fetch/package.json | 2 +- packages/block-library/CHANGELOG.md | 2 ++ packages/block-library/package.json | 2 +- packages/block-serialization-default-parser/CHANGELOG.md | 2 ++ packages/block-serialization-default-parser/package.json | 2 +- packages/block-serialization-spec-parser/CHANGELOG.md | 2 ++ packages/block-serialization-spec-parser/package.json | 2 +- packages/blocks/CHANGELOG.md | 2 ++ packages/blocks/package.json | 2 +- packages/components/CHANGELOG.md | 2 ++ packages/components/package.json | 2 +- packages/core-data/CHANGELOG.md | 2 ++ packages/core-data/package.json | 2 +- packages/data/CHANGELOG.md | 2 +- packages/data/package.json | 2 +- packages/date/CHANGELOG.md | 2 ++ packages/date/package.json | 2 +- packages/edit-post/CHANGELOG.md | 2 +- packages/edit-post/package.json | 2 +- packages/editor/CHANGELOG.md | 2 +- packages/editor/package.json | 2 +- packages/eslint-plugin/CHANGELOG.md | 5 +++++ packages/eslint-plugin/package.json | 2 +- packages/format-library/CHANGELOG.md | 2 ++ packages/format-library/package.json | 2 +- packages/html-entities/CHANGELOG.md | 2 ++ packages/html-entities/package.json | 3 +-- packages/list-reusable-blocks/CHANGELOG.md | 2 ++ packages/list-reusable-blocks/package.json | 2 +- packages/notices/CHANGELOG.md | 2 ++ packages/notices/package.json | 2 +- packages/nux/CHANGELOG.md | 2 ++ packages/nux/package.json | 2 +- packages/rich-text/CHANGELOG.md | 2 +- packages/rich-text/package.json | 2 +- packages/url/CHANGELOG.md | 2 ++ packages/url/package.json | 2 +- packages/viewport/CHANGELOG.md | 2 ++ packages/viewport/package.json | 2 +- 45 files changed, 65 insertions(+), 29 deletions(-) create mode 100644 packages/eslint-plugin/CHANGELOG.md diff --git a/gutenberg.php b/gutenberg.php index 9f9d7b183aeb5..62215b96d48d1 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,7 +3,7 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the new block editor in core. - * Version: 4.7.0-rc.1 + * Version: 4.7.0 * Author: Gutenberg Team * * @package gutenberg diff --git a/package-lock.json b/package-lock.json index ea80146bc3857..1867b41831f79 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "4.7.0-rc.1", + "version": "4.7.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 6493f7d59e1d2..03a6bdd8f7616 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "4.7.0-rc.1", + "version": "4.7.0", "private": true, "description": "A new WordPress editor experience", "repository": "git+https://github.com/WordPress/gutenberg.git", diff --git a/packages/annotations/CHANGELOG.md b/packages/annotations/CHANGELOG.md index edceba997fe67..9d8994d0b4861 100644 --- a/packages/annotations/CHANGELOG.md +++ b/packages/annotations/CHANGELOG.md @@ -1,3 +1,5 @@ +## 1.0.4 (2018-12-12) + ## 1.0.3 (2018-11-21) ## 1.0.2 (2018-11-20) diff --git a/packages/annotations/package.json b/packages/annotations/package.json index 5a73fb5e31ae4..3e909c7a0852c 100644 --- a/packages/annotations/package.json +++ b/packages/annotations/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/annotations", - "version": "1.0.3", + "version": "1.0.4", "description": "Annotate content in the Gutenberg editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/api-fetch/CHANGELOG.md b/packages/api-fetch/CHANGELOG.md index 912d833f3b909..b4c58ed5d9e1a 100644 --- a/packages/api-fetch/CHANGELOG.md +++ b/packages/api-fetch/CHANGELOG.md @@ -1,3 +1,5 @@ +## 2.2.6 (2018-12-12) + ## 2.2.5 (2018-11-20) ## 2.2.4 (2018-11-15) diff --git a/packages/api-fetch/package.json b/packages/api-fetch/package.json index 3cb6fb3c22979..0c3ab7e8e9bbc 100644 --- a/packages/api-fetch/package.json +++ b/packages/api-fetch/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/api-fetch", - "version": "2.2.5", + "version": "2.2.6", "description": "Utility to make WordPress REST API requests.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-library/CHANGELOG.md b/packages/block-library/CHANGELOG.md index e27ca871be384..f784c887fb110 100644 --- a/packages/block-library/CHANGELOG.md +++ b/packages/block-library/CHANGELOG.md @@ -1,3 +1,5 @@ +## 2.2.10 (2018-12-12) + ## 2.2.9 (2018-11-30) ## 2.2.8 (2018-11-30) diff --git a/packages/block-library/package.json b/packages/block-library/package.json index 5d3e510bbfe93..3f81dcf5dfd3d 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-library", - "version": "2.2.9", + "version": "2.2.10", "description": "Block library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-serialization-default-parser/CHANGELOG.md b/packages/block-serialization-default-parser/CHANGELOG.md index 6c4f4c53b0645..aaa75a2df97c0 100644 --- a/packages/block-serialization-default-parser/CHANGELOG.md +++ b/packages/block-serialization-default-parser/CHANGELOG.md @@ -1,3 +1,5 @@ +## 2.0.2 (2018-12-12) + ## 2.0.1 (2018-11-30) ## 2.0.0 (2018-11-12) diff --git a/packages/block-serialization-default-parser/package.json b/packages/block-serialization-default-parser/package.json index 8cf5725dfd2d2..b5482f3d1efd4 100644 --- a/packages/block-serialization-default-parser/package.json +++ b/packages/block-serialization-default-parser/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-serialization-default-parser", - "version": "2.0.1", + "version": "2.0.2", "description": "Block serialization specification parser for WordPress posts.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-serialization-spec-parser/CHANGELOG.md b/packages/block-serialization-spec-parser/CHANGELOG.md index 12ffab6521d47..c2db52c417908 100644 --- a/packages/block-serialization-spec-parser/CHANGELOG.md +++ b/packages/block-serialization-spec-parser/CHANGELOG.md @@ -1,3 +1,5 @@ +## 2.0.2 (2018-12-12) + ## 2.0.1 (2018-11-30) ## 2.0.0 (2018-11-12) diff --git a/packages/block-serialization-spec-parser/package.json b/packages/block-serialization-spec-parser/package.json index 7739f0bf04177..d7bafbb41539c 100644 --- a/packages/block-serialization-spec-parser/package.json +++ b/packages/block-serialization-spec-parser/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-serialization-spec-parser", - "version": "2.0.1", + "version": "2.0.2", "description": "Block serialization specification parser for WordPress posts.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/blocks/CHANGELOG.md b/packages/blocks/CHANGELOG.md index 33ca7db882fbd..e268a8260c14f 100644 --- a/packages/blocks/CHANGELOG.md +++ b/packages/blocks/CHANGELOG.md @@ -1,3 +1,5 @@ +## 6.0.4 (2018-12-12) + ## 6.0.3 (2018-11-30) ## 6.0.2 (2018-11-21) diff --git a/packages/blocks/package.json b/packages/blocks/package.json index af7151b58d806..afe971954c18c 100644 --- a/packages/blocks/package.json +++ b/packages/blocks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/blocks", - "version": "6.0.3", + "version": "6.0.4", "description": "Block API for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index fb93a60cd4712..8dfd644e15ea0 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -1,3 +1,5 @@ +## 7.0.4 (2018-12-12) + ## 7.0.3 (2018-11-30) ## 7.0.2 (2018-11-22) diff --git a/packages/components/package.json b/packages/components/package.json index 9ad1244df0dff..2425f1bf79dcb 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/components", - "version": "7.0.3", + "version": "7.0.4", "description": "UI components for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/core-data/CHANGELOG.md b/packages/core-data/CHANGELOG.md index 98ed42a5473f0..3594be36a67ec 100644 --- a/packages/core-data/CHANGELOG.md +++ b/packages/core-data/CHANGELOG.md @@ -1,3 +1,5 @@ +## 2.0.15 (2018-12-12) + ## 2.0.14 (2018-11-20) ## 2.0.13 (2018-11-15) diff --git a/packages/core-data/package.json b/packages/core-data/package.json index 4431099461900..316ac6907f9ab 100644 --- a/packages/core-data/package.json +++ b/packages/core-data/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/core-data", - "version": "2.0.14", + "version": "2.0.15", "description": "Access to and manipulation of core WordPress entities.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/data/CHANGELOG.md b/packages/data/CHANGELOG.md index d496bb6162e23..8b90a5d7c526a 100644 --- a/packages/data/CHANGELOG.md +++ b/packages/data/CHANGELOG.md @@ -1,4 +1,4 @@ -## 4.1.0 (Unreleased) +## 4.1.0 (2018-12-12) ### New Feature diff --git a/packages/data/package.json b/packages/data/package.json index a547a316872d2..825ddb8f71295 100644 --- a/packages/data/package.json +++ b/packages/data/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/data", - "version": "4.0.1", + "version": "4.1.0", "description": "Data module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/date/CHANGELOG.md b/packages/date/CHANGELOG.md index e6aa964c70518..54bee67c917a9 100644 --- a/packages/date/CHANGELOG.md +++ b/packages/date/CHANGELOG.md @@ -1,3 +1,5 @@ +## 3.0.1 (2018-12-12) + ## 3.0.0 (2018-11-15) ### Breaking Changes diff --git a/packages/date/package.json b/packages/date/package.json index 847cdf2762ad4..7378306fff28c 100644 --- a/packages/date/package.json +++ b/packages/date/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/date", - "version": "3.0.0", + "version": "3.0.1", "description": "Date module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/edit-post/CHANGELOG.md b/packages/edit-post/CHANGELOG.md index e4b0c1ad32c68..092061f4501ef 100644 --- a/packages/edit-post/CHANGELOG.md +++ b/packages/edit-post/CHANGELOG.md @@ -1,4 +1,4 @@ -## 3.1.5 (Unreleased) +## 3.1.5 (2018-12-12) ### Bug Fixes - Fix saving WYSIWYG Meta Boxes diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json index d8d3f4802aa39..0e0e4d008fd95 100644 --- a/packages/edit-post/package.json +++ b/packages/edit-post/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-post", - "version": "3.1.4", + "version": "3.1.5", "description": "Edit Post module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/editor/CHANGELOG.md b/packages/editor/CHANGELOG.md index 69dbe834a4aeb..e06655f6738b6 100644 --- a/packages/editor/CHANGELOG.md +++ b/packages/editor/CHANGELOG.md @@ -1,4 +1,4 @@ -## 9.0.5 (Unreleased) +## 9.0.5 (2018-12-12) ### Bug Fixes diff --git a/packages/editor/package.json b/packages/editor/package.json index 2f2676ee36853..6850b40372ae2 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/editor", - "version": "9.0.4", + "version": "9.0.5", "description": "Building blocks for WordPress editors.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md new file mode 100644 index 0000000000000..3d6a36751ddd1 --- /dev/null +++ b/packages/eslint-plugin/CHANGELOG.md @@ -0,0 +1,5 @@ +## 1.0.0 (2018-12-12) + +### New Features + +- Initial release. diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 7bce174d05838..13fbae86bfd68 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/eslint-plugin", - "version": "1.0.0-alpha.0", + "version": "1.0.0", "description": "ESLint plugin for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/format-library/CHANGELOG.md b/packages/format-library/CHANGELOG.md index cab02a2104311..730914fc9ebd8 100644 --- a/packages/format-library/CHANGELOG.md +++ b/packages/format-library/CHANGELOG.md @@ -1,3 +1,5 @@ +## 1.2.8 (2018-12-12) + ## 1.2.7 (2018-11-30) ## 1.2.6 (2018-11-30) diff --git a/packages/format-library/package.json b/packages/format-library/package.json index 9c78a6ba2eedc..bd41d3742ddbc 100644 --- a/packages/format-library/package.json +++ b/packages/format-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/format-library", - "version": "1.2.7", + "version": "1.2.8", "description": "Format library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/html-entities/CHANGELOG.md b/packages/html-entities/CHANGELOG.md index bc270815c38c8..f9355f9f96865 100644 --- a/packages/html-entities/CHANGELOG.md +++ b/packages/html-entities/CHANGELOG.md @@ -1,3 +1,5 @@ +## 2.0.2 (2018-12-12) + ## 2.0.1 (2018-11-21) ## 2.0.0 (2018-09-05) diff --git a/packages/html-entities/package.json b/packages/html-entities/package.json index db944aa08cffe..94fccaa33295c 100644 --- a/packages/html-entities/package.json +++ b/packages/html-entities/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/html-entities", - "version": "2.0.3", + "version": "2.0.4", "description": "HTML entity utilities for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -20,7 +20,6 @@ }, "main": "build/index.js", "module": "build-module/index.js", - "react-native": "src/index", "dependencies": { "@babel/runtime": "^7.0.0" }, diff --git a/packages/list-reusable-blocks/CHANGELOG.md b/packages/list-reusable-blocks/CHANGELOG.md index 8e8526a7f98e6..dd73de16abca4 100644 --- a/packages/list-reusable-blocks/CHANGELOG.md +++ b/packages/list-reusable-blocks/CHANGELOG.md @@ -1,3 +1,5 @@ +## 1.1.17 (2018-12-12) + ## 1.1.16 (2018-11-30) ## 1.1.15 (2018-11-22) diff --git a/packages/list-reusable-blocks/package.json b/packages/list-reusable-blocks/package.json index 8c455e8a45812..2ddb7b4afdf53 100644 --- a/packages/list-reusable-blocks/package.json +++ b/packages/list-reusable-blocks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/list-reusable-blocks", - "version": "1.1.16", + "version": "1.1.17", "description": "Adding Export/Import support to the reusable blocks listing.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/notices/CHANGELOG.md b/packages/notices/CHANGELOG.md index 3699727ec708e..76f6ad9cadaaa 100644 --- a/packages/notices/CHANGELOG.md +++ b/packages/notices/CHANGELOG.md @@ -1,3 +1,5 @@ +## 1.1.1 (2018-12-12) + ## 1.1.0 (2018-11-20) ### New Feature diff --git a/packages/notices/package.json b/packages/notices/package.json index 5e642a6d7f273..c7b0b8c623294 100644 --- a/packages/notices/package.json +++ b/packages/notices/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/notices", - "version": "1.1.0", + "version": "1.1.1", "description": "State management for notices.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/nux/CHANGELOG.md b/packages/nux/CHANGELOG.md index ffe9ba7ca937e..41aaa82bc4ced 100644 --- a/packages/nux/CHANGELOG.md +++ b/packages/nux/CHANGELOG.md @@ -1,3 +1,5 @@ +## 3.0.5 (2018-12-12) + ## 3.0.4 (2018-11-30) ## 3.0.3 (2018-11-22) diff --git a/packages/nux/package.json b/packages/nux/package.json index 28dd725e40fae..c9db7b58c0fe0 100644 --- a/packages/nux/package.json +++ b/packages/nux/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/nux", - "version": "3.0.4", + "version": "3.0.5", "description": "NUX (New User eXperience) module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/rich-text/CHANGELOG.md b/packages/rich-text/CHANGELOG.md index aadcae6d60799..38b48d8af8766 100644 --- a/packages/rich-text/CHANGELOG.md +++ b/packages/rich-text/CHANGELOG.md @@ -1,4 +1,4 @@ -## 3.0.3 (Unreleased) +## 3.0.3 (2018-12-12) ### Internal diff --git a/packages/rich-text/package.json b/packages/rich-text/package.json index 4ae221a90812f..18b5352a78a91 100644 --- a/packages/rich-text/package.json +++ b/packages/rich-text/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/rich-text", - "version": "3.0.2", + "version": "3.0.3", "description": "Rich text value and manipulation API.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/url/CHANGELOG.md b/packages/url/CHANGELOG.md index 984fb0428eb28..1b5f99789bbf8 100644 --- a/packages/url/CHANGELOG.md +++ b/packages/url/CHANGELOG.md @@ -1,3 +1,5 @@ +## 2.3.2 (2018-12-12) + ## 2.3.1 (2018-11-20) ### Bug fixes diff --git a/packages/url/package.json b/packages/url/package.json index 197f2726a6413..43b919a19eb78 100644 --- a/packages/url/package.json +++ b/packages/url/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/url", - "version": "2.3.1", + "version": "2.3.2", "description": "WordPress URL utilities.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/viewport/CHANGELOG.md b/packages/viewport/CHANGELOG.md index 0dd9a1369466c..62418c8307350 100644 --- a/packages/viewport/CHANGELOG.md +++ b/packages/viewport/CHANGELOG.md @@ -1,3 +1,5 @@ +## 2.0.13 (2018-12-12) + ## 2.0.12 (2018-11-20) ## 2.0.11 (2018-11-15) diff --git a/packages/viewport/package.json b/packages/viewport/package.json index 527fca472cd3d..e74015bd91349 100644 --- a/packages/viewport/package.json +++ b/packages/viewport/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/viewport", - "version": "2.0.12", + "version": "2.0.13", "description": "Viewport module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", From ff0ca733a8ed862f19e6557d6702237a00a171bf Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak Date: Thu, 13 Dec 2018 10:36:26 -0800 Subject: [PATCH 013/691] Add a line to PR template for developer documentation (#12822) * Add a line to PR template for developer documentation Developer documentation is critical for making a platform extendable, developers need to know the proper way to extend a feature, or in a sense that does may not even exist. * Update .github/PULL_REQUEST_TEMPLATE.md Co-Authored-By: mkaz --- .github/PULL_REQUEST_TEMPLATE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index a968800c43fed..cb60c1c2f0354 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -19,3 +19,4 @@ - [ ] My code follows the WordPress code style. - [ ] My code follows the accessibility standards. - [ ] My code has proper inline documentation. +- [ ] I've included developer documentation if appropriate. From c7afa348457e63a21ceaf7853b3f96ce71ee124f Mon Sep 17 00:00:00 2001 From: Faisal Alvi Date: Fri, 14 Dec 2018 14:47:23 +0530 Subject: [PATCH 014/691] Grammatical mistakes & Missing spaces (#12643) * Grammatical mistakes & Missing spaces Fixing grammatical mistakes & adding the required space between 2 lines. * Update docs/designers-developers/developers/filters/block-filters.md Co-Authored-By: faisal-alvi * Update docs/designers-developers/developers/filters/block-filters.md Co-Authored-By: faisal-alvi --- .../developers/filters/block-filters.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/designers-developers/developers/filters/block-filters.md b/docs/designers-developers/developers/filters/block-filters.md index a8df36979dd1e..bfbd33936df38 100644 --- a/docs/designers-developers/developers/filters/block-filters.md +++ b/docs/designers-developers/developers/filters/block-filters.md @@ -115,7 +115,7 @@ wp.hooks.addFilter( #### `blocks.switchToBlockType.transformedBlock` -Used to filters an individual transform result from block transformation. All of the original blocks are passed, since transformations are many-to-many, not one-to-one. +Used to filter an individual transform result from block transformation. All of the original blocks are passed since transformations are many-to-many, not one-to-one. #### `blocks.getBlockAttributes` @@ -322,9 +322,9 @@ function my_plugin_block_categories( $categories, $post ) { add_filter( 'block_categories', 'my_plugin_block_categories', 10, 2 ); ``` -You can also display an icon with your block category by setting an `icon` attribute.The value can be the slug of a [WordPress Dashicon](https://developer.wordpress.org/resource/dashicons/). +You can also display an icon with your block category by setting an `icon` attribute. The value can be the slug of a [WordPress Dashicon](https://developer.wordpress.org/resource/dashicons/). -It is possible to set an SVG as the icon of the category if a custom icon is needed.To do so, the icon should be rendered and set on the frontend, so it can make use of WordPress SVG, allowing mobile compatibility and making the icon more accessible. +It is possible to set an SVG as the icon of the category if a custom icon is needed. To do so, the icon should be rendered and set on the frontend, so it can make use of WordPress SVG, allowing mobile compatibility and making the icon more accessible. To set an SVG icon for the category shown in the previous example, add the following example JavaScript code to the editor calling `wp.blocks.updateCategory` e.g: ```js From 1ac6ee74263cfa59fd45a74989b791697bf8cbe8 Mon Sep 17 00:00:00 2001 From: ramizmanked Date: Fri, 14 Dec 2018 14:48:24 +0530 Subject: [PATCH 015/691] Some content format issues (#12636) I've noticed some content formatting issues while going through this page. Hence, I'm approaching some suggestion from my side. --- packages/editor/README.md | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/packages/editor/README.md b/packages/editor/README.md index 56040081b11e6..0b908871df924 100644 --- a/packages/editor/README.md +++ b/packages/editor/README.md @@ -28,15 +28,11 @@ Individual blocks are handled by the `VisualBlock` component, which attaches eve ## Components -Because many blocks share the same complex behaviors, reusable components -are made available to simplify implementations of your block's `edit` function. +Because many blocks share the same complex behaviors, reusable components are made available to simplify implementations of your block's `edit` function. ### `BlockControls` -When returned by your block's `edit` implementation, renders a toolbar of icon -buttons. This is useful for block-level modifications to be made available when -a block is selected. For example, if your block supports alignment, you may -want to display alignment options in the selected block's toolbar. +When returned by your block's `edit` implementation, renders a toolbar of icon buttons. This is useful for block-level modifications to be made available when a block is selected. For example, if your block supports alignment, you may want to display alignment options in the selected block's toolbar. Example: @@ -70,32 +66,20 @@ Example: ); ``` -Note in this example that we render `AlignmentToolbar` as a child of the -`BlockControls` element. This is another pre-configured component you can use -to simplify block text alignment. +Note in this example that we render `AlignmentToolbar` as a child of the `BlockControls` element. This is another pre-configured component you can use to simplify block text alignment. -Alternatively, you can create your own toolbar controls by passing an array of -`controls` as a prop to the `BlockControls` component. Each control should be -an object with the following properties: +Alternatively, you can create your own toolbar controls by passing an array of `controls` as a prop to the `BlockControls` component. Each control should be an object with the following properties: - `icon: string` - Slug of the Dashicon to be shown in the control's toolbar button - `title: string` - A human-readable localized text to be shown as the tooltip label of the control's button - `subscript: ?string` - Optional text to be shown adjacent the button icon as subscript (for example, heading levels) - `isActive: ?boolean` - Whether the control should be considered active / selected. Defaults to `false`. -To create divisions between sets of controls within the same `BlockControls` -element, passing `controls` instead as a nested array (array of arrays of -objects). A divider will be shown between each set of controls. +To create divisions between sets of controls within the same `BlockControls` element, passing `controls` instead as a nested array (array of arrays of objects). A divider will be shown between each set of controls. ### `RichText` -Render a rich -[`contenteditable` input](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Editable_content), -providing users the option to add emphasis to content or links to content. It -behaves similarly to a -[controlled component](https://facebook.github.io/react/docs/forms.html#controlled-components), -except that `onChange` is triggered less frequently than would be expected from -a traditional `input` field, usually when the user exits the field. +Render a rich [`contenteditable` input](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Editable_content), providing users the option to add emphasis to content or links to content. It behaves similarly to a [controlled component](https://facebook.github.io/react/docs/forms.html#controlled-components), except that `onChange` is triggered less frequently than would be expected from a traditional `input` field, usually when the user exits the field. The following properties (non-exhaustive list) are made available: From a617aeefb8359a5a12ca20ca3ef00681d1f814e1 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak Date: Fri, 14 Dec 2018 01:18:51 -0800 Subject: [PATCH 016/691] Fix incorrect value for __back_compat_meta_box (#12776) --- .../developers/backward-compatibility/meta-box.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/designers-developers/developers/backward-compatibility/meta-box.md b/docs/designers-developers/developers/backward-compatibility/meta-box.md index 8e722b4cb3156..5403ebf674ac3 100644 --- a/docs/designers-developers/developers/backward-compatibility/meta-box.md +++ b/docs/designers-developers/developers/backward-compatibility/meta-box.md @@ -27,7 +27,7 @@ After a meta box is converted to a block, it can be declared as existing for bac add_meta_box( 'my-meta-box', 'My Meta Box', 'my_meta_box_callback', null, 'normal', 'high', array( - '__back_compat_meta_box' => false, + '__back_compat_meta_box' => true, ) ); ``` From 068bb76f4004d8a18c7fae512add230d9c86916b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Wrede?= Date: Sun, 16 Dec 2018 20:58:51 +0100 Subject: [PATCH 017/691] Fix Typo (#12902) --- docs/designers-developers/developers/filters/block-filters.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/designers-developers/developers/filters/block-filters.md b/docs/designers-developers/developers/filters/block-filters.md index bfbd33936df38..3214fa1ba2da5 100644 --- a/docs/designers-developers/developers/filters/block-filters.md +++ b/docs/designers-developers/developers/filters/block-filters.md @@ -200,7 +200,7 @@ var withClientIdClassName = wp.compose.createHigherOrderComponent( function( Blo {}, props, { - classsName: "block-" + props.clientId, + className: "block-" + props.clientId, } ); @@ -335,5 +335,5 @@ To set an SVG icon for the category shown in the previous example, add the follo var svgIcon = el( SVG, { width: 20, height: 20, viewBox: '0 0 20 20'}, circle); wp.blocks.updateCategory( 'my-category', { icon: svgIcon } ); } )(); -``` +``` From 797c5f330d7437d66497209da1e1996ef9011f7c Mon Sep 17 00:00:00 2001 From: Naoki Ohashi Date: Mon, 17 Dec 2018 04:59:29 +0900 Subject: [PATCH 018/691] fix typo (#12905) --- docs/designers-developers/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/designers-developers/readme.md b/docs/designers-developers/readme.md index 5e54798a3c787..107bad052f309 100644 --- a/docs/designers-developers/readme.md +++ b/docs/designers-developers/readme.md @@ -6,6 +6,6 @@ Gutenberg is a transformation of the WordPress editor for working with content. Using a system of Blocks to compose and format content, the new block-based editor is designed to create rich, flexible layouts for websites and digital products. Content is created in the unit of blocks instead of freeform text with inserted media, embeds and Shortcodes (there's a Shortcode block though). -Blocks treat Paragraphs, Headings, Media, Embeds all as components that strung together make up the content stored in the WordPress database, replacing the traditional concept of freeform text with embeded media and shortcodes. The new editor is designed with progressive enhancement, meaning it is back-compatible with all legacy content, offers a process to try to convert and split a Classic block into block equivalents using client-side parsing and finally the blocks offer enhanced editing and format controls. +Blocks treat Paragraphs, Headings, Media, Embeds all as components that strung together make up the content stored in the WordPress database, replacing the traditional concept of freeform text with embedded media and shortcodes. The new editor is designed with progressive enhancement, meaning it is back-compatible with all legacy content, offers a process to try to convert and split a Classic block into block equivalents using client-side parsing and finally the blocks offer enhanced editing and format controls. The Editor offers rich new value to users with visual, drag-and-drop creation tools and powerful developer enhancements with modern vendor packages, reusable components, rich APIs and hooks to modify and extend the editor through Custom Blocks, Custom Block Styles and Plugins. From b1c17a6a06b63aa42ec8f36da9a6b5f1923405d7 Mon Sep 17 00:00:00 2001 From: Naoki Ohashi Date: Mon, 17 Dec 2018 05:00:56 +0900 Subject: [PATCH 019/691] Fix Typo (#12918) --- docs/designers-developers/key-concepts.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/designers-developers/key-concepts.md b/docs/designers-developers/key-concepts.md index ce270c0d2ee81..6ea644009b145 100644 --- a/docs/designers-developers/key-concepts.md +++ b/docs/designers-developers/key-concepts.md @@ -4,9 +4,9 @@ Blocks are an abstract unit for organizing and composing content, strung together to create content for a webpage. -Blocks are hiearchical, in that a block can be a child or parent to another block. One example is a two-column Columns block can be the parent block to multiple child blocks in each column. +Blocks are hierarchical, in that a block can be a child or parent to another block. One example is a two-column Columns block can be the parent block to multiple child blocks in each column. -If it helps, you can think of blocks as a more graceful shortcode, with rich formatting tools for users to compose content. To this point, there is a new Block Grammar. Distilled, the block grammar is an HTML comment, either a self-closing tag or with a begining tag and ending tag. In the main tag, depending on the block type and user customizations, there can be a JSON object. This raw form of the block is referred to as serialized. +If it helps, you can think of blocks as a more graceful shortcode, with rich formatting tools for users to compose content. To this point, there is a new Block Grammar. Distilled, the block grammar is an HTML comment, either a self-closing tag or with a beginning tag and ending tag. In the main tag, depending on the block type and user customizations, there can be a JSON object. This raw form of the block is referred to as serialized. ```html @@ -16,7 +16,7 @@ If it helps, you can think of blocks as a more graceful shortcode, with rich for Blocks can be static or dynamic. Static blocks contain rendered content and an object of Attributes used to re-render based on changes. Dynamic blocks require server-side data and rendering while the post content is being generated (rendering). -Each block contains Attributes or configuration settings, which can be sourced from raw HTML in the content, via meta or other customizible origins. +Each block contains Attributes or configuration settings, which can be sourced from raw HTML in the content, via meta or other customizable origins. The Paragraph is the default Block. Instead of a new line upon typing return on a keyboard, try to think of it as an empty paragraph block (type / to trigger an autocompleting Slash Inserter -- /image will pull up Images as well as Instagram embeds). From 6b82b6c86591b280c9aa24a45a9386ae5059e569 Mon Sep 17 00:00:00 2001 From: Koji Kuno Date: Mon, 17 Dec 2018 14:27:47 +0900 Subject: [PATCH 020/691] fixed typo (#12936) Changed from 'moreso than' to 'more so than'. ## Description ## How has this been tested? ## Screenshots ## Types of changes ## Checklist: - [ ] My code is tested. - [ ] My code follows the WordPress code style. - [ ] My code follows the accessibility standards. - [ ] My code has proper inline documentation. - [ ] I've included developer documentation if appropriate. --- docs/contributors/coding-guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contributors/coding-guidelines.md b/docs/contributors/coding-guidelines.md index 8264e76523104..c47fda1ab070a 100644 --- a/docs/contributors/coding-guidelines.md +++ b/docs/contributors/coding-guidelines.md @@ -170,7 +170,7 @@ function MyComponent() {} An exception to camel case is made for constant values which are never intended to be reassigned or mutated. Such variables must use the [SCREAMING_SNAKE_CASE convention](https://en.wikipedia.org/wiki/Snake_case). -In almost all cases, a constant should be defined in the top-most scope of a file. It is important to note that [JavaScript's `const` assignment](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const) is conceptually more limited than what is implied here, where a value assigned by `const` in JavaScript can in-fact be mutated, and is only protected against reassignment. A constant as defined in these coding guidelines applies only to values which are expected to never change, and is a strategy for developers to communicate intent moreso than it is a technical restriction. +In almost all cases, a constant should be defined in the top-most scope of a file. It is important to note that [JavaScript's `const` assignment](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const) is conceptually more limited than what is implied here, where a value assigned by `const` in JavaScript can in-fact be mutated, and is only protected against reassignment. A constant as defined in these coding guidelines applies only to values which are expected to never change, and is a strategy for developers to communicate intent more so than it is a technical restriction. ### Strings From e1092c0d0b75fe53ab57bc6c4cc9e32cb2e74e40 Mon Sep 17 00:00:00 2001 From: Koji Kuno Date: Mon, 17 Dec 2018 14:28:09 +0900 Subject: [PATCH 021/691] Fix typo (#12937) Fixed typo from 'comma separated' to 'comma-separated'. --- docs/contributors/design.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contributors/design.md b/docs/contributors/design.md index aefba0d0eb3d4..7b037fd3a4c94 100644 --- a/docs/contributors/design.md +++ b/docs/contributors/design.md @@ -46,7 +46,7 @@ Gutenberg wants to make it easier to author rich content. This means ensuring go The initial phase of Gutenberg as described in the kickoff goal is primarily limited to the content area (specifically `post_content`) of posts and pages. Within those confines, we are embracing the web as a vertical river of content by appending blocks sequentially, then adding layout options to each block. -That said, there isn’t any fixed limit to the kind of layouts Gutenberg will be able to create. It’s very possible for Gutenberg to grow beyond the confines of post and page content, to include the whole page — one could think of a theme template as a comma separated list of blocks, like this: +That said, there isn’t any fixed limit to the kind of layouts Gutenberg will be able to create. It’s very possible for Gutenberg to grow beyond the confines of post and page content, to include the whole page — one could think of a theme template as a comma-separated list of blocks, like this: ```js { From 6390264a47dcddc62efadb25cb4ee6e6735b72f4 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak Date: Mon, 17 Dec 2018 06:52:34 -0800 Subject: [PATCH 022/691] Add content to Contributors index page (#12910) * Add content to Contributors index page When visiting the Gutenberg Handbook the Contributors index page is blank. See: https://wordpress.org/gutenberg/handbook/contributors/ This PR adds some basic content and links to find more, including linking back to the repo's contributing document. * Remove newlines, update links --- docs/contributors/readme.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/contributors/readme.md b/docs/contributors/readme.md index e69de29bb2d1d..1feaadfcbf5b9 100644 --- a/docs/contributors/readme.md +++ b/docs/contributors/readme.md @@ -0,0 +1,11 @@ +# Contributors Guide + +Welcome to the Gutenberg Project Contributors Guide. + +The following guidelines are in place to create consistency across the project and the numerous contributors. See also the [Contributing Documentation](https://github.com/WordPress/gutenberg/blob/master/CONTRIBUTING.md) for technical details around setup, and submitting your contributions. + +* [Coding Guidelines](../../docs/contributors/coding-guidelines.md) outline additional patterns and conventions used in the Gutenberg project. +* [Copy Guidelines](../../docs/contributors/copy-guide.md) +* [Design Principles & Vision](../../docs/contributors/design.md) + +Please see the table of contents on the left side of the Gutenberg Handbook for the full list of contributor resources. From a15545474c798805bae3d98a8780163a1318654e Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak Date: Mon, 17 Dec 2018 06:53:14 -0800 Subject: [PATCH 023/691] Getting Started with JavaScript (#12689) * Getting Started with JavaScript * Reorder add_action/function * Update prefix to use myguten, instead of just my * Update docs/designers-developers/developers/tutorials/javascript/readme.md Co-Authored-By: mkaz * Update to block_editor_assets * Updates per danielbachhuber review. * Updates, and broken up into sections * Add Troubleshooting * Updates with suggestion. Props to @nosolosw * Update docs/designers-developers/developers/tutorials/javascript/versions-and-building.md Co-Authored-By: mkaz * Update docs/designers-developers/developers/tutorials/javascript/versions-and-building.md Co-Authored-By: mkaz * Add screenshot with style * Reorder TOC, move versions to end * Update link for browser support * Add check for dependency to troubleshooting section * General edits for spelling and flow * Update Table of Contents and generated manifest * Fix up header tags consistency and spaces * Edits per @chrisvanpatten review * Update docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md Co-Authored-By: mkaz * Update docs/designers-developers/developers/tutorials/javascript/loading-javascript.md Co-Authored-By: mkaz * Update docs/designers-developers/developers/tutorials/javascript/loading-javascript.md Co-Authored-By: mkaz * Update docs/designers-developers/developers/tutorials/javascript/loading-javascript.md Co-Authored-By: mkaz * Update docs/designers-developers/developers/tutorials/javascript/plugins-background.md Co-Authored-By: mkaz * Update docs/designers-developers/developers/tutorials/javascript/plugins-background.md Co-Authored-By: mkaz * Move screenshots to assets directory, add additional screenshot for success * Move block style variant to assets * Formating * Update images to full URLs * Fix titles --- .../assets/fancy-quote-in-inspector.png | Bin 0 -> 44857 bytes .../assets/fancy-quote-with-style.png | Bin 0 -> 42284 bytes .../assets/js-tutorial-console-log-error.png | Bin 0 -> 33443 bytes .../js-tutorial-console-log-success.png | Bin 0 -> 24046 bytes .../js-tutorial-error-blocks-undefined.png | Bin 0 -> 6986 bytes .../javascript/extending-the-block-editor.md | 66 ++++++++++++++++++ .../javascript/loading-javascript.md | 49 +++++++++++++ .../javascript/plugins-background.md | 16 +++++ .../developers/tutorials/javascript/readme.md | 18 +++++ .../tutorials/javascript/troubleshooting.md | 34 +++++++++ .../javascript/versions-and-building.md | 11 +++ .../developers/tutorials/readme.md | 5 +- docs/manifest.json | 36 ++++++++++ docs/toc.json | 7 ++ 14 files changed, 241 insertions(+), 1 deletion(-) create mode 100644 docs/designers-developers/assets/fancy-quote-in-inspector.png create mode 100644 docs/designers-developers/assets/fancy-quote-with-style.png create mode 100644 docs/designers-developers/assets/js-tutorial-console-log-error.png create mode 100644 docs/designers-developers/assets/js-tutorial-console-log-success.png create mode 100644 docs/designers-developers/assets/js-tutorial-error-blocks-undefined.png create mode 100644 docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md create mode 100644 docs/designers-developers/developers/tutorials/javascript/loading-javascript.md create mode 100644 docs/designers-developers/developers/tutorials/javascript/plugins-background.md create mode 100644 docs/designers-developers/developers/tutorials/javascript/readme.md create mode 100644 docs/designers-developers/developers/tutorials/javascript/troubleshooting.md create mode 100644 docs/designers-developers/developers/tutorials/javascript/versions-and-building.md diff --git a/docs/designers-developers/assets/fancy-quote-in-inspector.png b/docs/designers-developers/assets/fancy-quote-in-inspector.png new file mode 100644 index 0000000000000000000000000000000000000000..6bd8c06a9e3979801ec080c74af798ff7d93d031 GIT binary patch literal 44857 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 Wx(Bl7^uhW=Xsa8j6`#2r`u_maF0s=9 literal 0 HcmV?d00001 diff --git a/docs/designers-developers/assets/js-tutorial-console-log-error.png b/docs/designers-developers/assets/js-tutorial-console-log-error.png new file mode 100644 index 0000000000000000000000000000000000000000..836a663484192f4f204f002a3702a66063a7dc35 GIT binary patch literal 33443 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 literal 0 HcmV?d00001 diff --git a/docs/designers-developers/assets/js-tutorial-console-log-success.png b/docs/designers-developers/assets/js-tutorial-console-log-success.png new file mode 100644 index 0000000000000000000000000000000000000000..7b42853fb406427256db4e103b58779e19413a73 GIT binary patch literal 24046 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 c>_FAv-6Q+b_nz_lKi`SY&;S4c literal 0 HcmV?d00001 diff --git a/docs/designers-developers/assets/js-tutorial-error-blocks-undefined.png b/docs/designers-developers/assets/js-tutorial-error-blocks-undefined.png new file mode 100644 index 0000000000000000000000000000000000000000..1f27c36ce759560bda4cb689ab39c5bdaf7b9879 GIT binary patch literal 6986 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 literal 0 HcmV?d00001 diff --git a/docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md b/docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md new file mode 100644 index 0000000000000..67d703cb37310 --- /dev/null +++ b/docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md @@ -0,0 +1,66 @@ +# Extending the Block Editor + +Let's look at using the [Block Style Variation example](../../../../../docs/designers-developers/developers/filters/block-filters.md#block-style-variations) to extend the editor. This example allows you to add your own custom CSS class name to any core block type. + +Replace the existing `console.log()` code in your `myguten.js` file with: + +```js +wp.blocks.registerBlockStyle( 'core/quote', { + name: 'fancy-quote', + label: 'Fancy Quote' +} ); +``` + +**Important:** Notice that you are using a function from `wp.blocks` package. This means you must specify it as a dependency when you enqueue the script. Update the `myguten-plugin.php` file to: + +```php +` tag that loads your file. In our example, you would search for `myguten.js` and confirm it is being loaded. + +If you do not see the file being loaded, doublecheck the enqueue function is correct. You can also check your server logs to see if there is an error messages. + +## Confirm all dependencies are loaded + +The console log will show an error if a dependency your JavaScript code uses has not been declared and loaded in the browser. In the example, if `myguten.js` script is enqueued without declaring the `wp-blocks` dependency, the console log will show: + + + +You can correct by checking your `wp_enqueue_script` function includes all packages listed that are used: + +```js +wp_enqueue_script( + 'myguten-script', + plugins_url( 'myguten.js', __FILE__ ), + array( 'wp-blocks' ) +); +``` + diff --git a/docs/designers-developers/developers/tutorials/javascript/versions-and-building.md b/docs/designers-developers/developers/tutorials/javascript/versions-and-building.md new file mode 100644 index 0000000000000..c7de41efd4d0a --- /dev/null +++ b/docs/designers-developers/developers/tutorials/javascript/versions-and-building.md @@ -0,0 +1,11 @@ +# JavaScript versions and build step + +The Gutenberg Handbook shows JavaScript examples in two syntaxes: ES5 and ESNext. These are version names for the JavaScript language standard definitions. You may also see elsewhere the names ES6, or ECMAScript 2015 mentioned. See the [ECMAScript](https://en.wikipedia.org/wiki/ECMAScript) Wikipedia article for all the details. + +ES5 code is compatible with WordPress's minimum [target for browser support](https://make.wordpress.org/core/handbook/best-practices/browser-support/). + +"ESNext" doesn't refer to a specific version of JavaScript, but is "dynamic" and refers to the next language definitions, whatever they might be. Because some browsers won't support these features yet (because they're new or proposed), an extra build step is required to transform the code to a syntax that works in all browsers. Webpack and babel are the tools that perform this transformation step. + +Additionally, the ESNext code examples in the Gutenberg handbook include [JSX syntax](https://reactjs.org/docs/introducing-jsx.html), a syntax that blends HTML and JavaScript. It makes it easier to read and write markup code, but likewise requires the build step using webpack and babel to transform into compatible code. + +For simplicity, this tutorial uses the ES5 definition of JavaScript, without JSX. This code can run straight in your browser and does not require an additional build step. diff --git a/docs/designers-developers/developers/tutorials/readme.md b/docs/designers-developers/developers/tutorials/readme.md index d12dcd1c4982d..543f560c8d81c 100644 --- a/docs/designers-developers/developers/tutorials/readme.md +++ b/docs/designers-developers/developers/tutorials/readme.md @@ -1,3 +1,6 @@ # Tutorials -If you want to learn more about block creation, the [Blocks Tutorial](../../../../docs/designers-developers/developers/tutorials/block-tutorial/readme.md) is the best place to start. +* If you want to learn more about block creation, the [Blocks Tutorial](../../../../docs/designers-developers/developers/tutorials/block-tutorial/readme.md) is the best place to start. + +* See the [Getting Started with JavaScript Tutorial](../../../../docs/designers-developers/developers/tutorials/javascript/readme.md) to learn about how to use JavaScript within WordPress. + diff --git a/docs/manifest.json b/docs/manifest.json index dbf955bd2d247..79512ef3b96ca 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -197,6 +197,42 @@ "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/generate-blocks-with-wp-cli.md", "parent": "block-tutorial" }, + { + "title": "Getting Started with JavaScript", + "slug": "javascript", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/readme.md", + "parent": "tutorials" + }, + { + "title": "Plugins Background", + "slug": "plugins-background", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/plugins-background.md", + "parent": "javascript" + }, + { + "title": "Loading JavaScript", + "slug": "loading-javascript", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/loading-javascript.md", + "parent": "javascript" + }, + { + "title": "Extending the Block Editor", + "slug": "extending-the-block-editor", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md", + "parent": "javascript" + }, + { + "title": "Troubleshooting", + "slug": "troubleshooting", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/troubleshooting.md", + "parent": "javascript" + }, + { + "title": "JavaScript versions and build step", + "slug": "versions-and-building", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/versions-and-building.md", + "parent": "javascript" + }, { "title": "Designer Documentation", "slug": "designers", diff --git a/docs/toc.json b/docs/toc.json index 6dd5e876cbc00..c106d9a499ca6 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -36,6 +36,13 @@ {"docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbars-and-inspector.md" :[]}, {"docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md" :[]}, {"docs/designers-developers/developers/tutorials/block-tutorial/generate-blocks-with-wp-cli.md" :[]} + ]}, + { "docs/designers-developers/developers/tutorials/javascript/readme.md": [ + {"docs/designers-developers/developers/tutorials/javascript/plugins-background.md": []}, + { "docs/designers-developers/developers/tutorials/javascript/loading-javascript.md": []}, + { "docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md": []}, + { "docs/designers-developers/developers/tutorials/javascript/troubleshooting.md": []}, + { "docs/designers-developers/developers/tutorials/javascript/versions-and-building.md": []} ]} ]} ]}, From d9257b11544c8840925180a45273ed18a2c61ddb Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 17 Dec 2018 16:38:10 +0100 Subject: [PATCH 024/691] Restore block prop for BlockListBlock filter (#12943) * Restore block prop for BlockListBlock filter * Restore hook order * getBlock with memory leak * Add clientId and isValid to the block prop --- packages/editor/CHANGELOG.md | 6 ++++++ .../editor/src/components/block-list/block.js | 19 +++++++++++-------- packages/editor/src/hooks/align.js | 2 +- packages/editor/src/store/selectors.js | 18 ++++++++++++++++++ 4 files changed, 36 insertions(+), 9 deletions(-) diff --git a/packages/editor/CHANGELOG.md b/packages/editor/CHANGELOG.md index e06655f6738b6..a5e35f2c95108 100644 --- a/packages/editor/CHANGELOG.md +++ b/packages/editor/CHANGELOG.md @@ -1,3 +1,9 @@ +## 9.0.6 (Unreleased) + +### Bug Fixes + +- Restore the `block` prop in the `BlockListBlock` filter. + ## 9.0.5 (2018-12-12) ### Bug Fixes diff --git a/packages/editor/src/components/block-list/block.js b/packages/editor/src/components/block-list/block.js index 2523029c010ed..923e22362dd1d 100644 --- a/packages/editor/src/components/block-list/block.js +++ b/packages/editor/src/components/block-list/block.js @@ -657,9 +657,6 @@ const applyWithSelect = withSelect( ( select, { clientId, rootClientId, isLargeViewport } ) => { const { isBlockSelected, - getBlockName, - isBlockValid, - getBlockAttributes, isAncestorMultiSelected, isBlockMultiSelected, isFirstMultiSelectedBlock, @@ -675,13 +672,14 @@ const applyWithSelect = withSelect( getTemplateLock, getPreviousBlockClientId, getNextBlockClientId, + __unstableGetBlockWithoutInnerBlocks, } = select( 'core/editor' ); + const block = __unstableGetBlockWithoutInnerBlocks( clientId ); const isSelected = isBlockSelected( clientId ); const { hasFixedToolbar, focusMode } = getEditorSettings(); const templateLock = getTemplateLock( rootClientId ); const isParentOfSelectedBlock = hasSelectedInnerBlock( clientId, true ); - const name = getBlockName( clientId ); - const attributes = getBlockAttributes( clientId ); + const { name, attributes, isValid } = block; return { isPartOfMultiSelection: @@ -699,13 +697,19 @@ const applyWithSelect = withSelect( initialPosition: getSelectedBlocksInitialCaretPosition(), isEmptyDefaultBlock: name && isUnmodifiedDefaultBlock( { name, attributes } ), - isValid: isBlockValid( clientId ), isMovable: 'all' !== templateLock, isLocked: !! templateLock, isFocusMode: focusMode && isLargeViewport, hasFixedToolbar: hasFixedToolbar && isLargeViewport, + + // Users of the editor.BlockListBlock filter used to be able to access the block prop + // Ideally these blocks would rely on the clientId prop only. + // This is kept for backward compatibility reasons. + block, + name, attributes, + isValid, isSelected, isParentOfSelectedBlock, @@ -777,9 +781,8 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, { select } ) => { } ); export default compose( - withFilters( 'editor.BlockListBlock' ), withViewportMatch( { isLargeViewport: 'medium' } ), applyWithSelect, applyWithDispatch, - withFilters( 'editor.__experimentalBlockListBlock' ) + withFilters( 'editor.BlockListBlock' ) )( BlockListBlock ); diff --git a/packages/editor/src/hooks/align.js b/packages/editor/src/hooks/align.js index 46adcc0ba3ede..e22922c198b2c 100644 --- a/packages/editor/src/hooks/align.js +++ b/packages/editor/src/hooks/align.js @@ -206,7 +206,7 @@ export function addAssignedAlign( props, blockType, attributes ) { } addFilter( 'blocks.registerBlockType', 'core/align/addAttribute', addAttribute ); -addFilter( 'editor.__experimentalBlockListBlock', 'core/editor/align/with-data-align', withDataAlign ); +addFilter( 'editor.BlockListBlock', 'core/editor/align/with-data-align', withDataAlign ); addFilter( 'editor.BlockEdit', 'core/editor/align/with-toolbar-controls', withToolbarControls ); addFilter( 'blocks.getSaveContent.extraProps', 'core/align/addAssignedAlign', addAssignedAlign ); diff --git a/packages/editor/src/store/selectors.js b/packages/editor/src/store/selectors.js index d8b019930d1a0..86b127e240403 100644 --- a/packages/editor/src/store/selectors.js +++ b/packages/editor/src/store/selectors.js @@ -704,6 +704,24 @@ export const getBlock = createSelector( ] ); +export const __unstableGetBlockWithoutInnerBlocks = createSelector( + ( state, clientId ) => { + const block = state.editor.present.blocks.byClientId[ clientId ]; + if ( ! block ) { + return null; + } + + return { + ...block, + attributes: getBlockAttributes( state, clientId ), + }; + }, + ( state, clientId ) => [ + state.editor.present.blocks.byClientId[ clientId ], + ...getBlockAttributes.getDependants( state, clientId ), + ] +); + function getPostMeta( state, key ) { return has( state, [ 'editor', 'present', 'edits', 'meta', key ] ) ? get( state, [ 'editor', 'present', 'edits', 'meta', key ] ) : From ddac4f3cf8fd311169c7e125411343a437bdbb5a Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 17 Dec 2018 18:10:11 +0100 Subject: [PATCH 025/691] Fix JS error when removing a block (#12949) --- packages/editor/src/components/block-list/block.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/editor/src/components/block-list/block.js b/packages/editor/src/components/block-list/block.js index 923e22362dd1d..f29de8106fab5 100644 --- a/packages/editor/src/components/block-list/block.js +++ b/packages/editor/src/components/block-list/block.js @@ -679,7 +679,11 @@ const applyWithSelect = withSelect( const { hasFixedToolbar, focusMode } = getEditorSettings(); const templateLock = getTemplateLock( rootClientId ); const isParentOfSelectedBlock = hasSelectedInnerBlock( clientId, true ); - const { name, attributes, isValid } = block; + + // The fallback to `{}` is a temporary fix. + // This function should never be called when a block is not present in the state. + // It happens now because the order in withSelect rendering is not correct. + const { name, attributes, isValid } = block || {}; return { isPartOfMultiSelection: From e13bedbb213a45143a5f8aea12e4c381e3bac4b7 Mon Sep 17 00:00:00 2001 From: Jarred Kennedy Date: Wed, 19 Dec 2018 03:05:02 +1100 Subject: [PATCH 026/691] Fixed incorrect example code (#12906) --- packages/data/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/data/README.md b/packages/data/README.md index b5745e7719ed7..2c911ccf4cf8f 100644 --- a/packages/data/README.md +++ b/packages/data/README.md @@ -91,7 +91,7 @@ registerStore( 'my-shop', { }, resolvers: { - * getPrice( state, item ) { + * getPrice( item ) { const path = '/wp/v2/prices/' + item; const price = yield actions.fetchFromAPI( path ); return actions.setPrice( item, price ); @@ -127,7 +127,7 @@ The **`selectors`** object includes a set of functions for accessing and derivin A **resolver** is a side-effect for a selector. If your selector result may need to be fulfilled from an external source, you can define a resolver such that the first time the selector is called, the fulfillment behavior is effected. -The `resolvers` option should be passed as an object where each key is the name of the selector to act upon, the value a function which receives the same arguments passed to the selector. It can then dispatch as necessary to fulfill the requirements of the selector, taking advantage of the fact that most data consumers will subscribe to subsequent state changes (by `subscribe` or `withSelect`). +The `resolvers` option should be passed as an object where each key is the name of the selector to act upon, the value a function which receives the same arguments passed to the selector, excluding the state argument. It can then dispatch as necessary to fulfill the requirements of the selector, taking advantage of the fact that most data consumers will subscribe to subsequent state changes (by `subscribe` or `withSelect`). ### `controls` From 92e3942d6f433ab15f21103de0cf45bf131f8bf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= Date: Tue, 18 Dec 2018 18:20:35 +0100 Subject: [PATCH 027/691] Build tooling: Fix Travis failures with Docker instances (#12983) * Build tooling: Fix Travis failures with Docker instances * Docs: Fix manifest file * chore: Fix typo --- bin/install-wordpress.sh | 37 ++++++++++++++++++++++++------------- docs/manifest.json | 2 +- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/bin/install-wordpress.sh b/bin/install-wordpress.sh index a346152a579bd..affe7cc51d34e 100755 --- a/bin/install-wordpress.sh +++ b/bin/install-wordpress.sh @@ -45,41 +45,52 @@ echo '' # dirty up the tests. if [ "$1" == '--e2e_tests' ]; then echo -e $(status_message "Resetting test database...") - docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm $CLI db reset --yes >/dev/null + docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm -u 33 $CLI db reset --yes --quiet fi # Install WordPress. echo -e $(status_message "Installing WordPress...") # The `-u 33` flag tells Docker to run the command as a particular user and # prevents permissions errors. See: https://github.com/WordPress/gutenberg/pull/8427#issuecomment-410232369 -docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm -u 33 $CLI core install --title="$SITE_TITLE" --admin_user=admin --admin_password=password --admin_email=test@test.com --skip-email --url=http://localhost:$HOST_PORT >/dev/null +docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm -u 33 $CLI core install --title="$SITE_TITLE" --admin_user=admin --admin_password=password --admin_email=test@test.com --skip-email --url=http://localhost:$HOST_PORT --quiet if [ "$E2E_ROLE" = "author" ]; then - # Create an additional author user for testsing. - docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm -u 33 $CLI user create author author@example.com --role=author --user_pass=authpass + echo -e $(status_message "Creating an additional author user for testing...") + # Create an additional author user for testing. + docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm -u 33 $CLI user create author author@example.com --role=author --user_pass=authpass --quiet # Assign the existing Hello World post to the author. - docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm -u 33 $CLI post update 1 --post_author=2 + docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm -u 33 $CLI post update 1 --post_author=2 --quiet fi +# Make sure the uploads and upgrade folders exist and we have permissions to add files. +echo -e $(status_message "Ensuring that files can be uploaded...") +docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm $CONTAINER chmod 767 /var/www/html/wp-content/plugins +docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm $CONTAINER mkdir -p /var/www/html/wp-content/uploads +docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm $CONTAINER chmod -v 767 /var/www/html/wp-content/uploads +docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm $CONTAINER mkdir -p /var/www/html/wp-content/upgrade +docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm $CONTAINER chmod 767 /var/www/html/wp-content/upgrade + +CURRENT_WP_VERSION=$(docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run -T --rm $CLI core version) +echo -e $(status_message "Current WordPress version: $CURRENT_WP_VERSION...") + if [ "$WP_VERSION" == "latest" ]; then # Check for WordPress updates, to make sure we're running the very latest version. - docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm -u 33 $CLI core update >/dev/null + echo -e $(status_message "Updating WordPress to the latest version...") + docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm -u 33 $CLI core update --quiet fi # If the 'wordpress' volume wasn't during the down/up earlier, but the post port has changed, we need to update it. +echo -e $(status_message "Checking the site's url...") CURRENT_URL=$(docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run -T --rm $CLI option get siteurl) if [ "$CURRENT_URL" != "http://localhost:$HOST_PORT" ]; then - docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm $CLI option update home "http://localhost:$HOST_PORT" >/dev/null - docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm $CLI option update siteurl "http://localhost:$HOST_PORT" >/dev/null + docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm -u 33 $CLI option update home "http://localhost:$HOST_PORT" --quiet + docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm -u 33 $CLI option update siteurl "http://localhost:$HOST_PORT" --quiet fi # Activate Gutenberg. echo -e $(status_message "Activating Gutenberg...") -docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm $CLI plugin activate gutenberg >/dev/null - -# Make sure the uploads folder exist and we have permissions to add files there. -docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm $CONTAINER mkdir -p /var/www/html/wp-content/uploads -docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm $CONTAINER chmod -v 767 /var/www/html/wp-content/uploads +docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm -u 33 $CLI plugin activate gutenberg --quiet # Install a dummy favicon to avoid 404 errors. +echo -e $(status_message "Installing a dummy favicon...") docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm $CONTAINER touch /var/www/html/favicon.ico diff --git a/docs/manifest.json b/docs/manifest.json index 79512ef3b96ca..4dc762893f571 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -270,7 +270,7 @@ "parent": "designers-developers" }, { - "title": "Contributors", + "title": "Contributors Guide", "slug": "contributors", "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/readme.md", "parent": null From 5dba988cf740653b9ab41672c462c785f1ff1a38 Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Wed, 19 Dec 2018 13:59:15 +0800 Subject: [PATCH 028/691] Add section to setAttributes documentation about immutability of attributes (#12811) * Add section to setAttributes documentation about immutability of attributes * Simplify explanation of immutable attributes * Fix typo and add link to redux's immutability docs Co-Authored-By: talldan --- .../developers/block-api/block-edit-save.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/designers-developers/developers/block-api/block-edit-save.md b/docs/designers-developers/developers/block-api/block-edit-save.md index c6f4d603cc308..f72baac90858d 100644 --- a/docs/designers-developers/developers/block-api/block-edit-save.md +++ b/docs/designers-developers/developers/block-api/block-edit-save.md @@ -80,6 +80,24 @@ edit( { attributes, setAttributes, className, isSelected } ) { } ``` +When using attributes that are objects or arrays it's a good idea to copy or clone the attribute prior to updating it: + +```js +// Good - here a new array is created from the old list attribute and a new list item: +const { list } = attributes; +const addListItem = ( newListItem ) => setAttributes( { list: [ ...list, newListItem ] } ); + +// Bad - here the list from the existing attribute is modified directly to add the new list item: +const { list } = attributes; +const addListItem = ( newListItem ) => { + list.push( newListItem ); + setAttributes( { list } ); +}; + +``` + +Why do this? In JavaScript, arrays and objects are passed by reference, so this practice ensures changes won't affect other code that might hold references to the same data. Furthermore, Gutenberg follows the philosophy of the Redux library that [state should be immutable](https://redux.js.org/faq/immutable-data#what-are-the-benefits-of-immutability)—data should not be changed directly, but instead a new version of the data created containing the changes. + ## Save The `save` function defines the way in which the different attributes should be combined into the final markup, which is then serialized by Gutenberg into `post_content`. From 5f683de5dd4396d4e8d965835da40a228c583841 Mon Sep 17 00:00:00 2001 From: Jon Desrosiers Date: Wed, 19 Dec 2018 03:09:44 -0500 Subject: [PATCH 029/691] Fix alignment of property assignments in the `__construct()` function of the block parser. (#12995) --- packages/block-serialization-default-parser/parser.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/block-serialization-default-parser/parser.php b/packages/block-serialization-default-parser/parser.php index 216cb9a5f78e8..0d5887381a1b8 100644 --- a/packages/block-serialization-default-parser/parser.php +++ b/packages/block-serialization-default-parser/parser.php @@ -63,10 +63,10 @@ class WP_Block_Parser_Block { public $innerContent; function __construct( $name, $attrs, $innerBlocks, $innerHTML, $innerContent ) { - $this->blockName = $name; - $this->attrs = $attrs; - $this->innerBlocks = $innerBlocks; - $this->innerHTML = $innerHTML; + $this->blockName = $name; + $this->attrs = $attrs; + $this->innerBlocks = $innerBlocks; + $this->innerHTML = $innerHTML; $this->innerContent = $innerContent; } } From 2934d6c742333897774f62fa302d94559beffc3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= Date: Wed, 19 Dec 2018 09:18:54 +0100 Subject: [PATCH 030/691] Update recommended config provided for lint-js script (#12845) * Update default config provided for lint-js script * Update packages/scripts/CHANGELOG.md * Clarify usage of recommended configs in docs --- package-lock.json | 2 +- packages/scripts/CHANGELOG.md | 3 ++- packages/scripts/README.md | 8 ++++---- packages/scripts/config/.eslintrc.js | 2 +- packages/scripts/package.json | 2 +- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1867b41831f79..8038ebf538c07 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2762,9 +2762,9 @@ "dev": true, "requires": { "@wordpress/babel-preset-default": "file:packages/babel-preset-default", + "@wordpress/eslint-plugin": "file:packages/eslint-plugin", "@wordpress/jest-preset-default": "file:packages/jest-preset-default", "@wordpress/npm-package-json-lint-config": "file:packages/npm-package-json-lint-config", - "babel-eslint": "8.0.3", "chalk": "^2.4.1", "check-node-version": "^3.1.1", "cross-spawn": "^5.1.0", diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index 6f2977dc07f90..1af69f533f2fa 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -1,8 +1,9 @@ ## 2.5.0 (Unreleased) -### New Feature +### New Features - Added support for `check-engines` script ([#12721](https://github.com/WordPress/gutenberg/pull/12721)) +- Update default config provided for `lint-js` script ([#12845](https://github.com/WordPress/gutenberg/pull/12845)). ## 2.4.4 (2018-11-20) diff --git a/packages/scripts/README.md b/packages/scripts/README.md index 04ddf958782e0..b240177fbc631 100644 --- a/packages/scripts/README.md +++ b/packages/scripts/README.md @@ -30,7 +30,7 @@ _Example:_ ### `check-engines` -Check if the current `node`, `npm` (or `yarn`) versions match the given [semantic version](https://semver.org/) ranges. If the given version is not satisfied, information about installing the needed version is printed and the program exits with an error code. It uses [check-node-version](https://www.npmjs.com/package/check-node-version) behind the scenes with the default configuration provided. You can specify your own ranges as described in [check-node-version docs](https://www.npmjs.com/package/check-node-version). +Check if the current `node`, `npm` (or `yarn`) versions match the given [semantic version](https://semver.org/) ranges. If the given version is not satisfied, information about installing the needed version is printed and the program exits with an error code. It uses [check-node-version](https://www.npmjs.com/package/check-node-version) behind the scenes with the recommended configuration provided. You can specify your own ranges as described in [check-node-version docs](https://www.npmjs.com/package/check-node-version). _Example:_ @@ -48,7 +48,7 @@ This is how you execute the script with presented setup: ### `wp-scripts lint-js` -Helps enforce coding style guidelines for your JavaScript files. It uses [eslint](https://eslint.org/) with no rules provided (we plan to add zero config support in the near future). You can specify your own rules as described in [eslint docs](https://eslint.org/docs/rules/). +Helps enforce coding style guidelines for your JavaScript files. It uses [eslint](https://eslint.org/) with the set of recommended rules defined in [@wordpress/eslint-plugin](https://www.npmjs.com/package/@wordpress/eslint-plugin) npm package. You can override default rules with your own as described in [eslint docs](https://eslint.org/docs/rules/). _Example:_ @@ -65,7 +65,7 @@ This is how you execute the script with presented setup: ### `wp-scripts lint-pkg-json` -Helps enforce standards for your package.json files. It uses [npm-package-json-lint](https://www.npmjs.com/package/npm-package-json-lint) with the set of default rules provided. You can override them with your own rules as described in [npm-package-json-lint wiki](https://github.com/tclindner/npm-package-json-lint/wiki). +Helps enforce standards for your package.json files. It uses [npm-package-json-lint](https://www.npmjs.com/package/npm-package-json-lint) with the set of recommended rules defined in [@wordpress/npm-package-json-lint-config](https://www.npmjs.com/package/@wordpress/npm-package-json-lint-config) npm package. You can override default rules with your own as described in [npm-package-json-lint wiki](https://github.com/tclindner/npm-package-json-lint/wiki). _Example:_ @@ -84,7 +84,7 @@ This is how you execute those scripts using the presented setup: _Alias_: `wp-scripts test-unit-jest` -Launches the test runner. It uses [Jest](https://facebook.github.io/jest/) behind the scenes and you are able to utilize all of its [CLI options](https://facebook.github.io/jest/docs/en/cli.html). You can also run `./node_modules/.bin/wp-scripts test-unit-js --help` or `npm run test:help` (as presented below) to view all of the available options. +Launches the test runner. It uses [Jest](https://facebook.github.io/jest/) behind the scenes and you are able to utilize all of its [CLI options](https://facebook.github.io/jest/docs/en/cli.html). You can also run `./node_modules/.bin/wp-scripts test-unit-js --help` or `npm run test:help` (as presented below) to view all of the available options. By default, it uses the set of recommended options defined in [@wordpress/jest-preset-default](https://www.npmjs.com/package/@wordpress/jest-preset-default) npm package. You can override them with your own options as described in [Jest documentation](https://jestjs.io/docs/en/configuration). _Example:_ diff --git a/packages/scripts/config/.eslintrc.js b/packages/scripts/config/.eslintrc.js index 601a62eb177b8..5b59ed86f4d2e 100644 --- a/packages/scripts/config/.eslintrc.js +++ b/packages/scripts/config/.eslintrc.js @@ -1,3 +1,3 @@ module.exports = { - parser: 'babel-eslint', + extends: [ 'plugin:@wordpress/eslint-plugin/recommended' ], }; diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 6a0bc5e1986a9..c6232c62d586c 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -32,9 +32,9 @@ }, "dependencies": { "@wordpress/babel-preset-default": "file:../babel-preset-default", + "@wordpress/eslint-plugin": "file:../eslint-plugin", "@wordpress/jest-preset-default": "file:../jest-preset-default", "@wordpress/npm-package-json-lint-config": "file:../npm-package-json-lint-config", - "babel-eslint": "8.0.3", "chalk": "^2.4.1", "check-node-version": "^3.1.1", "cross-spawn": "^5.1.0", From 0484bc368bd0e014bbfcba6e17e08ae44cad00d4 Mon Sep 17 00:00:00 2001 From: Stephen Edgar Date: Wed, 19 Dec 2018 19:25:58 +1100 Subject: [PATCH 031/691] Add JSHint ESLint config (#12840) * Add ESLint JSHint config * Update packages/eslint-plugin/README.md Co-Authored-By: ntwb --- packages/eslint-plugin/README.md | 10 ++++++++++ packages/eslint-plugin/configs/jshint.js | 17 +++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 packages/eslint-plugin/configs/jshint.js diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index fd516bbf622b4..3463e97d84024 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -45,4 +45,14 @@ These rules can be used additively, so you could extend both `esnext` and `custo The granular rulesets will not define any environment globals. As such, if they are required for your project, you will need to define them yourself. +#### Legacy + +If you are using WordPress' `.jshintrc` JSHint configuration and you would like to take the first step to migrate to an ESLint equivalent it is also possible to define your own project's `.eslintrc` file as: + +```json +{ + "extends": [ "plugin:@wordpress/eslint-plugin/jshint" ] +} +``` +

    Code is Poetry.

    diff --git a/packages/eslint-plugin/configs/jshint.js b/packages/eslint-plugin/configs/jshint.js new file mode 100644 index 0000000000000..d153eeb1a536a --- /dev/null +++ b/packages/eslint-plugin/configs/jshint.js @@ -0,0 +1,17 @@ +module.exports = { + rules: { + curly: 'error', + eqeqeq: 'error', + 'no-caller': 'error', + 'no-cond-assign': [ 'error', 'except-parens' ], + 'no-eq-null': 'error', + 'no-irregular-whitespace': 'error', + 'no-trailing-spaces': 'error', + 'no-undef': 'error', + 'no-unused-expressions': 'error', + 'no-unused-vars': 'error', + 'one-var': [ 'error', 'always' ], + quotes: [ 'error', 'single' ], + 'wrap-iife': [ 'error', 'any' ], + }, +}; From 76123a44d288500400a9b3d90da4d0d12bc57be1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= Date: Wed, 19 Dec 2018 10:16:20 +0100 Subject: [PATCH 032/691] Scripts: Add test-e2e script to wp-scripts (#12437) * Scripts: Add test-e2e script * Docs: Add docs for test-e2e script added to scripts packages * Update package-lock.json * Refactor scripts based on feedback shared in the review * More fixes to wp-scripts test-e2e * Address feedback shared in the review * Fix runInBand flag usage --- package-lock.json | 563 +++++++++--------- package.json | 5 +- packages/babel-preset-default/package.json | 1 + packages/scripts/CHANGELOG.md | 1 + packages/scripts/README.md | 74 ++- packages/scripts/config/jest-e2e.config.js | 26 + packages/scripts/config/jest-unit.config.js | 21 + packages/scripts/config/jest.config.js | 28 - .../scripts/config}/puppeteer.config.js | 0 packages/scripts/package.json | 2 + packages/scripts/scripts/test-e2e.js | 45 ++ packages/scripts/scripts/test-unit-jest.js | 19 +- packages/scripts/utils/cli.js | 85 +++ packages/scripts/utils/config.js | 23 + packages/scripts/utils/file.js | 32 + packages/scripts/utils/index.js | 109 +--- 16 files changed, 611 insertions(+), 423 deletions(-) create mode 100644 packages/scripts/config/jest-e2e.config.js create mode 100644 packages/scripts/config/jest-unit.config.js delete mode 100644 packages/scripts/config/jest.config.js rename {test/e2e => packages/scripts/config}/puppeteer.config.js (100%) create mode 100644 packages/scripts/scripts/test-e2e.js create mode 100644 packages/scripts/utils/cli.js create mode 100644 packages/scripts/utils/config.js create mode 100644 packages/scripts/utils/file.js diff --git a/package-lock.json b/package-lock.json index 8038ebf538c07..4a190ed1cb0d2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2323,6 +2323,7 @@ "version": "file:packages/babel-preset-default", "dev": true, "requires": { + "@babel/core": "^7.0.0", "@babel/plugin-proposal-async-generator-functions": "^7.0.0", "@babel/plugin-proposal-object-rest-spread": "^7.0.0", "@babel/plugin-transform-react-jsx": "^7.0.0", @@ -2770,7 +2771,9 @@ "cross-spawn": "^5.1.0", "eslint": "^4.19.1", "jest": "^23.6.0", + "jest-puppeteer": "3.2.1", "npm-package-json-lint": "^3.3.1", + "puppeteer": "1.6.1", "read-pkg-up": "^1.0.1", "resolve-bin": "^0.4.0" } @@ -3020,12 +3023,6 @@ "normalize-path": "^2.1.1" } }, - "app-root-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.1.0.tgz", - "integrity": "sha1-mL9lmTJ+zqGZMJhm6BQDaP0uZGo=", - "dev": true - }, "append-transform": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-0.4.0.tgz", @@ -7357,16 +7354,6 @@ } } }, - "enzyme-matchers": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/enzyme-matchers/-/enzyme-matchers-6.0.2.tgz", - "integrity": "sha1-eU1OQyVNqqD//TpZHlhp/IYd7rI=", - "dev": true, - "requires": { - "circular-json-es6": "^2.0.1", - "deep-equal-ident": "^1.1.1" - } - }, "enzyme-to-json": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/enzyme-to-json/-/enzyme-to-json-3.3.4.tgz", @@ -8015,9 +8002,9 @@ } }, "expect-puppeteer": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/expect-puppeteer/-/expect-puppeteer-3.2.0.tgz", - "integrity": "sha1-tMMi4ouG6edPXG1jb7Hg3eXEpVk=", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/expect-puppeteer/-/expect-puppeteer-3.5.1.tgz", + "integrity": "sha512-SB5JeJCXWSRcUK39fBJlCA6qnVt3BG1/M9vYZ+XYq8gY9jab9Jm4BztsrAwDTWca1L+O/7dRYrG2BPziRtjh+Q==", "dev": true }, "express": { @@ -8513,14 +8500,15 @@ } }, "find-process": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/find-process/-/find-process-1.1.1.tgz", - "integrity": "sha1-V/sa28f0MEeG23IKSf69cIoxYtQ=", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/find-process/-/find-process-1.2.1.tgz", + "integrity": "sha512-z4RXYStNAcoi4+smpKbzJXbMT8DdvwqTE7wL7DWZMD0SkTRfQ49z9S7YaK24kuRseKr23YSZlnyL/TaJZtgM1g==", "dev": true, "requires": { "chalk": "^2.0.1", "commander": "^2.11.0", - "debug": "^2.6.8" + "debug": "^2.6.8", + "lodash": "^4.17.11" }, "dependencies": { "debug": { @@ -8531,6 +8519,12 @@ "requires": { "ms": "2.0.0" } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true } } }, @@ -11309,18 +11303,6 @@ "source-map": "^0.6.0" } }, - "jest-validate": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-23.6.0.tgz", - "integrity": "sha512-OFKapYxe72yz7agrDAWi8v2WL8GIfVqcbKRCLbRG9PAxtzF9b1SEDdTpytNDN12z2fJynoBwpMpvj2R39plI2A==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "jest-get-type": "^22.1.0", - "leven": "^2.1.0", - "pretty-format": "^23.6.0" - } - }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", @@ -11461,16 +11443,6 @@ "source-map": "^0.5.7" } }, - "babel-jest": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-23.6.0.tgz", - "integrity": "sha512-lqKGG6LYXYu+DQh/slrQ8nxXQkEkhugdXsU6St7GmhVS7Ilc/22ArwqXNJrf0QaOBjZB0360qZMwXqDYQHXaew==", - "dev": true, - "requires": { - "babel-plugin-istanbul": "^4.1.6", - "babel-preset-jest": "^23.2.0" - } - }, "braces": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", @@ -11563,18 +11535,6 @@ } } }, - "jest-validate": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-23.6.0.tgz", - "integrity": "sha512-OFKapYxe72yz7agrDAWi8v2WL8GIfVqcbKRCLbRG9PAxtzF9b1SEDdTpytNDN12z2fJynoBwpMpvj2R39plI2A==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "jest-get-type": "^22.1.0", - "leven": "^2.1.0", - "pretty-format": "^23.6.0" - } - }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", @@ -11604,55 +11564,60 @@ "parse-glob": "^3.0.4", "regex-cache": "^0.4.2" } - }, - "pretty-format": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", - "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - } } } }, "jest-dev-server": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jest-dev-server/-/jest-dev-server-3.2.0.tgz", - "integrity": "sha1-Gf5g7hVgyv/E8W9In680R52c28Y=", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/jest-dev-server/-/jest-dev-server-3.6.0.tgz", + "integrity": "sha512-UbDPDBjpD3t9hjZ6z4j1NW8+jYE1rP5jJ6qVLbWsnpPgfJDBziOhhUSspSvyCG3DW+txK8/Xtw1lwwiEponWpg==", "dev": true, "requires": { "chalk": "^2.4.1", "cwd": "^0.10.0", - "find-process": "^1.1.1", - "inquirer": "^6.0.0", - "spawnd": "^2.0.0", - "terminate": "^2.1.0", + "find-process": "^1.2.1", + "inquirer": "^6.2.0", + "spawnd": "^3.5.2", + "terminate": "^2.1.2", "wait-port": "^0.2.2" }, "dependencies": { + "ansi-regex": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", + "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", + "dev": true + }, "chardet": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.5.0.tgz", - "integrity": "sha512-9ZTaoBaePSCFvNlNGrsyI8ZVACP2svUtq0DkM7t4K2ClAa96sqOIRjAzDTc8zXzFt1cZR46rRzLTiHFSJ+Qw0g==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, "external-editor": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.0.tgz", - "integrity": "sha512-mpkfj0FEdxrIhOC04zk85X7StNtr0yXnG7zCb+8ikO8OJi2jsHh5YGoknNTyXgsbHOf1WOOcVU3kPFWT2WgCkQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", + "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", "dev": true, "requires": { - "chardet": "^0.5.0", - "iconv-lite": "^0.4.22", + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", "tmp": "^0.0.33" } }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, "inquirer": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.0.0.tgz", - "integrity": "sha512-tISQWRwtcAgrz+SHPhTH7d3e73k31gsOy6i1csonLc0u1dVK/wYvuOnFeiWqC5OXFIYbmrIFInef31wbT8MEJg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.1.tgz", + "integrity": "sha512-088kl3DRT2dLU5riVMKKr1DlImd6X7smDhpXUCkJDCKvTEJeRiXh0G132HG9u5a+6Ylw9plFRY7RuTnwohYSpg==", "dev": true, "requires": { "ansi-escapes": "^3.0.0", @@ -11661,23 +11626,32 @@ "cli-width": "^2.0.0", "external-editor": "^3.0.0", "figures": "^2.0.0", - "lodash": "^4.3.0", + "lodash": "^4.17.10", "mute-stream": "0.0.7", "run-async": "^2.2.0", "rxjs": "^6.1.0", "string-width": "^2.1.0", - "strip-ansi": "^4.0.0", + "strip-ansi": "^5.0.0", "through": "^2.3.6" } }, "rxjs": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.2.2.tgz", - "integrity": "sha512-0MI8+mkKAXZUF9vMrEoPnaoHkfzBPP4IGwUYRJhIRJF6/w3uByO1e91bEHn8zd43RdkTMKiooYKmwz7RH6zfOQ==", + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", + "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", "dev": true, "requires": { "tslib": "^1.9.0" } + }, + "strip-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.0.0.tgz", + "integrity": "sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow==", + "dev": true, + "requires": { + "ansi-regex": "^4.0.0" + } } } }, @@ -11691,18 +11665,6 @@ "diff": "^3.2.0", "jest-get-type": "^22.1.0", "pretty-format": "^23.6.0" - }, - "dependencies": { - "pretty-format": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", - "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - } - } } }, "jest-docblock": { @@ -11722,24 +11684,12 @@ "requires": { "chalk": "^2.0.1", "pretty-format": "^23.6.0" - }, - "dependencies": { - "pretty-format": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", - "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - } - } } }, "jest-environment-enzyme": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/jest-environment-enzyme/-/jest-environment-enzyme-6.0.2.tgz", - "integrity": "sha1-iQUmYi1d6KggoYSdJoTwO8AG4O8=", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/jest-environment-enzyme/-/jest-environment-enzyme-6.1.2.tgz", + "integrity": "sha512-WHeBKgBYOdryuOTEoK55lJwjg7Raery1OgXHLwukI3mSYgOkm2UrCDDT+vneqVgy7F8KuRHyStfD+TC/m2b7Kg==", "dev": true, "requires": { "jest-environment-jsdom": "^22.4.1" @@ -11884,28 +11834,38 @@ } }, "jest-environment-puppeteer": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/jest-environment-puppeteer/-/jest-environment-puppeteer-3.2.1.tgz", - "integrity": "sha1-xS6aqY8HtS83hyHV8f8PAAxODp8=", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/jest-environment-puppeteer/-/jest-environment-puppeteer-3.6.0.tgz", + "integrity": "sha512-3ULqgH6f+HRu53wxP0NDFb8uZFxn2+97tK4eZicktgst/zWpSJucEpbsVVNWk4cIHrPo79rYoUfomxnui/ndAg==", "dev": true, "requires": { "chalk": "^2.4.1", "cwd": "^0.10.0", - "jest-dev-server": "^3.2.0", - "lodash": "^4.17.10", - "mkdirp": "^0.5.1", - "rimraf": "^2.6.2" + "jest-dev-server": "^3.6.0", + "merge-deep": "^3.0.2" } }, "jest-enzyme": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/jest-enzyme/-/jest-enzyme-6.0.2.tgz", - "integrity": "sha1-vEZBad5sLVBgLgK7yUZEYFB/spU=", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/jest-enzyme/-/jest-enzyme-6.1.2.tgz", + "integrity": "sha512-+ds7r2ru3QkNJxelQ2tnC6d33pjUSsZHPD3v4TlnHlNMuGX3UKdxm5C46yZBvJICYBvIF+RFKBhLMM4evNM95Q==", "dev": true, "requires": { - "enzyme-matchers": "^6.0.2", + "enzyme-matchers": "^6.1.2", "enzyme-to-json": "^3.3.0", - "jest-environment-enzyme": "^6.0.2" + "jest-environment-enzyme": "^6.1.2" + }, + "dependencies": { + "enzyme-matchers": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/enzyme-matchers/-/enzyme-matchers-6.1.2.tgz", + "integrity": "sha512-cP9p+HMOZ1ZXQ+k2H4dCkxmTZzIvpEy5zv0ZjgoBl6D0U43v+bJGH5IeWHdIovCzgJ0dVcMCKJ6lNu83lYUCAA==", + "dev": true, + "requires": { + "circular-json-es6": "^2.0.1", + "deep-equal-ident": "^1.1.1" + } + } } }, "jest-get-type": { @@ -12070,17 +12030,6 @@ "is-extglob": "^1.0.0" } }, - "jest-matcher-utils": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-23.6.0.tgz", - "integrity": "sha512-rosyCHQfBcol4NsckTn01cdelzWLU9Cq7aaigDf8VwwpIRvWE/9zLgX2bON+FkEW69/0UuYslUe22SOdEf2nog==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "jest-get-type": "^22.1.0", - "pretty-format": "^23.6.0" - } - }, "jest-message-util": { "version": "23.4.0", "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-23.4.0.tgz", @@ -12140,16 +12089,6 @@ "regex-cache": "^0.4.2" } }, - "pretty-format": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", - "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - } - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -12165,18 +12104,6 @@ "dev": true, "requires": { "pretty-format": "^23.6.0" - }, - "dependencies": { - "pretty-format": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", - "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - } - } } }, "jest-matcher-utils": { @@ -12610,18 +12537,6 @@ } } }, - "jest-validate": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-23.6.0.tgz", - "integrity": "sha512-OFKapYxe72yz7agrDAWi8v2WL8GIfVqcbKRCLbRG9PAxtzF9b1SEDdTpytNDN12z2fJynoBwpMpvj2R39plI2A==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "jest-get-type": "^22.1.0", - "leven": "^2.1.0", - "pretty-format": "^23.6.0" - } - }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", @@ -12652,16 +12567,6 @@ "regex-cache": "^0.4.2" } }, - "pretty-format": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", - "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - } - }, "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -12767,17 +12672,6 @@ "is-extglob": "^1.0.0" } }, - "jest-matcher-utils": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-23.6.0.tgz", - "integrity": "sha512-rosyCHQfBcol4NsckTn01cdelzWLU9Cq7aaigDf8VwwpIRvWE/9zLgX2bON+FkEW69/0UuYslUe22SOdEf2nog==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "jest-get-type": "^22.1.0", - "pretty-format": "^23.6.0" - } - }, "jest-message-util": { "version": "23.4.0", "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-23.4.0.tgz", @@ -12820,16 +12714,6 @@ "parse-glob": "^3.0.4", "regex-cache": "^0.4.2" } - }, - "pretty-format": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", - "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - } } } }, @@ -12857,27 +12741,15 @@ } }, "jest-validate": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-23.4.0.tgz", - "integrity": "sha1-2W7t4B7wOskJwAnpyORVGX1IwgE=", + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-23.6.0.tgz", + "integrity": "sha512-OFKapYxe72yz7agrDAWi8v2WL8GIfVqcbKRCLbRG9PAxtzF9b1SEDdTpytNDN12z2fJynoBwpMpvj2R39plI2A==", "dev": true, "requires": { "chalk": "^2.0.1", "jest-get-type": "^22.1.0", "leven": "^2.1.0", - "pretty-format": "^23.2.0" - }, - "dependencies": { - "pretty-format": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.2.0.tgz", - "integrity": "sha1-OwqqY8AYpTWDNzwcs6XZbMXoMBc=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - } - } + "pretty-format": "^23.6.0" } }, "jest-watcher": { @@ -13295,8 +13167,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", - "dev": true, - "optional": true + "dev": true }, "lcid": { "version": "1.0.0", @@ -13407,12 +13278,11 @@ } }, "lint-staged": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-7.2.0.tgz", - "integrity": "sha512-jPoIMbmgtWMUrz/l0rhBVa1j6H71zr0rEoxDWBA333PZcaqBvELdg0Sf4tdGHlwrBM0GXaXMVgTRkLTm2vA7Jg==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-7.3.0.tgz", + "integrity": "sha512-AXk40M9DAiPi7f4tdJggwuKIViUplYtVj1os1MVEteW7qOkU50EOehayCfO9TsoGK24o/EsWb41yrEgfJDDjCw==", "dev": true, "requires": { - "app-root-path": "^2.0.1", "chalk": "^2.3.1", "commander": "^2.14.1", "cosmiconfig": "^5.0.2", @@ -13422,7 +13292,7 @@ "find-parent-dir": "^0.3.0", "is-glob": "^4.0.0", "is-windows": "^1.0.2", - "jest-validate": "^23.0.0", + "jest-validate": "^23.5.0", "listr": "^0.14.1", "lodash": "^4.17.5", "log-symbols": "^2.2.0", @@ -13474,15 +13344,6 @@ "object-assign": "^4.1.0" } }, - "indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "dev": true, - "requires": { - "repeating": "^2.0.0" - } - }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -13508,32 +13369,49 @@ } }, "listr": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/listr/-/listr-0.14.1.tgz", - "integrity": "sha512-MSMUUVN1f8aRnPi4034RkOqdiUlpYW+FqwFE3aL0uYNPRavkt2S2SsSpDDofn8BDpqv2RNnsdOcCHWsChcq77A==", + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/listr/-/listr-0.14.3.tgz", + "integrity": "sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA==", "dev": true, "requires": { "@samverschueren/stream-to-observable": "^0.3.0", - "cli-truncate": "^0.2.1", - "figures": "^1.7.0", - "indent-string": "^2.1.0", "is-observable": "^1.1.0", "is-promise": "^2.1.0", "is-stream": "^1.1.0", "listr-silent-renderer": "^1.1.1", - "listr-update-renderer": "^0.4.0", - "listr-verbose-renderer": "^0.4.0", + "listr-update-renderer": "^0.5.0", + "listr-verbose-renderer": "^0.5.0", + "p-map": "^2.0.0", + "rxjs": "^6.3.3" + }, + "dependencies": { + "p-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.0.0.tgz", + "integrity": "sha512-GO107XdrSUmtHxVoi60qc9tUl/KkNKm+X2CF4P9amalpGxv5YqVPJNfSb0wcA+syCopkZvYYIzW8OVTQW59x/w==", + "dev": true + } + } + }, + "listr-update-renderer": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz", + "integrity": "sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "cli-truncate": "^0.2.1", + "elegant-spinner": "^1.0.1", + "figures": "^1.7.0", + "indent-string": "^3.0.0", "log-symbols": "^1.0.2", - "log-update": "^1.0.2", - "ora": "^0.2.3", - "p-map": "^1.1.1", - "rxjs": "^6.1.0", + "log-update": "^2.3.0", "strip-ansi": "^3.0.1" }, "dependencies": { "chalk": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -13555,6 +13433,40 @@ } } }, + "listr-verbose-renderer": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.5.0.tgz", + "integrity": "sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "cli-cursor": "^2.1.0", + "date-fns": "^1.27.2", + "figures": "^2.0.0" + }, + "dependencies": { + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + } + } + }, + "log-update": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-2.3.0.tgz", + "integrity": "sha1-iDKP19HOeTiykoN0bwsbwSayRwg=", + "dev": true, + "requires": { + "ansi-escapes": "^3.0.0", + "cli-cursor": "^2.0.0", + "wrap-ansi": "^3.0.1" + } + }, "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", @@ -13562,9 +13474,9 @@ "dev": true }, "rxjs": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.2.2.tgz", - "integrity": "sha512-0MI8+mkKAXZUF9vMrEoPnaoHkfzBPP4IGwUYRJhIRJF6/w3uByO1e91bEHn8zd43RdkTMKiooYKmwz7RH6zfOQ==", + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", + "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", "dev": true, "requires": { "tslib": "^1.9.0" @@ -13584,6 +13496,33 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", "dev": true + }, + "wrap-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz", + "integrity": "sha1-KIoE2H7aXChuBg3+jxNc6NAH+Lo=", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } } } }, @@ -13679,6 +13618,30 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", "dev": true + }, + "wrap-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz", + "integrity": "sha1-KIoE2H7aXChuBg3+jxNc6NAH+Lo=", + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } } } }, @@ -14573,6 +14536,70 @@ "integrity": "sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==", "dev": true }, + "merge-deep": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.2.tgz", + "integrity": "sha512-T7qC8kg4Zoti1cFd8Cr0M+qaZfOwjlPDEdZIIPPB2JZctjaPM4fX+i7HOId69tAti2fvO6X5ldfYUONDODsrkA==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "clone-deep": "^0.2.4", + "kind-of": "^3.0.2" + }, + "dependencies": { + "clone-deep": { + "version": "0.2.4", + "resolved": "http://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", + "integrity": "sha1-TnPdCen7lxzDhnDF3O2cGJZIHMY=", + "dev": true, + "requires": { + "for-own": "^0.1.3", + "is-plain-object": "^2.0.1", + "kind-of": "^3.0.2", + "lazy-cache": "^1.0.3", + "shallow-clone": "^0.1.2" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, + "shallow-clone": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", + "integrity": "sha1-WQnodLp3EG1zrEFM/sH/yofZcGA=", + "dev": true, + "requires": { + "is-extendable": "^0.1.1", + "kind-of": "^2.0.1", + "lazy-cache": "^0.2.3", + "mixin-object": "^2.0.1" + }, + "dependencies": { + "kind-of": { + "version": "2.0.1", + "resolved": "http://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", + "integrity": "sha1-AY7HpM5+OobLkUG+UZ0kyPqpgbU=", + "dev": true, + "requires": { + "is-buffer": "^1.0.2" + } + }, + "lazy-cache": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz", + "integrity": "sha1-f+3fLctu23fRHvHRF6tf/fCrG2U=", + "dev": true + } + } + } + } + }, "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -14632,9 +14659,9 @@ } }, "mime": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz", - "integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz", + "integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==", "dev": true }, "mime-db": { @@ -17670,12 +17697,12 @@ "dev": true }, "ps-tree": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.1.0.tgz", - "integrity": "sha1-tCGyQUDWID8e08dplrRCewjowBQ=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", + "integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==", "dev": true, "requires": { - "event-stream": "~3.3.0" + "event-stream": "=3.3.4" } }, "pseudomap": { @@ -19644,14 +19671,14 @@ "dev": true }, "spawnd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/spawnd/-/spawnd-2.0.0.tgz", - "integrity": "sha1-0gIEA9xe9y7Kw/+rm0L6Fq6RdfU=", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/spawnd/-/spawnd-3.5.2.tgz", + "integrity": "sha512-taf6nYLIl8b3b1RNt0YuxnIUUgHqfx+nix8Rdr2FkNG8259+Jt8YJahrPDShOPa9vCMnDPfPsefRAY/oJy+QBg==", "dev": true, "requires": { "exit": "^0.1.2", "signal-exit": "^3.0.2", - "terminate": "^2.1.0", + "terminate": "^2.1.2", "wait-port": "^0.2.2" } }, @@ -20591,12 +20618,12 @@ } }, "terminate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/terminate/-/terminate-2.1.0.tgz", - "integrity": "sha1-qH7kJL4BodKPLzAQRQQ6W81nmgU=", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/terminate/-/terminate-2.1.2.tgz", + "integrity": "sha512-ltKc9MkgcRe7gzD7XSttHCF1feKM1pTkCdb58jFVWk1efPN9JIk/BHSlOaYF+hCcWoubeJQ8C8Phb0++fa6iNQ==", "dev": true, "requires": { - "ps-tree": "^1.1.0" + "ps-tree": "^1.1.1" } }, "test-exclude": { diff --git a/package.json b/package.json index 03a6bdd8f7616..a214f2987eca3 100644 --- a/package.json +++ b/package.json @@ -90,13 +90,12 @@ "jest-puppeteer": "3.2.1", "jsdom": "11.12.0", "lerna": "3.4.3", - "lint-staged": "7.2.0", + "lint-staged": "7.3.0", "lodash": "4.17.10", "mkdirp": "0.5.1", "node-sass": "4.11.0", "pegjs": "0.10.0", "phpegjs": "1.0.0-beta7", - "puppeteer": "1.6.1", "react-dom": "16.6.3", "react-test-renderer": "16.6.3", "redux": "4.0.0", @@ -179,7 +178,7 @@ "publish:prod": "npm run build:packages && lerna publish", "test": "npm run lint && npm run test-unit", "pretest-e2e": "concurrently \"./bin/reset-e2e-tests.sh\" \"npm run build\"", - "test-e2e": "cross-env JEST_PUPPETEER_CONFIG=test/e2e/puppeteer.config.js wp-scripts test-unit-js --config test/e2e/jest.config.json --runInBand", + "test-e2e": "wp-scripts test-e2e --config test/e2e/jest.config.json", "test-e2e:watch": "npm run test-e2e -- --watch", "test-php": "npm run lint-php && npm run test-unit-php", "test-unit": "wp-scripts test-unit-js --config test/unit/jest.config.json", diff --git a/packages/babel-preset-default/package.json b/packages/babel-preset-default/package.json index 08149a01f9b6b..95bc302227f14 100644 --- a/packages/babel-preset-default/package.json +++ b/packages/babel-preset-default/package.json @@ -23,6 +23,7 @@ }, "main": "index.js", "dependencies": { + "@babel/core": "^7.0.0", "@babel/plugin-proposal-async-generator-functions": "^7.0.0", "@babel/plugin-proposal-object-rest-spread": "^7.0.0", "@babel/plugin-transform-react-jsx": "^7.0.0", diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index 1af69f533f2fa..119e734319d31 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -3,6 +3,7 @@ ### New Features - Added support for `check-engines` script ([#12721](https://github.com/WordPress/gutenberg/pull/12721)) +- Added support for `test-e2e` script ([#12437](https://github.com/WordPress/gutenberg/pull/12437)). - Update default config provided for `lint-js` script ([#12845](https://github.com/WordPress/gutenberg/pull/12845)). ## 2.4.4 (2018-11-20) diff --git a/packages/scripts/README.md b/packages/scripts/README.md index b240177fbc631..2d578ef1b870b 100644 --- a/packages/scripts/README.md +++ b/packages/scripts/README.md @@ -20,8 +20,11 @@ _Example:_ { "scripts": { "check-engines": "wp-scripts check-engines", + "check-licenses": "wp-scripts check-licenses --production", + "lint:js": "wp-scripts lint-js .", "lint:pkg-json": "wp-scripts lint-pkg-json .", - "test": "wp-scripts test-unit-js" + "test:e2e": "wp-scripts test-e2e", + "test:unit": "wp-scripts test-unit-js" } } ``` @@ -45,8 +48,28 @@ _Example:_ This is how you execute the script with presented setup: * `npm run check-engines` - checks installed version of `node` and `npm`. +### `check-licenses` -### `wp-scripts lint-js` +Validates that all dependencies of a project are compatible with the project's own license. + +_Example:_ + +```json +{ + "scripts": { + "check-licenses": "wp-scripts check-licenses --prod --gpl2 --ignore=abab" + } +} +``` + +_Flags_: + +- `--prod` (or `--production`): When present, validates only `dependencies` and not `devDependencies` +- `--dev` (or `--development`): When present, validates both `dependencies` and `devDependencies` +- `--gpl2`: Validates against [GPLv2 license compatibility](https://www.gnu.org/licenses/license-list.en.html) +- `--ignore=a,b,c`: A comma-separated set of package names to ignore for validation. This is intended to be used primarily in cases where a dependency's `license` field is malformed. It's assumed that any `ignored` package argument would be manually vetted for compatibility by the project owner. + +### `lint-js` Helps enforce coding style guidelines for your JavaScript files. It uses [eslint](https://eslint.org/) with the set of recommended rules defined in [@wordpress/eslint-plugin](https://www.npmjs.com/package/@wordpress/eslint-plugin) npm package. You can override default rules with your own as described in [eslint docs](https://eslint.org/docs/rules/). @@ -63,7 +86,7 @@ _Example:_ This is how you execute the script with presented setup: * `npm run lint:js` - lints JavaScripts files in the whole project's. -### `wp-scripts lint-pkg-json` +### `lint-pkg-json` Helps enforce standards for your package.json files. It uses [npm-package-json-lint](https://www.npmjs.com/package/npm-package-json-lint) with the set of recommended rules defined in [@wordpress/npm-package-json-lint-config](https://www.npmjs.com/package/@wordpress/npm-package-json-lint-config) npm package. You can override default rules with your own as described in [npm-package-json-lint wiki](https://github.com/tclindner/npm-package-json-lint/wiki). @@ -80,52 +103,59 @@ _Example:_ This is how you execute those scripts using the presented setup: * `npm run lint:pkg-json` - lints `package.json` file in the project's root folder. -### `wp-scripts test-unit-js` +### `test-e2e` -_Alias_: `wp-scripts test-unit-jest` +Launches the End-To-End (E2E) test runner. It uses [Jest](https://facebook.github.io/jest/) behind the scenes and you are able to utilize all of its [CLI options](https://facebook.github.io/jest/docs/en/cli.html). You can also run `./node_modules/.bin/wp-scripts test:e2e --help` or `npm run test:e2e:help` (as presented below) to view all of the available options. -Launches the test runner. It uses [Jest](https://facebook.github.io/jest/) behind the scenes and you are able to utilize all of its [CLI options](https://facebook.github.io/jest/docs/en/cli.html). You can also run `./node_modules/.bin/wp-scripts test-unit-js --help` or `npm run test:help` (as presented below) to view all of the available options. By default, it uses the set of recommended options defined in [@wordpress/jest-preset-default](https://www.npmjs.com/package/@wordpress/jest-preset-default) npm package. You can override them with your own options as described in [Jest documentation](https://jestjs.io/docs/en/configuration). +Writing tests can be done using Puppeteer API: + +> [Puppeteer](https://pptr.dev/) is a Node library which provides a high-level API to control Chrome or Chromium over the [DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/). Puppeteer runs [headless](https://developers.google.com/web/updates/2017/04/headless-chrome) by default, but can be configured to run full (non-headless) Chrome or Chromium. _Example:_ ```json { "scripts": { - "test": "wp-scripts test-unit-js", - "test:help": "wp-scripts test-unit-js --help", - "test:watch": "wp-scripts test-unit-js --watch" + "test:e2e": "wp-scripts test-e2e", + "test:e2e:help": "wp-scripts test-e2e --help" } } ``` This is how you execute those scripts using the presented setup: -* `npm run test` or `npm test` - runs all unit tests. -* `npm run test:help` - prints all available options to configure unit tests runner. -* `npm run test:watch` - runs all unit tests in the watch mode. +* `npm run test:e2e` - runs all unit tests. +* `npm run test:e2e:help` - prints all available options to configure unit tests runner. -### `wp-scripts check-licenses` +This script automatically detects the best config to start Puppeteer but sometimes you may need to specify custom options: + - You can add a `jest-puppeteer.config.js` at the root of the project or define a custom path using `JEST_PUPPETEER_CONFIG` environment variable. Check [jest-puppeteer](https://github.com/smooth-code/jest-puppeteer#jest-puppeteerconfigjs) for more details. -Validates that all dependencies of a project are compatible with the project's own license. +We enforce that all tests run serially in the current process using [--runInBand](https://jestjs.io/docs/en/cli#runinband) Jest CLI option to avoid conflicts between tests caused by the fact that they share the same WordPress instance. + +### `test-unit-js` + +_Alias_: `test-unit-jest` + +Launches the unit test runner. It uses [Jest](https://facebook.github.io/jest/) behind the scenes and you are able to utilize all of its [CLI options](https://facebook.github.io/jest/docs/en/cli.html). You can also run `./node_modules/.bin/wp-scripts test-unit-js --help` or `npm run test:unit:help` (as presented below) to view all of the available options. By default, it uses the set of recommended options defined in [@wordpress/jest-preset-default](https://www.npmjs.com/package/@wordpress/jest-preset-default) npm package. You can override them with your own options as described in [Jest documentation](https://jestjs.io/docs/en/configuration). _Example:_ ```json { "scripts": { - "check-licenses": "wp-scripts check-licenses --prod --gpl2 --ignore=abab", + "test:unit": "wp-scripts test-unit-js", + "test:unit:help": "wp-scripts test-unit-js --help", + "test:unit:watch": "wp-scripts test-unit-js --watch" } } ``` -_Flags_: - -- `--prod` (or `--production`): When present, validates only `dependencies` and not `devDependencies` -- `--dev` (or `--development`): When present, validates both `dependencies` and `devDependencies` -- `--gpl2`: Validates against [GPLv2 license compatibility](https://www.gnu.org/licenses/license-list.en.html) -- `--ignore=a,b,c`: A comma-separated set of package names to ignore for validation. This is intended to be used primarily in cases where a dependency's `license` field is malformed. It's assumed that any `ignored` package argument would be manually vetted for compatibility by the project owner. +This is how you execute those scripts using the presented setup: +* `npm run test:unit` - runs all unit tests. +* `npm run test:unit:help` - prints all available options to configure unit tests runner. +* `npm run test:unit:watch` - runs all unit tests in the watch mode. ## Inspiration -This is inspired by [react-scripts](https://www.npmjs.com/package/react-scripts) and [kcd-scripts](https://www.npmjs.com/package/kcd-scripts). +This package is inspired by [react-scripts](https://www.npmjs.com/package/react-scripts) and [kcd-scripts](https://www.npmjs.com/package/kcd-scripts).

    Code is Poetry.

    diff --git a/packages/scripts/config/jest-e2e.config.js b/packages/scripts/config/jest-e2e.config.js new file mode 100644 index 0000000000000..fd8d722b8c3fc --- /dev/null +++ b/packages/scripts/config/jest-e2e.config.js @@ -0,0 +1,26 @@ +/** + * External dependencies + */ +const path = require( 'path' ); + +/** + * Internal dependencies + */ +const { hasBabelConfig } = require( '../utils' ); + +const jestE2EConfig = { + preset: 'jest-puppeteer', + testMatch: [ + '**/__tests__/**/*.js', + '**/?(*.)(spec|test).js', + '**/test/*.js', + ], +}; + +if ( ! hasBabelConfig() ) { + jestE2EConfig.transform = { + '^.+\\.jsx?$': path.join( __dirname, 'babel-transform' ), + }; +} + +module.exports = jestE2EConfig; diff --git a/packages/scripts/config/jest-unit.config.js b/packages/scripts/config/jest-unit.config.js new file mode 100644 index 0000000000000..34f48ea0e3376 --- /dev/null +++ b/packages/scripts/config/jest-unit.config.js @@ -0,0 +1,21 @@ +/** + * External dependencies + */ +const path = require( 'path' ); + +/** + * Internal dependencies + */ +const { hasBabelConfig } = require( '../utils' ); + +const jestUnitConfig = { + preset: '@wordpress/jest-preset-default', +}; + +if ( ! hasBabelConfig() ) { + jestUnitConfig.transform = { + '^.+\\.jsx?$': path.join( __dirname, 'babel-transform' ), + }; +} + +module.exports = jestUnitConfig; diff --git a/packages/scripts/config/jest.config.js b/packages/scripts/config/jest.config.js deleted file mode 100644 index b884ac499eddb..0000000000000 --- a/packages/scripts/config/jest.config.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * External dependencies - */ -const path = require( 'path' ); - -/** - * Internal dependencies - */ -const { - hasProjectFile, - hasPackageProp, -} = require( '../utils' ); - -const jestConfig = { - preset: '@wordpress/jest-preset-default', -}; - -const hasBabelConfig = hasProjectFile( '.babelrc' ) || - hasProjectFile( 'babel.config.js' ) || - hasPackageProp( 'babel' ); - -if ( ! hasBabelConfig ) { - jestConfig.transform = { - '^.+\\.jsx?$': path.join( __dirname, 'babel-transform' ), - }; -} - -module.exports = jestConfig; diff --git a/test/e2e/puppeteer.config.js b/packages/scripts/config/puppeteer.config.js similarity index 100% rename from test/e2e/puppeteer.config.js rename to packages/scripts/config/puppeteer.config.js diff --git a/packages/scripts/package.json b/packages/scripts/package.json index c6232c62d586c..4ee9bdb00850c 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -40,7 +40,9 @@ "cross-spawn": "^5.1.0", "eslint": "^4.19.1", "jest": "^23.6.0", + "jest-puppeteer": "3.2.1", "npm-package-json-lint": "^3.3.1", + "puppeteer": "1.6.1", "read-pkg-up": "^1.0.1", "resolve-bin": "^0.4.0" }, diff --git a/packages/scripts/scripts/test-e2e.js b/packages/scripts/scripts/test-e2e.js new file mode 100644 index 0000000000000..fc2e8b42f345d --- /dev/null +++ b/packages/scripts/scripts/test-e2e.js @@ -0,0 +1,45 @@ +// Do this as the first thing so that any code reading it knows the right env. +process.env.BABEL_ENV = 'test'; +process.env.NODE_ENV = 'test'; + +// Makes the script crash on unhandled rejections instead of silently +// ignoring them. In the future, promise rejections that are not handled will +// terminate the Node.js process with a non-zero exit code. +process.on( 'unhandledRejection', ( err ) => { + throw err; +} ); + +/** + * External dependencies + */ +const jest = require( 'jest' ); + +/** + * Internal dependencies + */ +const { + fromConfigRoot, + getCliArgs, + hasCliArg, + hasProjectFile, + hasJestConfig, +} = require( '../utils' ); + +// Provides a default config path for Puppeteer when jest-puppeteer.config.js +// wasn't found at the root of the project or a custom path wasn't defined +// using JEST_PUPPETEER_CONFIG environment variable. +if ( ! hasProjectFile( 'jest-puppeteer.config.js' ) && ! process.env.JEST_PUPPETEER_CONFIG ) { + process.env.JEST_PUPPETEER_CONFIG = fromConfigRoot( 'puppeteer.config.js' ); +} + +const config = ! hasJestConfig() ? + [ '--config', JSON.stringify( require( fromConfigRoot( 'jest-e2e.config.js' ) ) ) ] : + []; + +const hasRunInBand = hasCliArg( '--runInBand' ) || + hasCliArg( '-i' ); +const runInBand = ! hasRunInBand ? + [ '--runInBand' ] : + []; + +jest.run( [ ...config, ...runInBand, ...getCliArgs() ] ); diff --git a/packages/scripts/scripts/test-unit-jest.js b/packages/scripts/scripts/test-unit-jest.js index 2d511c0795635..1fb5743ac861c 100644 --- a/packages/scripts/scripts/test-unit-jest.js +++ b/packages/scripts/scripts/test-unit-jest.js @@ -18,22 +18,13 @@ const jest = require( 'jest' ); * Internal dependencies */ const { + fromConfigRoot, getCliArgs, - hasCliArg, - hasProjectFile, - hasPackageProp, + hasJestConfig, } = require( '../utils' ); -const args = getCliArgs(); - -const hasJestConfig = hasCliArg( '-c' ) || - hasCliArg( '--config' ) || - hasProjectFile( 'jest.config.js' ) || - hasProjectFile( 'jest.config.json' ) || - hasPackageProp( 'jest' ); - -const config = ! hasJestConfig ? - [ '--config', JSON.stringify( require( '../config/jest.config' ) ) ] : +const config = ! hasJestConfig() ? + [ '--config', JSON.stringify( require( fromConfigRoot( 'jest-unit.config.js' ) ) ) ] : []; -jest.run( [ ...config, ...args ] ); +jest.run( [ ...config, ...getCliArgs() ] ); diff --git a/packages/scripts/utils/cli.js b/packages/scripts/utils/cli.js new file mode 100644 index 0000000000000..3778379ced192 --- /dev/null +++ b/packages/scripts/utils/cli.js @@ -0,0 +1,85 @@ +/** + * External dependencies + */ +const spawn = require( 'cross-spawn' ); + +/** + * Internal dependencies + */ +const { + fromScriptsRoot, + hasScriptFile, +} = require( './file' ); +const { + exit, + getCliArgs, +} = require( './process' ); + +const getCliArg = ( arg ) => { + for ( const cliArg of getCliArgs() ) { + const [ name, value ] = cliArg.split( '=' ); + if ( name === arg ) { + return value || null; + } + } +}; + +const hasCliArg = ( arg ) => getCliArg( arg ) !== undefined; + +const handleSignal = ( signal ) => { + if ( signal === 'SIGKILL' ) { + // eslint-disable-next-line no-console + console.log( + 'The script failed because the process exited too early. ' + + 'This probably means the system ran out of memory or someone called ' + + '`kill -9` on the process.' + ); + } else if ( signal === 'SIGTERM' ) { + // eslint-disable-next-line no-console + console.log( + 'The script failed because the process exited too early. ' + + 'Someone might have called `kill` or `killall`, or the system could ' + + 'be shutting down.' + ); + } + exit( 1 ); +}; + +const spawnScript = ( scriptName, args = [] ) => { + if ( ! scriptName ) { + // eslint-disable-next-line no-console + console.log( 'Script name is missing.' ); + exit( 1 ); + } + + if ( ! hasScriptFile( scriptName ) ) { + // eslint-disable-next-line no-console + console.log( + 'Unknown script "' + scriptName + '". ' + + 'Perhaps you need to update @wordpress/scripts?' + ); + exit( 1 ); + } + + const { signal, status } = spawn.sync( + 'node', + [ + fromScriptsRoot( scriptName ), + ...args, + ], + { stdio: 'inherit' }, + ); + + if ( signal ) { + handleSignal( signal ); + } + + exit( status ); +}; + +module.exports = { + getCliArg, + getCliArgs, + hasCliArg, + spawnScript, +}; diff --git a/packages/scripts/utils/config.js b/packages/scripts/utils/config.js new file mode 100644 index 0000000000000..6be8ff91f0a4d --- /dev/null +++ b/packages/scripts/utils/config.js @@ -0,0 +1,23 @@ +/** + * Internal dependencies + */ +const { hasCliArg } = require( './cli' ); +const { hasProjectFile } = require( './file' ); +const { hasPackageProp } = require( './package' ); + +const hasBabelConfig = () => + hasProjectFile( '.babelrc' ) || + hasProjectFile( 'babel.config.js' ) || + hasPackageProp( 'babel' ); + +const hasJestConfig = () => + hasCliArg( '-c' ) || + hasCliArg( '--config' ) || + hasProjectFile( 'jest.config.js' ) || + hasProjectFile( 'jest.config.json' ) || + hasPackageProp( 'jest' ); + +module.exports = { + hasBabelConfig, + hasJestConfig, +}; diff --git a/packages/scripts/utils/file.js b/packages/scripts/utils/file.js new file mode 100644 index 0000000000000..7f66c5e0ade78 --- /dev/null +++ b/packages/scripts/utils/file.js @@ -0,0 +1,32 @@ +/** + * External dependencies + */ +const { existsSync } = require( 'fs' ); +const path = require( 'path' ); + +/** + * Internal dependencies + */ +const { getPackagePath } = require( './package' ); + +const fromProjectRoot = ( fileName ) => + path.join( path.dirname( getPackagePath() ), fileName ); + +const hasProjectFile = ( fileName ) => + existsSync( fromProjectRoot( fileName ) ); + +const fromConfigRoot = ( fileName ) => + path.join( path.dirname( __dirname ), 'config', fileName ); + +const fromScriptsRoot = ( scriptName ) => + path.join( path.dirname( __dirname ), 'scripts', `${ scriptName }.js` ); + +const hasScriptFile = ( scriptName ) => + existsSync( fromScriptsRoot( scriptName ) ); + +module.exports = { + fromConfigRoot, + fromScriptsRoot, + hasProjectFile, + hasScriptFile, +}; diff --git a/packages/scripts/utils/index.js b/packages/scripts/utils/index.js index cda8dc66ddaf6..473952fcdfc17 100644 --- a/packages/scripts/utils/index.js +++ b/packages/scripts/utils/index.js @@ -1,99 +1,32 @@ -/** - * External dependencies - */ -const spawn = require( 'cross-spawn' ); -const { existsSync } = require( 'fs' ); -const path = require( 'path' ); - /** * Internal dependencies */ -const { getPackagePath, hasPackageProp } = require( './package' ); -const { exit, getCliArgs } = require( './process' ); - -const getCliArg = ( arg ) => { - for ( const cliArg of getCliArgs() ) { - const [ name, value ] = cliArg.split( '=' ); - if ( name === arg ) { - return value || null; - } - } -}; - -const hasCliArg = ( arg ) => getCliArg( arg ) !== undefined; - -const fromProjectRoot = ( fileName ) => - path.join( path.dirname( getPackagePath() ), fileName ); - -const hasProjectFile = ( fileName ) => - existsSync( fromProjectRoot( fileName ) ); - -const fromConfigRoot = ( fileName ) => - path.join( path.dirname( __dirname ), 'config', fileName ); - -const fromScriptsRoot = ( scriptName ) => - path.join( path.dirname( __dirname ), 'scripts', `${ scriptName }.js` ); - -const hasScriptFile = ( scriptName ) => - existsSync( fromScriptsRoot( scriptName ) ); - -const handleSignal = ( signal ) => { - if ( signal === 'SIGKILL' ) { - // eslint-disable-next-line no-console - console.log( - 'The script failed because the process exited too early. ' + - 'This probably means the system ran out of memory or someone called ' + - '`kill -9` on the process.' - ); - } else if ( signal === 'SIGTERM' ) { - // eslint-disable-next-line no-console - console.log( - 'The script failed because the process exited too early. ' + - 'Someone might have called `kill` or `killall`, or the system could ' + - 'be shutting down.' - ); - } - exit( 1 ); -}; - -const spawnScript = ( scriptName, args = [] ) => { - if ( ! scriptName ) { - // eslint-disable-next-line no-console - console.log( 'Script name is missing.' ); - exit( 1 ); - } - - if ( ! hasScriptFile( scriptName ) ) { - // eslint-disable-next-line no-console - console.log( - 'Unknown script "' + scriptName + '". ' + - 'Perhaps you need to update @wordpress/scripts?' - ); - exit( 1 ); - } - - const { signal, status } = spawn.sync( - 'node', - [ - fromScriptsRoot( scriptName ), - ...args, - ], - { stdio: 'inherit' }, - ); - - if ( signal ) { - handleSignal( signal ); - } - - exit( status ); -}; +const { + getCliArg, + getCliArgs, + hasCliArg, + spawnScript, +} = require( './cli' ); +const { + hasBabelConfig, + hasJestConfig, +} = require( './config' ); +const { + fromConfigRoot, + hasProjectFile, +} = require( './file' ); +const { + hasPackageProp, +} = require( './package' ); module.exports = { fromConfigRoot, + getCliArg, getCliArgs, + hasBabelConfig, hasCliArg, - getCliArg, - hasProjectFile, + hasJestConfig, hasPackageProp, + hasProjectFile, spawnScript, }; From 3a39da5b7de8045f7e7cef6f0b5311ee31b3817d Mon Sep 17 00:00:00 2001 From: Hiroshi Urabe Date: Wed, 19 Dec 2018 18:29:32 +0900 Subject: [PATCH 033/691] [Documentation] fix link to edit-post documentation (#12835) * fix link to edit-post documentation * Update README.md * Update README.md --- packages/plugins/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugins/README.md b/packages/plugins/README.md index 49f348e01706b..4d4afd5896b19 100644 --- a/packages/plugins/README.md +++ b/packages/plugins/README.md @@ -29,7 +29,7 @@ This method takes two arguments: or an element (or function returning an element) if you choose to render your own SVG. - `render`: A component containing the UI elements to be rendered. -See [the edit-post module documentation](../edit-post/) for available components. +See [the edit-post module documentation](/packages/edit-post/) for available components. _Example:_ {% codetabs %} From 170f2a8b2b3375134b1c880cc3592f7e1895c610 Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler Date: Wed, 19 Dec 2018 11:30:11 +0100 Subject: [PATCH 034/691] Get the last mobile changes back in master (#12582) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Make a simple version of DefaultBlockAppender for mobile (#12434) * Make a simple version of DefaultBlockAppender for mobile * Use the same padding used for other blocks in DefaultBlockAppender * Update copy, auto focus and bind keypress * Do not bind key events * Change style of placeholder * Stop using classname-to-style autotransform in react native (#12552) * [RNMobile] Fix crash editing More blocks (#12620) * Use onChange instead of onChangeText in PlainText updates. * Convert the More block to Component, and re-use the same logic of the web when the field is empty. * Remove unsed variable, and format code * Move `);` to a new line * Fix SVG styles for mobile (#12608) * Fix SVG styles for mobile * Simplify condition since className is always a string * Check if Enter.key is the last inserted character, and add a new default block after the current More block. (#12639) Note: This is detected after the fact, and the newline could be visible on the block for a very small time. This is OK for the alpha, we will revisit the logic later. See https://github.com/wordpress-mobile/gutenberg-mobile/issues/324 * Call blur when deselecting RichText or PlainText component. (#12765) This is required to hide the keyboard when the focus moves to a non textual block. * Merge master into mobile (#12796) * RichText: fix onSetup doc (#12607) * Update broken links (#12660) * Update broken links There was 2 broken links. I changed same way with Data Module Reference page. * Update docs/designers-developers/developers/block-api/README.md Co-Authored-By: cagdasdag * Update docs/designers-developers/developers/block-api/README.md Co-Authored-By: cagdasdag * Docs: Update Glossary (#12479) * Update glossar with missing terms * Convert glossary to use dl/dt/dd dtags. Fixes #9976 * Fix TinyMCE link * remove spacing around tags * Add template definition, with link * Updates per review * Update docs/designers-developers/glossary.md Co-Authored-By: mkaz * Add documentation for `safeDecodeURI()` and `filterURLForDisplay()` (#12570) * Add documentation for `safeDecodeURI()` and `filterURLForDisplay()` * Whitespace * Consistant Capit… i mean capitalization * Oxford comma Co-Authored-By: georgeh * Update theme-support.md (#12661) * Fix: Undoing Image Selection in Image Block results in Broken Image (#12567) * Optimize isViewportMatch (#12542) * Cache createBlock call in isUnmodifiedDefaultBlock (#12521) * Cache createBlock call in isUnmodifiedDefaultBlock * Invalidate cache when default block name changes * Merge ifs * Font Size Picker: Use a menuitemradio role and better labels. (#12372) * Use a menuitemradio role and better labels. * Restore Button and remove MenuItem import. * Use template literals. * Set document title for preview interstitial (#12466) * Fix e2e tests after the WordPress 5.0 upgrade (#12715) * Fix e2e tests after the WordPress 5.0 upgrade * Remove the php unit tests testing the WP5.0 core instead of the plugin * Meta Boxes: Don't hide disabled meta boxes by modifying DOM (#12628) Hiding disabled meta boxes by setting `element.style.display = 'none'` interferes with plugins like ACF which rely on being able to show and hide meta boxes using `$.hide()` and `$.show()`. Hiding the meta box using a new `.edit-post-meta-boxes-area .is-hidden` class ensures that we don't interfere with third party code. * Add a word-wrap style to the facebook embed preview screen (#11890) * Add a word-break style to the facebook embed preview screen to prevent the long embed url from breaking the block boundary * Fix typo and missing space in scss comment * Adding @aldavigdis to the contributors list (#12686) * Make media & text block placeholder translatable (#12706) * Fix: Problems on Media & Text block resizing; Load wp-block-library styles before wp-edit-blocks. (#12619) * Get wordcount type from translation (#12586) * get wordcount type from translation * Add description to explain the options for wordcount type * Added mylsef into contributors.md. :) * Only render InserterWithShortcuts on hover (#12510) * Fix issue where default appender has icons overlaying the text (#12536) * Fix issue where default appender has icons overlaying the text This fixes #11425. It adds padding to the right of the default block appender to fit 3 icons. * chore: Tweak spelling * Classic Block: set correct focus back after blur (#12415) * Classic Block: set correct focus back after blur * Add e2e test * reset bookmark on mousedown and touchstart * e2e: Look for aria-label="Add Media" rather than "Insert Media" * RichText: only replace range and nodes if different (#12547) * RichText: only set range if different * Check rangeCount * Also compare nodes * Add e2e test * Simplify * RichText: Document isRangeEqual * Testing: RichText: Assure subscriber removal * Unsubscribe in page.evaluate * Mark temporary eslint-config package as private (#12734) * When a post is saved, check for tinymce and save any editors. (#12568) * When a post is saved, check for tinymce and save any editors. * Importing tinymce and using tinyMCE vs the object stored in window.tinymce. * Updated version number and changelog. * no longer importing tinymce since we use the tinyMCE global. tinyMCE.triggerSave works now. checking if tinyMCE exists before making the call just in case. * Using typeof to check for tinyMCE and fixed issues brought up in travis run. * using window.tinyMCE again to avoid warning RE undefined var * Restore the package.json version. * Add e2e tests for the custom wp_editor metaboxes * Rename functions, removing gutenberg_ prefix (#12326) * Rename functions, removing gutenberg_ and prefixing with wp_ * Remove wp_ prefix to match core * Remove function check per review * Annotations: Apply annotation className as string (#12741) * RichText: Ensure instance is selected before setting back selection (#12737) * Fix for #11663 (#12728) * Fixed Deleting an HTML Anchor attribute leaves an empty HTML id attribute * Fixed Deleting an HTML Anchor attribute leaves an empty * Update plugin version to 4.7.0-rc.1 (#12752) * Add an error state to the image block to allow upload errors to display (#10224) * Try: JS Console warning for when in Quirks Mode (#12575) * Try: JS Console warning for when in Quirks Mode This PR detects whether the browser is in Quirks Mode. Quirks Mode is a rendering method used when the doctype definition is missing or incorrectly placed in the HTML source, causing the browser to have difficulty detecting the type of document it is to render. This is usually caused by a PHP error, or even just a style tag that is output incorrectly on the page. See discussion in https://github.com/WordPress/gutenberg/pull/12455 and https://github.com/WordPress/gutenberg/issues/11378. The usual result is Gutenberg rendering incorrectly, notably with metaboxes overlapping content. The purpose of this PR is to help developers debug the issue and fix it at the root. As such, it adds a console warning, props @nickcernis for the text: ``` [Warning] Your browser is using Quirks Mode. This can cause rendering issues such as blocks overlaying meta boxes in the editor. Quirks Mode can be triggered by PHP errors or HTML code appearing before the opening . Try checking the raw page source or your site's PHP error log and resolving errors there, removing any HTML before the doctype, or disabling plugins. ``` It also augments the documentation to add a note about this. * Move warning to index.js * Remove try/catch. * Tweak: Remove redundant [warning] in warn call * Organizing screenshot assets for the block tutorial inside the designers-developers directory in the repo (#12745) * Rename backwards compatiblity to backward compatibility (#12751) * Rename backwards compatiblity to backward compatibility * Remove package-lock from commit * Update CONTRIBUTING.md Co-Authored-By: mkaz * Update CONTRIBUTING.md Co-Authored-By: mkaz * Whitespace in manifest * Update node-sass to 4.11.0 to support Node.js 11 (#12541) ## Description Fixes #12539 by updating node-sass to support Node.js 11. ## How has this been tested? Running `npm install` on macOS 10.14 with Node.js 11.2 without problems. ## Types of changes Minor dependency bump to support Node.js 11. ## Checklist: - [x] My code is tested. - [x] My code follows the WordPress code style. - [x] My code follows the accessibility standards. - [x] My code has proper inline documentation. * Update native more block styling (#12767) * Update styling of more block * Fix CI test * Update spaces * Convert indentation to tabs * Fix lint issues * Remove key attribute * Set fixed width to the text input * Make sure to properly compare empty text and undefined text variable before resetting the eventCount field. (#12815) * Use the default "Read More" text on init only, giving the ability to user to empty the field and re-start with empty text (#12821) * Merge 'origin/master' into 'origin/mobile' (#12836) * RichText: fix onSetup doc (#12607) * Update broken links (#12660) * Update broken links There was 2 broken links. I changed same way with Data Module Reference page. * Update docs/designers-developers/developers/block-api/README.md Co-Authored-By: cagdasdag * Update docs/designers-developers/developers/block-api/README.md Co-Authored-By: cagdasdag * Docs: Update Glossary (#12479) * Update glossar with missing terms * Convert glossary to use dl/dt/dd dtags. Fixes #9976 * Fix TinyMCE link * remove spacing around tags * Add template definition, with link * Updates per review * Update docs/designers-developers/glossary.md Co-Authored-By: mkaz * Add documentation for `safeDecodeURI()` and `filterURLForDisplay()` (#12570) * Add documentation for `safeDecodeURI()` and `filterURLForDisplay()` * Whitespace * Consistant Capit… i mean capitalization * Oxford comma Co-Authored-By: georgeh * Update theme-support.md (#12661) * Fix: Undoing Image Selection in Image Block results in Broken Image (#12567) * Optimize isViewportMatch (#12542) * Cache createBlock call in isUnmodifiedDefaultBlock (#12521) * Cache createBlock call in isUnmodifiedDefaultBlock * Invalidate cache when default block name changes * Merge ifs * Font Size Picker: Use a menuitemradio role and better labels. (#12372) * Use a menuitemradio role and better labels. * Restore Button and remove MenuItem import. * Use template literals. * Set document title for preview interstitial (#12466) * Fix e2e tests after the WordPress 5.0 upgrade (#12715) * Fix e2e tests after the WordPress 5.0 upgrade * Remove the php unit tests testing the WP5.0 core instead of the plugin * Meta Boxes: Don't hide disabled meta boxes by modifying DOM (#12628) Hiding disabled meta boxes by setting `element.style.display = 'none'` interferes with plugins like ACF which rely on being able to show and hide meta boxes using `$.hide()` and `$.show()`. Hiding the meta box using a new `.edit-post-meta-boxes-area .is-hidden` class ensures that we don't interfere with third party code. * Add a word-wrap style to the facebook embed preview screen (#11890) * Add a word-break style to the facebook embed preview screen to prevent the long embed url from breaking the block boundary * Fix typo and missing space in scss comment * Adding @aldavigdis to the contributors list (#12686) * Make media & text block placeholder translatable (#12706) * Fix: Problems on Media & Text block resizing; Load wp-block-library styles before wp-edit-blocks. (#12619) * Get wordcount type from translation (#12586) * get wordcount type from translation * Add description to explain the options for wordcount type * Added mylsef into contributors.md. :) * Only render InserterWithShortcuts on hover (#12510) * Fix issue where default appender has icons overlaying the text (#12536) * Fix issue where default appender has icons overlaying the text This fixes #11425. It adds padding to the right of the default block appender to fit 3 icons. * chore: Tweak spelling * Classic Block: set correct focus back after blur (#12415) * Classic Block: set correct focus back after blur * Add e2e test * reset bookmark on mousedown and touchstart * e2e: Look for aria-label="Add Media" rather than "Insert Media" * RichText: only replace range and nodes if different (#12547) * RichText: only set range if different * Check rangeCount * Also compare nodes * Add e2e test * Simplify * RichText: Document isRangeEqual * Testing: RichText: Assure subscriber removal * Unsubscribe in page.evaluate * Mark temporary eslint-config package as private (#12734) * When a post is saved, check for tinymce and save any editors. (#12568) * When a post is saved, check for tinymce and save any editors. * Importing tinymce and using tinyMCE vs the object stored in window.tinymce. * Updated version number and changelog. * no longer importing tinymce since we use the tinyMCE global. tinyMCE.triggerSave works now. checking if tinyMCE exists before making the call just in case. * Using typeof to check for tinyMCE and fixed issues brought up in travis run. * using window.tinyMCE again to avoid warning RE undefined var * Restore the package.json version. * Add e2e tests for the custom wp_editor metaboxes * Rename functions, removing gutenberg_ prefix (#12326) * Rename functions, removing gutenberg_ and prefixing with wp_ * Remove wp_ prefix to match core * Remove function check per review * Annotations: Apply annotation className as string (#12741) * RichText: Ensure instance is selected before setting back selection (#12737) * Fix for #11663 (#12728) * Fixed Deleting an HTML Anchor attribute leaves an empty HTML id attribute * Fixed Deleting an HTML Anchor attribute leaves an empty * Update plugin version to 4.7.0-rc.1 (#12752) * Add an error state to the image block to allow upload errors to display (#10224) * Try: JS Console warning for when in Quirks Mode (#12575) * Try: JS Console warning for when in Quirks Mode This PR detects whether the browser is in Quirks Mode. Quirks Mode is a rendering method used when the doctype definition is missing or incorrectly placed in the HTML source, causing the browser to have difficulty detecting the type of document it is to render. This is usually caused by a PHP error, or even just a style tag that is output incorrectly on the page. See discussion in https://github.com/WordPress/gutenberg/pull/12455 and https://github.com/WordPress/gutenberg/issues/11378. The usual result is Gutenberg rendering incorrectly, notably with metaboxes overlapping content. The purpose of this PR is to help developers debug the issue and fix it at the root. As such, it adds a console warning, props @nickcernis for the text: ``` [Warning] Your browser is using Quirks Mode. This can cause rendering issues such as blocks overlaying meta boxes in the editor. Quirks Mode can be triggered by PHP errors or HTML code appearing before the opening . Try checking the raw page source or your site's PHP error log and resolving errors there, removing any HTML before the doctype, or disabling plugins. ``` It also augments the documentation to add a note about this. * Move warning to index.js * Remove try/catch. * Tweak: Remove redundant [warning] in warn call * Organizing screenshot assets for the block tutorial inside the designers-developers directory in the repo (#12745) * Rename backwards compatiblity to backward compatibility (#12751) * Rename backwards compatiblity to backward compatibility * Remove package-lock from commit * Update CONTRIBUTING.md Co-Authored-By: mkaz * Update CONTRIBUTING.md Co-Authored-By: mkaz * Whitespace in manifest * Update node-sass to 4.11.0 to support Node.js 11 (#12541) ## Description Fixes #12539 by updating node-sass to support Node.js 11. ## How has this been tested? Running `npm install` on macOS 10.14 with Node.js 11.2 without problems. ## Types of changes Minor dependency bump to support Node.js 11. ## Checklist: - [x] My code is tested. - [x] My code follows the WordPress code style. - [x] My code follows the accessibility standards. - [x] My code has proper inline documentation. * Add attributes to ServerSideRender readme (#12793) * Add attributes to ServerSideRender readme Adds a code example demonstrating how to define attributes when registering a block that will use attributes in a ServerSideRender component. * Add whitespace and inline code markup to ServerSideRender readme Implements requested changes from code review. * Scripts: Add check-engines script to the package (#12721) * Scripts: Add check-engines script to the package * Update packages/scripts/CHANGELOG.md Co-Authored-By: gziolo * Update packages/scripts/README.md Co-Authored-By: gziolo * Update minimal node version to 10.x Co-Authored-By: gziolo * Move devDependencies to root package.json file (#12720) * Chore: Remove unused npm dependencies from the root package.json file * Move devDependencies to root package.json file * Fix php notice from the recent comments block (#12812) * RichText: Fix React warning shown when unmounting a currently selected RichText. (#12817) * Packages: Reimplement ESLint config as plugin (#12763) * Packages: Move eslint-config to eslint-plugin (Fails pre-commit, but in effort to ensure history preservation) * eslint-plugin: Add npmrc to avoid package-lock.json * Framework: Update path references for eslint-config to -plugin * eslint-plugin: Reimplement ESLint config as plugin * eslint-plugin: Unmark as private * eslint-plugin: Undocument custom ruleset * 4.7 (#12819) * Bump plugin version to 4.7.0 * chore(release): publish - @wordpress/annotations@1.0.4 - @wordpress/api-fetch@2.2.6 - @wordpress/block-library@2.2.10 - @wordpress/block-serialization-default-parser@2.0.2 - @wordpress/block-serialization-spec-parser@2.0.2 - @wordpress/blocks@6.0.4 - @wordpress/components@7.0.4 - @wordpress/core-data@2.0.15 - @wordpress/data@4.1.0 - @wordpress/date@3.0.1 - @wordpress/edit-post@3.1.5 - @wordpress/editor@9.0.5 - @wordpress/eslint-plugin@1.0.0 - @wordpress/format-library@1.2.8 - @wordpress/html-entities@2.0.4 - @wordpress/list-reusable-blocks@1.1.17 - @wordpress/notices@1.1.1 - @wordpress/nux@3.0.5 - @wordpress/rich-text@3.0.3 - @wordpress/url@2.3.2 - @wordpress/viewport@2.0.13 * Update changelogs after 4.7 package releases * Add back package-lock.json * Remove the call that does `blur` the undelying native component when deselecting RichText or PlainText. (#12886) * [rnmobile]: Send blockType prop to RNAztecView (#12869) * Revert "Remove the call that does `blur` the undelying native component (#12946) * Revert "Remove the call that does `blur` the undelying native component when deselecting RichText or PlainText. (#12886)" This reverts commit d9fe45ed6251486f213daf3fc9a7568e2b4977c5. * Blur only if it is iOS * Fix imports * Fix lint errors in the mobile branch (#12990) * Revert changes to lib/load.php * Fix lint errors in More * Fix lint error for PlainText * Fix lint error in RichText --- .../block-library/src/more/edit.native.js | 75 +++++++++++++++---- .../block-library/src/more/editor.native.scss | 25 ++++--- .../block-library/src/nextpage/edit.native.js | 2 +- .../src/primitives/svg/index.native.js | 12 +-- .../default-block-appender/index.native.js | 75 +++++++++++++++++++ .../default-block-appender/style.native.scss | 14 ++++ .../editor/src/components/index.native.js | 1 + .../src/components/plain-text/index.native.js | 19 ++++- .../src/components/rich-text/index.native.js | 10 ++- 9 files changed, 189 insertions(+), 44 deletions(-) create mode 100644 packages/editor/src/components/default-block-appender/index.native.js create mode 100644 packages/editor/src/components/default-block-appender/style.native.scss diff --git a/packages/block-library/src/more/edit.native.js b/packages/block-library/src/more/edit.native.js index 5b85da44359d5..6a73a44c73b6a 100644 --- a/packages/block-library/src/more/edit.native.js +++ b/packages/block-library/src/more/edit.native.js @@ -1,12 +1,17 @@ /** * External dependencies */ -import { View, Text } from 'react-native'; +import { View } from 'react-native'; /** * WordPress dependencies */ import { __ } from '@wordpress/i18n'; +import { Component } from '@wordpress/element'; +import { + getDefaultBlockName, + createBlock, +} from '@wordpress/blocks'; /** * Internal dependencies @@ -14,28 +19,68 @@ import { __ } from '@wordpress/i18n'; import { PlainText } from '@wordpress/editor'; import styles from './editor.scss'; -export default function MoreEdit( props ) { - const { attributes, setAttributes, onFocus, onBlur } = props; - const { customText } = attributes; - const defaultText = __( 'Read more' ); - const value = customText !== undefined ? customText : defaultText; +export default class MoreEdit extends Component { + constructor() { + super( ...arguments ); + this.onChangeInput = this.onChangeInput.bind( this ); - return ( - - - <!-- + this.state = { + defaultText: __( 'Read more' ), + }; + } + + onChangeInput( newValue ) { + // Detect Enter.key and add new empty block after. + // Note: This is detected after the fact, and the newline could be visible on the block + // for a very small time. This is OK for the alpha, we will revisit the logic later. + // See https://github.com/wordpress-mobile/gutenberg-mobile/issues/324 + if ( newValue.indexOf( '\n' ) !== -1 ) { + const { insertBlocksAfter } = this.props; + insertBlocksAfter( [ createBlock( getDefaultBlockName() ) ] ); + return; + } + // Set defaultText to an empty string, allowing the user to clear/replace the input field's text + this.setState( { + defaultText: '', + } ); + const value = newValue.length === 0 ? undefined : newValue; + this.props.setAttributes( { customText: value } ); + } + + renderLine() { + return ; + } + + renderText() { + const { attributes, onFocus, onBlur } = this.props; + const { customText } = attributes; + const { defaultText } = this.state; + const value = customText !== undefined ? customText : defaultText; + + return ( + setAttributes( { customText: newValue } ) } + onChange={ this.onChangeInput } placeholder={ defaultText } - isSelected={ props.isSelected } + isSelected={ this.props.isSelected } onFocus={ onFocus } onBlur={ onBlur } /> - <Text className={ styles[ 'block-library-more__right-marker' ] }>--&gt;</Text> </View> - </View> ); + ); + } + + render() { + return ( + <View style={ styles[ 'block-library-more__container' ] }> + { this.renderLine() } + { this.renderText() } + { this.renderLine() } + </View> + ); + } } diff --git a/packages/block-library/src/more/editor.native.scss b/packages/block-library/src/more/editor.native.scss index 46d36fe08e321..beb5ef423776f 100644 --- a/packages/block-library/src/more/editor.native.scss +++ b/packages/block-library/src/more/editor.native.scss @@ -2,21 +2,22 @@ .block-library-more__container { align-items: center; - padding-left: 4; - padding-right: 4; - padding-top: 4; - padding-bottom: 4; -} - -.block-library-more__sub-container { - align-items: center; + padding: 4px; flex-direction: row; } -.block-library-more__left-marker { - padding-right: 4; +.block-library-more__line { + background-color: #555d66; + height: 2; + flex: 1; } -.block-library-more__right-marker { - padding-left: 4; +.block-library-more__text { + text-decoration-style: solid; + flex: 0; + width: 200; + text-align: center; + margin-left: 15; + margin-right: 15; + margin-bottom: 5; } diff --git a/packages/block-library/src/nextpage/edit.native.js b/packages/block-library/src/nextpage/edit.native.js index 03cd3bfe3c7d1..413fda53fe217 100644 --- a/packages/block-library/src/nextpage/edit.native.js +++ b/packages/block-library/src/nextpage/edit.native.js @@ -18,7 +18,7 @@ export default function NextPageEdit( { attributes } ) { const { customText = __( 'Page break' ) } = attributes; return ( - <View className={ styles[ 'block-library-nextpage__container' ] }> + <View style={ styles[ 'block-library-nextpage__container' ] }> <Hr text={ customText } textStyle={ styles[ 'block-library-nextpage__text' ] } lineStyle={ styles[ 'block-library-nextpage__line' ] } /> diff --git a/packages/components/src/primitives/svg/index.native.js b/packages/components/src/primitives/svg/index.native.js index 47c49b1bb6128..b0272e6b5a7b9 100644 --- a/packages/components/src/primitives/svg/index.native.js +++ b/packages/components/src/primitives/svg/index.native.js @@ -17,16 +17,8 @@ export { } from 'react-native-svg'; export const SVG = ( props ) => { - // We're using the react-native-classname-to-style plugin, so when a `className` prop is passed it gets converted to `style` here. - // Given it carries a string (as it was originally className) but an object is expected for `style`, - // we need to check whether `style` exists and is a string, and convert it to an object - - let styleValues = {}; - if ( typeof props.style === 'string' ) { - const oneStyle = props.style.split( ' ' ).map( ( element ) => styles[ element ] ).filter( Boolean ); - styleValues = Object.assign( styleValues, ...oneStyle ); - } - + const stylesFromClasses = ( props.className || '' ).split( ' ' ).map( ( element ) => styles[ element ] ).filter( Boolean ); + const styleValues = Object.assign( {}, props.style, ...stylesFromClasses ); const safeProps = { ...props, style: styleValues }; return ( diff --git a/packages/editor/src/components/default-block-appender/index.native.js b/packages/editor/src/components/default-block-appender/index.native.js new file mode 100644 index 0000000000000..436ecc5772b00 --- /dev/null +++ b/packages/editor/src/components/default-block-appender/index.native.js @@ -0,0 +1,75 @@ +/** + * External dependencies + */ +import { TextInput, TouchableWithoutFeedback, View } from 'react-native'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { compose } from '@wordpress/compose'; +import { decodeEntities } from '@wordpress/html-entities'; +import { withSelect, withDispatch } from '@wordpress/data'; + +import styles from './style.scss'; + +export function DefaultBlockAppender( { + isLocked, + isVisible, + onAppend, + placeholder, +} ) { + if ( isLocked || ! isVisible ) { + return null; + } + + const value = decodeEntities( placeholder ) || __( 'Start writing or press \u2295 to add content' ); + + return ( + <TouchableWithoutFeedback + onPress={ onAppend } + > + <View style={ styles.blockHolder } pointerEvents="box-only"> + <View style={ styles.blockContainer }> + <TextInput + style={ styles.textView } + textAlignVertical="top" + multiline + numberOfLines={ 0 } + value={ value } + /> + </View> + </View> + </TouchableWithoutFeedback> + ); +} + +export default compose( + withSelect( ( select, ownProps ) => { + const { getBlockCount, getEditorSettings, getTemplateLock } = select( 'core/editor' ); + + const isEmpty = ! getBlockCount( ownProps.rootClientId ); + const { bodyPlaceholder } = getEditorSettings(); + + return { + isVisible: isEmpty, + isLocked: !! getTemplateLock( ownProps.rootClientId ), + placeholder: bodyPlaceholder, + }; + } ), + withDispatch( ( dispatch, ownProps ) => { + const { + insertDefaultBlock, + startTyping, + } = dispatch( 'core/editor' ); + + return { + onAppend() { + const { rootClientId } = ownProps; + + insertDefaultBlock( undefined, rootClientId ); + startTyping(); + }, + }; + } ), +)( DefaultBlockAppender ); diff --git a/packages/editor/src/components/default-block-appender/style.native.scss b/packages/editor/src/components/default-block-appender/style.native.scss new file mode 100644 index 0000000000000..cc08f6c820ca9 --- /dev/null +++ b/packages/editor/src/components/default-block-appender/style.native.scss @@ -0,0 +1,14 @@ + +.blockHolder { + flex: 1 1 auto; +} + +.blockContainer { + background-color: $white; + padding: 8px; +} + +.textView { + color: #87a6bc; + font-size: 16px; +} diff --git a/packages/editor/src/components/index.native.js b/packages/editor/src/components/index.native.js index dc651f9e7e0a6..229efa5879cc3 100644 --- a/packages/editor/src/components/index.native.js +++ b/packages/editor/src/components/index.native.js @@ -6,5 +6,6 @@ export { default as MediaPlaceholder } from './media-placeholder'; export { default as BlockFormatControls } from './block-format-controls'; export { default as BlockControls } from './block-controls'; export { default as BlockEdit } from './block-edit'; +export { default as DefaultBlockAppender } from './default-block-appender'; export { default as EditorHistoryRedo } from './editor-history/redo'; export { default as EditorHistoryUndo } from './editor-history/undo'; diff --git a/packages/editor/src/components/plain-text/index.native.js b/packages/editor/src/components/plain-text/index.native.js index e94f1939040f5..35ffd2782a133 100644 --- a/packages/editor/src/components/plain-text/index.native.js +++ b/packages/editor/src/components/plain-text/index.native.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { TextInput } from 'react-native'; +import { TextInput, Platform } from 'react-native'; /** * WordPress dependencies @@ -14,6 +14,11 @@ import { Component } from '@wordpress/element'; import styles from './style.scss'; export default class PlainText extends Component { + constructor() { + super( ...arguments ); + this.isIOS = Platform.OS === 'ios'; + } + componentDidMount() { // if isSelected is true, we should request the focus on this TextInput if ( ( this._input.isFocused() === false ) && ( this._input.props.isSelected === true ) ) { @@ -21,6 +26,12 @@ export default class PlainText extends Component { } } + componentDidUpdate( prevProps ) { + if ( ! this.props.isSelected && prevProps.isSelected && this.isIOS ) { + this._input.blur(); + } + } + focus() { this._input.focus(); } @@ -28,12 +39,14 @@ export default class PlainText extends Component { render() { return ( <TextInput + { ...this.props } ref={ ( x ) => this._input = x } className={ [ styles[ 'editor-plain-text' ], this.props.className ] } - onChangeText={ ( text ) => this.props.onChange( text ) } + onChange={ ( event ) => { + this.props.onChange( event.nativeEvent.text ); + } } onFocus={ this.props.onFocus } // always assign onFocus as a props onBlur={ this.props.onBlur } // always assign onBlur as a props - { ...this.props } /> ); } diff --git a/packages/editor/src/components/rich-text/index.native.js b/packages/editor/src/components/rich-text/index.native.js index 362dc4b359d1a..f6796a97fdbaa 100644 --- a/packages/editor/src/components/rich-text/index.native.js +++ b/packages/editor/src/components/rich-text/index.native.js @@ -2,7 +2,7 @@ * External dependencies */ import RCTAztecView from 'react-native-aztec'; -import { View } from 'react-native'; +import { View, Platform } from 'react-native'; import { forEach, merge, @@ -66,6 +66,7 @@ export function getFormatValue( formatName ) { export class RichText extends Component { constructor() { super( ...arguments ); + this.isIOS = Platform.OS === 'ios'; this.onChange = this.onChange.bind( this ); this.onEnter = this.onEnter.bind( this ); this.onBackspace = this.onBackspace.bind( this ); @@ -274,8 +275,8 @@ export class RichText extends Component { // If the component is changed React side (undo/redo/merging/splitting/custom text actions) // we need to make sure the native is updated as well - if ( nextProps.value && - this.lastContent && + if ( ( typeof nextProps.value !== 'undefined' ) && + ( typeof this.lastContent !== 'undefined' ) && nextProps.value !== this.lastContent ) { this.lastEventCount = undefined; // force a refresh on the native side } @@ -292,6 +293,8 @@ export class RichText extends Component { componentDidUpdate( prevProps ) { if ( this.props.isSelected && ! prevProps.isSelected ) { this._editor.focus(); + } else if ( ! this.props.isSelected && prevProps.isSelected && this.isIOS ) { + this._editor.blur(); } } @@ -370,6 +373,7 @@ export class RichText extends Component { onContentSizeChange={ this.onContentSizeChange } onActiveFormatsChange={ this.onActiveFormatsChange } isSelected={ this.props.isSelected } + blockType={ { tag: tagName } } color={ 'black' } maxImagesWidth={ 200 } style={ style } From fa711e8c5acd3646b346977b9b4626cb8ece5576 Mon Sep 17 00:00:00 2001 From: torres126 <43215253+torres126@users.noreply.github.com> Date: Wed, 19 Dec 2018 11:21:32 +0000 Subject: [PATCH 035/691] Fix typo in docs/designers-developers/key-concepts.md (#13005) --- docs/designers-developers/key-concepts.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/designers-developers/key-concepts.md b/docs/designers-developers/key-concepts.md index 6ea644009b145..ff9007185fe0e 100644 --- a/docs/designers-developers/key-concepts.md +++ b/docs/designers-developers/key-concepts.md @@ -117,7 +117,7 @@ We chose instead to try and find a way to keep the formality and explicitness an Of these options a novel approach was suggested that by storing data in HTML comments we would know that we wouldn't break the rest of the HTML in the document, that browsers should ignore it, and that we could simplify our approach to parsing the document. -Unique to comments is that they cannot legitimately exist in ambiguous places, such as inside of HTML attributes like `<img alt='data-id="14"'>`. Comments are also quite permissive. Whereas HTML attributes are complicated to parse properly, comments are quite easily described by a leading `<!--` followed by anything except `--` until the first `-->`. This simplicity and permisiveness means that the parser can be implemented in several ways without needing to understand HTML properly and we have the liberty to use more convenient syntax inside of the comment—we only need to escape double-hyphen sequences. We take advantage of this in how we store block attributes: JSON literals inside the comment. +Unique to comments is that they cannot legitimately exist in ambiguous places, such as inside of HTML attributes like `<img alt='data-id="14"'>`. Comments are also quite permissive. Whereas HTML attributes are complicated to parse properly, comments are quite easily described by a leading `<!--` followed by anything except `--` until the first `-->`. This simplicity and permissiveness means that the parser can be implemented in several ways without needing to understand HTML properly and we have the liberty to use more convenient syntax inside of the comment—we only need to escape double-hyphen sequences. We take advantage of this in how we store block attributes: JSON literals inside the comment. After running this through the parser we're left with a simple object we can manipulate idiomatically and we don't have to worry about escaping or unescaping the data. It's handled for us through the serialization process. Because the comments are so different from other HTML tags and because we can perform a first-pass to extract the top-level blocks, we don't actually depend on having fully valid HTML! From 47950c3c3214a80401ee895413312a8691716e45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Wed, 19 Dec 2018 12:40:52 +0100 Subject: [PATCH 036/691] Scripts: Add lint-style script based on stylelint (#12722) * Scripts: Add lint-css script based on stylelint * Update packages/scripts/README.md Co-Authored-By: gziolo <grzegorz@gziolo.pl> * Update default config for lint-style script * Scripts: Extend description for the package --- package-lock.json | 4 ++- package.json | 9 +++--- packages/scripts/CHANGELOG.md | 5 +-- packages/scripts/README.md | 30 +++++++++++++---- packages/scripts/config/.stylelintrc.json | 3 ++ packages/scripts/package.json | 6 ++-- packages/scripts/scripts/lint-style.js | 39 +++++++++++++++++++++++ 7 files changed, 80 insertions(+), 16 deletions(-) create mode 100644 packages/scripts/config/.stylelintrc.json create mode 100644 packages/scripts/scripts/lint-style.js diff --git a/package-lock.json b/package-lock.json index 4a190ed1cb0d2..eb6406cdd4b65 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2775,7 +2775,9 @@ "npm-package-json-lint": "^3.3.1", "puppeteer": "1.6.1", "read-pkg-up": "^1.0.1", - "resolve-bin": "^0.4.0" + "resolve-bin": "^0.4.0", + "stylelint": "^9.5.0", + "stylelint-config-wordpress": "^13.1.0" } }, "@wordpress/shortcode": { diff --git a/package.json b/package.json index a214f2987eca3..15e713f80c20d 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,6 @@ "shallowequal": "1.1.0", "sprintf-js": "1.1.1", "source-map-loader": "0.2.3", - "stylelint": "9.5.0", "stylelint-config-wordpress": "13.1.0", "uuid": "3.3.2", "webpack": "4.8.3", @@ -164,11 +163,11 @@ "fixtures:regenerate": "npm run fixtures:clean && npm run fixtures:generate", "lint": "concurrently \"npm run lint-js\" \"npm run lint-pkg-json\" \"npm run lint-css\"", "lint-js": "wp-scripts lint-js .", - "lint-js:fix": "wp-scripts lint-js . --fix", + "lint-js:fix": "npm run lint-js -- --fix", "lint-php": "docker-compose run --rm composer run-script lint", "lint-pkg-json": "wp-scripts lint-pkg-json ./packages", - "lint-css": "stylelint '**/*.scss'", - "lint-css:fix": "stylelint '**/*.scss' --fix", + "lint-css": "wp-scripts lint-style '**/*.scss'", + "lint-css:fix": "npm run lint-css -- --fix", "package-plugin": "./bin/build-plugin-zip.sh", "postinstall": "npm run check-licenses && npm run build:packages", "pot-to-php": "./bin/pot-to-php.js", @@ -193,7 +192,7 @@ "wp-scripts lint-pkg-json" ], "*.scss": [ - "stylelint" + "wp-scripts lint-style" ], "*.js": [ "wp-scripts lint-js" diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index 119e734319d31..3ff87bef238a4 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -3,8 +3,9 @@ ### New Features - Added support for `check-engines` script ([#12721](https://github.com/WordPress/gutenberg/pull/12721)) -- Added support for `test-e2e` script ([#12437](https://github.com/WordPress/gutenberg/pull/12437)). -- Update default config provided for `lint-js` script ([#12845](https://github.com/WordPress/gutenberg/pull/12845)). +- Added support for `lint-style` script ([#12722](https://github.com/WordPress/gutenberg/pull/12722)) +- Added support for `test-e2e` script ([#12437](https://github.com/WordPress/gutenberg/pull/12437)) +- Update default config provided for `lint-js` script ([#12845](https://github.com/WordPress/gutenberg/pull/12845)) ## 2.4.4 (2018-11-20) diff --git a/packages/scripts/README.md b/packages/scripts/README.md index 2d578ef1b870b..15a7405340dbe 100644 --- a/packages/scripts/README.md +++ b/packages/scripts/README.md @@ -1,6 +1,10 @@ # Scripts -Collection of JS scripts for WordPress development. +Collection of reusable scripts for WordPress development. + +Command-line interfaces help to turn working with an app into a pleasant experience, but it is still not enough to keep it easy to maintain in the long run. Developers are left on their own to keep all configurations and dependent tools up to date. This problem multiplies when they own more than one project which shares the same setup. Fortunately, there is a pattern that can simplify maintainers life – reusable scripts. This idea boils down to moving all the necessary configurations and scripts to one single tool dependency. In most cases, it should be possible to accomplish all tasks using the default settings, but some customization is allowed, too. With all that in place updating all projects should become a very straightforward task. + +_This package is inspired by [react-scripts](https://www.npmjs.com/package/react-scripts) and [kcd-scripts](https://www.npmjs.com/package/kcd-scripts)._ ## Installation @@ -21,6 +25,7 @@ _Example:_ "scripts": { "check-engines": "wp-scripts check-engines", "check-licenses": "wp-scripts check-licenses --production", + "lint:css": "wp-scripts lint-style '**/*.css'", "lint:js": "wp-scripts lint-js .", "lint:pkg-json": "wp-scripts lint-pkg-json .", "test:e2e": "wp-scripts test-e2e", @@ -84,7 +89,7 @@ _Example:_ ``` This is how you execute the script with presented setup: -* `npm run lint:js` - lints JavaScripts files in the whole project's. +* `npm run lint:js` - lints JavaScript files in the entire project's directories. ### `lint-pkg-json` @@ -103,6 +108,23 @@ _Example:_ This is how you execute those scripts using the presented setup: * `npm run lint:pkg-json` - lints `package.json` file in the project's root folder. +### `lint-style` + +Helps enforce coding style guidelines for your style files. It uses [stylelint](https://github.com/stylelint/stylelint) with the [stylelint-config-wordpress](https://github.com/WordPress-Coding-Standards/stylelint-config-wordpress) configuration per the [WordPress CSS Coding Standards](https://make.wordpress.org/core/handbook/best-practices/coding-standards/css/). You can override them with your own rules as described in [stylelint user guide](https://github.com/stylelint/stylelint/docs/user-guide.md). + +_Example:_ + +```json +{ + "scripts": { + "lint:css": "wp-scripts lint-style '**/*.css'" + } +} +``` + +This is how you execute the script with presented setup: +* `npm run lint:css` - lints CSS files in the whole project's directory. + ### `test-e2e` Launches the End-To-End (E2E) test runner. It uses [Jest](https://facebook.github.io/jest/) behind the scenes and you are able to utilize all of its [CLI options](https://facebook.github.io/jest/docs/en/cli.html). You can also run `./node_modules/.bin/wp-scripts test:e2e --help` or `npm run test:e2e:help` (as presented below) to view all of the available options. @@ -154,8 +176,4 @@ This is how you execute those scripts using the presented setup: * `npm run test:unit:help` - prints all available options to configure unit tests runner. * `npm run test:unit:watch` - runs all unit tests in the watch mode. -## Inspiration - -This package is inspired by [react-scripts](https://www.npmjs.com/package/react-scripts) and [kcd-scripts](https://www.npmjs.com/package/kcd-scripts). - <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/scripts/config/.stylelintrc.json b/packages/scripts/config/.stylelintrc.json new file mode 100644 index 0000000000000..e3113e5fb0b6a --- /dev/null +++ b/packages/scripts/config/.stylelintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "stylelint-config-wordpress" +} diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 4ee9bdb00850c..e6350aa34da1d 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -1,7 +1,7 @@ { "name": "@wordpress/scripts", "version": "2.4.4", - "description": "Collection of JS scripts for WordPress development.", + "description": "Collection of reusable scripts for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", "keywords": [ @@ -44,7 +44,9 @@ "npm-package-json-lint": "^3.3.1", "puppeteer": "1.6.1", "read-pkg-up": "^1.0.1", - "resolve-bin": "^0.4.0" + "resolve-bin": "^0.4.0", + "stylelint": "^9.5.0", + "stylelint-config-wordpress": "^13.1.0" }, "publishConfig": { "access": "public" diff --git a/packages/scripts/scripts/lint-style.js b/packages/scripts/scripts/lint-style.js new file mode 100644 index 0000000000000..d72a877fa9370 --- /dev/null +++ b/packages/scripts/scripts/lint-style.js @@ -0,0 +1,39 @@ +/** + * External dependencies + */ +const { sync: spawn } = require( 'cross-spawn' ); +const { sync: resolveBin } = require( 'resolve-bin' ); + +/** + * Internal dependencies + */ +const { + fromConfigRoot, + getCliArgs, + hasCliArg, + hasProjectFile, + hasPackageProp, +} = require( '../utils' ); + +const args = getCliArgs(); + +const hasStylelintConfig = hasCliArg( '--config' ) || + hasProjectFile( '.stylelintrc' ) || + hasProjectFile( '.stylelintrc.js' ) || + hasProjectFile( '.stylelintrc.json' ) || + hasProjectFile( '.stylelintrc.yaml' ) || + hasProjectFile( '.stylelintrc.yml' ) || + hasProjectFile( '.stylelint.config.js' ) || + hasPackageProp( 'stylelint' ); + +const config = ! hasStylelintConfig ? + [ '--config', fromConfigRoot( '.stylelintrc.json' ) ] : + []; + +const result = spawn( + resolveBin( 'stylelint' ), + [ ...config, ...args ], + { stdio: 'inherit' } +); + +process.exit( result.status ); From 262deea5082473e7a68e2d875ed356abab0413b9 Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler <dekervit@gmail.com> Date: Wed, 19 Dec 2018 17:10:06 +0100 Subject: [PATCH 037/691] Expose unregisterBlockType for mobile (#13015) --- packages/blocks/src/api/index.native.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/blocks/src/api/index.native.js b/packages/blocks/src/api/index.native.js index 1350df80ec222..7b0bd10eadfd7 100644 --- a/packages/blocks/src/api/index.native.js +++ b/packages/blocks/src/api/index.native.js @@ -14,6 +14,7 @@ export { } from './serializer'; export { registerBlockType, + unregisterBlockType, getFreeformContentHandlerName, setUnregisteredTypeHandlerName, getUnregisteredTypeHandlerName, From b359a9cfb5c326cfaaa8d4aea8c73172b796cd69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Wed, 19 Dec 2018 17:58:16 +0100 Subject: [PATCH 038/691] Update/release commits (#13011) * chore(release): publish - @wordpress/block-library@2.2.11 - @wordpress/edit-post@3.1.6 - @wordpress/editor@9.0.6 - @wordpress/format-library@1.2.9 * Update changelogs after 4.7.1 package release * Bump plugin version to 4.7.1 --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- packages/block-library/CHANGELOG.md | 2 ++ packages/block-library/package.json | 2 +- packages/edit-post/CHANGELOG.md | 2 ++ packages/edit-post/package.json | 2 +- packages/editor/CHANGELOG.md | 2 +- packages/editor/package.json | 2 +- packages/format-library/CHANGELOG.md | 2 ++ packages/format-library/package.json | 2 +- 11 files changed, 14 insertions(+), 8 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 62215b96d48d1..b1f6599c35c2f 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,7 +3,7 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the new block editor in core. - * Version: 4.7.0 + * Version: 4.7.1 * Author: Gutenberg Team * * @package gutenberg diff --git a/package-lock.json b/package-lock.json index eb6406cdd4b65..52818081f01b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "4.7.0", + "version": "4.7.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 15e713f80c20d..95ce65ad2682f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "4.7.0", + "version": "4.7.1", "private": true, "description": "A new WordPress editor experience", "repository": "git+https://github.com/WordPress/gutenberg.git", diff --git a/packages/block-library/CHANGELOG.md b/packages/block-library/CHANGELOG.md index f784c887fb110..7431ba83e3e71 100644 --- a/packages/block-library/CHANGELOG.md +++ b/packages/block-library/CHANGELOG.md @@ -1,3 +1,5 @@ +## 2.2.11 (2018-12-18) + ## 2.2.10 (2018-12-12) ## 2.2.9 (2018-11-30) diff --git a/packages/block-library/package.json b/packages/block-library/package.json index 3f81dcf5dfd3d..54846d2fafef8 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-library", - "version": "2.2.10", + "version": "2.2.11", "description": "Block library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/edit-post/CHANGELOG.md b/packages/edit-post/CHANGELOG.md index 092061f4501ef..21953d9218761 100644 --- a/packages/edit-post/CHANGELOG.md +++ b/packages/edit-post/CHANGELOG.md @@ -1,3 +1,5 @@ +## 3.1.6 (2018-12-18) + ## 3.1.5 (2018-12-12) ### Bug Fixes diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json index 0e0e4d008fd95..2e3d2d44c7106 100644 --- a/packages/edit-post/package.json +++ b/packages/edit-post/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-post", - "version": "3.1.5", + "version": "3.1.6", "description": "Edit Post module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/editor/CHANGELOG.md b/packages/editor/CHANGELOG.md index a5e35f2c95108..cb15210a97677 100644 --- a/packages/editor/CHANGELOG.md +++ b/packages/editor/CHANGELOG.md @@ -1,4 +1,4 @@ -## 9.0.6 (Unreleased) +## 9.0.6 (2018-12-18) ### Bug Fixes diff --git a/packages/editor/package.json b/packages/editor/package.json index 6850b40372ae2..811eb9643e4c0 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/editor", - "version": "9.0.5", + "version": "9.0.6", "description": "Building blocks for WordPress editors.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/format-library/CHANGELOG.md b/packages/format-library/CHANGELOG.md index 730914fc9ebd8..9726c7ff3bb56 100644 --- a/packages/format-library/CHANGELOG.md +++ b/packages/format-library/CHANGELOG.md @@ -1,3 +1,5 @@ +## 1.2.9 (2018-12-18) + ## 1.2.8 (2018-12-12) ## 1.2.7 (2018-11-30) diff --git a/packages/format-library/package.json b/packages/format-library/package.json index bd41d3742ddbc..c471b5507b3ea 100644 --- a/packages/format-library/package.json +++ b/packages/format-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/format-library", - "version": "1.2.8", + "version": "1.2.9", "description": "Format library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", From e3a9f5cd70a4430f1466a4e82ba6ef0c2e34e828 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Thu, 20 Dec 2018 07:19:05 +0100 Subject: [PATCH 039/691] Tests: Enabled popular plugins on Travis when running e2e test suite (#12578) * Tests: Enabled popular plugins on Travis when running e2e test suite * Try running WP-CLI with a different user * Tweak if statement * Add back chmod dance on plugins related folders * Create upgrade folder * Add a temporary noop catch when api fetch for meta box save fails. Not having a catch caused an end to end test to fail. * Fix: clickBelow logic clicked on the meta boxes when a big meta box was open. * Make todo LOUD ;-) * Remove obsolete lines after rebasing with master * Let's try without AFC * Trying without Yoast SEO this time * Revert changes for the test * Enable 2 more plugins * Try without AMP --- .travis.yml | 2 +- bin/install-wordpress.sh | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2dc47aa01d642..877794f883a38 100644 --- a/.travis.yml +++ b/.travis.yml @@ -58,7 +58,7 @@ jobs: - ./bin/run-wp-unit-tests.sh - stage: test - env: WP_VERSION=latest + env: WP_VERSION=latest POPULAR_PLUGINS=true script: - ./bin/run-e2e-tests.sh || exit 1 diff --git a/bin/install-wordpress.sh b/bin/install-wordpress.sh index affe7cc51d34e..40eec0810bd90 100755 --- a/bin/install-wordpress.sh +++ b/bin/install-wordpress.sh @@ -91,6 +91,13 @@ fi echo -e $(status_message "Activating Gutenberg...") docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm -u 33 $CLI plugin activate gutenberg --quiet +if [ "$POPULAR_PLUGINS" == "true" ]; then + echo -e $(status_message "Activating popular plugins...") + docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm -u 33 $CLI plugin install advanced-custom-fields --activate --quiet + docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm -u 33 $CLI plugin install jetpack --activate --quiet + docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm -u 33 $CLI plugin install wpforms-lite --activate --quiet +fi + # Install a dummy favicon to avoid 404 errors. echo -e $(status_message "Installing a dummy favicon...") docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm $CONTAINER touch /var/www/html/favicon.ico From 8e9d9e278f963b162ec06de13267dfd524e396c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milan=20Dini=C4=87?= <milan@srpski.biz> Date: Thu, 20 Dec 2018 09:21:43 +0100 Subject: [PATCH 040/691] Merge similar strings. (#12540) --- packages/editor/src/components/url-input/index.js | 2 +- packages/format-library/src/link/inline.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/editor/src/components/url-input/index.js b/packages/editor/src/components/url-input/index.js index 8e25784e6e15b..0d763957ce356 100644 --- a/packages/editor/src/components/url-input/index.js +++ b/packages/editor/src/components/url-input/index.js @@ -198,7 +198,7 @@ class URLInput extends Component { if ( this.state.selectedSuggestion !== null ) { this.selectLink( post ); // Announce a link has been selected when tabbing away from the input field. - this.props.speak( __( 'Link selected' ) ); + this.props.speak( __( 'Link selected.' ) ); } break; } diff --git a/packages/format-library/src/link/inline.js b/packages/format-library/src/link/inline.js index b007076e8c7f2..b2ef0beaecc68 100644 --- a/packages/format-library/src/link/inline.js +++ b/packages/format-library/src/link/inline.js @@ -217,7 +217,7 @@ class InlineLinkUI extends Component { } else if ( isActive ) { speak( __( 'Link edited.' ), 'assertive' ); } else { - speak( __( 'Link inserted' ), 'assertive' ); + speak( __( 'Link inserted.' ), 'assertive' ); } } From 4a77c06985dbb0b3e1ab88cf66f939df023516b3 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Thu, 20 Dec 2018 03:24:05 -0500 Subject: [PATCH 041/691] Hooks: Optimize addHook as appending operation (#12824) --- packages/hooks/benchmark/index.js | 46 ++++++++++++++++++++++++----- packages/hooks/src/createAddHook.js | 19 ++++++++---- 2 files changed, 51 insertions(+), 14 deletions(-) diff --git a/packages/hooks/benchmark/index.js b/packages/hooks/benchmark/index.js index b6d8f53c1e6c7..e78b2827bb1ff 100644 --- a/packages/hooks/benchmark/index.js +++ b/packages/hooks/benchmark/index.js @@ -3,17 +3,47 @@ const hooks = require( '../' ); const suite = new Benchmark.Suite; -function myCallback() {} +const filter = process.argv[ 2 ]; +const isInFilter = ( key ) => ! filter || filter === key; -hooks.addFilter( 'handled', 'myCallback', myCallback ); +function reset() { + hooks.removeAllFilters( 'example' ); + hooks.removeAllActions( 'example' ); +} + +if ( isInFilter( 'applyFilters' ) ) { + function myCallback() {} + + hooks.addFilter( 'example', 'myCallback', myCallback ); + + suite + .add( 'applyFilters - handled', () => { + hooks.applyFilters( 'handled' ); + } ) + .add( 'applyFilters - unhandled', () => { + hooks.applyFilters( 'unhandled' ); + } ); +} + +if ( isInFilter( 'addFilter' ) ) { + let hasSetHighPriority = false; + + suite + .add( 'addFilter - append last', () => { + hooks.addFilter( 'example', 'myCallback', () => {} ); + } ) + .add( 'addFilter - default before higher priority', () => { + if ( ! hasSetHighPriority ) { + hasSetHighPriority = true; + hooks.addFilter( 'example', 'priority', () => {}, 20 ); + } + + hooks.addFilter( 'example', 'myCallback', () => {} ); + } ); +} suite - .add( 'handled', () => { - hooks.applyFilters( 'handled' ); - } ) - .add( 'unhandled', () => { - hooks.applyFilters( 'unhandled' ); - } ) + .on( 'cycle', reset ) // eslint-disable-next-line no-console .on( 'cycle', ( event ) => console.log( event.target.toString() ) ) .run( { async: true } ); diff --git a/packages/hooks/src/createAddHook.js b/packages/hooks/src/createAddHook.js index 1f3d92b12965c..81b576d9d14e3 100644 --- a/packages/hooks/src/createAddHook.js +++ b/packages/hooks/src/createAddHook.js @@ -45,15 +45,22 @@ function createAddHook( hooks ) { if ( hooks[ hookName ] ) { // Find the correct insert index of the new hook. const handlers = hooks[ hookName ].handlers; - let i = 0; - while ( i < handlers.length ) { - if ( handlers[ i ].priority > priority ) { + + let i; + for ( i = handlers.length; i > 0; i-- ) { + if ( priority >= handlers[ i - 1 ].priority ) { break; } - i++; } - // Insert (or append) the new hook. - handlers.splice( i, 0, handler ); + + if ( i === handlers.length ) { + // If append, operate via direct assignment. + handlers[ i ] = handler; + } else { + // Otherwise, insert before index via splice. + handlers.splice( i, 0, handler ); + } + // We may also be currently executing this hook. If the callback // we're adding would come after the current callback, there's no // problem; otherwise we need to increase the execution index of From c7a63c2e5a1839433c5ead5480119f843885f745 Mon Sep 17 00:00:00 2001 From: Dominik Schilling <dominikschilling+git@gmail.com> Date: Thu, 20 Dec 2018 09:31:02 +0100 Subject: [PATCH 042/691] Fix RTL support for datepicker (#12719) * Don't convert react-dates styles to RTL * Pass isRTL flag to DayPickerSingleDateController to enable RTL support * Add comment for rtl:ignore section --- packages/components/src/date-time/date.js | 2 ++ packages/components/src/date-time/style.scss | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/components/src/date-time/date.js b/packages/components/src/date-time/date.js index c23d5a0f105b4..0854846b75244 100644 --- a/packages/components/src/date-time/date.js +++ b/packages/components/src/date-time/date.js @@ -13,6 +13,7 @@ import { Component } from '@wordpress/element'; * Module Constants */ const TIMEZONELESS_FORMAT = 'YYYY-MM-DDTHH:mm:ss'; +const isRTL = () => document.documentElement.dir === 'rtl'; class DatePicker extends Component { constructor() { @@ -55,6 +56,7 @@ class DatePicker extends Component { onDateChange={ this.onChangeMoment } transitionDuration={ 0 } weekDayFormat="ddd" + isRTL={ isRTL() } /> </div> ); diff --git a/packages/components/src/date-time/style.scss b/packages/components/src/date-time/style.scss index 8c99f8216e1fd..e564636246f2b 100644 --- a/packages/components/src/date-time/style.scss +++ b/packages/components/src/date-time/style.scss @@ -1,5 +1,8 @@ -// We can't reference this package with ~ because of how Lerna handles packages. 😩 +// We can't reference this package with ~ because of how Lerna handles packages. +// Also, don't convert styles to RTL because react-dates uses an isRTL flag instead. +/*rtl:begin:ignore*/ @import "node_modules/react-dates/lib/css/_datepicker"; +/*rtl:end:ignore*/ .components-datetime { .components-datetime__calendar-help { From 445a0b2562892f919dcb163d33371a7d99e33480 Mon Sep 17 00:00:00 2001 From: Benjamin Ritner <ben@kadencethemes.com> Date: Thu, 20 Dec 2018 01:31:59 -0700 Subject: [PATCH 043/691] Fix for latest posts time class (#12725) --- packages/block-library/src/latest-posts/edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/latest-posts/edit.js b/packages/block-library/src/latest-posts/edit.js index 50310e883ab3c..650d257603e18 100644 --- a/packages/block-library/src/latest-posts/edit.js +++ b/packages/block-library/src/latest-posts/edit.js @@ -176,7 +176,7 @@ class LatestPostsEdit extends Component { <li key={ i }> <a href={ post.link } target="_blank">{ decodeEntities( post.title.rendered.trim() ) || __( '(Untitled)' ) }</a> { displayPostDate && post.date_gmt && - <time dateTime={ format( 'c', post.date_gmt ) } className={ `${ this.props.className }__post-date` }> + <time dateTime={ format( 'c', post.date_gmt ) } className="wp-block-latest-posts__post-date"> { dateI18n( dateFormat, post.date_gmt ) } </time> } From 4f6fdbd2a9f84da0f6efae74e302f511a9a6f4bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Gomes?= <sergiomdgomes@gmail.com> Date: Thu, 20 Dec 2018 08:34:01 +0000 Subject: [PATCH 044/691] Improve typing performance by splitting attributes in state tree. (#12312) * Improve typing performance by splitting attributes in state tree. Attributes have been moved to `attributesByClientId` in order to reduce the impact of typing throughout the state tree. See #11782. Review fixes pass #1 Simplify block flattening code in reducers. Remove alignment toolbar optimization; should be a different PR Fix minor bug in test Fix failing getBlockDependantsCacheBust test Remove new `*WithoutAttributes` selectors. We'll go with a different approach: use the existing selectors, but keep the dependencies as they are. The attributes may get stale, but it doesn't matter if they're not being used. Change attributesById structure to not have an inner attributes Simplifying some selector dependencies Simplify withSaveReusableBlock a bit further and clarify its existence Reuse constant instead of running omit again Remove no longer needed mapClientIds Simplifying reducers after review comments. Further changes to selectors after review Fix types in JSDoc Renaming attributesByClientId to attributes Further selector fixes after review * Revert changes to getBlocks dependants --- packages/editor/src/store/reducer.js | 213 ++++++--- packages/editor/src/store/selectors.js | 23 +- packages/editor/src/store/test/reducer.js | 68 ++- packages/editor/src/store/test/selectors.js | 485 +++++++++++++++----- 4 files changed, 594 insertions(+), 195 deletions(-) diff --git a/packages/editor/src/store/reducer.js b/packages/editor/src/store/reducer.js index 385e99e8be536..dd198c4720181 100644 --- a/packages/editor/src/store/reducer.js +++ b/packages/editor/src/store/reducer.js @@ -13,6 +13,7 @@ import { omitBy, keys, isEqual, + isEmpty, overSome, get, } from 'lodash'; @@ -78,29 +79,52 @@ function mapBlockOrder( blocks, rootClientId = '' ) { } /** - * Given an array of blocks, returns an object containing all blocks, recursing - * into inner blocks. Keys correspond to the block client ID, the value of - * which is the block object. + * Helper method to iterate through all blocks, recursing into inner blocks, + * applying a transformation function to each one. + * Returns a flattened object with the transformed blocks. * * @param {Array} blocks Blocks to flatten. + * @param {Function} transform Transforming function to be applied to each block. * - * @return {Object} Flattened blocks object. + * @return {Object} Flattened object. */ -function getFlattenedBlocks( blocks ) { - const flattenedBlocks = {}; +function flattenBlocks( blocks, transform ) { + const result = {}; const stack = [ ...blocks ]; while ( stack.length ) { - // `innerBlocks` is redundant data which can fall out of sync, since - // this is reflected in `blocks.order`, so exclude from appended block. const { innerBlocks, ...block } = stack.shift(); - stack.push( ...innerBlocks ); - - flattenedBlocks[ block.clientId ] = block; + result[ block.clientId ] = transform( block ); } - return flattenedBlocks; + return result; +} + +/** + * Given an array of blocks, returns an object containing all blocks, without + * attributes, recursing into inner blocks. Keys correspond to the block client + * ID, the value of which is the attributes object. + * + * @param {Array} blocks Blocks to flatten. + * + * @return {Object} Flattened block attributes object. + */ +function getFlattenedBlocksWithoutAttributes( blocks ) { + return flattenBlocks( blocks, ( block ) => omit( block, 'attributes' ) ); +} + +/** + * Given an array of blocks, returns an object containing all block attributes, + * recursing into inner blocks. Keys correspond to the block client ID, the + * value of which is the attributes object. + * + * @param {Array} blocks Blocks to flatten. + * + * @return {Object} Flattened block attributes object. + */ +function getFlattenedBlockAttributes( blocks ) { + return flattenBlocks( blocks, ( block ) => block.attributes ); } /** @@ -252,7 +276,11 @@ const withBlockReset = ( reducer ) => ( state, action ) => { ...state, byClientId: { ...omit( state.byClientId, visibleClientIds ), - ...getFlattenedBlocks( action.blocks ), + ...getFlattenedBlocksWithoutAttributes( action.blocks ), + }, + attributes: { + ...omit( state.attributes, visibleClientIds ), + ...getFlattenedBlockAttributes( action.blocks ), }, order: { ...omit( state.order, visibleClientIds ), @@ -264,6 +292,43 @@ const withBlockReset = ( reducer ) => ( state, action ) => { return reducer( state, action ); }; +/** + * Higher-order reducer which targets the combined blocks reducer and handles + * the `SAVE_REUSABLE_BLOCK_SUCCESS` action. This action can't be handled by + * regular reducers and needs a higher-order reducer since it needs access to + * both `byClientId` and `attributes` simultaneously. + * + * @param {Function} reducer Original reducer function. + * + * @return {Function} Enhanced reducer function. + */ +const withSaveReusableBlock = ( reducer ) => ( state, action ) => { + if ( state && action.type === 'SAVE_REUSABLE_BLOCK_SUCCESS' ) { + const { id, updatedId } = action; + + // If a temporary reusable block is saved, we swap the temporary id with the final one + if ( id === updatedId ) { + return state; + } + + state = { ...state }; + + state.attributes = mapValues( state.attributes, ( attributes, clientId ) => { + const { name } = state.byClientId[ clientId ]; + if ( name === 'core/block' && attributes.ref === id ) { + return { + ...attributes, + ref: updatedId, + }; + } + + return attributes; + } ); + } + + return reducer( state, action ); +}; + /** * Undoable reducer returning the editor post state, including blocks parsed * from current HTML markup. @@ -342,6 +407,8 @@ export const editor = flow( [ withBlockReset, + withSaveReusableBlock, + // Track whether changes exist, resetting at each post save. Relies on // editor initialization firing post reset as an effect. withChangeDetection( { @@ -352,48 +419,71 @@ export const editor = flow( [ byClientId( state = {}, action ) { switch ( action.type ) { case 'SETUP_EDITOR_STATE': - return getFlattenedBlocks( action.blocks ); + return getFlattenedBlocksWithoutAttributes( action.blocks ); case 'RECEIVE_BLOCKS': return { ...state, - ...getFlattenedBlocks( action.blocks ), + ...getFlattenedBlocksWithoutAttributes( action.blocks ), }; - case 'UPDATE_BLOCK_ATTRIBUTES': + case 'UPDATE_BLOCK': // Ignore updates if block isn't known if ( ! state[ action.clientId ] ) { return state; } - // Consider as updates only changed values - const nextAttributes = reduce( action.attributes, ( result, value, key ) => { - if ( value !== result[ key ] ) { - result = getMutateSafeObject( state[ action.clientId ].attributes, result ); - result[ key ] = value; - } - - return result; - }, state[ action.clientId ].attributes ); - - // Skip update if nothing has been changed. The reference will - // match the original block if `reduce` had no changed values. - if ( nextAttributes === state[ action.clientId ].attributes ) { + // Do nothing if only attributes change. + const changes = omit( action.updates, 'attributes' ); + if ( isEmpty( changes ) ) { return state; } - // Otherwise merge attributes into state return { ...state, [ action.clientId ]: { ...state[ action.clientId ], - attributes: nextAttributes, + ...changes, }, }; + case 'INSERT_BLOCKS': + return { + ...state, + ...getFlattenedBlocksWithoutAttributes( action.blocks ), + }; + + case 'REPLACE_BLOCKS': + if ( ! action.blocks ) { + return state; + } + + return { + ...omit( state, action.clientIds ), + ...getFlattenedBlocksWithoutAttributes( action.blocks ), + }; + + case 'REMOVE_BLOCKS': + return omit( state, action.clientIds ); + } + + return state; + }, + + attributes( state = {}, action ) { + switch ( action.type ) { + case 'SETUP_EDITOR_STATE': + return getFlattenedBlockAttributes( action.blocks ); + + case 'RECEIVE_BLOCKS': + return { + ...state, + ...getFlattenedBlockAttributes( action.blocks ), + }; + case 'UPDATE_BLOCK': - // Ignore updates if block isn't known - if ( ! state[ action.clientId ] ) { + // Ignore updates if block isn't known or there are no attribute changes. + if ( ! state[ action.clientId ] || ! action.updates.attributes ) { return state; } @@ -401,14 +491,42 @@ export const editor = flow( [ ...state, [ action.clientId ]: { ...state[ action.clientId ], - ...action.updates, + ...action.updates.attributes, }, }; + case 'UPDATE_BLOCK_ATTRIBUTES': + // Ignore updates if block isn't known + if ( ! state[ action.clientId ] ) { + return state; + } + + // Consider as updates only changed values + const nextAttributes = reduce( action.attributes, ( result, value, key ) => { + if ( value !== result[ key ] ) { + result = getMutateSafeObject( state[ action.clientId ], result ); + result[ key ] = value; + } + + return result; + }, state[ action.clientId ] ); + + // Skip update if nothing has been changed. The reference will + // match the original block if `reduce` had no changed values. + if ( nextAttributes === state[ action.clientId ] ) { + return state; + } + + // Otherwise replace attributes in state + return { + ...state, + [ action.clientId ]: nextAttributes, + }; + case 'INSERT_BLOCKS': return { ...state, - ...getFlattenedBlocks( action.blocks ), + ...getFlattenedBlockAttributes( action.blocks ), }; case 'REPLACE_BLOCKS': @@ -418,34 +536,11 @@ export const editor = flow( [ return { ...omit( state, action.clientIds ), - ...getFlattenedBlocks( action.blocks ), + ...getFlattenedBlockAttributes( action.blocks ), }; case 'REMOVE_BLOCKS': return omit( state, action.clientIds ); - - case 'SAVE_REUSABLE_BLOCK_SUCCESS': { - const { id, updatedId } = action; - - // If a temporary reusable block is saved, we swap the temporary id with the final one - if ( id === updatedId ) { - return state; - } - - return mapValues( state, ( block ) => { - if ( block.name === 'core/block' && block.attributes.ref === id ) { - return { - ...block, - attributes: { - ...block.attributes, - ref: updatedId, - }, - }; - } - - return block; - } ); - } } return state; diff --git a/packages/editor/src/store/selectors.js b/packages/editor/src/store/selectors.js index 86b127e240403..3a7b34a0bdd83 100644 --- a/packages/editor/src/store/selectors.js +++ b/packages/editor/src/store/selectors.js @@ -642,7 +642,7 @@ export const getBlockAttributes = createSelector( return null; } - let { attributes } = block; + let attributes = state.editor.present.blocks.attributes[ clientId ]; // Inject custom source attribute values. // @@ -667,6 +667,7 @@ export const getBlockAttributes = createSelector( }, ( state, clientId ) => [ state.editor.present.blocks.byClientId[ clientId ], + state.editor.present.blocks.attributes[ clientId ], state.editor.present.edits.meta, state.initialEdits.meta, state.currentPost.meta, @@ -698,9 +699,8 @@ export const getBlock = createSelector( }; }, ( state, clientId ) => [ - state.editor.present.blocks.byClientId[ clientId ], - getBlockDependantsCacheBust( state, clientId ), ...getBlockAttributes.getDependants( state, clientId ), + getBlockDependantsCacheBust( state, clientId ), ] ); @@ -747,9 +747,7 @@ export const getBlocks = createSelector( ( clientId ) => getBlock( state, clientId ) ); }, - ( state ) => [ - state.editor.present.blocks, - ] + ( state ) => [ state.editor.present.blocks ] ); /** @@ -1143,10 +1141,8 @@ export const getMultiSelectedBlocks = createSelector( return multiSelectedBlockClientIds.map( ( clientId ) => getBlock( state, clientId ) ); }, ( state ) => [ - state.editor.present.blocks.order, - state.blockSelection.start, - state.blockSelection.end, - state.editor.present.blocks.byClientId, + ...getMultiSelectedBlockClientIds.getDependants( state ), + state.editor.present.blocks, state.editor.present.edits.meta, state.initialEdits.meta, state.currentPost.meta, @@ -1196,7 +1192,7 @@ const isAncestorOf = createSelector( return possibleAncestorId === idToCheck; }, ( state ) => [ - state.editor.present.blocks, + state.editor.present.blocks.order, ], ); @@ -2003,7 +1999,8 @@ export const getInserterItems = createSelector( }, ( state, rootClientId ) => [ state.blockListSettings[ rootClientId ], - state.editor.present.blocks, + state.editor.present.blocks.byClientId, + state.editor.present.blocks.order, state.preferences.insertUsage, state.settings.allowedBlockTypes, state.settings.templateLock, @@ -2037,7 +2034,7 @@ export const hasInserterItems = createSelector( }, ( state, rootClientId ) => [ state.blockListSettings[ rootClientId ], - state.editor.present.blocks, + state.editor.present.blocks.byClientId, state.settings.allowedBlockTypes, state.settings.templateLock, state.reusableBlocks.data, diff --git a/packages/editor/src/store/test/reducer.js b/packages/editor/src/store/test/reducer.js index 884314fe55309..e9c1db43e53e8 100644 --- a/packages/editor/src/store/test/reducer.js +++ b/packages/editor/src/store/test/reducer.js @@ -489,9 +489,12 @@ describe( 'state', () => { expect( state.present.blocks.byClientId.chicken ).toEqual( { clientId: 'chicken', name: 'core/test-block', - attributes: { content: 'ribs' }, isValid: true, } ); + + expect( state.present.blocks.attributes.chicken ).toEqual( { + content: 'ribs', + } ); } ); it( 'should update the reusable block reference if the temporary id is swapped', () => { @@ -517,11 +520,12 @@ describe( 'state', () => { expect( state.present.blocks.byClientId.chicken ).toEqual( { clientId: 'chicken', name: 'core/block', - attributes: { - ref: 3, - }, isValid: false, } ); + + expect( state.present.blocks.attributes.chicken ).toEqual( { + ref: 3, + } ); } ); it( 'should move the block up', () => { @@ -790,9 +794,11 @@ describe( 'state', () => { ribs: { clientId: 'ribs', name: 'core/test-block', - attributes: {}, }, } ); + expect( state.present.blocks.attributes ).toEqual( { + ribs: {}, + } ); } ); it( 'should remove multiple blocks', () => { @@ -827,9 +833,11 @@ describe( 'state', () => { ribs: { clientId: 'ribs', name: 'core/test-block', - attributes: {}, }, } ); + expect( state.present.blocks.attributes ).toEqual( { + ribs: {}, + } ); } ); it( 'should cascade remove to include inner blocks', () => { @@ -1211,6 +1219,46 @@ describe( 'state', () => { } ); describe( 'byClientId', () => { + it( 'should ignore updates to non-existent block', () => { + const original = deepFreeze( editor( undefined, { + type: 'RESET_BLOCKS', + blocks: [], + } ) ); + const state = editor( original, { + type: 'UPDATE_BLOCK_ATTRIBUTES', + clientId: 'kumquat', + attributes: { + updated: true, + }, + } ); + + expect( state.present.blocks.byClientId ).toBe( original.present.blocks.byClientId ); + } ); + + it( 'should return with same reference if no changes in updates', () => { + const original = deepFreeze( editor( undefined, { + type: 'RESET_BLOCKS', + blocks: [ { + clientId: 'kumquat', + attributes: { + updated: true, + }, + innerBlocks: [], + } ], + } ) ); + const state = editor( original, { + type: 'UPDATE_BLOCK_ATTRIBUTES', + clientId: 'kumquat', + attributes: { + updated: true, + }, + } ); + + expect( state.present.blocks.byClientId ).toBe( state.present.blocks.byClientId ); + } ); + } ); + + describe( 'attributes', () => { it( 'should return with attribute block updates', () => { const original = deepFreeze( editor( undefined, { type: 'RESET_BLOCKS', @@ -1228,7 +1276,7 @@ describe( 'state', () => { }, } ); - expect( state.present.blocks.byClientId.kumquat.attributes.updated ).toBe( true ); + expect( state.present.blocks.attributes.kumquat.updated ).toBe( true ); } ); it( 'should accumulate attribute block updates', () => { @@ -1250,7 +1298,7 @@ describe( 'state', () => { }, } ); - expect( state.present.blocks.byClientId.kumquat.attributes ).toEqual( { + expect( state.present.blocks.attributes.kumquat ).toEqual( { updated: true, moreUpdated: true, } ); @@ -1269,7 +1317,7 @@ describe( 'state', () => { }, } ); - expect( state.present.blocks.byClientId ).toBe( original.present.blocks.byClientId ); + expect( state.present.blocks.attributes ).toBe( original.present.blocks.attributes ); } ); it( 'should return with same reference if no changes in updates', () => { @@ -1291,7 +1339,7 @@ describe( 'state', () => { }, } ); - expect( state.present.blocks.byClientId ).toBe( state.present.blocks.byClientId ); + expect( state.present.blocks.attributes ).toBe( state.present.blocks.attributes ); } ); } ); } ); diff --git a/packages/editor/src/store/test/selectors.js b/packages/editor/src/store/test/selectors.js index 6303dde765a92..cfe66ed4c43c2 100644 --- a/packages/editor/src/store/test/selectors.js +++ b/packages/editor/src/store/test/selectors.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { filter, without } from 'lodash'; +import { filter, without, omit } from 'lodash'; /** * WordPress dependencies @@ -1150,6 +1150,7 @@ describe( 'selectors', () => { present: { blocks: { byClientId: {}, + attributes: {}, order: {}, }, edits: {}, @@ -1169,6 +1170,7 @@ describe( 'selectors', () => { present: { blocks: { byClientId: {}, + attributes: {}, order: {}, }, edits: {}, @@ -1192,6 +1194,7 @@ describe( 'selectors', () => { present: { blocks: { byClientId: {}, + attributes: {}, order: {}, }, edits: {}, @@ -1213,6 +1216,7 @@ describe( 'selectors', () => { present: { blocks: { byClientId: {}, + attributes: {}, order: {}, }, edits: {}, @@ -1238,9 +1242,11 @@ describe( 'selectors', () => { clientId: 123, name: 'core/test-block-a', isValid: true, - attributes: { - text: '', - }, + }, + }, + attributes: { + 123: { + text: '', }, }, order: { @@ -1267,9 +1273,11 @@ describe( 'selectors', () => { 123: { clientId: 123, name: 'core/test-freeform', - attributes: { - content: '', - }, + }, + }, + attributes: { + 123: { + content: '', }, }, order: { @@ -1297,9 +1305,11 @@ describe( 'selectors', () => { clientId: 123, name: 'core/test-freeform', isValid: true, - attributes: { - content: '', - }, + }, + }, + attributes: { + 123: { + content: '', }, }, order: { @@ -1327,6 +1337,7 @@ describe( 'selectors', () => { present: { blocks: { byClientId: {}, + attributes: {}, order: {}, }, edits: {}, @@ -1353,6 +1364,7 @@ describe( 'selectors', () => { present: { blocks: { byClientId: {}, + attributes: {}, order: {}, }, edits: {}, @@ -1498,6 +1510,7 @@ describe( 'selectors', () => { present: { blocks: { byClientId: {}, + attributes: {}, order: {}, }, edits: {}, @@ -1520,9 +1533,11 @@ describe( 'selectors', () => { clientId: 123, name: 'core/test-block-a', isValid: true, - attributes: { - text: '', - }, + }, + }, + attributes: { + 123: { + text: '', }, }, order: { @@ -1549,9 +1564,11 @@ describe( 'selectors', () => { clientId: 123, name: 'core/test-block-a', isValid: true, - attributes: { - text: '', - }, + }, + }, + attributes: { + 123: { + text: '', }, }, order: { @@ -1578,6 +1595,7 @@ describe( 'selectors', () => { present: { blocks: { byClientId: {}, + attributes: {}, order: {}, }, edits: {}, @@ -1598,6 +1616,7 @@ describe( 'selectors', () => { present: { blocks: { byClientId: {}, + attributes: {}, order: {}, }, edits: { @@ -1622,9 +1641,11 @@ describe( 'selectors', () => { clientId: 123, name: 'core/test-freeform', isValid: true, - attributes: { - content: '', - }, + }, + }, + attributes: { + 123: { + content: '', }, }, order: { @@ -1651,9 +1672,11 @@ describe( 'selectors', () => { clientId: 123, name: 'core/test-freeform', isValid: true, - attributes: { - content: '', - }, + }, + }, + attributes: { + 123: { + content: '', }, }, order: { @@ -1682,9 +1705,11 @@ describe( 'selectors', () => { clientId: 123, name: 'core/test-freeform', isValid: true, - attributes: { - content: 'Test Data', - }, + }, + }, + attributes: { + 123: { + content: 'Test Data', }, }, order: { @@ -1713,17 +1738,19 @@ describe( 'selectors', () => { clientId: 123, name: 'core/test-freeform', isValid: true, - attributes: { - content: '', - }, }, 456: { clientId: 456, name: 'core/test-freeform', isValid: true, - attributes: { - content: '', - }, + }, + }, + attributes: { + 123: { + content: '', + }, + 456: { + content: '', }, }, order: { @@ -1866,7 +1893,8 @@ describe( 'selectors', () => { } ); describe( 'getBlockDependantsCacheBust', () => { - const rootBlock = { clientId: 123, name: 'core/paragraph', attributes: {} }; + const rootBlock = { clientId: 123, name: 'core/paragraph' }; + const rootBlockAttributes = {}; const rootOrder = [ 123 ]; it( 'returns an unchanging reference', () => { @@ -1880,6 +1908,9 @@ describe( 'selectors', () => { byClientId: { 123: rootBlock, }, + attributes: { + 123: rootBlockAttributes, + }, order: { '': rootOrder, 123: rootBlockOrder, @@ -1899,6 +1930,9 @@ describe( 'selectors', () => { byClientId: { 123: rootBlock, }, + attributes: { + 123: rootBlockAttributes, + }, order: { '': rootOrder, 123: rootBlockOrder, @@ -1924,6 +1958,9 @@ describe( 'selectors', () => { byClientId: { 123: rootBlock, }, + attributes: { + 123: rootBlockAttributes, + }, order: { '': rootOrder, 123: [], @@ -1942,7 +1979,11 @@ describe( 'selectors', () => { blocks: { byClientId: { 123: rootBlock, - 456: { clientId: 456, name: 'core/paragraph', attributes: {} }, + 456: { clientId: 456, name: 'core/paragraph' }, + }, + attributes: { + 123: rootBlockAttributes, + 456: {}, }, order: { '': rootOrder, @@ -1963,7 +2004,8 @@ describe( 'selectors', () => { it( 'returns an unchanging reference on unchanging inner block', () => { const rootBlockOrder = [ 456 ]; - const childBlock = { clientId: 456, name: 'core/paragraph', attributes: {} }; + const childBlock = { clientId: 456, name: 'core/paragraph' }; + const childBlockAttributes = {}; const childBlockOrder = []; const state = { @@ -1975,6 +2017,10 @@ describe( 'selectors', () => { 123: rootBlock, 456: childBlock, }, + attributes: { + 123: rootBlockAttributes, + 456: childBlockAttributes, + }, order: { '': rootOrder, 123: rootBlockOrder, @@ -1996,6 +2042,10 @@ describe( 'selectors', () => { 123: rootBlock, 456: childBlock, }, + attributes: { + 123: rootBlockAttributes, + 456: childBlockAttributes, + }, order: { '': rootOrder, 123: rootBlockOrder, @@ -2024,7 +2074,11 @@ describe( 'selectors', () => { blocks: { byClientId: { 123: rootBlock, - 456: { clientId: 456, name: 'core/paragraph', attributes: {} }, + 456: { clientId: 456, name: 'core/paragraph' }, + }, + attributes: { + 123: rootBlockAttributes, + 456: {}, }, order: { '': rootOrder, @@ -2045,7 +2099,11 @@ describe( 'selectors', () => { blocks: { byClientId: { 123: rootBlock, - 456: { clientId: 456, name: 'core/paragraph', attributes: { content: [ 'foo' ] } }, + 456: { clientId: 456, name: 'core/paragraph' }, + }, + attributes: { + 123: rootBlockAttributes, + 456: { content: [ 'foo' ] }, }, order: { '': rootOrder, @@ -2066,7 +2124,8 @@ describe( 'selectors', () => { it( 'returns a new reference on updated grandchild inner block', () => { const rootBlockOrder = [ 456 ]; - const childBlock = { clientId: 456, name: 'core/paragraph', attributes: {} }; + const childBlock = { clientId: 456, name: 'core/paragraph' }; + const childBlockAttributes = {}; const childBlockOrder = [ 789 ]; const grandChildBlockOrder = []; @@ -2078,7 +2137,12 @@ describe( 'selectors', () => { byClientId: { 123: rootBlock, 456: childBlock, - 789: { clientId: 789, name: 'core/paragraph', attributes: {} }, + 789: { clientId: 789, name: 'core/paragraph' }, + }, + attributes: { + 123: rootBlockAttributes, + 456: childBlockAttributes, + 789: {}, }, order: { '': rootOrder, @@ -2101,7 +2165,12 @@ describe( 'selectors', () => { byClientId: { 123: rootBlock, 456: childBlock, - 789: { clientId: 789, name: 'core/paragraph', attributes: { content: [ 'foo' ] } }, + 789: { clientId: 789, name: 'core/paragraph' }, + }, + attributes: { + 123: rootBlockAttributes, + 456: childBlockAttributes, + 789: { content: [ 'foo' ] }, }, order: { '': rootOrder, @@ -2130,6 +2199,7 @@ describe( 'selectors', () => { present: { blocks: { byClientId: {}, + attributes: {}, order: {}, }, edits: {}, @@ -2153,9 +2223,11 @@ describe( 'selectors', () => { 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': { clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1', name: 'core/paragraph', - attributes: {}, }, }, + attributes: { + 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': {}, + }, order: { '': [ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1' ], 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': [], @@ -2181,7 +2253,10 @@ describe( 'selectors', () => { present: { blocks: { byClientId: { - 123: { clientId: 123, name: 'core/paragraph', attributes: {} }, + 123: { clientId: 123, name: 'core/paragraph' }, + }, + attributes: { + 123: {}, }, order: { '': [ 123 ], @@ -2209,6 +2284,7 @@ describe( 'selectors', () => { present: { blocks: { byClientId: {}, + attributes: {}, order: {}, }, edits: {}, @@ -2227,8 +2303,12 @@ describe( 'selectors', () => { present: { blocks: { byClientId: { - 123: { clientId: 123, name: 'core/paragraph', attributes: {} }, - 456: { clientId: 456, name: 'core/paragraph', attributes: {} }, + 123: { clientId: 123, name: 'core/paragraph' }, + 456: { clientId: 456, name: 'core/paragraph' }, + }, + attributes: { + 123: {}, + 456: {}, }, order: { '': [ 123 ], @@ -2279,7 +2359,10 @@ describe( 'selectors', () => { present: { blocks: { byClientId: { - 123: { clientId: 123, name: 'core/meta-block', attributes: {} }, + 123: { clientId: 123, name: 'core/meta-block' }, + }, + attributes: { + 123: {}, }, order: { '': [ 123 ], @@ -2313,8 +2396,12 @@ describe( 'selectors', () => { present: { blocks: { byClientId: { - 23: { clientId: 23, name: 'core/heading', attributes: {} }, - 123: { clientId: 123, name: 'core/paragraph', attributes: {} }, + 23: { clientId: 23, name: 'core/heading' }, + 123: { clientId: 123, name: 'core/paragraph' }, + }, + attributes: { + 23: {}, + 123: {}, }, order: { '': [ 123, 23 ], @@ -2341,21 +2428,38 @@ describe( 'selectors', () => { present: { blocks: { byClientId: { - 'uuid-2': { clientId: 'uuid-2', name: 'core/image', attributes: {} }, - 'uuid-4': { clientId: 'uuid-4', name: 'core/paragraph', attributes: {} }, - 'uuid-6': { clientId: 'uuid-6', name: 'core/paragraph', attributes: {} }, - 'uuid-8': { clientId: 'uuid-8', name: 'core/block', attributes: {} }, - 'uuid-10': { clientId: 'uuid-10', name: 'core/columns', attributes: {} }, - 'uuid-12': { clientId: 'uuid-12', name: 'core/column', attributes: {} }, - 'uuid-14': { clientId: 'uuid-14', name: 'core/column', attributes: {} }, - 'uuid-16': { clientId: 'uuid-16', name: 'core/quote', attributes: {} }, - 'uuid-18': { clientId: 'uuid-18', name: 'core/block', attributes: {} }, - 'uuid-20': { clientId: 'uuid-20', name: 'core/gallery', attributes: {} }, - 'uuid-22': { clientId: 'uuid-22', name: 'core/block', attributes: {} }, - 'uuid-24': { clientId: 'uuid-24', name: 'core/columns', attributes: {} }, - 'uuid-26': { clientId: 'uuid-26', name: 'core/column', attributes: {} }, - 'uuid-28': { clientId: 'uuid-28', name: 'core/column', attributes: {} }, - 'uuid-30': { clientId: 'uuid-30', name: 'core/paragraph', attributes: {} }, + 'uuid-2': { clientId: 'uuid-2', name: 'core/image' }, + 'uuid-4': { clientId: 'uuid-4', name: 'core/paragraph' }, + 'uuid-6': { clientId: 'uuid-6', name: 'core/paragraph' }, + 'uuid-8': { clientId: 'uuid-8', name: 'core/block' }, + 'uuid-10': { clientId: 'uuid-10', name: 'core/columns' }, + 'uuid-12': { clientId: 'uuid-12', name: 'core/column' }, + 'uuid-14': { clientId: 'uuid-14', name: 'core/column' }, + 'uuid-16': { clientId: 'uuid-16', name: 'core/quote' }, + 'uuid-18': { clientId: 'uuid-18', name: 'core/block' }, + 'uuid-20': { clientId: 'uuid-20', name: 'core/gallery' }, + 'uuid-22': { clientId: 'uuid-22', name: 'core/block' }, + 'uuid-24': { clientId: 'uuid-24', name: 'core/columns' }, + 'uuid-26': { clientId: 'uuid-26', name: 'core/column' }, + 'uuid-28': { clientId: 'uuid-28', name: 'core/column' }, + 'uuid-30': { clientId: 'uuid-30', name: 'core/paragraph' }, + }, + attributes: { + 'uuid-2': {}, + 'uuid-4': {}, + 'uuid-6': {}, + 'uuid-8': {}, + 'uuid-10': {}, + 'uuid-12': {}, + 'uuid-14': {}, + 'uuid-16': {}, + 'uuid-18': {}, + 'uuid-20': {}, + 'uuid-22': {}, + 'uuid-24': {}, + 'uuid-26': {}, + 'uuid-28': {}, + 'uuid-30': {}, }, order: { '': [ 'uuid-6', 'uuid-8', 'uuid-10', 'uuid-22' ], @@ -2401,21 +2505,38 @@ describe( 'selectors', () => { present: { blocks: { byClientId: { - 'uuid-2': { clientId: 'uuid-2', name: 'core/image', attributes: {} }, - 'uuid-4': { clientId: 'uuid-4', name: 'core/paragraph', attributes: {} }, - 'uuid-6': { clientId: 'uuid-6', name: 'core/paragraph', attributes: {} }, - 'uuid-8': { clientId: 'uuid-8', name: 'core/block', attributes: {} }, - 'uuid-10': { clientId: 'uuid-10', name: 'core/columns', attributes: {} }, - 'uuid-12': { clientId: 'uuid-12', name: 'core/column', attributes: {} }, - 'uuid-14': { clientId: 'uuid-14', name: 'core/column', attributes: {} }, - 'uuid-16': { clientId: 'uuid-16', name: 'core/quote', attributes: {} }, - 'uuid-18': { clientId: 'uuid-18', name: 'core/block', attributes: {} }, - 'uuid-20': { clientId: 'uuid-20', name: 'core/gallery', attributes: {} }, - 'uuid-22': { clientId: 'uuid-22', name: 'core/block', attributes: {} }, - 'uuid-24': { clientId: 'uuid-24', name: 'core/columns', attributes: {} }, - 'uuid-26': { clientId: 'uuid-26', name: 'core/column', attributes: {} }, - 'uuid-28': { clientId: 'uuid-28', name: 'core/column', attributes: {} }, - 'uuid-30': { clientId: 'uuid-30', name: 'core/paragraph', attributes: {} }, + 'uuid-2': { clientId: 'uuid-2', name: 'core/image' }, + 'uuid-4': { clientId: 'uuid-4', name: 'core/paragraph' }, + 'uuid-6': { clientId: 'uuid-6', name: 'core/paragraph' }, + 'uuid-8': { clientId: 'uuid-8', name: 'core/block' }, + 'uuid-10': { clientId: 'uuid-10', name: 'core/columns' }, + 'uuid-12': { clientId: 'uuid-12', name: 'core/column' }, + 'uuid-14': { clientId: 'uuid-14', name: 'core/column' }, + 'uuid-16': { clientId: 'uuid-16', name: 'core/quote' }, + 'uuid-18': { clientId: 'uuid-18', name: 'core/block' }, + 'uuid-20': { clientId: 'uuid-20', name: 'core/gallery' }, + 'uuid-22': { clientId: 'uuid-22', name: 'core/block' }, + 'uuid-24': { clientId: 'uuid-24', name: 'core/columns' }, + 'uuid-26': { clientId: 'uuid-26', name: 'core/column' }, + 'uuid-28': { clientId: 'uuid-28', name: 'core/column' }, + 'uuid-30': { clientId: 'uuid-30', name: 'core/paragraph' }, + }, + attributes: { + 'uuid-2': {}, + 'uuid-4': {}, + 'uuid-6': {}, + 'uuid-8': {}, + 'uuid-10': {}, + 'uuid-12': {}, + 'uuid-14': {}, + 'uuid-16': {}, + 'uuid-18': {}, + 'uuid-20': {}, + 'uuid-22': {}, + 'uuid-24': {}, + 'uuid-26': {}, + 'uuid-28': {}, + 'uuid-30': {}, }, order: { '': [ 'uuid-6', 'uuid-8', 'uuid-10', 'uuid-22' ], @@ -2464,8 +2585,12 @@ describe( 'selectors', () => { present: { blocks: { byClientId: { - 23: { clientId: 23, name: 'core/heading', attributes: {} }, - 123: { clientId: 123, name: 'core/paragraph', attributes: {} }, + 23: { clientId: 23, name: 'core/heading' }, + 123: { clientId: 123, name: 'core/paragraph' }, + }, + attributes: { + 23: {}, + 123: {}, }, order: { '': [ 123, 23 ], @@ -2484,9 +2609,14 @@ describe( 'selectors', () => { present: { blocks: { byClientId: { - 123: { clientId: 123, name: 'core/columns', attributes: {} }, - 456: { clientId: 456, name: 'core/paragraph', attributes: {} }, - 789: { clientId: 789, name: 'core/paragraph', attributes: {} }, + 123: { clientId: 123, name: 'core/columns' }, + 456: { clientId: 456, name: 'core/paragraph' }, + 789: { clientId: 789, name: 'core/paragraph' }, + }, + attributes: { + 123: {}, + 456: {}, + 789: {}, }, order: { '': [ 123 ], @@ -2542,9 +2672,14 @@ describe( 'selectors', () => { present: { blocks: { byClientId: { - 123: { clientId: 123, name: 'core/heading', attributes: {} }, - 456: { clientId: 456, name: 'core/paragraph', attributes: {} }, - 789: { clientId: 789, name: 'core/paragraph', attributes: {} }, + 123: { clientId: 123, name: 'core/heading' }, + 456: { clientId: 456, name: 'core/paragraph' }, + 789: { clientId: 789, name: 'core/paragraph' }, + }, + attributes: { + 123: {}, + 456: {}, + 789: {}, }, order: { '': [ 123, 456 ], @@ -2568,6 +2703,7 @@ describe( 'selectors', () => { present: { blocks: { byClientId: {}, + attributes: {}, order: {}, }, }, @@ -2613,8 +2749,12 @@ describe( 'selectors', () => { present: { blocks: { byClientId: { - 23: { clientId: 23, name: 'core/heading', attributes: {} }, - 123: { clientId: 123, name: 'core/paragraph', attributes: {} }, + 23: { clientId: 23, name: 'core/heading' }, + 123: { clientId: 123, name: 'core/paragraph' }, + }, + attributes: { + 23: {}, + 123: {}, }, order: { '': [ 23, 123 ], @@ -2639,8 +2779,12 @@ describe( 'selectors', () => { present: { blocks: { byClientId: { - 23: { clientId: 23, name: 'core/heading', attributes: {} }, - 123: { clientId: 123, name: 'core/paragraph', attributes: {} }, + 23: { clientId: 23, name: 'core/heading' }, + 123: { clientId: 123, name: 'core/paragraph' }, + }, + attributes: { + 23: {}, + 123: {}, }, order: { '': [ 23, 123 ], @@ -2665,8 +2809,12 @@ describe( 'selectors', () => { present: { blocks: { byClientId: { - 23: { clientId: 23, name: 'core/heading', attributes: {} }, - 123: { clientId: 123, name: 'core/paragraph', attributes: {} }, + 23: { clientId: 23, name: 'core/heading' }, + 123: { clientId: 123, name: 'core/paragraph' }, + }, + attributes: { + 23: {}, + 123: {}, }, order: { '': [ 23, 123 ], @@ -2835,6 +2983,7 @@ describe( 'selectors', () => { present: { blocks: { byClientId: {}, + attributes: {}, order: {}, }, edits: {}, @@ -3433,6 +3582,10 @@ describe( 'selectors', () => { clientId1: { clientId: 'clientId1' }, clientId2: { clientId: 'clientId2' }, }, + attributes: { + clientId1: {}, + clientId2: {}, + }, order: { '': [ 'clientId1' ], clientId1: [ 'clientId2' ], @@ -3469,6 +3622,9 @@ describe( 'selectors', () => { byClientId: { clientId1: { clientId: 'clientId1' }, }, + attributes: { + clientId1: {}, + }, order: { '': [ 'clientId1' ], clientId1: [], @@ -3502,6 +3658,10 @@ describe( 'selectors', () => { clientId1: { clientId: 'clientId1' }, clientId2: { clientId: 'clientId2' }, }, + attributes: { + clientId1: {}, + clientId2: {}, + }, order: { '': [ 'clientId1' ], clientId1: [ 'clientId2' ], @@ -3536,6 +3696,10 @@ describe( 'selectors', () => { clientId1: { clientId: 'clientId1' }, clientId2: { clientId: 'clientId2' }, }, + attributes: { + clientId1: {}, + clientId2: {}, + }, order: { '': [ 'clientId1', 'clientId2' ], clientId1: [], @@ -3570,6 +3734,10 @@ describe( 'selectors', () => { clientId1: { clientId: 'clientId1' }, clientId2: { clientId: 'clientId2' }, }, + attributes: { + clientId1: {}, + clientId2: {}, + }, order: { '': [ 'clientId1', 'clientId2' ], clientId1: [], @@ -3684,6 +3852,7 @@ describe( 'selectors', () => { present: { blocks: { byClientId: {}, + attributes: {}, order: {}, }, edits: {}, @@ -3702,8 +3871,12 @@ describe( 'selectors', () => { present: { blocks: { byClientId: { - 123: { clientId: 123, name: 'core/image', attributes: {} }, - 456: { clientId: 456, name: 'core/quote', attributes: {} }, + 123: { clientId: 123, name: 'core/image' }, + 456: { clientId: 456, name: 'core/quote' }, + }, + attributes: { + 123: {}, + 456: {}, }, order: { '': [ 123, 456 ], @@ -3725,7 +3898,10 @@ describe( 'selectors', () => { present: { blocks: { byClientId: { - 123: { clientId: 123, name: 'core/image', attributes: {} }, + 123: { clientId: 123, name: 'core/image' }, + }, + attributes: { + 123: {}, }, order: { '': [ 123 ], @@ -3747,7 +3923,10 @@ describe( 'selectors', () => { present: { blocks: { byClientId: { - 456: { clientId: 456, name: 'core/quote', attributes: {} }, + 456: { clientId: 456, name: 'core/quote' }, + }, + attributes: { + 456: {}, }, order: { '': [ 456 ], @@ -3769,7 +3948,10 @@ describe( 'selectors', () => { present: { blocks: { byClientId: { - 567: { clientId: 567, name: 'core-embed/youtube', attributes: {} }, + 567: { clientId: 567, name: 'core-embed/youtube' }, + }, + attributes: { + 567: {}, }, order: { '': [ 567 ], @@ -3791,8 +3973,12 @@ describe( 'selectors', () => { present: { blocks: { byClientId: { - 456: { clientId: 456, name: 'core/quote', attributes: {} }, - 789: { clientId: 789, name: 'core/paragraph', attributes: {} }, + 456: { clientId: 456, name: 'core/quote' }, + 789: { clientId: 789, name: 'core/paragraph' }, + }, + attributes: { + 456: {}, + 789: {}, }, order: { '': [ 456, 789 ], @@ -3844,7 +4030,10 @@ describe( 'selectors', () => { present: { blocks: { byClientId: { - [ block.clientId ]: block, + [ block.clientId ]: omit( block, 'attributes' ), + }, + attributes: { + [ block.clientId ]: block.attributes, }, order: { '': [ block.clientId ], @@ -3872,7 +4061,10 @@ describe( 'selectors', () => { present: { blocks: { byClientId: { - [ block.clientId ]: block, + [ block.clientId ]: omit( block, 'attributes' ), + }, + attributes: { + [ block.clientId ]: block.attributes, }, order: { '': [ block.clientId ], @@ -3899,7 +4091,10 @@ describe( 'selectors', () => { present: { blocks: { byClientId: { - [ unknownBlock.clientId ]: unknownBlock, + [ unknownBlock.clientId ]: omit( unknownBlock, 'attributes' ), + }, + attributes: { + [ unknownBlock.clientId ]: unknownBlock.attributes, }, order: { '': [ unknownBlock.clientId ], @@ -3929,8 +4124,12 @@ describe( 'selectors', () => { present: { blocks: { byClientId: { - [ firstUnknown.clientId ]: firstUnknown, - [ secondUnknown.clientId ]: secondUnknown, + [ firstUnknown.clientId ]: omit( firstUnknown, 'attributes' ), + [ secondUnknown.clientId ]: omit( secondUnknown, 'attributes' ), + }, + attributes: { + [ firstUnknown.clientId ]: firstUnknown.attributes, + [ secondUnknown.clientId ]: secondUnknown.attributes, }, order: { '': [ firstUnknown.clientId, secondUnknown.clientId ], @@ -3955,7 +4154,10 @@ describe( 'selectors', () => { present: { blocks: { byClientId: { - [ defaultBlock.clientId ]: defaultBlock, + [ defaultBlock.clientId ]: omit( defaultBlock, 'attributes' ), + }, + attributes: { + [ defaultBlock.clientId ]: defaultBlock.attributes, }, order: { '': [ defaultBlock.clientId ], @@ -3981,11 +4183,13 @@ describe( 'selectors', () => { blocks: { byClientId: { [ defaultBlock.clientId ]: { - ...defaultBlock, - attributes: { - ...defaultBlock.attributes, - modified: true, - }, + ...omit( defaultBlock, 'attributes' ), + }, + }, + attributes: { + [ defaultBlock.clientId ]: { + ...defaultBlock.attributes, + modified: true, }, }, order: { @@ -4012,6 +4216,7 @@ describe( 'selectors', () => { present: { blocks: { byClientId: {}, + attributes: {}, }, }, }, @@ -4027,6 +4232,7 @@ describe( 'selectors', () => { present: { blocks: { byClientId: {}, + attributes: {}, }, }, }, @@ -4044,6 +4250,7 @@ describe( 'selectors', () => { present: { blocks: { byClientId: {}, + attributes: {}, }, }, }, @@ -4061,6 +4268,7 @@ describe( 'selectors', () => { present: { blocks: { byClientId: {}, + attributes: {}, }, }, }, @@ -4078,6 +4286,7 @@ describe( 'selectors', () => { present: { blocks: { byClientId: {}, + attributes: {}, }, }, }, @@ -4095,6 +4304,9 @@ describe( 'selectors', () => { byClientId: { block1: { name: 'core/test-block-a' }, }, + attributes: { + block1: {}, + }, }, }, }, @@ -4112,6 +4324,9 @@ describe( 'selectors', () => { byClientId: { block1: { name: 'core/test-block-b' }, }, + attributes: { + block1: {}, + }, }, }, }, @@ -4129,6 +4344,9 @@ describe( 'selectors', () => { byClientId: { block1: { name: 'core/test-block-a' }, }, + attributes: { + block1: {}, + }, }, }, }, @@ -4150,6 +4368,9 @@ describe( 'selectors', () => { byClientId: { block1: { name: 'core/test-block-a' }, }, + attributes: { + block1: {}, + }, }, }, }, @@ -4171,6 +4392,9 @@ describe( 'selectors', () => { byClientId: { block1: { name: 'core/test-block-b' }, }, + attributes: { + block1: {}, + }, }, }, }, @@ -4194,6 +4418,9 @@ describe( 'selectors', () => { byClientId: { block1: { name: 'core/test-block-a' }, }, + attributes: { + block1: {}, + }, order: {}, }, edits: {}, @@ -4255,12 +4482,18 @@ describe( 'selectors', () => { block1ref: { name: 'core/block', clientId: 'block1ref', + }, + itselfBlock1: { name: 'core/test-block-a' }, + itselfBlock2: { name: 'core/test-block-b' }, + }, + attributes: { + block1ref: { attributes: { ref: 1, }, }, - itselfBlock1: { name: 'core/test-block-a' }, - itselfBlock2: { name: 'core/test-block-b' }, + itselfBlock1: {}, + itselfBlock2: {}, }, order: { '': [ 'block1ref' ], @@ -4311,15 +4544,23 @@ describe( 'selectors', () => { block2ref: { name: 'core/block', clientId: 'block1ref', - attributes: { - ref: 2, - }, }, referredBlock1: { name: 'core/test-block-a' }, referredBlock2: { name: 'core/test-block-b' }, childReferredBlock2: { name: 'core/test-block-a' }, grandchildReferredBlock2: { name: 'core/test-block-b' }, }, + attributes: { + block2ref: { + attributes: { + ref: 2, + }, + }, + referredBlock1: {}, + referredBlock2: {}, + childReferredBlock2: {}, + grandchildReferredBlock2: {}, + }, order: { '': [ 'block2ref' ], referredBlock2: [ 'childReferredBlock2' ], @@ -4370,6 +4611,10 @@ describe( 'selectors', () => { block1: { name: 'core/test-block-a' }, block2: { name: 'core/test-block-a' }, }, + attributes: { + block1: {}, + block2: {}, + }, order: {}, }, edits: {}, @@ -4413,6 +4658,12 @@ describe( 'selectors', () => { block3: { name: 'core/test-block-a' }, block4: { name: 'core/test-block-a' }, }, + attributes: { + block1: {}, + block2: {}, + block3: {}, + block4: {}, + }, order: { '': [ 'block3', 'block4' ], }, @@ -4477,6 +4728,9 @@ describe( 'selectors', () => { byClientId: { block1: { clientId: 'block1', name: 'core/test-block-b' }, }, + attributes: { + block1: { attribute: {} }, + }, order: { '': [ 'block1' ], }, @@ -4506,6 +4760,7 @@ describe( 'selectors', () => { present: { blocks: { byClientId: {}, + attributes: {}, order: {}, }, edits: {}, @@ -4533,6 +4788,7 @@ describe( 'selectors', () => { present: { blocks: { byClientId: {}, + attributes: {}, order: {}, }, edits: {}, @@ -4565,6 +4821,9 @@ describe( 'selectors', () => { byClientId: { block1: { name: 'core/test-block-b' }, }, + attributes: { + block1: { attribute: {} }, + }, order: { '': [ 'block1' ], }, From c523c8b3273302dafb663ef5d6c252863d17a159 Mon Sep 17 00:00:00 2001 From: Pascal Birchler <pascal.birchler@gmail.com> Date: Thu, 20 Dec 2018 09:36:14 +0100 Subject: [PATCH 045/691] Remove temporary workaround that should have been removed in Gutenberg 4.5 (#12556) --- lib/client-assets.php | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/lib/client-assets.php b/lib/client-assets.php index c1a6e847bba5e..d38a3c2098feb 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -501,24 +501,6 @@ function gutenberg_register_scripts_and_styles() { $live_reload_url ); } - - // Temporary backward compatibility for `wp-polyfill-ecmascript`, which has - // since been absorbed into `wp-polyfill`. - // - // [TODO][REMOVEME] To be removed in Gutenberg v4.5. - gutenberg_override_script( - 'wp-polyfill-ecmascript', - null, - array( - 'wp-polyfill', - 'wp-deprecated', - ) - ); - wp_script_add_data( - 'wp-polyfill-ecmascript', - 'data', - 'wp.deprecated( "wp-polyfill-ecmascript script handle", { plugin: "Gutenberg", version: "4.5" } );' - ); } add_action( 'wp_enqueue_scripts', 'gutenberg_register_scripts_and_styles', 5 ); add_action( 'admin_enqueue_scripts', 'gutenberg_register_scripts_and_styles', 5 ); From 80be381921ce818c0381e877f510bbc0be0fcf24 Mon Sep 17 00:00:00 2001 From: John <johng75@gmail.com> Date: Thu, 20 Dec 2018 08:36:39 +0000 Subject: [PATCH 046/691] Remove duplicate toolbarcontainer class name (#12357) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove duplicate toolbarcontainer class name `components-toolbar` is added by `ToolbarContainer` itself, so we don’t need to add it again * Swap components-toolbar between container and toolbar --- packages/components/src/toolbar/toolbar-container.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/components/src/toolbar/toolbar-container.js b/packages/components/src/toolbar/toolbar-container.js index 3be8e6566da91..9361c1fcf0bbb 100644 --- a/packages/components/src/toolbar/toolbar-container.js +++ b/packages/components/src/toolbar/toolbar-container.js @@ -1,10 +1,5 @@ -/** - * External dependencies - */ -import classnames from 'classnames'; - const ToolbarContainer = ( props ) => ( - <div className={ classnames( 'components-toolbar', props.className ) }> + <div className={ props.className }> { props.children } </div> ); From fbf51d12bfc133479fbcceb69ec5c459a4848255 Mon Sep 17 00:00:00 2001 From: Pascal Birchler <pascal.birchler@gmail.com> Date: Thu, 20 Dec 2018 09:39:30 +0100 Subject: [PATCH 047/691] Disable clipboard button in file block during upload (#12499) See #12493. --- packages/block-library/src/file/edit.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/block-library/src/file/edit.js b/packages/block-library/src/file/edit.js index eb311fb48a73a..eb707a104eec5 100644 --- a/packages/block-library/src/file/edit.js +++ b/packages/block-library/src/file/edit.js @@ -216,6 +216,7 @@ class FileEdit extends Component { className={ `${ className }__copy-url-button` } onCopy={ this.confirmCopyURL } onFinishCopy={ this.resetCopyConfirmation } + disabled={ isBlobURL( href ) } > { showCopyConfirmation ? __( 'Copied!' ) : __( 'Copy URL' ) } </ClipboardButton> From 87d5bd148ef74f4ed7f903751633a7f308496dcf Mon Sep 17 00:00:00 2001 From: Andrei Lupu <euthelup@gmail.com> Date: Thu, 20 Dec 2018 10:40:41 +0200 Subject: [PATCH 048/691] Persist alignment when transforming a gallery to an image and vice-versa (#12242) * Persist alignment when transforming a gallery to an image and vice-versa. For #11374 * Optimize loop itteration for #12242 --- packages/block-library/src/gallery/index.js | 24 ++++++++++++--------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/packages/block-library/src/gallery/index.js b/packages/block-library/src/gallery/index.js index 080da725b8d92..b43b9ae3a6034 100644 --- a/packages/block-library/src/gallery/index.js +++ b/packages/block-library/src/gallery/index.js @@ -99,14 +99,18 @@ export const settings = { isMultiBlock: true, blocks: [ 'core/image' ], transform: ( attributes ) => { + // Init the align attribute from the first item which may be either the placeholder or an image. + let { align } = attributes[ 0 ]; + // Loop through all the images and check if they have the same align. + align = every( attributes, [ 'align', align ] ) ? align : undefined; + const validImages = filter( attributes, ( { id, url } ) => id && url ); - if ( validImages.length > 0 ) { - return createBlock( 'core/gallery', { - images: validImages.map( ( { id, url, alt, caption } ) => ( { id, url, alt, caption } ) ), - ids: validImages.map( ( { id } ) => id ), - } ); - } - return createBlock( 'core/gallery' ); + + return createBlock( 'core/gallery', { + images: validImages.map( ( { id, url, alt, caption } ) => ( { id, url, alt, caption } ) ), + ids: validImages.map( ( { id } ) => id ), + align, + } ); }, }, { @@ -174,11 +178,11 @@ export const settings = { { type: 'block', blocks: [ 'core/image' ], - transform: ( { images } ) => { + transform: ( { images, align } ) => { if ( images.length > 0 ) { - return images.map( ( { id, url, alt, caption } ) => createBlock( 'core/image', { id, url, alt, caption } ) ); + return images.map( ( { id, url, alt, caption } ) => createBlock( 'core/image', { id, url, alt, caption, align } ) ); } - return createBlock( 'core/image' ); + return createBlock( 'core/image', { align } ); }, }, ], From 481a56d102e92e66d8602cc477863b349a092317 Mon Sep 17 00:00:00 2001 From: Nick Cernis <nick@cern.is> Date: Thu, 20 Dec 2018 09:41:36 +0100 Subject: [PATCH 049/691] =?UTF-8?q?Fix=20=E2=80=9Calign=20center=E2=80=9D?= =?UTF-8?q?=20button=20to=20add=20data-align=3D"center"=20in=20the=20edito?= =?UTF-8?q?r=20(#12566)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolves #12306, where clicking the center button on the Latest Posts block previously had no effect on the block markup in the admin area. --- packages/block-library/src/latest-posts/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/latest-posts/index.js b/packages/block-library/src/latest-posts/index.js index fac9809772056..e1dd91ea70af8 100644 --- a/packages/block-library/src/latest-posts/index.js +++ b/packages/block-library/src/latest-posts/index.js @@ -28,7 +28,7 @@ export const settings = { getEditWrapperProps( attributes ) { const { align } = attributes; - if ( 'left' === align || 'right' === align || 'wide' === align || 'full' === align ) { + if ( [ 'left', 'center', 'right', 'wide', 'full' ].includes( align ) ) { return { 'data-align': align }; } }, From 0222ecf25f4c80d098860709d40111fca58f667a Mon Sep 17 00:00:00 2001 From: John <johng75@gmail.com> Date: Thu, 20 Dec 2018 08:42:12 +0000 Subject: [PATCH 050/691] Paragraph: clear dropcap height so hover area shown correctly (#12177) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Paragraph: clear dropcap height so hover area shown correctly Because the dropcap letter is floated, the height isn’t taken into account, causing the block’s hover area to be incorrect. Clear the float so the height is applied. * Restrict dropcap clearer to only focussed block --- packages/block-library/src/paragraph/style.scss | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/block-library/src/paragraph/style.scss b/packages/block-library/src/paragraph/style.scss index 325cc438b4d62..becb47973704d 100644 --- a/packages/block-library/src/paragraph/style.scss +++ b/packages/block-library/src/paragraph/style.scss @@ -28,6 +28,13 @@ p { text-transform: uppercase; font-style: normal; } + + &.has-drop-cap:not(:focus)::after { + content: ""; + display: table; + clear: both; + padding-top: $block-padding; + } } p.has-background { From 4d36d81c5ac702bb1c409c735219d83300a9a0ac Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Thu, 20 Dec 2018 03:42:52 -0500 Subject: [PATCH 051/691] Data: Optimize partial application of runSelector (#12849) --- packages/data/CHANGELOG.md | 6 ++++++ packages/data/src/namespace-store.js | 19 ++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/packages/data/CHANGELOG.md b/packages/data/CHANGELOG.md index 8b90a5d7c526a..7ebc677c61e07 100644 --- a/packages/data/CHANGELOG.md +++ b/packages/data/CHANGELOG.md @@ -1,3 +1,9 @@ +## 4.2.0 (Unreleased) + +### Enhancements + +- Optimized performance of selector execution (~511% improvement) + ## 4.1.0 (2018-12-12) ### New Feature diff --git a/packages/data/src/namespace-store.js b/packages/data/src/namespace-store.js index 2b8c283c3a478..dc0c0075fd620 100644 --- a/packages/data/src/namespace-store.js +++ b/packages/data/src/namespace-store.js @@ -103,7 +103,24 @@ function createReduxStore( reducer, key, registry ) { * @return {Object} Selectors mapped to the redux store provided. */ function mapSelectors( selectors, store ) { - const createStateSelector = ( selector ) => ( ...args ) => selector( store.getState(), ...args ); + const createStateSelector = ( selector ) => function runSelector() { + // This function is an optimized implementation of: + // + // selector( store.getState(), ...arguments ) + // + // Where the above would incur an `Array#concat` in its application, + // the logic here instead efficiently constructs an arguments array via + // direct assignment. + const argsLength = arguments.length; + const args = new Array( argsLength + 1 ); + args[ 0 ] = store.getState(); + for ( let i = 0; i < argsLength; i++ ) { + args[ i + 1 ] = arguments[ i ]; + } + + return selector( ...args ); + }; + return mapValues( selectors, createStateSelector ); } From ff64aedf610f57536f7092b12923278ff9e2fb26 Mon Sep 17 00:00:00 2001 From: William Earnhardt <wearnhardt@gmail.com> Date: Thu, 20 Dec 2018 04:20:18 -0500 Subject: [PATCH 052/691] Expand PHPCS scanning to all PHP in Gutenberg (#13016) * Update PHPCS config to include most files * Add file comment to post-content.php * Fix PHPCS issues in e2e tests * Fix PHPCS issues in packages --- .../parser.php | 154 +++++++++++------- .../test/test-parser.php | 10 +- .../test/test-parser.php | 10 +- phpcs.xml.dist | 28 +++- post-content.php | 8 + .../disable-login-autofocus.php | 1 - test/e2e/test-plugins/align-hook.php | 3 +- test/e2e/test-plugins/block-icons.php | 1 + .../container-without-paragraph.php | 1 + .../e2e/test-plugins/default-post-content.php | 17 +- .../test-plugins/deprecated-node-matcher.php | 3 +- test/e2e/test-plugins/hooks-api.php | 3 +- .../test-plugins/inner-blocks-templates.php | 3 +- test/e2e/test-plugins/meta-box.php | 12 +- test/e2e/test-plugins/post-formats.php | 5 +- test/e2e/test-plugins/templates.php | 6 +- test/e2e/test-plugins/wp-editor-metabox.php | 49 ++++-- 17 files changed, 220 insertions(+), 94 deletions(-) diff --git a/packages/block-serialization-default-parser/parser.php b/packages/block-serialization-default-parser/parser.php index 0d5887381a1b8..8b565f1a28b9b 100644 --- a/packages/block-serialization-default-parser/parser.php +++ b/packages/block-serialization-default-parser/parser.php @@ -1,4 +1,9 @@ <?php +/** + * Block Serialization Parser + * + * @package WordPress + */ /** * Class WP_Block_Parser_Block @@ -62,6 +67,19 @@ class WP_Block_Parser_Block { */ public $innerContent; + /** + * Constructor. + * + * Will populate object properties from the provided arguments. + * + * @since 3.8.0 + * + * @param string $name Name of block. + * @param array $attrs Optional set of attributes from block comment delimiters. + * @param array $innerBlocks List of inner blocks (of this same class). + * @param string $innerHTML Resultant HTML from inside block comment delimiters after removing inner blocks. + * @param array $innerContent List of string fragments and null markers where inner blocks were found. + */ function __construct( $name, $attrs, $innerBlocks, $innerHTML, $innerContent ) { $this->blockName = $name; $this->attrs = $attrs; @@ -121,6 +139,19 @@ class WP_Block_Parser_Frame { */ public $leading_html_start; + /** + * Constructor + * + * Will populate object properties from the provided arguments. + * + * @since 3.8.0 + * + * @param WP_Block_Parser_Block $block Full or partial block. + * @param int $token_start Byte offset into document for start of parse token. + * @param int $token_length Byte length of entire parse token string. + * @param int $prev_offset Byte offset into document for after parse token ends. + * @param int $leading_html_start Byte offset into document where leading HTML before token starts. + */ function __construct( $block, $token_start, $token_length, $prev_offset = null, $leading_html_start = null ) { $this->block = $block; $this->token_start = $token_start; @@ -190,7 +221,7 @@ class WP_Block_Parser { * * @since 3.8.0 * - * @param string $document + * @param string $document Input document being parsed. * @return WP_Block_Parser_Block[] */ function parse( $document ) { @@ -201,7 +232,7 @@ function parse( $document ) { $this->empty_attrs = json_decode( '{}', true ); do { - // twiddle our thumbs + // twiddle our thumbs. } while ( $this->proceed() ); return $this->output; @@ -226,12 +257,12 @@ function proceed() { list( $token_type, $block_name, $attrs, $start_offset, $token_length ) = $next_token; $stack_depth = count( $this->stack ); - // we may have some HTML soup before the next block + // we may have some HTML soup before the next block. $leading_html_start = $start_offset > $this->offset ? $this->offset : null; switch ( $token_type ) { case 'no-more-tokens': - // if not in a block then flush output + // if not in a block then flush output. if ( 0 === $stack_depth ) { $this->add_freeform(); return false; @@ -246,7 +277,7 @@ function proceed() { * - assume an implicit closer (easiest when not nesting) */ - // for the easy case we'll assume an implicit closer + // for the easy case we'll assume an implicit closer. if ( 1 === $stack_depth ) { $this->add_block_from_stack(); return false; @@ -269,19 +300,21 @@ function proceed() { */ if ( 0 === $stack_depth ) { if ( isset( $leading_html_start ) ) { - $this->output[] = (array) self::freeform( substr( - $this->document, - $leading_html_start, - $start_offset - $leading_html_start - ) ); + $this->output[] = (array) self::freeform( + substr( + $this->document, + $leading_html_start, + $start_offset - $leading_html_start + ) + ); } $this->output[] = (array) new WP_Block_Parser_Block( $block_name, $attrs, array(), '', array() ); - $this->offset = $start_offset + $token_length; + $this->offset = $start_offset + $token_length; return true; } - // otherwise we found an inner block + // otherwise we found an inner block. $this->add_inner_block( new WP_Block_Parser_Block( $block_name, $attrs, array(), '', array() ), $start_offset, @@ -291,14 +324,17 @@ function proceed() { return true; case 'block-opener': - // track all newly-opened blocks on the stack - array_push( $this->stack, new WP_Block_Parser_Frame( - new WP_Block_Parser_Block( $block_name, $attrs, array(), '', array() ), - $start_offset, - $token_length, - $start_offset + $token_length, - $leading_html_start - ) ); + // track all newly-opened blocks on the stack. + array_push( + $this->stack, + new WP_Block_Parser_Frame( + new WP_Block_Parser_Block( $block_name, $attrs, array(), '', array() ), + $start_offset, + $token_length, + $start_offset + $token_length, + $leading_html_start + ) + ); $this->offset = $start_offset + $token_length; return true; @@ -318,7 +354,7 @@ function proceed() { return false; } - // if we're not nesting then this is easy - close the block + // if we're not nesting then this is easy - close the block. if ( 1 === $stack_depth ) { $this->add_block_from_stack( $start_offset ); $this->offset = $start_offset + $token_length; @@ -329,11 +365,11 @@ function proceed() { * otherwise we're nested and we have to close out the current * block and add it as a new innerBlock to the parent */ - $stack_top = array_pop( $this->stack ); - $html = substr( $this->document, $stack_top->prev_offset, $start_offset - $stack_top->prev_offset ); - $stack_top->block->innerHTML .= $html; + $stack_top = array_pop( $this->stack ); + $html = substr( $this->document, $stack_top->prev_offset, $start_offset - $stack_top->prev_offset ); + $stack_top->block->innerHTML .= $html; $stack_top->block->innerContent[] = $html; - $stack_top->prev_offset = $start_offset + $token_length; + $stack_top->prev_offset = $start_offset + $token_length; $this->add_inner_block( $stack_top->block, @@ -345,7 +381,7 @@ function proceed() { return true; default: - // This is an error + // This is an error. $this->add_freeform(); return false; } @@ -381,32 +417,32 @@ function next_token() { $this->offset ); - // if we get here we probably have catastrophic backtracking or out-of-memory in the PCRE + // if we get here we probably have catastrophic backtracking or out-of-memory in the PCRE. if ( false === $has_match ) { return array( 'no-more-tokens', null, null, null, null ); } - // we have no more tokens + // we have no more tokens. if ( 0 === $has_match ) { return array( 'no-more-tokens', null, null, null, null ); } - list( $match, $started_at ) = $matches[ 0 ]; + list( $match, $started_at ) = $matches[0]; $length = strlen( $match ); - $is_closer = isset( $matches[ 'closer' ] ) && -1 !== $matches[ 'closer' ][ 1 ]; - $is_void = isset( $matches[ 'void' ] ) && -1 !== $matches[ 'void' ][ 1 ]; - $namespace = $matches[ 'namespace' ]; - $namespace = ( isset( $namespace ) && -1 !== $namespace[ 1 ] ) ? $namespace[ 0 ] : 'core/'; - $name = $namespace . $matches[ 'name' ][ 0 ]; - $has_attrs = isset( $matches[ 'attrs' ] ) && -1 !== $matches[ 'attrs' ][ 1 ]; + $is_closer = isset( $matches['closer'] ) && -1 !== $matches['closer'][1]; + $is_void = isset( $matches['void'] ) && -1 !== $matches['void'][1]; + $namespace = $matches['namespace']; + $namespace = ( isset( $namespace ) && -1 !== $namespace[1] ) ? $namespace[0] : 'core/'; + $name = $namespace . $matches['name'][0]; + $has_attrs = isset( $matches['attrs'] ) && -1 !== $matches['attrs'][1]; /* * Fun fact! It's not trivial in PHP to create "an empty associative array" since all arrays * are associative arrays. If we use `array()` we get a JSON `[]` */ $attrs = $has_attrs - ? json_decode( $matches[ 'attrs' ][ 0 ], /* as-associative */ true ) + ? json_decode( $matches['attrs'][0], /* as-associative */ true ) : $this->empty_attrs; /* @@ -414,7 +450,7 @@ function next_token() { * This is an error */ if ( $is_closer && ( $is_void || $has_attrs ) ) { - // we can ignore them since they don't hurt anything + // we can ignore them since they don't hurt anything. } if ( $is_void ) { @@ -434,8 +470,8 @@ function next_token() { * @internal * @since 3.9.0 * - * @param string $innerHTML HTML content of block - * @return WP_Block_Parser_Block freeform block object + * @param string $innerHTML HTML content of block. + * @return WP_Block_Parser_Block freeform block object. */ function freeform( $innerHTML ) { return new WP_Block_Parser_Block( null, $this->empty_attrs, array(), $innerHTML, array( $innerHTML ) ); @@ -443,11 +479,11 @@ function freeform( $innerHTML ) { /** * Pushes a length of text from the input document - * to the output list as a freeform block + * to the output list as a freeform block. * * @internal * @since 3.8.0 - * @param null $length how many bytes of document text to output + * @param null $length how many bytes of document text to output. */ function add_freeform( $length = null ) { $length = $length ? $length : strlen( $this->document ) - $this->offset; @@ -461,35 +497,35 @@ function add_freeform( $length = null ) { /** * Given a block structure from memory pushes - * a new block to the output list + * a new block to the output list. * * @internal * @since 3.8.0 - * @param WP_Block_Parser_Block $block the block to add to the output - * @param int $token_start byte offset into the document where the first token for the block starts - * @param int $token_length byte length of entire block from start of opening token to end of closing token - * @param int|null $last_offset last byte offset into document if continuing form earlier output + * @param WP_Block_Parser_Block $block The block to add to the output. + * @param int $token_start Byte offset into the document where the first token for the block starts. + * @param int $token_length Byte length of entire block from start of opening token to end of closing token. + * @param int|null $last_offset Last byte offset into document if continuing form earlier output. */ function add_inner_block( WP_Block_Parser_Block $block, $token_start, $token_length, $last_offset = null ) { - $parent = $this->stack[ count( $this->stack ) - 1 ]; + $parent = $this->stack[ count( $this->stack ) - 1 ]; $parent->block->innerBlocks[] = (array) $block; - $html = substr( $this->document, $parent->prev_offset, $token_start - $parent->prev_offset ); + $html = substr( $this->document, $parent->prev_offset, $token_start - $parent->prev_offset ); if ( ! empty( $html ) ) { - $parent->block->innerHTML .= $html; + $parent->block->innerHTML .= $html; $parent->block->innerContent[] = $html; } $parent->block->innerContent[] = null; - $parent->prev_offset = $last_offset ? $last_offset : $token_start + $token_length; + $parent->prev_offset = $last_offset ? $last_offset : $token_start + $token_length; } /** - * Pushes the top block from the parsing stack to the output list + * Pushes the top block from the parsing stack to the output list. * * @internal * @since 3.8.0 - * @param int|null $end_offset byte offset into document for where we should stop sending text output as HTML + * @param int|null $end_offset byte offset into document for where we should stop sending text output as HTML. */ function add_block_from_stack( $end_offset = null ) { $stack_top = array_pop( $this->stack ); @@ -500,16 +536,18 @@ function add_block_from_stack( $end_offset = null ) { : substr( $this->document, $prev_offset ); if ( ! empty( $html ) ) { - $stack_top->block->innerHTML .= $html; + $stack_top->block->innerHTML .= $html; $stack_top->block->innerContent[] = $html; } if ( isset( $stack_top->leading_html_start ) ) { - $this->output[] = (array) self::freeform( substr( - $this->document, - $stack_top->leading_html_start, - $stack_top->token_start - $stack_top->leading_html_start - ) ); + $this->output[] = (array) self::freeform( + substr( + $this->document, + $stack_top->leading_html_start, + $stack_top->token_start - $stack_top->leading_html_start + ) + ); } $this->output[] = (array) $stack_top->block; diff --git a/packages/block-serialization-default-parser/test/test-parser.php b/packages/block-serialization-default-parser/test/test-parser.php index a1ab00b6d7bf4..1391c0edd1684 100644 --- a/packages/block-serialization-default-parser/test/test-parser.php +++ b/packages/block-serialization-default-parser/test/test-parser.php @@ -1,6 +1,14 @@ <?php +/** + * PHP Test Helper + * + * Facilitates running PHP parser against same tests as the JS parser implementation. + * + * @package gutenberg + */ -require_once __DIR__ . '/../parser.php'; +// Include the default parser. +require_once dirname( __FILE__ ) . '/../parser.php'; $parser = new WP_Block_Parser(); diff --git a/packages/block-serialization-spec-parser/test/test-parser.php b/packages/block-serialization-spec-parser/test/test-parser.php index 2300facd5857c..28c523951c343 100644 --- a/packages/block-serialization-spec-parser/test/test-parser.php +++ b/packages/block-serialization-spec-parser/test/test-parser.php @@ -1,6 +1,14 @@ <?php +/** + * PHP Test Helper + * + * Facilitates running PHP parser against same tests as the JS parser implementation. + * + * @package gutenberg + */ -require_once __DIR__ . '/../../../lib/parser.php'; +// Include the generated parser. +require_once dirname( __FILE__ ) . '/../../../lib/parser.php'; $parser = new Gutenberg_PEG_Parser(); diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 4d08317519112..9e0d0f2bd2a4b 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -25,12 +25,15 @@ <arg value="ps"/> <arg name="extensions" value="php"/> - <file>./bin</file> - <file>./packages/block-library/src</file> - <file>./lib</file> + <file>.</file> + + <!-- Exclude 3rd party libraries --> + <exclude-pattern>./node_modules</exclude-pattern> + <exclude-pattern>./vendor</exclude-pattern> + + <!-- Exclude generated files --> + <exclude-pattern>./languages/gutenberg-translations.php</exclude-pattern> <exclude-pattern>./lib/parser.php</exclude-pattern> - <file>./phpunit</file> - <file>gutenberg.php</file> <rule ref="PHPCompatibility.PHP.NewKeywords.t_namespaceFound"> <exclude-pattern>lib/class-wp-rest-block-renderer-controller.php</exclude-pattern> @@ -66,4 +69,19 @@ <rule ref="Squiz.Commenting.FunctionCommentThrowTag.Missing"> <exclude-pattern>phpunit/*</exclude-pattern> </rule> + + <!-- Ignore snake case error in parser --> + <rule ref="WordPress.NamingConventions.ValidVariableName.NotSnakeCase"> + <exclude-pattern>./packages/block-serialization-default-parser/parser.php</exclude-pattern> + </rule> + <rule ref="WordPress.NamingConventions.ValidVariableName.NotSnakeCaseMemberVar"> + <exclude-pattern>./packages/block-serialization-default-parser/parser.php</exclude-pattern> + </rule> + <rule ref="WordPress.NamingConventions.ValidVariableName.MemberNotSnakeCase"> + <exclude-pattern>./packages/block-serialization-default-parser/parser.php</exclude-pattern> + </rule> + <!-- Ignore filename error since it requires WP core build process change --> + <rule ref="WordPress.Files.FileName.InvalidClassFileName"> + <exclude-pattern>./packages/block-serialization-default-parser/parser.php</exclude-pattern> + </rule> </ruleset> diff --git a/post-content.php b/post-content.php index f34262d1eed4c..fd59ab75ad1b8 100644 --- a/post-content.php +++ b/post-content.php @@ -1,3 +1,11 @@ +<?php +/** + * Default content of the demo page + * + * @package gutenberg + */ + +?> <!-- wp:cover {"url":"https://cldup.com/Fz-ASbo2s3.jpg","align":"wide"} --> <div class="wp-block-cover has-background-dim alignwide" style="background-image:url(https://cldup.com/Fz-ASbo2s3.jpg)"><p class="wp-block-cover-text"><?php _e( 'Of Mountains &amp; Printing Presses', 'gutenberg' ); ?></p></div> <!-- /wp:cover --> diff --git a/test/e2e/test-mu-plugins/disable-login-autofocus.php b/test/e2e/test-mu-plugins/disable-login-autofocus.php index 07b8c826e2337..d06ead7e6b276 100644 --- a/test/e2e/test-mu-plugins/disable-login-autofocus.php +++ b/test/e2e/test-mu-plugins/disable-login-autofocus.php @@ -1,5 +1,4 @@ <?php - /** * Plugin Name: Gutenberg Test Plugin, Disable Login Autofocus * Plugin URI: https://github.com/WordPress/gutenberg diff --git a/test/e2e/test-plugins/align-hook.php b/test/e2e/test-plugins/align-hook.php index 0103a759cd700..8ecc005651e11 100644 --- a/test/e2e/test-plugins/align-hook.php +++ b/test/e2e/test-plugins/align-hook.php @@ -6,6 +6,7 @@ * * @package gutenberg-test-align-hook */ + wp_enqueue_script( 'gutenberg-test-align-hook', plugins_url( 'align-hook/index.js', __FILE__ ), @@ -13,7 +14,7 @@ 'wp-blocks', 'wp-element', 'wp-editor', - 'wp-i18n' + 'wp-i18n', ), filemtime( plugin_dir_path( __FILE__ ) . 'align-hook/index.js' ), true diff --git a/test/e2e/test-plugins/block-icons.php b/test/e2e/test-plugins/block-icons.php index 0110f2cb68719..eb97b52cf9eb0 100644 --- a/test/e2e/test-plugins/block-icons.php +++ b/test/e2e/test-plugins/block-icons.php @@ -6,6 +6,7 @@ * * @package gutenberg-test-block-icons */ + wp_enqueue_script( 'gutenberg-test-block-icons', plugins_url( 'block-icons/index.js', __FILE__ ), diff --git a/test/e2e/test-plugins/container-without-paragraph.php b/test/e2e/test-plugins/container-without-paragraph.php index 2d7a20c002605..e9876f4c95b88 100644 --- a/test/e2e/test-plugins/container-without-paragraph.php +++ b/test/e2e/test-plugins/container-without-paragraph.php @@ -6,6 +6,7 @@ * * @package gutenberg-test-container-without-paragraph */ + wp_enqueue_script( 'gutenberg-test-container-without-paragraph', plugins_url( 'container-without-paragraph/index.js', __FILE__ ), diff --git a/test/e2e/test-plugins/default-post-content.php b/test/e2e/test-plugins/default-post-content.php index 85bd563565b8b..bca4dd1627ae9 100644 --- a/test/e2e/test-plugins/default-post-content.php +++ b/test/e2e/test-plugins/default-post-content.php @@ -10,20 +10,23 @@ /** * Change the default title. */ -add_filter( 'default_title', function() { +function gutenberg_test_default_title() { return 'My default title'; -} ); +} +add_filter( 'default_title', 'gutenberg_test_default_title' ); /** - * Change teh default post content. + * Change the default post content. */ -add_filter( 'default_content', function() { +function gutenberg_test_default_content() { return 'My default content'; -} ); +} +add_filter( 'default_content', 'gutenberg_test_default_content' ); /** * Change the default excerpt. */ -add_filter( 'default_excerpt', function() { +function gutenberg_test_default_excerpt() { return 'My default excerpt'; -} ); +} +add_filter( 'default_excerpt', 'gutenberg_test_default_excerpt' ); diff --git a/test/e2e/test-plugins/deprecated-node-matcher.php b/test/e2e/test-plugins/deprecated-node-matcher.php index 9892b688cff5e..bd72e4aa15157 100644 --- a/test/e2e/test-plugins/deprecated-node-matcher.php +++ b/test/e2e/test-plugins/deprecated-node-matcher.php @@ -6,6 +6,7 @@ * * @package gutenberg-test-deprecated-node-matcher */ + wp_enqueue_script( 'gutenberg-test-deprecated-node-matcher', plugins_url( 'deprecated-node-matcher/index.js', __FILE__ ), @@ -13,7 +14,7 @@ 'lodash', 'wp-blocks', 'wp-element', - 'wp-editor' + 'wp-editor', ), filemtime( plugin_dir_path( __FILE__ ) . 'deprecated-node-matcher/index.js' ), true diff --git a/test/e2e/test-plugins/hooks-api.php b/test/e2e/test-plugins/hooks-api.php index 1520a29771546..38f44860716dd 100644 --- a/test/e2e/test-plugins/hooks-api.php +++ b/test/e2e/test-plugins/hooks-api.php @@ -6,6 +6,7 @@ * * @package gutenberg-test-hooks-api */ + wp_enqueue_script( 'gutenberg-test-hooks-api', plugins_url( 'hooks-api/index.js', __FILE__ ), @@ -15,7 +16,7 @@ 'wp-element', 'wp-editor', 'wp-hooks', - 'wp-i18n' + 'wp-i18n', ), filemtime( plugin_dir_path( __FILE__ ) . 'hooks-api/index.js' ), true diff --git a/test/e2e/test-plugins/inner-blocks-templates.php b/test/e2e/test-plugins/inner-blocks-templates.php index cec9dae6b9278..9454cfc7cb40a 100644 --- a/test/e2e/test-plugins/inner-blocks-templates.php +++ b/test/e2e/test-plugins/inner-blocks-templates.php @@ -6,6 +6,7 @@ * * @package gutenberg-test-inner-blocks-templates */ + wp_enqueue_script( 'gutenberg-test-inner-blocks-templates', plugins_url( 'inner-blocks-templates/index.js', __FILE__ ), @@ -15,7 +16,7 @@ 'wp-element', 'wp-editor', 'wp-hooks', - 'wp-i18n' + 'wp-i18n', ), filemtime( plugin_dir_path( __FILE__ ) . 'inner-blocks-templates/index.js' ), true diff --git a/test/e2e/test-plugins/meta-box.php b/test/e2e/test-plugins/meta-box.php index bc16f0bb0c496..0e3d921396934 100644 --- a/test/e2e/test-plugins/meta-box.php +++ b/test/e2e/test-plugins/meta-box.php @@ -1,5 +1,4 @@ <?php - /** * Plugin Name: Gutenberg Test Plugin, Meta Box * Plugin URI: https://github.com/WordPress/gutenberg @@ -8,10 +7,16 @@ * @package gutenberg-test-meta-box */ +/** + * Prints string for meta box + */ function gutenberg_test_meta_box_render_meta_box() { echo 'Hello World'; } +/** + * Add a test meta box + */ function gutenberg_test_meta_box_add_meta_box() { add_meta_box( 'gutenberg-test-meta-box', @@ -24,7 +29,9 @@ function gutenberg_test_meta_box_add_meta_box() { } add_action( 'add_meta_boxes', 'gutenberg_test_meta_box_add_meta_box' ); - +/** + * Print excerpt in <meta> tag in wp_head + */ function gutenberg_test_meta_box_render_head() { // Emulates what plugins like Yoast do with meta data on the front end. // Tests that our excerpt processing does not interfere with dynamic blocks. @@ -33,5 +40,4 @@ function gutenberg_test_meta_box_render_head() { <meta property="gutenberg:hello" content="<?php echo esc_attr( $excerpt ); ?>" /> <?php } - add_action( 'wp_head', 'gutenberg_test_meta_box_render_head' ); diff --git a/test/e2e/test-plugins/post-formats.php b/test/e2e/test-plugins/post-formats.php index b0fdec68947fc..8ceb56ec67554 100644 --- a/test/e2e/test-plugins/post-formats.php +++ b/test/e2e/test-plugins/post-formats.php @@ -10,6 +10,9 @@ add_theme_support( 'post-formats', array( 'image', 'gallery' ) ); add_action( 'init', 'gutenberg_test_plugin_post_formats_add_post_support', 11 ); +/** + * Add post-formats support to pages + */ function gutenberg_test_plugin_post_formats_add_post_support() { - add_post_type_support( 'page', 'post-formats' ); + add_post_type_support( 'page', 'post-formats' ); } diff --git a/test/e2e/test-plugins/templates.php b/test/e2e/test-plugins/templates.php index dd49633b91456..b4d399905c68e 100644 --- a/test/e2e/test-plugins/templates.php +++ b/test/e2e/test-plugins/templates.php @@ -12,10 +12,10 @@ */ function gutenberg_test_templates_register_book_type() { $args = array( - 'public' => true, - 'label' => 'Books', + 'public' => true, + 'label' => 'Books', 'show_in_rest' => true, - 'template' => array( + 'template' => array( array( 'core/image' ), array( 'core/paragraph', diff --git a/test/e2e/test-plugins/wp-editor-metabox.php b/test/e2e/test-plugins/wp-editor-metabox.php index 088fd2ee6e87a..7559ad57764bc 100644 --- a/test/e2e/test-plugins/wp-editor-metabox.php +++ b/test/e2e/test-plugins/wp-editor-metabox.php @@ -7,21 +7,50 @@ * @package gutenberg-test-wp-editor-metabox */ -add_action( 'add_meta_boxes', function(){ - add_meta_box( 'test_tinymce', 'Test TinyMCE', function( $post ){ - $field_value = get_post_meta( $post->ID, 'test_tinymce', true ); - wp_editor( $field_value, 'test_tinymce_id', array( +add_action( 'add_meta_boxes', 'gutenberg_test_add_tinymce_meta_box' ); +/** + * Adds a TinyMCE meta box for testing + */ +function gutenberg_test_add_tinymce_meta_box() { + add_meta_box( + 'test_tinymce', + 'Test TinyMCE', + 'gutenberg_test_render_tinymce_meta_box', + null, + 'advanced', + 'high' + ); +} + +/** + * Render the TinyMCE meta box + * + * @param WP_Post $post The current post object. + */ +function gutenberg_test_render_tinymce_meta_box( $post ) { + $field_value = get_post_meta( $post->ID, 'test_tinymce', true ); + wp_editor( + $field_value, + 'test_tinymce_id', + array( 'wpautop' => true, 'media_buttons' => false, 'textarea_name' => 'test_tinymce', 'textarea_rows' => 10, - 'teeny' => true - ) ); - }, null, 'advanced', 'high' ); -}); -add_action( 'save_post', function( $post_id ){ + 'teeny' => true, + ) + ); +} + +/** + * Save the TinyMCE meta box + * + * @param int $post_id The ID of the current post. + */ +function gutenberg_test_save_tinymce_meta_box( $post_id ) { if ( ! isset( $_POST['test_tinymce'] ) ) { return; } update_post_meta( $post_id, 'test_tinymce', $_POST['test_tinymce'] ); -}); +} +add_action( 'save_post', 'gutenberg_test_save_tinymce_meta_box' ); From 6cc58e82806a36f15bcae63aa2cef6aa66a8601f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20Van=C2=A0Dorpe?= <iseulde@automattic.com> Date: Thu, 20 Dec 2018 11:51:33 +0100 Subject: [PATCH 053/691] Fix converting caption shortcode with link (#12315) * Fix converting caption shortcode with link * Parse HTML to DOM instead of using RegExp * Source anchor info only from first element --- packages/block-library/src/image/index.js | 44 +++++++++++++------ .../blocks-raw-handling.spec.js.snap | 20 ++++++++- test/integration/blocks-raw-handling.spec.js | 25 ++++++++--- .../fixtures/caption-shortcode-out.html | 3 -- .../shortcode-caption-with-caption-link.html | 1 + .../fixtures/shortcode-caption-with-link.html | 1 + ...ortcode-in.html => shortcode-caption.html} | 0 7 files changed, 71 insertions(+), 23 deletions(-) delete mode 100644 test/integration/fixtures/caption-shortcode-out.html create mode 100644 test/integration/fixtures/shortcode-caption-with-caption-link.html create mode 100644 test/integration/fixtures/shortcode-caption-with-link.html rename test/integration/fixtures/{caption-shortcode-in.html => shortcode-caption.html} (100%) diff --git a/packages/block-library/src/image/index.js b/packages/block-library/src/image/index.js index 0a5ca7618c4aa..65bd1df8f1dc2 100644 --- a/packages/block-library/src/image/index.js +++ b/packages/block-library/src/image/index.js @@ -111,6 +111,21 @@ const schema = { }, }; +function getFirstAnchorAttributeFormHTML( html, attributeName ) { + const { body } = document.implementation.createHTMLDocument( '' ); + + body.innerHTML = html; + + const { firstElementChild } = body; + + if ( + firstElementChild && + firstElementChild.nodeName === 'A' + ) { + return firstElementChild.getAttribute( attributeName ) || undefined; + } +} + export const settings = { title: __( 'Image' ), @@ -185,27 +200,28 @@ export const settings = { }, caption: { shortcode: ( attributes, { shortcode } ) => { - const { content } = shortcode; - return content.replace( /\s*<img[^>]*>\s/, '' ); + const { body } = document.implementation.createHTMLDocument( '' ); + + body.innerHTML = shortcode.content; + body.removeChild( body.firstElementChild ); + + return body.innerHTML.trim(); }, }, href: { - type: 'string', - source: 'attribute', - attribute: 'href', - selector: 'a', + shortcode: ( attributes, { shortcode } ) => { + return getFirstAnchorAttributeFormHTML( shortcode.content, 'href' ); + }, }, rel: { - type: 'string', - source: 'attribute', - attribute: 'rel', - selector: 'a', + shortcode: ( attributes, { shortcode } ) => { + return getFirstAnchorAttributeFormHTML( shortcode.content, 'rel' ); + }, }, linkClass: { - type: 'string', - source: 'attribute', - attribute: 'class', - selector: 'a', + shortcode: ( attributes, { shortcode } ) => { + return getFirstAnchorAttributeFormHTML( shortcode.content, 'class' ); + }, }, id: { type: 'number', diff --git a/test/integration/__snapshots__/blocks-raw-handling.spec.js.snap b/test/integration/__snapshots__/blocks-raw-handling.spec.js.snap index 320b201a836e3..9d8761f906216 100644 --- a/test/integration/__snapshots__/blocks-raw-handling.spec.js.snap +++ b/test/integration/__snapshots__/blocks-raw-handling.spec.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Blocks raw handling rawHandler should convert HTML post to blocks with minimal content changes 1`] = ` +exports[`rawHandler should convert HTML post to blocks with minimal content changes 1`] = ` "<!-- wp:heading --> <h2>Howdy</h2> <!-- /wp:heading --> @@ -54,3 +54,21 @@ exports[`Blocks raw handling rawHandler should convert HTML post to blocks with <blockquote><h1>Heading</h1><p>Text.</p></blockquote> <!-- /wp:html -->" `; + +exports[`rawHandler should convert a caption shortcode 1`] = ` +"<!-- wp:image {\\"id\\":122,\\"align\\":\\"none\\",\\"className\\":\\"size-medium wp-image-122\\"} --> +<figure class=\\"wp-block-image alignnone size-medium wp-image-122\\"><img src=\\"image.png\\" alt=\\"\\" class=\\"wp-image-122\\"/><figcaption>test</figcaption></figure> +<!-- /wp:image -->" +`; + +exports[`rawHandler should convert a caption shortcode with caption 1`] = ` +"<!-- wp:image {\\"id\\":122,\\"align\\":\\"none\\",\\"className\\":\\"size-medium wp-image-122\\"} --> +<figure class=\\"wp-block-image alignnone size-medium wp-image-122\\"><img src=\\"image.png\\" alt=\\"\\" class=\\"wp-image-122\\"/><figcaption><a href=\\"https://w.org\\">test</a></figcaption></figure> +<!-- /wp:image -->" +`; + +exports[`rawHandler should convert a caption shortcode with link 1`] = ` +"<!-- wp:image {\\"id\\":754,\\"align\\":\\"none\\"} --> +<figure class=\\"wp-block-image alignnone\\"><a href=\\"http://build.wordpress-develop.test/wp-content/uploads/2011/07/100_5478.jpg\\"><img src=\\"http://build.wordpress-develop.test/wp-content/uploads/2011/07/100_5478.jpg?w=604\\" alt=\\"Bell on Wharf\\" class=\\"wp-image-754\\"/></a><figcaption>Bell on wharf in San Francisco</figcaption></figure> +<!-- /wp:image -->" +`; diff --git a/test/integration/blocks-raw-handling.spec.js b/test/integration/blocks-raw-handling.spec.js index b0997d3b757d9..3f0e951026537 100644 --- a/test/integration/blocks-raw-handling.spec.js +++ b/test/integration/blocks-raw-handling.spec.js @@ -155,11 +155,26 @@ describe( 'Blocks raw handling', () => { } ); } ); } ); +} ); - describe( 'rawHandler', () => { - it( 'should convert HTML post to blocks with minimal content changes', () => { - const HTML = readFile( path.join( __dirname, 'fixtures/wordpress-convert.html' ) ); - expect( serialize( rawHandler( { HTML } ) ) ).toMatchSnapshot(); - } ); +describe( 'rawHandler', () => { + it( 'should convert HTML post to blocks with minimal content changes', () => { + const HTML = readFile( path.join( __dirname, 'fixtures/wordpress-convert.html' ) ); + expect( serialize( rawHandler( { HTML } ) ) ).toMatchSnapshot(); + } ); + + it( 'should convert a caption shortcode', () => { + const HTML = readFile( path.join( __dirname, 'fixtures/shortcode-caption.html' ) ); + expect( serialize( rawHandler( { HTML } ) ) ).toMatchSnapshot(); + } ); + + it( 'should convert a caption shortcode with link', () => { + const HTML = readFile( path.join( __dirname, 'fixtures/shortcode-caption-with-link.html' ) ); + expect( serialize( rawHandler( { HTML } ) ) ).toMatchSnapshot(); + } ); + + it( 'should convert a caption shortcode with caption', () => { + const HTML = readFile( path.join( __dirname, 'fixtures/shortcode-caption-with-caption-link.html' ) ); + expect( serialize( rawHandler( { HTML } ) ) ).toMatchSnapshot(); } ); } ); diff --git a/test/integration/fixtures/caption-shortcode-out.html b/test/integration/fixtures/caption-shortcode-out.html deleted file mode 100644 index 2027b65d2feae..0000000000000 --- a/test/integration/fixtures/caption-shortcode-out.html +++ /dev/null @@ -1,3 +0,0 @@ -<!-- wp:image {"id":122,"align":"none","className":"size-medium wp-image-122"} --> -<figure class="wp-block-image alignnone size-medium wp-image-122"><img src="image.png" alt="" class="wp-image-122"/><figcaption>test</figcaption></figure> -<!-- /wp:image --> diff --git a/test/integration/fixtures/shortcode-caption-with-caption-link.html b/test/integration/fixtures/shortcode-caption-with-caption-link.html new file mode 100644 index 0000000000000..4eb0f6c4f0c1f --- /dev/null +++ b/test/integration/fixtures/shortcode-caption-with-caption-link.html @@ -0,0 +1 @@ +<p>[caption id="attachment_122" align="alignnone" width="300"]<img class="size-medium wp-image-122" src="image.png" alt="" width="300" height="101" /> <a href="https://w.org">test</a>[/caption]</p> diff --git a/test/integration/fixtures/shortcode-caption-with-link.html b/test/integration/fixtures/shortcode-caption-with-link.html new file mode 100644 index 0000000000000..3405b7ac1ecf0 --- /dev/null +++ b/test/integration/fixtures/shortcode-caption-with-link.html @@ -0,0 +1 @@ +[caption id="attachment_754" align="alignnone" width="604"]<a href="http://build.wordpress-develop.test/wp-content/uploads/2011/07/100_5478.jpg"><img class="wp-image-754 size-large" src="http://build.wordpress-develop.test/wp-content/uploads/2011/07/100_5478.jpg?w=604" alt="Bell on Wharf" width="604" height="453" /></a> Bell on wharf in San Francisco[/caption] diff --git a/test/integration/fixtures/caption-shortcode-in.html b/test/integration/fixtures/shortcode-caption.html similarity index 100% rename from test/integration/fixtures/caption-shortcode-in.html rename to test/integration/fixtures/shortcode-caption.html From caccc11991bd2541e2302d36fe7283863b31f923 Mon Sep 17 00:00:00 2001 From: mzorz <mariozorz@gmail.com> Date: Thu, 20 Dec 2018 09:41:42 -0300 Subject: [PATCH 054/691] temporarily disable link formatting (#13036) --- .../editor/src/components/rich-text/index.native.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/editor/src/components/rich-text/index.native.js b/packages/editor/src/components/rich-text/index.native.js index f6796a97fdbaa..d8f9a1cfb0476 100644 --- a/packages/editor/src/components/rich-text/index.native.js +++ b/packages/editor/src/components/rich-text/index.native.js @@ -40,11 +40,12 @@ const FORMATTING_CONTROLS = [ title: __( 'Italic' ), format: 'italic', }, - { - icon: 'admin-links', - title: __( 'Link' ), - format: 'link', - }, + // TODO: get this back after alpha + // { + // icon: 'admin-links', + // title: __( 'Link' ), + // format: 'link', + // }, { icon: 'editor-strikethrough', title: __( 'Strikethrough' ), From 4df245b738d410e108696b9753ca7843ed71e5aa Mon Sep 17 00:00:00 2001 From: Ned Zimmerman <ned@bight.ca> Date: Thu, 20 Dec 2018 09:24:56 -0400 Subject: [PATCH 055/691] Change header level in BlockCompare component (fix #12504) (#12723) * Change header level in BlockCompare component Fixes #12504 * Fix test * fix: Restore H1-style margins --- packages/editor/src/components/block-compare/block-view.js | 2 +- packages/editor/src/components/block-compare/style.scss | 1 + .../block-compare/test/__snapshots__/block-view.js.snap | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/editor/src/components/block-compare/block-view.js b/packages/editor/src/components/block-compare/block-view.js index 2d9e368aefb61..0c8189a32baa3 100644 --- a/packages/editor/src/components/block-compare/block-view.js +++ b/packages/editor/src/components/block-compare/block-view.js @@ -7,7 +7,7 @@ const BlockView = ( { title, rawContent, renderedContent, action, actionText, cl return ( <div className={ className }> <div className="editor-block-compare__content"> - <h1 className="editor-block-compare__heading">{ title }</h1> + <h2 className="editor-block-compare__heading">{ title }</h2> <div className="editor-block-compare__html"> { rawContent } diff --git a/packages/editor/src/components/block-compare/style.scss b/packages/editor/src/components/block-compare/style.scss index 64112d2e559fa..dd6242be2640a 100644 --- a/packages/editor/src/components/block-compare/style.scss +++ b/packages/editor/src/components/block-compare/style.scss @@ -74,5 +74,6 @@ .editor-block-compare__heading { font-size: 1em; font-weight: 400; + margin: 0.67em 0; } } diff --git a/packages/editor/src/components/block-compare/test/__snapshots__/block-view.js.snap b/packages/editor/src/components/block-compare/test/__snapshots__/block-view.js.snap index 087e5187b1fa1..5177c366d7006 100644 --- a/packages/editor/src/components/block-compare/test/__snapshots__/block-view.js.snap +++ b/packages/editor/src/components/block-compare/test/__snapshots__/block-view.js.snap @@ -7,11 +7,11 @@ exports[`BlockView should match snapshot 1`] = ` <div className="editor-block-compare__content" > - <h1 + <h2 className="editor-block-compare__heading" > title - </h1> + </h2> <div className="editor-block-compare__html" > From a897e056d3165b409bd2a588a16c21d2e9b8a5bd Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Thu, 20 Dec 2018 13:37:45 +0000 Subject: [PATCH 056/691] Fix: Only 10 taxonomies with 'show_in_rest' available in the editor's sidebar (#12971) Fixes: https://github.com/WordPress/gutenberg/issues/12649 Props to @danielbachhuber and @VadymPogorelov for the debugging. ## Description In the taxomies data request present in the PostTaxonomies component we had a per-page config of -1 to request all the taxonomies. https://github.com/WordPress/gutenberg/blob/master/packages/editor/src/components/post-taxonomies/index.js#L41 But on the checking component that verifies the post type relationship with the taxonomies we did set the per page config of -1 so only ten taxonomies were being requested. ## How has this been tested? I added the following code snippet for testing purposes: ``` add_action( 'init', function() { for ( $j=0; $j < 12; $j++ ) { register_post_type( 'cpt_' . $j, array( 'label' => 'cpt ' . $j, 'show_in_rest' => true, 'public' => true, ) ); for ( $i=0; $i < 4; $i++ ) { register_taxonomy( 'taxonomy_' . $j . '_' . $i, 'cpt_' . $j, array( 'label' => 'taxonomy ' . $j . ' ' .$i, 'show_in_rest' => true, ) ); } } }); ``` I verified that even in CPT greater than 2 the taxnomies appear as expected, on master that's not the case. --- packages/editor/src/components/post-taxonomies/check.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor/src/components/post-taxonomies/check.js b/packages/editor/src/components/post-taxonomies/check.js index e892b672d7778..432e1e15506a7 100644 --- a/packages/editor/src/components/post-taxonomies/check.js +++ b/packages/editor/src/components/post-taxonomies/check.js @@ -22,7 +22,7 @@ export default compose( [ withSelect( ( select ) => { return { postType: select( 'core/editor' ).getCurrentPostType(), - taxonomies: select( 'core' ).getTaxonomies(), + taxonomies: select( 'core' ).getTaxonomies( { per_page: -1 } ), }; } ), ] )( PostTaxonomiesCheck ); From d126cfadbdfe89b86ad5e124609692f9e8faf7ee Mon Sep 17 00:00:00 2001 From: Miguel Fonseca <miguelcsf@gmail.com> Date: Thu, 20 Dec 2018 13:57:44 +0000 Subject: [PATCH 057/691] Docs: Release: Clarify pinging core team members (#12970) * Docs: Release: Clarify pinging core team members Reduce the bus factor by not referencing an individual contributor. * docs: Add link to Gutenberg Core Team * docs: Include direct links to Slack channel --- docs/contributors/release.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/contributors/release.md b/docs/contributors/release.md index e909ab5281aab..e30b0c5c0e0c3 100644 --- a/docs/contributors/release.md +++ b/docs/contributors/release.md @@ -39,7 +39,7 @@ Creating a release candidate involves: ### Bumping the Version 1. Create [a pull request like this](https://github.com/WordPress/gutenberg/pull/9663), bumping the version number in `gutenberg.php`, `package.json`, and `package-lock.json`. -2. Check that there's no work-in-progress that's just about to land. [Inform committers in `#core-editor` on Slack](https://make.wordpress.org/chat/) to hold off on merging any changes until after the release candidate is tagged. +2. Check that there's no work-in-progress that's just about to land. [Inform committers in `#core-editor` on Slack](https://wordpress.slack.com/messages/C02QB2JS7) to hold off on merging any changes until after the release candidate is tagged. 3. Merge the version bump pull request. ### Tag the Release @@ -198,7 +198,7 @@ You should check that folks are able to install the new version from their Dashb 1. Publish the [make/core](https://make.wordpress.org/core/) release blog post drafted earlier. 2. Pat yourself on the back! 👍 -If you don't have access to [make.wordpress.org/core](https://make.wordpress.org/core/), ping [Matias Ventura](https://profiles.wordpress.org/matveb) or someone else on the Gutenberg Core team to publish the post. +If you don't have access to [make.wordpress.org/core](https://make.wordpress.org/core/), ping [someone on the Gutenberg Core team](https://github.com/orgs/WordPress/teams/gutenberg-core) in the [WordPress #core-editor Slack channel](https://wordpress.slack.com/messages/C02QB2JS7) to publish the post. --------- From 8ebbc8141a02947d4b6201ea20554988f05f7d06 Mon Sep 17 00:00:00 2001 From: Naoki Ohashi <n.globe.us@gmail.com> Date: Thu, 20 Dec 2018 23:51:40 +0900 Subject: [PATCH 058/691] Add my name to CONTRIBUTORS.md file. (#13032) --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 8279df075b66b..d0920a8a0191b 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -124,3 +124,4 @@ This list is manually curated to include valuable contributions by volunteers th | @designsimply | @designsimply | | @aldavigdis | @aldavigdis | | @miya0001 | @miyauchi | +| @naogify | @naoki0h | From 39e336fa47627c00187b3d13c614b37ea33b7a3f Mon Sep 17 00:00:00 2001 From: Andrei Lupu <euthelup@gmail.com> Date: Thu, 20 Dec 2018 16:54:14 +0200 Subject: [PATCH 059/691] Small typo in the Range Control's readme (#12989) Just a small typo in the documentation --- packages/components/src/range-control/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/range-control/README.md b/packages/components/src/range-control/README.md index 3966d604df865..b35557b867a07 100644 --- a/packages/components/src/range-control/README.md +++ b/packages/components/src/range-control/README.md @@ -66,7 +66,7 @@ If this property is true, a button to reset the the slider is rendered. ### initialPosition -In no value exists this prop contains the slider starting position. +If no value exists this prop contains the slider starting position. - Type: `Number` - Required: No From 882166f6707f641fce825621505a6f7b14f0d3f8 Mon Sep 17 00:00:00 2001 From: alexislloyd <alexislloyd@gmail.com> Date: Thu, 20 Dec 2018 09:55:03 -0500 Subject: [PATCH 060/691] Update block design guidelines to include info on setup states (#12985) * Update block design guidelines to include info on setup states Proposed guidelines for when to include a setup state in your block design. Would appreciate feedback on content and validation of markdown syntax. * Edited block-design.md to reflect comments on #12985 Made edits in response to comments from @jasmussen and @chrisvanpatten one #12985: - Removed extra new lines around images - Updated wording to resolve any confusion around setup state vs placeholder nomenclature. --- .../designers/block-design.md | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/docs/designers-developers/designers/block-design.md b/docs/designers-developers/designers/block-design.md index ae6abaf4c8a37..22fc9de64ef4e 100644 --- a/docs/designers-developers/designers/block-design.md +++ b/docs/designers-developers/designers/block-design.md @@ -19,6 +19,33 @@ Basic block settings won’t always make sense in the context of the placeholder The sidebar is not visible by default on a small / mobile screen, and may also be collapsed in a desktop view. Therefore, it should not be relied on for anything that is necessary for the basic operation of the block. Pick good defaults, make important actions available in the block toolbar, and think of the sidebar as something that only power users may discover. In addition, use sections and headers in the block sidebar if there are more than a handful of options, in order to allow users to easily scan and understand the options available. +## Setup state vs. live preview state + +Setup states, sometimes referred to as "placeholders", can be used to walk users through an initial process before shoing the live preview state of the block. The setup process gathers information from the user that is needed to render the block. A block’s setup state is indicated with a grey background to provide clear differentiation for the user. Not all blocks have setup states — for example, the paragraph block. + +![An example of a gallery block’s setup state on a grey background](https://make.wordpress.org/design/files/2018/12/gallery-setup.png) + +A setup state is **not** necessary if: + +- You can provide good default content in the block that will meet most people’s needs. +- That default content is easy to edit and customize. + +Use a setup state if: + +- There isn’t a clear default state that would work for most users. +- You need to gather input from the user that doesn’t have a 1-1 relationship with the live preview of the block (for example, if you need the user to input an API key to render content). +- You need more information from the user in order to render useful default content. + +For blocks that do have setup states, once the user has gone through the setup process, the placeholder is replaced with the live preview state of that block. + +![An example of the image gallery’s live preview state](https://make.wordpress.org/design/files/2018/12/gallery-live-preview.png) + +When the block is selected, additional controls may be revealed to customize the block’s contents. For example, when the image gallery is selected, it reveals controls to remove or add images. + +![An example of additional controls being revealed on selection of a block.](https://make.wordpress.org/design/files/2018/12/gallery-additional-controls.png) + +In most cases, a block’s setup state is only shown once and then further customization is done via the live preview state. However, in some cases it might be desirable to allow the user to return to the setup state — for example, if all the block content has been deleted or via a link from the block’s toolbar or sidebar. + ## Do's and Don'ts ### Blocks From 5ca3383e141879082b5a4fc412e4b85a38b3899c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Thu, 20 Dec 2018 16:06:50 +0100 Subject: [PATCH 061/691] Tests: Disable a fragile link e2e test until it is improved (#13043) --- test/e2e/specs/links.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/e2e/specs/links.test.js b/test/e2e/specs/links.test.js index eaa159d1bc0a3..e5fecebe10ec0 100644 --- a/test/e2e/specs/links.test.js +++ b/test/e2e/specs/links.test.js @@ -272,7 +272,8 @@ describe( 'Links', () => { }; // Test for regressions of https://github.com/WordPress/gutenberg/issues/10496. - it( 'allows autocomplete suggestions to be selected with the mouse', async () => { + // Disabled until improved as it wasn't reliable enough. + it.skip( 'allows autocomplete suggestions to be selected with the mouse', async () => { // First create a post that we can search for using the link autocompletion. const titleText = 'Test post mouse'; const postURL = await createPostWithTitle( titleText ); From 4e561733f456ec9acba39901ac777b465ac34348 Mon Sep 17 00:00:00 2001 From: Ajit Bohra <ajit@lubus.in> Date: Thu, 20 Dec 2018 20:42:41 +0530 Subject: [PATCH 062/691] Docs: Remove shortname from fontsizes (#13033) We are no longer using the `shortname` for `fontsizes` --- docs/designers-developers/developers/themes/theme-support.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/designers-developers/developers/themes/theme-support.md b/docs/designers-developers/developers/themes/theme-support.md index a5da2c7199e97..2e1157808a12e 100644 --- a/docs/designers-developers/developers/themes/theme-support.md +++ b/docs/designers-developers/developers/themes/theme-support.md @@ -138,25 +138,21 @@ Blocks may allow the user to configure the font sizes they use, e.g., the paragr add_theme_support( 'editor-font-sizes', array( array( 'name' => __( 'small', 'themeLangDomain' ), - 'shortName' => __( 'S', 'themeLangDomain' ), 'size' => 12, 'slug' => 'small' ), array( 'name' => __( 'regular', 'themeLangDomain' ), - 'shortName' => __( 'M', 'themeLangDomain' ), 'size' => 16, 'slug' => 'regular' ), array( 'name' => __( 'large', 'themeLangDomain' ), - 'shortName' => __( 'L', 'themeLangDomain' ), 'size' => 36, 'slug' => 'large' ), array( 'name' => __( 'larger', 'themeLangDomain' ), - 'shortName' => __( 'XL', 'themeLangDomain' ), 'size' => 50, 'slug' => 'larger' ) From 20d17f7ffad2a20dc98cda0112091affc18b43a9 Mon Sep 17 00:00:00 2001 From: Maedah Batool <MaedahBatool@users.noreply.github.com> Date: Thu, 20 Dec 2018 20:13:44 +0500 Subject: [PATCH 063/691] Improve documentation in CONTRIBUTING.md file (#11657) * Improve: Docs in CONTRIBUTING.md file Improve documentation in CONTRIBUTING.md file. Title-cased the headings and fixed grammatical/typo issues and errors. * Applied comments from feedback https://github.com/WordPress/gutenberg/pull/11657#issuecomment-437951039 --- CONTRIBUTING.md | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e9a47c7322e47..799c90da66229 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing -Thank you for thinking about contributing to WordPress' Gutenberg project! If you're unsure of anything, know that you're 💯 welcome to submit an issue or pull request on any topic. The worst that can happen is that you'll be politely directed to the best location to ask your question, or to change something in your pull request. We appreciate any sort of contribution, and don't want a wall of rules to get in the way of that. +Thank you for thinking about contributing to WordPress' Gutenberg project! If you're unsure of anything, know that you're 💯 welcome to submit an issue or pull request on any topic. The worst that can happen is that you'll be politely directed to the best location to ask your question or to change something in your pull request. We appreciate any sort of contribution and don't want a wall of rules to get in the way of that. As with all WordPress projects, we want to ensure a welcoming environment for everyone. With that in mind, all contributors are expected to follow our [Code of Conduct](CODE_OF_CONDUCT.md). @@ -14,7 +14,7 @@ Gutenberg is a Node.js-based project, built primarily in JavaScript. The easiest way to get started (on MacOS, Linux, or Windows 10 with the Linux Subsystem) is by running the Local Environment setup script, `./bin/setup-local-env.sh`. This will check if you have everything installed and updated, and help you download any extra tools you need. -For other version of Windows, or if you prefer to set things up manually, be sure to have <a href="https://nodejs.org/en/">Node.js installed first</a>. You should be running a Node version matching the [current active LTS release](https://github.com/nodejs/Release#release-schedule) or newer for this plugin to work correctly. You can check your Node.js version by typing `node -v` in the Terminal prompt. +For another version of Windows, or if you prefer to set things up manually, be sure to have <a href="https://nodejs.org/en/">Node.js installed first</a>. You should be running a Node version matching the [current active LTS release](https://github.com/nodejs/Release#release-schedule) or newer for this plugin to work correctly. You can check your Node.js version by typing `node -v` in the Terminal prompt. If you have an incompatible version of Node in your development environment, you can use [nvm](https://github.com/creationix/nvm) to change node versions on the command line: @@ -23,7 +23,7 @@ npx nvm install npx nvm use ``` -You should also have the latest release of [npm installed][npm]. npm is a separate project from Node.js and is updated frequently. If you've just installed Node.js which includes a version of npm within the installation you most likely will need to also update your npm installation. To update npm, type this into your terminal: `npm install npm@latest -g` +You also should have the latest release of [npm installed][npm]. npm is a separate project from Node.js and is updated frequently. If you've just installed Node.js which includes a version of npm within the installation you most likely will need also to update your npm installation. To update npm, type this into your terminal: `npm install npm@latest -g` To test the plugin, or to contribute to it, you can clone this repository and build the plugin files using Node. How you do that depends on whether you're developing locally or uploading the plugin to a remote host. @@ -45,10 +45,10 @@ Then, run a setup script to check if docker and node are configured properly and **If you're developing themes, or core WordPress functionality alongside Gutenberg**, you can make the WordPress files accessible in `wordpress/` by following these instructions instead: 1. If this is your first time setting up the environment, run `DOCKER_ENV=localwpdev ./bin/setup-local-env.sh` instead of `./bin/setup-local-env.sh` -2. If you've already had the previous environment set up, you need to start fresh, and you can do that by first running `docker-compose down --rmi all`. After that you can repeat step 1. +2. If you've already had the previous environment set up, you need to start fresh, and you can do that by first running `docker-compose down --rmi all`. After that, you can repeat step 1. 3. If you turn off your computer or restart Docker, you can get your local WordPress dev environment back by typing `docker-compose -f docker-compose.yml -f docker-compose-localdev.yml up`. If you just run `docker-compose up`, you will get the vanilla install that doesn't expose the WordPress folder. -**If everything was successful**, you'll see the following ascii art: +**If everything was successful**, you'll see the following ASCII art: ``` Welcome to... @@ -59,7 +59,7 @@ Welcome to... `---' ``` -The WordPress installation should be available at `http://localhost:8888` (username: `admin`, password: `password`). +The WordPress installation should be available at `http://localhost:8888` (**Username**: `admin`, **Password**: `password`). Inside the "docker" directory, you can use any docker command to interact with your containers. If this port is in use, you can override it in your `docker-compose.override.yml` file. If you're running [e2e tests](https://wordpress.org/gutenberg/handbook/reference/testing-overview/#end-to-end-testing), this change will be used correctly. To bring down this local WordPress instance later run: @@ -79,7 +79,7 @@ Next, open a terminal (or if on Windows, a command prompt) and navigate to the r ### On A Remote Server -Open a terminal (or if on Windows, a command prompt) and navigate to the repository you cloned. Now type `npm install` to get the dependencies all set up. Once that finishes, you can type `npm run build`. You can now upload the entire repository to your `wp-content/plugins` directory on your webserver and activate the plugin from the WordPress admin. +Open a terminal (or if on Windows, a command prompt) and navigate to the repository you cloned. Now type `npm install` to get the dependencies all set up. Once that finishes, you can type `npm run build`. You can now upload the entire repository to your `wp-content/plugins` directory on your web server and activate the plugin from the WordPress admin. You can also type `npm run package-plugin` which will run the two commands above and create a zip file automatically for you which you can use to install Gutenberg through the WordPress admin. @@ -88,9 +88,9 @@ You can also type `npm run package-plugin` which will run the two commands above A good workflow for new contributors to follow is listed below: - Fork Gutenberg repository - Clone forked repository -- Create new branch +- Create a new branch - Make code changes -- Commit code changes within newly created branch +- Commit code changes within the newly created branch - Push branch to forked repository - Submit Pull Request to Gutenberg repository @@ -108,15 +108,15 @@ The workflow is documented in greater detail in the [repository management](./do ## Testing -Gutenberg contains both PHP and JavaScript code, and encourages testing and code style linting for both. It also incorporates end-to-end testing using [Google Puppeteer](https://developers.google.com/web/tools/puppeteer/). You can find out more details in [Testing Overview document](./docs/contributors/testing-overview.md). +Gutenberg contains both PHP and JavaScript code and encourages testing and code style linting for both. It also incorporates end-to-end testing using [Google Puppeteer](https://developers.google.com/web/tools/puppeteer/). You can find out more details in [Testing Overview document](./docs/contributors/testing-overview.md). -## Managing packages +## Managing Packages This repository uses [lerna] to manage Gutenberg modules and publish them as packages to [npm]. -### Creating new package +### Creating a New Package -When creating a new package you need to provide at least the following: +When creating a new package, you need to provide at least the following: 1. `package.json` based on the template: ```json @@ -160,11 +160,11 @@ When creating a new package you need to provide at least the following: - Usage example - `Code is Poetry` logo (`<br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p>`) -### Maintaining changelogs +### Maintaining Changelogs Maintaining dozens of npm packages is difficult—it can be tough to keep track of changes. That's why we use `CHANGELOG.md` files for each package to simplify the release process. All packages should follow the [Semantic Versioning (`semver`) specification](https://semver.org/). -The developer who proposes a change (pull request) is responsible to choose the correct version increment (`major`, `minor`, or `patch`) according to the following guidelines: +The developer who proposes a change (pull request) is responsible for choosing the correct version increment (`major`, `minor`, or `patch`) according to the following guidelines: - Major version X (X.y.z | X > 0) should be changed with any backward incompatible/"breaking" change. This will usually occur at the final stage of deprecating and removing of a feature. - Minor version Y (x.Y.z | x > 0) should be changed when you add functionality or change functionality in a backward compatible manner. It must be incremented if any public API functionality is marked as deprecated. @@ -184,7 +184,7 @@ _Example:_ ``` - If you need to add something considered a bug fix, you add the item to `Bug Fix` section and leave the version as 1.2.2. -- If it's a new feature you add the item to `New Feature` section and change version to 1.3.0. +- If it's a new feature, you add the item to `New Feature` section and change version to 1.3.0. - If it's a breaking change you want to introduce, add the item to `Breaking Change` section and bump the version to 2.0.0. - If you struggle to classify a change as one of the above, then it might be not necessary to include it. @@ -192,13 +192,13 @@ The version bump is only necessary if one of the following applies: - There are no other unreleased changes. - The type of change you're introducing is incompatible (more severe) than the other unreleased changes. -### Releasing packages +### Releasing Packages Lerna automatically releases all outdated packages. To check which packages are outdated and will be released, type `npm run publish:check`. If you have the ability to publish packages, you _must_ have [2FA enabled](https://docs.npmjs.com/getting-started/using-two-factor-authentication) on your [npm account][npm]. -#### Before releasing +#### Before Releasing Confirm that you're logged in to [npm], by running `npm whoami`. If you're not logged in, run `npm adduser` to login. @@ -214,9 +214,9 @@ If you're publishing a new package, ensure that its `package.json` file contains You can check your package configs by running `npm run lint-pkg-json`. -#### Development release +#### Development Release -Run the following command to release a dev version of the outdated packages, replacing `123456` with your 2FA code. Make sure you're using a freshly generated 2FA code, rather than one that's about to timeout. This is a little cumbersome, but helps to prevent the release process from dying mid-deploy. +Run the following command to release a dev version of the outdated packages, replacing `123456` with your 2FA code. Make sure you're using a freshly generated 2FA code, rather than one that's about to timeout. This is a little cumbersome but helps to prevent the release process from dying mid-deploy. ```bash NPM_CONFIG_OTP=123456 npm run publish:dev @@ -226,7 +226,7 @@ Lerna will ask you which version number you want to choose for each package. For Lerna will then publish to [npm], commit the `package.json` changes and create the git tags. -#### Production release +#### Production Release To release a production version for the outdated packages, run the following command, replacing `123456` with your (freshly generated, as above) 2FA code: @@ -236,7 +236,7 @@ NPM_CONFIG_OTP=123456 npm run publish:prod Choose the correct version based on `CHANGELOG.md` files, confirm your choices and let Lerna do its magic. -## How Designers Can Contribute +## How Can Designers Contribute? If you'd like to contribute to the design or front-end, feel free to contribute to tickets labelled [Needs Design](https://github.com/WordPress/gutenberg/issues?q=is%3Aissue+is%3Aopen+label%3A%22Needs+Design%22) or [Needs Design Feedback](https://github.com/WordPress/gutenberg/issues?q=is%3Aissue+is%3Aopen+label%3A"Needs+Design+Feedback%22). We could use your thoughtful replies, mockups, animatics, sketches, doodles. Proposed changes are best done as minimal and specific iterations on the work that precedes it so we can compare. If you use <a href="https://www.sketchapp.com/">Sketch</a>, you can grab <a href="https://cloudup.com/cMPXM8Va2cy">the source file for the mockups</a> (updated April 6th). From e2b0a2913b2d37bf12421dccff0e40f4bc33fa73 Mon Sep 17 00:00:00 2001 From: Alex <araby424@gmail.com> Date: Thu, 20 Dec 2018 10:43:07 -0500 Subject: [PATCH 064/691] update/spelling-error (#12469) Edited spelling error from "hiearchical" to "hierarchical" From e74f78bd8040ba581136d94734932cf3c41b42d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Thu, 20 Dec 2018 18:14:08 +0100 Subject: [PATCH 065/691] Docs: Update all local links in README files to work with npm (#13030) * Docs: Update all local links in README files to work with npm * Update packages/blocks/README.md * Fix new lines in blocks README file * Revert nux component link * Revert link to the block library * Update link to component from edit-post package --- .../README.md | 2 +- .../block-serialization-spec-parser/README.md | 2 +- packages/blocks/README.md | 110 ++++-------------- packages/components/src/menu-item/README.md | 8 +- packages/components/src/tooltip/README.md | 2 +- packages/core-data/README.md | 4 +- packages/data/README.md | 8 +- packages/edit-post/README.md | 6 +- .../src/components/autocompleters/README.md | 2 +- .../src/components/media-upload/README.md | 2 +- packages/element/README.md | 4 +- packages/i18n/README.md | 2 +- packages/notices/README.md | 2 +- packages/plugins/README.md | 2 +- packages/viewport/README.md | 2 +- 15 files changed, 48 insertions(+), 110 deletions(-) diff --git a/packages/block-serialization-default-parser/README.md b/packages/block-serialization-default-parser/README.md index 8c35f3f52d65d..254d8f7bdcd19 100644 --- a/packages/block-serialization-default-parser/README.md +++ b/packages/block-serialization-default-parser/README.md @@ -1,6 +1,6 @@ # Block Serialization Default Parser -This library contains the default block serialization parser implementations for WordPress documents. It provides native PHP and JavaScript parsers that implement the [specification](../../docs/grammar.md) from [`@wordpress/block-serialization-spec-parser`](../block-serialization-spec-parser/README.md) and which normally operates on the document stored in `post_content`. +This library contains the default block serialization parser implementations for WordPress documents. It provides native PHP and JavaScript parsers that implement the [specification](/docs/contributors/grammar.md) from [`@wordpress/block-serialization-spec-parser`](/packages/block-serialization-spec-parser/README.md) and which normally operates on the document stored in `post_content`. ## Installation diff --git a/packages/block-serialization-spec-parser/README.md b/packages/block-serialization-spec-parser/README.md index 8de72e547276f..e4750b42cdfda 100644 --- a/packages/block-serialization-spec-parser/README.md +++ b/packages/block-serialization-spec-parser/README.md @@ -1,6 +1,6 @@ # Block Serialization Spec Parser -This library contains the grammar file (`grammar.pegjs`) for WordPress posts which is a block serialization [_specification_](../../docs/grammar.md) which is used to generate the actual _parser_ which is also bundled in this package. +This library contains the grammar file (`grammar.pegjs`) for WordPress posts which is a block serialization [_specification_](/docs/contributors/grammar.md) which is used to generate the actual _parser_ which is also bundled in this package. PEG parser generators are available in many languages, though different libraries may require some translation of this grammar into their syntax. For more information see: diff --git a/packages/blocks/README.md b/packages/blocks/README.md index fe7d7b9dced3a..380ea9b9b9da4 100644 --- a/packages/blocks/README.md +++ b/packages/blocks/README.md @@ -1,18 +1,10 @@ # Blocks -"Block" is the abstract term used to describe units of markup that, composed -together, form the content or layout of a webpage. The idea combines concepts -of what in WordPress today we achieve with shortcodes, custom HTML, and embed -discovery into a single consistent API and user experience. +"Block" is the abstract term used to describe units of markup that, composed together, form the content or layout of a webpage. The idea combines concepts of what in WordPress today we achieve with shortcodes, custom HTML, and embed discovery into a single consistent API and user experience. -For more context, refer to -[_What Are Little Blocks Made Of?_](https://make.wordpress.org/design/2017/01/25/what-are-little-blocks-made-of/) -from the -[Make WordPress Design](https://make.wordpress.org/design/) -blog. +For more context, refer to [_What Are Little Blocks Made Of?_](https://make.wordpress.org/design/2017/01/25/what-are-little-blocks-made-of/) from the [Make WordPress Design](https://make.wordpress.org/design/) blog. -The following documentation outlines steps you as a developer will need to -follow to add your own custom blocks to WordPress's editor interfaces. +The following documentation outlines steps you as a developer will need to follow to add your own custom blocks to WordPress's editor interfaces. ## Installation @@ -26,14 +18,9 @@ _This package assumes that your code will run in an **ES2015+** environment. If ## Getting Started -If you're not already accustomed to working with JavaScript in your WordPress -plugins, you may first want to reference the guide on -[_Including CSS & JavaScript_](https://developer.wordpress.org/themes/basics/including-css-javascript/) -in the Theme Handbook. +If you're not already accustomed to working with JavaScript in your WordPress plugins, you may first want to reference the guide on [_Including CSS & JavaScript_](https://developer.wordpress.org/themes/basics/including-css-javascript/) in the Theme Handbook. -At a minimum, you will need to enqueue scripts for your block as part of a -`enqueue_block_editor_assets` action callback, with a dependency on the -`wp-blocks` and `wp-element` script handles: +At a minimum, you will need to enqueue scripts for your block as part of a `enqueue_block_editor_assets` action callback, with a dependency on the `wp-blocks` and `wp-element` script handles: ```php <?php @@ -49,65 +36,34 @@ function myplugin_enqueue_block_editor_assets() { add_action( 'enqueue_block_editor_assets', 'myplugin_enqueue_block_editor_assets' ); ``` -The `enqueue_block_editor_assets` hook is only run in the Gutenberg editor -context when the editor is ready to receive additional scripts and stylesheets. -There is also an `enqueue_block_assets` hook which is run under __both__ the -editor and front-end contexts. This should be used to enqueue stylesheets -common to the front-end and the editor. (The rules can be overridden in the -editor-specific stylesheet if necessary.) - -The following sections will describe what you'll need to include in `block.js` -to describe the behavior of your custom block. - -Note that all JavaScript code samples in this document are enclosed in a -function that is evaluated immediately afterwards. We recommend using either -ES6 modules -[as used in this project](https://wordpress.org/gutenberg/handbook/reference/coding-guidelines/#imports) -(documentation on setting up a plugin with Webpack + ES6 modules coming soon) -or these -["immediately-invoked function expressions"](https://en.wikipedia.org/wiki/Immediately-invoked_function_expression) -as used in this document. Both of these methods ensure that your plugin's -variables will not pollute the global `window` object, which could cause -incompatibilities with WordPress core or with other plugins. +The `enqueue_block_editor_assets` hook is only run in the Gutenberg editor context when the editor is ready to receive additional scripts and stylesheets. There is also an `enqueue_block_assets` hook which is run under __both__ the editor and front-end contexts. This should be used to enqueue stylesheets common to the front-end and the editor. (The rules can be overridden in the editor-specific stylesheet if necessary.) + +The following sections will describe what you'll need to include in `block.js` to describe the behavior of your custom block. + +Note that all JavaScript code samples in this document are enclosed in a function that is evaluated immediately afterwards. We recommend using either ES6 modules [as used in this project](https://wordpress.org/gutenberg/handbook/reference/coding-guidelines/#imports) (documentation on setting up a plugin with Webpack + ES6 modules coming soon) or these ["immediately-invoked function expressions"](https://en.wikipedia.org/wiki/Immediately-invoked_function_expression) as used in this document. Both of these methods ensure that your plugin's variables will not pollute the global `window` object, which could cause incompatibilities with WordPress core or with other plugins. ## Example -Let's imagine you wanted to define a block to show a randomly generated image -in a post's content using -[lorempixel.com](http://lorempixel.com/). -The service provides a choice of category and you'd like to offer this as an -option when editing the post. +Let's imagine you wanted to define a block to show a randomly generated image in a post's content using [lorempixel.com](http://lorempixel.com/). The service provides a choice of category and you'd like to offer this as an option when editing the post. Take a step back and consider the ideal workflow for adding a new random image: - Insert the block. It should be shown in some empty state, with an option to choose a category in a select dropdown. - Upon confirming my selection, a preview of the image should be shown next to the dropdown. -At this point, you might realize that while you'd want some controls to be -shown when editing content, the markup included in the published post might not -appear the same (your visitors should not see a dropdown field when reading -your content). +At this point, you might realize that while you'd want some controls to be shown when editing content, the markup included in the published post might not appear the same (your visitors should not see a dropdown field when reading your content). This leads to the first requirement of describing a block: -__You will need to provide implementations both for what's to be shown in an -editor and what's to be saved with the published content__. +__You will need to provide implementations both for what's to be shown in an editor and what's to be saved with the published content__. -To eliminate redundant effort here, share common behaviors by splitting your -code up into -[components](../element). +To eliminate redundant effort here, share common behaviors by splitting your code up into [components](/packages/element/README.md). -Now that we've considered user interaction, you should think about the -underlying values that determine the markup generated by your block. In our -example, the output is affected only when the category changes. Put another -way: __the output of a block is a function of its attributes__. +Now that we've considered user interaction, you should think about the underlying values that determine the markup generated by your block. In our example, the output is affected only when the category changes. Put another way: __the output of a block is a function of its attributes__. -The category, a simple string, is the only thing we require to be able to -generate the image we want to include in the published content. We call these -underlying values of a block instance its __attributes__. +The category, a simple string, is the only thing we require to be able to generate the image we want to include in the published content. We call these underlying values of a block instance its __attributes__. -With these concepts in mind, let's explore an implementation of our random -image block: +With these concepts in mind, let's explore an implementation of our random image block: ```php <?php @@ -199,7 +155,7 @@ Let's briefly review a few items you might observe in the implementation: your plugin. This helps prevent conflicts when more than one plugin registers a block with the same name. - You will use `createElement` to describe the structure of your block's - markup. See the [Element documentation](../element/README.md) for more + markup. See the [Element documentation](/packages/element/README.md) for more information. - Extracting `RandomImage` to a separate function allows us to reuse it in both the editor-specific interface and the published content. @@ -210,34 +166,17 @@ Let's briefly review a few items you might observe in the implementation: - React provides conveniences for working with `select` element with [`value` and `onChange` props](https://facebook.github.io/react/docs/forms.html#the-select-tag). -By concerning yourself only with describing the markup of a block given its -attributes, you need not worry about maintaining the state of the page, or how -your block interacts in the context of the surrounding editor. - -But how does the markup become an object of attributes? We need a pattern for -encoding the values into the published post's markup, and then retrieving them -the next time the post is edited. This is the motivation for the block's -`attributes` property. The shape of this object matches that of the attributes -object we'd like to receive, where each value is a -[__source__](http://github.com/aduth/hpq) -which tries to find the desired value from the markup of the block. - -In the random image block above, we've given the `alt` attribute of the image a -secondary responsibility of tracking the selected category. There are a few -other ways we could have achieved this, but the category value happens to work -well as an `alt` descriptor. In the `attributes` property, we define an object -with a key of `category` whose value tries to find this `alt` attribute of the -markup. If it's successful, the category's value in our `edit` and `save` -functions will be assigned. In the case of a new block or invalid markup, this -value would be `undefined`, so we adjust our return value accordingly. +By concerning yourself only with describing the markup of a block given its attributes, you need not worry about maintaining the state of the page, or how your block interacts in the context of the surrounding editor. + +But how does the markup become an object of attributes? We need a pattern for encoding the values into the published post's markup, and then retrieving them the next time the post is edited. This is the motivation for the block's `attributes` property. The shape of this object matches that of the attributes object we'd like to receive, where each value is a [__source__](http://github.com/aduth/hpq) which tries to find the desired value from the markup of the block. + +In the random image block above, we've given the `alt` attribute of the image a secondary responsibility of tracking the selected category. There are a few other ways we could have achieved this, but the category value happens to work well as an `alt` descriptor. In the `attributes` property, we define an object with a key of `category` whose value tries to find this `alt` attribute of the markup. If it's successful, the category's value in our `edit` and `save` functions will be assigned. In the case of a new block or invalid markup, this value would be `undefined`, so we adjust our return value accordingly. ## API ### `wp.blocks.registerBlockType( name: string, typeDefinition: Object )` -Registers a new block provided a unique name and an object defining its -behavior. Once registered, the block is made available as an option to any -editor interface where blocks are implemented. +Registers a new block provided a unique name and an object defining its behavior. Once registered, the block is made available as an option to any editor interface where blocks are implemented. - `title: string` - A human-readable [localized](https://codex.wordpress.org/I18n_for_WordPress_Developers#Handling_JavaScript_files) @@ -275,5 +214,4 @@ Returns type definitions associated with a registered block. Returns settings associated with a registered control. - <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/components/src/menu-item/README.md b/packages/components/src/menu-item/README.md index 76cff639fce51..eacd6770f7aaf 100644 --- a/packages/components/src/menu-item/README.md +++ b/packages/components/src/menu-item/README.md @@ -1,6 +1,6 @@ # MenuItem -MenuItem is a component which renders a button intended to be used in combination with the [DropdownMenu component](../dropdown-menu). +MenuItem is a component which renders a button intended to be used in combination with the [DropdownMenu component](/packages/components/src/dropdown-menu/README.md). ## Usage @@ -23,7 +23,7 @@ const MyMenuItem = withState( { ## Props -MenuItem supports the following props. Any additional props are passed through to the underlying [Button](../button) or [IconButton](../icon-button) component. +MenuItem supports the following props. Any additional props are passed through to the underlying [Button](/packages/components/src/button/README.md) or [IconButton](/packages/components/src/icon-button/README.md) component. ### `children` @@ -57,14 +57,14 @@ Refer to documentation for [`label`](#label). - Type: `string` - Required: No -Refer to documentation for [IconButton's `icon` prop](../icon-button/README.md#icon). +Refer to documentation for [IconButton's `icon` prop](/packages/components/src/icon-button/README.md#icon). ### `shortcut` - Type: `string` - Required: No -Refer to documentation for [Shortcut's `shortcut` prop](../shortcut/README.md#shortcut). +Refer to documentation for [Shortcut's `shortcut` prop](/packages/components/src/shortcut/README.md#shortcut). ### `role` diff --git a/packages/components/src/tooltip/README.md b/packages/components/src/tooltip/README.md index bf155161fd0d6..f3f702a6b486d 100644 --- a/packages/components/src/tooltip/README.md +++ b/packages/components/src/tooltip/README.md @@ -36,7 +36,7 @@ The direction in which the tooltip should open relative to its parent node. Spec The element to which the tooltip should anchor. -__NOTE:__ You must pass only a single child. Tooltip renders itself as a clone of `children` with a [`Popover`](../popover) added as an additional child. +__NOTE:__ You must pass only a single child. Tooltip renders itself as a clone of `children` with a [`Popover`](/packages/components/src/popover/README.md) added as an additional child. - Type: `Element` - Required: Yes diff --git a/packages/core-data/README.md b/packages/core-data/README.md index f2b61d9157315..8470bbda78a94 100644 --- a/packages/core-data/README.md +++ b/packages/core-data/README.md @@ -1,8 +1,8 @@ # Core Data -Core Data is a [data module](../data) intended to simplify access to and manipulation of core WordPress entities. It registers its own store and provides a number of selectors which resolve data from the WordPress REST API automatically, along with dispatching action creators to manipulate data. +Core Data is a [data module](/packages/data/README.md) intended to simplify access to and manipulation of core WordPress entities. It registers its own store and provides a number of selectors which resolve data from the WordPress REST API automatically, along with dispatching action creators to manipulate data. -Used in combination with features of the data module such as [`subscribe`](https://github.com/WordPress/gutenberg/tree/master/packages/data#subscribe-function) or [higher-order components](https://github.com/WordPress/gutenberg/tree/master/packages/data#higher-order-components), it enables a developer to easily add data into the logic and display of their plugin. +Used in combination with features of the data module such as [`subscribe`](/packages/data/README.md#subscribe-function) or [higher-order components](/packages/data/README.md#higher-order-components), it enables a developer to easily add data into the logic and display of their plugin. ## Installation diff --git a/packages/data/README.md b/packages/data/README.md index 2c911ccf4cf8f..630d30ef9df6c 100644 --- a/packages/data/README.md +++ b/packages/data/README.md @@ -131,13 +131,13 @@ The `resolvers` option should be passed as an object where each key is the name ### `controls` -_**Note:** Controls are an opt-in feature, enabled via `use` (the [Plugins API](https://github.com/WordPress/gutenberg/tree/master/packages/data/src/plugins))._ +_**Note:** Controls are an opt-in feature, enabled via `use` (the [Plugins API](/packages/data/src/plugins/README.md))._ A **control** defines the execution flow behavior associated with a specific action type. This can be particularly useful in implementing asynchronous data flows for your store. By defining your action creator or resolvers as a generator which yields specific controlled action types, the execution will proceed as defined by the control handler. The `controls` option should be passed as an object where each key is the name of the action type to act upon, the value a function which receives the original action object. It should returns either a promise which is to resolve when evaluation of the action should continue, or a value. The value or resolved promise value is assigned on the return value of the yield assignment. If the control handler returns undefined, the execution is not continued. -Refer to the [documentation of `@wordpress/redux-routine`](https://github.com/WordPress/gutenberg/tree/master/packages/redux-routine/) for more information. +Refer to the [documentation of `@wordpress/redux-routine`](/packages/redux-routine/README.md) for more information. ## Data Access and Manipulation @@ -225,7 +225,7 @@ registerStore( 'my-shop', { ### Higher-Order Components -A higher-order component is a function which accepts a [component](https://github.com/WordPress/gutenberg/tree/master/packages/element) and returns a new, enhanced component. A stateful user interface should respond to changes in the underlying state and updates its displayed element accordingly. WordPress uses higher-order components both as a means to separate the purely visual aspects of an interface from its data backing, and to ensure that the data is kept in-sync with the stores. +A higher-order component is a function which accepts a [component](/packages/element/README.md) and returns a new, enhanced component. A stateful user interface should respond to changes in the underlying state and updates its displayed element accordingly. WordPress uses higher-order components both as a means to separate the purely visual aspects of an interface from its data backing, and to ensure that the data is kept in-sync with the stores. #### `withSelect( mapSelectToProps: Function ): Function` @@ -406,7 +406,7 @@ registry.registerGenericStore( 'custom-data', createCustomStore() ); The data module shares many of the same [core principles](https://redux.js.org/introduction/three-principles) and [API method naming](https://redux.js.org/api-reference) of [Redux](https://redux.js.org/). In fact, it is implemented atop Redux. Where it differs is in establishing a modularization pattern for creating separate but interdependent stores, and in codifying conventions such as selector functions as the primary entry point for data access. -The [higher-order components](#higher-order-components) were created to complement this distinction. The intention with splitting `withSelect` and `withDispatch` — where in React Redux they are combined under `connect` as `mapStateToProps` and `mapDispatchToProps` arguments — is to more accurately reflect that dispatch is not dependent upon a subscription to state changes, and to allow for state-derived values to be used in `withDispatch` (via [higher-order component composition](https://github.com/WordPress/gutenberg/tree/master/packages/compose)). +The [higher-order components](#higher-order-components) were created to complement this distinction. The intention with splitting `withSelect` and `withDispatch` — where in React Redux they are combined under `connect` as `mapStateToProps` and `mapDispatchToProps` arguments — is to more accurately reflect that dispatch is not dependent upon a subscription to state changes, and to allow for state-derived values to be used in `withDispatch` (via [higher-order component composition](/packages/compose/README.md)). Specific implementation differences from Redux and React Redux: diff --git a/packages/edit-post/README.md b/packages/edit-post/README.md index a150193a78a61..48e54d76ae36e 100644 --- a/packages/edit-post/README.md +++ b/packages/edit-post/README.md @@ -18,11 +18,11 @@ _This package assumes that your code will run in an **ES2015+** environment. If Extending the editor UI can be accomplished with the `registerPlugin` API, allowing you to define all your plugin's UI elements in one place. -Refer to [the plugins module documentation](../plugins/) for more information. +Refer to [the plugins module documentation](/packages/plugins/README.md) for more information. ## Plugin Components -The following components can be used with the `registerPlugin` ([see documentation](../plugins)) API. +The following components can be used with the `registerPlugin` ([see documentation](/packages/plugins/README.md)) API. They can be found in the global variable `wp.editPost` when defining `wp-edit-post` as a script dependency. ### `PluginBlockSettingsMenuItem` @@ -249,7 +249,7 @@ const MyButtonMoreMenuItem = () => ( #### Props -`PluginMoreMenuItem` supports the following props. Any additional props are passed through to the underlying [MenuItem](../components/src/menu-item) component. +`PluginMoreMenuItem` supports the following props. Any additional props are passed through to the underlying [MenuItem](/packages/components/src/menu-item/README.md) component. ##### href diff --git a/packages/editor/src/components/autocompleters/README.md b/packages/editor/src/components/autocompleters/README.md index a802c02f75dba..d7c039e01b22a 100644 --- a/packages/editor/src/components/autocompleters/README.md +++ b/packages/editor/src/components/autocompleters/README.md @@ -1,4 +1,4 @@ Autocompleters ============== -The Autocompleter interface is documented [here](../../components/autocomplete/README.md) with the `Autocomplete` component in `@wordpress/components`. +The Autocompleter interface is documented [here](/packages/components/src/autocomplete/README.md) with the `Autocomplete` component in `@wordpress/components`. diff --git a/packages/editor/src/components/media-upload/README.md b/packages/editor/src/components/media-upload/README.md index 1dc11cb2a37ed..1fdeed671bd1e 100644 --- a/packages/editor/src/components/media-upload/README.md +++ b/packages/editor/src/components/media-upload/README.md @@ -20,7 +20,7 @@ addFilter( ); ``` -You can check how this component is implemented for the edit post page using `wp.media` module in [edit-post](../../../../edit-post/src/hooks/components/media-upload/index.js). +You can check how this component is implemented for the edit post page using `wp.media` module in [edit-post](https://github.com/WordPress/gutenberg/tree/master/packages/edit-post/src/hooks/components/media-upload/index.js). ## Usage diff --git a/packages/element/README.md b/packages/element/README.md index 0154d2762859b..cca88dff95240 100755 --- a/packages/element/README.md +++ b/packages/element/README.md @@ -47,9 +47,9 @@ Refer to the [official React Quick Start guide](https://reactjs.org/docs/hello-w ## Why React? -At the risk of igniting debate surrounding any single "best" front-end framework, the choice to use any tool should be motivated specifically to serve the requirements of the system. In modeling the concept of a [block](../../blocks/README.md), we observe the following technical requirements: +At the risk of igniting debate surrounding any single "best" front-end framework, the choice to use any tool should be motivated specifically to serve the requirements of the system. In modeling the concept of a [block](/packages/blocks/README.md), we observe the following technical requirements: -- An understanding of a block in terms of its underlying values (in the [random image example](../blocks/README.md#example), a category) +- An understanding of a block in terms of its underlying values (in the [random image example](/packages/blocks/README.md#example), a category) - A means to describe the UI of a block given these values At its most basic, React provides a simple input / output mechanism. __Given a set of inputs ("props"), a developer describes the output to be shown on the page.__ This is most elegantly observed in its [function components](https://reactjs.org/docs/components-and-props.html#functional-and-class-components). React serves the role of reconciling the desired output with the current state of the page. diff --git a/packages/i18n/README.md b/packages/i18n/README.md index f1d6857b2a813..5e57b0f4cb806 100644 --- a/packages/i18n/README.md +++ b/packages/i18n/README.md @@ -27,7 +27,7 @@ Note that you will not need to specify [domain](https://codex.wordpress.org/I18n ## Build -You can use the [WordPress i18n babel plugin](../babel-plugin-makepot/README.md) to generate a `.pot` file containing all your localized strings. +You can use the [WordPress i18n babel plugin](/packages/babel-plugin-makepot/README.md) to generate a `.pot` file containing all your localized strings. The package also includes a `pot-to-php` script used to generate a php files containing the messages listed in a `.pot` file. This is useful to trick WordPress.org translation strings discovery since at the moment, WordPress.org is not capable of parsing strings directly from JavaScript files. diff --git a/packages/notices/README.md b/packages/notices/README.md index 1091435266540..a32e69725b39c 100644 --- a/packages/notices/README.md +++ b/packages/notices/README.md @@ -18,7 +18,7 @@ _This package assumes that your code will run in an **ES2015+** environment. If When imported, the notices module registers a data store on the `core/notices` namespace. -For more information about consuming from a data store, refer to [the `@wordpress/data` documentation on _Data Access and Manipulation_](https://wordpress.org/gutenberg/handbook/packages/packages-data/#data-access-and-manipulation). +For more information about consuming from a data store, refer to [the `@wordpress/data` documentation on _Data Access and Manipulation_](/packages/data/README.md#data-access-and-manipulation). For a full list of actions and selectors available in the `core/notices` namespace, refer to the [_Notices Data_ Handbook page](https://wordpress.org/gutenberg/handbook/packages/packages-data/packages-data-core-edit-post/). diff --git a/packages/plugins/README.md b/packages/plugins/README.md index 4d4afd5896b19..a4ace1daf8f3c 100644 --- a/packages/plugins/README.md +++ b/packages/plugins/README.md @@ -29,7 +29,7 @@ This method takes two arguments: or an element (or function returning an element) if you choose to render your own SVG. - `render`: A component containing the UI elements to be rendered. -See [the edit-post module documentation](/packages/edit-post/) for available components. +See [the edit-post module documentation](/packages/edit-post/README.md) for available components. _Example:_ {% codetabs %} diff --git a/packages/viewport/README.md b/packages/viewport/README.md index 9f15a3a6d8e33..08e2d33fb748f 100644 --- a/packages/viewport/README.md +++ b/packages/viewport/README.md @@ -1,6 +1,6 @@ # Viewport -Viewport is a module for responding to changes in the browser viewport size. It registers its own [data module](https://github.com/WordPress/gutenberg/tree/master/packages/data), updated in response to browser media queries on a standard set of supported breakpoints. This data and the included higher-order components can be used in your own modules and components to implement viewport-dependent behaviors. +Viewport is a module for responding to changes in the browser viewport size. It registers its own [data module](/packages/data/README.md), updated in response to browser media queries on a standard set of supported breakpoints. This data and the included higher-order components can be used in your own modules and components to implement viewport-dependent behaviors. ## Installation From d0879a46999941ce1f229153febea8cfd96a75a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milan=20Dini=C4=87?= <milan@srpski.biz> Date: Thu, 20 Dec 2018 21:47:07 +0100 Subject: [PATCH 066/691] Make help text more in line with other help texts (#11068) * Make help text more in line with other help texts. * Remove submodule. * Update embed-preview.js --- packages/block-library/src/embed/embed-preview.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/embed/embed-preview.js b/packages/block-library/src/embed/embed-preview.js index 2282ed04809a2..addc30e0cd341 100644 --- a/packages/block-library/src/embed/embed-preview.js +++ b/packages/block-library/src/embed/embed-preview.js @@ -54,7 +54,7 @@ const EmbedPreview = ( props ) => { { ( cannotPreview ) ? ( <Placeholder icon={ <BlockIcon icon={ icon } showColors /> } label={ label }> <p className="components-placeholder__error"><a href={ url }>{ url }</a></p> - <p className="components-placeholder__error">{ __( 'Previews for this are unavailable in the editor, sorry!' ) }</p> + <p className="components-placeholder__error">{ __( 'Sorry, we cannot preview this embedded content in the editor.' ) }</p> </Placeholder> ) : embedWrapper } { ( ! RichText.isEmpty( caption ) || isSelected ) && ( From 8f736654da31f11e11845442185a9110054e6a35 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Thu, 20 Dec 2018 21:00:30 -0500 Subject: [PATCH 067/691] URL: Return querystring fragment from addQueryArgs when URL undefined (#12803) --- packages/url/CHANGELOG.md | 6 ++++++ packages/url/src/index.js | 30 +++++++++++++++++++++-------- packages/url/src/test/index.test.js | 8 ++++++-- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/packages/url/CHANGELOG.md b/packages/url/CHANGELOG.md index 1b5f99789bbf8..3b9d9be440fc1 100644 --- a/packages/url/CHANGELOG.md +++ b/packages/url/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.3.3 (Unreleased) + +### Bug Fixes + +- `addQueryArgs` will return only the querystring fragment if the passed `url` is undefined. Previously, an uncaught error would be thrown. + ## 2.3.2 (2018-12-12) ## 2.3.1 (2018-11-20) diff --git a/packages/url/src/index.js b/packages/url/src/index.js index 2f399711cd637..deae7b633756c 100644 --- a/packages/url/src/index.js +++ b/packages/url/src/index.js @@ -159,19 +159,33 @@ export function isValidFragment( fragment ) { } /** - * Appends arguments to the query string of the url + * Appends arguments as querystring to the provided URL. If the URL already + * includes query arguments, the arguments are merged with (and take precedent + * over) the existing set. * - * @param {string} url URL - * @param {Object} args Query Args + * @param {?string} url URL to which arguments should be appended. If omitted, + * only the resulting querystring is returned. + * @param {Object} args Query arguments to apply to URL. * - * @return {string} Updated URL + * @return {string} URL with arguments applied. */ -export function addQueryArgs( url, args ) { +export function addQueryArgs( url = '', args ) { + let baseUrl = url; + + // Determine whether URL already had query arguments. const queryStringIndex = url.indexOf( '?' ); - const query = queryStringIndex !== -1 ? parse( url.substr( queryStringIndex + 1 ) ) : {}; - const baseUrl = queryStringIndex !== -1 ? url.substr( 0, queryStringIndex ) : url; + if ( queryStringIndex !== -1 ) { + // Merge into existing query arguments. + args = Object.assign( + parse( url.substr( queryStringIndex + 1 ) ), + args + ); + + // Change working base URL to omit previous query arguments. + baseUrl = baseUrl.substr( 0, queryStringIndex ); + } - return baseUrl + '?' + stringify( { ...query, ...args } ); + return baseUrl + '?' + stringify( args ); } /** diff --git a/packages/url/src/test/index.test.js b/packages/url/src/test/index.test.js index eaf4287462acc..5e1ea6818090c 100644 --- a/packages/url/src/test/index.test.js +++ b/packages/url/src/test/index.test.js @@ -285,14 +285,14 @@ describe( 'isValidFragment', () => { } ); describe( 'addQueryArgs', () => { - it( 'should append args to an URL without query string', () => { + it( 'should append args to a URL without query string', () => { const url = 'https://andalouses.example/beach'; const args = { sun: 'true', sand: 'false' }; expect( addQueryArgs( url, args ) ).toBe( 'https://andalouses.example/beach?sun=true&sand=false' ); } ); - it( 'should append args to an URL with query string', () => { + it( 'should append args to a URL with query string', () => { const url = 'https://andalouses.example/beach?night=false'; const args = { sun: 'true', sand: 'false' }; @@ -326,6 +326,10 @@ describe( 'addQueryArgs', () => { expect( addQueryArgs( url, args ) ).toBe( 'https://andalouses.example/beach?activity=fun%20in%20the%20sun' ); } ); + + it( 'should return only querystring when passed undefined url', () => { + expect( addQueryArgs( undefined, { sun: 'true' } ) ).toBe( '?sun=true' ); + } ); } ); describe( 'getQueryArg', () => { From 13683fff571ed978b0f1ae338758d4b031a4c62a Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 21 Dec 2018 03:26:59 -0500 Subject: [PATCH 068/691] Editor: Return previewLink from post link only if available (#12800) * Editor: Return previewLink from post link only if available * Editor: Add CHANGELOG entry for publicly_queryable autosave fix * Editor: Resolve previewLink lint errors * Editor: Document autosave error handling --- packages/editor/CHANGELOG.md | 1 + packages/editor/src/store/reducer.js | 14 +++-- packages/editor/src/store/test/reducer.js | 66 +++++++++++++++++++++++ 3 files changed, 77 insertions(+), 4 deletions(-) diff --git a/packages/editor/CHANGELOG.md b/packages/editor/CHANGELOG.md index cb15210a97677..f8465425eed10 100644 --- a/packages/editor/CHANGELOG.md +++ b/packages/editor/CHANGELOG.md @@ -9,6 +9,7 @@ ### Bug Fixes - `getEditedPostAttribute` now correctly returns the merged result of edits as a partial change when given `'meta'` as the `attributeName`. +- Fixes an error and unrecoverable state which occurs on autosave completion for a `'publicly_queryable' => false` post type. ## 9.0.4 (2018-11-30) diff --git a/packages/editor/src/store/reducer.js b/packages/editor/src/store/reducer.js index dd198c4720181..06061290396de 100644 --- a/packages/editor/src/store/reducer.js +++ b/packages/editor/src/store/reducer.js @@ -1300,17 +1300,23 @@ export function autosave( state = null, action ) { } /** - * Reducer returning the poost preview link + * Reducer returning the post preview link. * - * @param {string?} state The preview link - * @param {Object} action Dispatched action. + * @param {string?} state The preview link + * @param {Object} action Dispatched action. * * @return {string?} Updated state. */ export function previewLink( state = null, action ) { switch ( action.type ) { case 'REQUEST_POST_UPDATE_SUCCESS': - return action.post.preview_link || addQueryArgs( action.post.link, { preview: true } ); + if ( action.post.preview_link ) { + return action.post.preview_link; + } else if ( action.post.link ) { + return addQueryArgs( action.post.link, { preview: true } ); + } + + return state; case 'REQUEST_POST_UPDATE_START': // Invalidate known preview link when autosave starts. diff --git a/packages/editor/src/store/test/reducer.js b/packages/editor/src/store/test/reducer.js index e9c1db43e53e8..2d11a0e319a2d 100644 --- a/packages/editor/src/store/test/reducer.js +++ b/packages/editor/src/store/test/reducer.js @@ -37,6 +37,7 @@ import { blockListSettings, autosave, postSavingLock, + previewLink, } from '../reducer'; import { INITIAL_EDITS_DEFAULTS } from '../defaults'; @@ -2544,4 +2545,69 @@ describe( 'state', () => { expect( state ).toEqual( {} ); } ); } ); + + describe( 'previewLink', () => { + it( 'returns null by default', () => { + const state = previewLink( undefined, {} ); + + expect( state ).toBe( null ); + } ); + + it( 'returns preview link from save success', () => { + const state = previewLink( null, { + type: 'REQUEST_POST_UPDATE_SUCCESS', + post: { + preview_link: 'https://example.com/?p=2611&preview=true', + }, + } ); + + expect( state ).toBe( 'https://example.com/?p=2611&preview=true' ); + } ); + + it( 'returns post link with query arg from save success if no preview link', () => { + const state = previewLink( null, { + type: 'REQUEST_POST_UPDATE_SUCCESS', + post: { + link: 'https://example.com/sample-post/', + }, + } ); + + expect( state ).toBe( 'https://example.com/sample-post/?preview=true' ); + } ); + + it( 'returns same state if save success without preview link or post link', () => { + // Bug: This can occur for post types which are defined as + // `publicly_queryable => false` (non-viewable). + // + // See: https://github.com/WordPress/gutenberg/issues/12677 + const state = previewLink( null, { + type: 'REQUEST_POST_UPDATE_SUCCESS', + post: { + preview_link: '', + }, + } ); + + expect( state ).toBe( null ); + } ); + + it( 'returns resets on preview start', () => { + const state = previewLink( 'https://example.com/sample-post/', { + type: 'REQUEST_POST_UPDATE_START', + options: { + isPreview: true, + }, + } ); + + expect( state ).toBe( null ); + } ); + + it( 'returns state on non-preview save start', () => { + const state = previewLink( 'https://example.com/sample-post/', { + type: 'REQUEST_POST_UPDATE_START', + options: {}, + } ); + + expect( state ).toBe( 'https://example.com/sample-post/' ); + } ); + } ); } ); From e84194903bec3fedfd440183f3dab46358a83414 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Fri, 21 Dec 2018 08:27:51 +0000 Subject: [PATCH 069/691] Fix: TypeError: Cannot read property 'prefix' of null when post is publicly_queryable but not public (#12884) --- .../src/components/sidebar/post-link/index.js | 6 +++-- .../src/components/post-permalink/index.js | 24 ++++++++++++++++--- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/packages/edit-post/src/components/sidebar/post-link/index.js b/packages/edit-post/src/components/sidebar/post-link/index.js index 4b614805e0301..760f71109911c 100644 --- a/packages/edit-post/src/components/sidebar/post-link/index.js +++ b/packages/edit-post/src/components/sidebar/post-link/index.js @@ -124,8 +124,10 @@ export default compose( [ } = select( 'core' ); const { link, id } = getCurrentPost(); + const postTypeName = getEditedPostAttribute( 'type' ); const postType = getPostType( postTypeName ); + return { isNew: isEditedPostNew(), postLink: link, @@ -140,8 +142,8 @@ export default compose( [ postID: id, }; } ), - ifCondition( ( { isEnabled, isNew, postLink, isViewable } ) => { - return isEnabled && ! isNew && postLink && isViewable; + ifCondition( ( { isEnabled, isNew, postLink, isViewable, permalinkParts } ) => { + return isEnabled && ! isNew && postLink && isViewable && permalinkParts; } ), withDispatch( ( dispatch ) => { const { toggleEditorPanelOpened } = dispatch( 'core/edit-post' ); diff --git a/packages/editor/src/components/post-permalink/index.js b/packages/editor/src/components/post-permalink/index.js index 767f2507f7717..c28a1d1754485 100644 --- a/packages/editor/src/components/post-permalink/index.js +++ b/packages/editor/src/components/post-permalink/index.js @@ -2,6 +2,7 @@ * External dependencies */ import classnames from 'classnames'; +import { get } from 'lodash'; /** * WordPress dependencies @@ -57,9 +58,19 @@ class PostPermalink extends Component { } render() { - const { isNew, postLink, permalinkParts, postSlug, postTitle, postID, isEditable, isPublished } = this.props; - - if ( isNew || ! postLink ) { + const { + isEditable, + isNew, + isPublished, + isViewable, + permalinkParts, + postLink, + postSlug, + postID, + postTitle, + } = this.props; + + if ( isNew || ! isViewable || ! permalinkParts || ! postLink ) { return null; } @@ -138,9 +149,15 @@ export default compose( [ getEditedPostAttribute, isCurrentPostPublished, } = select( 'core/editor' ); + const { + getPostType, + } = select( 'core' ); const { id, link } = getCurrentPost(); + const postTypeName = getEditedPostAttribute( 'type' ); + const postType = getPostType( postTypeName ); + return { isNew: isEditedPostNew(), postLink: link, @@ -150,6 +167,7 @@ export default compose( [ isPublished: isCurrentPostPublished(), postTitle: getEditedPostAttribute( 'title' ), postID: id, + isViewable: get( postType, [ 'viewable' ], false ), }; } ), withDispatch( ( dispatch ) => { From 9b607fa8f6ea5c25cbba2544a3492604328cbc87 Mon Sep 17 00:00:00 2001 From: Adam Silverstein <adam@10up.com> Date: Fri, 21 Dec 2018 02:06:14 -0700 Subject: [PATCH 070/691] Exit early from onClick handler when isButtonDisabled (#12885) * disable onClick event when button is disabled * clean up unit tests after click handler change --- .../components/post-publish-button/index.js | 3 +++ .../post-publish-button/test/index.js | 21 ++++++++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/packages/editor/src/components/post-publish-button/index.js b/packages/editor/src/components/post-publish-button/index.js index cfed6dbd112f5..18eba3606e711 100644 --- a/packages/editor/src/components/post-publish-button/index.js +++ b/packages/editor/src/components/post-publish-button/index.js @@ -73,6 +73,9 @@ export class PostPublishButton extends Component { } const onClick = () => { + if ( isButtonDisabled ) { + return; + } onSubmit(); onStatusChange( publishStatus ); onSave(); diff --git a/packages/editor/src/components/post-publish-button/test/index.js b/packages/editor/src/components/post-publish-button/test/index.js index 8fbef6007144a..30156438cc14f 100644 --- a/packages/editor/src/components/post-publish-button/test/index.js +++ b/packages/editor/src/components/post-publish-button/test/index.js @@ -103,7 +103,10 @@ describe( 'PostPublishButton', () => { <PostPublishButton hasPublishAction={ false } onStatusChange={ onStatusChange } - onSave={ onSave } /> + onSave={ onSave } + isSaveable={ true } + isPublishable={ true } + /> ); wrapper.simulate( 'click' ); @@ -119,7 +122,9 @@ describe( 'PostPublishButton', () => { hasPublishAction={ true } onStatusChange={ onStatusChange } onSave={ onSave } - isBeingScheduled /> + isBeingScheduled + isSaveable={ true } + isPublishable={ true } /> ); wrapper.simulate( 'click' ); @@ -135,7 +140,9 @@ describe( 'PostPublishButton', () => { hasPublishAction={ true } onStatusChange={ onStatusChange } onSave={ onSave } - visibility="private" /> + visibility="private" + isSaveable={ true } + isPublishable={ true } /> ); wrapper.simulate( 'click' ); @@ -150,7 +157,9 @@ describe( 'PostPublishButton', () => { <PostPublishButton hasPublishAction={ true } onStatusChange={ onStatusChange } - onSave={ onSave } /> + onSave={ onSave } + isSaveable={ true } + isPublishable={ true } /> ); wrapper.simulate( 'click' ); @@ -167,7 +176,9 @@ describe( 'PostPublishButton', () => { <PostPublishButton hasPublishAction={ true } onStatusChange={ onStatusChange } - onSave={ onSave } /> + onSave={ onSave } + isSaveable={ true } + isPublishable={ true } /> ); wrapper.simulate( 'click' ); From d97a3739374ec6fee7ebd5b268853b50762d468e Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 21 Dec 2018 04:59:00 -0500 Subject: [PATCH 071/691] Editor: Handle block merge in withDispatch callback (#12820) --- .../editor/src/components/block-list/block.js | 54 +++++++------------ 1 file changed, 19 insertions(+), 35 deletions(-) diff --git a/packages/editor/src/components/block-list/block.js b/packages/editor/src/components/block-list/block.js index f29de8106fab5..6687629420610 100644 --- a/packages/editor/src/components/block-list/block.js +++ b/packages/editor/src/components/block-list/block.js @@ -59,7 +59,6 @@ export class BlockListBlock extends Component { this.maybeHover = this.maybeHover.bind( this ); this.forceFocusedContextualToolbar = this.forceFocusedContextualToolbar.bind( this ); this.hideHoverEffects = this.hideHoverEffects.bind( this ); - this.mergeBlocks = this.mergeBlocks.bind( this ); this.insertBlocksAfter = this.insertBlocksAfter.bind( this ); this.onFocus = this.onFocus.bind( this ); this.preventDrag = this.preventDrag.bind( this ); @@ -235,30 +234,6 @@ export class BlockListBlock extends Component { } } - mergeBlocks( forward = false ) { - const { - clientId, - getPreviousBlockClientId, - getNextBlockClientId, - onMerge, - } = this.props; - const previousBlockClientId = getPreviousBlockClientId( clientId ); - const nextBlockClientId = getNextBlockClientId( clientId ); - // Do nothing when it's the first block. - if ( - ( ! forward && ! previousBlockClientId ) || - ( forward && ! nextBlockClientId ) - ) { - return; - } - - if ( forward ) { - onMerge( clientId, nextBlockClientId ); - } else { - onMerge( previousBlockClientId, clientId ); - } - } - insertBlocksAfter( blocks ) { this.props.onInsertBlocks( blocks, this.props.order + 1 ); } @@ -504,7 +479,7 @@ export class BlockListBlock extends Component { setAttributes={ this.setAttributes } insertBlocksAfter={ isLocked ? undefined : this.insertBlocksAfter } onReplace={ isLocked ? undefined : onReplace } - mergeBlocks={ isLocked ? undefined : this.mergeBlocks } + mergeBlocks={ isLocked ? undefined : this.props.onMerge } clientId={ clientId } isSelectionEnabled={ this.props.isSelectionEnabled } toggleSelection={ this.props.toggleSelection } @@ -670,8 +645,6 @@ const applyWithSelect = withSelect( getEditorSettings, hasSelectedInnerBlock, getTemplateLock, - getPreviousBlockClientId, - getNextBlockClientId, __unstableGetBlockWithoutInnerBlocks, } = select( 'core/editor' ); const block = __unstableGetBlockWithoutInnerBlocks( clientId ); @@ -716,11 +689,6 @@ const applyWithSelect = withSelect( isValid, isSelected, isParentOfSelectedBlock, - - // We only care about these selectors when events are triggered. - // We call them dynamically in the event handlers to avoid unnecessary re-renders. - getPreviousBlockClientId, - getNextBlockClientId, }; } ); @@ -758,8 +726,24 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, { select } ) => { onRemove( clientId ) { removeBlock( clientId ); }, - onMerge( ...args ) { - mergeBlocks( ...args ); + onMerge( forward ) { + const { clientId } = ownProps; + const { + getPreviousBlockClientId, + getNextBlockClientId, + } = select( 'core/editor' ); + + if ( forward ) { + const nextBlockClientId = getNextBlockClientId( clientId ); + if ( nextBlockClientId ) { + mergeBlocks( clientId, nextBlockClientId ); + } + } else { + const previousBlockClientId = getPreviousBlockClientId( clientId ); + if ( previousBlockClientId ) { + mergeBlocks( previousBlockClientId, clientId ); + } + } }, onReplace( blocks ) { replaceBlocks( [ ownProps.clientId ], blocks ); From 73b1a4f7d88dfdd780e53b65746ddc4b8296bfa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Gomes?= <sergiomdgomes@gmail.com> Date: Fri, 21 Dec 2018 10:16:57 +0000 Subject: [PATCH 072/691] Reduce rerendering on startup (#12847) * Ensure viewport data store changes happen immediately on startup. We need to call `flush`, so that the changes happen synchronously, rather than asynchronously, on the next event loop. This avoids the re-rendering of all `BlockListBlock` instances during startup. * Ensure `state.hasUploadPermissions` is always a boolean. The reducer for this property defaults to an empty object, which not only is incorrect (since this is a boolean), but also causes unnecessary re-renders by changing reference. By defaulting to `true` instead, we fix both problems. * Add a couple of tests for hasUploadPermissions reducer. * Core Data: Add CHANGELOG entry for hasUplaodPermissions boolean fix * Viewport: Add CHANGELOG entry for synchronous assignment --- packages/core-data/CHANGELOG.md | 6 ++++++ packages/core-data/src/reducer.js | 2 +- packages/core-data/src/test/reducer.js | 21 ++++++++++++++++++++- packages/viewport/CHANGELOG.md | 6 ++++++ packages/viewport/src/index.js | 1 + 5 files changed, 34 insertions(+), 2 deletions(-) diff --git a/packages/core-data/CHANGELOG.md b/packages/core-data/CHANGELOG.md index 3594be36a67ec..45a5c0b714228 100644 --- a/packages/core-data/CHANGELOG.md +++ b/packages/core-data/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.0.16 (Unreleased) + +### Bug Fixes + +- Fixed the `hasUploadPermissions` selector to always return a boolean. Previously, it may have returned an empty object. This should have no impact for most consumers, assuming usage as a [truthy value](https://developer.mozilla.org/en-US/docs/Glossary/Truthy) in conditions. + ## 2.0.15 (2018-12-12) ## 2.0.14 (2018-11-20) diff --git a/packages/core-data/src/reducer.js b/packages/core-data/src/reducer.js index d37969cbdae14..bb14a0283b7ca 100644 --- a/packages/core-data/src/reducer.js +++ b/packages/core-data/src/reducer.js @@ -225,7 +225,7 @@ export function embedPreviews( state = {}, action ) { * * @return {Object} Updated state. */ -export function hasUploadPermissions( state = {}, action ) { +export function hasUploadPermissions( state = true, action ) { switch ( action.type ) { case 'RECEIVE_UPLOAD_PERMISSIONS': return action.hasUploadPermissions; diff --git a/packages/core-data/src/test/reducer.js b/packages/core-data/src/test/reducer.js index 90f9b21f1a8cf..f6647becf0743 100644 --- a/packages/core-data/src/test/reducer.js +++ b/packages/core-data/src/test/reducer.js @@ -7,7 +7,7 @@ import { filter } from 'lodash'; /** * Internal dependencies */ -import { terms, entities, embedPreviews } from '../reducer'; +import { terms, entities, embedPreviews, hasUploadPermissions } from '../reducer'; describe( 'terms()', () => { it( 'returns an empty object by default', () => { @@ -117,3 +117,22 @@ describe( 'embedPreviews()', () => { } ); } ); } ); + +describe( 'hasUploadPermissions()', () => { + it( 'returns true by default', () => { + const state = hasUploadPermissions( undefined, {} ); + + expect( state ).toEqual( true ); + } ); + + it( 'returns with updated upload permissions value', () => { + const originalState = true; + + const state = hasUploadPermissions( originalState, { + type: 'RECEIVE_UPLOAD_PERMISSIONS', + hasUploadPermissions: false, + } ); + + expect( state ).toEqual( false ); + } ); +} ); diff --git a/packages/viewport/CHANGELOG.md b/packages/viewport/CHANGELOG.md index 62418c8307350..73ded0598caa8 100644 --- a/packages/viewport/CHANGELOG.md +++ b/packages/viewport/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.1.0 (Unreleased) + +### Improvements + +- The initial viewport state is assigned synchronously, rather than on the next process tick. This should have an impact of fewer callbacks made to data subscribers. + ## 2.0.13 (2018-12-12) ## 2.0.12 (2018-11-20) diff --git a/packages/viewport/src/index.js b/packages/viewport/src/index.js index 754e0679cd09f..bd65fd4e6da09 100644 --- a/packages/viewport/src/index.js +++ b/packages/viewport/src/index.js @@ -76,3 +76,4 @@ window.addEventListener( 'orientationchange', setIsMatching ); // Set initial values setIsMatching(); +setIsMatching.flush(); From c123f1d80ffbdf4c7cd5f8fcf17ecd0dab42f54e Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 21 Dec 2018 05:22:08 -0500 Subject: [PATCH 073/691] Components: Render DropZone children only if dragging over element (#12852) * Components: Render DropZone children only if dragging over element * Update audio test snapshot * Update cover test snapshot * Update video test snapshot * Update gallery test snapshot --- .../audio/test/__snapshots__/index.js.snap | 26 +-------------- .../cover/test/__snapshots__/index.js.snap | 26 +-------------- .../gallery/test/__snapshots__/index.js.snap | 26 +-------------- .../video/test/__snapshots__/index.js.snap | 26 +-------------- packages/components/src/drop-zone/index.js | 33 ++++++++++--------- packages/components/src/drop-zone/style.scss | 5 --- 6 files changed, 21 insertions(+), 121 deletions(-) diff --git a/packages/block-library/src/audio/test/__snapshots__/index.js.snap b/packages/block-library/src/audio/test/__snapshots__/index.js.snap index e064799f81a1d..ec88637dd0579 100644 --- a/packages/block-library/src/audio/test/__snapshots__/index.js.snap +++ b/packages/block-library/src/audio/test/__snapshots__/index.js.snap @@ -33,31 +33,7 @@ exports[`core/audio block edit matches snapshot 1`] = ` > <div class="components-drop-zone" - > - <div - class="components-drop-zone__content" - > - <svg - aria-hidden="true" - class="dashicon dashicons-upload components-drop-zone__content-icon" - focusable="false" - height="40" - role="img" - viewBox="0 0 20 20" - width="40" - xmlns="http://www.w3.org/2000/svg" - > - <path - d="M8 14V8H5l5-6 5 6h-3v6H8zm-2 2v-6H4v8h12.01v-8H14v6H6z" - /> - </svg> - <span - class="components-drop-zone__content-text" - > - Drop files to upload - </span> - </div> - </div> + /> <div class="components-form-file-upload" > diff --git a/packages/block-library/src/cover/test/__snapshots__/index.js.snap b/packages/block-library/src/cover/test/__snapshots__/index.js.snap index 0592141c6088c..8ed40b5ba4265 100644 --- a/packages/block-library/src/cover/test/__snapshots__/index.js.snap +++ b/packages/block-library/src/cover/test/__snapshots__/index.js.snap @@ -33,31 +33,7 @@ exports[`core/cover block edit matches snapshot 1`] = ` > <div class="components-drop-zone" - > - <div - class="components-drop-zone__content" - > - <svg - aria-hidden="true" - class="dashicon dashicons-upload components-drop-zone__content-icon" - focusable="false" - height="40" - role="img" - viewBox="0 0 20 20" - width="40" - xmlns="http://www.w3.org/2000/svg" - > - <path - d="M8 14V8H5l5-6 5 6h-3v6H8zm-2 2v-6H4v8h12.01v-8H14v6H6z" - /> - </svg> - <span - class="components-drop-zone__content-text" - > - Drop files to upload - </span> - </div> - </div> + /> <div class="components-form-file-upload" > diff --git a/packages/block-library/src/gallery/test/__snapshots__/index.js.snap b/packages/block-library/src/gallery/test/__snapshots__/index.js.snap index 73fc964e1f4a4..221e6659c92ea 100644 --- a/packages/block-library/src/gallery/test/__snapshots__/index.js.snap +++ b/packages/block-library/src/gallery/test/__snapshots__/index.js.snap @@ -33,31 +33,7 @@ exports[`core/gallery block edit matches snapshot 1`] = ` > <div class="components-drop-zone" - > - <div - class="components-drop-zone__content" - > - <svg - aria-hidden="true" - class="dashicon dashicons-upload components-drop-zone__content-icon" - focusable="false" - height="40" - role="img" - viewBox="0 0 20 20" - width="40" - xmlns="http://www.w3.org/2000/svg" - > - <path - d="M8 14V8H5l5-6 5 6h-3v6H8zm-2 2v-6H4v8h12.01v-8H14v6H6z" - /> - </svg> - <span - class="components-drop-zone__content-text" - > - Drop files to upload - </span> - </div> - </div> + /> <div class="components-form-file-upload" > diff --git a/packages/block-library/src/video/test/__snapshots__/index.js.snap b/packages/block-library/src/video/test/__snapshots__/index.js.snap index 0f7157024c04c..0ae056f34736c 100644 --- a/packages/block-library/src/video/test/__snapshots__/index.js.snap +++ b/packages/block-library/src/video/test/__snapshots__/index.js.snap @@ -33,31 +33,7 @@ exports[`core/video block edit matches snapshot 1`] = ` > <div class="components-drop-zone" - > - <div - class="components-drop-zone__content" - > - <svg - aria-hidden="true" - class="dashicon dashicons-upload components-drop-zone__content-icon" - focusable="false" - height="40" - role="img" - viewBox="0 0 20 20" - width="40" - xmlns="http://www.w3.org/2000/svg" - > - <path - d="M8 14V8H5l5-6 5 6h-3v6H8zm-2 2v-6H4v8h12.01v-8H14v6H6z" - /> - </svg> - <span - class="components-drop-zone__content-text" - > - Drop files to upload - </span> - </div> - </div> + /> <div class="components-form-file-upload" > diff --git a/packages/components/src/drop-zone/index.js b/packages/components/src/drop-zone/index.js index 5a9960583ed19..07e384e22f947 100644 --- a/packages/components/src/drop-zone/index.js +++ b/packages/components/src/drop-zone/index.js @@ -71,24 +71,25 @@ class DropZoneComponent extends Component { [ `is-dragging-${ type }` ]: !! type, } ); - return ( - <div ref={ this.dropZoneElement } className={ classes }> + let children; + if ( isDraggingOverElement ) { + children = ( <div className="components-drop-zone__content"> - { [ - <Dashicon - key="icon" - icon="upload" - size="40" - className="components-drop-zone__content-icon" - />, - <span - key="text" - className="components-drop-zone__content-text" - > - { label ? label : __( 'Drop files to upload' ) } - </span>, - ] } + <Dashicon + icon="upload" + size="40" + className="components-drop-zone__content-icon" + /> + <span className="components-drop-zone__content-text"> + { label ? label : __( 'Drop files to upload' ) } + </span> </div> + ); + } + + return ( + <div ref={ this.dropZoneElement } className={ classes }> + { children } </div> ); } diff --git a/packages/components/src/drop-zone/style.scss b/packages/components/src/drop-zone/style.scss index 0103f4af01158..4b36a7fb9a2bc 100644 --- a/packages/components/src/drop-zone/style.scss +++ b/packages/components/src/drop-zone/style.scss @@ -19,10 +19,6 @@ &.is-dragging-over-element { background-color: rgba($blue-dark-900, 0.8); - - .components-drop-zone__content { - display: block; - } } } @@ -37,7 +33,6 @@ text-align: center; color: $white; transition: transform 0.2s ease-in-out; - display: none; } .components-drop-zone.is-dragging-over-element .components-drop-zone__content { From 1a7e32ee2345cd23efc969027285c6fd89ec1322 Mon Sep 17 00:00:00 2001 From: Courtney <courtneykburton@gmail.com> Date: Fri, 21 Dec 2018 10:55:50 -0500 Subject: [PATCH 074/691] Added MenuItem readme (#12485) * Added MenuItem readme * Updated spacing Updated the spacing to remove unneccesary lines. * Updated usage Updated details for how and when to use the MenuItem component. * Updated formatting errors I believe I updated formatting errors in spacing, extra spaces as well as backticks. * Fix up a few things! - Ticks - Restored an older version as there was a slight mixup - Whitespace - Other polish --- .../designers/menu-item.md | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 docs/designers-developers/designers/menu-item.md diff --git a/docs/designers-developers/designers/menu-item.md b/docs/designers-developers/designers/menu-item.md new file mode 100644 index 0000000000000..ebd9108c6ae4f --- /dev/null +++ b/docs/designers-developers/designers/menu-item.md @@ -0,0 +1,98 @@ +# MenuItem + +`MenuItem` is a component which renders a button intended to be used in combination with the `MenuGroup` component. + +![An image of a MenuItem being highlighted inside of a DropdownMenu component](https://wordpress.org/gutenberg/files/2018/11/MenuItem.png) + +1. MenuItem + +## Table of contents + +1. [Design guidelines](#design-guidelines) +2. [Development guidelines](#development-guidelines) +3. [Related components](#related-components) + +## Design guidelines + +### Usage + +A `MenuGroup` contiaining `MenuItem`s can be used within a `Dropdown`. A `MenuGroup` can also have other `MenuGroup`s within it so menus can be nested. + +## Development guidelines + +### Usage + +```jsx +import { MenuItem } from '@wordpress/components'; +import { withState } from '@wordpress/compose'; + +const MyMenuItem = withState( { + isActive: true, +} )( ( { isActive, setState } ) => ( + <MenuItem + icon={ isActive ? 'yes' : 'no' } + isSelected={ isActive } + onClick={ () => setState( state => ( { isActive: ! state.isActive } ) ) } + > + Toggle + </MenuItem> +) ); +``` + +### Props + +`MenuItem` supports the following props. Any additional props are passed through to the underlying [Button](../button) or [IconButton](../icon-button) component. + +#### `children` + +- Type: `WPElement` +- Required: No + +Element to render as child of button. + +Element + +#### `label` + +- Type: `string` +- Required: No + +String to use as primary button label text, applied as `aria-label`. Useful in cases where an `info` prop is passed, where `label` should be the minimal text of the button, described in further detail by `info`. + +Defaults to the value of `children`, if `children` is passed as a string. + +#### `info` + +- Type: `string` +- Required: No + +Text to use as description for button text. + +Refer to documentation for [`label`](#label). + +#### `icon` + +- Type: `string` +- Required: No + +Refer to documentation for [IconButton's `icon` prop](../icon-button/README.md#icon). + +#### `shortcut` + +- Type: `string` +- Required: No + +Refer to documentation for [Shortcut's `shortcut` prop](../shortcut/README.md#shortcut). + +#### `role` + +- Type: `string` +- Require: No +- Default: `'menuitem'` + +[Aria Spec](https://www.w3.org/TR/wai-aria-1.1/#aria-checked). If you need to have selectable menu items use `MenuItemRadio` for single select, and `MenuItemCheckbox` for multiselect. + +## Related components + +- The `DropdownMenu` displays a list of actions (each contained in a `MenuItem`, `MenuItemsChoice`, or `MenuGroup`) in a compact way. It appears in a `Popover` after the user has interacted with an element (a button or icon) or when they perform a specific action. +- `MenuItemsChoice` From 3f23b1a69a1919b37c67f971cdf98112eb18b5e7 Mon Sep 17 00:00:00 2001 From: Dave Whitley <drw158@gmail.com> Date: Fri, 21 Dec 2018 09:56:32 -0600 Subject: [PATCH 075/691] Radio documentation: updating radio readme (#11226) * Updating Radio readme Initial commit to update Radio readme. These changes add design documentation. * Adding readme images * Image path update Updating the path of the images. Making a small tweak to phrasing. * Updates so that only a single h1 is present * image alt text removal The alt text for these images has been removed because removing the images from the document doesn't affect comprehension. * Removing images * Removing images * Removing images * Adding images hosted on wordpress.org * docs: tweak some usage recommendations * Tweak spacing * docs: Use headers --- .../components/src/radio-control/README.md | 71 ++++++++++++++++--- 1 file changed, 63 insertions(+), 8 deletions(-) diff --git a/packages/components/src/radio-control/README.md b/packages/components/src/radio-control/README.md index 88f5379749059..25ccbcafaa88e 100644 --- a/packages/components/src/radio-control/README.md +++ b/packages/components/src/radio-control/README.md @@ -1,11 +1,61 @@ # RadioControl -RadioControl component is used to generate radio input fields. +Use radio buttons when you want users to select one option from a set, and you want to show them all the available options at once. +![](https://make.wordpress.org/design/files/2018/11/radio.png) +Selected and unselected radio buttons -## Usage +## Table of contents + +1. [Design guidelines](#design-guidelines) +2. [Development guidelines](#development-guidelines) +3. [Related components](#related-components) + +## Design guidelines + +### Usage + +#### When to use radio buttons + +Use radio buttons when you want users to: + +- Select a single option from a list. +- Expose all available options. + +If you have a list of available options that can be collapsed, consider using a dropdown menu instead, as dropdowns use less space. A country selection field, for instance, would be very large as a group of radio buttons and wouldn't help the user gain more context by seeing all options at once. + +#### Do + +![](https://make.wordpress.org/design/files/2018/11/radio-usage-do.png) +Use radio buttons when only one item can be selected from a list. + +#### Don’t + +![](https://make.wordpress.org/design/files/2018/11/radio-usage-dont.png) +Don’t use checkboxes when only one item can be selected from a list. Use radio buttons instead. + +#### Defaults + +When using radio buttons **one should be selected by default** (i.e., when the page loads, in the case of a web application). + +##### User control + +In most interactions, a user should be able to undo and redo their actions. With most selection controls you can un-choose a selection, but in this instance you cannot click or tap a selected radio button to deselect it—selecting is a final action. The finality isn’t conveyed when none are selected by default. Selecting a radio button by default communicates that the user is required to choose one in the set. + +##### Expediting tasks + +When one a choice in a set of radio buttons is the most desirable or frequently selected, it’s helpful to select it by default. Doing this reduces the interaction cost and can save the user time and clicks. + +##### The power of suggestion + +Designs with a radio button selected by default make a strong suggestion to the user. It can help them make the best decision and increase their confidence. (Use this guidance with caution, and only for good.) + +## Development guidelines + +### Usage Render a user interface to select the user type using radio inputs. + ```jsx import { RadioControl } from '@wordpress/components'; import { withState } from '@wordpress/compose'; @@ -26,32 +76,32 @@ const MyRadioControl = withState( { ) ); ``` -## Props +### Props The component accepts the following props: -### label +#### label If this property is added, a label will be generated using label property as the content. - Type: `String` - Required: No -### help +#### help If this property is added, a help text will be generated using help property as the content. - Type: `String` - Required: No -### selected +#### selected The value property of the currently selected option. - Type: `Object` - Required: No -### options +#### options An array of objects containing the following properties: * `label`: (string) The label to be shown to the user. @@ -60,9 +110,14 @@ An array of objects containing the following properties: - Type: `Array` - Required: No -### onChange +#### onChange A function that receives the value of the new option that is being selected as input. - Type: `function` - Required: Yes + +## Related components + +* To select one or more items from a set, use the `CheckboxControl` component. +* To toggle a single setting on or off, use the `ToggleControl` component. From d8f3ec309de365fd37f3d8d56bb9388f0305043b Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Fri, 21 Dec 2018 19:20:38 +0000 Subject: [PATCH 076/691] Add: End 2 End tests on the sidebar premalinkl for CPT's. (#13060) --- .../e2e/specs/sidebar-permalink-panel.test.js | 36 ++++++++++ test/e2e/test-plugins/custom-post-types.php | 68 +++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 test/e2e/test-plugins/custom-post-types.php diff --git a/test/e2e/specs/sidebar-permalink-panel.test.js b/test/e2e/specs/sidebar-permalink-panel.test.js index 03e24ff34ec3d..cff6ce3ea7bec 100644 --- a/test/e2e/specs/sidebar-permalink-panel.test.js +++ b/test/e2e/specs/sidebar-permalink-panel.test.js @@ -7,12 +7,21 @@ import { openDocumentSettingsSidebar, publishPost, } from '../support/utils'; +import { activatePlugin, deactivatePlugin } from '../support/plugins'; // This tests are not together with the remaining sidebar tests, // because we need to publish/save a post, to correctly test the permalink panel. // The sidebar test suit enforces that focus is never lost, but during save operations // the focus is lost and a new element is focused once the save is completed. describe( 'Sidebar Permalink Panel', () => { + beforeAll( async () => { + await activatePlugin( 'gutenberg-test-custom-post-types' ); + } ); + + afterAll( async () => { + await deactivatePlugin( 'gutenberg-test-custom-post-types' ); + } ); + it( 'should not render permalink sidebar panel while the post is new', async () => { await newPost(); await openDocumentSettingsSidebar(); @@ -32,4 +41,31 @@ describe( 'Sidebar Permalink Panel', () => { } ); expect( await findSidebarPanelWithTitle( 'Permalink' ) ).toBeUndefined(); } ); + + it( 'should not render link panel when post is publicly queryable but not public', async () => { + await newPost( { postType: 'public_q_not_public' } ); + await page.keyboard.type( 'aaaaa' ); + await publishPost(); + // Start editing again. + await page.type( '.editor-post-title__input', ' (Updated)' ); + expect( await findSidebarPanelWithTitle( 'Permalink' ) ).toBeUndefined(); + } ); + + it( 'should not render link panel when post is public but not publicly queryable', async () => { + await newPost( { postType: 'not_public_q_public' } ); + await page.keyboard.type( 'aaaaa' ); + await publishPost(); + // Start editing again. + await page.type( '.editor-post-title__input', ' (Updated)' ); + expect( await findSidebarPanelWithTitle( 'Permalink' ) ).toBeUndefined(); + } ); + + it( 'should render link panel when post is public and publicly queryable', async () => { + await newPost( { postType: 'public_q_public' } ); + await page.keyboard.type( 'aaaaa' ); + await publishPost(); + // Start editing again. + await page.type( '.editor-post-title__input', ' (Updated)' ); + expect( await findSidebarPanelWithTitle( 'Permalink' ) ).toBeDefined(); + } ); } ); diff --git a/test/e2e/test-plugins/custom-post-types.php b/test/e2e/test-plugins/custom-post-types.php new file mode 100644 index 0000000000000..6262c89f66d37 --- /dev/null +++ b/test/e2e/test-plugins/custom-post-types.php @@ -0,0 +1,68 @@ +<?php +/** + * Plugin Name: Gutenberg Test Custom Post Types + * Plugin URI: https://github.com/WordPress/gutenberg + * Author: Gutenberg Team + * + * @package gutenberg-test-custom-post-types + */ + +/** + * Registers a custom post type that is public_queryable but not public. + */ +function public_queryable_true_public_false_cpt() { + $public_queryable_true_public_false = 'public_q_not_public'; + register_post_type( + $public_queryable_true_public_false, + array( + 'label' => 'PublicQNotPublic', + 'show_in_rest' => true, + 'public' => false, + 'publicly_queryable' => true, + 'supports' => array( 'title', 'editor', 'revisions' ), + 'show_ui' => true, + 'show_in_menu' => true, + ) + ); +} +add_action( 'init', 'public_queryable_true_public_false_cpt' ); + +/** + * Registers a custom post type that is public but not public_queryable. + */ +function public_queryable_false_public_true_cpt() { + $public_queryable_false_public_true = 'not_public_q_public'; + register_post_type( + $public_queryable_false_public_true, + array( + 'label' => 'NotPublicQPublic', + 'show_in_rest' => true, + 'public' => true, + 'publicly_queryable' => false, + 'supports' => array( 'title', 'editor', 'revisions' ), + 'show_ui' => true, + 'show_in_menu' => true, + ) + ); +} +add_action( 'init', 'public_queryable_false_public_true_cpt' ); + +/** + * Registers a custom post type that is public and public_queryable. + */ +function public_queryable_true_public_true_cpt() { + $public_queryable_true_public_true = 'public_q_public'; + register_post_type( + $public_queryable_true_public_true, + array( + 'label' => 'PublicQueryPublic', + 'show_in_rest' => true, + 'public' => true, + 'publicly_queryable' => true, + 'supports' => array( 'title', 'editor', 'revisions' ), + 'show_ui' => true, + 'show_in_menu' => true, + ) + ); +} +add_action( 'init', 'public_queryable_true_public_true_cpt' ); From e9f6a6bbcdea251d781cdd2ef9b2cf46b875a169 Mon Sep 17 00:00:00 2001 From: Joen Asmussen <joen@automattic.com> Date: Mon, 24 Dec 2018 10:15:19 +0100 Subject: [PATCH 077/691] Try: Improve scrolling of navigation menu on small screens (#12644) * Try: Improve scrolling of navigation menu on small screens We scroll the editing canvas, the inspector sidebar, and the block library independently at desktop breakpoints. We do this so that the sidebar inspector can stay in view even if you have scrolled far down the editing canvas, and to avoid scroll bleed. However because the navigation sidebar (on the left) has flyout menus on the desktop breakpoints, we can't yet scroll this one separately. If a user has a bunch of plugins installed that add menu items, or a small screen, these manu items might go beyond the visible height of the viewport. To make these accessible regardless, when this happens the `body` element scrolls to let you reach them. In this situation, there is currently an issue where the editing canvas might scroll out of view when you scroll to the bottom of the sidebar. This PR improves that situation by making the editing canvas `position: fixed;`, same as the sidebar is. This ensures that the entire editor scrolls with you down the page, as you scroll the `body` content. This needs a good testing. `position: fixed;` does not inherit from a relative parent, which means we have to specifiy a matrix of left margins to accommodate for the different configurations of the navigation menu: auto-collapsed, manually collapsed, or the default width. To test, please verify that everything works as intended. Please test: - All breakpoints beyond the 600px small breakpoint. - Fullscreen and not fullscreen modes. - With the navigation menu auto collapsing, and explicitly collapsed. - With the inspector sidebar present or hidden. - With metaboxes present and not present. * Add flex rule to fix IE issue. * Address feedback, and fix test issue. * Try making the test more reliable using small columns --- .../src/components/layout/style.scss | 59 +++++++++++++++++-- .../__snapshots__/writing-flow.test.js.snap | 4 +- test/e2e/specs/writing-flow.test.js | 11 ++-- 3 files changed, 60 insertions(+), 14 deletions(-) diff --git a/packages/edit-post/src/components/layout/style.scss b/packages/edit-post/src/components/layout/style.scss index 0cd4a03af0dfe..7e31582af07bd 100644 --- a/packages/edit-post/src/components/layout/style.scss +++ b/packages/edit-post/src/components/layout/style.scss @@ -28,7 +28,7 @@ } } - // on mobile, toolbars behave differently + // On mobile, toolbars behave differently. @include break-small { padding-top: $header-height; } @@ -38,7 +38,7 @@ padding-top: $block-controls-height; } - // on mobile, toolbars behave differently + // On mobile, toolbars behave differently. @include break-small { padding-top: $header-height + $block-toolbar-height; @@ -68,11 +68,60 @@ } .edit-post-layout__content { - position: relative; display: flex; - min-height: 100%; flex-direction: column; + // These rules are specific to mobile and small breakpoints. + min-height: 100%; + position: relative; + + // We scroll the main editing canvas, the sidebar, and the block library separately to prevent scroll bleed. + // Because the navigation sidebar menu has "flyout" menus, we can't yet, scroll that independently, but as soon + // as we can, we should simplify these rules. + // In the mean time, if a user has a small screen and lots of plugin-added menu items in the navigation menu, + // they have to be able to scroll. To accommodate the flyout menus, we scroll the `body` element for this. + @include break-medium() { + // Because the body element scrolls the navigation sidebar, we have to use position fixed here. + // Otherwise you would scroll the editing canvas out of view when you scroll the sidebar. + position: fixed; + bottom: 0; + left: 0; + right: 0; + + // Because this is scoped to break-medium and larger, the admin-bar is always this height. + top: $header-height + $admin-bar-height; + + // Sadly, `position: fixed;` do not inherit boundaries from a relative parent. Due to that we have to compensate using `calc`. + min-height: calc(100% - #{ $header-height + $admin-bar-height }); + height: auto; // This overrides the 100% height the element inherits from line 3. + + // In this matrix, we compensate for any configurations for the presence and width of the navigation sidebar. + // This is similar to the code in the @editor-left mixin, but uses margins instead. + // Because we are beyond the medium breakpoint, we only have to worry about folded, auto-folded, and default. + margin-left: $admin-sidebar-width; + + // Auto fold is when on smaller breakpoints, nav menu auto colllapses. + body.auto-fold & { + margin-left: $admin-sidebar-width-collapsed; + + @include break-large() { + margin-left: $admin-sidebar-width; + } + } + + // Sidebar manually collapsed. + body.folded & { + margin-left: $admin-sidebar-width-collapsed; + } + + // Undo the above rules for fullscreen mode. + body.is-fullscreen-mode & { + margin-left: 0 !important; + position: relative; + top: inherit; + } + } + // Pad the scroll box so content on the bottom can be scrolled up. padding-bottom: 50vh; @include break-small { @@ -95,7 +144,7 @@ } .edit-post-visual-editor { - flex-grow: 1; + flex: 1 1 auto; // In IE11 flex-basis: 100% cause a bug where the metaboxes area overlap with the content area. // But it works as expected without it. diff --git a/test/e2e/specs/__snapshots__/writing-flow.test.js.snap b/test/e2e/specs/__snapshots__/writing-flow.test.js.snap index a33dda228193e..3852f38781625 100644 --- a/test/e2e/specs/__snapshots__/writing-flow.test.js.snap +++ b/test/e2e/specs/__snapshots__/writing-flow.test.js.snap @@ -8,13 +8,13 @@ exports[`adding blocks Should navigate inner blocks with arrow keys 1`] = ` <!-- wp:columns --> <div class=\\"wp-block-columns has-2-columns\\"><!-- wp:column --> <div class=\\"wp-block-column\\"><!-- wp:paragraph --> -<p>First column paragraph</p> +<p>First col</p> <!-- /wp:paragraph --></div> <!-- /wp:column --> <!-- wp:column --> <div class=\\"wp-block-column\\"><!-- wp:paragraph --> -<p>Second column paragraph</p> +<p>Second col</p> <!-- /wp:paragraph --></div> <!-- /wp:column --></div> <!-- /wp:columns --> diff --git a/test/e2e/specs/writing-flow.test.js b/test/e2e/specs/writing-flow.test.js index 7c4197755bab8..064eaf681d6a8 100644 --- a/test/e2e/specs/writing-flow.test.js +++ b/test/e2e/specs/writing-flow.test.js @@ -23,14 +23,14 @@ describe( 'adding blocks', () => { await page.keyboard.press( 'Enter' ); await page.keyboard.type( '/columns' ); await page.keyboard.press( 'Enter' ); - await page.keyboard.type( 'First column paragraph' ); + await page.keyboard.type( 'First col' ); // Arrow down should navigate through layouts in columns block (to // its default appender). Two key presses are required since the first // will land user on the Column wrapper block. await page.keyboard.press( 'ArrowDown' ); await page.keyboard.press( 'ArrowDown' ); - await page.keyboard.type( 'Second column paragraph' ); + await page.keyboard.type( 'Second col' ); // Arrow down from last of layouts exits nested context to default // appender of root level. @@ -40,22 +40,19 @@ describe( 'adding blocks', () => { // Arrow up into nested context focuses last text input await page.keyboard.press( 'ArrowUp' ); activeElementText = await page.evaluate( () => document.activeElement.textContent ); - expect( activeElementText ).toBe( 'Second column paragraph' ); + expect( activeElementText ).toBe( 'Second col' ); // Arrow up in inner blocks should navigate through (1) column wrapper, // (2) text fields. - // We need to arrow up key presses in the paragraph block because it shows up in two lines. - await page.keyboard.press( 'ArrowUp' ); await page.keyboard.press( 'ArrowUp' ); await page.keyboard.press( 'ArrowUp' ); activeElementText = await page.evaluate( () => document.activeElement.textContent ); - expect( activeElementText ).toBe( 'First column paragraph' ); + expect( activeElementText ).toBe( 'First col' ); // Arrow up from first text field in nested context focuses column and // columns wrappers before escaping out. let activeElementBlockType; await page.keyboard.press( 'ArrowUp' ); - await page.keyboard.press( 'ArrowUp' ); activeElementBlockType = await page.evaluate( () => ( document.activeElement.getAttribute( 'data-type' ) ) ); From 47e69f3ad986695ed2d181c73f0ded88844ed855 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Mon, 24 Dec 2018 04:29:24 -0500 Subject: [PATCH 078/691] Performance: Move unused variable initializations after early return (#12827) * Performance: Move unused variable initializations after early return * Small tweak about JS var scoping * Fix block switcher --- bin/packages/watch.js | 3 +-- packages/block-library/src/cover/index.js | 20 +++++++++---------- packages/block-library/src/image/edit.js | 2 +- packages/blocks/src/api/factory.js | 4 ++-- packages/blocks/src/api/serializer.js | 7 ++++--- packages/dom/src/dom.js | 2 +- .../src/components/post-saved-state/index.js | 2 +- .../editor/src/components/rich-text/index.js | 5 ++--- .../editor/src/editor-styles/ast/parse.js | 2 ++ .../editor-styles/ast/stringify/identity.js | 2 ++ packages/editor/src/store/effects.js | 2 +- packages/editor/src/store/effects/posts.js | 5 +++-- packages/editor/src/store/selectors.js | 11 +++++----- 13 files changed, 36 insertions(+), 31 deletions(-) diff --git a/bin/packages/watch.js b/bin/packages/watch.js index ebf112df00c89..d12d5410f2b7f 100644 --- a/bin/packages/watch.js +++ b/bin/packages/watch.js @@ -34,12 +34,11 @@ getPackages().forEach( ( p ) => { try { fs.accessSync( srcDir, fs.F_OK ); fs.watch( path.resolve( p, 'src' ), { recursive: true }, ( event, filename ) => { - const filePath = path.resolve( srcDir, filename ); - if ( ! isSourceFile( filename ) ) { return; } + const filePath = path.resolve( srcDir, filename ); if ( ( event === 'change' || event === 'rename' ) && exists( filePath ) ) { // eslint-disable-next-line no-console console.log( chalk.green( '->' ), `${ event }: ${ filename }` ); diff --git a/packages/block-library/src/cover/index.js b/packages/block-library/src/cover/index.js index 8dff936dc440d..09df032d9418d 100644 --- a/packages/block-library/src/cover/index.js +++ b/packages/block-library/src/cover/index.js @@ -235,16 +235,6 @@ export const settings = { backgroundColor: overlayColor.color, }; - const classes = classnames( - className, - contentAlign !== 'center' && `has-${ contentAlign }-content`, - dimRatioToClass( dimRatio ), - { - 'has-background-dim': dimRatio !== 0, - 'has-parallax': hasParallax, - } - ); - const controls = ( <Fragment> <BlockControls> @@ -346,6 +336,16 @@ export const settings = { ); } + const classes = classnames( + className, + contentAlign !== 'center' && `has-${ contentAlign }-content`, + dimRatioToClass( dimRatio ), + { + 'has-background-dim': dimRatio !== 0, + 'has-parallax': hasParallax, + } + ); + return ( <Fragment> { controls } diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index f7fdd73a9aaa5..2a3a2b9530a36 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -370,7 +370,6 @@ class ImageEdit extends Component { linkTarget, } = attributes; const isExternal = isExternalImage( id, url ); - const imageSizeOptions = this.getImageSizeOptions(); let toolbarEditButton; if ( url ) { @@ -446,6 +445,7 @@ class ImageEdit extends Component { const isResizable = [ 'wide', 'full' ].indexOf( align ) === -1 && isLargeViewport; const isLinkURLInputReadOnly = linkDestination !== LINK_DESTINATION_CUSTOM; + const imageSizeOptions = this.getImageSizeOptions(); const getInspectorControls = ( imageWidth, imageHeight ) => ( <InspectorControls> diff --git a/packages/blocks/src/api/factory.js b/packages/blocks/src/api/factory.js index f51dfb7e6576d..9068a8b3d8cd3 100644 --- a/packages/blocks/src/api/factory.js +++ b/packages/blocks/src/api/factory.js @@ -116,10 +116,9 @@ const isPossibleTransformForSource = ( transform, direction, blocks ) => { if ( isEmpty( blocks ) ) { return false; } - const isMultiBlock = blocks.length > 1; - const sourceBlock = first( blocks ); // If multiple blocks are selected, only multi block transforms are allowed. + const isMultiBlock = blocks.length > 1; const isValidForMultiBlocks = ! isMultiBlock || transform.isMultiBlock; if ( ! isValidForMultiBlocks ) { return false; @@ -132,6 +131,7 @@ const isPossibleTransformForSource = ( transform, direction, blocks ) => { } // Check if the transform's block name matches the source block only if this is a transform 'from'. + const sourceBlock = first( blocks ); const hasMatchingName = direction !== 'from' || transform.blocks.indexOf( sourceBlock.name ) !== -1; if ( ! hasMatchingName ) { return false; diff --git a/packages/blocks/src/api/serializer.js b/packages/blocks/src/api/serializer.js index 443b68a14a593..74219369a2917 100644 --- a/packages/blocks/src/api/serializer.js +++ b/packages/blocks/src/api/serializer.js @@ -261,17 +261,18 @@ export function getCommentDelimitedContent( rawBlockName, attributes, content ) */ export function serializeBlock( block ) { const blockName = block.name; - const blockType = getBlockType( blockName ); const saveContent = getBlockContent( block ); - const saveAttributes = getCommentAttributes( blockType, block.attributes ); switch ( blockName ) { case getFreeformContentHandlerName(): case getUnregisteredTypeHandlerName(): return saveContent; - default: + default: { + const blockType = getBlockType( blockName ); + const saveAttributes = getCommentAttributes( blockType, block.attributes ); return getCommentDelimitedContent( blockName, saveAttributes, saveContent ); + } } } diff --git a/packages/dom/src/dom.js b/packages/dom/src/dom.js index c718cdbebe190..7bfe0d101ddb9 100644 --- a/packages/dom/src/dom.js +++ b/packages/dom/src/dom.js @@ -380,7 +380,6 @@ export function placeCaretAtVerticalEdge( container, isReverse, rect, mayUseScro const editableRect = container.getBoundingClientRect(); const x = rect.left; const y = isReverse ? ( editableRect.bottom - buffer ) : ( editableRect.top + buffer ); - const selection = window.getSelection(); let range = hiddenCaretRangeFromPoint( document, x, y, container ); @@ -413,6 +412,7 @@ export function placeCaretAtVerticalEdge( container, isReverse, rect, mayUseScro } } + const selection = window.getSelection(); selection.removeAllRanges(); selection.addRange( range ); container.focus(); diff --git a/packages/editor/src/components/post-saved-state/index.js b/packages/editor/src/components/post-saved-state/index.js index 21b047c5f1079..26716757bc588 100644 --- a/packages/editor/src/components/post-saved-state/index.js +++ b/packages/editor/src/components/post-saved-state/index.js @@ -57,7 +57,6 @@ export class PostSavedState extends Component { isLargeViewport, } = this.props; const { forceSavedMessage } = this.state; - const hasPublishAction = get( post, [ '_links', 'wp:action-publish' ], false ); if ( isSaving ) { // TODO: Classes generation should be common across all return // paths of this function, including proper naming convention for @@ -93,6 +92,7 @@ export class PostSavedState extends Component { // Once the post has been submitted for review this button // is not needed for the contributor role. + const hasPublishAction = get( post, [ '_links', 'wp:action-publish' ], false ); if ( ! hasPublishAction && isPending ) { return null; } diff --git a/packages/editor/src/components/rich-text/index.js b/packages/editor/src/components/rich-text/index.js index bc814d8ebff9c..72d86b8441556 100644 --- a/packages/editor/src/components/rich-text/index.js +++ b/packages/editor/src/components/rich-text/index.js @@ -215,7 +215,6 @@ export class RichText extends Component { items = isNil( items ) ? [] : items; files = isNil( files ) ? [] : files; - const item = find( [ ...items, ...files ], ( { type } ) => /^image\/(?:jpe?g|png|gif)$/.test( type ) ); let plainText = ''; let html = ''; @@ -244,6 +243,7 @@ export class RichText extends Component { // Only process file if no HTML is present. // Note: a pasted file may have the URL as plain text. + const item = find( [ ...items, ...files ], ( { type } ) => /^image\/(?:jpe?g|png|gif)$/.test( type ) ); if ( item && ! html ) { const file = item.getAsFile ? item.getAsFile() : item; const content = pasteHandler( { @@ -605,12 +605,11 @@ export class RichText extends Component { * @param {Object} context The context for splitting. */ splitContent( blocks = [], context = {} ) { - const record = this.createRecord(); - if ( ! this.onSplit ) { return; } + const record = this.createRecord(); let [ before, after ] = split( record ); // In case split occurs at the trailing or leading edge of the field, diff --git a/packages/editor/src/editor-styles/ast/parse.js b/packages/editor/src/editor-styles/ast/parse.js index 707340ac93854..0c91c972f7ba3 100644 --- a/packages/editor/src/editor-styles/ast/parse.js +++ b/packages/editor/src/editor-styles/ast/parse.js @@ -1,3 +1,5 @@ +/* eslint-disable @wordpress/no-unused-vars-before-return */ + // Adapted from https://github.com/reworkcss/css // because we needed to remove source map support. diff --git a/packages/editor/src/editor-styles/ast/stringify/identity.js b/packages/editor/src/editor-styles/ast/stringify/identity.js index a26a800ca9e76..87de0945b9f2d 100644 --- a/packages/editor/src/editor-styles/ast/stringify/identity.js +++ b/packages/editor/src/editor-styles/ast/stringify/identity.js @@ -1,3 +1,5 @@ +/* eslint-disable @wordpress/no-unused-vars-before-return */ + // Adapted from https://github.com/reworkcss/css // because we needed to remove source map support. diff --git a/packages/editor/src/store/effects.js b/packages/editor/src/store/effects.js index 7c05730540dbe..18e753d3f70bf 100644 --- a/packages/editor/src/store/effects.js +++ b/packages/editor/src/store/effects.js @@ -158,7 +158,6 @@ export default { const state = store.getState(); const [ firstBlockClientId, secondBlockClientId ] = action.blocks; const blockA = getBlock( state, firstBlockClientId ); - const blockB = getBlock( state, secondBlockClientId ); const blockType = getBlockType( blockA.name ); // Only focus the previous block if it's not mergeable @@ -169,6 +168,7 @@ export default { // We can only merge blocks with similar types // thus, we transform the block to merge first + const blockB = getBlock( state, secondBlockClientId ); const blocksWithTheSameType = blockA.name === blockB.name ? [ blockB ] : switchToBlockType( blockB, blockA.name ); diff --git a/packages/editor/src/store/effects/posts.js b/packages/editor/src/store/effects/posts.js index ed5f596b167d4..4469d8dbfd326 100644 --- a/packages/editor/src/store/effects/posts.js +++ b/packages/editor/src/store/effects/posts.js @@ -49,8 +49,6 @@ const TRASH_POST_NOTICE_ID = 'TRASH_POST_NOTICE_ID'; export const requestPostUpdate = async ( action, store ) => { const { dispatch, getState } = store; const state = getState(); - const post = getCurrentPost( state ); - const isAutosave = !! action.options.isAutosave; // Prevent save if not saveable. // We don't check for dirtiness here as this can be overriden in the UI. @@ -59,6 +57,7 @@ export const requestPostUpdate = async ( action, store ) => { } let edits = getPostEdits( state ); + const isAutosave = !! action.options.isAutosave; if ( isAutosave ) { edits = pick( edits, [ 'title', 'content', 'excerpt' ] ); } @@ -78,6 +77,8 @@ export const requestPostUpdate = async ( action, store ) => { edits = { status: 'draft', ...edits }; } + const post = getCurrentPost( state ); + let toSend = { ...edits, content: getEditedPostContent( state ), diff --git a/packages/editor/src/store/selectors.js b/packages/editor/src/store/selectors.js index 3a7b34a0bdd83..06ee816ce1cc0 100644 --- a/packages/editor/src/store/selectors.js +++ b/packages/editor/src/store/selectors.js @@ -290,8 +290,6 @@ export function getCurrentPostAttribute( state, attributeName ) { * @return {*} Post attribute value. */ export function getEditedPostAttribute( state, attributeName ) { - const edits = getPostEdits( state ); - // Special cases switch ( attributeName ) { case 'content': @@ -299,6 +297,7 @@ export function getEditedPostAttribute( state, attributeName ) { } // Fall back to saved post value if not edited. + const edits = getPostEdits( state ); if ( ! edits.hasOwnProperty( attributeName ) ) { return getCurrentPostAttribute( state, attributeName ); } @@ -349,13 +348,15 @@ export function getAutosaveAttribute( state, attributeName ) { */ export function getEditedPostVisibility( state ) { const status = getEditedPostAttribute( state, 'status' ); - const password = getEditedPostAttribute( state, 'password' ); - if ( status === 'private' ) { return 'private'; - } else if ( password ) { + } + + const password = getEditedPostAttribute( state, 'password' ); + if ( password ) { return 'password'; } + return 'public'; } From 80cdc3a3728709c0dede170c01f7e49d17f449b5 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Tue, 25 Dec 2018 09:47:13 +0100 Subject: [PATCH 079/691] Avoid PHP notices in e2e tests plugins (#13094) --- test/e2e/test-plugins/align-hook.php | 31 +++-- test/e2e/test-plugins/block-icons.php | 35 +++-- .../container-without-paragraph.php | 29 +++-- .../test-plugins/deprecated-node-matcher.php | 31 +++-- test/e2e/test-plugins/hooks-api.php | 35 +++-- .../test-plugins/inner-blocks-templates.php | 35 +++-- test/e2e/test-plugins/plugins-api.php | 123 +++++++++--------- 7 files changed, 184 insertions(+), 135 deletions(-) diff --git a/test/e2e/test-plugins/align-hook.php b/test/e2e/test-plugins/align-hook.php index 8ecc005651e11..89435541e3d2c 100644 --- a/test/e2e/test-plugins/align-hook.php +++ b/test/e2e/test-plugins/align-hook.php @@ -7,15 +7,22 @@ * @package gutenberg-test-align-hook */ -wp_enqueue_script( - 'gutenberg-test-align-hook', - plugins_url( 'align-hook/index.js', __FILE__ ), - array( - 'wp-blocks', - 'wp-element', - 'wp-editor', - 'wp-i18n', - ), - filemtime( plugin_dir_path( __FILE__ ) . 'align-hook/index.js' ), - true -); +/** + * Registers a custom script for the plugin. + */ +function enqueue_align_plugin_script() { + wp_enqueue_script( + 'gutenberg-test-align-hook', + plugins_url( 'align-hook/index.js', __FILE__ ), + array( + 'wp-blocks', + 'wp-element', + 'wp-editor', + 'wp-i18n', + ), + filemtime( plugin_dir_path( __FILE__ ) . 'align-hook/index.js' ), + true + ); +} + +add_action( 'init', 'enqueue_align_plugin_script' ); diff --git a/test/e2e/test-plugins/block-icons.php b/test/e2e/test-plugins/block-icons.php index eb97b52cf9eb0..6f79bf7d7e1d6 100644 --- a/test/e2e/test-plugins/block-icons.php +++ b/test/e2e/test-plugins/block-icons.php @@ -7,17 +7,24 @@ * @package gutenberg-test-block-icons */ -wp_enqueue_script( - 'gutenberg-test-block-icons', - plugins_url( 'block-icons/index.js', __FILE__ ), - array( - 'wp-blocks', - 'wp-components', - 'wp-element', - 'wp-editor', - 'wp-hooks', - 'wp-i18n', - ), - filemtime( plugin_dir_path( __FILE__ ) . 'block-icons/index.js' ), - true -); +/** + * Registers a custom script for the plugin. + */ +function enqueue_block_icons_plugin_script() { + wp_enqueue_script( + 'gutenberg-test-block-icons', + plugins_url( 'block-icons/index.js', __FILE__ ), + array( + 'wp-blocks', + 'wp-components', + 'wp-element', + 'wp-editor', + 'wp-hooks', + 'wp-i18n', + ), + filemtime( plugin_dir_path( __FILE__ ) . 'block-icons/index.js' ), + true + ); +} + +add_action( 'init', 'enqueue_block_icons_plugin_script' ); diff --git a/test/e2e/test-plugins/container-without-paragraph.php b/test/e2e/test-plugins/container-without-paragraph.php index e9876f4c95b88..315e3eb4258e4 100644 --- a/test/e2e/test-plugins/container-without-paragraph.php +++ b/test/e2e/test-plugins/container-without-paragraph.php @@ -7,14 +7,21 @@ * @package gutenberg-test-container-without-paragraph */ -wp_enqueue_script( - 'gutenberg-test-container-without-paragraph', - plugins_url( 'container-without-paragraph/index.js', __FILE__ ), - array( - 'wp-blocks', - 'wp-element', - 'wp-editor', - ), - filemtime( plugin_dir_path( __FILE__ ) . 'container-without-paragraph/index.js' ), - true -); +/** + * Registers a custom script for the plugin. + */ +function enqueue_container_without_paragraph_plugin_script() { + wp_enqueue_script( + 'gutenberg-test-container-without-paragraph', + plugins_url( 'container-without-paragraph/index.js', __FILE__ ), + array( + 'wp-blocks', + 'wp-element', + 'wp-editor', + ), + filemtime( plugin_dir_path( __FILE__ ) . 'container-without-paragraph/index.js' ), + true + ); +} + +add_action( 'init', 'enqueue_container_without_paragraph_plugin_script' ); diff --git a/test/e2e/test-plugins/deprecated-node-matcher.php b/test/e2e/test-plugins/deprecated-node-matcher.php index bd72e4aa15157..964cc7eec9c36 100644 --- a/test/e2e/test-plugins/deprecated-node-matcher.php +++ b/test/e2e/test-plugins/deprecated-node-matcher.php @@ -7,15 +7,22 @@ * @package gutenberg-test-deprecated-node-matcher */ -wp_enqueue_script( - 'gutenberg-test-deprecated-node-matcher', - plugins_url( 'deprecated-node-matcher/index.js', __FILE__ ), - array( - 'lodash', - 'wp-blocks', - 'wp-element', - 'wp-editor', - ), - filemtime( plugin_dir_path( __FILE__ ) . 'deprecated-node-matcher/index.js' ), - true -); +/** + * Registers a custom script for the plugin. + */ +function enqueue_deprecated_node_matcher_plugin_script() { + wp_enqueue_script( + 'gutenberg-test-deprecated-node-matcher', + plugins_url( 'deprecated-node-matcher/index.js', __FILE__ ), + array( + 'lodash', + 'wp-blocks', + 'wp-element', + 'wp-editor', + ), + filemtime( plugin_dir_path( __FILE__ ) . 'deprecated-node-matcher/index.js' ), + true + ); +} + +add_action( 'init', 'enqueue_deprecated_node_matcher_plugin_script' ); diff --git a/test/e2e/test-plugins/hooks-api.php b/test/e2e/test-plugins/hooks-api.php index 38f44860716dd..f3533d8d86748 100644 --- a/test/e2e/test-plugins/hooks-api.php +++ b/test/e2e/test-plugins/hooks-api.php @@ -7,17 +7,24 @@ * @package gutenberg-test-hooks-api */ -wp_enqueue_script( - 'gutenberg-test-hooks-api', - plugins_url( 'hooks-api/index.js', __FILE__ ), - array( - 'wp-blocks', - 'wp-components', - 'wp-element', - 'wp-editor', - 'wp-hooks', - 'wp-i18n', - ), - filemtime( plugin_dir_path( __FILE__ ) . 'hooks-api/index.js' ), - true -); +/** + * Registers a custom script for the plugin. + */ +function enqueue_hooks_plugin_script() { + wp_enqueue_script( + 'gutenberg-test-hooks-api', + plugins_url( 'hooks-api/index.js', __FILE__ ), + array( + 'wp-blocks', + 'wp-components', + 'wp-element', + 'wp-editor', + 'wp-hooks', + 'wp-i18n', + ), + filemtime( plugin_dir_path( __FILE__ ) . 'hooks-api/index.js' ), + true + ); +} + +add_action( 'init', 'enqueue_hooks_plugin_script' ); diff --git a/test/e2e/test-plugins/inner-blocks-templates.php b/test/e2e/test-plugins/inner-blocks-templates.php index 9454cfc7cb40a..792a2dc0234d1 100644 --- a/test/e2e/test-plugins/inner-blocks-templates.php +++ b/test/e2e/test-plugins/inner-blocks-templates.php @@ -7,17 +7,24 @@ * @package gutenberg-test-inner-blocks-templates */ -wp_enqueue_script( - 'gutenberg-test-inner-blocks-templates', - plugins_url( 'inner-blocks-templates/index.js', __FILE__ ), - array( - 'wp-blocks', - 'wp-components', - 'wp-element', - 'wp-editor', - 'wp-hooks', - 'wp-i18n', - ), - filemtime( plugin_dir_path( __FILE__ ) . 'inner-blocks-templates/index.js' ), - true -); +/** + * Registers a custom script for the plugin. + */ +function enqueue_container_without_paragraph_plugin_script() { + wp_enqueue_script( + 'gutenberg-test-inner-blocks-templates', + plugins_url( 'inner-blocks-templates/index.js', __FILE__ ), + array( + 'wp-blocks', + 'wp-components', + 'wp-element', + 'wp-editor', + 'wp-hooks', + 'wp-i18n', + ), + filemtime( plugin_dir_path( __FILE__ ) . 'inner-blocks-templates/index.js' ), + true + ); +} + +add_action( 'init', 'enqueue_container_without_paragraph_plugin_script' ); diff --git a/test/e2e/test-plugins/plugins-api.php b/test/e2e/test-plugins/plugins-api.php index 64a8cb2364448..75c1180e44afc 100644 --- a/test/e2e/test-plugins/plugins-api.php +++ b/test/e2e/test-plugins/plugins-api.php @@ -7,64 +7,71 @@ * @package gutenberg-test-plugin-plugins-api */ -wp_enqueue_script( - 'gutenberg-test-plugins-api-post-status-info', - plugins_url( 'plugins-api/post-status-info.js', __FILE__ ), - array( - 'wp-edit-post', - 'wp-element', - 'wp-i18n', - 'wp-plugins', - ), - filemtime( plugin_dir_path( __FILE__ ) . 'plugins-api/post-status-info.js' ), - true -); +/** + * Registers custom scripts for the plugin. + */ +function enqueue_plugins_api_plugin_scripts() { + wp_enqueue_script( + 'gutenberg-test-plugins-api-post-status-info', + plugins_url( 'plugins-api/post-status-info.js', __FILE__ ), + array( + 'wp-edit-post', + 'wp-element', + 'wp-i18n', + 'wp-plugins', + ), + filemtime( plugin_dir_path( __FILE__ ) . 'plugins-api/post-status-info.js' ), + true + ); + + wp_enqueue_script( + 'gutenberg-test-plugins-api-publish-pane;', + plugins_url( 'plugins-api/publish-panel.js', __FILE__ ), + array( + 'wp-edit-post', + 'wp-element', + 'wp-i18n', + 'wp-plugins', + ), + filemtime( plugin_dir_path( __FILE__ ) . 'plugins-api/publish-panel.js' ), + true + ); -wp_enqueue_script( - 'gutenberg-test-plugins-api-publish-pane;', - plugins_url( 'plugins-api/publish-panel.js', __FILE__ ), - array( - 'wp-edit-post', - 'wp-element', - 'wp-i18n', - 'wp-plugins', - ), - filemtime( plugin_dir_path( __FILE__ ) . 'plugins-api/publish-panel.js' ), - true -); + wp_enqueue_script( + 'gutenberg-test-plugins-api-sidebar', + plugins_url( 'plugins-api/sidebar.js', __FILE__ ), + array( + 'wp-components', + 'wp-compose', + 'wp-data', + 'wp-edit-post', + 'wp-editor', + 'wp-element', + 'wp-i18n', + 'wp-plugins', + 'wp-annotations', + ), + filemtime( plugin_dir_path( __FILE__ ) . 'plugins-api/sidebar.js' ), + true + ); -wp_enqueue_script( - 'gutenberg-test-plugins-api-sidebar', - plugins_url( 'plugins-api/sidebar.js', __FILE__ ), - array( - 'wp-components', - 'wp-compose', - 'wp-data', - 'wp-edit-post', - 'wp-editor', - 'wp-element', - 'wp-i18n', - 'wp-plugins', - 'wp-annotations', - ), - filemtime( plugin_dir_path( __FILE__ ) . 'plugins-api/sidebar.js' ), - true -); + wp_enqueue_script( + 'gutenberg-test-annotations-sidebar', + plugins_url( 'plugins-api/annotations-sidebar.js', __FILE__ ), + array( + 'wp-components', + 'wp-compose', + 'wp-data', + 'wp-edit-post', + 'wp-editor', + 'wp-element', + 'wp-i18n', + 'wp-plugins', + 'wp-annotations', + ), + filemtime( plugin_dir_path( __FILE__ ) . 'plugins-api/annotations-sidebar.js' ), + true + ); +} -wp_enqueue_script( - 'gutenberg-test-annotations-sidebar', - plugins_url( 'plugins-api/annotations-sidebar.js', __FILE__ ), - array( - 'wp-components', - 'wp-compose', - 'wp-data', - 'wp-edit-post', - 'wp-editor', - 'wp-element', - 'wp-i18n', - 'wp-plugins', - 'wp-annotations', - ), - filemtime( plugin_dir_path( __FILE__ ) . 'plugins-api/annotations-sidebar.js' ), - true -); +add_action( 'init', 'enqueue_plugins_api_plugin_scripts' ); From 7ea50218a1de9cd00aceb0a58b4c879719f26d9b Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Tue, 25 Dec 2018 10:02:51 +0100 Subject: [PATCH 080/691] Add an e2e test for blocks with meta attributes (#13095) --- .../meta-attribute-block.test.js.snap | 3 ++ test/e2e/specs/meta-attribute-block.test.js | 47 +++++++++++++++++++ .../e2e/test-plugins/meta-attribute-block.php | 36 ++++++++++++++ .../meta-attribute-block/index.js | 35 ++++++++++++++ 4 files changed, 121 insertions(+) create mode 100644 test/e2e/specs/__snapshots__/meta-attribute-block.test.js.snap create mode 100644 test/e2e/specs/meta-attribute-block.test.js create mode 100644 test/e2e/test-plugins/meta-attribute-block.php create mode 100644 test/e2e/test-plugins/meta-attribute-block/index.js diff --git a/test/e2e/specs/__snapshots__/meta-attribute-block.test.js.snap b/test/e2e/specs/__snapshots__/meta-attribute-block.test.js.snap new file mode 100644 index 0000000000000..076a8ebbaac6e --- /dev/null +++ b/test/e2e/specs/__snapshots__/meta-attribute-block.test.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Block with a meta attribute Should persist the meta attribute properly 1`] = `"<!-- wp:test/test-meta-attribute-block /-->"`; diff --git a/test/e2e/specs/meta-attribute-block.test.js b/test/e2e/specs/meta-attribute-block.test.js new file mode 100644 index 0000000000000..b8d7fc4c5188e --- /dev/null +++ b/test/e2e/specs/meta-attribute-block.test.js @@ -0,0 +1,47 @@ +/** + * Internal dependencies + */ +import { + newPost, + getEditedPostContent, + saveDraft, + insertBlock, +} from '../support/utils'; +import { activatePlugin, deactivatePlugin } from '../support/plugins'; + +describe( 'Block with a meta attribute', () => { + beforeAll( async () => { + await activatePlugin( 'gutenberg-test-meta-attribute-block' ); + } ); + + beforeEach( async () => { + await newPost(); + } ); + + afterAll( async () => { + await deactivatePlugin( 'gutenberg-test-meta-attribute-block' ); + } ); + + it( 'Should persist the meta attribute properly', async () => { + await insertBlock( 'Test Meta Attribute Block' ); + await page.keyboard.type( 'Meta Value' ); + await saveDraft(); + await page.reload(); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + const persistedValue = await page.evaluate( () => document.querySelector( '.my-meta-input' ).value ); + expect( persistedValue ).toBe( 'Meta Value' ); + } ); + + it( 'Should use the same value in all the blocks', async () => { + await insertBlock( 'Test Meta Attribute Block' ); + await insertBlock( 'Test Meta Attribute Block' ); + await insertBlock( 'Test Meta Attribute Block' ); + await page.keyboard.type( 'Meta Value' ); + + const persistedValues = await page.evaluate( () => Array.from( document.querySelectorAll( '.my-meta-input' ) ).map( ( input ) => input.value ) ); + persistedValues.forEach( ( val ) => { + expect( val ).toBe( 'Meta Value' ); + } ); + } ); +} ); diff --git a/test/e2e/test-plugins/meta-attribute-block.php b/test/e2e/test-plugins/meta-attribute-block.php new file mode 100644 index 0000000000000..45fbcc661e108 --- /dev/null +++ b/test/e2e/test-plugins/meta-attribute-block.php @@ -0,0 +1,36 @@ +<?php +/** + * Plugin Name: Gutenberg Test Meta Attribute Block + * Plugin URI: https://github.com/WordPress/gutenberg + * Author: Gutenberg Team + * + * @package gutenberg-test-meta-attribute-block + */ + +/** + * Registers a custom script and a custom meta for the plugin. + */ +function init_test_meta_attribute_block_plugin() { + wp_enqueue_script( + 'gutenberg-test-meta-attribute-block', + plugins_url( 'meta-attribute-block/index.js', __FILE__ ), + array( + 'wp-blocks', + 'wp-element', + ), + filemtime( plugin_dir_path( __FILE__ ) . 'meta-attribute-block/index.js' ), + true + ); + + register_meta( + 'post', + 'my_meta', + array( + 'show_in_rest' => true, + 'single' => true, + 'type' => 'string', + ) + ); +} + +add_action( 'init', 'init_test_meta_attribute_block_plugin' ); diff --git a/test/e2e/test-plugins/meta-attribute-block/index.js b/test/e2e/test-plugins/meta-attribute-block/index.js new file mode 100644 index 0000000000000..0910e648299ae --- /dev/null +++ b/test/e2e/test-plugins/meta-attribute-block/index.js @@ -0,0 +1,35 @@ +( function() { + var registerBlockType = wp.blocks.registerBlockType; + var el = wp.element.createElement; + + registerBlockType( 'test/test-meta-attribute-block', { + title: 'Test Meta Attribute Block', + icon: 'star', + category: 'common', + + attributes: { + content: { + type: "string", + source: "meta", + meta: "my_meta", + }, + }, + + edit: function( props ) { + return el( + 'input', + { + className: 'my-meta-input', + value: props.attributes.content, + onChange: function( event ) { + props.setAttributes( { content: event.target.value } ); + }, + } + ); + }, + + save: function() { + return null; + }, + } ); +} )(); From d4bbde634f8e704ac63cf93845380357a7f22071 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Thu, 27 Dec 2018 17:34:19 +0000 Subject: [PATCH 081/691] Add end 2 end test to category addition (#13102) This commit adds end 2 end tests to the category addition. Taxonomies are an area of the editor where we had some regressions in the past. This is just a start, more tests will be added to test complex interactions using custom taxonomies. --- test/e2e/specs/taxonomies.test.js | 96 +++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 test/e2e/specs/taxonomies.test.js diff --git a/test/e2e/specs/taxonomies.test.js b/test/e2e/specs/taxonomies.test.js new file mode 100644 index 0000000000000..a247b28968991 --- /dev/null +++ b/test/e2e/specs/taxonomies.test.js @@ -0,0 +1,96 @@ +/** + * Internal dependencies + */ +import { + findSidebarPanelWithTitle, + newPost, + openDocumentSettingsSidebar, + publishPost, +} from '../support/utils'; + +describe( 'Taxonomies', () => { + const canCreatTermInTaxonomy = ( taxonomy ) => { + return page.evaluate( + ( _taxonomy ) => { + const post = wp.data.select( 'core/editor' ).getCurrentPost(); + if ( ! post._links ) { + return false; + } + return !! post._links[ `wp:action-create-${ _taxonomy }` ]; + }, + taxonomy + ); + }; + + const getSelectCategories = () => { + return page.evaluate( + () => { + return Array.from( document.querySelectorAll( + '.editor-post-taxonomies__hierarchical-terms-input:checked' + ) ).map( ( node ) => { + return node.parentElement.querySelector( + 'label' + ).innerText; + } ); + } + ); + }; + + it( 'should be able to open the categories panel and create a new main category if the user has the right capabilities', async () => { + await newPost(); + + await openDocumentSettingsSidebar(); + + const categoriesPanel = await findSidebarPanelWithTitle( 'Categories' ); + expect( categoriesPanel ).toBeDefined(); + + // Open the categories panel. + await categoriesPanel.click( 'button' ); + + // If the user has no permission to add a new category finish the test. + if ( ! ( await canCreatTermInTaxonomy( 'category' ) ) ) { + return; + } + + await page.waitForSelector( 'button.editor-post-taxonomies__hierarchical-terms-add' ); + + // Click add new category button. + await page.click( 'button.editor-post-taxonomies__hierarchical-terms-add' ); + + // Type the category name in the field. + await page.type( + '.editor-post-taxonomies__hierarchical-terms-input[type=text]', + 'z rand category 1' + ); + + // Click the submit button. + await page.click( '.editor-post-taxonomies__hierarchical-terms-submit' ); + + // Wait for the categories to load. + await page.waitForSelector( '.editor-post-taxonomies__hierarchical-terms-input:checked' ); + + let selectedCategories = await getSelectCategories(); + + // The new category is selected. + expect( selectedCategories ).toHaveLength( 1 ); + expect( selectedCategories[ 0 ] ).toEqual( 'z rand category 1' ); + + // Type something in the title so we can publish the post. + await page.type( '.editor-post-title__input', 'Hello World' ); + + // Publish the post. + await publishPost(); + + // Reload the editor. + await page.reload(); + + // Wait for the categories to load. + await page.waitForSelector( '.editor-post-taxonomies__hierarchical-terms-input:checked' ); + + selectedCategories = await getSelectCategories(); + + // The category selection was persisted after the publish process. + expect( selectedCategories ).toHaveLength( 1 ); + expect( selectedCategories[ 0 ] ).toEqual( 'z rand category 1' ); + } ); +} ); From 84e1e8031cb54b322f01adde9d560112290c6909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?sarah=20=E2=9C=88=20semark?= <sarah@triggersandsparks.com> Date: Fri, 28 Dec 2018 00:48:07 +0000 Subject: [PATCH 082/691] Update reference to design resources. (#13119) Current link 404s; updating to point to the proper spot. --- docs/contributors/reference.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contributors/reference.md b/docs/contributors/reference.md index 2a4714d5e75a2..551c6f82ab8e7 100644 --- a/docs/contributors/reference.md +++ b/docs/contributors/reference.md @@ -14,4 +14,4 @@ Released under GPL license, made by [Cristel Rossignol](https://twitter.com/cris ## Mockups -Mockup Sketch files are available in <a href="https://wordpress.org/gutenberg/handbook/reference/design-principles/#more-resources">the Design section</a>. +Mockup Sketch files are available in <a href="https://wordpress.org/gutenberg/handbook/designers-developers/designers/design-resources/">the Design section</a>. From 6262b603bb5b8cb13ac7f118334bf71622a5b5f3 Mon Sep 17 00:00:00 2001 From: torres126 <43215253+torres126@users.noreply.github.com> Date: Tue, 1 Jan 2019 09:52:47 +0000 Subject: [PATCH 083/691] Update License Year (#13145) Minor change, but as countries start heading into 2019 within the next few hours, it's probably worth updating the License year now. :smile: --- LICENSE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.md b/LICENSE.md index 1ebca723d015d..7918e383b331d 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ ### WordPress - Web publishing software - Copyright 2011-2018 by the contributors + Copyright 2011-2019 by the contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by From 1084f5149a1898cfb0f1faef75ace26a02ca5bed Mon Sep 17 00:00:00 2001 From: Stephen Edgar <stephen@netweb.com.au> Date: Wed, 2 Jan 2019 22:20:01 +1100 Subject: [PATCH 084/691] Update @wordpress/browserslist-config install readme (#13126) --- packages/browserslist-config/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/browserslist-config/README.md b/packages/browserslist-config/README.md index 7889d74837f49..4718d1cc398e4 100644 --- a/packages/browserslist-config/README.md +++ b/packages/browserslist-config/README.md @@ -7,7 +7,7 @@ Install the module ```shell -$ npm install @wordpress/browserslist-config +$ npm install browserslist @wordpress/browserslist-config --save-dev ``` ## Usage From 4c8bde39d945a9b15f69e64de63d2fa9ac364adc Mon Sep 17 00:00:00 2001 From: etoledom <etoledom@icloud.com> Date: Wed, 2 Jan 2019 20:24:53 +0200 Subject: [PATCH 085/691] [rnmobile]: Merge rnmobile/release/0.3 into master (#13162) * temporarily disable link formatting * Make sure RichText resigns focus when unmounted (#13048) --- packages/editor/src/components/rich-text/index.native.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/editor/src/components/rich-text/index.native.js b/packages/editor/src/components/rich-text/index.native.js index d8f9a1cfb0476..fd9d074472f5a 100644 --- a/packages/editor/src/components/rich-text/index.native.js +++ b/packages/editor/src/components/rich-text/index.native.js @@ -291,6 +291,12 @@ export class RichText extends Component { } } + componentWillUnmount() { + if ( this._editor.isFocused() ) { + this._editor.blur(); + } + } + componentDidUpdate( prevProps ) { if ( this.props.isSelected && ! prevProps.isSelected ) { this._editor.focus(); From b4ff7cf0a5c7121bee73953c22cf99e08865e9e9 Mon Sep 17 00:00:00 2001 From: GutenDev <44389517+GutenDev@users.noreply.github.com> Date: Thu, 3 Jan 2019 00:36:49 +0600 Subject: [PATCH 086/691] Added contributor (#13160) --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index d0920a8a0191b..0cde50e287487 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -125,3 +125,4 @@ This list is manually curated to include valuable contributions by volunteers th | @aldavigdis | @aldavigdis | | @miya0001 | @miyauchi | | @naogify | @naoki0h | +| @gutendev | @gutendev | From 94e8c2e7c6ac8f530087cefeb3fe104b208781f6 Mon Sep 17 00:00:00 2001 From: Mukesh Panchal <mukeshpanchal27@users.noreply.github.com> Date: Thu, 3 Jan 2019 22:17:02 +0530 Subject: [PATCH 087/691] docs: Updated font size name (#13082) In Theme Support you will find section called 'Block Font Sizes'; initially there were two sizes of 36 and 50 defined under one common name e.g. Large. Now changes has been made to display 'Large' for size of 36 and 'Huge' for size of 50. --- .../developers/themes/theme-support.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/designers-developers/developers/themes/theme-support.md b/docs/designers-developers/developers/themes/theme-support.md index 2e1157808a12e..07ab19f68df7d 100644 --- a/docs/designers-developers/developers/themes/theme-support.md +++ b/docs/designers-developers/developers/themes/theme-support.md @@ -137,24 +137,24 @@ Blocks may allow the user to configure the font sizes they use, e.g., the paragr ```php add_theme_support( 'editor-font-sizes', array( array( - 'name' => __( 'small', 'themeLangDomain' ), + 'name' => __( 'Small', 'themeLangDomain' ), 'size' => 12, 'slug' => 'small' ), array( - 'name' => __( 'regular', 'themeLangDomain' ), + 'name' => __( 'Normal', 'themeLangDomain' ), 'size' => 16, - 'slug' => 'regular' + 'slug' => 'normal' ), array( - 'name' => __( 'large', 'themeLangDomain' ), + 'name' => __( 'Large', 'themeLangDomain' ), 'size' => 36, 'slug' => 'large' ), array( - 'name' => __( 'larger', 'themeLangDomain' ), + 'name' => __( 'Huge', 'themeLangDomain' ), 'size' => 50, - 'slug' => 'larger' + 'slug' => 'huge' ) ) ); ``` From ca21a905c16f080fa8130ecc781bc256e4ea2b7a Mon Sep 17 00:00:00 2001 From: Nick Diego <ndiego@outermostdesign.com> Date: Thu, 3 Jan 2019 12:00:29 -0500 Subject: [PATCH 088/691] Make headings in panel readme consistent (#13173) ## Description This is a super basic fix. Just updates props for the Panel Readme from h6 to h5 to be consistent with other sections in this doc. --- packages/components/src/panel/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/components/src/panel/README.md b/packages/components/src/panel/README.md index 1c5c2bcf95df0..eefb417b74555 100644 --- a/packages/components/src/panel/README.md +++ b/packages/components/src/panel/README.md @@ -8,14 +8,14 @@ The `Panel` creates a container with a header that can take collapsible `PanelBo #### Props -###### className +##### className The class that will be added with `components-panel`. If no `className` is passed only `components-panel__body` and `is-opened` is used. - Type: `String` - Required: No -###### header +##### header Title of the `Panel`. Text will be rendered inside an `<h2>` tag. @@ -72,6 +72,7 @@ Whether or not the panel will start open. - Type: `Boolean` - Required: No - Default: true + --- ### PanelRow From c3407a996c5110ca32c868b77253111fd45618e4 Mon Sep 17 00:00:00 2001 From: "Pratik K. Yadav" <45303921+pratikkry@users.noreply.github.com> Date: Fri, 4 Jan 2019 00:42:32 +0530 Subject: [PATCH 089/691] Update Readme FAQ and Design Principles link (#13148) * Readme update Design Principles link * Update Readme FAQ Link --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 855958ef9635e..4878bf03990a2 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ Blocks are the unifying evolution of what is now covered, in different ways, by Imagine a custom `employee` block that a client can drag onto an `About` page to automatically display a picture, name, and bio of all the employees. Imagine a whole universe of plugins just as flexible, all extending WordPress in the same way. Imagine simplified menus and widgets. Users who can instantly understand and use WordPress—and 90% of plugins. This will allow you to easily compose beautiful posts like <a href="http://moc.co/sandbox/example-post/">this example</a>. -Check out the <a href="https://github.com/WordPress/gutenberg/blob/master/docs/reference/faq.md">FAQ</a> for answers to the most common questions about the project. +Check out the <a href="https://wordpress.org/gutenberg/handbook/designers-developers/faq/">FAQ</a> for answers to the most common questions about the project. ## Compatibility @@ -71,7 +71,7 @@ Please see <a href="https://github.com/WordPress/gutenberg/blob/master/CONTRIBUT - <a href="http://matiasventura.com/post/gutenberg-or-the-ship-of-theseus/">Gutenberg, or the Ship of Theseus</a>, with examples of what Gutenberg might do in the future - <a href="https://make.wordpress.org/core/2017/01/17/editor-technical-overview/">Editor Technical Overview</a> -- <a href="https://wordpress.org/gutenberg/handbook/reference/design-principles/">Design Principles and block design best practices</a> +- <a href="https://wordpress.org/gutenberg/handbook/contributors/design/">Design Principles and block design best practices</a> - <a href="https://github.com/Automattic/wp-post-grammar">WP Post Grammar Parser</a> - <a href="https://make.wordpress.org/core/tag/gutenberg/">Development updates on make.wordpress.org</a> - <a href="https://wordpress.org/gutenberg/handbook/">Documentation: Creating Blocks, Reference, and Guidelines</a> From 4ea81f60cd321e6b036e132672be1413004b6681 Mon Sep 17 00:00:00 2001 From: Kelly Dwan <ryelle@users.noreply.github.com> Date: Thu, 3 Jan 2019 14:12:49 -0500 Subject: [PATCH 090/691] Prevent ordered/unordered list conversions if onTagNameChange is not provided (#13144) --- packages/editor/src/components/rich-text/list-edit.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/editor/src/components/rich-text/list-edit.js b/packages/editor/src/components/rich-text/list-edit.js index 1e92d3d792eb4..cb52fe688d7ef 100644 --- a/packages/editor/src/components/rich-text/list-edit.js +++ b/packages/editor/src/components/rich-text/list-edit.js @@ -72,7 +72,7 @@ export const ListEdit = ( { editor, onTagNameChange, tagName, onSyncDOM } ) => ( <BlockFormatControls> <Toolbar controls={ [ - { + onTagNameChange && { icon: 'editor-ul', title: __( 'Convert to unordered list' ), isActive: isActiveListType( editor, 'ul', tagName ), @@ -85,7 +85,7 @@ export const ListEdit = ( { editor, onTagNameChange, tagName, onSyncDOM } ) => ( } }, }, - { + onTagNameChange && { icon: 'editor-ol', title: __( 'Convert to ordered list' ), isActive: isActiveListType( editor, 'ol', tagName ), @@ -114,7 +114,7 @@ export const ListEdit = ( { editor, onTagNameChange, tagName, onSyncDOM } ) => ( onSyncDOM(); }, }, - ] } + ].filter( Boolean ) } /> </BlockFormatControls> </Fragment> From 232012c141da400d367e59199da84edfbeec87bb Mon Sep 17 00:00:00 2001 From: Nicola Heald <nicola@notnowlewis.com> Date: Thu, 3 Jan 2019 19:13:43 +0000 Subject: [PATCH 091/691] Remove deprecated embed providers (#12759) See https://core.trac.wordpress.org/ticket/45399 --- .../block-library/src/embed/core-embeds.js | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/packages/block-library/src/embed/core-embeds.js b/packages/block-library/src/embed/core-embeds.js index 3a2e861e0c0aa..d14c76cadff2a 100644 --- a/packages/block-library/src/embed/core-embeds.js +++ b/packages/block-library/src/embed/core-embeds.js @@ -153,15 +153,6 @@ export const others = [ }, patterns: [ /^https?:\/\/(www\.)?dailymotion\.com\/.+/i ], }, - { - name: 'core-embed/funnyordie', - settings: { - title: 'Funny or Die', - icon: embedVideoIcon, - description: __( 'Embed Funny or Die content.' ), - }, - patterns: [ /^https?:\/\/(www\.)?funnyordie\.com\/.+/i ], - }, { name: 'core-embed/hulu', settings: { @@ -217,15 +208,6 @@ export const others = [ }, patterns: [ /^https?:\/\/(www\.)?mixcloud\.com\/.+/i ], }, - { - name: 'core-embed/photobucket', - settings: { - title: 'Photobucket', - icon: embedPhotoIcon, - description: __( 'Embed a Photobucket image.' ), - }, - patterns: [ /^http:\/\/g?i*\.photobucket\.com\/.+/i ], - }, { name: 'core-embed/polldaddy', settings: { From 74c3d396562d8e5cb36c82920430dd4d7f739b40 Mon Sep 17 00:00:00 2001 From: Robert Anderson <robert@noisysocks.com> Date: Fri, 4 Jan 2019 20:30:11 +1100 Subject: [PATCH 092/691] Improve Travis CI build times (#13103) --- .travis.yml | 65 +++++++++++++++++++++++++++++++++++++--------------- package.json | 2 -- 2 files changed, 46 insertions(+), 21 deletions(-) diff --git a/.travis.yml b/.travis.yml index 877794f883a38..6fcc4d931c194 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ dist: trusty -language: php +language: generic services: - docker @@ -13,56 +13,83 @@ notifications: cache: directories: - $HOME/.composer/cache - - $HOME/.phpbrew + - $HOME/.jest-cache - $HOME/.npm - -before_install: - - nvm install && nvm use - - npm install npm -g + - $HOME/.nvm/.cache + - $HOME/.phpbrew branches: only: - master +before_install: + - nvm install + jobs: include: - - stage: test + - name: JS unit tests env: WP_VERSION=latest + before_install: + - nvm install --latest-npm + install: + - npm ci script: - - npm install || exit 1 - - npm run ci || exit 1 + - npm run lint + - npm run check-local-changes + - npm run test-unit -- --ci --maxWorkers=2 --cacheDirectory="$HOME/.jest-cache" - - stage: test + - name: PHP unit tests (Docker) env: WP_VERSION=latest DOCKER=true script: - ./bin/run-wp-unit-tests.sh - - stage: test + - name: PHP unit tests (PHP 5.6) + language: php php: 5.6 env: WP_VERSION=latest script: - ./bin/run-wp-unit-tests.sh if: branch = master and type != "pull_request" - - stage: test - php: 7.1 + - name: PHP unit tests (PHP 5.3) env: WP_VERSION=latest SWITCH_TO_PHP=5.3 script: - ./bin/run-wp-unit-tests.sh if: branch = master and type != "pull_request" - - stage: test - php: 7.1 + - name: PHP unit tests (PHP 5.2) env: WP_VERSION=latest SWITCH_TO_PHP=5.2 script: - ./bin/run-wp-unit-tests.sh - - stage: test + - name: E2E tests (Admin with plugins) (1/2) env: WP_VERSION=latest POPULAR_PLUGINS=true + install: + - ./bin/setup-local-env.sh + script: + - $( npm bin )/jest --config test/e2e/jest.config.json --listTests > ~/.jest-e2e-tests + - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 2 == 0' < ~/.jest-e2e-tests ) + + - name: E2E tests (Admin with plugins) (2/2) + env: WP_VERSION=latest POPULAR_PLUGINS=true + install: + - ./bin/setup-local-env.sh + script: + - $( npm bin )/jest --config test/e2e/jest.config.json --listTests > ~/.jest-e2e-tests + - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 2 == 1' < ~/.jest-e2e-tests ) + + - name: E2E tests (Author without plugins) (1/2) + env: WP_VERSION=latest E2E_ROLE=author + install: + - ./bin/setup-local-env.sh script: - - ./bin/run-e2e-tests.sh || exit 1 + - $( npm bin )/jest --config test/e2e/jest.config.json --listTests > ~/.jest-e2e-tests + - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 2 == 0' < ~/.jest-e2e-tests ) - - stage: test + - name: E2E tests (Author without plugins) (2/2) env: WP_VERSION=latest E2E_ROLE=author + install: + - ./bin/setup-local-env.sh script: - - ./bin/run-e2e-tests.sh || exit 1 + - $( npm bin )/jest --config test/e2e/jest.config.json --listTests > ~/.jest-e2e-tests + - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 2 == 1' < ~/.jest-e2e-tests ) diff --git a/package.json b/package.json index 95ce65ad2682f..50048e3bc45e7 100644 --- a/package.json +++ b/package.json @@ -152,7 +152,6 @@ "check-licenses": "concurrently \"wp-scripts check-licenses --prod --gpl2\" \"wp-scripts check-licenses --dev\"", "precheck-local-changes": "npm run docs:build", "check-local-changes": "( git diff -U0 | xargs -0 node bin/process-git-diff ) || ( echo \"There are local uncommitted changes after one or both of 'npm install' or 'npm run docs:build'!\" && exit 1 );", - "ci": "concurrently \"npm run lint\" \"npm run test-unit:ci\" \"npm run check-local-changes\"", "predev": "npm run check-engines", "dev": "npm run build:packages && concurrently \"cross-env webpack --watch\" \"npm run dev:packages\"", "dev:packages": "node ./bin/packages/watch.js", @@ -183,7 +182,6 @@ "test-unit": "wp-scripts test-unit-js --config test/unit/jest.config.json", "test-unit:update": "npm run test-unit -- --updateSnapshot", "test-unit:watch": "npm run test-unit -- --watch", - "test-unit:ci": "npm run test-unit -- --ci --runInBand", "test-unit-php": "docker-compose run --rm wordpress_phpunit phpunit", "test-unit-php-multisite": "docker-compose run -e WP_MULTISITE=1 --rm wordpress_phpunit phpunit" }, From cc13393666db9fa078ac2a50e0226db67824788b Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Fri, 4 Jan 2019 09:30:53 +0000 Subject: [PATCH 093/691] Expose additionalData in mediaUpload function (#13130) --- packages/editor/src/utils/media-upload/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/editor/src/utils/media-upload/index.js b/packages/editor/src/utils/media-upload/index.js index 8693cede6f590..12e0335033247 100644 --- a/packages/editor/src/utils/media-upload/index.js +++ b/packages/editor/src/utils/media-upload/index.js @@ -18,6 +18,7 @@ import { mediaUpload } from './media-upload'; * Wrapper around mediaUpload() that injects the current post ID. * * @param {Object} $0 Parameters object passed to the function. + * @param {?Object} $0.additionalData Additional data to include in the request. * @param {string} $0.allowedTypes Array with the types of media that can be uploaded, if unset all types are allowed. * @param {Array} $0.filesList List of files. * @param {?number} $0.maxUploadFileSize Maximum upload size in bytes allowed for the site. @@ -25,6 +26,7 @@ import { mediaUpload } from './media-upload'; * @param {Function} $0.onFileChange Function called each time a file or a temporary representation of the file is available. */ export default function( { + additionalData = {}, allowedTypes, filesList, maxUploadFileSize, @@ -44,6 +46,7 @@ export default function( { onFileChange, additionalData: { post: getCurrentPostId(), + ...additionalData, }, maxUploadFileSize, onError: ( { message } ) => onError( message ), From bbc106a925532e57d25b8bfce62814debff888a1 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Fri, 4 Jan 2019 10:34:42 +0100 Subject: [PATCH 094/691] Avoid rerendering all the blocks when selecting a block (#13073) --- packages/editor/src/components/block-list/block.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/editor/src/components/block-list/block.js b/packages/editor/src/components/block-list/block.js index 6687629420610..75267e71e0c0d 100644 --- a/packages/editor/src/components/block-list/block.js +++ b/packages/editor/src/components/block-list/block.js @@ -214,7 +214,6 @@ export class BlockListBlock extends Component { isHovered || isPartOfMultiSelection || isSelected || - this.props.isMultiSelecting || this.hadTouchStart ) { return; @@ -376,7 +375,6 @@ export class BlockListBlock extends Component { isFirstMultiSelected, isTypingWithinBlock, isCaretWithinFormattedText, - isMultiSelecting, isEmptyDefaultBlock, isMovable, isParentOfSelectedBlock, @@ -386,7 +384,7 @@ export class BlockListBlock extends Component { isValid, attributes, } = this.props; - const isHovered = this.state.isHovered && ! isMultiSelecting; + const isHovered = this.state.isHovered && ! isPartOfMultiSelection; const blockType = getBlockType( name ); // translators: %s: Type of block (i.e. Text, Image etc) const blockLabel = sprintf( __( 'Block: %s' ), blockType.title ); @@ -416,7 +414,6 @@ export class BlockListBlock extends Component { ! isFocusMode && ( isSelected || hoverArea === 'left' ) && ! showEmptyBlockSideInserter && - ! isMultiSelecting && ! isPartOfMultiSelection && ! isTypingWithinBlock; const shouldShowBreadcrumb = @@ -635,7 +632,6 @@ const applyWithSelect = withSelect( isAncestorMultiSelected, isBlockMultiSelected, isFirstMultiSelectedBlock, - isMultiSelecting, isTyping, isCaretWithinFormattedText, getBlockIndex, @@ -662,7 +658,6 @@ const applyWithSelect = withSelect( isPartOfMultiSelection: isBlockMultiSelected( clientId ) || isAncestorMultiSelected( clientId ), isFirstMultiSelected: isFirstMultiSelectedBlock( clientId ), - isMultiSelecting: isMultiSelecting(), // We only care about this prop when the block is selected // Thus to avoid unnecessary rerenders we avoid updating the prop if the block is not selected. isTypingWithinBlock: From 5d34eb939413db810ea41062fb33b86033ccee63 Mon Sep 17 00:00:00 2001 From: Miguel Torres <miguelmariatorresrojas@gmail.com> Date: Fri, 4 Jan 2019 10:38:50 +0100 Subject: [PATCH 095/691] API Fetch: Support custom fetch handlers (#12365) --- packages/api-fetch/CHANGELOG.md | 6 + packages/api-fetch/README.md | 23 ++++ packages/api-fetch/src/index.js | 175 +++++++++++++++------------ packages/api-fetch/src/test/index.js | 34 ++++++ 4 files changed, 158 insertions(+), 80 deletions(-) diff --git a/packages/api-fetch/CHANGELOG.md b/packages/api-fetch/CHANGELOG.md index b4c58ed5d9e1a..2e813a48221b2 100644 --- a/packages/api-fetch/CHANGELOG.md +++ b/packages/api-fetch/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.3.0 (Unreleased) + +### New Feature + +- Default fetch handler can be overridden with a custom fetch handler + ## 2.2.6 (2018-12-12) ## 2.2.5 (2018-11-20) diff --git a/packages/api-fetch/README.md b/packages/api-fetch/README.md index 85bd65e1be632..26075a7cef6c4 100644 --- a/packages/api-fetch/README.md +++ b/packages/api-fetch/README.md @@ -85,4 +85,27 @@ const rootURL = "http://my-wordpress-site/wp-json/"; apiFetch.use( apiFetch.createRootURLMiddleware( rootURL ) ); ``` +### Custom fetch handler + +The `api-fetch` package uses `window.fetch` for making the requests but you can use a custom fetch handler by using the `setFetchHandler` method. The custom fetch handler will receive the `options` passed to the `apiFetch` calls. + +**Example** + +The example below uses a custom fetch handler for making all the requests with [`axios`](https://github.com/axios/axios). + +```js +import apiFetch from '@wordpress/api-fetch'; +import axios from 'axios'; + +apiFetch.setFetchHandler( ( options ) => { + const { url, path, data, method } = options; + + return axios( { + url: url || path, + method, + data, + } ); +} ); +``` + <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/api-fetch/src/index.js b/packages/api-fetch/src/index.js index d8e978b05e464..27fb225fe94eb 100644 --- a/packages/api-fetch/src/index.js +++ b/packages/api-fetch/src/index.js @@ -38,106 +38,121 @@ const DEFAULT_OPTIONS = { credentials: 'include', }; -const middlewares = []; +const middlewares = [ + userLocaleMiddleware, + namespaceEndpointMiddleware, + httpV1Middleware, + fetchAllMiddleware, +]; function registerMiddleware( middleware ) { - middlewares.push( middleware ); + middlewares.unshift( middleware ); } -function apiFetch( options ) { - const raw = ( nextOptions ) => { - const { url, path, data, parse = true, ...remainingOptions } = nextOptions; - let { body, headers } = nextOptions; +const defaultFetchHandler = ( nextOptions ) => { + const { url, path, data, parse = true, ...remainingOptions } = nextOptions; + let { body, headers } = nextOptions; + + // Merge explicitly-provided headers with default values. + headers = { ...DEFAULT_HEADERS, ...headers }; + + // The `data` property is a shorthand for sending a JSON body. + if ( data ) { + body = JSON.stringify( data ); + headers[ 'Content-Type' ] = 'application/json'; + } + + const responsePromise = window.fetch( + url || path, + { + ...DEFAULT_OPTIONS, + ...remainingOptions, + body, + headers, + } + ); + const checkStatus = ( response ) => { + if ( response.status >= 200 && response.status < 300 ) { + return response; + } + + throw response; + }; - // Merge explicitly-provided headers with default values. - headers = { ...DEFAULT_HEADERS, ...headers }; + const parseResponse = ( response ) => { + if ( parse ) { + if ( response.status === 204 ) { + return null; + } - // The `data` property is a shorthand for sending a JSON body. - if ( data ) { - body = JSON.stringify( data ); - headers[ 'Content-Type' ] = 'application/json'; + return response.json ? response.json() : Promise.reject( response ); } - const responsePromise = window.fetch( - url || path, - { - ...DEFAULT_OPTIONS, - ...remainingOptions, - body, - headers, - } - ); - const checkStatus = ( response ) => { - if ( response.status >= 200 && response.status < 300 ) { - return response; - } + return response; + }; - throw response; - }; + return responsePromise + .then( checkStatus ) + .then( parseResponse ) + .catch( ( response ) => { + if ( ! parse ) { + throw response; + } - const parseResponse = ( response ) => { - if ( parse ) { - if ( response.status === 204 ) { - return null; - } + const invalidJsonError = { + code: 'invalid_json', + message: __( 'The response is not a valid JSON response.' ), + }; - return response.json ? response.json() : Promise.reject( response ); + if ( ! response || ! response.json ) { + throw invalidJsonError; } - return response; - }; - - return responsePromise - .then( checkStatus ) - .then( parseResponse ) - .catch( ( response ) => { - if ( ! parse ) { - throw response; - } - - const invalidJsonError = { - code: 'invalid_json', - message: __( 'The response is not a valid JSON response.' ), - }; - - if ( ! response || ! response.json ) { + return response.json() + .catch( () => { throw invalidJsonError; - } - - return response.json() - .catch( () => { - throw invalidJsonError; - } ) - .then( ( error ) => { - const unknownError = { - code: 'unknown_error', - message: __( 'An unknown error occurred.' ), - }; - - throw error || unknownError; - } ); - } ); - }; + } ) + .then( ( error ) => { + const unknownError = { + code: 'unknown_error', + message: __( 'An unknown error occurred.' ), + }; + + throw error || unknownError; + } ); + } ); +}; + +let fetchHandler = defaultFetchHandler; + +/** + * Defines a custom fetch handler for making the requests that will override + * the default one using window.fetch + * + * @param {Function} newFetchHandler The new fetch handler + */ +function setFetchHandler( newFetchHandler ) { + fetchHandler = newFetchHandler; +} + +function apiFetch( options ) { + const steps = [ ...middlewares, fetchHandler ]; + + const createRunStep = ( index ) => ( workingOptions ) => { + const step = steps[ index ]; + if ( index === steps.length - 1 ) { + return step( workingOptions ); + } - const steps = [ - raw, - fetchAllMiddleware, - httpV1Middleware, - namespaceEndpointMiddleware, - userLocaleMiddleware, - ...middlewares, - ].reverse(); - - const runMiddleware = ( index ) => ( nextOptions ) => { - const nextMiddleware = steps[ index ]; - const next = runMiddleware( index + 1 ); - return nextMiddleware( nextOptions, next ); + const next = createRunStep( index + 1 ); + return step( workingOptions, next ); }; - return runMiddleware( 0 )( options ); + return createRunStep( 0 )( options ); } apiFetch.use = registerMiddleware; +apiFetch.setFetchHandler = setFetchHandler; apiFetch.createNonceMiddleware = createNonceMiddleware; apiFetch.createPreloadingMiddleware = createPreloadingMiddleware; diff --git a/packages/api-fetch/src/test/index.js b/packages/api-fetch/src/test/index.js index 88d4f35b09a57..1becd96d96576 100644 --- a/packages/api-fetch/src/test/index.js +++ b/packages/api-fetch/src/test/index.js @@ -183,4 +183,38 @@ describe( 'apiFetch', () => { } ); } ); } ); + + it( 'should not use the default fetch handler when using a custom fetch handler', () => { + const customFetchHandler = jest.fn(); + + apiFetch.setFetchHandler( customFetchHandler ); + + apiFetch( { path: '/random' } ); + + expect( window.fetch ).not.toHaveBeenCalled(); + + expect( customFetchHandler ).toHaveBeenCalledWith( { + path: '/random?_locale=user', + } ); + } ); + + it( 'should run the last-registered user-defined middleware first', () => { + // This could potentially impact other tests in that a lingering + // middleware is left. For the purposes of this test, it is sufficient + // to ensure that the last-registered middleware receives the original + // options object. It also assumes that some built-in middleware would + // either mutate or clone the original options if the extra middleware + // had been pushed to the stack. + expect.assertions( 1 ); + + const expectedOptions = {}; + + apiFetch.use( ( actualOptions, next ) => { + expect( actualOptions ).toBe( expectedOptions ); + + return next( actualOptions ); + } ); + + apiFetch( expectedOptions ); + } ); } ); From 2575d239c586a45c86935dd0b07e11e2b1ca00ca Mon Sep 17 00:00:00 2001 From: Pascal Birchler <pascal.birchler@gmail.com> Date: Fri, 4 Jan 2019 10:40:24 +0100 Subject: [PATCH 096/691] Add filter for preview interstitial markup (#12463) --- .../developers/filters/editor-filters.md | 14 ++++++++++++++ .../src/components/post-preview-button/index.js | 8 ++++++++ 2 files changed, 22 insertions(+) diff --git a/docs/designers-developers/developers/filters/editor-filters.md b/docs/designers-developers/developers/filters/editor-filters.md index 1289b8241936b..a205d583103b0 100644 --- a/docs/designers-developers/developers/filters/editor-filters.md +++ b/docs/designers-developers/developers/filters/editor-filters.md @@ -16,3 +16,17 @@ var withImageSize = function( size, mediaId, postId ) { wp.hooks.addFilter( 'editor.PostFeaturedImage.imageSize', 'my-plugin/with-image-size', withImageSize ); ``` +### `editor.PostPreview.interstitialMarkup` + +Filters the interstitial message shown when generating previews. + +_Example:_ + +```js +var customPreviewMessage = function() { + return '<b>Post preview is being generated!</b>'; +}; + +wp.hooks.addFilter( 'editor.PostPreview.interstitialMarkup', 'my-plugin/custom-preview-message', customPreviewMessage ); +``` + diff --git a/packages/editor/src/components/post-preview-button/index.js b/packages/editor/src/components/post-preview-button/index.js index b735ebcd5324e..265daa1a7db3f 100644 --- a/packages/editor/src/components/post-preview-button/index.js +++ b/packages/editor/src/components/post-preview-button/index.js @@ -12,6 +12,7 @@ import { __, _x } from '@wordpress/i18n'; import { withSelect, withDispatch } from '@wordpress/data'; import { DotTip } from '@wordpress/nux'; import { ifCondition, compose } from '@wordpress/compose'; +import { applyFilters } from '@wordpress/hooks'; function writeInterstitialMessage( targetDocument ) { let markup = renderToString( @@ -79,6 +80,13 @@ function writeInterstitialMessage( targetDocument ) { </style> `; + /** + * Filters the interstitial message shown when generating previews. + * + * @param {String} markup The preview interstitial markup. + */ + markup = applyFilters( 'editor.PostPreview.interstitialMarkup', markup ); + targetDocument.write( markup ); targetDocument.title = __( 'Generating preview…' ); targetDocument.close(); From 27dca98898436c943b836d9e9d82abcc53bc3d0e Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Fri, 4 Jan 2019 11:13:38 +0100 Subject: [PATCH 097/691] Bump plugin version to 4.8 (#13125) --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index b1f6599c35c2f..2df698bd41413 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,7 +3,7 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the new block editor in core. - * Version: 4.7.1 + * Version: 4.8.0 * Author: Gutenberg Team * * @package gutenberg diff --git a/package-lock.json b/package-lock.json index 52818081f01b4..3d54418b81877 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "4.7.1", + "version": "4.8.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 50048e3bc45e7..5e4a1a741eb98 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "4.7.1", + "version": "4.8.0", "private": true, "description": "A new WordPress editor experience", "repository": "git+https://github.com/WordPress/gutenberg.git", From 7e72ba5252d5c22d35f3aaedb0b68b59e0011cd1 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 4 Jan 2019 06:28:40 -0500 Subject: [PATCH 098/691] Testing: Extract error message from console logging in E2E surfacing (#13179) --- test/e2e/support/setup-test-framework.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/test/e2e/support/setup-test-framework.js b/test/e2e/support/setup-test-framework.js index 1b951ebc579fb..46961026e8629 100644 --- a/test/e2e/support/setup-test-framework.js +++ b/test/e2e/support/setup-test-framework.js @@ -2,6 +2,7 @@ * External dependencies */ import 'expect-puppeteer'; +import { get } from 'lodash'; /** * WordPress dependencies @@ -108,7 +109,7 @@ function observeConsoleLogging() { return; } - const text = message.text(); + let text = message.text(); // An exception is made for _blanket_ deprecation warnings: Those // which log regardless of whether a deprecated feature is in use. @@ -124,6 +125,19 @@ function observeConsoleLogging() { const logFunction = OBSERVED_CONSOLE_MESSAGE_TYPES[ type ]; + // As of Puppeteer 1.6.1, `message.text()` wrongly returns an object of + // type JSHandle for error logging, instead of the expected string. + // + // See: https://github.com/GoogleChrome/puppeteer/issues/3397 + // + // The recommendation there to asynchronously resolve the error value + // upon a console event may be prone to a race condition with the test + // completion, leaving a possibility of an error not being surfaced + // correctly. Instead, the logic here synchronously inspects the + // internal object shape of the JSHandle to find the error text. If it + // cannot be found, the default text value is used instead. + text = get( message.args(), [ 0, '_remoteObject', 'description' ], text ); + // Disable reason: We intentionally bubble up the console message // which, unless the test explicitly anticipates the logging via // @wordpress/jest-console matchers, will cause the intended test From f003f0530775ad1fd6c79b1fc07342e720af65fc Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 4 Jan 2019 09:10:39 -0500 Subject: [PATCH 099/691] url: Return URL unmodified if no args to append in addQueryArgs (#13168) * url: Avoid adding/removing ? if no query args added * url: Restore support for falsey args --- packages/url/CHANGELOG.md | 1 + packages/url/src/index.js | 5 +++++ packages/url/src/test/index.test.js | 13 ++++++++++++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/url/CHANGELOG.md b/packages/url/CHANGELOG.md index 3b9d9be440fc1..14f0b24650bd5 100644 --- a/packages/url/CHANGELOG.md +++ b/packages/url/CHANGELOG.md @@ -3,6 +3,7 @@ ### Bug Fixes - `addQueryArgs` will return only the querystring fragment if the passed `url` is undefined. Previously, an uncaught error would be thrown. +- `addQueryArgs` will not append (or remove) a `?` if there are no query arguments to be added. Previously, `?` would be wrongly appended even if there was no querystring generated. ## 2.3.2 (2018-12-12) diff --git a/packages/url/src/index.js b/packages/url/src/index.js index deae7b633756c..da3077640e243 100644 --- a/packages/url/src/index.js +++ b/packages/url/src/index.js @@ -170,6 +170,11 @@ export function isValidFragment( fragment ) { * @return {string} URL with arguments applied. */ export function addQueryArgs( url = '', args ) { + // If no arguments are to be appended, return original URL. + if ( ! args || ! Object.keys( args ).length ) { + return url; + } + let baseUrl = url; // Determine whether URL already had query arguments. diff --git a/packages/url/src/test/index.test.js b/packages/url/src/test/index.test.js index 5e1ea6818090c..ce42edd047c25 100644 --- a/packages/url/src/test/index.test.js +++ b/packages/url/src/test/index.test.js @@ -328,7 +328,18 @@ describe( 'addQueryArgs', () => { } ); it( 'should return only querystring when passed undefined url', () => { - expect( addQueryArgs( undefined, { sun: 'true' } ) ).toBe( '?sun=true' ); + const url = undefined; + const args = { sun: 'true' }; + + expect( addQueryArgs( url, args ) ).toBe( '?sun=true' ); + } ); + + it( 'should return URL argument unaffected if no query arguments to append', () => { + [ '', 'https://example.com', 'https://example.com?' ].forEach( ( url ) => { + [ undefined, {} ].forEach( ( args ) => { + expect( addQueryArgs( url, args ) ).toBe( url ); + } ); + } ); } ); } ); From 95edac1e42cb10ed7da9f406696cdc85d6e476d5 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 4 Jan 2019 09:53:58 -0500 Subject: [PATCH 100/691] Hooks: Dispatch all heartbeat events as hooks actions (#11781) * Framework: Dispatch all heartbeat events as actions * Editor: Send PostLockModal lock request via XHR * Editor: Use hooks for PostLockedModal heartbeat handling * Framework: Update package-lock.json per jQuery dependency drop * Editor: Remove unsupported event object from action callback * Editor: Assure withGlobalEvents is last to wrap component Since it tests for static method on which to call * Editor: Reuse hook name across disparate hooks --- lib/client-assets.php | 27 ++++++++- lib/packages-dependencies.php | 1 - package-lock.json | 6 -- packages/editor/CHANGELOG.md | 6 ++ packages/editor/package.json | 1 - .../src/components/post-locked-modal/index.js | 59 +++++++++++-------- 6 files changed, 64 insertions(+), 36 deletions(-) diff --git a/lib/client-assets.php b/lib/client-assets.php index d38a3c2098feb..d3e84f1b9779c 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -1026,11 +1026,32 @@ function gutenberg_editor_scripts_and_styles( $hook ) { // to disable it outright. wp_enqueue_script( 'heartbeat' ); - // Transform a "heartbeat-tick" jQuery event into "heartbeat.tick" hook action. - // This removes the need of using jQuery for listening to the event. + // Transforms heartbeat jQuery events into equivalent hook actions. This + // avoids a dependency on jQuery for listening to the event. + $heartbeat_hooks = <<<JS +( function() { + jQuery( document ).on( [ + 'heartbeat-send', + 'heartbeat-tick', + 'heartbeat-error', + 'heartbeat-connection-lost', + 'heartbeat-connection-restored', + 'heartbeat-nonces-expired', + ].join( ' ' ), function( event ) { + var actionName = event.type.replace( /-/g, '.' ), + args; + + // Omit the event argument in applying arguments to the hook callback. + // The remaining arguments are passed to the hook. + args = Array.prototype.slice.call( arguments, 1 ); + + wp.hooks.doAction.apply( null, [ actionName ].concat( args ) ); + } ); +} )(); +JS; wp_add_inline_script( 'heartbeat', - 'jQuery( document ).on( "heartbeat-tick", function ( event, response ) { wp.hooks.doAction( "heartbeat.tick", response ) } );', + $heartbeat_hooks, 'after' ); diff --git a/lib/packages-dependencies.php b/lib/packages-dependencies.php index 34c70851ff727..ada718023b33c 100644 --- a/lib/packages-dependencies.php +++ b/lib/packages-dependencies.php @@ -131,7 +131,6 @@ 'wp-viewport', ), 'wp-editor' => array( - 'jquery', 'lodash', 'wp-tinymce-lists', 'wp-a11y', diff --git a/package-lock.json b/package-lock.json index 3d54418b81877..917bb93ee5006 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2562,7 +2562,6 @@ "classnames": "^2.2.5", "dom-scroll-into-view": "^1.2.1", "inherits": "^2.0.3", - "jquery": "^3.3.1", "lodash": "^4.17.10", "memize": "^1.0.5", "react-autosize-textarea": "^3.0.2", @@ -12774,11 +12773,6 @@ "merge-stream": "^1.0.1" } }, - "jquery": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz", - "integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==" - }, "js-base64": { "version": "2.4.6", "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.6.tgz", diff --git a/packages/editor/CHANGELOG.md b/packages/editor/CHANGELOG.md index f8465425eed10..672be5a61d0a0 100644 --- a/packages/editor/CHANGELOG.md +++ b/packages/editor/CHANGELOG.md @@ -1,3 +1,9 @@ +## 9.0.7 (Unreleased) + +### Internal + +- Removed `jQuery` dependency + ## 9.0.6 (2018-12-18) ### Bug Fixes diff --git a/packages/editor/package.json b/packages/editor/package.json index 811eb9643e4c0..a1b72033210d4 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -47,7 +47,6 @@ "classnames": "^2.2.5", "dom-scroll-into-view": "^1.2.1", "inherits": "^2.0.3", - "jquery": "^3.3.1", "lodash": "^4.17.10", "memize": "^1.0.5", "react-autosize-textarea": "^3.0.2", diff --git a/packages/editor/src/components/post-locked-modal/index.js b/packages/editor/src/components/post-locked-modal/index.js index db8de6f00579c..ecdb4b6a54b32 100644 --- a/packages/editor/src/components/post-locked-modal/index.js +++ b/packages/editor/src/components/post-locked-modal/index.js @@ -1,7 +1,6 @@ /** * External dependencies */ -import jQuery from 'jquery'; import { get } from 'lodash'; /** @@ -12,7 +11,8 @@ import { Modal, Button } from '@wordpress/components'; import { withSelect, withDispatch } from '@wordpress/data'; import { addQueryArgs } from '@wordpress/url'; import { Component } from '@wordpress/element'; -import { compose, withGlobalEvents } from '@wordpress/compose'; +import { addAction, removeAction } from '@wordpress/hooks'; +import { compose, withGlobalEvents, withInstanceId } from '@wordpress/compose'; /** * Internal dependencies @@ -30,17 +30,30 @@ class PostLockedModal extends Component { } componentDidMount() { + const hookName = this.getHookName(); + // Details on these events on the Heartbeat API docs // https://developer.wordpress.org/plugins/javascript/heartbeat-api/ - jQuery( document ) - .on( 'heartbeat-send.refresh-lock', this.sendPostLock ) - .on( 'heartbeat-tick.refresh-lock', this.receivePostLock ); + addAction( 'heartbeat.send', hookName, this.sendPostLock ); + addAction( 'heartbeat.tick', hookName, this.receivePostLock ); } componentWillUnmount() { - jQuery( document ) - .off( 'heartbeat-send.refresh-lock', this.sendPostLock ) - .off( 'heartbeat-tick.refresh-lock', this.receivePostLock ); + const hookName = this.getHookName(); + + removeAction( 'heartbeat.send', hookName ); + removeAction( 'heartbeat.tick', hookName ); + } + + /** + * Returns a `@wordpress/hooks` hook name specific to the instance of the + * component. + * + * @return {string} Hook name prefix. + */ + getHookName() { + const { instanceId } = this.props; + return 'core/editor/post-locked-modal-' + instanceId; } /** @@ -49,10 +62,9 @@ class PostLockedModal extends Component { * When the user does not send a heartbeat in a heartbeat-tick * the user is no longer editing and another user can start editing. * - * @param {Object} event Event. - * @param {Object} data Data to send in the heartbeat request. + * @param {Object} data Data to send in the heartbeat request. */ - sendPostLock( event, data ) { + sendPostLock( data ) { const { isLocked, activePostLock, postId } = this.props; if ( isLocked ) { return; @@ -67,10 +79,9 @@ class PostLockedModal extends Component { /** * Refresh post locks: update the lock string or show the dialog if somebody has taken over editing. * - * @param {Object} event Event. - * @param {Object} data Data received in the heartbeat request + * @param {Object} data Data received in the heartbeat request */ - receivePostLock( event, data ) { + receivePostLock( data ) { if ( ! data[ 'wp-refresh-post-lock' ] ) { return; } @@ -104,18 +115,15 @@ class PostLockedModal extends Component { return; } - const data = { - action: 'wp-remove-post-lock', - _wpnonce: postLockUtils.unlockNonce, - post_ID: postId, - active_post_lock: activePostLock, - }; + const data = new window.FormData(); + data.append( 'action', 'wp-remove-post-lock' ); + data.append( '_wpnonce', postLockUtils.unlockNonce ); + data.append( 'post_ID', postId ); + data.append( 'active_post_lock', activePostLock ); - jQuery.post( { - async: false, - url: postLockUtils.ajaxUrl, - data, - } ); + const xhr = new window.XMLHttpRequest(); + xhr.open( 'POST', postLockUtils.ajaxUrl, false ); + xhr.send( data ); } render() { @@ -232,6 +240,7 @@ export default compose( updatePostLock, }; } ), + withInstanceId, withGlobalEvents( { beforeunload: 'releasePostLock', } ) From f16d74376c36b66648e66f6e36309a48694af279 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 4 Jan 2019 11:36:08 -0500 Subject: [PATCH 101/691] Data: Avoid persistence on unchanging subset of keys (#12996) * Data: Avoid persistence on unchanging subset of keys * Data: Avoid calling picking reducer if same top-level state * Testing: Use E2E utils to effect tips disabling The newPost function includes built-in awareness to disable tips, and does so by default. --- .../data/src/plugins/persistence/index.js | 46 +++++++++++++--- .../src/plugins/persistence/test/index.js | 55 +++++++++++++++++++ test/e2e/specs/new-post.test.js | 14 +---- 3 files changed, 96 insertions(+), 19 deletions(-) diff --git a/packages/data/src/plugins/persistence/index.js b/packages/data/src/plugins/persistence/index.js index b619f2622b068..3ae854b2ca6cd 100644 --- a/packages/data/src/plugins/persistence/index.js +++ b/packages/data/src/plugins/persistence/index.js @@ -1,12 +1,13 @@ /** * External dependencies */ -import { pick, flow } from 'lodash'; +import { flow } from 'lodash'; /** * Internal dependencies */ import defaultStorage from './storage/default'; +import { combineReducers } from '../../'; /** * Persistence plugin options. @@ -37,7 +38,7 @@ const DEFAULT_STORAGE_KEY = 'WP_DATA'; * Higher-order reducer to provides an initial value when state is undefined. * * @param {Function} reducer Original reducer. - * @param {*} initialState Value to use as initial state. + * @param {*} initialState Value to use as initial state. * * @return {Function} Enhanced reducer. */ @@ -47,6 +48,23 @@ export function withInitialState( reducer, initialState ) { }; } +/** + * Higher-order reducer which invokes the original reducer only if state is + * inequal from that of the action's `nextState` property, otherwise returning + * the original state reference. + * + * @param {Function} reducer Original reducer. + * + * @return {Function} Enhanced reducer. + */ +export const withLazySameState = ( reducer ) => ( state, action ) => { + if ( action.nextState === state ) { + return state; + } + + return reducer( state, action ); +}; + /** * Creates a persistence interface, exposing getter and setter methods (`get` * and `set` respectively). @@ -125,15 +143,27 @@ export default function( registry, pluginOptions ) { * @return {Function} Enhanced dispatch function. */ function createPersistOnChange( getState, reducerKey, keys ) { - let lastState = getState(); + let getPersistedState; + if ( Array.isArray( keys ) ) { + // Given keys, the persisted state should by produced as an object + // of the subset of keys. This implementation uses combineReducers + // to leverage its behavior of returning the same object when none + // of the property values changes. This allows a strict reference + // equality to bypass a persistence set on an unchanging state. + const reducers = keys.reduce( ( result, key ) => Object.assign( result, { + [ key ]: ( state, action ) => action.nextState[ key ], + } ), {} ); + + getPersistedState = withLazySameState( combineReducers( reducers ) ); + } else { + getPersistedState = ( state, action ) => action.nextState; + } + + let lastState = getPersistedState( undefined, { nextState: getState() } ); return ( result ) => { - let state = getState(); + const state = getPersistedState( lastState, { nextState: getState() } ); if ( state !== lastState ) { - if ( Array.isArray( keys ) ) { - state = pick( state, keys ); - } - persistence.set( reducerKey, state ); lastState = state; } diff --git a/packages/data/src/plugins/persistence/test/index.js b/packages/data/src/plugins/persistence/test/index.js index fc04e395cb4a2..55a9fa79d459e 100644 --- a/packages/data/src/plugins/persistence/test/index.js +++ b/packages/data/src/plugins/persistence/test/index.js @@ -4,6 +4,7 @@ import plugin, { createPersistenceInterface, withInitialState, + withLazySameState, } from '../'; import objectStorage from '../storage/object'; import { createRegistry } from '../../../'; @@ -138,6 +139,34 @@ describe( 'persistence', () => { expect( objectStorage.setItem ).toHaveBeenCalledWith( 'WP_DATA', '{"test":{"foo":1}}' ); } ); + it( 'should not persist an unchanging subset', () => { + const initialState = { foo: 'bar' }; + function reducer( state = initialState, action ) { + const { type, key, value } = action; + if ( type === 'SET_KEY_VALUE' ) { + return { ...state, [ key ]: value }; + } + + return state; + } + + registry.registerStore( 'test', { + reducer, + persist: [ 'foo' ], + actions: { + setKeyValue( key, value ) { + return { type: 'SET_KEY_VALUE', key, value }; + }, + }, + } ); + + registry.dispatch( 'test' ).setKeyValue( 'foo', 1 ); + objectStorage.setItem.mockClear(); + + registry.dispatch( 'test' ).setKeyValue( 'foo', 1 ); + expect( objectStorage.setItem ).not.toHaveBeenCalled(); + } ); + describe( 'createPersistenceInterface', () => { const storage = objectStorage; const storageKey = 'FOO'; @@ -194,4 +223,30 @@ describe( 'persistence', () => { expect( enhanced() ).toBe( 2 ); } ); } ); + + describe( 'withLazySameState', () => { + it( 'should call the original reducer if action.nextState differs from state', () => { + const reducer = jest.fn().mockImplementation( ( state, action ) => action.nextState ); + const enhanced = withLazySameState( reducer ); + + reducer.mockClear(); + + const state = enhanced( 1, { nextState: 2 } ); + + expect( state ).toBe( 2 ); + expect( reducer ).toHaveBeenCalled(); + } ); + + it( 'should not call the original reducer if action.nextState equals state', () => { + const reducer = jest.fn().mockImplementation( ( state, action ) => action.nextState ); + const enhanced = withLazySameState( reducer ); + + reducer.mockClear(); + + const state = enhanced( 1, { nextState: 1 } ); + + expect( state ).toBe( 1 ); + expect( reducer ).not.toHaveBeenCalled(); + } ); + } ); } ); diff --git a/test/e2e/specs/new-post.test.js b/test/e2e/specs/new-post.test.js index 44e08f1902f2d..232584dd3c630 100644 --- a/test/e2e/specs/new-post.test.js +++ b/test/e2e/specs/new-post.test.js @@ -7,11 +7,13 @@ import { activatePlugin, deactivatePlugin } from '../support/plugins'; describe( 'new editor state', () => { beforeAll( async () => { await activatePlugin( 'gutenberg-test-plugin-post-formats-support' ); + } ); + + beforeEach( async () => { await newPost(); } ); afterAll( async () => { - await newPost(); await deactivatePlugin( 'gutenberg-test-plugin-post-formats-support' ); } ); @@ -38,16 +40,6 @@ describe( 'new editor state', () => { } ); it( 'should focus the title if the title is empty', async () => { - // We need to remove the tips to make sure they aren't clicked/removed - // during our check of the title `textarea`'s focus. - await page.evaluate( () => { - return wp.data.dispatch( 'core/nux' ).disableTips(); - } ); - - // And then reload the page to ensure we get a new page that should - // autofocus the title, without any NUX tips. - await page.reload(); - const activeElementClasses = await page.evaluate( () => { return Object.values( document.activeElement.classList ); } ); From 4928f16b7ebdba334e9c6bcd7e0a03c50ad3bde6 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 4 Jan 2019 11:49:09 -0500 Subject: [PATCH 102/691] Build: Change package build step to async flow (#8093) * Build: Change package build step to async flow * Build: Trigger LazyResult#then for postcss.process See: http://api.postcss.org/LazyResult.html#css >This is why this method is only for debug purpose, you should always use LazyResult#then. * Build: Consistently use css property for stringified result Effectively unchanged, since stringification of the LazyResult object is aliased to the css property. See: http://api.postcss.org/LazyResult.html#toString --- bin/packages/build.js | 107 ++++++++++++++++++++++++------------------ package-lock.json | 16 ------- package.json | 1 - 3 files changed, 62 insertions(+), 62 deletions(-) diff --git a/bin/packages/build.js b/bin/packages/build.js index 02009c1e4a4d3..cc0bcf5f76ae6 100755 --- a/bin/packages/build.js +++ b/bin/packages/build.js @@ -8,15 +8,15 @@ /** * External dependencies */ +const { promisify } = require( 'util' ); const fs = require( 'fs' ); const path = require( 'path' ); -const glob = require( 'glob' ); +let glob = require( 'glob' ); const babel = require( '@babel/core' ); const chalk = require( 'chalk' ); -const mkdirp = require( 'mkdirp' ); +let mkdirp = require( 'mkdirp' ); const sass = require( 'node-sass' ); const postcss = require( 'postcss' ); -const deasync = require( 'deasync' ); /** * Internal dependencies @@ -36,6 +36,14 @@ const BUILD_DIR = { }; const DONE = chalk.reset.inverse.bold.green( ' DONE ' ); +// Promisification +const readFile = promisify( fs.readFile ); +const writeFile = promisify( fs.writeFile ); +const transformFile = promisify( babel.transformFile ); +const renderSass = promisify( sass.render ); +glob = promisify( glob ); +mkdirp = promisify( mkdirp ); + /** * Get the package name for a specified file * @@ -73,6 +81,8 @@ function getBuildPath( file, buildFolder ) { * Given a list of scss and js filepaths, divide them into sets them and rebuild. * * @param {Array} files list of files to rebuild + * + * @return {Promise} Promise resolving when files are built. */ function buildFiles( files ) { // Reduce files into a unique sets of javaScript files and scss packages. @@ -87,8 +97,10 @@ function buildFiles( files ) { return accumulator; }, { jsFiles: new Set(), scssPackagePaths: new Set() } ); - buildPaths.jsFiles.forEach( buildJsFile ); - buildPaths.scssPackagePaths.forEach( buildPackageScss ); + return Promise.all( [ + ...buildPaths.jsFiles.map( buildJsFile ), + ...buildPaths.scssPackagePaths.map( buildPackageScss ), + ] ); } /** @@ -96,30 +108,39 @@ function buildFiles( files ) { * * @param {string} file File path to build * @param {boolean} silent Show logs + * + * @return {Promise} Promise resolving when file is built. */ function buildJsFile( file, silent ) { - buildJsFileFor( file, silent, 'main' ); - buildJsFileFor( file, silent, 'module' ); + return Promise.all( [ + buildJsFileFor( file, silent, 'main' ), + buildJsFileFor( file, silent, 'module' ), + ] ); } /** * Build a package's scss styles * * @param {string} packagePath The path to the package. + * + * @return {Promise} Promise resolving when file is built. */ -function buildPackageScss( packagePath ) { +async function buildPackageScss( packagePath ) { const srcDir = path.resolve( packagePath, SRC_DIR ); - const scssFiles = glob.sync( `${ srcDir }/*.scss` ); + const scssFiles = await glob( `${ srcDir }/*.scss` ); // Build scss files individually. - scssFiles.forEach( buildScssFile ); + return Promise.all( scssFiles.map( buildScssFile ) ); } -function buildScssFile( styleFile ) { +async function buildScssFile( styleFile ) { const outputFile = getBuildPath( styleFile.replace( '.scss', '.css' ), BUILD_DIR.style ); const outputFileRTL = getBuildPath( styleFile.replace( '.scss', '-rtl.css' ), BUILD_DIR.style ); - mkdirp.sync( path.dirname( outputFile ) ); - const builtSass = sass.renderSync( { + + await mkdirp( path.dirname( outputFile ) ); + + const contents = await readFile( styleFile, 'utf8' ); + const builtSass = await renderSass( { file: styleFile, includePaths: [ path.resolve( __dirname, '../../assets/stylesheets' ) ], data: ( @@ -131,27 +152,22 @@ function buildScssFile( styleFile ) { 'animations', 'z-index', ].map( ( imported ) => `@import "${ imported }";` ).join( ' ' ) + - fs.readFileSync( styleFile, 'utf8' ) + contents ), } ); - const postCSSSync = ( callback ) => { - postcss( require( './post-css-config' ) ) - .process( builtSass.css, { from: 'src/app.css', to: 'dest/app.css' } ) - .then( ( result ) => callback( null, result ) ); - }; - - const postCSSRTLSync = ( ltrCSS, callback ) => { - postcss( [ require( 'rtlcss' )() ] ) - .process( ltrCSS, { from: 'src/app.css', to: 'dest/app.css' } ) - .then( ( result ) => callback( null, result ) ); - }; + const result = await postcss( require( './post-css-config' ) ).process( builtSass.css, { + from: 'src/app.css', + to: 'dest/app.css', + } ); - const result = deasync( postCSSSync )(); - fs.writeFileSync( outputFile, result.css ); + const resultRTL = await postcss( [ require( 'rtlcss' )() ] ).process( result.css, { + from: 'src/app.css', + to: 'dest/app.css', + } ); - const resultRTL = deasync( postCSSRTLSync )( result ); - fs.writeFileSync( outputFileRTL, resultRTL ); + await writeFile( outputFile, result.css ); + await writeFile( outputFileRTL, resultRTL.css ); } /** @@ -161,17 +177,17 @@ function buildScssFile( styleFile ) { * @param {boolean} silent Show logs * @param {string} environment Dist environment (node or es5) */ -function buildJsFileFor( file, silent, environment ) { +async function buildJsFileFor( file, silent, environment ) { const buildDir = BUILD_DIR[ environment ]; const destPath = getBuildPath( file, buildDir ); const babelOptions = getBabelConfig( environment ); babelOptions.sourceMaps = true; babelOptions.sourceFileName = file; - mkdirp.sync( path.dirname( destPath ) ); - const transformed = babel.transformFileSync( file, babelOptions ); - fs.writeFileSync( destPath + '.map', JSON.stringify( transformed.map ) ); - fs.writeFileSync( destPath, transformed.code + '\n//# sourceMappingURL=' + path.basename( destPath ) + '.map' ); + await mkdirp( path.dirname( destPath ) ); + const transformed = await transformFile( file, babelOptions ); + writeFile( destPath + '.map', JSON.stringify( transformed.map ) ); + writeFile( destPath, transformed.code + '\n//# sourceMappingURL=' + path.basename( destPath ) + '.map' ); if ( ! silent ) { process.stdout.write( @@ -189,9 +205,9 @@ function buildJsFileFor( file, silent, environment ) { * * @param {string} packagePath absolute package path */ -function buildPackage( packagePath ) { +async function buildPackage( packagePath ) { const srcDir = path.resolve( packagePath, SRC_DIR ); - const jsFiles = glob.sync( `${ srcDir }/**/*.js`, { + const jsFiles = await glob( `${ srcDir }/**/*.js`, { ignore: [ `${ srcDir }/**/test/**/*.js`, `${ srcDir }/**/__mocks__/**/*.js`, @@ -199,14 +215,15 @@ function buildPackage( packagePath ) { nodir: true, } ); - process.stdout.write( `${ path.basename( packagePath ) }\n` ); - - // Build js files individually. - jsFiles.forEach( ( file ) => buildJsFile( file, true ) ); + await Promise.all( [ + // Build js files individually. + ...jsFiles.map( ( file ) => buildJsFile( file, true ) ), - // Build package CSS files - buildPackageScss( packagePath ); + // Build package CSS files + buildPackageScss( packagePath ), + ] ); + process.stdout.write( `${ path.basename( packagePath ) }\n` ); process.stdout.write( `${ DONE }\n` ); } @@ -216,7 +233,7 @@ if ( files.length ) { buildFiles( files ); } else { process.stdout.write( chalk.inverse( '>> Building packages \n' ) ); - getPackages() - .forEach( buildPackage ); - process.stdout.write( '\n' ); + Promise.all( getPackages().map( buildPackage ) ).then( () => { + process.stdout.write( '\n' ); + } ); } diff --git a/package-lock.json b/package-lock.json index 917bb93ee5006..61b1904fc0b20 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4544,12 +4544,6 @@ "integrity": "sha512-XBaoWE9RW8pPdPQNibZsW2zh8TW6gcarXp1FZPwT8Uop8ScSNldJEWf2k9l3HeTqdrEwsOsFcq74RiJECW34yA==", "dev": true }, - "bindings": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz", - "integrity": "sha1-FK1hE4EtLTfXLme0ystLtyZQXxE=", - "dev": true - }, "block-stream": { "version": "0.0.9", "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", @@ -6749,16 +6743,6 @@ "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", "dev": true }, - "deasync": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/deasync/-/deasync-0.1.13.tgz", - "integrity": "sha512-/6ngYM7AapueqLtvOzjv9+11N2fHDSrkxeMF1YPE20WIfaaawiBg+HZH1E5lHrcJxlKR42t6XPOEmMmqcAsU1g==", - "dev": true, - "requires": { - "bindings": "~1.2.1", - "nan": "^2.0.7" - } - }, "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", diff --git a/package.json b/package.json index 5e4a1a741eb98..3d832fc95c1dd 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,6 @@ "cross-env": "3.2.4", "cssnano": "4.0.3", "enzyme": "3.7.0", - "deasync": "0.1.13", "deep-freeze": "0.0.1", "doctrine": "2.1.0", "eslint-plugin-jest": "21.5.0", From 97f8b3d3064232bab69b6d20259c88750c040af3 Mon Sep 17 00:00:00 2001 From: Mike Selander <mikeselander@gmail.com> Date: Mon, 7 Jan 2019 00:47:03 -0700 Subject: [PATCH 103/691] Add unique class to Page Attributes Parent TreeSelector Component (#13167) --- packages/editor/src/components/page-attributes/parent.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/editor/src/components/page-attributes/parent.js b/packages/editor/src/components/page-attributes/parent.js index ca0a4acaf4246..6032794e75422 100644 --- a/packages/editor/src/components/page-attributes/parent.js +++ b/packages/editor/src/components/page-attributes/parent.js @@ -31,6 +31,7 @@ export function PageAttributesParent( { parent, postType, items, onUpdateParent } ) ) ); return ( <TreeSelect + className="editor-page-attributes__parent" label={ parentPageLabel } noOptionLabel={ `(${ __( 'no parent' ) })` } tree={ pagesTree } From f0ca6a6a1b90acd74371e798f8413733683a9dc7 Mon Sep 17 00:00:00 2001 From: Zebulan Stanphill <zebulanstanphill@gmail.com> Date: Mon, 7 Jan 2019 02:02:50 -0600 Subject: [PATCH 104/691] Cover block: use supports flag for block align (#10758) --- packages/block-library/src/cover/index.js | 31 ++++++++--------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/packages/block-library/src/cover/index.js b/packages/block-library/src/cover/index.js index 09df032d9418d..b1ceb293c336f 100644 --- a/packages/block-library/src/cover/index.js +++ b/packages/block-library/src/cover/index.js @@ -23,7 +23,6 @@ import { compose } from '@wordpress/compose'; import { BlockControls, InspectorControls, - BlockAlignmentToolbar, MediaPlaceholder, MediaUpload, MediaUploadCheck, @@ -34,8 +33,6 @@ import { getColorClassName, } from '@wordpress/editor'; -const validAlignments = [ 'left', 'center', 'right', 'wide', 'full' ]; - const blockAttributes = { title: { type: 'string', @@ -45,9 +42,6 @@ const blockAttributes = { url: { type: 'string', }, - align: { - type: 'string', - }, contentAlign: { type: 'string', default: 'center', @@ -92,6 +86,10 @@ export const settings = { attributes: blockAttributes, + supports: { + align: true, + }, + transforms: { from: [ { @@ -168,20 +166,12 @@ export const settings = { ], }, - getEditWrapperProps( attributes ) { - const { align } = attributes; - if ( -1 !== validAlignments.indexOf( align ) ) { - return { 'data-align': align }; - } - }, - edit: compose( [ withColors( { overlayColor: 'background-color' } ), withNotices, ] )( ( { attributes, setAttributes, isSelected, className, noticeOperations, noticeUI, overlayColor, setOverlayColor } ) => { const { - align, backgroundType, contentAlign, dimRatio, @@ -190,7 +180,6 @@ export const settings = { title, url, } = attributes; - const updateAlignment = ( nextAlign ) => setAttributes( { align: nextAlign } ); const onSelectMedia = ( media ) => { if ( ! media || ! media.url ) { setAttributes( { url: undefined, id: undefined } ); @@ -238,10 +227,6 @@ export const settings = { const controls = ( <Fragment> <BlockControls> - <BlockAlignmentToolbar - value={ align } - onChange={ updateAlignment } - /> { !! url && ( <Fragment> <AlignmentToolbar @@ -381,7 +366,6 @@ export const settings = { save( { attributes } ) { const { - align, backgroundType, contentAlign, customOverlayColor, @@ -407,7 +391,6 @@ export const settings = { 'has-parallax': hasParallax, [ `has-${ contentAlign }-content` ]: contentAlign !== 'center', }, - align ? `align${ align }` : null, ); return ( @@ -429,6 +412,9 @@ export const settings = { deprecated: [ { attributes: { ...blockAttributes, + align: { + type: 'string', + }, }, supports: { @@ -466,6 +452,9 @@ export const settings = { }, { attributes: { ...blockAttributes, + align: { + type: 'string', + }, title: { type: 'string', source: 'html', From a3f4362725f543c93390cd3fadfa36eb6bdcdc28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Wrede?= <soerenwrede@gmail.com> Date: Mon, 7 Jan 2019 09:05:39 +0100 Subject: [PATCH 105/691] codex link i18n (#12458) --- packages/editor/src/components/post-excerpt/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor/src/components/post-excerpt/index.js b/packages/editor/src/components/post-excerpt/index.js index 6d4419cabef11..c985e4cbdfca8 100644 --- a/packages/editor/src/components/post-excerpt/index.js +++ b/packages/editor/src/components/post-excerpt/index.js @@ -15,7 +15,7 @@ function PostExcerpt( { excerpt, onUpdateExcerpt } ) { onChange={ ( value ) => onUpdateExcerpt( value ) } value={ excerpt } /> - <ExternalLink href="https://codex.wordpress.org/Excerpt"> + <ExternalLink href={ __( 'https://codex.wordpress.org/Excerpt' ) }> { __( 'Learn more about manual excerpts' ) } </ExternalLink> </div> From 491174367c559130da945171877094b6e2648872 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Mon, 7 Jan 2019 08:06:55 +0000 Subject: [PATCH 106/691] Refactor getBlockContentSchema to not mutate its input (#12859) * Refactor getBlockContentSchema to not mutate its input. * Add first test case --- .../blocks/src/api/raw-handling/test/utils.js | 162 +++++++++++++++++- packages/blocks/src/api/raw-handling/utils.js | 32 ++-- 2 files changed, 178 insertions(+), 16 deletions(-) diff --git a/packages/blocks/src/api/raw-handling/test/utils.js b/packages/blocks/src/api/raw-handling/test/utils.js index 8f8e02a6520ed..66b0e8ffd0a5e 100644 --- a/packages/blocks/src/api/raw-handling/test/utils.js +++ b/packages/blocks/src/api/raw-handling/test/utils.js @@ -1,8 +1,31 @@ + +/** + * External dependencies + */ +import deepFreeze from 'deep-freeze'; + /** * Internal dependencies */ import { getPhrasingContentSchema } from '../phrasing-content'; -import { isEmpty, isPlain, removeInvalidHTML } from '../utils'; +import { getBlockContentSchema, isEmpty, isPlain, removeInvalidHTML } from '../utils'; + +jest.mock( '@wordpress/data', () => { + return { + select: jest.fn( ( store ) => { + switch ( store ) { + case 'core/blocks': { + return { + hasBlockSupport: ( blockName, supports ) => { + return blockName === 'core/paragraph' && + supports === 'anchor'; + }, + }; + } + } + } ), + }; +} ); describe( 'isEmpty', () => { function isEmptyHTML( HTML ) { @@ -164,3 +187,140 @@ describe( 'removeInvalidHTML', () => { expect( removeInvalidHTML( input, schema ) ).toBe( output ); } ); } ); + +describe( 'getBlockContentSchema', () => { + const myContentSchema = { + strong: {}, + em: {}, + }; + + it( 'should handle a single raw transform', () => { + const transforms = deepFreeze( [ { + blockName: 'core/paragraph', + type: 'raw', + selector: 'p', + schema: { + p: { + children: myContentSchema, + }, + }, + } ] ); + const output = { + p: { + children: myContentSchema, + attributes: [ 'id' ], + isMatch: undefined, + }, + }; + expect( + getBlockContentSchema( transforms ) + ).toEqual( output ); + } ); + + it( 'should handle multiple raw transforms', () => { + const preformattedIsMatch = ( input ) => { + return input === 4; + }; + const transforms = deepFreeze( [ { + blockName: 'core/paragraph', + type: 'raw', + schema: { + p: { + children: myContentSchema, + }, + }, + }, { + blockName: 'core/preformatted', + type: 'raw', + isMatch: preformattedIsMatch, + schema: { + pre: { + children: myContentSchema, + }, + }, + } ] ); + const output = { + p: { + children: myContentSchema, + attributes: [ 'id' ], + isMatch: undefined, + }, + pre: { + children: myContentSchema, + attributes: [], + isMatch: preformattedIsMatch, + }, + }; + expect( + getBlockContentSchema( transforms ) + ).toEqual( output ); + } ); + + it( 'should correctly merge the children', () => { + const transforms = deepFreeze( [ { + blockName: 'my/preformatted', + type: 'raw', + schema: { + pre: { + children: { + sub: {}, + sup: {}, + strong: {}, + }, + }, + }, + }, { + blockName: 'core/preformatted', + type: 'raw', + schema: { + pre: { + children: myContentSchema, + }, + }, + } ] ); + const output = { + pre: { + children: { + strong: {}, + em: {}, + sub: {}, + sup: {}, + }, + }, + }; + expect( + getBlockContentSchema( transforms ) + ).toEqual( output ); + } ); + + it( 'should correctly merge the attributes', () => { + const transforms = deepFreeze( [ { + blockName: 'my/preformatted', + type: 'raw', + schema: { + pre: { + attributes: [ 'data-chicken' ], + children: myContentSchema, + }, + }, + }, { + blockName: 'core/preformatted', + type: 'raw', + schema: { + pre: { + attributes: [ 'data-ribs' ], + children: myContentSchema, + }, + }, + } ] ); + const output = { + pre: { + children: myContentSchema, + attributes: [ 'data-chicken', 'data-ribs' ], + }, + }; + expect( + getBlockContentSchema( transforms ) + ).toEqual( output ); + } ); +} ); diff --git a/packages/blocks/src/api/raw-handling/utils.js b/packages/blocks/src/api/raw-handling/utils.js index 1859da6493a58..82f0903824a76 100644 --- a/packages/blocks/src/api/raw-handling/utils.js +++ b/packages/blocks/src/api/raw-handling/utils.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { mergeWith, includes, noop } from 'lodash'; +import { mapValues, mergeWith, includes, noop } from 'lodash'; /** * WordPress dependencies @@ -28,23 +28,25 @@ const { ELEMENT_NODE, TEXT_NODE } = window.Node; */ export function getBlockContentSchema( transforms ) { const schemas = transforms.map( ( { isMatch, blockName, schema } ) => { - // If the block supports the "anchor" functionality, it needs to keep its ID attribute. - if ( hasBlockSupport( blockName, 'anchor' ) ) { - for ( const tag in schema ) { - if ( ! schema[ tag ].attributes ) { - schema[ tag ].attributes = []; - } - schema[ tag ].attributes.push( 'id' ); - } + const hasAnchorSupport = hasBlockSupport( blockName, 'anchor' ); + // If the block does not has anchor support and the transform does not + // provides an isMatch we can return the schema right away. + if ( ! hasAnchorSupport && ! isMatch ) { + return schema; } - // If an isMatch function exists add it to each schema tag that it applies to. - if ( isMatch ) { - for ( const tag in schema ) { - schema[ tag ].isMatch = isMatch; + return mapValues( schema, ( value ) => { + let attributes = value.attributes || []; + // If the block supports the "anchor" functionality, it needs to keep its ID attribute. + if ( hasAnchorSupport ) { + attributes = [ ...attributes, 'id' ]; } - } - return schema; + return { + ...value, + attributes, + isMatch: isMatch ? isMatch : undefined, + }; + } ); } ); return mergeWith( {}, ...schemas, ( objValue, srcValue, key ) => { From da8542d9307ddbfbd7904eaa256c7f2353a75f33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Mon, 7 Jan 2019 10:20:01 +0100 Subject: [PATCH 107/691] Testing: Refactor public API of e2e test utils (#13188) * Tests: Use CLI options rather than custom config for e2e tests run * Refactor public API of e2e test utils * Fix createURL method signature * Apply suggestions from code review Co-Authored-By: gziolo <grzegorz@gziolo.pl> * Apply suggestions from code review Co-Authored-By: gziolo <grzegorz@gziolo.pl> * Tests: Fix Travis jobs with e2e tests * Tests: Revert util method name to saveDraft --- .travis.yml | 8 +-- package.json | 2 +- test/e2e/jest.config.json | 11 --- test/e2e/specs/a11y.test.js | 16 ++--- test/e2e/specs/adding-blocks.test.js | 12 ++-- test/e2e/specs/adding-inline-tokens.test.js | 4 +- test/e2e/specs/align-hook.test.js | 13 ++-- test/e2e/specs/annotations.test.js | 7 +- test/e2e/specs/block-deletion.test.js | 10 +-- .../specs/block-hierarchy-navigation.test.js | 28 ++++---- test/e2e/specs/block-icons.test.js | 11 +-- test/e2e/specs/block-mover.test.js | 4 +- test/e2e/specs/block-switcher.test.js | 12 ++-- test/e2e/specs/blocks/classic.test.js | 10 +-- test/e2e/specs/blocks/code.test.js | 4 +- test/e2e/specs/blocks/heading.test.js | 4 +- test/e2e/specs/blocks/html.test.js | 4 +- test/e2e/specs/blocks/list.test.js | 36 +++++----- test/e2e/specs/blocks/quote.test.js | 34 +++++----- test/e2e/specs/blocks/separator.test.js | 4 +- test/e2e/specs/change-detection.test.js | 26 +++---- .../compatibility-classic-editor.test.js | 4 +- test/e2e/specs/container-blocks.test.js | 17 ++--- test/e2e/specs/convert-block-type.test.js | 8 +-- test/e2e/specs/datepicker.test.js | 4 +- test/e2e/specs/demo.test.js | 6 +- .../e2e/specs/deprecated-node-matcher.test.js | 13 ++-- test/e2e/specs/editor-modes.test.js | 6 +- test/e2e/specs/embedding.test.js | 40 +++++------ test/e2e/specs/font-size-picker.test.js | 10 +-- test/e2e/specs/format-api.test.js | 13 ++-- test/e2e/specs/fullscreen-mode.test.js | 4 +- test/e2e/specs/hooks-api.test.js | 7 +- test/e2e/specs/invalid-block.test.js | 4 +- test/e2e/specs/links.test.js | 68 +++++++++---------- test/e2e/specs/manage-reusable-blocks.test.js | 6 +- test/e2e/specs/mentions.test.js | 4 +- test/e2e/specs/meta-attribute-block.test.js | 9 +-- test/e2e/specs/meta-boxes.test.js | 15 ++-- test/e2e/specs/multi-block-selection.test.js | 16 ++--- test/e2e/specs/navigable-toolbar.test.js | 6 +- .../specs/new-post-default-content.test.js | 12 +++- test/e2e/specs/new-post.test.js | 11 +-- test/e2e/specs/nux.test.js | 8 +-- test/e2e/specs/plugins-api.test.js | 7 +- test/e2e/specs/popovers.test.js | 4 +- test/e2e/specs/post-visibility.test.js | 8 +-- test/e2e/specs/preferences.test.js | 4 +- test/e2e/specs/preview.test.js | 8 +-- test/e2e/specs/publish-button.test.js | 4 +- test/e2e/specs/publish-panel.test.js | 8 +-- test/e2e/specs/publishing.test.js | 18 ++--- test/e2e/specs/reusable-blocks.test.js | 12 ++-- test/e2e/specs/rich-text.test.js | 24 +++---- test/e2e/specs/shortcut-help.test.js | 10 +-- .../e2e/specs/sidebar-permalink-panel.test.js | 15 ++-- test/e2e/specs/sidebar.test.js | 40 +++++------ test/e2e/specs/splitting-merging.test.js | 26 +++---- test/e2e/specs/style-variation.test.js | 4 +- test/e2e/specs/taxonomies.test.js | 4 +- test/e2e/specs/templates.test.js | 35 +++++----- test/e2e/specs/undo.test.js | 24 +++---- test/e2e/specs/wp-editor-meta-box.test.js | 10 ++- test/e2e/specs/writing-flow.test.js | 50 +++++++------- test/e2e/support/plugins.js | 63 ----------------- test/e2e/support/setup-test-framework.js | 8 +-- test/e2e/support/utils/accept-page-dialog.js | 8 --- test/e2e/support/utils/activate-plugin.js | 19 ++++++ .../utils/{new-post.js => create-new-post.js} | 6 +- .../utils/{get-url.js => create-url.js} | 4 +- test/e2e/support/utils/deactivate-plugin.js | 19 ++++++ .../utils/disable-pre-publish-checks.js | 4 +- .../utils/enable-page-dialog-accept.js | 9 ++- .../utils/enable-pre-publish-checks.js | 4 +- test/e2e/support/utils/go-to-wp-path.js | 14 ---- test/e2e/support/utils/index.js | 39 ++++++----- test/e2e/support/utils/install-plugin.js | 20 ++++++ test/e2e/support/utils/is-current-url.js | 24 +++++++ test/e2e/support/utils/is-embedding.js | 17 ----- test/e2e/support/utils/is-wp-path.js | 24 ------- test/e2e/support/utils/login-user.js | 31 +++++++++ test/e2e/support/utils/login.js | 23 ------- .../utils/mocks/create-embedding-matcher.js | 34 ++++++++++ .../create-json-response.js} | 4 +- .../create-url-matcher.js} | 2 +- test/e2e/support/utils/mocks/index.js | 5 ++ .../utils/{ => mocks}/mock-or-transform.js | 2 +- .../{ => mocks}/set-up-response-mocking.js | 0 test/e2e/support/utils/parameter-equals.js | 17 ----- test/e2e/support/utils/press-key-times.js | 31 +++++++++ ...modifier.js => press-key-with-modifier.js} | 2 +- test/e2e/support/utils/press-times.js | 21 ------ test/e2e/support/utils/promise-sequence.js | 14 ---- ...-post-with-pre-publish-checks-disabled.js} | 2 +- ...et-viewport.js => set-browser-viewport.js} | 6 +- test/e2e/support/utils/{ => shared}/config.js | 0 .../utils/{ => shared}/get-json-response.js | 0 ...-to-editor.js => switch-editor-mode-to.js} | 2 +- .../e2e/support/utils/switch-to-admin-user.js | 18 ----- test/e2e/support/utils/switch-to-test-user.js | 18 ----- .../e2e/support/utils/switch-user-to-admin.js | 16 +++++ test/e2e/support/utils/switch-user-to-test.js | 16 +++++ ...ggle-option.js => toggle-screen-option.js} | 2 +- ...convert-block.js => transform-block-to.js} | 2 +- test/e2e/support/utils/uninstall-plugin.js | 25 +++++++ test/e2e/support/utils/visit-admin-page.js | 28 ++++++++ test/e2e/support/utils/visit-admin.js | 25 ------- ...sions.js => wait-for-window-dimensions.js} | 2 +- 108 files changed, 763 insertions(+), 733 deletions(-) delete mode 100644 test/e2e/jest.config.json delete mode 100644 test/e2e/support/plugins.js delete mode 100644 test/e2e/support/utils/accept-page-dialog.js create mode 100644 test/e2e/support/utils/activate-plugin.js rename test/e2e/support/utils/{new-post.js => create-new-post.js} (82%) rename test/e2e/support/utils/{get-url.js => create-url.js} (83%) create mode 100644 test/e2e/support/utils/deactivate-plugin.js delete mode 100644 test/e2e/support/utils/go-to-wp-path.js create mode 100644 test/e2e/support/utils/install-plugin.js create mode 100644 test/e2e/support/utils/is-current-url.js delete mode 100644 test/e2e/support/utils/is-embedding.js delete mode 100644 test/e2e/support/utils/is-wp-path.js create mode 100644 test/e2e/support/utils/login-user.js delete mode 100644 test/e2e/support/utils/login.js create mode 100644 test/e2e/support/utils/mocks/create-embedding-matcher.js rename test/e2e/support/utils/{json-response.js => mocks/create-json-response.js} (74%) rename test/e2e/support/utils/{match-url.js => mocks/create-url-matcher.js} (86%) create mode 100644 test/e2e/support/utils/mocks/index.js rename test/e2e/support/utils/{ => mocks}/mock-or-transform.js (95%) rename test/e2e/support/utils/{ => mocks}/set-up-response-mocking.js (100%) delete mode 100644 test/e2e/support/utils/parameter-equals.js create mode 100644 test/e2e/support/utils/press-key-times.js rename test/e2e/support/utils/{press-with-modifier.js => press-key-with-modifier.js} (94%) delete mode 100644 test/e2e/support/utils/press-times.js delete mode 100644 test/e2e/support/utils/promise-sequence.js rename test/e2e/support/utils/{publish-post-without-pre-publish-checks.js => publish-post-with-pre-publish-checks-disabled.js} (82%) rename test/e2e/support/utils/{set-viewport.js => set-browser-viewport.js} (65%) rename test/e2e/support/utils/{ => shared}/config.js (100%) rename test/e2e/support/utils/{ => shared}/get-json-response.js (100%) rename test/e2e/support/utils/{switch-to-editor.js => switch-editor-mode-to.js} (85%) delete mode 100644 test/e2e/support/utils/switch-to-admin-user.js delete mode 100644 test/e2e/support/utils/switch-to-test-user.js create mode 100644 test/e2e/support/utils/switch-user-to-admin.js create mode 100644 test/e2e/support/utils/switch-user-to-test.js rename test/e2e/support/utils/{toggle-option.js => toggle-screen-option.js} (90%) rename test/e2e/support/utils/{convert-block.js => transform-block-to.js} (86%) create mode 100644 test/e2e/support/utils/uninstall-plugin.js create mode 100644 test/e2e/support/utils/visit-admin-page.js delete mode 100644 test/e2e/support/utils/visit-admin.js rename test/e2e/support/utils/{wait-for-page-dimensions.js => wait-for-window-dimensions.js} (88%) diff --git a/.travis.yml b/.travis.yml index 6fcc4d931c194..192c1b00f9959 100644 --- a/.travis.yml +++ b/.travis.yml @@ -67,7 +67,7 @@ jobs: install: - ./bin/setup-local-env.sh script: - - $( npm bin )/jest --config test/e2e/jest.config.json --listTests > ~/.jest-e2e-tests + - $( npm bin )/wp-scripts test-e2e --testPathPattern=/test/e2e/specs/ --listTests > ~/.jest-e2e-tests - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 2 == 0' < ~/.jest-e2e-tests ) - name: E2E tests (Admin with plugins) (2/2) @@ -75,7 +75,7 @@ jobs: install: - ./bin/setup-local-env.sh script: - - $( npm bin )/jest --config test/e2e/jest.config.json --listTests > ~/.jest-e2e-tests + - $( npm bin )/wp-scripts test-e2e --testPathPattern=/test/e2e/specs/ --listTests > ~/.jest-e2e-tests - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 2 == 1' < ~/.jest-e2e-tests ) - name: E2E tests (Author without plugins) (1/2) @@ -83,7 +83,7 @@ jobs: install: - ./bin/setup-local-env.sh script: - - $( npm bin )/jest --config test/e2e/jest.config.json --listTests > ~/.jest-e2e-tests + - $( npm bin )/wp-scripts test-e2e --testPathPattern=/test/e2e/specs/ --listTests > ~/.jest-e2e-tests - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 2 == 0' < ~/.jest-e2e-tests ) - name: E2E tests (Author without plugins) (2/2) @@ -91,5 +91,5 @@ jobs: install: - ./bin/setup-local-env.sh script: - - $( npm bin )/jest --config test/e2e/jest.config.json --listTests > ~/.jest-e2e-tests + - $( npm bin )/wp-scripts test-e2e --testPathPattern=/test/e2e/specs/ --listTests > ~/.jest-e2e-tests - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 2 == 1' < ~/.jest-e2e-tests ) diff --git a/package.json b/package.json index 3d832fc95c1dd..f908971125040 100644 --- a/package.json +++ b/package.json @@ -175,7 +175,7 @@ "publish:prod": "npm run build:packages && lerna publish", "test": "npm run lint && npm run test-unit", "pretest-e2e": "concurrently \"./bin/reset-e2e-tests.sh\" \"npm run build\"", - "test-e2e": "wp-scripts test-e2e --config test/e2e/jest.config.json", + "test-e2e": "wp-scripts test-e2e --setupTestFrameworkScriptFile=./test/e2e/support/setup-test-framework.js --testPathPattern=/test/e2e/specs/", "test-e2e:watch": "npm run test-e2e -- --watch", "test-php": "npm run lint-php && npm run test-unit-php", "test-unit": "wp-scripts test-unit-js --config test/unit/jest.config.json", diff --git a/test/e2e/jest.config.json b/test/e2e/jest.config.json deleted file mode 100644 index 354879e3d358a..0000000000000 --- a/test/e2e/jest.config.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "rootDir": "../../", - "preset": "jest-puppeteer", - "setupTestFrameworkScriptFile": "<rootDir>/test/e2e/support/setup-test-framework.js", - "testMatch": [ - "<rootDir>/test/e2e/specs/**/(*.)test.js" - ], - "transform": { - "^.+\\.jsx?$": "<rootDir>/node_modules/babel-jest" - } -} diff --git a/test/e2e/specs/a11y.test.js b/test/e2e/specs/a11y.test.js index 19403fe69e9e4..93745d38116c2 100644 --- a/test/e2e/specs/a11y.test.js +++ b/test/e2e/specs/a11y.test.js @@ -2,8 +2,8 @@ * Internal dependencies */ import { - newPost, - pressWithModifier, + createNewPost, + pressKeyWithModifier, } from '../support/utils'; function isCloseButtonFocused() { @@ -14,11 +14,11 @@ function isCloseButtonFocused() { describe( 'a11y', () => { beforeEach( async () => { - await newPost(); + await createNewPost(); } ); it( 'tabs header bar', async () => { - await pressWithModifier( 'ctrl', '~' ); + await pressKeyWithModifier( 'ctrl', '~' ); await page.keyboard.press( 'Tab' ); @@ -31,7 +31,7 @@ describe( 'a11y', () => { it( 'constrains focus to a modal when tabbing', async () => { // Open keyboard help modal. - await pressWithModifier( 'access', 'h' ); + await pressKeyWithModifier( 'access', 'h' ); // The close button should not be focused by default; this is a strange UX // experience. @@ -45,7 +45,7 @@ describe( 'a11y', () => { } ); it( 'returns focus to the first tabbable in a modal after blurring a tabbable', async () => { - await pressWithModifier( 'access', 'h' ); + await pressKeyWithModifier( 'access', 'h' ); // Click to move focus to an element after the last tabbable within the // modal. @@ -57,13 +57,13 @@ describe( 'a11y', () => { } ); it( 'returns focus to the last tabbable in a modal after blurring a tabbable and tabbing in reverse direction', async () => { - await pressWithModifier( 'access', 'h' ); + await pressKeyWithModifier( 'access', 'h' ); // Click to move focus to an element before the first tabbable within // the modal. await page.click( '.components-modal__header-heading' ); - await pressWithModifier( 'shift', 'Tab' ); + await pressKeyWithModifier( 'shift', 'Tab' ); expect( await isCloseButtonFocused() ).toBe( true ); } ); diff --git a/test/e2e/specs/adding-blocks.test.js b/test/e2e/specs/adding-blocks.test.js index b46bff8600254..a9688ff154deb 100644 --- a/test/e2e/specs/adding-blocks.test.js +++ b/test/e2e/specs/adding-blocks.test.js @@ -2,15 +2,15 @@ * Internal dependencies */ import { - newPost, + createNewPost, insertBlock, getEditedPostContent, - pressTimes, + pressKeyTimes, } from '../support/utils'; describe( 'adding blocks', () => { beforeEach( async () => { - await newPost(); + await createNewPost(); } ); /** @@ -66,8 +66,8 @@ describe( 'adding blocks', () => { await page.keyboard.type( 'Foo' ); await page.keyboard.press( 'ArrowUp' ); await page.keyboard.press( 'ArrowUp' ); - await pressTimes( 'ArrowRight', 3 ); - await pressTimes( 'Delete', 6 ); + await pressKeyTimes( 'ArrowRight', 3 ); + await pressKeyTimes( 'Delete', 6 ); await page.keyboard.type( ' text' ); // Ensure newline preservation in shortcode block. @@ -93,7 +93,7 @@ describe( 'adding blocks', () => { document.activeElement.classList.contains( 'editor-inserter__search' ) ) ); await page.keyboard.type( 'para' ); - await pressTimes( 'Tab', 3 ); + await pressKeyTimes( 'Tab', 3 ); await page.keyboard.press( 'Enter' ); await page.keyboard.type( 'Second paragraph' ); diff --git a/test/e2e/specs/adding-inline-tokens.test.js b/test/e2e/specs/adding-inline-tokens.test.js index d1504f7e35eb7..14f504d1db471 100644 --- a/test/e2e/specs/adding-inline-tokens.test.js +++ b/test/e2e/specs/adding-inline-tokens.test.js @@ -13,12 +13,12 @@ import { clickBlockAppender, getEditedPostContent, insertBlock, - newPost, + createNewPost, } from '../support/utils'; describe( 'adding inline tokens', () => { beforeAll( async () => { - await newPost(); + await createNewPost(); } ); it( 'should insert inline image', async () => { diff --git a/test/e2e/specs/align-hook.test.js b/test/e2e/specs/align-hook.test.js index 252d5692a1379..d22725cdc62e5 100644 --- a/test/e2e/specs/align-hook.test.js +++ b/test/e2e/specs/align-hook.test.js @@ -2,14 +2,15 @@ * Internal dependencies */ import { - newPost, - insertBlock, - getEditedPostContent, - setPostContent, + activatePlugin, + createNewPost, + deactivatePlugin, getAllBlocks, + getEditedPostContent, + insertBlock, selectBlockByClientId, + setPostContent, } from '../support/utils'; -import { activatePlugin, deactivatePlugin } from '../support/plugins'; describe( 'Align Hook Works As Expected', () => { beforeAll( async () => { @@ -17,7 +18,7 @@ describe( 'Align Hook Works As Expected', () => { } ); beforeEach( async () => { - await newPost(); + await createNewPost(); } ); afterAll( async () => { diff --git a/test/e2e/specs/annotations.test.js b/test/e2e/specs/annotations.test.js index 3fa1eefdec9dd..3cdd39877afb1 100644 --- a/test/e2e/specs/annotations.test.js +++ b/test/e2e/specs/annotations.test.js @@ -2,10 +2,11 @@ * Internal dependencies */ import { + activatePlugin, clickOnMoreMenuItem, - newPost, + createNewPost, + deactivatePlugin, } from '../support/utils'; -import { activatePlugin, deactivatePlugin } from '../support/plugins'; const clickOnBlockSettingsMenuItem = async ( buttonLabel ) => { await expect( page ).toClick( '.editor-block-settings-menu__toggle' ); @@ -25,7 +26,7 @@ describe( 'Using Plugins API', () => { } ); beforeEach( async () => { - await newPost(); + await createNewPost(); } ); /** diff --git a/test/e2e/specs/block-deletion.test.js b/test/e2e/specs/block-deletion.test.js index 6b8c97fc12dce..76e9d94d2616d 100644 --- a/test/e2e/specs/block-deletion.test.js +++ b/test/e2e/specs/block-deletion.test.js @@ -4,12 +4,12 @@ import { clickBlockAppender, getEditedPostContent, - newPost, - pressWithModifier, + createNewPost, + pressKeyWithModifier, } from '../support/utils'; const addThreeParagraphsToNewPost = async () => { - await newPost(); + await createNewPost(); // Add demo content await clickBlockAppender(); @@ -49,7 +49,7 @@ describe( 'block deletion -', () => { it( 'results in two remaining blocks and positions the caret at the end of the second block', async () => { // Type some text to assert that the shortcut also deletes block content. await page.keyboard.type( 'this is block 2' ); - await pressWithModifier( 'access', 'z' ); + await pressKeyWithModifier( 'access', 'z' ); expect( await getEditedPostContent() ).toMatchSnapshot(); // Type additional text and assert that caret position is correct by comparing to snapshot. @@ -97,7 +97,7 @@ describe( 'block deletion -', () => { await page.keyboard.press( 'Enter' ); // Press the up arrow once to select the third and fourth blocks. - await pressWithModifier( 'shift', 'ArrowUp' ); + await pressKeyWithModifier( 'shift', 'ArrowUp' ); // Now that the block wrapper is selected, press backspace to delete it. await page.keyboard.press( 'Backspace' ); diff --git a/test/e2e/specs/block-hierarchy-navigation.test.js b/test/e2e/specs/block-hierarchy-navigation.test.js index 2ce629b1f12e4..377e6b963c922 100644 --- a/test/e2e/specs/block-hierarchy-navigation.test.js +++ b/test/e2e/specs/block-hierarchy-navigation.test.js @@ -2,20 +2,20 @@ * Internal dependencies */ import { - newPost, + createNewPost, insertBlock, getEditedPostContent, - pressTimes, - pressWithModifier, + pressKeyTimes, + pressKeyWithModifier, } from '../support/utils'; async function openBlockNavigator() { - return pressWithModifier( 'access', 'o' ); + return pressKeyWithModifier( 'access', 'o' ); } describe( 'Navigating the block hierarchy', () => { beforeEach( async () => { - await newPost(); + await createNewPost(); } ); it( 'should navigate using the block hierarchy dropdown menu', async () => { @@ -44,7 +44,7 @@ describe( 'Navigating the block hierarchy', () => { await lastColumnsBlockMenuItem.click(); // Insert text in the last column block. - await pressTimes( 'Tab', 2 ); // Navigate to the appender. + await pressKeyTimes( 'Tab', 2 ); // Navigate to the appender. await page.keyboard.type( 'Third column' ); expect( await getEditedPostContent() ).toMatchSnapshot(); @@ -61,22 +61,22 @@ describe( 'Navigating the block hierarchy', () => { await page.keyboard.press( 'Enter' ); // Move focus to the sidebar area. - await pressWithModifier( 'ctrl', '`' ); - await pressWithModifier( 'ctrl', '`' ); - await pressWithModifier( 'ctrl', '`' ); - await pressWithModifier( 'ctrl', '`' ); - await pressTimes( 'Tab', 4 ); + await pressKeyWithModifier( 'ctrl', '`' ); + await pressKeyWithModifier( 'ctrl', '`' ); + await pressKeyWithModifier( 'ctrl', '`' ); + await pressKeyWithModifier( 'ctrl', '`' ); + await pressKeyTimes( 'Tab', 4 ); // Tweak the columns count by increasing it by one. page.keyboard.press( 'ArrowRight' ); // Navigate to the last column in the columns block. await openBlockNavigator(); - await pressTimes( 'Tab', 4 ); + await pressKeyTimes( 'Tab', 4 ); await page.keyboard.press( 'Enter' ); // Insert text in the last column block - await pressTimes( 'Tab', 2 ); // Navigate to the appender. + await pressKeyTimes( 'Tab', 2 ); // Navigate to the appender. await page.keyboard.type( 'Third column' ); expect( await getEditedPostContent() ).toMatchSnapshot(); @@ -97,7 +97,7 @@ describe( 'Navigating the block hierarchy', () => { await page.keyboard.press( 'Space' ); // Replace its content. - await pressWithModifier( 'primary', 'A' ); + await pressKeyWithModifier( 'primary', 'A' ); await page.keyboard.type( 'and I say hello' ); expect( await getEditedPostContent() ).toMatchSnapshot(); diff --git a/test/e2e/specs/block-icons.test.js b/test/e2e/specs/block-icons.test.js index 4042b05a6f700..c523bb156ea80 100644 --- a/test/e2e/specs/block-icons.test.js +++ b/test/e2e/specs/block-icons.test.js @@ -2,12 +2,13 @@ * Internal dependencies */ import { - pressWithModifier, - newPost, + activatePlugin, + createNewPost, + deactivatePlugin, insertBlock, + pressKeyWithModifier, searchForBlock, } from '../support/utils'; -import { activatePlugin, deactivatePlugin } from '../support/plugins'; const INSERTER_BUTTON_SELECTOR = '.components-popover__content .editor-block-types-list__item'; const INSERTER_ICON_WRAPPER_SELECTOR = `${ INSERTER_BUTTON_SELECTOR } .editor-block-types-list__item-icon`; @@ -35,7 +36,7 @@ async function getFirstInserterIcon() { } async function selectFirstBlock() { - await pressWithModifier( 'access', 'o' ); + await pressKeyWithModifier( 'access', 'o' ); const navButtons = await page.$$( '.editor-block-navigation__item-button' ); await navButtons[ 0 ].click(); } @@ -58,7 +59,7 @@ describe( 'Correctly Renders Block Icons on Inserter and Inspector', () => { } ); beforeEach( async () => { - await newPost(); + await createNewPost(); } ); afterAll( async () => { diff --git a/test/e2e/specs/block-mover.test.js b/test/e2e/specs/block-mover.test.js index e53ccdd753564..05a204f5afd74 100644 --- a/test/e2e/specs/block-mover.test.js +++ b/test/e2e/specs/block-mover.test.js @@ -1,11 +1,11 @@ /** * Internal dependencies */ -import { newPost } from '../support/utils'; +import { createNewPost } from '../support/utils'; describe( 'block mover', () => { beforeEach( async () => { - await newPost(); + await createNewPost(); } ); it( 'should show block mover when more than one block exists', async () => { diff --git a/test/e2e/specs/block-switcher.test.js b/test/e2e/specs/block-switcher.test.js index 953117171894a..50cd91db7358d 100644 --- a/test/e2e/specs/block-switcher.test.js +++ b/test/e2e/specs/block-switcher.test.js @@ -4,21 +4,21 @@ import { hasBlockSwitcher, getAvailableBlockTransforms, - newPost, + createNewPost, insertBlock, - pressWithModifier, + pressKeyWithModifier, } from '../support/utils'; describe( 'adding blocks', () => { beforeEach( async () => { - await newPost(); + await createNewPost(); } ); it( 'Should show the expected block transforms on the list block when the blocks are removed', async () => { // Insert a list block. await insertBlock( 'List' ); await page.keyboard.type( 'List content' ); - await pressWithModifier( 'alt', 'F10' ); + await pressKeyWithModifier( 'alt', 'F10' ); // Verify the block switcher exists. expect( await hasBlockSwitcher() ).toBeTruthy(); @@ -41,7 +41,7 @@ describe( 'adding blocks', () => { // Insert a list block. await insertBlock( 'List' ); await page.keyboard.type( 'List content' ); - await pressWithModifier( 'alt', 'F10' ); + await pressKeyWithModifier( 'alt', 'F10' ); // Verify the block switcher exists. expect( await hasBlockSwitcher() ).toBeTruthy(); @@ -66,7 +66,7 @@ describe( 'adding blocks', () => { // Insert a list block. await insertBlock( 'List' ); await page.keyboard.type( 'List content' ); - await pressWithModifier( 'alt', 'F10' ); + await pressKeyWithModifier( 'alt', 'F10' ); // Verify the block switcher exists. expect( await hasBlockSwitcher() ).toBeFalsy(); diff --git a/test/e2e/specs/blocks/classic.test.js b/test/e2e/specs/blocks/classic.test.js index f0b733b4a9947..9c5b26de26773 100644 --- a/test/e2e/specs/blocks/classic.test.js +++ b/test/e2e/specs/blocks/classic.test.js @@ -11,14 +11,14 @@ import uuid from 'uuid/v4'; */ import { getEditedPostContent, - newPost, + createNewPost, insertBlock, - pressWithModifier, + pressKeyWithModifier, } from '../../support/utils'; describe( 'Classic', () => { beforeEach( async () => { - await newPost(); + await createNewPost(); } ); it( 'should be inserted', async () => { @@ -29,7 +29,7 @@ describe( 'Classic', () => { await page.focus( '.mce-content-body' ); await page.keyboard.type( 'test' ); // Move focus away. - await pressWithModifier( 'shift', 'Tab' ); + await pressKeyWithModifier( 'shift', 'Tab' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); @@ -65,7 +65,7 @@ describe( 'Classic', () => { await page.waitForSelector( '.mce-content-body img' ); // Move focus away. - await pressWithModifier( 'shift', 'Tab' ); + await pressKeyWithModifier( 'shift', 'Tab' ); const regExp = new RegExp( `test<img class="alignnone size-full wp-image-\\d+" src="[^"]+\\/${ filename }\\.png" alt="" width="10" height="10" \\/>` ); expect( await getEditedPostContent() ).toMatch( regExp ); diff --git a/test/e2e/specs/blocks/code.test.js b/test/e2e/specs/blocks/code.test.js index 85cdbe1dd715f..f3d9c8e1c4fc7 100644 --- a/test/e2e/specs/blocks/code.test.js +++ b/test/e2e/specs/blocks/code.test.js @@ -4,12 +4,12 @@ import { clickBlockAppender, getEditedPostContent, - newPost, + createNewPost, } from '../../support/utils'; describe( 'Code', () => { beforeEach( async () => { - await newPost(); + await createNewPost(); } ); it( 'can be created by three backticks and enter', async () => { diff --git a/test/e2e/specs/blocks/heading.test.js b/test/e2e/specs/blocks/heading.test.js index 101dbf8831736..284161ec76c40 100644 --- a/test/e2e/specs/blocks/heading.test.js +++ b/test/e2e/specs/blocks/heading.test.js @@ -4,12 +4,12 @@ import { clickBlockAppender, getEditedPostContent, - newPost, + createNewPost, } from '../../support/utils'; describe( 'Separator', () => { beforeEach( async () => { - await newPost(); + await createNewPost(); } ); it( 'can be created by prefixing number sign and a space', async () => { diff --git a/test/e2e/specs/blocks/html.test.js b/test/e2e/specs/blocks/html.test.js index 8f1996c00435a..2026a2fb1ae92 100644 --- a/test/e2e/specs/blocks/html.test.js +++ b/test/e2e/specs/blocks/html.test.js @@ -4,12 +4,12 @@ import { clickBlockAppender, getEditedPostContent, - newPost, + createNewPost, } from '../../support/utils'; describe( 'HTML block', () => { beforeEach( async () => { - await newPost(); + await createNewPost(); } ); it( 'can be created by typing "/html"', async () => { diff --git a/test/e2e/specs/blocks/list.test.js b/test/e2e/specs/blocks/list.test.js index f4bd5b29d9094..27b313d44081d 100644 --- a/test/e2e/specs/blocks/list.test.js +++ b/test/e2e/specs/blocks/list.test.js @@ -4,16 +4,16 @@ import { clickBlockAppender, getEditedPostContent, - newPost, - pressTimes, - convertBlock, - pressWithModifier, + createNewPost, + pressKeyTimes, + transformBlockTo, + pressKeyWithModifier, insertBlock, } from '../../support/utils'; describe( 'List', () => { beforeEach( async () => { - await newPost(); + await createNewPost(); } ); it( 'can be created by using an asterisk at the start of a paragraph block', async () => { @@ -32,7 +32,7 @@ describe( 'List', () => { // Create a list with the slash block shortcut. await clickBlockAppender(); await page.keyboard.type( 'test' ); - await pressTimes( 'ArrowLeft', 4 ); + await pressKeyTimes( 'ArrowLeft', 4 ); await page.keyboard.type( '* ' ); expect( await getEditedPostContent() ).toMatchSnapshot(); @@ -49,7 +49,7 @@ describe( 'List', () => { it( 'can undo asterisk transform', async () => { await clickBlockAppender(); await page.keyboard.type( '1. ' ); - await pressWithModifier( 'primary', 'z' ); + await pressKeyWithModifier( 'primary', 'z' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); @@ -67,7 +67,7 @@ describe( 'List', () => { it( 'can be created by converting a paragraph', async () => { await clickBlockAppender(); await page.keyboard.type( 'test' ); - await convertBlock( 'List' ); + await transformBlockTo( 'List' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); @@ -80,7 +80,7 @@ describe( 'List', () => { await page.keyboard.down( 'Shift' ); await page.click( '[data-type="core/paragraph"]' ); await page.keyboard.up( 'Shift' ); - await convertBlock( 'List' ); + await transformBlockTo( 'List' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); @@ -88,9 +88,9 @@ describe( 'List', () => { it( 'can be created by converting a paragraph with line breaks', async () => { await clickBlockAppender(); await page.keyboard.type( 'one' ); - await pressWithModifier( 'shift', 'Enter' ); + await pressKeyWithModifier( 'shift', 'Enter' ); await page.keyboard.type( 'two' ); - await convertBlock( 'List' ); + await transformBlockTo( 'List' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); @@ -100,7 +100,7 @@ describe( 'List', () => { await page.keyboard.type( 'one' ); await page.keyboard.press( 'Enter' ); await page.keyboard.type( 'two' ); - await convertBlock( 'Paragraph' ); + await transformBlockTo( 'Paragraph' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); @@ -115,7 +115,7 @@ describe( 'List', () => { await page.mouse.move( 250, 350, { steps: 10 } ); await page.click( 'button[aria-label="Indent list item"]' ); await page.keyboard.type( 'two' ); - await convertBlock( 'Paragraph' ); + await transformBlockTo( 'Paragraph' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); @@ -125,7 +125,7 @@ describe( 'List', () => { await page.keyboard.type( 'one' ); await page.keyboard.press( 'Enter' ); await page.keyboard.type( 'two' ); - await convertBlock( 'List' ); + await transformBlockTo( 'List' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); @@ -135,7 +135,7 @@ describe( 'List', () => { await page.keyboard.type( 'one' ); await page.keyboard.press( 'Enter' ); await page.keyboard.type( 'two' ); - await convertBlock( 'Quote' ); + await transformBlockTo( 'Quote' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); @@ -149,7 +149,7 @@ describe( 'List', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); await page.keyboard.type( 'two' ); - await pressTimes( 'ArrowLeft', 'two'.length ); + await pressKeyTimes( 'ArrowLeft', 'two'.length ); await page.keyboard.press( 'Backspace' ); expect( await getEditedPostContent() ).toMatchSnapshot(); @@ -174,7 +174,7 @@ describe( 'List', () => { // Should merge lists into one. await page.keyboard.press( 'ArrowDown' ); - await pressTimes( 'ArrowLeft', 'two'.length ); + await pressKeyTimes( 'ArrowLeft', 'two'.length ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); @@ -199,7 +199,7 @@ describe( 'List', () => { await insertBlock( 'List' ); await page.keyboard.type( 'one' ); await page.keyboard.press( 'Enter' ); - await pressWithModifier( 'primary', 'm' ); + await pressKeyWithModifier( 'primary', 'm' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); diff --git a/test/e2e/specs/blocks/quote.test.js b/test/e2e/specs/blocks/quote.test.js index f6d1fa3fbfd81..a5168f8cb743c 100644 --- a/test/e2e/specs/blocks/quote.test.js +++ b/test/e2e/specs/blocks/quote.test.js @@ -4,15 +4,15 @@ import { clickBlockAppender, getEditedPostContent, - newPost, - pressTimes, - convertBlock, + createNewPost, + pressKeyTimes, + transformBlockTo, insertBlock, } from '../../support/utils'; describe( 'Quote', () => { beforeEach( async () => { - await newPost(); + await createNewPost(); } ); it( 'can be created by using > at the start of a paragraph block', async () => { @@ -31,7 +31,7 @@ describe( 'Quote', () => { // Create a list with the slash block shortcut. await clickBlockAppender(); await page.keyboard.type( 'test' ); - await pressTimes( 'ArrowLeft', 4 ); + await pressKeyTimes( 'ArrowLeft', 4 ); await page.keyboard.type( '> ' ); expect( await getEditedPostContent() ).toMatchSnapshot(); @@ -50,7 +50,7 @@ describe( 'Quote', () => { it( 'can be created by converting a paragraph', async () => { await clickBlockAppender(); await page.keyboard.type( 'test' ); - await convertBlock( 'Quote' ); + await transformBlockTo( 'Quote' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); @@ -63,7 +63,7 @@ describe( 'Quote', () => { await page.keyboard.down( 'Shift' ); await page.click( '[data-type="core/paragraph"]' ); await page.keyboard.up( 'Shift' ); - await convertBlock( 'Quote' ); + await transformBlockTo( 'Quote' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); @@ -74,7 +74,7 @@ describe( 'Quote', () => { await page.keyboard.type( 'one' ); await page.keyboard.press( 'Enter' ); await page.keyboard.type( 'two' ); - await convertBlock( 'Paragraph' ); + await transformBlockTo( 'Paragraph' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); @@ -86,7 +86,7 @@ describe( 'Quote', () => { await page.keyboard.type( 'two' ); await page.keyboard.press( 'Tab' ); await page.keyboard.type( 'cite' ); - await convertBlock( 'Paragraph' ); + await transformBlockTo( 'Paragraph' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); @@ -95,14 +95,14 @@ describe( 'Quote', () => { await insertBlock( 'Quote' ); await page.keyboard.press( 'Tab' ); await page.keyboard.type( 'cite' ); - await convertBlock( 'Paragraph' ); + await transformBlockTo( 'Paragraph' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); it( 'and renders a void paragraph if both the cite and quote are void', async () => { await insertBlock( 'Quote' ); - await convertBlock( 'Paragraph' ); + await transformBlockTo( 'Paragraph' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); @@ -111,7 +111,7 @@ describe( 'Quote', () => { it( 'can be created by converting a heading', async () => { await insertBlock( 'Heading' ); await page.keyboard.type( 'test' ); - await convertBlock( 'Quote' ); + await transformBlockTo( 'Quote' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); @@ -123,13 +123,13 @@ describe( 'Quote', () => { await page.keyboard.type( 'two' ); await page.keyboard.press( 'Tab' ); await page.keyboard.type( 'cite' ); - await convertBlock( 'Heading' ); + await transformBlockTo( 'Heading' ); expect( await getEditedPostContent() ).toMatchSnapshot(); await page.click( '[data-type="core/quote"]' ); - await convertBlock( 'Heading' ); + await transformBlockTo( 'Heading' ); expect( await getEditedPostContent() ).toMatchSnapshot(); await page.click( '[data-type="core/quote"]' ); - await convertBlock( 'Heading' ); + await transformBlockTo( 'Heading' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); @@ -140,7 +140,7 @@ describe( 'Quote', () => { await page.keyboard.type( 'two' ); await page.keyboard.press( 'Tab' ); await page.keyboard.type( 'cite' ); - await convertBlock( 'Pullquote' ); + await transformBlockTo( 'Pullquote' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); @@ -148,7 +148,7 @@ describe( 'Quote', () => { await insertBlock( 'Quote' ); await insertBlock( 'Paragraph' ); await page.keyboard.type( 'test' ); - await pressTimes( 'ArrowLeft', 'test'.length ); + await pressKeyTimes( 'ArrowLeft', 'test'.length ); await page.keyboard.press( 'Backspace' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); diff --git a/test/e2e/specs/blocks/separator.test.js b/test/e2e/specs/blocks/separator.test.js index 348d7a6ed45fc..1b3883b51b92e 100644 --- a/test/e2e/specs/blocks/separator.test.js +++ b/test/e2e/specs/blocks/separator.test.js @@ -4,12 +4,12 @@ import { clickBlockAppender, getEditedPostContent, - newPost, + createNewPost, } from '../../support/utils'; describe( 'Separator', () => { beforeEach( async () => { - await newPost(); + await createNewPost(); } ); it( 'can be created by three dashes and enter', async () => { diff --git a/test/e2e/specs/change-detection.test.js b/test/e2e/specs/change-detection.test.js index 8efe2fc1b0031..857e2b166c3ed 100644 --- a/test/e2e/specs/change-detection.test.js +++ b/test/e2e/specs/change-detection.test.js @@ -3,8 +3,8 @@ */ import { clickBlockAppender, - newPost, - pressWithModifier, + createNewPost, + pressKeyWithModifier, ensureSidebarOpened, publishPost, } from '../support/utils'; @@ -15,7 +15,7 @@ describe( 'Change detection', () => { beforeEach( async () => { hadInterceptedSave = false; - await newPost(); + await createNewPost(); } ); afterEach( () => { @@ -68,7 +68,7 @@ describe( 'Change detection', () => { await interceptSave(); // Keyboard shortcut Ctrl+S save. - await pressWithModifier( 'primary', 'S' ); + await pressKeyWithModifier( 'primary', 'S' ); expect( hadInterceptedSave ).toBe( false ); } ); @@ -134,7 +134,7 @@ describe( 'Change detection', () => { } ); it( 'Should not prompt to confirm unsaved changes for new post with initial edits', async () => { - await newPost( { + await createNewPost( { title: 'My New Post', content: 'My content', excerpt: 'My excerpt', @@ -163,7 +163,7 @@ describe( 'Change detection', () => { page.waitForSelector( '.editor-post-saved-state.is-saved' ), // Keyboard shortcut Ctrl+S save. - pressWithModifier( 'primary', 'S' ), + pressKeyWithModifier( 'primary', 'S' ), ] ); await assertIsDirty( false ); @@ -177,13 +177,13 @@ describe( 'Change detection', () => { page.waitForSelector( '.editor-post-saved-state.is-saved' ), // Keyboard shortcut Ctrl+S save. - pressWithModifier( 'primary', 'S' ), + pressKeyWithModifier( 'primary', 'S' ), ] ); await interceptSave(); // Keyboard shortcut Ctrl+S save. - await pressWithModifier( 'primary', 'S' ); + await pressKeyWithModifier( 'primary', 'S' ); expect( hadInterceptedSave ).toBe( false ); } ); @@ -195,7 +195,7 @@ describe( 'Change detection', () => { await Promise.all( [ // Keyboard shortcut Ctrl+S save. - pressWithModifier( 'primary', 'S' ), + pressKeyWithModifier( 'primary', 'S' ), // Ensure save update fails and presents button. page.waitForXPath( @@ -221,7 +221,7 @@ describe( 'Change detection', () => { await interceptSave(); // Keyboard shortcut Ctrl+S save. - await pressWithModifier( 'primary', 'S' ); + await pressKeyWithModifier( 'primary', 'S' ); await releaseSaveIntercept(); @@ -237,7 +237,7 @@ describe( 'Change detection', () => { await interceptSave(); // Keyboard shortcut Ctrl+S save. - await pressWithModifier( 'primary', 'S' ); + await pressKeyWithModifier( 'primary', 'S' ); await page.type( '.editor-post-title__input', '!' ); @@ -254,7 +254,7 @@ describe( 'Change detection', () => { await interceptSave(); // Keyboard shortcut Ctrl+S save. - await pressWithModifier( 'primary', 'S' ); + await pressKeyWithModifier( 'primary', 'S' ); // Dirty post while save is in-flight. await page.type( '.editor-post-title__input', '!' ); @@ -276,7 +276,7 @@ describe( 'Change detection', () => { await interceptSave(); // Keyboard shortcut Ctrl+S save. - await pressWithModifier( 'primary', 'S' ); + await pressKeyWithModifier( 'primary', 'S' ); await clickBlockAppender(); diff --git a/test/e2e/specs/compatibility-classic-editor.test.js b/test/e2e/specs/compatibility-classic-editor.test.js index 3851b8a093851..a3a062731c316 100644 --- a/test/e2e/specs/compatibility-classic-editor.test.js +++ b/test/e2e/specs/compatibility-classic-editor.test.js @@ -1,11 +1,11 @@ /** * Internal dependencies */ -import { newPost, insertBlock, publishPost } from '../support/utils'; +import { createNewPost, insertBlock, publishPost } from '../support/utils'; describe( 'Compatibility with Classic Editor', () => { beforeEach( async () => { - await newPost(); + await createNewPost(); } ); it( 'Should not apply autop when rendering blocks', async () => { diff --git a/test/e2e/specs/container-blocks.test.js b/test/e2e/specs/container-blocks.test.js index bafa4415fd77c..b5aa2b84096cc 100644 --- a/test/e2e/specs/container-blocks.test.js +++ b/test/e2e/specs/container-blocks.test.js @@ -2,12 +2,13 @@ * Internal dependencies */ import { - newPost, - insertBlock, - switchToEditor, + activatePlugin, + createNewPost, + deactivatePlugin, getEditedPostContent, + insertBlock, + switchEditorModeTo, } from '../support/utils'; -import { activatePlugin, deactivatePlugin } from '../support/plugins'; describe( 'InnerBlocks Template Sync', () => { beforeAll( async () => { @@ -15,7 +16,7 @@ describe( 'InnerBlocks Template Sync', () => { } ); beforeEach( async () => { - await newPost(); + await createNewPost(); } ); afterAll( async () => { @@ -29,7 +30,7 @@ describe( 'InnerBlocks Template Sync', () => { <!-- /wp:paragraph --> `; await insertBlock( blockName ); - await switchToEditor( 'Code' ); + await switchEditorModeTo( 'Code' ); await page.$eval( '.editor-post-text-editor', ( element, _paragraph, _blockSlug ) => { const blockDelimiter = `<!-- /wp:${ _blockSlug } -->`; element.value = element.value.replace( blockDelimiter, `${ _paragraph }${ blockDelimiter }` ); @@ -37,7 +38,7 @@ describe( 'InnerBlocks Template Sync', () => { // Press "Enter" inside the Code Editor to fire the `onChange` event for the new value. await page.click( '.editor-post-text-editor' ); await page.keyboard.press( 'Enter' ); - await switchToEditor( 'Visual' ); + await switchEditorModeTo( 'Visual' ); }; it( 'Ensures blocks without locking are kept intact even if they do not match the template ', async () => { @@ -66,7 +67,7 @@ describe( 'Container block without paragraph support', () => { } ); beforeEach( async () => { - await newPost(); + await createNewPost(); } ); afterAll( async () => { diff --git a/test/e2e/specs/convert-block-type.test.js b/test/e2e/specs/convert-block-type.test.js index 2801bee35fda8..0d32512b4c54f 100644 --- a/test/e2e/specs/convert-block-type.test.js +++ b/test/e2e/specs/convert-block-type.test.js @@ -3,14 +3,14 @@ */ import { getEditedPostContent, - newPost, + createNewPost, insertBlock, - convertBlock, + transformBlockTo, } from '../support/utils'; describe( 'Code block', () => { beforeEach( async () => { - await newPost(); + await createNewPost(); } ); it( 'should convert to a preformatted block', async () => { @@ -24,7 +24,7 @@ describe( 'Code block', () => { const originalPostContent = await getEditedPostContent(); expect( originalPostContent ).toMatchSnapshot(); - await convertBlock( 'Preformatted' ); + await transformBlockTo( 'Preformatted' ); // The content should now be a Preformatted block with no data loss. const convertedPostContent = await getEditedPostContent(); diff --git a/test/e2e/specs/datepicker.test.js b/test/e2e/specs/datepicker.test.js index 11bd817098098..7a12f4b82c230 100644 --- a/test/e2e/specs/datepicker.test.js +++ b/test/e2e/specs/datepicker.test.js @@ -1,11 +1,11 @@ /** * Internal dependencies */ -import { newPost } from '../support/utils'; +import { createNewPost } from '../support/utils'; describe( 'Datepicker', () => { beforeEach( async () => { - await newPost(); + await createNewPost(); } ); it( 'should show the publishing date as "Immediately" if the date is not altered', async () => { diff --git a/test/e2e/specs/demo.test.js b/test/e2e/specs/demo.test.js index cb8ef8b85dc40..30b4b3f887d95 100644 --- a/test/e2e/specs/demo.test.js +++ b/test/e2e/specs/demo.test.js @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { visitAdmin, matchURL, setUpResponseMocking, mockOrTransform } from '../support/utils'; +import { visitAdminPage, createURLMatcher, setUpResponseMocking, mockOrTransform } from '../support/utils'; const MOCK_VIMEO_RESPONSE = { url: 'https://vimeo.com/22439234', @@ -24,11 +24,11 @@ describe( 'new editor state', () => { beforeAll( async () => { setUpResponseMocking( [ { - match: matchURL( 'oembed%2F1.0%2Fproxy' ), + match: createURLMatcher( 'oembed%2F1.0%2Fproxy' ), onRequestMatch: mockOrTransform( couldNotBePreviewed, MOCK_VIMEO_RESPONSE, stripIframeFromEmbed ), }, ] ); - await visitAdmin( 'post-new.php', 'gutenberg-demo' ); + await visitAdminPage( 'post-new.php', 'gutenberg-demo' ); } ); it( 'content should load without making the post dirty', async () => { diff --git a/test/e2e/specs/deprecated-node-matcher.test.js b/test/e2e/specs/deprecated-node-matcher.test.js index a93f23f0de883..f59ad9892846f 100644 --- a/test/e2e/specs/deprecated-node-matcher.test.js +++ b/test/e2e/specs/deprecated-node-matcher.test.js @@ -2,12 +2,13 @@ * Internal dependencies */ import { - newPost, - insertBlock, + activatePlugin, + createNewPost, + deactivatePlugin, getEditedPostContent, - pressWithModifier, + insertBlock, + pressKeyWithModifier, } from '../support/utils'; -import { activatePlugin, deactivatePlugin } from '../support/plugins'; describe( 'Deprecated Node Matcher', () => { beforeAll( async () => { @@ -15,7 +16,7 @@ describe( 'Deprecated Node Matcher', () => { } ); beforeEach( async () => { - await newPost(); + await createNewPost(); } ); afterAll( async () => { @@ -37,7 +38,7 @@ describe( 'Deprecated Node Matcher', () => { await page.keyboard.down( 'Shift' ); await page.keyboard.press( 'ArrowLeft' ); await page.keyboard.up( 'Shift' ); - await pressWithModifier( 'primary', 'b' ); + await pressKeyWithModifier( 'primary', 'b' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); } ); diff --git a/test/e2e/specs/editor-modes.test.js b/test/e2e/specs/editor-modes.test.js index 85faa8344a3f6..e8aee11aaa1c8 100644 --- a/test/e2e/specs/editor-modes.test.js +++ b/test/e2e/specs/editor-modes.test.js @@ -1,11 +1,11 @@ /** * Internal dependencies */ -import { clickBlockAppender, newPost, switchToEditor } from '../support/utils'; +import { clickBlockAppender, createNewPost, switchEditorModeTo } from '../support/utils'; describe( 'Editing modes (visual/HTML)', () => { beforeEach( async () => { - await newPost(); + await createNewPost(); await clickBlockAppender(); await page.keyboard.type( 'Hello world!' ); } ); @@ -95,7 +95,7 @@ describe( 'Editing modes (visual/HTML)', () => { expect( blockInspectorTab ).not.toBeNull(); // Switch to Code Editor - await switchToEditor( 'Code' ); + await switchEditorModeTo( 'Code' ); // The Block inspector should not be active anymore blockInspectorTab = await page.$( '.edit-post-sidebar__panel-tab.is-active[data-label="Block"]' ); diff --git a/test/e2e/specs/embedding.test.js b/test/e2e/specs/embedding.test.js index 45b37006cecb8..46d7bd4986d82 100644 --- a/test/e2e/specs/embedding.test.js +++ b/test/e2e/specs/embedding.test.js @@ -3,10 +3,10 @@ */ import { clickBlockAppender, - newPost, - isEmbedding, + createNewPost, + createEmbeddingMatcher, setUpResponseMocking, - JSONResponse, + createJSONResponse, getEditedPostContent, clickButton, } from '../support/utils'; @@ -61,32 +61,32 @@ const MOCK_BAD_WORDPRESS_RESPONSE = { const MOCK_RESPONSES = [ { - match: isEmbedding( 'https://wordpress.org/gutenberg/handbook/' ), - onRequestMatch: JSONResponse( MOCK_BAD_WORDPRESS_RESPONSE ), + match: createEmbeddingMatcher( 'https://wordpress.org/gutenberg/handbook/' ), + onRequestMatch: createJSONResponse( MOCK_BAD_WORDPRESS_RESPONSE ), }, { - match: isEmbedding( 'https://wordpress.org/gutenberg/handbook/block-api/attributes/' ), - onRequestMatch: JSONResponse( MOCK_EMBED_WORDPRESS_SUCCESS_RESPONSE ), + match: createEmbeddingMatcher( 'https://wordpress.org/gutenberg/handbook/block-api/attributes/' ), + onRequestMatch: createJSONResponse( MOCK_EMBED_WORDPRESS_SUCCESS_RESPONSE ), }, { - match: isEmbedding( 'https://www.youtube.com/watch?v=lXMskKTw3Bc' ), - onRequestMatch: JSONResponse( MOCK_EMBED_VIDEO_SUCCESS_RESPONSE ), + match: createEmbeddingMatcher( 'https://www.youtube.com/watch?v=lXMskKTw3Bc' ), + onRequestMatch: createJSONResponse( MOCK_EMBED_VIDEO_SUCCESS_RESPONSE ), }, { - match: isEmbedding( 'https://cloudup.com/cQFlxqtY4ob' ), - onRequestMatch: JSONResponse( MOCK_EMBED_RICH_SUCCESS_RESPONSE ), + match: createEmbeddingMatcher( 'https://cloudup.com/cQFlxqtY4ob' ), + onRequestMatch: createJSONResponse( MOCK_EMBED_RICH_SUCCESS_RESPONSE ), }, { - match: isEmbedding( 'https://twitter.com/notnownikki' ), - onRequestMatch: JSONResponse( MOCK_EMBED_RICH_SUCCESS_RESPONSE ), + match: createEmbeddingMatcher( 'https://twitter.com/notnownikki' ), + onRequestMatch: createJSONResponse( MOCK_EMBED_RICH_SUCCESS_RESPONSE ), }, { - match: isEmbedding( 'https://twitter.com/thatbunty' ), - onRequestMatch: JSONResponse( MOCK_BAD_EMBED_PROVIDER_RESPONSE ), + match: createEmbeddingMatcher( 'https://twitter.com/thatbunty' ), + onRequestMatch: createJSONResponse( MOCK_BAD_EMBED_PROVIDER_RESPONSE ), }, { - match: isEmbedding( 'https://twitter.com/wooyaygutenberg123454312' ), - onRequestMatch: JSONResponse( MOCK_CANT_EMBED_RESPONSE ), + match: createEmbeddingMatcher( 'https://twitter.com/wooyaygutenberg123454312' ), + onRequestMatch: createJSONResponse( MOCK_CANT_EMBED_RESPONSE ), }, ]; @@ -143,7 +143,7 @@ const addAllEmbeds = async () => { describe( 'Embedding content', () => { beforeAll( async () => await setUpResponseMocking( MOCK_RESPONSES ) ); - beforeEach( newPost ); + beforeEach( createNewPost ); it( 'should render embeds in the correct state', async () => { await addAllEmbeds(); @@ -184,8 +184,8 @@ describe( 'Embedding content', () => { await setUpResponseMocking( [ { - match: isEmbedding( 'https://twitter.com/wooyaygutenberg123454312' ), - onRequestMatch: JSONResponse( MOCK_EMBED_RICH_SUCCESS_RESPONSE ), + match: createEmbeddingMatcher( 'https://twitter.com/wooyaygutenberg123454312' ), + onRequestMatch: createJSONResponse( MOCK_EMBED_RICH_SUCCESS_RESPONSE ), }, ] ); diff --git a/test/e2e/specs/font-size-picker.test.js b/test/e2e/specs/font-size-picker.test.js index 42bfe7e17f5a4..78b78068b764b 100644 --- a/test/e2e/specs/font-size-picker.test.js +++ b/test/e2e/specs/font-size-picker.test.js @@ -4,13 +4,13 @@ import { clickBlockAppender, getEditedPostContent, - newPost, - pressTimes, + createNewPost, + pressKeyTimes, } from '../support/utils'; describe( 'Font Size Picker', () => { beforeEach( async () => { - await newPost(); + await createNewPost(); } ); it( 'should apply a named font size using the font size buttons', async () => { @@ -85,8 +85,8 @@ describe( 'Font Size Picker', () => { // Clear the custom font size input. await page.click( '.blocks-font-size .components-range-control__number' ); - await pressTimes( 'ArrowRight', 4 ); - await pressTimes( 'Backspace', 4 ); + await pressKeyTimes( 'ArrowRight', 4 ); + await pressKeyTimes( 'Backspace', 4 ); // Ensure content matches snapshot. const content = await getEditedPostContent(); diff --git a/test/e2e/specs/format-api.test.js b/test/e2e/specs/format-api.test.js index 6a1361a854e13..915d23b679a1e 100644 --- a/test/e2e/specs/format-api.test.js +++ b/test/e2e/specs/format-api.test.js @@ -2,12 +2,13 @@ * Internal dependencies */ import { + activatePlugin, clickBlockAppender, + createNewPost, + deactivatePlugin, getEditedPostContent, - newPost, - pressWithModifier, + pressKeyWithModifier, } from '../support/utils'; -import { activatePlugin, deactivatePlugin } from '../support/plugins'; describe( 'Using Format API', () => { beforeAll( async () => { @@ -19,7 +20,7 @@ describe( 'Using Format API', () => { } ); beforeEach( async () => { - await newPost(); + await createNewPost(); } ); it( 'Format toolbar is present in a paragraph block', async () => { @@ -32,8 +33,8 @@ describe( 'Using Format API', () => { it( 'Clicking the control wraps the selected text properly with HTML code', async () => { await clickBlockAppender(); await page.keyboard.type( 'First paragraph' ); - await pressWithModifier( 'shiftAlt', 'ArrowLeft' ); - await pressWithModifier( 'primary', 'A' ); + await pressKeyWithModifier( 'shiftAlt', 'ArrowLeft' ); + await pressKeyWithModifier( 'primary', 'A' ); await page.mouse.move( 200, 300, { steps: 10 } ); await page.click( '[aria-label="Custom Link"]' ); expect( await getEditedPostContent() ).toMatchSnapshot(); diff --git a/test/e2e/specs/fullscreen-mode.test.js b/test/e2e/specs/fullscreen-mode.test.js index 9b697516d51d0..a960a4a1dd27d 100644 --- a/test/e2e/specs/fullscreen-mode.test.js +++ b/test/e2e/specs/fullscreen-mode.test.js @@ -2,13 +2,13 @@ * Internal dependencies */ import { - newPost, + createNewPost, clickOnMoreMenuItem, } from '../support/utils'; describe( 'Fullscreen Mode', () => { beforeAll( async () => { - await newPost(); + await createNewPost(); } ); it( 'should open the fullscreen mode from the more menu', async () => { diff --git a/test/e2e/specs/hooks-api.test.js b/test/e2e/specs/hooks-api.test.js index 3af80549b511d..3f4ec35837a96 100644 --- a/test/e2e/specs/hooks-api.test.js +++ b/test/e2e/specs/hooks-api.test.js @@ -2,10 +2,11 @@ * Internal dependencies */ import { + activatePlugin, clickBlockAppender, - newPost, + createNewPost, + deactivatePlugin, } from '../support/utils'; -import { activatePlugin, deactivatePlugin } from '../support/plugins'; describe( 'Using Hooks API', () => { beforeAll( async () => { @@ -17,7 +18,7 @@ describe( 'Using Hooks API', () => { } ); beforeEach( async () => { - await newPost(); + await createNewPost(); } ); it( 'Should contain a reset block button on the sidebar', async () => { diff --git a/test/e2e/specs/invalid-block.test.js b/test/e2e/specs/invalid-block.test.js index 4a3a9934b0eb9..c06d1f8752efd 100644 --- a/test/e2e/specs/invalid-block.test.js +++ b/test/e2e/specs/invalid-block.test.js @@ -2,13 +2,13 @@ * Internal dependencies */ import { - newPost, + createNewPost, clickBlockAppender, } from '../support/utils'; describe( 'invalid blocks', () => { beforeEach( async () => { - await newPost(); + await createNewPost(); } ); it( 'Should show an invalid block message with clickable options', async () => { diff --git a/test/e2e/specs/links.test.js b/test/e2e/specs/links.test.js index e5fecebe10ec0..aa439447fcf5a 100644 --- a/test/e2e/specs/links.test.js +++ b/test/e2e/specs/links.test.js @@ -4,9 +4,9 @@ import { clickBlockAppender, getEditedPostContent, - newPost, - pressWithModifier, - pressTimes, + createNewPost, + pressKeyWithModifier, + pressKeyTimes, insertBlock, } from '../support/utils'; @@ -18,7 +18,7 @@ import { describe( 'Links', () => { beforeEach( async () => { - await newPost(); + await createNewPost(); } ); const waitForAutoFocus = async () => { @@ -36,7 +36,7 @@ describe( 'Links', () => { await page.keyboard.type( 'This is Gutenberg' ); // Select some text - await pressWithModifier( 'shiftAlt', 'ArrowLeft' ); + await pressKeyWithModifier( 'shiftAlt', 'ArrowLeft' ); // Click on the Link button await page.click( 'button[aria-label="Link"]' ); @@ -60,10 +60,10 @@ describe( 'Links', () => { await page.keyboard.type( 'This is Gutenberg' ); // Select some text - await pressWithModifier( 'shiftAlt', 'ArrowLeft' ); + await pressKeyWithModifier( 'shiftAlt', 'ArrowLeft' ); // Press Cmd+K to insert a link - await pressWithModifier( 'primary', 'K' ); + await pressKeyWithModifier( 'primary', 'K' ); // Wait for the URL field to auto-focus await waitForAutoFocus(); @@ -87,7 +87,7 @@ describe( 'Links', () => { await moveMouse(); // Press Cmd+K to insert a link - await pressWithModifier( 'primary', 'K' ); + await pressKeyWithModifier( 'primary', 'K' ); // Wait for the URL field to auto-focus await waitForAutoFocus(); @@ -108,10 +108,10 @@ describe( 'Links', () => { await page.keyboard.type( 'This is Gutenberg: https://wordpress.org/gutenberg' ); // Select the URL - await pressWithModifier( 'shiftAlt', 'ArrowLeft' ); - await pressWithModifier( 'shiftAlt', 'ArrowLeft' ); - await pressWithModifier( 'shiftAlt', 'ArrowLeft' ); - await pressWithModifier( 'shiftAlt', 'ArrowLeft' ); + await pressKeyWithModifier( 'shiftAlt', 'ArrowLeft' ); + await pressKeyWithModifier( 'shiftAlt', 'ArrowLeft' ); + await pressKeyWithModifier( 'shiftAlt', 'ArrowLeft' ); + await pressKeyWithModifier( 'shiftAlt', 'ArrowLeft' ); // Click on the Link button await page.click( 'button[aria-label="Link"]' ); @@ -126,7 +126,7 @@ describe( 'Links', () => { await page.keyboard.type( 'This is Gutenberg' ); // Select some text - await pressWithModifier( 'shiftAlt', 'ArrowLeft' ); + await pressKeyWithModifier( 'shiftAlt', 'ArrowLeft' ); // Click on the Link button await page.click( 'button[aria-label="Link"]' ); @@ -147,7 +147,7 @@ describe( 'Links', () => { await page.keyboard.type( 'This is Gutenberg' ); // Select some text - await pressWithModifier( 'shiftAlt', 'ArrowLeft' ); + await pressKeyWithModifier( 'shiftAlt', 'ArrowLeft' ); // Click on the Link button await page.click( 'button[aria-label="Link"]' ); @@ -244,7 +244,7 @@ describe( 'Links', () => { it( 'can be edited with collapsed selection', async () => { await createAndReselectLink(); // Make a collapsed selection inside the link - await pressTimes( 'ArrowRight', 3 ); + await pressKeyTimes( 'ArrowRight', 3 ); await moveMouse(); await page.click( 'button[aria-label="Edit"]' ); await waitForAutoFocus(); @@ -254,7 +254,7 @@ describe( 'Links', () => { } ); const createPostWithTitle = async ( titleText ) => { - await newPost(); + await createNewPost(); await page.type( '.editor-post-title__input', titleText ); await page.click( '.editor-post-publish-panel__toggle' ); @@ -280,10 +280,10 @@ describe( 'Links', () => { // Now create a new post and try to select the post created previously // from the autocomplete suggestions. - await newPost(); + await createNewPost(); await clickBlockAppender(); await page.keyboard.type( 'This is Gutenberg' ); - await pressWithModifier( 'shiftAlt', 'ArrowLeft' ); + await pressKeyWithModifier( 'shiftAlt', 'ArrowLeft' ); await page.click( 'button[aria-label="Link"]' ); // Wait for the URL field to auto-focus @@ -318,15 +318,15 @@ describe( 'Links', () => { const titleText = 'Test post keyboard'; const postURL = await createPostWithTitle( titleText ); - await newPost(); + await createNewPost(); await clickBlockAppender(); // Now in a new post and try to create a link from an autocomplete suggestion using the keyboard. await page.keyboard.type( 'This is Gutenberg' ); - await pressWithModifier( 'shiftAlt', 'ArrowLeft' ); + await pressKeyWithModifier( 'shiftAlt', 'ArrowLeft' ); // Press Cmd+K to insert a link - await pressWithModifier( 'primary', 'K' ); + await pressKeyWithModifier( 'primary', 'K' ); // Wait for the URL field to auto-focus await waitForAutoFocus(); @@ -354,15 +354,15 @@ describe( 'Links', () => { const titleText = 'Test post escape'; await createPostWithTitle( titleText ); - await newPost(); + await createNewPost(); await clickBlockAppender(); // Now in a new post and try to create a link from an autocomplete suggestion using the keyboard. await page.keyboard.type( 'This is Gutenberg' ); - await pressWithModifier( 'shiftAlt', 'ArrowLeft' ); + await pressKeyWithModifier( 'shiftAlt', 'ArrowLeft' ); // Press Cmd+K to insert a link - await pressWithModifier( 'primary', 'K' ); + await pressKeyWithModifier( 'primary', 'K' ); // Wait for the URL field to auto-focus await waitForAutoFocus(); @@ -378,7 +378,7 @@ describe( 'Links', () => { expect( await page.$( '.editor-url-popover' ) ).toBeNull(); // Press Cmd+K to insert a link - await pressWithModifier( 'primary', 'K' ); + await pressKeyWithModifier( 'primary', 'K' ); // Wait for the URL field to auto-focus await waitForAutoFocus(); @@ -389,7 +389,7 @@ describe( 'Links', () => { expect( await page.$( '.editor-url-popover' ) ).toBeNull(); // Press Cmd+K to insert a link - await pressWithModifier( 'primary', 'K' ); + await pressKeyWithModifier( 'primary', 'K' ); // Wait for the URL field to auto-focus await waitForAutoFocus(); @@ -410,8 +410,8 @@ describe( 'Links', () => { // Create a block with some text and format it as a link. await clickBlockAppender(); await page.keyboard.type( 'This is Gutenberg' ); - await pressWithModifier( 'shiftAlt', 'ArrowLeft' ); - await pressWithModifier( 'primary', 'K' ); + await pressKeyWithModifier( 'shiftAlt', 'ArrowLeft' ); + await pressKeyWithModifier( 'primary', 'K' ); await waitForAutoFocus(); await page.keyboard.type( URL ); await page.keyboard.press( 'Enter' ); @@ -428,7 +428,7 @@ describe( 'Links', () => { // Press Cmd+K to edit the link and the url-input should become // focused with the value previously inserted. - await pressWithModifier( 'primary', 'K' ); + await pressKeyWithModifier( 'primary', 'K' ); await waitForAutoFocus(); const activeElementParentClasses = await page.evaluate( () => Object.values( document.activeElement.parentElement.classList ) ); expect( activeElementParentClasses ).toContain( 'editor-url-input' ); @@ -439,8 +439,8 @@ describe( 'Links', () => { it( 'adds an assertive message for screenreader users when an invalid link is set', async () => { await clickBlockAppender(); await page.keyboard.type( 'This is Gutenberg' ); - await pressWithModifier( 'shiftAlt', 'ArrowLeft' ); - await pressWithModifier( 'primary', 'K' ); + await pressKeyWithModifier( 'shiftAlt', 'ArrowLeft' ); + await pressKeyWithModifier( 'primary', 'K' ); await waitForAutoFocus(); await page.keyboard.type( 'http://#test.com' ); await page.keyboard.press( 'Enter' ); @@ -460,7 +460,7 @@ describe( 'Links', () => { await page.click( '.editor-block-navigation__item button' ); // Select some text - await pressWithModifier( 'shiftAlt', 'ArrowLeft' ); + await pressKeyWithModifier( 'shiftAlt', 'ArrowLeft' ); // Click on the Link button await page.click( 'button[aria-label="Link"]' ); @@ -489,8 +489,8 @@ describe( 'Links', () => { await clickBlockAppender(); await page.keyboard.type( 'This is WordPress' ); // Select "WordPress". - await pressWithModifier( 'shiftAlt', 'ArrowLeft' ); - await pressWithModifier( 'primary', 'k' ); + await pressKeyWithModifier( 'shiftAlt', 'ArrowLeft' ); + await pressKeyWithModifier( 'primary', 'k' ); await waitForAutoFocus(); await page.keyboard.type( 'w.org' ); // Navigate to the settings toggle. diff --git a/test/e2e/specs/manage-reusable-blocks.test.js b/test/e2e/specs/manage-reusable-blocks.test.js index 21276524961c3..acd97baa175ed 100644 --- a/test/e2e/specs/manage-reusable-blocks.test.js +++ b/test/e2e/specs/manage-reusable-blocks.test.js @@ -6,11 +6,11 @@ import path from 'path'; /** * Internal dependencies */ -import { visitAdmin } from '../support/utils'; +import { visitAdminPage } from '../support/utils'; describe( 'Managing reusable blocks', () => { beforeAll( async () => { - await visitAdmin( 'edit.php', 'post_type=wp_block' ); + await visitAdminPage( 'edit.php', 'post_type=wp_block' ); } ); it( 'Should import reusable blocks', async () => { @@ -34,7 +34,7 @@ describe( 'Managing reusable blocks', () => { expect( noticeContent ).toEqual( 'Reusable block imported successfully!' ); // Refresh the page - await visitAdmin( 'edit.php', 'post_type=wp_block' ); + await visitAdminPage( 'edit.php', 'post_type=wp_block' ); // The reusable block has been imported page.waitForXPath( 'div[@class="post_title"][contains(text(), "Greeting")]' ); diff --git a/test/e2e/specs/mentions.test.js b/test/e2e/specs/mentions.test.js index 33ad92caaf98b..a289a8491b298 100644 --- a/test/e2e/specs/mentions.test.js +++ b/test/e2e/specs/mentions.test.js @@ -2,14 +2,14 @@ * Internal dependencies */ import { - newPost, + createNewPost, getEditedPostContent, clickBlockAppender, } from '../support/utils'; describe( 'autocomplete mentions', () => { beforeAll( async () => { - await newPost(); + await createNewPost(); } ); it( 'should insert mention', async () => { diff --git a/test/e2e/specs/meta-attribute-block.test.js b/test/e2e/specs/meta-attribute-block.test.js index b8d7fc4c5188e..6dc54ea8c1849 100644 --- a/test/e2e/specs/meta-attribute-block.test.js +++ b/test/e2e/specs/meta-attribute-block.test.js @@ -2,12 +2,13 @@ * Internal dependencies */ import { - newPost, + activatePlugin, + createNewPost, + deactivatePlugin, getEditedPostContent, - saveDraft, insertBlock, + saveDraft, } from '../support/utils'; -import { activatePlugin, deactivatePlugin } from '../support/plugins'; describe( 'Block with a meta attribute', () => { beforeAll( async () => { @@ -15,7 +16,7 @@ describe( 'Block with a meta attribute', () => { } ); beforeEach( async () => { - await newPost(); + await createNewPost(); } ); afterAll( async () => { diff --git a/test/e2e/specs/meta-boxes.test.js b/test/e2e/specs/meta-boxes.test.js index 073d7b788cedb..c7d73a8f71fe2 100644 --- a/test/e2e/specs/meta-boxes.test.js +++ b/test/e2e/specs/meta-boxes.test.js @@ -1,13 +1,18 @@ /** * Internal dependencies */ -import { newPost, insertBlock, publishPost } from '../support/utils'; -import { activatePlugin, deactivatePlugin } from '../support/plugins'; +import { + activatePlugin, + createNewPost, + deactivatePlugin, + insertBlock, + publishPost, +} from '../support/utils'; describe( 'Meta boxes', () => { beforeAll( async () => { await activatePlugin( 'gutenberg-test-plugin-meta-box' ); - await newPost(); + await createNewPost(); } ); afterAll( async () => { @@ -38,14 +43,14 @@ describe( 'Meta boxes', () => { it( 'Should render dynamic blocks when the meta box uses the excerpt for front end rendering', async () => { // Publish a post so there's something for the latest posts dynamic block to render. - await newPost(); + await createNewPost(); await page.type( '.editor-post-title__input', 'A published post' ); await insertBlock( 'Paragraph' ); await page.keyboard.type( 'Hello there!' ); await publishPost(); // Publish a post with the latest posts dynamic block. - await newPost(); + await createNewPost(); await page.type( '.editor-post-title__input', 'Dynamic block test' ); await insertBlock( 'Latest Posts' ); await publishPost(); diff --git a/test/e2e/specs/multi-block-selection.test.js b/test/e2e/specs/multi-block-selection.test.js index 959fc5bc44790..b8d6c4fa2de7e 100644 --- a/test/e2e/specs/multi-block-selection.test.js +++ b/test/e2e/specs/multi-block-selection.test.js @@ -4,13 +4,13 @@ import { clickBlockAppender, insertBlock, - newPost, - pressWithModifier, + createNewPost, + pressKeyWithModifier, } from '../support/utils'; describe( 'Multi-block selection', () => { beforeEach( async () => { - await newPost(); + await createNewPost(); } ); it( 'Should select/unselect multiple blocks', async () => { @@ -59,7 +59,7 @@ describe( 'Multi-block selection', () => { // Multiselect via keyboard await page.click( 'body' ); - await pressWithModifier( 'primary', 'a' ); + await pressKeyWithModifier( 'primary', 'a' ); // Verify selection await expectMultiSelected( blocks, true ); @@ -72,8 +72,8 @@ describe( 'Multi-block selection', () => { // Select all via double shortcut. await page.click( firstBlockSelector ); - await pressWithModifier( 'primary', 'a' ); - await pressWithModifier( 'primary', 'a' ); + await pressKeyWithModifier( 'primary', 'a' ); + await pressKeyWithModifier( 'primary', 'a' ); await expectMultiSelected( blocks, true ); } ); @@ -126,8 +126,8 @@ describe( 'Multi-block selection', () => { await page.keyboard.type( 'Third Paragraph' ); // Multiselect via keyboard. - await pressWithModifier( 'primary', 'a' ); - await pressWithModifier( 'primary', 'a' ); + await pressKeyWithModifier( 'primary', 'a' ); + await pressKeyWithModifier( 'primary', 'a' ); // TODO: It would be great to do this test by spying on `wp.a11y.speak`, // but it's very difficult to do that because `wp.a11y` has diff --git a/test/e2e/specs/navigable-toolbar.test.js b/test/e2e/specs/navigable-toolbar.test.js index e62753149ace2..e3e58aa059928 100644 --- a/test/e2e/specs/navigable-toolbar.test.js +++ b/test/e2e/specs/navigable-toolbar.test.js @@ -6,7 +6,7 @@ import { forEach } from 'lodash'; /** * Internal dependencies */ -import { newPost, pressWithModifier } from '../support/utils'; +import { createNewPost, pressKeyWithModifier } from '../support/utils'; describe( 'block toolbar', () => { forEach( { @@ -14,7 +14,7 @@ describe( 'block toolbar', () => { contextual: false, }, ( isUnifiedToolbar, label ) => { beforeEach( async () => { - await newPost(); + await createNewPost(); await page.evaluate( ( _isUnifiedToolbar ) => { const { select, dispatch } = wp.data; @@ -44,7 +44,7 @@ describe( 'block toolbar', () => { await page.keyboard.type( 'Example' ); // Upward - await pressWithModifier( 'alt', 'F10' ); + await pressKeyWithModifier( 'alt', 'F10' ); expect( await isInBlockToolbar() ).toBe( true ); // Downward diff --git a/test/e2e/specs/new-post-default-content.test.js b/test/e2e/specs/new-post-default-content.test.js index 28949da44f1b4..df5334319e307 100644 --- a/test/e2e/specs/new-post-default-content.test.js +++ b/test/e2e/specs/new-post-default-content.test.js @@ -1,8 +1,14 @@ /** * Internal dependencies */ -import { findSidebarPanelWithTitle, newPost, getEditedPostContent, openDocumentSettingsSidebar } from '../support/utils'; -import { activatePlugin, deactivatePlugin } from '../support/plugins'; +import { + activatePlugin, + createNewPost, + deactivatePlugin, + findSidebarPanelWithTitle, + getEditedPostContent, + openDocumentSettingsSidebar, +} from '../support/utils'; describe( 'new editor filtered state', () => { beforeAll( async () => { @@ -10,7 +16,7 @@ describe( 'new editor filtered state', () => { } ); beforeEach( async () => { - await newPost(); + await createNewPost(); } ); afterAll( async () => { diff --git a/test/e2e/specs/new-post.test.js b/test/e2e/specs/new-post.test.js index 232584dd3c630..c8dc83b6a2535 100644 --- a/test/e2e/specs/new-post.test.js +++ b/test/e2e/specs/new-post.test.js @@ -1,8 +1,11 @@ /** * Internal dependencies */ -import { newPost } from '../support/utils'; -import { activatePlugin, deactivatePlugin } from '../support/plugins'; +import { + activatePlugin, + createNewPost, + deactivatePlugin, +} from '../support/utils'; describe( 'new editor state', () => { beforeAll( async () => { @@ -10,7 +13,7 @@ describe( 'new editor state', () => { } ); beforeEach( async () => { - await newPost(); + await createNewPost(); } ); afterAll( async () => { @@ -74,7 +77,7 @@ describe( 'new editor state', () => { } ); it( 'should be saveable with sufficient initial edits', async () => { - await newPost( { title: 'Here is the title' } ); + await createNewPost( { title: 'Here is the title' } ); // Verify saveable by presence of the Save Draft button. await page.$( 'button.editor-post-save-draft' ); diff --git a/test/e2e/specs/nux.test.js b/test/e2e/specs/nux.test.js index 8702587f6b55b..23fad060527ba 100644 --- a/test/e2e/specs/nux.test.js +++ b/test/e2e/specs/nux.test.js @@ -4,9 +4,9 @@ import { clickBlockAppender, clickOnMoreMenuItem, - newPost, + createNewPost, saveDraft, - toggleOption, + toggleScreenOption, } from '../support/utils'; describe( 'New User Experience (NUX)', () => { @@ -35,7 +35,7 @@ describe( 'New User Experience (NUX)', () => { } beforeEach( async () => { - await newPost( { enableTips: true } ); + await createNewPost( { enableTips: true } ); } ); it( 'should show tips to a first-time user', async () => { @@ -100,7 +100,7 @@ describe( 'New User Experience (NUX)', () => { expect( areTipsEnabled ).toEqual( false ); // Toggle the 'Enable Tips' option to enable. - await toggleOption( 'Enable Tips' ); + await toggleScreenOption( 'Enable Tips' ); // Tips should once again appear. nuxTipElements = await page.$$( '.nux-dot-tip' ); diff --git a/test/e2e/specs/plugins-api.test.js b/test/e2e/specs/plugins-api.test.js index d149cf492a5a3..3da0a0a1314b9 100644 --- a/test/e2e/specs/plugins-api.test.js +++ b/test/e2e/specs/plugins-api.test.js @@ -2,14 +2,15 @@ * Internal dependencies */ import { + activatePlugin, clickBlockAppender, clickOnMoreMenuItem, + createNewPost, + deactivatePlugin, openDocumentSettingsSidebar, - newPost, openPublishPanel, publishPost, } from '../support/utils'; -import { activatePlugin, deactivatePlugin } from '../support/plugins'; describe( 'Using Plugins API', () => { beforeAll( async () => { @@ -21,7 +22,7 @@ describe( 'Using Plugins API', () => { } ); beforeEach( async () => { - await newPost(); + await createNewPost(); } ); describe( 'Post Status Info', () => { diff --git a/test/e2e/specs/popovers.test.js b/test/e2e/specs/popovers.test.js index 202c8d7cb9362..dff7692aa9e9d 100644 --- a/test/e2e/specs/popovers.test.js +++ b/test/e2e/specs/popovers.test.js @@ -1,11 +1,11 @@ /** * Internal dependencies */ -import { newPost } from '../support/utils'; +import { createNewPost } from '../support/utils'; describe( 'popovers', () => { beforeEach( async () => { - await newPost(); + await createNewPost(); } ); describe( 'dropdown', () => { diff --git a/test/e2e/specs/post-visibility.test.js b/test/e2e/specs/post-visibility.test.js index 392fd3f9c6efd..70ec9dcff905c 100644 --- a/test/e2e/specs/post-visibility.test.js +++ b/test/e2e/specs/post-visibility.test.js @@ -2,17 +2,17 @@ * Internal dependencies */ import { - setViewport, - newPost, + setBrowserViewport, + createNewPost, openDocumentSettingsSidebar, } from '../support/utils'; describe( 'Post visibility', () => { [ 'large', 'small' ].forEach( ( viewport ) => { it( `can be changed when the viewport is ${ viewport }`, async () => { - await setViewport( viewport ); + await setBrowserViewport( viewport ); - await newPost(); + await createNewPost(); await openDocumentSettingsSidebar(); diff --git a/test/e2e/specs/preferences.test.js b/test/e2e/specs/preferences.test.js index d405009969fa2..52e5e2c3c8558 100644 --- a/test/e2e/specs/preferences.test.js +++ b/test/e2e/specs/preferences.test.js @@ -1,11 +1,11 @@ /** * Internal dependencies */ -import { newPost } from '../support/utils'; +import { createNewPost } from '../support/utils'; describe( 'preferences', () => { beforeAll( async () => { - await newPost(); + await createNewPost(); } ); /** diff --git a/test/e2e/specs/preview.test.js b/test/e2e/specs/preview.test.js index 7cd1f57daf73f..0d0e00385f4ce 100644 --- a/test/e2e/specs/preview.test.js +++ b/test/e2e/specs/preview.test.js @@ -8,15 +8,15 @@ import { parse } from 'url'; * Internal dependencies */ import { - newPost, - getUrl, + createNewPost, + createURL, publishPost, saveDraft, } from '../support/utils'; describe( 'Preview', () => { beforeEach( async () => { - await newPost(); + await createNewPost(); } ); async function openPreviewPage( editorPage ) { @@ -72,7 +72,7 @@ describe( 'Preview', () => { return window.location.search.match( /[\?&]post=(\d+)/ ); } ) ).jsonValue(); - const expectedPreviewURL = getUrl( '', `?p=${ postId }&preview=true` ); + const expectedPreviewURL = createURL( '', `?p=${ postId }&preview=true` ); expect( previewPage.url() ).toBe( expectedPreviewURL ); // Title in preview should match input. diff --git a/test/e2e/specs/publish-button.test.js b/test/e2e/specs/publish-button.test.js index 0db024ce7ac81..d70ca1998fe13 100644 --- a/test/e2e/specs/publish-button.test.js +++ b/test/e2e/specs/publish-button.test.js @@ -2,13 +2,13 @@ import { arePrePublishChecksEnabled, disablePrePublishChecks, enablePrePublishChecks, - newPost, + createNewPost, } from '../support/utils'; describe( 'PostPublishButton', () => { let werePrePublishChecksEnabled; beforeEach( async () => { - await newPost( ); + await createNewPost( ); werePrePublishChecksEnabled = await arePrePublishChecksEnabled(); if ( werePrePublishChecksEnabled ) { await disablePrePublishChecks(); diff --git a/test/e2e/specs/publish-panel.test.js b/test/e2e/specs/publish-panel.test.js index 27842682577c7..274051666701f 100644 --- a/test/e2e/specs/publish-panel.test.js +++ b/test/e2e/specs/publish-panel.test.js @@ -2,16 +2,16 @@ import { arePrePublishChecksEnabled, disablePrePublishChecks, enablePrePublishChecks, - newPost, + createNewPost, openPublishPanel, - pressWithModifier, + pressKeyWithModifier, publishPost, } from '../support/utils'; describe( 'PostPublishPanel', () => { let werePrePublishChecksEnabled; beforeEach( async () => { - await newPost( ); + await createNewPost( ); werePrePublishChecksEnabled = await arePrePublishChecksEnabled(); if ( ! werePrePublishChecksEnabled ) { await enablePrePublishChecks(); @@ -52,7 +52,7 @@ describe( 'PostPublishPanel', () => { it( 'should retain focus within the panel', async () => { await page.type( '.editor-post-title__input', 'E2E Test Post' ); await openPublishPanel(); - await pressWithModifier( 'shift', 'Tab' ); + await pressKeyWithModifier( 'shift', 'Tab' ); const focusedElementClassList = await page.$eval( ':focus', ( focusedElement ) => { return Object.values( focusedElement.classList ); diff --git a/test/e2e/specs/publishing.test.js b/test/e2e/specs/publishing.test.js index eca85bf6f01ed..e2face65187a3 100644 --- a/test/e2e/specs/publishing.test.js +++ b/test/e2e/specs/publishing.test.js @@ -2,13 +2,13 @@ * Internal dependencies */ import { - newPost, + createNewPost, publishPost, - publishPostWithoutPrePublishChecks, + publishPostWithPrePublishChecksDisabled, enablePrePublishChecks, disablePrePublishChecks, arePrePublishChecksEnabled, - setViewport, + setBrowserViewport, } from '../support/utils'; describe( 'Publishing', () => { @@ -16,7 +16,7 @@ describe( 'Publishing', () => { let werePrePublishChecksEnabled; describe( `a ${ postType }`, () => { beforeEach( async () => { - await newPost( postType ); + await createNewPost( postType ); werePrePublishChecksEnabled = await arePrePublishChecksEnabled(); if ( ! werePrePublishChecksEnabled ) { await enablePrePublishChecks(); @@ -50,7 +50,7 @@ describe( 'Publishing', () => { let werePrePublishChecksEnabled; describe( `a ${ postType } with pre-publish checks disabled`, () => { beforeEach( async () => { - await newPost( postType ); + await createNewPost( postType ); werePrePublishChecksEnabled = await arePrePublishChecksEnabled(); if ( werePrePublishChecksEnabled ) { await disablePrePublishChecks(); @@ -70,7 +70,7 @@ describe( 'Publishing', () => { expect( await page.$( '.editor-post-publish-panel__toggle' ) ).toBeNull(); expect( await page.$( '.editor-post-publish-button' ) ).not.toBeNull(); - await publishPostWithoutPrePublishChecks(); + await publishPostWithPrePublishChecksDisabled(); // The post-publishing panel should have been not shown. expect( await page.$( '.editor-post-publish-panel' ) ).toBeNull(); @@ -82,16 +82,16 @@ describe( 'Publishing', () => { let werePrePublishChecksEnabled; describe( `a ${ postType } in small viewports`, () => { beforeEach( async () => { - await newPost( postType ); + await createNewPost( postType ); werePrePublishChecksEnabled = await arePrePublishChecksEnabled(); if ( werePrePublishChecksEnabled ) { await disablePrePublishChecks(); } - await setViewport( 'small' ); + await setBrowserViewport( 'small' ); } ); afterEach( async () => { - await setViewport( 'large' ); + await setBrowserViewport( 'large' ); if ( werePrePublishChecksEnabled ) { await enablePrePublishChecks(); } diff --git a/test/e2e/specs/reusable-blocks.test.js b/test/e2e/specs/reusable-blocks.test.js index 3dc3d32afa6c1..a73f6917440dd 100644 --- a/test/e2e/specs/reusable-blocks.test.js +++ b/test/e2e/specs/reusable-blocks.test.js @@ -3,8 +3,8 @@ */ import { insertBlock, - newPost, - pressWithModifier, + createNewPost, + pressKeyWithModifier, searchForBlock, getEditedPostContent, } from '../support/utils'; @@ -17,7 +17,7 @@ function waitForAndAcceptDialog() { describe( 'Reusable Blocks', () => { beforeAll( async () => { - await newPost(); + await createNewPost(); } ); beforeEach( async () => { @@ -210,7 +210,7 @@ describe( 'Reusable Blocks', () => { } ); it( 'can be created from multiselection', async () => { - await newPost(); + await createNewPost(); // Insert a Two paragraphs block await insertBlock( 'Paragraph' ); @@ -219,8 +219,8 @@ describe( 'Reusable Blocks', () => { await page.keyboard.type( 'Second paragraph' ); // Select all the blocks - await pressWithModifier( 'primary', 'a' ); - await pressWithModifier( 'primary', 'a' ); + await pressKeyWithModifier( 'primary', 'a' ); + await pressKeyWithModifier( 'primary', 'a' ); // Trigger isTyping = false await page.mouse.move( 200, 300, { steps: 10 } ); diff --git a/test/e2e/specs/rich-text.test.js b/test/e2e/specs/rich-text.test.js index 2f1012766444f..6b472f2085c6d 100644 --- a/test/e2e/specs/rich-text.test.js +++ b/test/e2e/specs/rich-text.test.js @@ -2,16 +2,16 @@ * Internal dependencies */ import { - newPost, + createNewPost, getEditedPostContent, insertBlock, clickBlockAppender, - pressWithModifier, + pressKeyWithModifier, } from '../support/utils'; describe( 'RichText', () => { beforeEach( async () => { - await newPost(); + await createNewPost(); } ); it( 'should handle change in tag name gracefully', async () => { @@ -29,8 +29,8 @@ describe( 'RichText', () => { it( 'should apply formatting with access shortcut', async () => { await clickBlockAppender(); await page.keyboard.type( 'test' ); - await pressWithModifier( 'primary', 'a' ); - await pressWithModifier( 'access', 'd' ); + await pressKeyWithModifier( 'primary', 'a' ); + await pressKeyWithModifier( 'access', 'd' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); @@ -38,8 +38,8 @@ describe( 'RichText', () => { it( 'should apply formatting with primary shortcut', async () => { await clickBlockAppender(); await page.keyboard.type( 'test' ); - await pressWithModifier( 'primary', 'a' ); - await pressWithModifier( 'primary', 'b' ); + await pressKeyWithModifier( 'primary', 'a' ); + await pressKeyWithModifier( 'primary', 'b' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); @@ -48,10 +48,10 @@ describe( 'RichText', () => { await clickBlockAppender(); await page.keyboard.type( 'Some ' ); // All following characters should now be bold. - await pressWithModifier( 'primary', 'b' ); + await pressKeyWithModifier( 'primary', 'b' ); await page.keyboard.type( 'bold' ); // All following characters should no longer be bold. - await pressWithModifier( 'primary', 'b' ); + await pressKeyWithModifier( 'primary', 'b' ); await page.keyboard.type( '.' ); expect( await getEditedPostContent() ).toMatchSnapshot(); @@ -63,7 +63,7 @@ describe( 'RichText', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); - await pressWithModifier( 'primary', 'z' ); + await pressKeyWithModifier( 'primary', 'z' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); @@ -71,9 +71,9 @@ describe( 'RichText', () => { it( 'should only mutate text data on input', async () => { await clickBlockAppender(); await page.keyboard.type( '1' ); - await pressWithModifier( 'primary', 'b' ); + await pressKeyWithModifier( 'primary', 'b' ); await page.keyboard.type( '2' ); - await pressWithModifier( 'primary', 'b' ); + await pressKeyWithModifier( 'primary', 'b' ); await page.keyboard.type( '3' ); await page.evaluate( () => { diff --git a/test/e2e/specs/shortcut-help.test.js b/test/e2e/specs/shortcut-help.test.js index ea69639fe3622..064e8466bc3aa 100644 --- a/test/e2e/specs/shortcut-help.test.js +++ b/test/e2e/specs/shortcut-help.test.js @@ -2,15 +2,15 @@ * Internal dependencies */ import { - newPost, + createNewPost, clickOnMoreMenuItem, clickOnCloseModalButton, - pressWithModifier, + pressKeyWithModifier, } from '../support/utils'; describe( 'keyboard shortcut help modal', () => { beforeAll( async () => { - await newPost(); + await createNewPost(); } ); it( 'displays the shortcut help modal when opened using the menu item in the more menu', async () => { @@ -26,13 +26,13 @@ describe( 'keyboard shortcut help modal', () => { } ); it( 'displays the shortcut help modal when opened using the shortcut key (access+h)', async () => { - await pressWithModifier( 'access', 'h' ); + await pressKeyWithModifier( 'access', 'h' ); const shortcutHelpModalElements = await page.$$( '.edit-post-keyboard-shortcut-help' ); expect( shortcutHelpModalElements ).toHaveLength( 1 ); } ); it( 'closes the shortcut help modal when the shortcut key (access+h) is pressed again', async () => { - await pressWithModifier( 'access', 'h' ); + await pressKeyWithModifier( 'access', 'h' ); const shortcutHelpModalElements = await page.$$( '.edit-post-keyboard-shortcut-help' ); expect( shortcutHelpModalElements ).toHaveLength( 0 ); } ); diff --git a/test/e2e/specs/sidebar-permalink-panel.test.js b/test/e2e/specs/sidebar-permalink-panel.test.js index cff6ce3ea7bec..95e18e3df49bd 100644 --- a/test/e2e/specs/sidebar-permalink-panel.test.js +++ b/test/e2e/specs/sidebar-permalink-panel.test.js @@ -2,12 +2,13 @@ * Internal dependencies */ import { + activatePlugin, + createNewPost, + deactivatePlugin, findSidebarPanelWithTitle, - newPost, openDocumentSettingsSidebar, publishPost, } from '../support/utils'; -import { activatePlugin, deactivatePlugin } from '../support/plugins'; // This tests are not together with the remaining sidebar tests, // because we need to publish/save a post, to correctly test the permalink panel. @@ -23,13 +24,13 @@ describe( 'Sidebar Permalink Panel', () => { } ); it( 'should not render permalink sidebar panel while the post is new', async () => { - await newPost(); + await createNewPost(); await openDocumentSettingsSidebar(); expect( await findSidebarPanelWithTitle( 'Permalink' ) ).toBeUndefined(); } ); it( 'should render permalink sidebar panel after the post is published and allow its removal', async () => { - await newPost(); + await createNewPost(); await page.keyboard.type( 'aaaaa' ); await publishPost(); // Start editing again. @@ -43,7 +44,7 @@ describe( 'Sidebar Permalink Panel', () => { } ); it( 'should not render link panel when post is publicly queryable but not public', async () => { - await newPost( { postType: 'public_q_not_public' } ); + await createNewPost( { postType: 'public_q_not_public' } ); await page.keyboard.type( 'aaaaa' ); await publishPost(); // Start editing again. @@ -52,7 +53,7 @@ describe( 'Sidebar Permalink Panel', () => { } ); it( 'should not render link panel when post is public but not publicly queryable', async () => { - await newPost( { postType: 'not_public_q_public' } ); + await createNewPost( { postType: 'not_public_q_public' } ); await page.keyboard.type( 'aaaaa' ); await publishPost(); // Start editing again. @@ -61,7 +62,7 @@ describe( 'Sidebar Permalink Panel', () => { } ); it( 'should render link panel when post is public and publicly queryable', async () => { - await newPost( { postType: 'public_q_public' } ); + await createNewPost( { postType: 'public_q_public' } ); await page.keyboard.type( 'aaaaa' ); await publishPost(); // Start editing again. diff --git a/test/e2e/specs/sidebar.test.js b/test/e2e/specs/sidebar.test.js index 3b5c9b9271667..81cd4dfd57c64 100644 --- a/test/e2e/specs/sidebar.test.js +++ b/test/e2e/specs/sidebar.test.js @@ -3,11 +3,11 @@ */ import { findSidebarPanelWithTitle, - newPost, + createNewPost, observeFocusLoss, openDocumentSettingsSidebar, - pressWithModifier, - setViewport, + pressKeyWithModifier, + setBrowserViewport, } from '../support/utils'; const SIDEBAR_SELECTOR = '.edit-post-sidebar'; @@ -20,8 +20,8 @@ describe( 'Sidebar', () => { } ); it( 'should have sidebar visible at the start with document sidebar active on desktop', async () => { - await setViewport( 'large' ); - await newPost(); + await setBrowserViewport( 'large' ); + await createNewPost(); const { nodesCount, content, height, width } = await page.$$eval( ACTIVE_SIDEBAR_TAB_SELECTOR, ( nodes ) => { const firstNode = nodes[ 0 ]; return { @@ -44,20 +44,20 @@ describe( 'Sidebar', () => { } ); it( 'should have the sidebar closed by default on mobile', async () => { - await setViewport( 'small' ); - await newPost(); + await setBrowserViewport( 'small' ); + await createNewPost(); const sidebar = await page.$( SIDEBAR_SELECTOR ); expect( sidebar ).toBeNull(); } ); it( 'should close the sidebar when resizing from desktop to mobile', async () => { - await setViewport( 'large' ); - await newPost(); + await setBrowserViewport( 'large' ); + await createNewPost(); const sidebars = await page.$$( SIDEBAR_SELECTOR ); expect( sidebars ).toHaveLength( 1 ); - await setViewport( 'small' ); + await setBrowserViewport( 'small' ); const sidebarsMobile = await page.$$( SIDEBAR_SELECTOR ); // sidebar should be closed when resizing to mobile. @@ -65,27 +65,27 @@ describe( 'Sidebar', () => { } ); it( 'should reopen sidebar the sidebar when resizing from mobile to desktop if the sidebar was closed automatically', async () => { - await setViewport( 'large' ); - await newPost(); - await setViewport( 'small' ); + await setBrowserViewport( 'large' ); + await createNewPost(); + await setBrowserViewport( 'small' ); const sidebarsMobile = await page.$$( SIDEBAR_SELECTOR ); expect( sidebarsMobile ).toHaveLength( 0 ); - await setViewport( 'large' ); + await setBrowserViewport( 'large' ); const sidebarsDesktop = await page.$$( SIDEBAR_SELECTOR ); expect( sidebarsDesktop ).toHaveLength( 1 ); } ); it( 'should preserve tab order while changing active tab', async () => { - await newPost(); + await createNewPost(); // Region navigate to Sidebar. - await pressWithModifier( 'ctrl', '`' ); - await pressWithModifier( 'ctrl', '`' ); - await pressWithModifier( 'ctrl', '`' ); - await pressWithModifier( 'ctrl', '`' ); + await pressKeyWithModifier( 'ctrl', '`' ); + await pressKeyWithModifier( 'ctrl', '`' ); + await pressKeyWithModifier( 'ctrl', '`' ); + await pressKeyWithModifier( 'ctrl', '`' ); // Tab lands at first (presumed selected) option "Document". await page.keyboard.press( 'Tab' ); @@ -106,7 +106,7 @@ describe( 'Sidebar', () => { } ); it( 'should be possible to programmatically remove Document Settings panels', async () => { - await newPost(); + await createNewPost(); await openDocumentSettingsSidebar(); diff --git a/test/e2e/specs/splitting-merging.test.js b/test/e2e/specs/splitting-merging.test.js index f6456dfa1e342..e45a62fff6507 100644 --- a/test/e2e/specs/splitting-merging.test.js +++ b/test/e2e/specs/splitting-merging.test.js @@ -2,16 +2,16 @@ * Internal dependencies */ import { - newPost, + createNewPost, insertBlock, getEditedPostContent, - pressTimes, - pressWithModifier, + pressKeyTimes, + pressKeyWithModifier, } from '../support/utils'; describe( 'splitting and merging blocks', () => { beforeEach( async () => { - await newPost(); + await createNewPost(); } ); it( 'should split and merge paragraph blocks using Enter and Backspace', async () => { @@ -21,7 +21,7 @@ describe( 'splitting and merging blocks', () => { // Move caret between 'First' and 'Second' and press Enter to split // paragraph blocks - await pressTimes( 'ArrowLeft', 6 ); + await pressKeyTimes( 'ArrowLeft', 6 ); await page.keyboard.press( 'Enter' ); // Assert that there are now two paragraph blocks with correct content @@ -34,16 +34,16 @@ describe( 'splitting and merging blocks', () => { await page.keyboard.type( 'Between' ); expect( await getEditedPostContent() ).toMatchSnapshot(); - await pressTimes( 'Backspace', 7 ); // Delete "Between" + await pressKeyTimes( 'Backspace', 7 ); // Delete "Between" // Edge case: Without ensuring that the editor still has focus when // restoring a bookmark, the caret may be inadvertently moved back to // an inline boundary after a split occurs. await page.keyboard.press( 'Home' ); await page.keyboard.down( 'Shift' ); - await pressTimes( 'ArrowRight', 5 ); + await pressKeyTimes( 'ArrowRight', 5 ); await page.keyboard.up( 'Shift' ); - await pressWithModifier( 'primary', 'b' ); + await pressKeyWithModifier( 'primary', 'b' ); // Collapse selection, still within inline boundary. await page.keyboard.press( 'ArrowRight' ); await page.keyboard.press( 'Enter' ); @@ -56,13 +56,13 @@ describe( 'splitting and merging blocks', () => { // Regression Test: Caret should reset to end of inline boundary when // backspacing to delete second paragraph. await insertBlock( 'Paragraph' ); - await pressWithModifier( 'primary', 'b' ); + await pressKeyWithModifier( 'primary', 'b' ); await page.keyboard.type( 'Foo' ); await page.keyboard.press( 'Enter' ); await page.keyboard.press( 'Backspace' ); // Replace contents of first paragraph with "Bar". - await pressTimes( 'Backspace', 4 ); + await pressKeyTimes( 'Backspace', 4 ); await page.keyboard.type( 'Bar' ); expect( await getEditedPostContent() ).toMatchSnapshot(); @@ -107,7 +107,7 @@ describe( 'splitting and merging blocks', () => { // Select text. await page.keyboard.down( 'Shift' ); - await pressTimes( 'ArrowLeft', 3 ); + await pressKeyTimes( 'ArrowLeft', 3 ); await page.keyboard.up( 'Shift' ); // Delete selection. @@ -126,9 +126,9 @@ describe( 'splitting and merging blocks', () => { // The regression appeared to only affect paragraphs created while // within an inline boundary. await page.keyboard.down( 'Shift' ); - await pressTimes( 'ArrowLeft', 3 ); + await pressKeyTimes( 'ArrowLeft', 3 ); await page.keyboard.up( 'Shift' ); - await pressWithModifier( 'primary', 'b' ); + await pressKeyWithModifier( 'primary', 'b' ); await page.keyboard.press( 'ArrowRight' ); await page.keyboard.press( 'Enter' ); await page.keyboard.press( 'Enter' ); diff --git a/test/e2e/specs/style-variation.test.js b/test/e2e/specs/style-variation.test.js index 360b8378bd345..660d779b646a4 100644 --- a/test/e2e/specs/style-variation.test.js +++ b/test/e2e/specs/style-variation.test.js @@ -1,11 +1,11 @@ /** * Internal dependencies */ -import { newPost, insertBlock, getEditedPostContent } from '../support/utils'; +import { createNewPost, insertBlock, getEditedPostContent } from '../support/utils'; describe( 'adding blocks', () => { beforeAll( async () => { - await newPost(); + await createNewPost(); } ); it( 'Should switch the style of the quote block', async () => { diff --git a/test/e2e/specs/taxonomies.test.js b/test/e2e/specs/taxonomies.test.js index a247b28968991..6ee40e925b2d4 100644 --- a/test/e2e/specs/taxonomies.test.js +++ b/test/e2e/specs/taxonomies.test.js @@ -3,7 +3,7 @@ */ import { findSidebarPanelWithTitle, - newPost, + createNewPost, openDocumentSettingsSidebar, publishPost, } from '../support/utils'; @@ -37,7 +37,7 @@ describe( 'Taxonomies', () => { }; it( 'should be able to open the categories panel and create a new main category if the user has the right capabilities', async () => { - await newPost(); + await createNewPost(); await openDocumentSettingsSidebar(); diff --git a/test/e2e/specs/templates.test.js b/test/e2e/specs/templates.test.js index 4d8d16e9ce951..a2e06eb0a8751 100644 --- a/test/e2e/specs/templates.test.js +++ b/test/e2e/specs/templates.test.js @@ -2,16 +2,17 @@ * Internal dependencies */ import { - newPost, + activatePlugin, + clickBlockAppender, + createNewPost, + deactivatePlugin, getEditedPostContent, + pressKeyWithModifier, saveDraft, - pressWithModifier, - visitAdmin, - clickBlockAppender, - switchToAdminUser, - switchToTestUser, + switchUserToAdmin, + switchUserToTest, + visitAdminPage, } from '../support/utils'; -import { activatePlugin, deactivatePlugin } from '../support/plugins'; describe( 'templates', () => { describe( 'Using a CPT with a predefined template', () => { @@ -20,7 +21,7 @@ describe( 'templates', () => { } ); beforeEach( async () => { - await newPost( { postType: 'book' } ); + await createNewPost( { postType: 'book' } ); } ); afterAll( async () => { @@ -48,7 +49,7 @@ describe( 'templates', () => { // re-added after saving and reloading the editor. await page.type( '.editor-post-title__input', 'My Empty Book' ); await page.keyboard.press( 'ArrowDown' ); - await pressWithModifier( 'primary', 'A' ); + await pressKeyWithModifier( 'primary', 'A' ); await page.keyboard.press( 'Backspace' ); await saveDraft(); await page.reload(); @@ -62,14 +63,14 @@ describe( 'templates', () => { async function setPostFormat( format ) { // To set the post format, we need to be the admin user. - await switchToAdminUser(); - await visitAdmin( 'options-writing.php' ); + await switchUserToAdmin(); + await visitAdminPage( 'options-writing.php' ); await page.select( '#default_post_format', format ); await Promise.all( [ page.waitForNavigation(), page.click( '#submit' ), ] ); - await switchToTestUser(); + await switchUserToTest(); } beforeAll( async () => { @@ -82,13 +83,13 @@ describe( 'templates', () => { } ); it( 'should populate new post with default block for format', async () => { - await newPost(); + await createNewPost(); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); it( 'should not populate edited post with default block for format', async () => { - await newPost(); + await createNewPost(); // Remove the default block template to verify that it's not // re-added after saving and reloading the editor. @@ -105,11 +106,11 @@ describe( 'templates', () => { it( 'should not populate new page with default block for format', async () => { // This test always needs to run as the admin user, because other roles can't create pages. // It can't be skipped, because then it failed because of not testing the snapshot. - await switchToAdminUser(); - await newPost( { postType: 'page' } ); + await switchUserToAdmin(); + await createNewPost( { postType: 'page' } ); expect( await getEditedPostContent() ).toMatchSnapshot(); - await switchToTestUser(); + await switchUserToTest(); } ); } ); } ); diff --git a/test/e2e/specs/undo.test.js b/test/e2e/specs/undo.test.js index fe67a73dfb680..5a9eb0351ec41 100644 --- a/test/e2e/specs/undo.test.js +++ b/test/e2e/specs/undo.test.js @@ -4,13 +4,13 @@ import { clickBlockAppender, getEditedPostContent, - newPost, - pressWithModifier, + createNewPost, + pressKeyWithModifier, } from '../support/utils'; describe( 'undo', () => { beforeEach( async () => { - await newPost(); + await createNewPost(); } ); it( 'should undo typing after a pause', async () => { @@ -22,7 +22,7 @@ describe( 'undo', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); - await pressWithModifier( 'primary', 'z' ); + await pressKeyWithModifier( 'primary', 'z' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); @@ -31,12 +31,12 @@ describe( 'undo', () => { await clickBlockAppender(); await page.keyboard.type( 'before keyboard ' ); - await pressWithModifier( 'primary', 'b' ); + await pressKeyWithModifier( 'primary', 'b' ); await page.keyboard.type( 'after keyboard' ); expect( await getEditedPostContent() ).toMatchSnapshot(); - await pressWithModifier( 'primary', 'z' ); + await pressKeyWithModifier( 'primary', 'z' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); @@ -52,12 +52,12 @@ describe( 'undo', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); - await pressWithModifier( 'primary', 'z' ); // Undo 3rd paragraph text. - await pressWithModifier( 'primary', 'z' ); // Undo 3rd block. - await pressWithModifier( 'primary', 'z' ); // Undo 2nd paragraph text. - await pressWithModifier( 'primary', 'z' ); // Undo 2nd block. - await pressWithModifier( 'primary', 'z' ); // Undo 1st paragraph text. - await pressWithModifier( 'primary', 'z' ); // Undo 1st block. + await pressKeyWithModifier( 'primary', 'z' ); // Undo 3rd paragraph text. + await pressKeyWithModifier( 'primary', 'z' ); // Undo 3rd block. + await pressKeyWithModifier( 'primary', 'z' ); // Undo 2nd paragraph text. + await pressKeyWithModifier( 'primary', 'z' ); // Undo 2nd block. + await pressKeyWithModifier( 'primary', 'z' ); // Undo 1st paragraph text. + await pressKeyWithModifier( 'primary', 'z' ); // Undo 1st block. expect( await getEditedPostContent() ).toBe( '' ); // After undoing every action, there should be no more undo history. diff --git a/test/e2e/specs/wp-editor-meta-box.test.js b/test/e2e/specs/wp-editor-meta-box.test.js index 496057bb55c1c..579097afcf91e 100644 --- a/test/e2e/specs/wp-editor-meta-box.test.js +++ b/test/e2e/specs/wp-editor-meta-box.test.js @@ -1,13 +1,17 @@ /** * Internal dependencies */ -import { newPost, publishPost } from '../support/utils'; -import { activatePlugin, deactivatePlugin } from '../support/plugins'; +import { + activatePlugin, + createNewPost, + deactivatePlugin, + publishPost, +} from '../support/utils'; describe( 'WP Editor Meta Boxes', () => { beforeAll( async () => { await activatePlugin( 'gutenberg-test-plugin-wp-editor-meta-box' ); - await newPost(); + await createNewPost(); } ); afterAll( async () => { diff --git a/test/e2e/specs/writing-flow.test.js b/test/e2e/specs/writing-flow.test.js index 064eaf681d6a8..24e21642862f6 100644 --- a/test/e2e/specs/writing-flow.test.js +++ b/test/e2e/specs/writing-flow.test.js @@ -4,14 +4,14 @@ import { clickBlockAppender, getEditedPostContent, - newPost, - pressTimes, - pressWithModifier, + createNewPost, + pressKeyTimes, + pressKeyWithModifier, } from '../support/utils'; describe( 'adding blocks', () => { beforeEach( async () => { - await newPost(); + await createNewPost(); } ); it( 'Should navigate inner blocks with arrow keys', async () => { @@ -82,13 +82,13 @@ describe( 'adding blocks', () => { await page.keyboard.type( 'Third' ); // Navigate to second paragraph - await pressTimes( 'ArrowLeft', 6 ); + await pressKeyTimes( 'ArrowLeft', 6 ); // Bold second paragraph text await page.keyboard.down( 'Shift' ); - await pressTimes( 'ArrowLeft', 6 ); + await pressKeyTimes( 'ArrowLeft', 6 ); await page.keyboard.up( 'Shift' ); - await pressWithModifier( 'primary', 'b' ); + await pressKeyWithModifier( 'primary', 'b' ); // Arrow left from selected bold should collapse to before the inline // boundary. Arrow once more to traverse into first paragraph. @@ -109,18 +109,18 @@ describe( 'adding blocks', () => { // Arrow right from end of first should traverse to second, *BEFORE* // the bolded text. Another press should move within inline boundary. - await pressTimes( 'ArrowRight', 2 ); + await pressKeyTimes( 'ArrowRight', 2 ); await page.keyboard.type( 'Inside' ); // Arrow left from end of beginning of inline boundary should move to // the outside of the inline boundary. - await pressTimes( 'ArrowLeft', 6 ); + await pressKeyTimes( 'ArrowLeft', 6 ); await page.keyboard.press( 'ArrowLeft' ); // Separate for emphasis. await page.keyboard.type( 'Before' ); // Likewise, test at the end of the inline boundary for same effect. await page.keyboard.press( 'ArrowRight' ); // Move inside - await pressTimes( 'ArrowRight', 12 ); + await pressKeyTimes( 'ArrowRight', 12 ); await page.keyboard.type( 'Inside' ); await page.keyboard.press( 'ArrowRight' ); @@ -145,7 +145,7 @@ describe( 'adding blocks', () => { // Ensure no zero-width space character. Notably, this can occur when // save occurs while at an inline boundary edge. await clickBlockAppender(); - await pressWithModifier( 'primary', 'b' ); + await pressKeyWithModifier( 'primary', 'b' ); expect( await getEditedPostContent() ).toMatchSnapshot(); // Backspace to remove the content in this block, resetting it. @@ -153,7 +153,7 @@ describe( 'adding blocks', () => { // Ensure no data-mce-selected. Notably, this can occur when content // is saved while typing within an inline boundary. - await pressWithModifier( 'primary', 'b' ); + await pressKeyWithModifier( 'primary', 'b' ); await page.keyboard.type( 'Inside' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); @@ -161,14 +161,14 @@ describe( 'adding blocks', () => { it( 'should insert line break at end', async () => { await clickBlockAppender(); await page.keyboard.type( 'a' ); - await pressWithModifier( 'shift', 'Enter' ); + await pressKeyWithModifier( 'shift', 'Enter' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); it( 'should insert line break at end and continue writing', async () => { await clickBlockAppender(); await page.keyboard.type( 'a' ); - await pressWithModifier( 'shift', 'Enter' ); + await pressKeyWithModifier( 'shift', 'Enter' ); await page.keyboard.type( 'b' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); @@ -177,7 +177,7 @@ describe( 'adding blocks', () => { await clickBlockAppender(); await page.keyboard.type( 'ab' ); await page.keyboard.press( 'ArrowLeft' ); - await pressWithModifier( 'shift', 'Enter' ); + await pressKeyWithModifier( 'shift', 'Enter' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); @@ -185,13 +185,13 @@ describe( 'adding blocks', () => { await clickBlockAppender(); await page.keyboard.type( 'a' ); await page.keyboard.press( 'ArrowLeft' ); - await pressWithModifier( 'shift', 'Enter' ); + await pressKeyWithModifier( 'shift', 'Enter' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); it( 'should insert line break in empty container', async () => { await clickBlockAppender(); - await pressWithModifier( 'shift', 'Enter' ); + await pressKeyWithModifier( 'shift', 'Enter' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); @@ -209,7 +209,7 @@ describe( 'adding blocks', () => { expect( isInTitle ).toBe( true ); // Should remain in title upon modifier + ArrowDown: - await pressWithModifier( 'primary', 'ArrowDown' ); + await pressKeyWithModifier( 'primary', 'ArrowDown' ); isInTitle = await page.evaluate( () => ( !! document.activeElement.closest( '.editor-post-title' ) ) ); @@ -226,7 +226,7 @@ describe( 'adding blocks', () => { it( 'should not delete surrounding space when deleting a word with Backspace', async () => { await clickBlockAppender(); await page.keyboard.type( '1 2 3' ); - await pressTimes( 'ArrowLeft', ' 3'.length ); + await pressKeyTimes( 'ArrowLeft', ' 3'.length ); await page.keyboard.press( 'Backspace' ); expect( await getEditedPostContent() ).toMatchSnapshot(); @@ -239,12 +239,12 @@ describe( 'adding blocks', () => { it( 'should not delete surrounding space when deleting a word with Alt+Backspace', async () => { await clickBlockAppender(); await page.keyboard.type( 'alpha beta gamma' ); - await pressTimes( 'ArrowLeft', ' gamma'.length ); + await pressKeyTimes( 'ArrowLeft', ' gamma'.length ); if ( process.platform === 'darwin' ) { - await pressWithModifier( 'alt', 'Backspace' ); + await pressKeyWithModifier( 'alt', 'Backspace' ); } else { - await pressWithModifier( 'primary', 'Backspace' ); + await pressKeyWithModifier( 'primary', 'Backspace' ); } expect( await getEditedPostContent() ).toMatchSnapshot(); @@ -257,9 +257,9 @@ describe( 'adding blocks', () => { it( 'should not delete surrounding space when deleting a selected word', async () => { await clickBlockAppender(); await page.keyboard.type( 'alpha beta gamma' ); - await pressTimes( 'ArrowLeft', ' gamma'.length ); + await pressKeyTimes( 'ArrowLeft', ' gamma'.length ); await page.keyboard.down( 'Shift' ); - await pressTimes( 'ArrowLeft', 'beta'.length ); + await pressKeyTimes( 'ArrowLeft', 'beta'.length ); await page.keyboard.up( 'Shift' ); await page.keyboard.press( 'Backspace' ); @@ -272,7 +272,7 @@ describe( 'adding blocks', () => { it( 'should create valid paragraph blocks when rapidly pressing Enter', async () => { await clickBlockAppender(); - await pressTimes( 'Enter', 10 ); + await pressKeyTimes( 'Enter', 10 ); // Check that none of the paragraph blocks have <br> in them. expect( await getEditedPostContent() ).toMatchSnapshot(); diff --git a/test/e2e/support/plugins.js b/test/e2e/support/plugins.js deleted file mode 100644 index f8d64a7a1ec17..0000000000000 --- a/test/e2e/support/plugins.js +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Node dependencies - */ -import { visitAdmin, switchToAdminUser, switchToTestUser } from './utils'; - -/** - * Install a plugin from the WP.org repository. - * - * @param {string} slug Plugin slug. - * @param {string?} searchTerm If the plugin is not findable by its slug use an alternative term to search. - */ -export async function installPlugin( slug, searchTerm ) { - await switchToAdminUser(); - await visitAdmin( 'plugin-install.php', 's=' + encodeURIComponent( searchTerm || slug ) + '&tab=search&type=term' ); - await page.click( '.install-now[data-slug="' + slug + '"]' ); - await page.waitForSelector( '.activate-now[data-slug="' + slug + '"]' ); - await switchToTestUser(); -} - -/** - * Activates an installed plugin. - * - * @param {string} slug Plugin slug. - */ -export async function activatePlugin( slug ) { - await switchToAdminUser(); - await visitAdmin( 'plugins.php' ); - await page.click( 'tr[data-slug="' + slug + '"] .activate a' ); - await page.waitForSelector( 'tr[data-slug="' + slug + '"] .deactivate a' ); - await switchToTestUser(); -} - -/** - * Deactivates an active plugin. - * - * @param {string} slug Plugin slug. - */ -export async function deactivatePlugin( slug ) { - await switchToAdminUser(); - await visitAdmin( 'plugins.php' ); - await page.click( 'tr[data-slug="' + slug + '"] .deactivate a' ); - await page.waitForSelector( 'tr[data-slug="' + slug + '"] .delete a' ); - await switchToTestUser(); -} - -/** - * Uninstall a plugin. - * - * @param {string} slug Plugin slug. - */ -export async function uninstallPlugin( slug ) { - await switchToAdminUser(); - await visitAdmin( 'plugins.php' ); - const confirmPromise = new Promise( ( resolve ) => { - page.once( 'dialog', () => resolve() ); - } ); - await Promise.all( [ - confirmPromise, - page.click( 'tr[data-slug="' + slug + '"] .delete a' ), - ] ); - await page.waitForSelector( 'tr[data-slug="' + slug + '"].deleted' ); - await switchToTestUser(); -} diff --git a/test/e2e/support/setup-test-framework.js b/test/e2e/support/setup-test-framework.js index 46961026e8629..ce775d4da2513 100644 --- a/test/e2e/support/setup-test-framework.js +++ b/test/e2e/support/setup-test-framework.js @@ -15,8 +15,8 @@ import '@wordpress/jest-console'; import { clearLocalStorage, enablePageDialogAccept, - setViewport, - visitAdmin, + setBrowserViewport, + visitAdminPage, } from './utils'; /** @@ -49,7 +49,7 @@ jest.setTimeout( PUPPETEER_TIMEOUT || 100000 ); async function setupBrowser() { await clearLocalStorage(); - await setViewport( 'large' ); + await setBrowserViewport( 'large' ); } /** @@ -59,7 +59,7 @@ async function setupBrowser() { */ async function trashExistingPosts() { // Visit `/wp-admin/edit.php` so we can see a list of posts and delete them. - await visitAdmin( 'edit.php' ); + await visitAdminPage( 'edit.php' ); // If this selector doesn't exist there are no posts for us to delete. const bulkSelector = await page.$( '#bulk-action-selector-top' ); diff --git a/test/e2e/support/utils/accept-page-dialog.js b/test/e2e/support/utils/accept-page-dialog.js deleted file mode 100644 index d10b40bf4f94e..0000000000000 --- a/test/e2e/support/utils/accept-page-dialog.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Callback which automatically accepts dialog. - * - * @param {puppeteer.Dialog} dialog Dialog object dispatched by page via the 'dialog' event. - */ -export async function acceptPageDialog( dialog ) { - await dialog.accept(); -} diff --git a/test/e2e/support/utils/activate-plugin.js b/test/e2e/support/utils/activate-plugin.js new file mode 100644 index 0000000000000..c99b70b92376d --- /dev/null +++ b/test/e2e/support/utils/activate-plugin.js @@ -0,0 +1,19 @@ +/** + * Node dependencies + */ +import { switchUserToAdmin } from './switch-user-to-admin'; +import { switchUserToTest } from './switch-user-to-test'; +import { visitAdminPage } from './visit-admin-page'; + +/** + * Activates an installed plugin. + * + * @param {string} slug Plugin slug. + */ +export async function activatePlugin( slug ) { + await switchUserToAdmin(); + await visitAdminPage( 'plugins.php' ); + await page.click( `tr[data-slug="${ slug }"] .activate a` ); + await page.waitForSelector( `tr[data-slug="${ slug }"] .deactivate a` ); + await switchUserToTest(); +} diff --git a/test/e2e/support/utils/new-post.js b/test/e2e/support/utils/create-new-post.js similarity index 82% rename from test/e2e/support/utils/new-post.js rename to test/e2e/support/utils/create-new-post.js index cdc229fa73f73..91a95bf5216a4 100644 --- a/test/e2e/support/utils/new-post.js +++ b/test/e2e/support/utils/create-new-post.js @@ -6,14 +6,14 @@ import { addQueryArgs } from '@wordpress/url'; /** * Internal dependencies */ -import { visitAdmin } from './visit-admin'; +import { visitAdminPage } from './visit-admin-page'; /** * Creates new post. * * @param {Object} obj Object to create new post, along with tips enabling option. */ -export async function newPost( { +export async function createNewPost( { postType, title, content, @@ -26,7 +26,7 @@ export async function newPost( { content, excerpt, } ).slice( 1 ); - await visitAdmin( 'post-new.php', query ); + await visitAdminPage( 'post-new.php', query ); await page.evaluate( ( _enableTips ) => { const action = _enableTips ? 'enableTips' : 'disableTips'; diff --git a/test/e2e/support/utils/get-url.js b/test/e2e/support/utils/create-url.js similarity index 83% rename from test/e2e/support/utils/get-url.js rename to test/e2e/support/utils/create-url.js index 3f8d3fa533dce..ff7271c8ebf8f 100644 --- a/test/e2e/support/utils/get-url.js +++ b/test/e2e/support/utils/create-url.js @@ -4,7 +4,7 @@ import { join } from 'path'; import { URL } from 'url'; -import { WP_BASE_URL } from './config'; +import { WP_BASE_URL } from './shared/config'; /** * Creates new URL by parsing base URL, WPPath and query string. @@ -13,7 +13,7 @@ import { WP_BASE_URL } from './config'; * @param {?string} query String to be serialized as query portion of URL. * @return {string} String which represents full URL. */ -export function getUrl( WPPath, query = '' ) { +export function createURL( WPPath, query = '' ) { const url = new URL( WP_BASE_URL ); url.pathname = join( url.pathname, WPPath ); diff --git a/test/e2e/support/utils/deactivate-plugin.js b/test/e2e/support/utils/deactivate-plugin.js new file mode 100644 index 0000000000000..43120f20d4034 --- /dev/null +++ b/test/e2e/support/utils/deactivate-plugin.js @@ -0,0 +1,19 @@ +/** + * Node dependencies + */ +import { switchUserToAdmin } from './switch-user-to-admin'; +import { switchUserToTest } from './switch-user-to-test'; +import { visitAdminPage } from './visit-admin-page'; + +/** + * Deactivates an active plugin. + * + * @param {string} slug Plugin slug. + */ +export async function deactivatePlugin( slug ) { + await switchUserToAdmin(); + await visitAdminPage( 'plugins.php' ); + await page.click( `tr[data-slug="${ slug }"] .deactivate a` ); + await page.waitForSelector( `tr[data-slug="${ slug }"] .delete a` ); + await switchUserToTest(); +} diff --git a/test/e2e/support/utils/disable-pre-publish-checks.js b/test/e2e/support/utils/disable-pre-publish-checks.js index 26c892652eb88..34d23ff06e534 100644 --- a/test/e2e/support/utils/disable-pre-publish-checks.js +++ b/test/e2e/support/utils/disable-pre-publish-checks.js @@ -1,11 +1,11 @@ /** * Internal dependencies */ -import { toggleOption } from './toggle-option'; +import { toggleScreenOption } from './toggle-screen-option'; /** * Disables Pre-publish checks. */ export async function disablePrePublishChecks() { - await toggleOption( 'Enable Pre-publish Checks', false ); + await toggleScreenOption( 'Enable Pre-publish Checks', false ); } diff --git a/test/e2e/support/utils/enable-page-dialog-accept.js b/test/e2e/support/utils/enable-page-dialog-accept.js index c419de917ac98..f8f2976de67a7 100644 --- a/test/e2e/support/utils/enable-page-dialog-accept.js +++ b/test/e2e/support/utils/enable-page-dialog-accept.js @@ -1,8 +1,11 @@ /** - * Internal dependencies + * Callback which automatically accepts dialog. + * + * @param {puppeteer.Dialog} dialog Dialog object dispatched by page via the 'dialog' event. */ -import { acceptPageDialog } from './accept-page-dialog'; - +async function acceptPageDialog( dialog ) { + await dialog.accept(); +} /** * Enables even listener which accepts a page dialog which * may appear when navigating away from Gutenberg. diff --git a/test/e2e/support/utils/enable-pre-publish-checks.js b/test/e2e/support/utils/enable-pre-publish-checks.js index a99ad3422514c..794a0c685e4e9 100644 --- a/test/e2e/support/utils/enable-pre-publish-checks.js +++ b/test/e2e/support/utils/enable-pre-publish-checks.js @@ -1,11 +1,11 @@ /** * Internal dependencies */ -import { toggleOption } from './toggle-option'; +import { toggleScreenOption } from './toggle-screen-option'; /** * Enables Pre-publish checks. */ export async function enablePrePublishChecks() { - await toggleOption( 'Enable Pre-publish Checks', true ); + await toggleScreenOption( 'Enable Pre-publish Checks', true ); } diff --git a/test/e2e/support/utils/go-to-wp-path.js b/test/e2e/support/utils/go-to-wp-path.js deleted file mode 100644 index dcfe992fd83d6..0000000000000 --- a/test/e2e/support/utils/go-to-wp-path.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Internal dependencies - */ -import { getUrl } from './get-url'; - -/** - * Navigates to URL created from WPPath and query. - * - * @param {string} WPPath String to be serialized as pathname. - * @param {string} query String to be serialized as query portion of URL. - */ -export async function goToWPPath( WPPath, query ) { - await page.goto( getUrl( WPPath, query ) ); -} diff --git a/test/e2e/support/utils/index.js b/test/e2e/support/utils/index.js index 70d77e47b104a..5534c981f46a9 100644 --- a/test/e2e/support/utils/index.js +++ b/test/e2e/support/utils/index.js @@ -1,10 +1,13 @@ +export { activatePlugin } from './activate-plugin'; export { arePrePublishChecksEnabled } from './are-pre-publish-checks-enabled'; export { clearLocalStorage } from './clear-local-storage'; export { clickBlockAppender } from './click-block-appender'; export { clickButton } from './click-button'; export { clickOnCloseModalButton } from './click-on-close-modal-button'; export { clickOnMoreMenuItem } from './click-on-more-menu-item'; -export { convertBlock } from './convert-block'; +export { createNewPost } from './create-new-post'; +export { createURL } from './create-url'; +export { deactivatePlugin } from './deactivate-plugin'; export { disablePrePublishChecks } from './disable-pre-publish-checks'; export { enablePageDialogAccept } from './enable-page-dialog-accept'; export { enablePrePublishChecks } from './enable-pre-publish-checks'; @@ -13,30 +16,30 @@ export { findSidebarPanelWithTitle } from './find-sidebar-panel-with-title'; export { getAllBlocks } from './get-all-blocks'; export { getAvailableBlockTransforms } from './get-available-block-transforms'; export { getEditedPostContent } from './get-edited-post-content'; -export { getUrl } from './get-url'; export { hasBlockSwitcher } from './has-block-switcher'; export { insertBlock } from './insert-block'; -export { isEmbedding } from './is-embedding'; -export { JSONResponse } from './json-response'; -export { matchURL } from './match-url'; -export { mockOrTransform } from './mock-or-transform'; -export { newPost } from './new-post'; +export { installPlugin } from './install-plugin'; +export { isCurrentURL } from './is-current-url'; +export { loginUser } from './login-user'; export { observeFocusLoss } from './observe-focus-loss'; export { openDocumentSettingsSidebar } from './open-document-settings-sidebar'; export { openPublishPanel } from './open-publish-panel'; -export { pressTimes } from './press-times'; -export { pressWithModifier } from './press-with-modifier'; +export { pressKeyTimes } from './press-key-times'; +export { pressKeyWithModifier } from './press-key-with-modifier'; export { publishPost } from './publish-post'; -export { publishPostWithoutPrePublishChecks } from './publish-post-without-pre-publish-checks'; +export { publishPostWithPrePublishChecksDisabled } from './publish-post-with-pre-publish-checks-disabled'; export { saveDraft } from './save-draft'; export { searchForBlock } from './search-for-block'; export { selectBlockByClientId } from './select-block-by-client-id'; +export { setBrowserViewport } from './set-browser-viewport'; export { setPostContent } from './set-post-content'; -export { setViewport } from './set-viewport'; -export { setUpResponseMocking } from './set-up-response-mocking'; -export { switchToAdminUser } from './switch-to-admin-user'; -export { switchToEditor } from './switch-to-editor'; -export { switchToTestUser } from './switch-to-test-user'; -export { toggleOption } from './toggle-option'; -export { visitAdmin } from './visit-admin'; -export { waitForPageDimensions } from './wait-for-page-dimensions'; +export { switchEditorModeTo } from './switch-editor-mode-to'; +export { switchUserToAdmin } from './switch-user-to-admin'; +export { switchUserToTest } from './switch-user-to-test'; +export { toggleScreenOption } from './toggle-screen-option'; +export { transformBlockTo } from './transform-block-to'; +export { uninstallPlugin } from './uninstall-plugin'; +export { visitAdminPage } from './visit-admin-page'; +export { waitForWindowDimensions } from './wait-for-window-dimensions'; + +export * from './mocks'; diff --git a/test/e2e/support/utils/install-plugin.js b/test/e2e/support/utils/install-plugin.js new file mode 100644 index 0000000000000..d83885c619e8a --- /dev/null +++ b/test/e2e/support/utils/install-plugin.js @@ -0,0 +1,20 @@ +/** + * Node dependencies + */ +import { switchUserToAdmin } from './switch-user-to-admin'; +import { switchUserToTest } from './switch-user-to-test'; +import { visitAdminPage } from './visit-admin-page'; + +/** + * Installs a plugin from the WP.org repository. + * + * @param {string} slug Plugin slug. + * @param {string?} searchTerm If the plugin is not findable by its slug use an alternative term to search. + */ +export async function installPlugin( slug, searchTerm ) { + await switchUserToAdmin(); + await visitAdminPage( 'plugin-install.php', 's=' + encodeURIComponent( searchTerm || slug ) + '&tab=search&type=term' ); + await page.click( `.install-now[data-slug="${ slug }"]` ); + await page.waitForSelector( `.activate-now[data-slug="${ slug }"]` ); + await switchUserToTest(); +} diff --git a/test/e2e/support/utils/is-current-url.js b/test/e2e/support/utils/is-current-url.js new file mode 100644 index 0000000000000..01b78ffde0576 --- /dev/null +++ b/test/e2e/support/utils/is-current-url.js @@ -0,0 +1,24 @@ +/** + * Node dependencies + */ +import { URL } from 'url'; + +/** + * Internal dependencies + */ +import { createURL } from './create-url'; + +/** + * Checks if current URL is a WordPress path. + * + * @param {string} WPPath String to be serialized as pathname. + * @param {?string} query String to be serialized as query portion of URL. + * @return {boolean} Boolean represents whether current URL is or not a WordPress path. + */ +export function isCurrentURL( WPPath, query = '' ) { + const currentURL = new URL( page.url() ); + + currentURL.search = query; + + return createURL( WPPath ) === currentURL.href; +} diff --git a/test/e2e/support/utils/is-embedding.js b/test/e2e/support/utils/is-embedding.js deleted file mode 100644 index a3b9b15810249..0000000000000 --- a/test/e2e/support/utils/is-embedding.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Internal dependencies - */ -import { matchURL } from './match-url'; -import { parameterEquals } from './parameter-equals'; - -/** - * Creates a function to determine if a request is embedding a certain URL. - * - * @param {string} url The URL to check against a request. - * @return {function} Function that determines if a request is for the embed API, embedding a specific URL. - */ -export function isEmbedding( url ) { - return ( request ) => - matchURL( 'oembed%2F1.0%2Fproxy' )( request ) && - parameterEquals( 'url', url )( request ); -} diff --git a/test/e2e/support/utils/is-wp-path.js b/test/e2e/support/utils/is-wp-path.js deleted file mode 100644 index 3bfe446f052fa..0000000000000 --- a/test/e2e/support/utils/is-wp-path.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Node dependencies - */ -import { URL } from 'url'; - -/** - * Internal dependencies - */ -import { getUrl } from './get-url'; - -/** - * Checks if current url is a WordPress path. - * - * @param {string} WPPath String to be serialized as pathname. - * @param {?string} query String to be serialized as query portion of URL. - * @return {boolean} Boolean represents wheter current URL is or not a WordPress path. - */ -export function isWPPath( WPPath, query = '' ) { - const currentUrl = new URL( page.url() ); - - currentUrl.search = query; - - return getUrl( WPPath ) === currentUrl.href; -} diff --git a/test/e2e/support/utils/login-user.js b/test/e2e/support/utils/login-user.js new file mode 100644 index 0000000000000..8c7063ee940cb --- /dev/null +++ b/test/e2e/support/utils/login-user.js @@ -0,0 +1,31 @@ + +/** + * Internal dependencies + */ +import { WP_USERNAME, WP_PASSWORD } from './shared/config'; +import { createURL } from './create-url'; +import { isCurrentURL } from './is-current-url'; +import { pressKeyWithModifier } from './press-key-with-modifier'; + +/** + * Performs log in with specified username and password. + * + * @param {?string} username String to be used as user credential. + * @param {?string} password String to be used as user credential. + */ +export async function loginUser( username = WP_USERNAME, password = WP_PASSWORD ) { + if ( ! isCurrentURL( 'wp-login.php' ) ) { + await page.goto( + createURL( 'wp-login.php' ) + ); + } + + await page.focus( '#user_login' ); + await pressKeyWithModifier( 'primary', 'a' ); + await page.type( '#user_login', username ); + await page.focus( '#user_pass' ); + await pressKeyWithModifier( 'primary', 'a' ); + await page.type( '#user_pass', password ); + + await Promise.all( [ page.waitForNavigation(), page.click( '#wp-submit' ) ] ); +} diff --git a/test/e2e/support/utils/login.js b/test/e2e/support/utils/login.js deleted file mode 100644 index 87ae8af6a493f..0000000000000 --- a/test/e2e/support/utils/login.js +++ /dev/null @@ -1,23 +0,0 @@ - -/** - * Internal dependencies - */ -import { WP_USERNAME, WP_PASSWORD } from './config'; -import { pressWithModifier } from './press-with-modifier'; - -/** - * Performs log in with specified username and password. - * - * @param {?string} username String to be used as user credential. - * @param {?string} password String to be used as user credential. - */ -export async function login( username = WP_USERNAME, password = WP_PASSWORD ) { - await page.focus( '#user_login' ); - await pressWithModifier( 'primary', 'a' ); - await page.type( '#user_login', username ); - await page.focus( '#user_pass' ); - await pressWithModifier( 'primary', 'a' ); - await page.type( '#user_pass', password ); - - await Promise.all( [ page.waitForNavigation(), page.click( '#wp-submit' ) ] ); -} diff --git a/test/e2e/support/utils/mocks/create-embedding-matcher.js b/test/e2e/support/utils/mocks/create-embedding-matcher.js new file mode 100644 index 0000000000000..66e1b301cbe0f --- /dev/null +++ b/test/e2e/support/utils/mocks/create-embedding-matcher.js @@ -0,0 +1,34 @@ +/** + * Internal dependencies + */ +import { createURLMatcher } from './create-url-matcher'; + +/** + * Creates a function to determine if a request has a parameter with a certain value. + * + * @param {string} parameterName The query parameter to check. + * @param {string} value The value to check for. + * @return {function} Function that determines if a request's query parameter is the specified value. + */ +function parameterEquals( parameterName, value ) { + return ( request ) => { + const url = request.url(); + const match = new RegExp( `.*${ parameterName }=([^&]+).*` ).exec( url ); + if ( ! match ) { + return false; + } + return value === decodeURIComponent( match[ 1 ] ); + }; +} + +/** + * Creates a function to determine if a request is embedding a certain URL. + * + * @param {string} url The URL to check against a request. + * @return {function} Function that determines if a request is for the embed API, embedding a specific URL. + */ +export function createEmbeddingMatcher( url ) { + return ( request ) => + createURLMatcher( 'oembed%2F1.0%2Fproxy' )( request ) && + parameterEquals( 'url', url )( request ); +} diff --git a/test/e2e/support/utils/json-response.js b/test/e2e/support/utils/mocks/create-json-response.js similarity index 74% rename from test/e2e/support/utils/json-response.js rename to test/e2e/support/utils/mocks/create-json-response.js index 0e3dc5e256ba6..f7bddf0befe64 100644 --- a/test/e2e/support/utils/json-response.js +++ b/test/e2e/support/utils/mocks/create-json-response.js @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { getJSONResponse } from './get-json-response'; +import { getJSONResponse } from '../shared/get-json-response'; /** * Respond to a request with a JSON response. @@ -9,6 +9,6 @@ import { getJSONResponse } from './get-json-response'; * @param {string} mockResponse The mock object to wrap in a JSON response. * @return {Promise} Promise that responds to a request with the mock JSON response. */ -export function JSONResponse( mockResponse ) { +export function createJSONResponse( mockResponse ) { return async ( request ) => request.respond( getJSONResponse( mockResponse ) ); } diff --git a/test/e2e/support/utils/match-url.js b/test/e2e/support/utils/mocks/create-url-matcher.js similarity index 86% rename from test/e2e/support/utils/match-url.js rename to test/e2e/support/utils/mocks/create-url-matcher.js index c7f2bda330b8e..db299a7864133 100644 --- a/test/e2e/support/utils/match-url.js +++ b/test/e2e/support/utils/mocks/create-url-matcher.js @@ -4,6 +4,6 @@ * @param {string} substring The substring to check for. * @return {function} Function that determines if a request's URL contains substring. */ -export function matchURL( substring ) { +export function createURLMatcher( substring ) { return ( request ) => -1 !== request.url().indexOf( substring ); } diff --git a/test/e2e/support/utils/mocks/index.js b/test/e2e/support/utils/mocks/index.js new file mode 100644 index 0000000000000..4212e2badb7c1 --- /dev/null +++ b/test/e2e/support/utils/mocks/index.js @@ -0,0 +1,5 @@ +export { createURLMatcher } from './create-url-matcher'; +export { createJSONResponse } from './create-json-response'; +export { createEmbeddingMatcher } from './create-embedding-matcher'; +export { mockOrTransform } from './mock-or-transform'; +export { setUpResponseMocking } from './set-up-response-mocking'; diff --git a/test/e2e/support/utils/mock-or-transform.js b/test/e2e/support/utils/mocks/mock-or-transform.js similarity index 95% rename from test/e2e/support/utils/mock-or-transform.js rename to test/e2e/support/utils/mocks/mock-or-transform.js index 4a97a4d41e9bc..0f858aabe56ca 100644 --- a/test/e2e/support/utils/mock-or-transform.js +++ b/test/e2e/support/utils/mocks/mock-or-transform.js @@ -6,7 +6,7 @@ import fetch from 'node-fetch'; /** * Internal dependencies */ -import { getJSONResponse } from './get-json-response'; +import { getJSONResponse } from '../shared/get-json-response'; /** * Mocks a request with the supplied mock object, or allows it to run with an optional transform, based on the diff --git a/test/e2e/support/utils/set-up-response-mocking.js b/test/e2e/support/utils/mocks/set-up-response-mocking.js similarity index 100% rename from test/e2e/support/utils/set-up-response-mocking.js rename to test/e2e/support/utils/mocks/set-up-response-mocking.js diff --git a/test/e2e/support/utils/parameter-equals.js b/test/e2e/support/utils/parameter-equals.js deleted file mode 100644 index 85c46566afc30..0000000000000 --- a/test/e2e/support/utils/parameter-equals.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Creates a function to determine if a request has a parameter with a certain value. - * - * @param {string} parameterName The query parameter to check. - * @param {string} value The value to check for. - * @return {function} Function that determines if a request's query parameter is the specified value. - */ -export function parameterEquals( parameterName, value ) { - return ( request ) => { - const url = request.url(); - const match = new RegExp( `.*${ parameterName }=([^&]+).*` ).exec( url ); - if ( ! match ) { - return false; - } - return value === decodeURIComponent( match[ 1 ] ); - }; -} diff --git a/test/e2e/support/utils/press-key-times.js b/test/e2e/support/utils/press-key-times.js new file mode 100644 index 0000000000000..f78b66d09dede --- /dev/null +++ b/test/e2e/support/utils/press-key-times.js @@ -0,0 +1,31 @@ +/** + * External dependencies + */ +import { times } from 'lodash'; + +/** + * Given an array of functions, each returning a promise, performs all + * promises in sequence (waterfall) order. + * + * @param {Function[]} sequence Array of promise creators. + * + * @return {Promise} Promise resolving once all in the sequence complete. + */ +async function promiseSequence( sequence ) { + return sequence.reduce( + ( current, next ) => current.then( next ), + Promise.resolve() + ); +} + +/** + * Presses the given keyboard key a number of times in sequence. + * + * @param {string} key Key to press. + * @param {number} count Number of times to press. + * + * @return {Promise} Promise resolving when key presses complete. + */ +export async function pressKeyTimes( key, count ) { + return promiseSequence( times( count, () => () => page.keyboard.press( key ) ) ); +} diff --git a/test/e2e/support/utils/press-with-modifier.js b/test/e2e/support/utils/press-key-with-modifier.js similarity index 94% rename from test/e2e/support/utils/press-with-modifier.js rename to test/e2e/support/utils/press-key-with-modifier.js index ec274dfd39766..e20100ba54e5f 100644 --- a/test/e2e/support/utils/press-with-modifier.js +++ b/test/e2e/support/utils/press-key-with-modifier.js @@ -15,7 +15,7 @@ import { modifiers, SHIFT, ALT, CTRL } from '@wordpress/keycodes'; * @param {string} modifier Modifier key. * @param {string} key Key to press while modifier held. */ -export async function pressWithModifier( modifier, key ) { +export async function pressKeyWithModifier( modifier, key ) { const isAppleOS = () => process.platform === 'darwin'; const overWrittenModifiers = { ...modifiers, diff --git a/test/e2e/support/utils/press-times.js b/test/e2e/support/utils/press-times.js deleted file mode 100644 index 2e406fd31a024..0000000000000 --- a/test/e2e/support/utils/press-times.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * External dependencies - */ -import { times } from 'lodash'; - -/** - * Internal dependencies - */ -import { promiseSequence } from './promise-sequence'; - -/** - * Presses the given keyboard key a number of times in sequence. - * - * @param {string} key Key to press. - * @param {number} count Number of times to press. - * - * @return {Promise} Promise resolving when key presses complete. - */ -export async function pressTimes( key, count ) { - return promiseSequence( times( count, () => () => page.keyboard.press( key ) ) ); -} diff --git a/test/e2e/support/utils/promise-sequence.js b/test/e2e/support/utils/promise-sequence.js deleted file mode 100644 index b8e77ba9eb19f..0000000000000 --- a/test/e2e/support/utils/promise-sequence.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Given an array of functions, each returning a promise, performs all - * promises in sequence (waterfall) order. - * - * @param {Function[]} sequence Array of promise creators. - * - * @return {Promise} Promise resolving once all in the sequence complete. - */ -export async function promiseSequence( sequence ) { - return sequence.reduce( - ( current, next ) => current.then( next ), - Promise.resolve() - ); -} diff --git a/test/e2e/support/utils/publish-post-without-pre-publish-checks.js b/test/e2e/support/utils/publish-post-with-pre-publish-checks-disabled.js similarity index 82% rename from test/e2e/support/utils/publish-post-without-pre-publish-checks.js rename to test/e2e/support/utils/publish-post-with-pre-publish-checks-disabled.js index b2b550ad0a840..ab7914e4eb508 100644 --- a/test/e2e/support/utils/publish-post-without-pre-publish-checks.js +++ b/test/e2e/support/utils/publish-post-with-pre-publish-checks-disabled.js @@ -4,7 +4,7 @@ * * @return {Promise} Promise resolving when publish is complete. */ -export async function publishPostWithoutPrePublishChecks() { +export async function publishPostWithPrePublishChecksDisabled() { await page.click( '.editor-post-publish-button' ); return page.waitForSelector( '.components-notice.is-success' ); } diff --git a/test/e2e/support/utils/set-viewport.js b/test/e2e/support/utils/set-browser-viewport.js similarity index 65% rename from test/e2e/support/utils/set-viewport.js rename to test/e2e/support/utils/set-browser-viewport.js index 6ef289013763d..f6421215e00aa 100644 --- a/test/e2e/support/utils/set-viewport.js +++ b/test/e2e/support/utils/set-browser-viewport.js @@ -1,19 +1,19 @@ /** * Internal dependencies */ -import { waitForPageDimensions } from './wait-for-page-dimensions'; +import { waitForWindowDimensions } from './wait-for-window-dimensions'; /** * Sets browser viewport to specified type. * * @param {string} type String to represent dimensions type; can be either small or large. */ -export async function setViewport( type ) { +export async function setBrowserViewport( type ) { const allowedDimensions = { large: { width: 960, height: 700 }, small: { width: 600, height: 700 }, }; const currentDimension = allowedDimensions[ type ]; await page.setViewport( currentDimension ); - await waitForPageDimensions( currentDimension.width, currentDimension.height ); + await waitForWindowDimensions( currentDimension.width, currentDimension.height ); } diff --git a/test/e2e/support/utils/config.js b/test/e2e/support/utils/shared/config.js similarity index 100% rename from test/e2e/support/utils/config.js rename to test/e2e/support/utils/shared/config.js diff --git a/test/e2e/support/utils/get-json-response.js b/test/e2e/support/utils/shared/get-json-response.js similarity index 100% rename from test/e2e/support/utils/get-json-response.js rename to test/e2e/support/utils/shared/get-json-response.js diff --git a/test/e2e/support/utils/switch-to-editor.js b/test/e2e/support/utils/switch-editor-mode-to.js similarity index 85% rename from test/e2e/support/utils/switch-to-editor.js rename to test/e2e/support/utils/switch-editor-mode-to.js index 75529b8721b53..64d08364eb430 100644 --- a/test/e2e/support/utils/switch-to-editor.js +++ b/test/e2e/support/utils/switch-editor-mode-to.js @@ -2,7 +2,7 @@ * Switches editor mode. * @param {string} mode String editor mode. */ -export async function switchToEditor( mode ) { +export async function switchEditorModeTo( mode ) { await page.click( '.edit-post-more-menu [aria-label="Show more tools & options"]' ); diff --git a/test/e2e/support/utils/switch-to-admin-user.js b/test/e2e/support/utils/switch-to-admin-user.js deleted file mode 100644 index a98401ee76297..0000000000000 --- a/test/e2e/support/utils/switch-to-admin-user.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Internal dependencies - */ -import { goToWPPath } from './go-to-wp-path'; -import { login } from './login'; -import { WP_USERNAME, WP_ADMIN_USER } from './config'; - -/** - * Switches the current user to the admin user (if the user - * running the test is not already the admin user). - */ -export async function switchToAdminUser() { - if ( WP_USERNAME === WP_ADMIN_USER.username ) { - return; - } - await goToWPPath( 'wp-login.php' ); - await login( WP_ADMIN_USER.username, WP_ADMIN_USER.password ); -} diff --git a/test/e2e/support/utils/switch-to-test-user.js b/test/e2e/support/utils/switch-to-test-user.js deleted file mode 100644 index 3eccb9db03924..0000000000000 --- a/test/e2e/support/utils/switch-to-test-user.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Internal dependencies - */ -import { goToWPPath } from './go-to-wp-path'; -import { login } from './login'; -import { WP_USERNAME, WP_ADMIN_USER } from './config'; - -/** - * Switches the current user to whichever user we should be - * running the tests as (if we're not already that user). - */ -export async function switchToTestUser() { - if ( WP_USERNAME === WP_ADMIN_USER.username ) { - return; - } - await goToWPPath( 'wp-login.php' ); - await login(); -} diff --git a/test/e2e/support/utils/switch-user-to-admin.js b/test/e2e/support/utils/switch-user-to-admin.js new file mode 100644 index 0000000000000..f36cab835297b --- /dev/null +++ b/test/e2e/support/utils/switch-user-to-admin.js @@ -0,0 +1,16 @@ +/** + * Internal dependencies + */ +import { loginUser } from './login-user'; +import { WP_USERNAME, WP_ADMIN_USER } from './shared/config'; + +/** + * Switches the current user to the admin user (if the user + * running the test is not already the admin user). + */ +export async function switchUserToAdmin() { + if ( WP_USERNAME === WP_ADMIN_USER.username ) { + return; + } + await loginUser( WP_ADMIN_USER.username, WP_ADMIN_USER.password ); +} diff --git a/test/e2e/support/utils/switch-user-to-test.js b/test/e2e/support/utils/switch-user-to-test.js new file mode 100644 index 0000000000000..c726de258fb57 --- /dev/null +++ b/test/e2e/support/utils/switch-user-to-test.js @@ -0,0 +1,16 @@ +/** + * Internal dependencies + */ +import { loginUser } from './login-user'; +import { WP_USERNAME, WP_ADMIN_USER } from './shared/config'; + +/** + * Switches the current user to whichever user we should be + * running the tests as (if we're not already that user). + */ +export async function switchUserToTest() { + if ( WP_USERNAME === WP_ADMIN_USER.username ) { + return; + } + await loginUser(); +} diff --git a/test/e2e/support/utils/toggle-option.js b/test/e2e/support/utils/toggle-screen-option.js similarity index 90% rename from test/e2e/support/utils/toggle-option.js rename to test/e2e/support/utils/toggle-screen-option.js index 8da5a8122d27f..df256f1664a66 100644 --- a/test/e2e/support/utils/toggle-option.js +++ b/test/e2e/support/utils/toggle-screen-option.js @@ -10,7 +10,7 @@ import { clickOnMoreMenuItem } from './click-on-more-menu-item'; * @param {?boolean} shouldBeChecked If true, turns the option on. If false, off. If * undefined, the option will be toggled. */ -export async function toggleOption( label, shouldBeChecked = undefined ) { +export async function toggleScreenOption( label, shouldBeChecked = undefined ) { await clickOnMoreMenuItem( 'Options' ); const [ handle ] = await page.$x( `//label[contains(text(), "${ label }")]` ); diff --git a/test/e2e/support/utils/convert-block.js b/test/e2e/support/utils/transform-block-to.js similarity index 86% rename from test/e2e/support/utils/convert-block.js rename to test/e2e/support/utils/transform-block-to.js index 798ec7ab88d0a..cbf43b526479c 100644 --- a/test/e2e/support/utils/convert-block.js +++ b/test/e2e/support/utils/transform-block-to.js @@ -3,7 +3,7 @@ * * @param {string} name Block name. */ -export async function convertBlock( name ) { +export async function transformBlockTo( name ) { await page.mouse.move( 200, 300, { steps: 10 } ); await page.mouse.move( 250, 350, { steps: 10 } ); await page.click( '.editor-block-switcher__toggle' ); diff --git a/test/e2e/support/utils/uninstall-plugin.js b/test/e2e/support/utils/uninstall-plugin.js new file mode 100644 index 0000000000000..aca1b35652c01 --- /dev/null +++ b/test/e2e/support/utils/uninstall-plugin.js @@ -0,0 +1,25 @@ +/** + * Node dependencies + */ +import { switchUserToAdmin } from './switch-user-to-admin'; +import { switchUserToTest } from './switch-user-to-test'; +import { visitAdminPage } from './visit-admin-page'; + +/** + * Uninstalls a plugin. + * + * @param {string} slug Plugin slug. + */ +export async function uninstallPlugin( slug ) { + await switchUserToAdmin(); + await visitAdminPage( 'plugins.php' ); + const confirmPromise = new Promise( ( resolve ) => { + page.once( 'dialog', () => resolve() ); + } ); + await Promise.all( [ + confirmPromise, + page.click( `tr[data-slug="${ slug }"] .delete a` ), + ] ); + await page.waitForSelector( `tr[data-slug="${ slug }"].deleted` ); + await switchUserToTest(); +} diff --git a/test/e2e/support/utils/visit-admin-page.js b/test/e2e/support/utils/visit-admin-page.js new file mode 100644 index 0000000000000..4878f69d277f0 --- /dev/null +++ b/test/e2e/support/utils/visit-admin-page.js @@ -0,0 +1,28 @@ +/** + * Node dependencies + */ +import { join } from 'path'; + +/** + * Internal dependencies + */ +import { createURL } from './create-url'; +import { isCurrentURL } from './is-current-url'; +import { loginUser } from './login-user'; + +/** + * Visits admin page; if user is not logged in then it logging in it first, then visits admin page. + * + * @param {string} adminPath String to be serialized as pathname. + * @param {string} query String to be serialized as query portion of URL. + */ +export async function visitAdminPage( adminPath, query ) { + await page.goto( + createURL( join( 'wp-admin', adminPath ), query ) + ); + + if ( isCurrentURL( 'wp-login.php' ) ) { + await loginUser(); + await visitAdminPage( adminPath, query ); + } +} diff --git a/test/e2e/support/utils/visit-admin.js b/test/e2e/support/utils/visit-admin.js deleted file mode 100644 index 2c7a473998e1f..0000000000000 --- a/test/e2e/support/utils/visit-admin.js +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Node dependencies - */ -import { join } from 'path'; - -/** - * Internal dependencies - */ -import { goToWPPath } from './go-to-wp-path'; -import { isWPPath } from './is-wp-path'; -import { login } from './login'; - -/** - * Visits admin page; if user is not logged in then it logging in it first, then visits admin page. - * @param {string} adminPath String to be serialized as pathname. - * @param {string} query String to be serialized as query portion of URL. - */ -export async function visitAdmin( adminPath, query ) { - await goToWPPath( join( 'wp-admin', adminPath ), query ); - - if ( isWPPath( 'wp-login.php' ) ) { - await login(); - await visitAdmin( adminPath, query ); - } -} diff --git a/test/e2e/support/utils/wait-for-page-dimensions.js b/test/e2e/support/utils/wait-for-window-dimensions.js similarity index 88% rename from test/e2e/support/utils/wait-for-page-dimensions.js rename to test/e2e/support/utils/wait-for-window-dimensions.js index 023fc43522d66..9b505cf3b1280 100644 --- a/test/e2e/support/utils/wait-for-page-dimensions.js +++ b/test/e2e/support/utils/wait-for-window-dimensions.js @@ -7,7 +7,7 @@ * @param {number} width Width of the window. * @param {height} height Height of the window. */ -export async function waitForPageDimensions( width, height ) { +export async function waitForWindowDimensions( width, height ) { await page .mainFrame() .waitForFunction( From afb6afec53c4358d3aa83ca42c85a0494b5dd267 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Mon, 7 Jan 2019 11:53:33 +0100 Subject: [PATCH 108/691] Tests: Move e2e tests to the npm package (#12465) --- .eslintignore | 2 +- .eslintrc.js | 2 +- .travis.yml | 8 ++-- docker-compose.yml | 8 ++-- docs/manifest.json | 6 +++ jsconfig.json | 2 +- package-lock.json | 12 ++++++ package.json | 4 +- packages/tests-e2e/.npmrc | 1 + packages/tests-e2e/README.md | 13 +++++++ .../assets/10x10_e2e_test_image_z9T8jK.png | Bin .../assets/greeting-reusable-block.json | 0 .../mu-plugins}/disable-login-autofocus.php | 0 packages/tests-e2e/package.json | 36 ++++++++++++++++++ .../tests-e2e/plugins}/align-hook.php | 0 .../tests-e2e/plugins}/align-hook/index.js | 0 .../tests-e2e/plugins}/block-icons.php | 0 .../tests-e2e/plugins}/block-icons/index.js | 0 .../plugins}/container-without-paragraph.php | 0 .../container-without-paragraph/index.js | 0 .../tests-e2e/plugins}/custom-post-types.php | 0 .../plugins}/default-post-content.php | 0 .../plugins}/deprecated-node-matcher.php | 0 .../plugins}/deprecated-node-matcher/index.js | 0 .../tests-e2e/plugins}/format-api.php | 0 .../tests-e2e/plugins}/format-api/index.js | 0 .../tests-e2e/plugins}/hooks-api.php | 0 .../tests-e2e/plugins}/hooks-api/index.js | 0 .../plugins}/inner-blocks-templates.php | 0 .../plugins}/inner-blocks-templates/index.js | 0 .../plugins}/meta-attribute-block.php | 0 .../plugins}/meta-attribute-block/index.js | 0 .../tests-e2e/plugins}/meta-box.php | 0 .../tests-e2e/plugins}/plugins-api.php | 0 .../plugins-api/annotations-sidebar.js | 0 .../plugins}/plugins-api/post-status-info.js | 0 .../plugins}/plugins-api/publish-panel.js | 0 .../tests-e2e/plugins}/plugins-api/sidebar.js | 0 .../tests-e2e/plugins}/post-formats.php | 0 .../tests-e2e/plugins}/templates.php | 0 .../tests-e2e/plugins}/wp-editor-metabox.php | 0 .../__snapshots__/adding-blocks.test.js.snap | 0 .../__snapshots__/align-hook.test.js.snap | 0 .../__snapshots__/block-deletion.test.js.snap | 0 .../block-hierarchy-navigation.test.js.snap | 0 .../compatibility-classic-editor.test.js.snap | 0 .../container-blocks.test.js.snap | 0 .../convert-block-type.test.js.snap | 0 .../deprecated-node-matcher.test.js.snap | 0 .../__snapshots__/embedding.test.js.snap | 0 .../font-size-picker.test.js.snap | 0 .../__snapshots__/format-api.test.js.snap | 0 .../specs/__snapshots__/links.test.js.snap | 0 .../specs/__snapshots__/mentions.test.js.snap | 0 .../meta-attribute-block.test.js.snap | 0 .../__snapshots__/plugins-api.test.js.snap | 0 .../reusable-blocks.test.js.snap | 0 .../__snapshots__/rich-text.test.js.snap | 0 .../splitting-merging.test.js.snap | 0 .../style-variation.test.js.snap | 0 .../__snapshots__/templates.test.js.snap | 0 .../specs/__snapshots__/undo.test.js.snap | 0 .../wp-editor-meta-box.test.js.snap | 0 .../__snapshots__/writing-flow.test.js.snap | 0 .../tests-e2e}/specs/a11y.test.js | 0 .../tests-e2e}/specs/adding-blocks.test.js | 0 .../specs/adding-inline-tokens.test.js | 0 .../tests-e2e}/specs/align-hook.test.js | 0 .../tests-e2e}/specs/annotations.test.js | 0 .../tests-e2e}/specs/block-deletion.test.js | 0 .../specs/block-hierarchy-navigation.test.js | 0 .../tests-e2e}/specs/block-icons.test.js | 0 .../tests-e2e}/specs/block-mover.test.js | 0 .../tests-e2e}/specs/block-switcher.test.js | 0 .../blocks/__snapshots__/classic.test.js.snap | 0 .../blocks/__snapshots__/code.test.js.snap | 0 .../blocks/__snapshots__/heading.test.js.snap | 0 .../blocks/__snapshots__/html.test.js.snap | 0 .../blocks/__snapshots__/list.test.js.snap | 0 .../blocks/__snapshots__/quote.test.js.snap | 0 .../__snapshots__/separator.test.js.snap | 0 .../tests-e2e}/specs/blocks/classic.test.js | 0 .../tests-e2e}/specs/blocks/code.test.js | 0 .../tests-e2e}/specs/blocks/heading.test.js | 0 .../tests-e2e}/specs/blocks/html.test.js | 0 .../tests-e2e}/specs/blocks/list.test.js | 0 .../tests-e2e}/specs/blocks/quote.test.js | 0 .../tests-e2e}/specs/blocks/separator.test.js | 0 .../tests-e2e}/specs/change-detection.test.js | 0 .../compatibility-classic-editor.test.js | 0 .../tests-e2e}/specs/container-blocks.test.js | 0 .../specs/convert-block-type.test.js | 0 .../tests-e2e}/specs/datepicker.test.js | 0 .../tests-e2e}/specs/demo.test.js | 0 .../specs/deprecated-node-matcher.test.js | 0 .../tests-e2e}/specs/editor-modes.test.js | 0 .../tests-e2e}/specs/embedding.test.js | 0 .../tests-e2e}/specs/font-size-picker.test.js | 0 .../tests-e2e}/specs/format-api.test.js | 0 .../tests-e2e}/specs/fullscreen-mode.test.js | 0 .../tests-e2e}/specs/hooks-api.test.js | 0 .../tests-e2e}/specs/invalid-block.test.js | 0 .../tests-e2e}/specs/links.test.js | 0 .../specs/manage-reusable-blocks.test.js | 0 .../tests-e2e}/specs/mentions.test.js | 0 .../specs/meta-attribute-block.test.js | 0 .../tests-e2e}/specs/meta-boxes.test.js | 0 .../specs/multi-block-selection.test.js | 0 .../specs/navigable-toolbar.test.js | 0 .../specs/new-post-default-content.test.js | 0 .../tests-e2e}/specs/new-post.test.js | 0 .../tests-e2e}/specs/nux.test.js | 0 .../tests-e2e}/specs/plugins-api.test.js | 0 .../tests-e2e}/specs/popovers.test.js | 0 .../tests-e2e}/specs/post-visibility.test.js | 0 .../tests-e2e}/specs/preferences.test.js | 0 .../tests-e2e}/specs/preview.test.js | 0 .../tests-e2e}/specs/publish-button.test.js | 0 .../tests-e2e}/specs/publish-panel.test.js | 0 .../tests-e2e}/specs/publishing.test.js | 0 .../tests-e2e}/specs/reusable-blocks.test.js | 0 .../tests-e2e}/specs/rich-text.test.js | 0 .../tests-e2e}/specs/shortcut-help.test.js | 0 .../specs/sidebar-permalink-panel.test.js | 0 .../tests-e2e}/specs/sidebar.test.js | 0 .../specs/splitting-merging.test.js | 0 .../tests-e2e}/specs/style-variation.test.js | 0 .../tests-e2e}/specs/taxonomies.test.js | 0 .../tests-e2e}/specs/templates.test.js | 0 .../tests-e2e}/specs/undo.test.js | 0 .../specs/wp-editor-meta-box.test.js | 0 .../tests-e2e}/specs/writing-flow.test.js | 0 .../support/setup-test-framework.js | 0 .../support/utils/activate-plugin.js | 2 +- .../utils/are-pre-publish-checks-enabled.js | 0 .../support/utils/clear-local-storage.js | 0 .../support/utils/click-block-appender.js | 0 .../tests-e2e}/support/utils/click-button.js | 0 .../utils/click-on-close-modal-button.js | 0 .../support/utils/click-on-more-menu-item.js | 0 .../support/utils/create-new-post.js | 0 .../tests-e2e}/support/utils/create-url.js | 0 .../support/utils/deactivate-plugin.js | 2 +- .../utils/disable-pre-publish-checks.js | 0 .../utils/enable-page-dialog-accept.js | 0 .../utils/enable-pre-publish-checks.js | 0 .../support/utils/ensure-sidebar-opened.js | 0 .../utils/find-sidebar-panel-with-title.js | 0 .../support/utils/get-all-blocks.js | 0 .../utils/get-available-block-transforms.js | 0 .../support/utils/get-edited-post-content.js | 0 .../support/utils/has-block-switcher.js | 0 .../tests-e2e}/support/utils/index.js | 0 .../tests-e2e}/support/utils/insert-block.js | 0 .../support/utils/install-plugin.js | 2 +- .../support/utils/is-current-url.js | 0 .../tests-e2e}/support/utils/login-user.js | 0 .../utils/mocks/create-embedding-matcher.js | 0 .../utils/mocks/create-json-response.js | 0 .../support/utils/mocks/create-url-matcher.js | 0 .../tests-e2e}/support/utils/mocks/index.js | 0 .../support/utils/mocks/mock-or-transform.js | 0 .../utils/mocks/set-up-response-mocking.js | 0 .../support/utils/observe-focus-loss.js | 0 .../utils/open-document-settings-sidebar.js | 0 .../support/utils/open-publish-panel.js | 0 .../support/utils/press-key-times.js | 0 .../support/utils/press-key-with-modifier.js | 0 ...h-post-with-pre-publish-checks-disabled.js | 0 .../tests-e2e}/support/utils/publish-post.js | 0 .../tests-e2e}/support/utils/save-draft.js | 0 .../support/utils/search-for-block.js | 0 .../utils/select-block-by-client-id.js | 0 .../support/utils/set-browser-viewport.js | 0 .../support/utils/set-post-content.js | 0 .../tests-e2e}/support/utils/shared/config.js | 0 .../support/utils/shared/get-json-response.js | 0 .../support/utils/switch-editor-mode-to.js | 3 +- .../support/utils/switch-user-to-admin.js | 0 .../support/utils/switch-user-to-test.js | 0 .../support/utils/toggle-screen-option.js | 0 .../support/utils/transform-block-to.js | 0 .../support/utils/uninstall-plugin.js | 2 +- .../support/utils/visit-admin-page.js | 0 .../utils/wait-for-window-dimensions.js | 0 test/unit/jest.config.json | 2 +- 186 files changed, 88 insertions(+), 19 deletions(-) create mode 100644 packages/tests-e2e/.npmrc create mode 100644 packages/tests-e2e/README.md rename {test/e2e => packages/tests-e2e}/assets/10x10_e2e_test_image_z9T8jK.png (100%) rename {test/e2e => packages/tests-e2e}/assets/greeting-reusable-block.json (100%) rename {test/e2e/test-mu-plugins => packages/tests-e2e/mu-plugins}/disable-login-autofocus.php (100%) create mode 100644 packages/tests-e2e/package.json rename {test/e2e/test-plugins => packages/tests-e2e/plugins}/align-hook.php (100%) rename {test/e2e/test-plugins => packages/tests-e2e/plugins}/align-hook/index.js (100%) rename {test/e2e/test-plugins => packages/tests-e2e/plugins}/block-icons.php (100%) rename {test/e2e/test-plugins => packages/tests-e2e/plugins}/block-icons/index.js (100%) rename {test/e2e/test-plugins => packages/tests-e2e/plugins}/container-without-paragraph.php (100%) rename {test/e2e/test-plugins => packages/tests-e2e/plugins}/container-without-paragraph/index.js (100%) rename {test/e2e/test-plugins => packages/tests-e2e/plugins}/custom-post-types.php (100%) rename {test/e2e/test-plugins => packages/tests-e2e/plugins}/default-post-content.php (100%) rename {test/e2e/test-plugins => packages/tests-e2e/plugins}/deprecated-node-matcher.php (100%) rename {test/e2e/test-plugins => packages/tests-e2e/plugins}/deprecated-node-matcher/index.js (100%) rename {test/e2e/test-plugins => packages/tests-e2e/plugins}/format-api.php (100%) rename {test/e2e/test-plugins => packages/tests-e2e/plugins}/format-api/index.js (100%) rename {test/e2e/test-plugins => packages/tests-e2e/plugins}/hooks-api.php (100%) rename {test/e2e/test-plugins => packages/tests-e2e/plugins}/hooks-api/index.js (100%) rename {test/e2e/test-plugins => packages/tests-e2e/plugins}/inner-blocks-templates.php (100%) rename {test/e2e/test-plugins => packages/tests-e2e/plugins}/inner-blocks-templates/index.js (100%) rename {test/e2e/test-plugins => packages/tests-e2e/plugins}/meta-attribute-block.php (100%) rename {test/e2e/test-plugins => packages/tests-e2e/plugins}/meta-attribute-block/index.js (100%) rename {test/e2e/test-plugins => packages/tests-e2e/plugins}/meta-box.php (100%) rename {test/e2e/test-plugins => packages/tests-e2e/plugins}/plugins-api.php (100%) rename {test/e2e/test-plugins => packages/tests-e2e/plugins}/plugins-api/annotations-sidebar.js (100%) rename {test/e2e/test-plugins => packages/tests-e2e/plugins}/plugins-api/post-status-info.js (100%) rename {test/e2e/test-plugins => packages/tests-e2e/plugins}/plugins-api/publish-panel.js (100%) rename {test/e2e/test-plugins => packages/tests-e2e/plugins}/plugins-api/sidebar.js (100%) rename {test/e2e/test-plugins => packages/tests-e2e/plugins}/post-formats.php (100%) rename {test/e2e/test-plugins => packages/tests-e2e/plugins}/templates.php (100%) rename {test/e2e/test-plugins => packages/tests-e2e/plugins}/wp-editor-metabox.php (100%) rename {test/e2e => packages/tests-e2e}/specs/__snapshots__/adding-blocks.test.js.snap (100%) rename {test/e2e => packages/tests-e2e}/specs/__snapshots__/align-hook.test.js.snap (100%) rename {test/e2e => packages/tests-e2e}/specs/__snapshots__/block-deletion.test.js.snap (100%) rename {test/e2e => packages/tests-e2e}/specs/__snapshots__/block-hierarchy-navigation.test.js.snap (100%) rename {test/e2e => packages/tests-e2e}/specs/__snapshots__/compatibility-classic-editor.test.js.snap (100%) rename {test/e2e => packages/tests-e2e}/specs/__snapshots__/container-blocks.test.js.snap (100%) rename {test/e2e => packages/tests-e2e}/specs/__snapshots__/convert-block-type.test.js.snap (100%) rename {test/e2e => packages/tests-e2e}/specs/__snapshots__/deprecated-node-matcher.test.js.snap (100%) rename {test/e2e => packages/tests-e2e}/specs/__snapshots__/embedding.test.js.snap (100%) rename {test/e2e => packages/tests-e2e}/specs/__snapshots__/font-size-picker.test.js.snap (100%) rename {test/e2e => packages/tests-e2e}/specs/__snapshots__/format-api.test.js.snap (100%) rename {test/e2e => packages/tests-e2e}/specs/__snapshots__/links.test.js.snap (100%) rename {test/e2e => packages/tests-e2e}/specs/__snapshots__/mentions.test.js.snap (100%) rename {test/e2e => packages/tests-e2e}/specs/__snapshots__/meta-attribute-block.test.js.snap (100%) rename {test/e2e => packages/tests-e2e}/specs/__snapshots__/plugins-api.test.js.snap (100%) rename {test/e2e => packages/tests-e2e}/specs/__snapshots__/reusable-blocks.test.js.snap (100%) rename {test/e2e => packages/tests-e2e}/specs/__snapshots__/rich-text.test.js.snap (100%) rename {test/e2e => packages/tests-e2e}/specs/__snapshots__/splitting-merging.test.js.snap (100%) rename {test/e2e => packages/tests-e2e}/specs/__snapshots__/style-variation.test.js.snap (100%) rename {test/e2e => packages/tests-e2e}/specs/__snapshots__/templates.test.js.snap (100%) rename {test/e2e => packages/tests-e2e}/specs/__snapshots__/undo.test.js.snap (100%) rename {test/e2e => packages/tests-e2e}/specs/__snapshots__/wp-editor-meta-box.test.js.snap (100%) rename {test/e2e => packages/tests-e2e}/specs/__snapshots__/writing-flow.test.js.snap (100%) rename {test/e2e => packages/tests-e2e}/specs/a11y.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/adding-blocks.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/adding-inline-tokens.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/align-hook.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/annotations.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/block-deletion.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/block-hierarchy-navigation.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/block-icons.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/block-mover.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/block-switcher.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/blocks/__snapshots__/classic.test.js.snap (100%) rename {test/e2e => packages/tests-e2e}/specs/blocks/__snapshots__/code.test.js.snap (100%) rename {test/e2e => packages/tests-e2e}/specs/blocks/__snapshots__/heading.test.js.snap (100%) rename {test/e2e => packages/tests-e2e}/specs/blocks/__snapshots__/html.test.js.snap (100%) rename {test/e2e => packages/tests-e2e}/specs/blocks/__snapshots__/list.test.js.snap (100%) rename {test/e2e => packages/tests-e2e}/specs/blocks/__snapshots__/quote.test.js.snap (100%) rename {test/e2e => packages/tests-e2e}/specs/blocks/__snapshots__/separator.test.js.snap (100%) rename {test/e2e => packages/tests-e2e}/specs/blocks/classic.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/blocks/code.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/blocks/heading.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/blocks/html.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/blocks/list.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/blocks/quote.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/blocks/separator.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/change-detection.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/compatibility-classic-editor.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/container-blocks.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/convert-block-type.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/datepicker.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/demo.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/deprecated-node-matcher.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/editor-modes.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/embedding.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/font-size-picker.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/format-api.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/fullscreen-mode.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/hooks-api.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/invalid-block.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/links.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/manage-reusable-blocks.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/mentions.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/meta-attribute-block.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/meta-boxes.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/multi-block-selection.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/navigable-toolbar.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/new-post-default-content.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/new-post.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/nux.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/plugins-api.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/popovers.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/post-visibility.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/preferences.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/preview.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/publish-button.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/publish-panel.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/publishing.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/reusable-blocks.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/rich-text.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/shortcut-help.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/sidebar-permalink-panel.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/sidebar.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/splitting-merging.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/style-variation.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/taxonomies.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/templates.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/undo.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/wp-editor-meta-box.test.js (100%) rename {test/e2e => packages/tests-e2e}/specs/writing-flow.test.js (100%) rename {test/e2e => packages/tests-e2e}/support/setup-test-framework.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/activate-plugin.js (95%) rename {test/e2e => packages/tests-e2e}/support/utils/are-pre-publish-checks-enabled.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/clear-local-storage.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/click-block-appender.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/click-button.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/click-on-close-modal-button.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/click-on-more-menu-item.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/create-new-post.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/create-url.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/deactivate-plugin.js (95%) rename {test/e2e => packages/tests-e2e}/support/utils/disable-pre-publish-checks.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/enable-page-dialog-accept.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/enable-pre-publish-checks.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/ensure-sidebar-opened.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/find-sidebar-panel-with-title.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/get-all-blocks.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/get-available-block-transforms.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/get-edited-post-content.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/has-block-switcher.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/index.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/insert-block.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/install-plugin.js (96%) rename {test/e2e => packages/tests-e2e}/support/utils/is-current-url.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/login-user.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/mocks/create-embedding-matcher.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/mocks/create-json-response.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/mocks/create-url-matcher.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/mocks/index.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/mocks/mock-or-transform.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/mocks/set-up-response-mocking.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/observe-focus-loss.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/open-document-settings-sidebar.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/open-publish-panel.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/press-key-times.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/press-key-with-modifier.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/publish-post-with-pre-publish-checks-disabled.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/publish-post.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/save-draft.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/search-for-block.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/select-block-by-client-id.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/set-browser-viewport.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/set-post-content.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/shared/config.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/shared/get-json-response.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/switch-editor-mode-to.js (86%) rename {test/e2e => packages/tests-e2e}/support/utils/switch-user-to-admin.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/switch-user-to-test.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/toggle-screen-option.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/transform-block-to.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/uninstall-plugin.js (96%) rename {test/e2e => packages/tests-e2e}/support/utils/visit-admin-page.js (100%) rename {test/e2e => packages/tests-e2e}/support/utils/wait-for-window-dimensions.js (100%) diff --git a/.eslintignore b/.eslintignore index cfc995e9e9aef..57cc5ea6a6336 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,6 +1,6 @@ build build-module node_modules -test/e2e/test-plugins +packages/tests-e2e/plugins vendor packages/block-serialization-spec-parser/parser.js diff --git a/.eslintrc.js b/.eslintrc.js index af4bda32427a9..9bce17b6e61f9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -182,7 +182,7 @@ module.exports = { }, overrides: [ { - files: [ 'test/e2e/**/*.js' ], + files: [ 'packages/tests-e2e/**/*.js' ], env: { browser: true, }, diff --git a/.travis.yml b/.travis.yml index 192c1b00f9959..250eb24ac2af5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -67,7 +67,7 @@ jobs: install: - ./bin/setup-local-env.sh script: - - $( npm bin )/wp-scripts test-e2e --testPathPattern=/test/e2e/specs/ --listTests > ~/.jest-e2e-tests + - $( npm bin )/wp-scripts test-e2e --testPathPattern=/packages/tests-e2e/specs/ --listTests > ~/.jest-e2e-tests - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 2 == 0' < ~/.jest-e2e-tests ) - name: E2E tests (Admin with plugins) (2/2) @@ -75,7 +75,7 @@ jobs: install: - ./bin/setup-local-env.sh script: - - $( npm bin )/wp-scripts test-e2e --testPathPattern=/test/e2e/specs/ --listTests > ~/.jest-e2e-tests + - $( npm bin )/wp-scripts test-e2e --testPathPattern=/packages/tests-e2e/specs/ --listTests > ~/.jest-e2e-tests - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 2 == 1' < ~/.jest-e2e-tests ) - name: E2E tests (Author without plugins) (1/2) @@ -83,7 +83,7 @@ jobs: install: - ./bin/setup-local-env.sh script: - - $( npm bin )/wp-scripts test-e2e --testPathPattern=/test/e2e/specs/ --listTests > ~/.jest-e2e-tests + - $( npm bin )/wp-scripts test-e2e --testPathPattern=/packages/tests-e2e/specs/ --listTests > ~/.jest-e2e-tests - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 2 == 0' < ~/.jest-e2e-tests ) - name: E2E tests (Author without plugins) (2/2) @@ -91,5 +91,5 @@ jobs: install: - ./bin/setup-local-env.sh script: - - $( npm bin )/wp-scripts test-e2e --testPathPattern=/test/e2e/specs/ --listTests > ~/.jest-e2e-tests + - $( npm bin )/wp-scripts test-e2e --testPathPattern=/packages/tests-e2e/specs/ --listTests > ~/.jest-e2e-tests - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 2 == 1' < ~/.jest-e2e-tests ) diff --git a/docker-compose.yml b/docker-compose.yml index 797b3f7f0a8e6..234da4726c5d0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,8 +12,8 @@ services: volumes: - wordpress:/var/www/html - .:/var/www/html/wp-content/plugins/gutenberg - - ./test/e2e/test-plugins:/var/www/html/wp-content/plugins/gutenberg-test-plugins - - ./test/e2e/test-mu-plugins:/var/www/html/wp-content/mu-plugins + - ./packages/tests-e2e/plugins:/var/www/html/wp-content/plugins/gutenberg-test-plugins + - ./packages/tests-e2e/mu-plugins:/var/www/html/wp-content/mu-plugins depends_on: - mysql @@ -59,8 +59,8 @@ services: volumes: - wordpress_e2e_tests:/var/www/html - .:/var/www/html/wp-content/plugins/gutenberg - - ./test/e2e/test-plugins:/var/www/html/wp-content/plugins/gutenberg-test-plugins - - ./test/e2e/test-mu-plugins:/var/www/html/wp-content/mu-plugins + - ./packages/tests-e2e/plugins:/var/www/html/wp-content/plugins/gutenberg-test-plugins + - ./test/e2e/mu-plugins:/var/www/html/wp-content/mu-plugins cli_e2e_tests: image: wordpress:cli diff --git a/docs/manifest.json b/docs/manifest.json index 4dc762893f571..40506ab5e932e 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -653,6 +653,12 @@ "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/shortcode/README.md", "parent": "packages" }, + { + "title": "@wordpress/tests-e2e", + "slug": "packages-tests-e2e", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/tests-e2e/README.md", + "parent": "packages" + }, { "title": "@wordpress/token-list", "slug": "packages-token-list", diff --git a/jsconfig.json b/jsconfig.json index 7e800a9c053b4..c3f3e3de874e5 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -9,7 +9,7 @@ "build", "build-module", "node_modules", - "test/e2e/test-plugins", + "packages/tests-e2e/plugins", "vendor" ] } diff --git a/package-lock.json b/package-lock.json index 61b1904fc0b20..8d73b5d4671a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2787,6 +2787,18 @@ "memize": "^1.0.5" } }, + "@wordpress/tests-e2e": { + "version": "file:packages/tests-e2e", + "dev": true, + "requires": { + "@wordpress/jest-console": "file:packages/jest-console", + "@wordpress/keycodes": "file:packages/keycodes", + "@wordpress/url": "file:packages/url", + "expect-puppeteer": "^3.2.0", + "lodash": "^4.17.10", + "node-fetch": "^1.7.3" + } + }, "@wordpress/token-list": { "version": "file:packages/token-list", "requires": { diff --git a/package.json b/package.json index f908971125040..a7355d5faadbe 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "@wordpress/npm-package-json-lint-config": "file:packages/npm-package-json-lint-config", "@wordpress/postcss-themes": "file:packages/postcss-themes", "@wordpress/scripts": "file:packages/scripts", + "@wordpress/tests-e2e": "file:packages/tests-e2e", "babel-loader": "8.0.0", "benchmark": "2.1.4", "browserslist": "3.2.8", @@ -86,7 +87,6 @@ "husky": "0.14.3", "is-plain-obj": "1.1.0", "is-equal-shallow": "0.1.3", - "jest-puppeteer": "3.2.1", "jsdom": "11.12.0", "lerna": "3.4.3", "lint-staged": "7.3.0", @@ -175,7 +175,7 @@ "publish:prod": "npm run build:packages && lerna publish", "test": "npm run lint && npm run test-unit", "pretest-e2e": "concurrently \"./bin/reset-e2e-tests.sh\" \"npm run build\"", - "test-e2e": "wp-scripts test-e2e --setupTestFrameworkScriptFile=./test/e2e/support/setup-test-framework.js --testPathPattern=/test/e2e/specs/", + "test-e2e": "wp-scripts test-e2e --setupTestFrameworkScriptFile=./packages/tests-e2e/support/setup-test-framework.js --testPathPattern=/packages/tests-e2e/specs/", "test-e2e:watch": "npm run test-e2e -- --watch", "test-php": "npm run lint-php && npm run test-unit-php", "test-unit": "wp-scripts test-unit-js --config test/unit/jest.config.json", diff --git a/packages/tests-e2e/.npmrc b/packages/tests-e2e/.npmrc new file mode 100644 index 0000000000000..43c97e719a5a8 --- /dev/null +++ b/packages/tests-e2e/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/packages/tests-e2e/README.md b/packages/tests-e2e/README.md new file mode 100644 index 0000000000000..d2d79debc91bb --- /dev/null +++ b/packages/tests-e2e/README.md @@ -0,0 +1,13 @@ +# Tests E2E + +End-To-End (E2E) tests for WordPress. + +## Installation + +Install the module + +```bash +npm install @wordpress/tests-e2e --save-dev +``` + +<br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/test/e2e/assets/10x10_e2e_test_image_z9T8jK.png b/packages/tests-e2e/assets/10x10_e2e_test_image_z9T8jK.png similarity index 100% rename from test/e2e/assets/10x10_e2e_test_image_z9T8jK.png rename to packages/tests-e2e/assets/10x10_e2e_test_image_z9T8jK.png diff --git a/test/e2e/assets/greeting-reusable-block.json b/packages/tests-e2e/assets/greeting-reusable-block.json similarity index 100% rename from test/e2e/assets/greeting-reusable-block.json rename to packages/tests-e2e/assets/greeting-reusable-block.json diff --git a/test/e2e/test-mu-plugins/disable-login-autofocus.php b/packages/tests-e2e/mu-plugins/disable-login-autofocus.php similarity index 100% rename from test/e2e/test-mu-plugins/disable-login-autofocus.php rename to packages/tests-e2e/mu-plugins/disable-login-autofocus.php diff --git a/packages/tests-e2e/package.json b/packages/tests-e2e/package.json new file mode 100644 index 0000000000000..b2cbd88c6685f --- /dev/null +++ b/packages/tests-e2e/package.json @@ -0,0 +1,36 @@ +{ + "name": "@wordpress/tests-e2e", + "private": true, + "version": "1.0.0-alpha.0", + "description": "End-To-End (E2E) tests for WordPress.", + "author": "The WordPress Contributors", + "license": "GPL-2.0-or-later", + "keywords": [ + "wordpress", + "tests", + "e2e" + ], + "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/tests-e2e/README.md", + "repository": { + "type": "git", + "url": "https://github.com/WordPress/gutenberg.git" + }, + "bugs": { + "url": "https://github.com/WordPress/gutenberg/issues" + }, + "dependencies": { + "@wordpress/jest-console": "file:../jest-console", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/url": "file:../url", + "expect-puppeteer": "^3.2.0", + "lodash": "^4.17.10", + "node-fetch": "^1.7.3" + }, + "peerDependencies": { + "jest": ">=23.0.0", + "puppeteer": ">=1.6.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/test/e2e/test-plugins/align-hook.php b/packages/tests-e2e/plugins/align-hook.php similarity index 100% rename from test/e2e/test-plugins/align-hook.php rename to packages/tests-e2e/plugins/align-hook.php diff --git a/test/e2e/test-plugins/align-hook/index.js b/packages/tests-e2e/plugins/align-hook/index.js similarity index 100% rename from test/e2e/test-plugins/align-hook/index.js rename to packages/tests-e2e/plugins/align-hook/index.js diff --git a/test/e2e/test-plugins/block-icons.php b/packages/tests-e2e/plugins/block-icons.php similarity index 100% rename from test/e2e/test-plugins/block-icons.php rename to packages/tests-e2e/plugins/block-icons.php diff --git a/test/e2e/test-plugins/block-icons/index.js b/packages/tests-e2e/plugins/block-icons/index.js similarity index 100% rename from test/e2e/test-plugins/block-icons/index.js rename to packages/tests-e2e/plugins/block-icons/index.js diff --git a/test/e2e/test-plugins/container-without-paragraph.php b/packages/tests-e2e/plugins/container-without-paragraph.php similarity index 100% rename from test/e2e/test-plugins/container-without-paragraph.php rename to packages/tests-e2e/plugins/container-without-paragraph.php diff --git a/test/e2e/test-plugins/container-without-paragraph/index.js b/packages/tests-e2e/plugins/container-without-paragraph/index.js similarity index 100% rename from test/e2e/test-plugins/container-without-paragraph/index.js rename to packages/tests-e2e/plugins/container-without-paragraph/index.js diff --git a/test/e2e/test-plugins/custom-post-types.php b/packages/tests-e2e/plugins/custom-post-types.php similarity index 100% rename from test/e2e/test-plugins/custom-post-types.php rename to packages/tests-e2e/plugins/custom-post-types.php diff --git a/test/e2e/test-plugins/default-post-content.php b/packages/tests-e2e/plugins/default-post-content.php similarity index 100% rename from test/e2e/test-plugins/default-post-content.php rename to packages/tests-e2e/plugins/default-post-content.php diff --git a/test/e2e/test-plugins/deprecated-node-matcher.php b/packages/tests-e2e/plugins/deprecated-node-matcher.php similarity index 100% rename from test/e2e/test-plugins/deprecated-node-matcher.php rename to packages/tests-e2e/plugins/deprecated-node-matcher.php diff --git a/test/e2e/test-plugins/deprecated-node-matcher/index.js b/packages/tests-e2e/plugins/deprecated-node-matcher/index.js similarity index 100% rename from test/e2e/test-plugins/deprecated-node-matcher/index.js rename to packages/tests-e2e/plugins/deprecated-node-matcher/index.js diff --git a/test/e2e/test-plugins/format-api.php b/packages/tests-e2e/plugins/format-api.php similarity index 100% rename from test/e2e/test-plugins/format-api.php rename to packages/tests-e2e/plugins/format-api.php diff --git a/test/e2e/test-plugins/format-api/index.js b/packages/tests-e2e/plugins/format-api/index.js similarity index 100% rename from test/e2e/test-plugins/format-api/index.js rename to packages/tests-e2e/plugins/format-api/index.js diff --git a/test/e2e/test-plugins/hooks-api.php b/packages/tests-e2e/plugins/hooks-api.php similarity index 100% rename from test/e2e/test-plugins/hooks-api.php rename to packages/tests-e2e/plugins/hooks-api.php diff --git a/test/e2e/test-plugins/hooks-api/index.js b/packages/tests-e2e/plugins/hooks-api/index.js similarity index 100% rename from test/e2e/test-plugins/hooks-api/index.js rename to packages/tests-e2e/plugins/hooks-api/index.js diff --git a/test/e2e/test-plugins/inner-blocks-templates.php b/packages/tests-e2e/plugins/inner-blocks-templates.php similarity index 100% rename from test/e2e/test-plugins/inner-blocks-templates.php rename to packages/tests-e2e/plugins/inner-blocks-templates.php diff --git a/test/e2e/test-plugins/inner-blocks-templates/index.js b/packages/tests-e2e/plugins/inner-blocks-templates/index.js similarity index 100% rename from test/e2e/test-plugins/inner-blocks-templates/index.js rename to packages/tests-e2e/plugins/inner-blocks-templates/index.js diff --git a/test/e2e/test-plugins/meta-attribute-block.php b/packages/tests-e2e/plugins/meta-attribute-block.php similarity index 100% rename from test/e2e/test-plugins/meta-attribute-block.php rename to packages/tests-e2e/plugins/meta-attribute-block.php diff --git a/test/e2e/test-plugins/meta-attribute-block/index.js b/packages/tests-e2e/plugins/meta-attribute-block/index.js similarity index 100% rename from test/e2e/test-plugins/meta-attribute-block/index.js rename to packages/tests-e2e/plugins/meta-attribute-block/index.js diff --git a/test/e2e/test-plugins/meta-box.php b/packages/tests-e2e/plugins/meta-box.php similarity index 100% rename from test/e2e/test-plugins/meta-box.php rename to packages/tests-e2e/plugins/meta-box.php diff --git a/test/e2e/test-plugins/plugins-api.php b/packages/tests-e2e/plugins/plugins-api.php similarity index 100% rename from test/e2e/test-plugins/plugins-api.php rename to packages/tests-e2e/plugins/plugins-api.php diff --git a/test/e2e/test-plugins/plugins-api/annotations-sidebar.js b/packages/tests-e2e/plugins/plugins-api/annotations-sidebar.js similarity index 100% rename from test/e2e/test-plugins/plugins-api/annotations-sidebar.js rename to packages/tests-e2e/plugins/plugins-api/annotations-sidebar.js diff --git a/test/e2e/test-plugins/plugins-api/post-status-info.js b/packages/tests-e2e/plugins/plugins-api/post-status-info.js similarity index 100% rename from test/e2e/test-plugins/plugins-api/post-status-info.js rename to packages/tests-e2e/plugins/plugins-api/post-status-info.js diff --git a/test/e2e/test-plugins/plugins-api/publish-panel.js b/packages/tests-e2e/plugins/plugins-api/publish-panel.js similarity index 100% rename from test/e2e/test-plugins/plugins-api/publish-panel.js rename to packages/tests-e2e/plugins/plugins-api/publish-panel.js diff --git a/test/e2e/test-plugins/plugins-api/sidebar.js b/packages/tests-e2e/plugins/plugins-api/sidebar.js similarity index 100% rename from test/e2e/test-plugins/plugins-api/sidebar.js rename to packages/tests-e2e/plugins/plugins-api/sidebar.js diff --git a/test/e2e/test-plugins/post-formats.php b/packages/tests-e2e/plugins/post-formats.php similarity index 100% rename from test/e2e/test-plugins/post-formats.php rename to packages/tests-e2e/plugins/post-formats.php diff --git a/test/e2e/test-plugins/templates.php b/packages/tests-e2e/plugins/templates.php similarity index 100% rename from test/e2e/test-plugins/templates.php rename to packages/tests-e2e/plugins/templates.php diff --git a/test/e2e/test-plugins/wp-editor-metabox.php b/packages/tests-e2e/plugins/wp-editor-metabox.php similarity index 100% rename from test/e2e/test-plugins/wp-editor-metabox.php rename to packages/tests-e2e/plugins/wp-editor-metabox.php diff --git a/test/e2e/specs/__snapshots__/adding-blocks.test.js.snap b/packages/tests-e2e/specs/__snapshots__/adding-blocks.test.js.snap similarity index 100% rename from test/e2e/specs/__snapshots__/adding-blocks.test.js.snap rename to packages/tests-e2e/specs/__snapshots__/adding-blocks.test.js.snap diff --git a/test/e2e/specs/__snapshots__/align-hook.test.js.snap b/packages/tests-e2e/specs/__snapshots__/align-hook.test.js.snap similarity index 100% rename from test/e2e/specs/__snapshots__/align-hook.test.js.snap rename to packages/tests-e2e/specs/__snapshots__/align-hook.test.js.snap diff --git a/test/e2e/specs/__snapshots__/block-deletion.test.js.snap b/packages/tests-e2e/specs/__snapshots__/block-deletion.test.js.snap similarity index 100% rename from test/e2e/specs/__snapshots__/block-deletion.test.js.snap rename to packages/tests-e2e/specs/__snapshots__/block-deletion.test.js.snap diff --git a/test/e2e/specs/__snapshots__/block-hierarchy-navigation.test.js.snap b/packages/tests-e2e/specs/__snapshots__/block-hierarchy-navigation.test.js.snap similarity index 100% rename from test/e2e/specs/__snapshots__/block-hierarchy-navigation.test.js.snap rename to packages/tests-e2e/specs/__snapshots__/block-hierarchy-navigation.test.js.snap diff --git a/test/e2e/specs/__snapshots__/compatibility-classic-editor.test.js.snap b/packages/tests-e2e/specs/__snapshots__/compatibility-classic-editor.test.js.snap similarity index 100% rename from test/e2e/specs/__snapshots__/compatibility-classic-editor.test.js.snap rename to packages/tests-e2e/specs/__snapshots__/compatibility-classic-editor.test.js.snap diff --git a/test/e2e/specs/__snapshots__/container-blocks.test.js.snap b/packages/tests-e2e/specs/__snapshots__/container-blocks.test.js.snap similarity index 100% rename from test/e2e/specs/__snapshots__/container-blocks.test.js.snap rename to packages/tests-e2e/specs/__snapshots__/container-blocks.test.js.snap diff --git a/test/e2e/specs/__snapshots__/convert-block-type.test.js.snap b/packages/tests-e2e/specs/__snapshots__/convert-block-type.test.js.snap similarity index 100% rename from test/e2e/specs/__snapshots__/convert-block-type.test.js.snap rename to packages/tests-e2e/specs/__snapshots__/convert-block-type.test.js.snap diff --git a/test/e2e/specs/__snapshots__/deprecated-node-matcher.test.js.snap b/packages/tests-e2e/specs/__snapshots__/deprecated-node-matcher.test.js.snap similarity index 100% rename from test/e2e/specs/__snapshots__/deprecated-node-matcher.test.js.snap rename to packages/tests-e2e/specs/__snapshots__/deprecated-node-matcher.test.js.snap diff --git a/test/e2e/specs/__snapshots__/embedding.test.js.snap b/packages/tests-e2e/specs/__snapshots__/embedding.test.js.snap similarity index 100% rename from test/e2e/specs/__snapshots__/embedding.test.js.snap rename to packages/tests-e2e/specs/__snapshots__/embedding.test.js.snap diff --git a/test/e2e/specs/__snapshots__/font-size-picker.test.js.snap b/packages/tests-e2e/specs/__snapshots__/font-size-picker.test.js.snap similarity index 100% rename from test/e2e/specs/__snapshots__/font-size-picker.test.js.snap rename to packages/tests-e2e/specs/__snapshots__/font-size-picker.test.js.snap diff --git a/test/e2e/specs/__snapshots__/format-api.test.js.snap b/packages/tests-e2e/specs/__snapshots__/format-api.test.js.snap similarity index 100% rename from test/e2e/specs/__snapshots__/format-api.test.js.snap rename to packages/tests-e2e/specs/__snapshots__/format-api.test.js.snap diff --git a/test/e2e/specs/__snapshots__/links.test.js.snap b/packages/tests-e2e/specs/__snapshots__/links.test.js.snap similarity index 100% rename from test/e2e/specs/__snapshots__/links.test.js.snap rename to packages/tests-e2e/specs/__snapshots__/links.test.js.snap diff --git a/test/e2e/specs/__snapshots__/mentions.test.js.snap b/packages/tests-e2e/specs/__snapshots__/mentions.test.js.snap similarity index 100% rename from test/e2e/specs/__snapshots__/mentions.test.js.snap rename to packages/tests-e2e/specs/__snapshots__/mentions.test.js.snap diff --git a/test/e2e/specs/__snapshots__/meta-attribute-block.test.js.snap b/packages/tests-e2e/specs/__snapshots__/meta-attribute-block.test.js.snap similarity index 100% rename from test/e2e/specs/__snapshots__/meta-attribute-block.test.js.snap rename to packages/tests-e2e/specs/__snapshots__/meta-attribute-block.test.js.snap diff --git a/test/e2e/specs/__snapshots__/plugins-api.test.js.snap b/packages/tests-e2e/specs/__snapshots__/plugins-api.test.js.snap similarity index 100% rename from test/e2e/specs/__snapshots__/plugins-api.test.js.snap rename to packages/tests-e2e/specs/__snapshots__/plugins-api.test.js.snap diff --git a/test/e2e/specs/__snapshots__/reusable-blocks.test.js.snap b/packages/tests-e2e/specs/__snapshots__/reusable-blocks.test.js.snap similarity index 100% rename from test/e2e/specs/__snapshots__/reusable-blocks.test.js.snap rename to packages/tests-e2e/specs/__snapshots__/reusable-blocks.test.js.snap diff --git a/test/e2e/specs/__snapshots__/rich-text.test.js.snap b/packages/tests-e2e/specs/__snapshots__/rich-text.test.js.snap similarity index 100% rename from test/e2e/specs/__snapshots__/rich-text.test.js.snap rename to packages/tests-e2e/specs/__snapshots__/rich-text.test.js.snap diff --git a/test/e2e/specs/__snapshots__/splitting-merging.test.js.snap b/packages/tests-e2e/specs/__snapshots__/splitting-merging.test.js.snap similarity index 100% rename from test/e2e/specs/__snapshots__/splitting-merging.test.js.snap rename to packages/tests-e2e/specs/__snapshots__/splitting-merging.test.js.snap diff --git a/test/e2e/specs/__snapshots__/style-variation.test.js.snap b/packages/tests-e2e/specs/__snapshots__/style-variation.test.js.snap similarity index 100% rename from test/e2e/specs/__snapshots__/style-variation.test.js.snap rename to packages/tests-e2e/specs/__snapshots__/style-variation.test.js.snap diff --git a/test/e2e/specs/__snapshots__/templates.test.js.snap b/packages/tests-e2e/specs/__snapshots__/templates.test.js.snap similarity index 100% rename from test/e2e/specs/__snapshots__/templates.test.js.snap rename to packages/tests-e2e/specs/__snapshots__/templates.test.js.snap diff --git a/test/e2e/specs/__snapshots__/undo.test.js.snap b/packages/tests-e2e/specs/__snapshots__/undo.test.js.snap similarity index 100% rename from test/e2e/specs/__snapshots__/undo.test.js.snap rename to packages/tests-e2e/specs/__snapshots__/undo.test.js.snap diff --git a/test/e2e/specs/__snapshots__/wp-editor-meta-box.test.js.snap b/packages/tests-e2e/specs/__snapshots__/wp-editor-meta-box.test.js.snap similarity index 100% rename from test/e2e/specs/__snapshots__/wp-editor-meta-box.test.js.snap rename to packages/tests-e2e/specs/__snapshots__/wp-editor-meta-box.test.js.snap diff --git a/test/e2e/specs/__snapshots__/writing-flow.test.js.snap b/packages/tests-e2e/specs/__snapshots__/writing-flow.test.js.snap similarity index 100% rename from test/e2e/specs/__snapshots__/writing-flow.test.js.snap rename to packages/tests-e2e/specs/__snapshots__/writing-flow.test.js.snap diff --git a/test/e2e/specs/a11y.test.js b/packages/tests-e2e/specs/a11y.test.js similarity index 100% rename from test/e2e/specs/a11y.test.js rename to packages/tests-e2e/specs/a11y.test.js diff --git a/test/e2e/specs/adding-blocks.test.js b/packages/tests-e2e/specs/adding-blocks.test.js similarity index 100% rename from test/e2e/specs/adding-blocks.test.js rename to packages/tests-e2e/specs/adding-blocks.test.js diff --git a/test/e2e/specs/adding-inline-tokens.test.js b/packages/tests-e2e/specs/adding-inline-tokens.test.js similarity index 100% rename from test/e2e/specs/adding-inline-tokens.test.js rename to packages/tests-e2e/specs/adding-inline-tokens.test.js diff --git a/test/e2e/specs/align-hook.test.js b/packages/tests-e2e/specs/align-hook.test.js similarity index 100% rename from test/e2e/specs/align-hook.test.js rename to packages/tests-e2e/specs/align-hook.test.js diff --git a/test/e2e/specs/annotations.test.js b/packages/tests-e2e/specs/annotations.test.js similarity index 100% rename from test/e2e/specs/annotations.test.js rename to packages/tests-e2e/specs/annotations.test.js diff --git a/test/e2e/specs/block-deletion.test.js b/packages/tests-e2e/specs/block-deletion.test.js similarity index 100% rename from test/e2e/specs/block-deletion.test.js rename to packages/tests-e2e/specs/block-deletion.test.js diff --git a/test/e2e/specs/block-hierarchy-navigation.test.js b/packages/tests-e2e/specs/block-hierarchy-navigation.test.js similarity index 100% rename from test/e2e/specs/block-hierarchy-navigation.test.js rename to packages/tests-e2e/specs/block-hierarchy-navigation.test.js diff --git a/test/e2e/specs/block-icons.test.js b/packages/tests-e2e/specs/block-icons.test.js similarity index 100% rename from test/e2e/specs/block-icons.test.js rename to packages/tests-e2e/specs/block-icons.test.js diff --git a/test/e2e/specs/block-mover.test.js b/packages/tests-e2e/specs/block-mover.test.js similarity index 100% rename from test/e2e/specs/block-mover.test.js rename to packages/tests-e2e/specs/block-mover.test.js diff --git a/test/e2e/specs/block-switcher.test.js b/packages/tests-e2e/specs/block-switcher.test.js similarity index 100% rename from test/e2e/specs/block-switcher.test.js rename to packages/tests-e2e/specs/block-switcher.test.js diff --git a/test/e2e/specs/blocks/__snapshots__/classic.test.js.snap b/packages/tests-e2e/specs/blocks/__snapshots__/classic.test.js.snap similarity index 100% rename from test/e2e/specs/blocks/__snapshots__/classic.test.js.snap rename to packages/tests-e2e/specs/blocks/__snapshots__/classic.test.js.snap diff --git a/test/e2e/specs/blocks/__snapshots__/code.test.js.snap b/packages/tests-e2e/specs/blocks/__snapshots__/code.test.js.snap similarity index 100% rename from test/e2e/specs/blocks/__snapshots__/code.test.js.snap rename to packages/tests-e2e/specs/blocks/__snapshots__/code.test.js.snap diff --git a/test/e2e/specs/blocks/__snapshots__/heading.test.js.snap b/packages/tests-e2e/specs/blocks/__snapshots__/heading.test.js.snap similarity index 100% rename from test/e2e/specs/blocks/__snapshots__/heading.test.js.snap rename to packages/tests-e2e/specs/blocks/__snapshots__/heading.test.js.snap diff --git a/test/e2e/specs/blocks/__snapshots__/html.test.js.snap b/packages/tests-e2e/specs/blocks/__snapshots__/html.test.js.snap similarity index 100% rename from test/e2e/specs/blocks/__snapshots__/html.test.js.snap rename to packages/tests-e2e/specs/blocks/__snapshots__/html.test.js.snap diff --git a/test/e2e/specs/blocks/__snapshots__/list.test.js.snap b/packages/tests-e2e/specs/blocks/__snapshots__/list.test.js.snap similarity index 100% rename from test/e2e/specs/blocks/__snapshots__/list.test.js.snap rename to packages/tests-e2e/specs/blocks/__snapshots__/list.test.js.snap diff --git a/test/e2e/specs/blocks/__snapshots__/quote.test.js.snap b/packages/tests-e2e/specs/blocks/__snapshots__/quote.test.js.snap similarity index 100% rename from test/e2e/specs/blocks/__snapshots__/quote.test.js.snap rename to packages/tests-e2e/specs/blocks/__snapshots__/quote.test.js.snap diff --git a/test/e2e/specs/blocks/__snapshots__/separator.test.js.snap b/packages/tests-e2e/specs/blocks/__snapshots__/separator.test.js.snap similarity index 100% rename from test/e2e/specs/blocks/__snapshots__/separator.test.js.snap rename to packages/tests-e2e/specs/blocks/__snapshots__/separator.test.js.snap diff --git a/test/e2e/specs/blocks/classic.test.js b/packages/tests-e2e/specs/blocks/classic.test.js similarity index 100% rename from test/e2e/specs/blocks/classic.test.js rename to packages/tests-e2e/specs/blocks/classic.test.js diff --git a/test/e2e/specs/blocks/code.test.js b/packages/tests-e2e/specs/blocks/code.test.js similarity index 100% rename from test/e2e/specs/blocks/code.test.js rename to packages/tests-e2e/specs/blocks/code.test.js diff --git a/test/e2e/specs/blocks/heading.test.js b/packages/tests-e2e/specs/blocks/heading.test.js similarity index 100% rename from test/e2e/specs/blocks/heading.test.js rename to packages/tests-e2e/specs/blocks/heading.test.js diff --git a/test/e2e/specs/blocks/html.test.js b/packages/tests-e2e/specs/blocks/html.test.js similarity index 100% rename from test/e2e/specs/blocks/html.test.js rename to packages/tests-e2e/specs/blocks/html.test.js diff --git a/test/e2e/specs/blocks/list.test.js b/packages/tests-e2e/specs/blocks/list.test.js similarity index 100% rename from test/e2e/specs/blocks/list.test.js rename to packages/tests-e2e/specs/blocks/list.test.js diff --git a/test/e2e/specs/blocks/quote.test.js b/packages/tests-e2e/specs/blocks/quote.test.js similarity index 100% rename from test/e2e/specs/blocks/quote.test.js rename to packages/tests-e2e/specs/blocks/quote.test.js diff --git a/test/e2e/specs/blocks/separator.test.js b/packages/tests-e2e/specs/blocks/separator.test.js similarity index 100% rename from test/e2e/specs/blocks/separator.test.js rename to packages/tests-e2e/specs/blocks/separator.test.js diff --git a/test/e2e/specs/change-detection.test.js b/packages/tests-e2e/specs/change-detection.test.js similarity index 100% rename from test/e2e/specs/change-detection.test.js rename to packages/tests-e2e/specs/change-detection.test.js diff --git a/test/e2e/specs/compatibility-classic-editor.test.js b/packages/tests-e2e/specs/compatibility-classic-editor.test.js similarity index 100% rename from test/e2e/specs/compatibility-classic-editor.test.js rename to packages/tests-e2e/specs/compatibility-classic-editor.test.js diff --git a/test/e2e/specs/container-blocks.test.js b/packages/tests-e2e/specs/container-blocks.test.js similarity index 100% rename from test/e2e/specs/container-blocks.test.js rename to packages/tests-e2e/specs/container-blocks.test.js diff --git a/test/e2e/specs/convert-block-type.test.js b/packages/tests-e2e/specs/convert-block-type.test.js similarity index 100% rename from test/e2e/specs/convert-block-type.test.js rename to packages/tests-e2e/specs/convert-block-type.test.js diff --git a/test/e2e/specs/datepicker.test.js b/packages/tests-e2e/specs/datepicker.test.js similarity index 100% rename from test/e2e/specs/datepicker.test.js rename to packages/tests-e2e/specs/datepicker.test.js diff --git a/test/e2e/specs/demo.test.js b/packages/tests-e2e/specs/demo.test.js similarity index 100% rename from test/e2e/specs/demo.test.js rename to packages/tests-e2e/specs/demo.test.js diff --git a/test/e2e/specs/deprecated-node-matcher.test.js b/packages/tests-e2e/specs/deprecated-node-matcher.test.js similarity index 100% rename from test/e2e/specs/deprecated-node-matcher.test.js rename to packages/tests-e2e/specs/deprecated-node-matcher.test.js diff --git a/test/e2e/specs/editor-modes.test.js b/packages/tests-e2e/specs/editor-modes.test.js similarity index 100% rename from test/e2e/specs/editor-modes.test.js rename to packages/tests-e2e/specs/editor-modes.test.js diff --git a/test/e2e/specs/embedding.test.js b/packages/tests-e2e/specs/embedding.test.js similarity index 100% rename from test/e2e/specs/embedding.test.js rename to packages/tests-e2e/specs/embedding.test.js diff --git a/test/e2e/specs/font-size-picker.test.js b/packages/tests-e2e/specs/font-size-picker.test.js similarity index 100% rename from test/e2e/specs/font-size-picker.test.js rename to packages/tests-e2e/specs/font-size-picker.test.js diff --git a/test/e2e/specs/format-api.test.js b/packages/tests-e2e/specs/format-api.test.js similarity index 100% rename from test/e2e/specs/format-api.test.js rename to packages/tests-e2e/specs/format-api.test.js diff --git a/test/e2e/specs/fullscreen-mode.test.js b/packages/tests-e2e/specs/fullscreen-mode.test.js similarity index 100% rename from test/e2e/specs/fullscreen-mode.test.js rename to packages/tests-e2e/specs/fullscreen-mode.test.js diff --git a/test/e2e/specs/hooks-api.test.js b/packages/tests-e2e/specs/hooks-api.test.js similarity index 100% rename from test/e2e/specs/hooks-api.test.js rename to packages/tests-e2e/specs/hooks-api.test.js diff --git a/test/e2e/specs/invalid-block.test.js b/packages/tests-e2e/specs/invalid-block.test.js similarity index 100% rename from test/e2e/specs/invalid-block.test.js rename to packages/tests-e2e/specs/invalid-block.test.js diff --git a/test/e2e/specs/links.test.js b/packages/tests-e2e/specs/links.test.js similarity index 100% rename from test/e2e/specs/links.test.js rename to packages/tests-e2e/specs/links.test.js diff --git a/test/e2e/specs/manage-reusable-blocks.test.js b/packages/tests-e2e/specs/manage-reusable-blocks.test.js similarity index 100% rename from test/e2e/specs/manage-reusable-blocks.test.js rename to packages/tests-e2e/specs/manage-reusable-blocks.test.js diff --git a/test/e2e/specs/mentions.test.js b/packages/tests-e2e/specs/mentions.test.js similarity index 100% rename from test/e2e/specs/mentions.test.js rename to packages/tests-e2e/specs/mentions.test.js diff --git a/test/e2e/specs/meta-attribute-block.test.js b/packages/tests-e2e/specs/meta-attribute-block.test.js similarity index 100% rename from test/e2e/specs/meta-attribute-block.test.js rename to packages/tests-e2e/specs/meta-attribute-block.test.js diff --git a/test/e2e/specs/meta-boxes.test.js b/packages/tests-e2e/specs/meta-boxes.test.js similarity index 100% rename from test/e2e/specs/meta-boxes.test.js rename to packages/tests-e2e/specs/meta-boxes.test.js diff --git a/test/e2e/specs/multi-block-selection.test.js b/packages/tests-e2e/specs/multi-block-selection.test.js similarity index 100% rename from test/e2e/specs/multi-block-selection.test.js rename to packages/tests-e2e/specs/multi-block-selection.test.js diff --git a/test/e2e/specs/navigable-toolbar.test.js b/packages/tests-e2e/specs/navigable-toolbar.test.js similarity index 100% rename from test/e2e/specs/navigable-toolbar.test.js rename to packages/tests-e2e/specs/navigable-toolbar.test.js diff --git a/test/e2e/specs/new-post-default-content.test.js b/packages/tests-e2e/specs/new-post-default-content.test.js similarity index 100% rename from test/e2e/specs/new-post-default-content.test.js rename to packages/tests-e2e/specs/new-post-default-content.test.js diff --git a/test/e2e/specs/new-post.test.js b/packages/tests-e2e/specs/new-post.test.js similarity index 100% rename from test/e2e/specs/new-post.test.js rename to packages/tests-e2e/specs/new-post.test.js diff --git a/test/e2e/specs/nux.test.js b/packages/tests-e2e/specs/nux.test.js similarity index 100% rename from test/e2e/specs/nux.test.js rename to packages/tests-e2e/specs/nux.test.js diff --git a/test/e2e/specs/plugins-api.test.js b/packages/tests-e2e/specs/plugins-api.test.js similarity index 100% rename from test/e2e/specs/plugins-api.test.js rename to packages/tests-e2e/specs/plugins-api.test.js diff --git a/test/e2e/specs/popovers.test.js b/packages/tests-e2e/specs/popovers.test.js similarity index 100% rename from test/e2e/specs/popovers.test.js rename to packages/tests-e2e/specs/popovers.test.js diff --git a/test/e2e/specs/post-visibility.test.js b/packages/tests-e2e/specs/post-visibility.test.js similarity index 100% rename from test/e2e/specs/post-visibility.test.js rename to packages/tests-e2e/specs/post-visibility.test.js diff --git a/test/e2e/specs/preferences.test.js b/packages/tests-e2e/specs/preferences.test.js similarity index 100% rename from test/e2e/specs/preferences.test.js rename to packages/tests-e2e/specs/preferences.test.js diff --git a/test/e2e/specs/preview.test.js b/packages/tests-e2e/specs/preview.test.js similarity index 100% rename from test/e2e/specs/preview.test.js rename to packages/tests-e2e/specs/preview.test.js diff --git a/test/e2e/specs/publish-button.test.js b/packages/tests-e2e/specs/publish-button.test.js similarity index 100% rename from test/e2e/specs/publish-button.test.js rename to packages/tests-e2e/specs/publish-button.test.js diff --git a/test/e2e/specs/publish-panel.test.js b/packages/tests-e2e/specs/publish-panel.test.js similarity index 100% rename from test/e2e/specs/publish-panel.test.js rename to packages/tests-e2e/specs/publish-panel.test.js diff --git a/test/e2e/specs/publishing.test.js b/packages/tests-e2e/specs/publishing.test.js similarity index 100% rename from test/e2e/specs/publishing.test.js rename to packages/tests-e2e/specs/publishing.test.js diff --git a/test/e2e/specs/reusable-blocks.test.js b/packages/tests-e2e/specs/reusable-blocks.test.js similarity index 100% rename from test/e2e/specs/reusable-blocks.test.js rename to packages/tests-e2e/specs/reusable-blocks.test.js diff --git a/test/e2e/specs/rich-text.test.js b/packages/tests-e2e/specs/rich-text.test.js similarity index 100% rename from test/e2e/specs/rich-text.test.js rename to packages/tests-e2e/specs/rich-text.test.js diff --git a/test/e2e/specs/shortcut-help.test.js b/packages/tests-e2e/specs/shortcut-help.test.js similarity index 100% rename from test/e2e/specs/shortcut-help.test.js rename to packages/tests-e2e/specs/shortcut-help.test.js diff --git a/test/e2e/specs/sidebar-permalink-panel.test.js b/packages/tests-e2e/specs/sidebar-permalink-panel.test.js similarity index 100% rename from test/e2e/specs/sidebar-permalink-panel.test.js rename to packages/tests-e2e/specs/sidebar-permalink-panel.test.js diff --git a/test/e2e/specs/sidebar.test.js b/packages/tests-e2e/specs/sidebar.test.js similarity index 100% rename from test/e2e/specs/sidebar.test.js rename to packages/tests-e2e/specs/sidebar.test.js diff --git a/test/e2e/specs/splitting-merging.test.js b/packages/tests-e2e/specs/splitting-merging.test.js similarity index 100% rename from test/e2e/specs/splitting-merging.test.js rename to packages/tests-e2e/specs/splitting-merging.test.js diff --git a/test/e2e/specs/style-variation.test.js b/packages/tests-e2e/specs/style-variation.test.js similarity index 100% rename from test/e2e/specs/style-variation.test.js rename to packages/tests-e2e/specs/style-variation.test.js diff --git a/test/e2e/specs/taxonomies.test.js b/packages/tests-e2e/specs/taxonomies.test.js similarity index 100% rename from test/e2e/specs/taxonomies.test.js rename to packages/tests-e2e/specs/taxonomies.test.js diff --git a/test/e2e/specs/templates.test.js b/packages/tests-e2e/specs/templates.test.js similarity index 100% rename from test/e2e/specs/templates.test.js rename to packages/tests-e2e/specs/templates.test.js diff --git a/test/e2e/specs/undo.test.js b/packages/tests-e2e/specs/undo.test.js similarity index 100% rename from test/e2e/specs/undo.test.js rename to packages/tests-e2e/specs/undo.test.js diff --git a/test/e2e/specs/wp-editor-meta-box.test.js b/packages/tests-e2e/specs/wp-editor-meta-box.test.js similarity index 100% rename from test/e2e/specs/wp-editor-meta-box.test.js rename to packages/tests-e2e/specs/wp-editor-meta-box.test.js diff --git a/test/e2e/specs/writing-flow.test.js b/packages/tests-e2e/specs/writing-flow.test.js similarity index 100% rename from test/e2e/specs/writing-flow.test.js rename to packages/tests-e2e/specs/writing-flow.test.js diff --git a/test/e2e/support/setup-test-framework.js b/packages/tests-e2e/support/setup-test-framework.js similarity index 100% rename from test/e2e/support/setup-test-framework.js rename to packages/tests-e2e/support/setup-test-framework.js diff --git a/test/e2e/support/utils/activate-plugin.js b/packages/tests-e2e/support/utils/activate-plugin.js similarity index 95% rename from test/e2e/support/utils/activate-plugin.js rename to packages/tests-e2e/support/utils/activate-plugin.js index c99b70b92376d..ce52f380f266b 100644 --- a/test/e2e/support/utils/activate-plugin.js +++ b/packages/tests-e2e/support/utils/activate-plugin.js @@ -1,5 +1,5 @@ /** - * Node dependencies + * Internal dependencies */ import { switchUserToAdmin } from './switch-user-to-admin'; import { switchUserToTest } from './switch-user-to-test'; diff --git a/test/e2e/support/utils/are-pre-publish-checks-enabled.js b/packages/tests-e2e/support/utils/are-pre-publish-checks-enabled.js similarity index 100% rename from test/e2e/support/utils/are-pre-publish-checks-enabled.js rename to packages/tests-e2e/support/utils/are-pre-publish-checks-enabled.js diff --git a/test/e2e/support/utils/clear-local-storage.js b/packages/tests-e2e/support/utils/clear-local-storage.js similarity index 100% rename from test/e2e/support/utils/clear-local-storage.js rename to packages/tests-e2e/support/utils/clear-local-storage.js diff --git a/test/e2e/support/utils/click-block-appender.js b/packages/tests-e2e/support/utils/click-block-appender.js similarity index 100% rename from test/e2e/support/utils/click-block-appender.js rename to packages/tests-e2e/support/utils/click-block-appender.js diff --git a/test/e2e/support/utils/click-button.js b/packages/tests-e2e/support/utils/click-button.js similarity index 100% rename from test/e2e/support/utils/click-button.js rename to packages/tests-e2e/support/utils/click-button.js diff --git a/test/e2e/support/utils/click-on-close-modal-button.js b/packages/tests-e2e/support/utils/click-on-close-modal-button.js similarity index 100% rename from test/e2e/support/utils/click-on-close-modal-button.js rename to packages/tests-e2e/support/utils/click-on-close-modal-button.js diff --git a/test/e2e/support/utils/click-on-more-menu-item.js b/packages/tests-e2e/support/utils/click-on-more-menu-item.js similarity index 100% rename from test/e2e/support/utils/click-on-more-menu-item.js rename to packages/tests-e2e/support/utils/click-on-more-menu-item.js diff --git a/test/e2e/support/utils/create-new-post.js b/packages/tests-e2e/support/utils/create-new-post.js similarity index 100% rename from test/e2e/support/utils/create-new-post.js rename to packages/tests-e2e/support/utils/create-new-post.js diff --git a/test/e2e/support/utils/create-url.js b/packages/tests-e2e/support/utils/create-url.js similarity index 100% rename from test/e2e/support/utils/create-url.js rename to packages/tests-e2e/support/utils/create-url.js diff --git a/test/e2e/support/utils/deactivate-plugin.js b/packages/tests-e2e/support/utils/deactivate-plugin.js similarity index 95% rename from test/e2e/support/utils/deactivate-plugin.js rename to packages/tests-e2e/support/utils/deactivate-plugin.js index 43120f20d4034..bfaaf7e0a7eeb 100644 --- a/test/e2e/support/utils/deactivate-plugin.js +++ b/packages/tests-e2e/support/utils/deactivate-plugin.js @@ -1,5 +1,5 @@ /** - * Node dependencies + * Internal dependencies */ import { switchUserToAdmin } from './switch-user-to-admin'; import { switchUserToTest } from './switch-user-to-test'; diff --git a/test/e2e/support/utils/disable-pre-publish-checks.js b/packages/tests-e2e/support/utils/disable-pre-publish-checks.js similarity index 100% rename from test/e2e/support/utils/disable-pre-publish-checks.js rename to packages/tests-e2e/support/utils/disable-pre-publish-checks.js diff --git a/test/e2e/support/utils/enable-page-dialog-accept.js b/packages/tests-e2e/support/utils/enable-page-dialog-accept.js similarity index 100% rename from test/e2e/support/utils/enable-page-dialog-accept.js rename to packages/tests-e2e/support/utils/enable-page-dialog-accept.js diff --git a/test/e2e/support/utils/enable-pre-publish-checks.js b/packages/tests-e2e/support/utils/enable-pre-publish-checks.js similarity index 100% rename from test/e2e/support/utils/enable-pre-publish-checks.js rename to packages/tests-e2e/support/utils/enable-pre-publish-checks.js diff --git a/test/e2e/support/utils/ensure-sidebar-opened.js b/packages/tests-e2e/support/utils/ensure-sidebar-opened.js similarity index 100% rename from test/e2e/support/utils/ensure-sidebar-opened.js rename to packages/tests-e2e/support/utils/ensure-sidebar-opened.js diff --git a/test/e2e/support/utils/find-sidebar-panel-with-title.js b/packages/tests-e2e/support/utils/find-sidebar-panel-with-title.js similarity index 100% rename from test/e2e/support/utils/find-sidebar-panel-with-title.js rename to packages/tests-e2e/support/utils/find-sidebar-panel-with-title.js diff --git a/test/e2e/support/utils/get-all-blocks.js b/packages/tests-e2e/support/utils/get-all-blocks.js similarity index 100% rename from test/e2e/support/utils/get-all-blocks.js rename to packages/tests-e2e/support/utils/get-all-blocks.js diff --git a/test/e2e/support/utils/get-available-block-transforms.js b/packages/tests-e2e/support/utils/get-available-block-transforms.js similarity index 100% rename from test/e2e/support/utils/get-available-block-transforms.js rename to packages/tests-e2e/support/utils/get-available-block-transforms.js diff --git a/test/e2e/support/utils/get-edited-post-content.js b/packages/tests-e2e/support/utils/get-edited-post-content.js similarity index 100% rename from test/e2e/support/utils/get-edited-post-content.js rename to packages/tests-e2e/support/utils/get-edited-post-content.js diff --git a/test/e2e/support/utils/has-block-switcher.js b/packages/tests-e2e/support/utils/has-block-switcher.js similarity index 100% rename from test/e2e/support/utils/has-block-switcher.js rename to packages/tests-e2e/support/utils/has-block-switcher.js diff --git a/test/e2e/support/utils/index.js b/packages/tests-e2e/support/utils/index.js similarity index 100% rename from test/e2e/support/utils/index.js rename to packages/tests-e2e/support/utils/index.js diff --git a/test/e2e/support/utils/insert-block.js b/packages/tests-e2e/support/utils/insert-block.js similarity index 100% rename from test/e2e/support/utils/insert-block.js rename to packages/tests-e2e/support/utils/insert-block.js diff --git a/test/e2e/support/utils/install-plugin.js b/packages/tests-e2e/support/utils/install-plugin.js similarity index 96% rename from test/e2e/support/utils/install-plugin.js rename to packages/tests-e2e/support/utils/install-plugin.js index d83885c619e8a..37a47ae382a24 100644 --- a/test/e2e/support/utils/install-plugin.js +++ b/packages/tests-e2e/support/utils/install-plugin.js @@ -1,5 +1,5 @@ /** - * Node dependencies + * Internal dependencies */ import { switchUserToAdmin } from './switch-user-to-admin'; import { switchUserToTest } from './switch-user-to-test'; diff --git a/test/e2e/support/utils/is-current-url.js b/packages/tests-e2e/support/utils/is-current-url.js similarity index 100% rename from test/e2e/support/utils/is-current-url.js rename to packages/tests-e2e/support/utils/is-current-url.js diff --git a/test/e2e/support/utils/login-user.js b/packages/tests-e2e/support/utils/login-user.js similarity index 100% rename from test/e2e/support/utils/login-user.js rename to packages/tests-e2e/support/utils/login-user.js diff --git a/test/e2e/support/utils/mocks/create-embedding-matcher.js b/packages/tests-e2e/support/utils/mocks/create-embedding-matcher.js similarity index 100% rename from test/e2e/support/utils/mocks/create-embedding-matcher.js rename to packages/tests-e2e/support/utils/mocks/create-embedding-matcher.js diff --git a/test/e2e/support/utils/mocks/create-json-response.js b/packages/tests-e2e/support/utils/mocks/create-json-response.js similarity index 100% rename from test/e2e/support/utils/mocks/create-json-response.js rename to packages/tests-e2e/support/utils/mocks/create-json-response.js diff --git a/test/e2e/support/utils/mocks/create-url-matcher.js b/packages/tests-e2e/support/utils/mocks/create-url-matcher.js similarity index 100% rename from test/e2e/support/utils/mocks/create-url-matcher.js rename to packages/tests-e2e/support/utils/mocks/create-url-matcher.js diff --git a/test/e2e/support/utils/mocks/index.js b/packages/tests-e2e/support/utils/mocks/index.js similarity index 100% rename from test/e2e/support/utils/mocks/index.js rename to packages/tests-e2e/support/utils/mocks/index.js diff --git a/test/e2e/support/utils/mocks/mock-or-transform.js b/packages/tests-e2e/support/utils/mocks/mock-or-transform.js similarity index 100% rename from test/e2e/support/utils/mocks/mock-or-transform.js rename to packages/tests-e2e/support/utils/mocks/mock-or-transform.js diff --git a/test/e2e/support/utils/mocks/set-up-response-mocking.js b/packages/tests-e2e/support/utils/mocks/set-up-response-mocking.js similarity index 100% rename from test/e2e/support/utils/mocks/set-up-response-mocking.js rename to packages/tests-e2e/support/utils/mocks/set-up-response-mocking.js diff --git a/test/e2e/support/utils/observe-focus-loss.js b/packages/tests-e2e/support/utils/observe-focus-loss.js similarity index 100% rename from test/e2e/support/utils/observe-focus-loss.js rename to packages/tests-e2e/support/utils/observe-focus-loss.js diff --git a/test/e2e/support/utils/open-document-settings-sidebar.js b/packages/tests-e2e/support/utils/open-document-settings-sidebar.js similarity index 100% rename from test/e2e/support/utils/open-document-settings-sidebar.js rename to packages/tests-e2e/support/utils/open-document-settings-sidebar.js diff --git a/test/e2e/support/utils/open-publish-panel.js b/packages/tests-e2e/support/utils/open-publish-panel.js similarity index 100% rename from test/e2e/support/utils/open-publish-panel.js rename to packages/tests-e2e/support/utils/open-publish-panel.js diff --git a/test/e2e/support/utils/press-key-times.js b/packages/tests-e2e/support/utils/press-key-times.js similarity index 100% rename from test/e2e/support/utils/press-key-times.js rename to packages/tests-e2e/support/utils/press-key-times.js diff --git a/test/e2e/support/utils/press-key-with-modifier.js b/packages/tests-e2e/support/utils/press-key-with-modifier.js similarity index 100% rename from test/e2e/support/utils/press-key-with-modifier.js rename to packages/tests-e2e/support/utils/press-key-with-modifier.js diff --git a/test/e2e/support/utils/publish-post-with-pre-publish-checks-disabled.js b/packages/tests-e2e/support/utils/publish-post-with-pre-publish-checks-disabled.js similarity index 100% rename from test/e2e/support/utils/publish-post-with-pre-publish-checks-disabled.js rename to packages/tests-e2e/support/utils/publish-post-with-pre-publish-checks-disabled.js diff --git a/test/e2e/support/utils/publish-post.js b/packages/tests-e2e/support/utils/publish-post.js similarity index 100% rename from test/e2e/support/utils/publish-post.js rename to packages/tests-e2e/support/utils/publish-post.js diff --git a/test/e2e/support/utils/save-draft.js b/packages/tests-e2e/support/utils/save-draft.js similarity index 100% rename from test/e2e/support/utils/save-draft.js rename to packages/tests-e2e/support/utils/save-draft.js diff --git a/test/e2e/support/utils/search-for-block.js b/packages/tests-e2e/support/utils/search-for-block.js similarity index 100% rename from test/e2e/support/utils/search-for-block.js rename to packages/tests-e2e/support/utils/search-for-block.js diff --git a/test/e2e/support/utils/select-block-by-client-id.js b/packages/tests-e2e/support/utils/select-block-by-client-id.js similarity index 100% rename from test/e2e/support/utils/select-block-by-client-id.js rename to packages/tests-e2e/support/utils/select-block-by-client-id.js diff --git a/test/e2e/support/utils/set-browser-viewport.js b/packages/tests-e2e/support/utils/set-browser-viewport.js similarity index 100% rename from test/e2e/support/utils/set-browser-viewport.js rename to packages/tests-e2e/support/utils/set-browser-viewport.js diff --git a/test/e2e/support/utils/set-post-content.js b/packages/tests-e2e/support/utils/set-post-content.js similarity index 100% rename from test/e2e/support/utils/set-post-content.js rename to packages/tests-e2e/support/utils/set-post-content.js diff --git a/test/e2e/support/utils/shared/config.js b/packages/tests-e2e/support/utils/shared/config.js similarity index 100% rename from test/e2e/support/utils/shared/config.js rename to packages/tests-e2e/support/utils/shared/config.js diff --git a/test/e2e/support/utils/shared/get-json-response.js b/packages/tests-e2e/support/utils/shared/get-json-response.js similarity index 100% rename from test/e2e/support/utils/shared/get-json-response.js rename to packages/tests-e2e/support/utils/shared/get-json-response.js diff --git a/test/e2e/support/utils/switch-editor-mode-to.js b/packages/tests-e2e/support/utils/switch-editor-mode-to.js similarity index 86% rename from test/e2e/support/utils/switch-editor-mode-to.js rename to packages/tests-e2e/support/utils/switch-editor-mode-to.js index 64d08364eb430..6978b2ee81247 100644 --- a/test/e2e/support/utils/switch-editor-mode-to.js +++ b/packages/tests-e2e/support/utils/switch-editor-mode-to.js @@ -1,6 +1,7 @@ /** * Switches editor mode. -* @param {string} mode String editor mode. + * + * @param {string} mode String editor mode. */ export async function switchEditorModeTo( mode ) { await page.click( diff --git a/test/e2e/support/utils/switch-user-to-admin.js b/packages/tests-e2e/support/utils/switch-user-to-admin.js similarity index 100% rename from test/e2e/support/utils/switch-user-to-admin.js rename to packages/tests-e2e/support/utils/switch-user-to-admin.js diff --git a/test/e2e/support/utils/switch-user-to-test.js b/packages/tests-e2e/support/utils/switch-user-to-test.js similarity index 100% rename from test/e2e/support/utils/switch-user-to-test.js rename to packages/tests-e2e/support/utils/switch-user-to-test.js diff --git a/test/e2e/support/utils/toggle-screen-option.js b/packages/tests-e2e/support/utils/toggle-screen-option.js similarity index 100% rename from test/e2e/support/utils/toggle-screen-option.js rename to packages/tests-e2e/support/utils/toggle-screen-option.js diff --git a/test/e2e/support/utils/transform-block-to.js b/packages/tests-e2e/support/utils/transform-block-to.js similarity index 100% rename from test/e2e/support/utils/transform-block-to.js rename to packages/tests-e2e/support/utils/transform-block-to.js diff --git a/test/e2e/support/utils/uninstall-plugin.js b/packages/tests-e2e/support/utils/uninstall-plugin.js similarity index 96% rename from test/e2e/support/utils/uninstall-plugin.js rename to packages/tests-e2e/support/utils/uninstall-plugin.js index aca1b35652c01..bde3828a21040 100644 --- a/test/e2e/support/utils/uninstall-plugin.js +++ b/packages/tests-e2e/support/utils/uninstall-plugin.js @@ -1,5 +1,5 @@ /** - * Node dependencies + * Internal dependencies */ import { switchUserToAdmin } from './switch-user-to-admin'; import { switchUserToTest } from './switch-user-to-test'; diff --git a/test/e2e/support/utils/visit-admin-page.js b/packages/tests-e2e/support/utils/visit-admin-page.js similarity index 100% rename from test/e2e/support/utils/visit-admin-page.js rename to packages/tests-e2e/support/utils/visit-admin-page.js diff --git a/test/e2e/support/utils/wait-for-window-dimensions.js b/packages/tests-e2e/support/utils/wait-for-window-dimensions.js similarity index 100% rename from test/e2e/support/utils/wait-for-window-dimensions.js rename to packages/tests-e2e/support/utils/wait-for-window-dimensions.js diff --git a/test/unit/jest.config.json b/test/unit/jest.config.json index ccd22b33408f3..90efca5890533 100644 --- a/test/unit/jest.config.json +++ b/test/unit/jest.config.json @@ -11,7 +11,7 @@ "testURL": "http://localhost", "testPathIgnorePatterns": [ "/node_modules/", - "/test/e2e", + "/packages/tests-e2e", "<rootDir>/.*/build/", "<rootDir>/.*/build-module/" ], From ba327ac2e5a325c43b2c594f84b321ddb33a09b0 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Mon, 7 Jan 2019 13:16:04 +0100 Subject: [PATCH 109/691] Update the Gutenberg Release Docs (#13222) --- docs/contributors/release.md | 198 ++++++++++++++++++----------------- 1 file changed, 100 insertions(+), 98 deletions(-) diff --git a/docs/contributors/release.md b/docs/contributors/release.md index e30b0c5c0e0c3..6aa69cb4f9906 100644 --- a/docs/contributors/release.md +++ b/docs/contributors/release.md @@ -1,162 +1,142 @@ # Gutenberg Release Process -This document serves as a checklist for building and releasing a new version of Gutenberg. It documents our release process, but it is helpful if you'd like to understand how Gutenberg ships release candidates and new versions. +This Repository is used to perform several types of releases. This document serves as a checklist for each one of these. It is helpful if you'd like to understand the different workflows. To release Gutenberg, you need commit access to the [WordPress.org plugin repository]. 🙂 -## Gutenberg Versioning +## Plugin Releases -Gutenberg follows semantic versioning; we release a new major version approximately every two weeks. The current and next versions are [tracked in GitHub milestones](https://github.com/WordPress/gutenberg/milestones), along with each version's tagging date. The date in the milestone is the date of **tagging the release candidate**, not the final version that will be published to the [plugin repository]. +### Versioning -Up until version `4.0.0`, "point releases"/minor releases (eg. `3.8` > `3.9`) effectively constituted a major version bump. +We release a new major version approximately every two weeks. The current and next versions are [tracked in GitHub milestones](https://github.com/WordPress/gutenberg/milestones), along with each version's tagging date. -Our [deprecation policy](./deprecated.md) maintains deprecated APIs/functions for two major versions, whenever possible. +### Release Candidates -# Release Candidates +On the date of the current milestone, we publish a release candidate and make it available for plugin authors and users to test. If any regressions are found with a release candidate, a new release candidate can be published. -Since [`3.8`](https://github.com/WordPress/gutenberg/releases/tag/v3.8.0), we [publish a release candidate](https://github.com/WordPress/gutenberg/releases/tag/v3.8.0-rc.1) at least one week before we publish a new version. This release candidate is available for plugin authors and for users to test. If any bugs/regressions are found with a release candidate, they should be cherry-picked into a new release candidate and it should be published. + +The date in the milestone is the date of **tagging the release candidate**. On this date, all remaining PRs on the milestone are moved automatically to the next release. Release candidates should be versioned incrementally, starting with `-rc.1`, then `-rc.2`, and so on. -## Creating a Release Candidate +#### Creating the first Release Candidate -Creating a release candidate involves: +Releasing the first release candidate for this milestone (`x.x`) involves: 1. writing a release blog post and changelog -2. bumping the version -3. tagging the release +2. creating the release branch +3. bumping the version and tagging the release 4. building the plugin -5. publishing the new release to GitHub -6. committing to the [plugin repository] +5. publishing the release to GitHub +6. publishing the call for testing -### Writing the Release Post and Changelog +##### Writing the Release Post and Changelog 1. Open the [list of closed pull requests](https://github.com/WordPress/gutenberg/pulls?utf8=✓&q=is%3Apr+is%3Aclosed+sort%3Acreated-desc+) and filter by the current milestone. 2. Read through each PR to determine if it needs to be included in the blog post and/or changelog. 3. Choose a few features to highlight in the release post; record an animation of them in use. 4. Save the draft post on [make.wordpress.org/core](https://make.wordpress.org/core/); this post should be published after the actual release. -### Bumping the Version +##### Creating the Release Branch -1. Create [a pull request like this](https://github.com/WordPress/gutenberg/pull/9663), bumping the version number in `gutenberg.php`, `package.json`, and `package-lock.json`. -2. Check that there's no work-in-progress that's just about to land. [Inform committers in `#core-editor` on Slack](https://wordpress.slack.com/messages/C02QB2JS7) to hold off on merging any changes until after the release candidate is tagged. -3. Merge the version bump pull request. +For each milestone (let's assume it's `x.x` here), a release branch is used to release all RCs and minor releases. For the first RC of the milestone, a release branch is created from master. -### Tag the Release +``` +git checkout master +git checkout -b release/x.x +git push origin release/x.x +``` -1. [Create a new release on GitHub](https://github.com/WordPress/gutenberg/releases/new). -2. If you were releasing the `5.0.0` release candidate, label it `v5.0.0-rc.1`. -3. The GitHub release screen should look like this: -[![GitHub Release Screenshot](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/reference/release-screenshot.png)](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/reference/release-screenshot.png) -4. Creative emojis related to a key feature in this release are encouraged. Emojis are fun! -5. Publish the release. +##### Bumping the Version and Tagging the Release -### Build the Plugin +1. Checkout the `release/x.x` branch. +2. Create [a commit like this](https://github.com/WordPress/gutenberg/pull/13125/commits/13fa651dadc2472abb9b95f80db9d5f23e63ae9c), bumping the version number in `gutenberg.php`, `package.json`, and `package-lock.json` to `x.x.0-rc.1`. +3. Create a Pull Request from the release branch into `master` using the changelog as a description and ensure the tests pass properly. +4. Tag the RC version. `git tag vx.x.0-rc.1` from the release branch. +5. Push the tag `git push --tags`. +6. Merge the version bump pull request and avoid removing the release branch. + +##### Build the Plugin 1. Run `git fetch --tags`. -2. Check out the tag for this release; it will be the tag you created on the GitHub release page; assuming version `5.0.0`, you should run `git checkout v5.0.0-rc.1`. +2. Check out the tag for this release, you should run `git checkout vx.x.0-rc.1`. 3. Run `./bin/build-plugin-zip.sh` from the root of project. This packages a zip file with a release build of `gutenberg.zip`. -### Publish the Release on GitHub +##### Publish the Release on GitHub -1. Upload the a `gutenberg.zip` file to the [GitHub Releases page](https://github.com/WordPress/gutenberg/releases) -2. Post a link to the release page to the [`#core-editor` channel](https://wordpress.slack.com/messages/C02QB2JS7). +1. [Create a new release on GitHub](https://github.com/WordPress/gutenberg/releases/new). +2. If you were releasing the `x.x.0-rc.1` release candidate, label it `x.x.0-rc.1` and use the `vx.x.x-rc.1` as a tag. +3. Upload the a `gutenberg.zip` file into the release. +4. Use the changelog as a description of the release. +5. Publish the release. -Here's an example [release candidate page](https://github.com/WordPress/gutenberg/releases/tag/v3.8.0-rc.1); yours should look like that when you're finished. +Here's an example [release candidate page](https://github.com/WordPress/gutenberg/releases/tag/v4.6.0-rc.1); yours should look like that when you're finished. -### Commit to the Plugin Repository +##### Publishing the Call For Testing -You'll need to use Subversion for this step. +Ping someone from the `[#core-test](https://wordpress.slack.com/messages/C03B0H5J0)` team to publish a call for testing post. Here's an [example call for testing post.](https://make.wordpress.org/test/2019/01/04/call-for-testing-gutenberg-4-8/) -1. Do an SVN checkout of `https://wordpress.org/plugins/gutenberg/`: - * If this is your first checkout, run: `svn checkout https://plugins.svn.wordpress.org/gutenberg` - * If you already have a copy, run: `svn up` -2. Delete the contents of `trunk` except for the `readme.txt` and `changelog.txt` files (these files don’t exist in the `git` repo, only in Subversion). -3. Extract the contents of the zip file to `trunk`. -4. Edit `readme.txt`, replacing the changelog for the previous version with the current release's changelog. -5. Add the changelog for the current release to `changelog.txt`. -6. Add new files/remove deleted files from the repository: -```bash -# Add new files: -svn st | grep '^\?' | awk '{print $2}' | xargs svn add -# Delete old files: -svn st | grep '^!' | awk '{print $2}' | xargs svn rm -``` -7. Commit the new version to `trunk`: -```bash -# Replace vX.X.X-rc.1 with your version: -svn ci -m "Committing Gutenberg version vX.X.X-rc.1" -``` +#### Creating Release Candidate Patches (done via `git cherry-pick`) -## Creating Release Candidate Patches (done via `git cherry-pick`) +If a bug is found in a release candidate and a fix is committed to `master`, we should include that fix in a new release candidate. To do this you'll need to use `git cherry-pick` to add these changes to the milestone's release branch. This way only fixes are added to the release candidate and not all the new code that has landed on `master` since tagging: -If a bug is found in a release candidate and a fix is committed to `master`, we should include that fix in a new release candidate. To do this you'll need to use `git cherry-pick`. This way only fixes are added to the release candidate and not all the new code that has landed on `master` since tagging: - -1. Create a pull request against master that only bumps the release candidate number in `gutenberg.php`, `package.json`, and `package-lock.json`. If your current release candidate is `v3.8.0-rc.1`, you would change the version number to `v3.8.0-rc.2` -2. After this pull request is merged into master, note its commit SHA. -3. Run `git fetch --tags` to load the tags. -4. Check out the latest release candidate (for example: `git checkout v3.8.0-rc.1`). -5. Cherry-pick fix commits (in chronological order) with `git cherry-pick [SHA]`. -6. Cherry-pick the version bump commit you noted in step 2. -7. Tag this release by incrementing its `rc.X` number and push it to GitHub: -```bash -git tag v3.8.0-rc.2 -git push origin v3.8.0-rc.2 -``` -8. Follow the steps in [tag the release](#tag-the-release), [build the plugin](#build-the-plugin), [publish the release on GitHub](#publish-the-release-on-github), and [commit to the plugin repository](#commit-to-the-plugin-repository). You can copy the existing changelog from the previous release candidate. +1. Checkout the corresponding release branch with: `git checkout release/x.x`. +2. Cherry-pick fix commits (in chronological order) with `git cherry-pick [SHA]`. +3. Create [a commit like this](https://github.com/WordPress/gutenberg/pull/13125/commits/13fa651dadc2472abb9b95f80db9d5f23e63ae9c), bumping the version number in `gutenberg.php`, `package.json`, and `package-lock.json` to `x.x.0-rc.2`. +4. Create a Pull Request from the release branch into `master` using the changelog as a description and ensure the tests pass properly. +5. Tag the RC version. `git tag vx.x.0-rc.2` from the release branch. +6. Push the tag `git push --tags`. +7. Merge the version bump pull request and avoid removing the release branch. +8. Follow the steps in [build the plugin](#build-the-plugin) and [publish the release on GitHub](#publish-the-release-on-github). -It's worth mentioning that a new release candidate has been released in the [`#core-editor` channel](https://wordpress.slack.com/messages/C02QB2JS7). +You can copy the existing changelog from the previous release candidate. Let other contributors know that a new release candidate has been released in the [`#core-editor` channel](https://wordpress.slack.com/messages/C02QB2JS7) and the call for testing post. -# Official Gutenberg Releases™ +### Official Gutenberg Releases™ -The process of releasing Gutenberg is similar to creating a release candidate, except we don't use the `-rc.X` in the `git` tag and we publish a new branch in the subversion repository. This updates the version available in the WordPress plugin repository and will cause WordPress sites around the world to prompt users to update to this new version. The steps below are very similar to the ones above, for a release candidate, but they're spelled out here in their entirely to reduce confusion. 😅 +The process of releasing Gutenberg is similar to creating a release candidate, except we don't use the `-rc.X` in the `git` tag and we publish a new branch in the subversion repository. This updates the version available in the WordPress plugin repository and will cause WordPress sites around the world to prompt users to update to this new version. -## Creating a Release +#### Creating a Release Creating a release involves: 1. verifying the release blog post and changelog 2. bumping the version -4. building the plugin -5. publishing the new release to GitHub -6. committing to the [plugin repository] -7. publishing the release blog post +3. building the plugin +4. publishing the new release to GitHub +5. committing to the [plugin repository] +6. publishing the release blog post -### Verifying the Release Post and Changelog +##### Verifying the Release Post and Changelog 1. Check the draft post on [make.wordpress.org/core](https://make.wordpress.org/core/); make sure the changelog reflects what's shipping in the release. -### Bumping the Version +##### Bumping the Version -1. Create [a pull request with a commit like this](https://github.com/WordPress/gutenberg/commit/00d01049685f11f9bb721ad3437cb928814ab2a2#diff-b9cfc7f2cdf78a7f4b91a753d10865a2), removing the `-rc.X` from the version number in `gutenberg.php`, `package.json`, and `package-lock.json`. -2. Merge the version bump pull request. (If you want to live dangerously, you can push the version bump directly to `master`. 😎) -3. Note the commit SHA of the version bump. -4. Cherry-pick the version bump commit into your release candidate branch: -```bash -# Assuming you're releasing version v5.0.0, based on v5.0.0-rc.1. -git fetch --tags -git checkout v.5.0.0-rc.1 -git cherry-pick [VERSION-BUMP-COMMIT-SHA] -git tag v5.0.0 -git push origin v5.0.0 -``` +1. Checkout the release branch `git checkout release/x.x`. + +**Note:** This branch should never be removed or rebased. This means in case of conflicts when creating PRs from this branch, create temporary branches in order to merge these PRs and avoid touching the release branch. + +2. Create [a commit like this](https://github.com/WordPress/gutenberg/commit/00d01049685f11f9bb721ad3437cb928814ab2a2#diff-b9cfc7f2cdf78a7f4b91a753d10865a2), removing the `-rc.X` from the version number in `gutenberg.php`, `package.json`, and `package-lock.json`. +3. Create a Pull Request from the release branch into `master` using the changelog as a description and ensure the tests pass properly. +4. Tag the version. `git tag vx.x.0` from the release branch. +5. Push the tag `git push --tags`. +6. Merge the version bump pull request and avoid removing the release branch. -### Build the Plugin +##### Build the Plugin 1. Run `git fetch --tags`. -2. Check out the tag for this release; it will be the tag you created on the GitHub release page; assuming version `5.0.0`, you should run `git checkout v5.0.0-rc.1`. +2. Check out the tag for this release, you should run `git checkout vx.x.0`. 3. Run `./bin/build-plugin-zip.sh` from the root of project. This packages a zip file with a release build of `gutenberg.zip`. -### Publish the Release on GitHub +##### Publish the Release on GitHub 1. [Create a new release on GitHub](https://github.com/WordPress/gutenberg/releases/new). -2. If you are releasing `5.0.0`, target the tag `v5.0.0`. -3. Upload the `gutenberg.zip` file -4. Post a link to the release page to the [`#core-editor` channel](https://wordpress.slack.com/messages/C02QB2JS7). - -Here's an example [release page](https://github.com/WordPress/gutenberg/releases/tag/v3.8.0); yours should look like that when you're finished. +2. If you were releasing the `x.x.0` release candidate, label it `x.x.0` and use the `vx.x.x` as a tag. +3. Upload the a `gutenberg.zip` file into the release. +4. Use the changelog as a description of the release. +5. Publish the release. -### Commit to the Plugin Repository +##### Commit to the Plugin Repository You'll need to use Subversion to publish the plugin to WordPress.org. @@ -200,6 +180,28 @@ You should check that folks are able to install the new version from their Dashb If you don't have access to [make.wordpress.org/core](https://make.wordpress.org/core/), ping [someone on the Gutenberg Core team](https://github.com/orgs/WordPress/teams/gutenberg-core) in the [WordPress #core-editor Slack channel](https://wordpress.slack.com/messages/C02QB2JS7) to publish the post. +## Packages Releases and WordPress Core Updates + +WordPress Core Updates are based on the `g-minor` branch. Releasing packages in order to update WordPress Core involves updating the `g-minor` branch (the workflow depends on whether it's a minor or major WordPress release) and run the package release process. + +### Major WordPress Releases + +For major WordPress releases, the last Gutenberg plugin release is merged into `g-minor`. This involves the following steps: + +1. Checkout the last published Gutenberg's release branch `git checkout release/x.x` +2. Create a Pull Request from this branch into `g-minor`. +3. Merge the branch. + +### Minor WordPress Releases + +For minor releases, the critical fixes targetted for this WordPress Minor release should be cherry-picked into the `g-minor` branch one by one in their chronological order. + +### Releasing the WordPress packages + +1. Checkout the `g-minor` branch. +2. Run [the package release process](https://github.com/WordPress/gutenberg/blob/master/CONTRIBUTING.md#releasing-packages) +3. Update the `CHANGELOG` files of the published packages with the new released versions and commit to the `g-minor` branch. + --------- Ta-da! 🎉 From 68f135bd844c673f1b032a4c9b26238bb5f0eb7b Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Mon, 7 Jan 2019 08:16:13 -0500 Subject: [PATCH 110/691] Revert "Build: Change package build step to async flow (#8093)" (#13195) This reverts commit 4928f16b7ebdba334e9c6bcd7e0a03c50ad3bde6. --- bin/packages/build.js | 107 ++++++++++++++++++------------------------ package-lock.json | 16 +++++++ package.json | 1 + 3 files changed, 62 insertions(+), 62 deletions(-) diff --git a/bin/packages/build.js b/bin/packages/build.js index cc0bcf5f76ae6..02009c1e4a4d3 100755 --- a/bin/packages/build.js +++ b/bin/packages/build.js @@ -8,15 +8,15 @@ /** * External dependencies */ -const { promisify } = require( 'util' ); const fs = require( 'fs' ); const path = require( 'path' ); -let glob = require( 'glob' ); +const glob = require( 'glob' ); const babel = require( '@babel/core' ); const chalk = require( 'chalk' ); -let mkdirp = require( 'mkdirp' ); +const mkdirp = require( 'mkdirp' ); const sass = require( 'node-sass' ); const postcss = require( 'postcss' ); +const deasync = require( 'deasync' ); /** * Internal dependencies @@ -36,14 +36,6 @@ const BUILD_DIR = { }; const DONE = chalk.reset.inverse.bold.green( ' DONE ' ); -// Promisification -const readFile = promisify( fs.readFile ); -const writeFile = promisify( fs.writeFile ); -const transformFile = promisify( babel.transformFile ); -const renderSass = promisify( sass.render ); -glob = promisify( glob ); -mkdirp = promisify( mkdirp ); - /** * Get the package name for a specified file * @@ -81,8 +73,6 @@ function getBuildPath( file, buildFolder ) { * Given a list of scss and js filepaths, divide them into sets them and rebuild. * * @param {Array} files list of files to rebuild - * - * @return {Promise} Promise resolving when files are built. */ function buildFiles( files ) { // Reduce files into a unique sets of javaScript files and scss packages. @@ -97,10 +87,8 @@ function buildFiles( files ) { return accumulator; }, { jsFiles: new Set(), scssPackagePaths: new Set() } ); - return Promise.all( [ - ...buildPaths.jsFiles.map( buildJsFile ), - ...buildPaths.scssPackagePaths.map( buildPackageScss ), - ] ); + buildPaths.jsFiles.forEach( buildJsFile ); + buildPaths.scssPackagePaths.forEach( buildPackageScss ); } /** @@ -108,39 +96,30 @@ function buildFiles( files ) { * * @param {string} file File path to build * @param {boolean} silent Show logs - * - * @return {Promise} Promise resolving when file is built. */ function buildJsFile( file, silent ) { - return Promise.all( [ - buildJsFileFor( file, silent, 'main' ), - buildJsFileFor( file, silent, 'module' ), - ] ); + buildJsFileFor( file, silent, 'main' ); + buildJsFileFor( file, silent, 'module' ); } /** * Build a package's scss styles * * @param {string} packagePath The path to the package. - * - * @return {Promise} Promise resolving when file is built. */ -async function buildPackageScss( packagePath ) { +function buildPackageScss( packagePath ) { const srcDir = path.resolve( packagePath, SRC_DIR ); - const scssFiles = await glob( `${ srcDir }/*.scss` ); + const scssFiles = glob.sync( `${ srcDir }/*.scss` ); // Build scss files individually. - return Promise.all( scssFiles.map( buildScssFile ) ); + scssFiles.forEach( buildScssFile ); } -async function buildScssFile( styleFile ) { +function buildScssFile( styleFile ) { const outputFile = getBuildPath( styleFile.replace( '.scss', '.css' ), BUILD_DIR.style ); const outputFileRTL = getBuildPath( styleFile.replace( '.scss', '-rtl.css' ), BUILD_DIR.style ); - - await mkdirp( path.dirname( outputFile ) ); - - const contents = await readFile( styleFile, 'utf8' ); - const builtSass = await renderSass( { + mkdirp.sync( path.dirname( outputFile ) ); + const builtSass = sass.renderSync( { file: styleFile, includePaths: [ path.resolve( __dirname, '../../assets/stylesheets' ) ], data: ( @@ -152,22 +131,27 @@ async function buildScssFile( styleFile ) { 'animations', 'z-index', ].map( ( imported ) => `@import "${ imported }";` ).join( ' ' ) + - contents + fs.readFileSync( styleFile, 'utf8' ) ), } ); - const result = await postcss( require( './post-css-config' ) ).process( builtSass.css, { - from: 'src/app.css', - to: 'dest/app.css', - } ); + const postCSSSync = ( callback ) => { + postcss( require( './post-css-config' ) ) + .process( builtSass.css, { from: 'src/app.css', to: 'dest/app.css' } ) + .then( ( result ) => callback( null, result ) ); + }; - const resultRTL = await postcss( [ require( 'rtlcss' )() ] ).process( result.css, { - from: 'src/app.css', - to: 'dest/app.css', - } ); + const postCSSRTLSync = ( ltrCSS, callback ) => { + postcss( [ require( 'rtlcss' )() ] ) + .process( ltrCSS, { from: 'src/app.css', to: 'dest/app.css' } ) + .then( ( result ) => callback( null, result ) ); + }; + + const result = deasync( postCSSSync )(); + fs.writeFileSync( outputFile, result.css ); - await writeFile( outputFile, result.css ); - await writeFile( outputFileRTL, resultRTL.css ); + const resultRTL = deasync( postCSSRTLSync )( result ); + fs.writeFileSync( outputFileRTL, resultRTL ); } /** @@ -177,17 +161,17 @@ async function buildScssFile( styleFile ) { * @param {boolean} silent Show logs * @param {string} environment Dist environment (node or es5) */ -async function buildJsFileFor( file, silent, environment ) { +function buildJsFileFor( file, silent, environment ) { const buildDir = BUILD_DIR[ environment ]; const destPath = getBuildPath( file, buildDir ); const babelOptions = getBabelConfig( environment ); babelOptions.sourceMaps = true; babelOptions.sourceFileName = file; - await mkdirp( path.dirname( destPath ) ); - const transformed = await transformFile( file, babelOptions ); - writeFile( destPath + '.map', JSON.stringify( transformed.map ) ); - writeFile( destPath, transformed.code + '\n//# sourceMappingURL=' + path.basename( destPath ) + '.map' ); + mkdirp.sync( path.dirname( destPath ) ); + const transformed = babel.transformFileSync( file, babelOptions ); + fs.writeFileSync( destPath + '.map', JSON.stringify( transformed.map ) ); + fs.writeFileSync( destPath, transformed.code + '\n//# sourceMappingURL=' + path.basename( destPath ) + '.map' ); if ( ! silent ) { process.stdout.write( @@ -205,9 +189,9 @@ async function buildJsFileFor( file, silent, environment ) { * * @param {string} packagePath absolute package path */ -async function buildPackage( packagePath ) { +function buildPackage( packagePath ) { const srcDir = path.resolve( packagePath, SRC_DIR ); - const jsFiles = await glob( `${ srcDir }/**/*.js`, { + const jsFiles = glob.sync( `${ srcDir }/**/*.js`, { ignore: [ `${ srcDir }/**/test/**/*.js`, `${ srcDir }/**/__mocks__/**/*.js`, @@ -215,15 +199,14 @@ async function buildPackage( packagePath ) { nodir: true, } ); - await Promise.all( [ - // Build js files individually. - ...jsFiles.map( ( file ) => buildJsFile( file, true ) ), + process.stdout.write( `${ path.basename( packagePath ) }\n` ); + + // Build js files individually. + jsFiles.forEach( ( file ) => buildJsFile( file, true ) ); - // Build package CSS files - buildPackageScss( packagePath ), - ] ); + // Build package CSS files + buildPackageScss( packagePath ); - process.stdout.write( `${ path.basename( packagePath ) }\n` ); process.stdout.write( `${ DONE }\n` ); } @@ -233,7 +216,7 @@ if ( files.length ) { buildFiles( files ); } else { process.stdout.write( chalk.inverse( '>> Building packages \n' ) ); - Promise.all( getPackages().map( buildPackage ) ).then( () => { - process.stdout.write( '\n' ); - } ); + getPackages() + .forEach( buildPackage ); + process.stdout.write( '\n' ); } diff --git a/package-lock.json b/package-lock.json index 8d73b5d4671a2..55c478e4e3279 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4556,6 +4556,12 @@ "integrity": "sha512-XBaoWE9RW8pPdPQNibZsW2zh8TW6gcarXp1FZPwT8Uop8ScSNldJEWf2k9l3HeTqdrEwsOsFcq74RiJECW34yA==", "dev": true }, + "bindings": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz", + "integrity": "sha1-FK1hE4EtLTfXLme0ystLtyZQXxE=", + "dev": true + }, "block-stream": { "version": "0.0.9", "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", @@ -6755,6 +6761,16 @@ "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", "dev": true }, + "deasync": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/deasync/-/deasync-0.1.13.tgz", + "integrity": "sha512-/6ngYM7AapueqLtvOzjv9+11N2fHDSrkxeMF1YPE20WIfaaawiBg+HZH1E5lHrcJxlKR42t6XPOEmMmqcAsU1g==", + "dev": true, + "requires": { + "bindings": "~1.2.1", + "nan": "^2.0.7" + } + }, "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", diff --git a/package.json b/package.json index a7355d5faadbe..012664ba79945 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "cross-env": "3.2.4", "cssnano": "4.0.3", "enzyme": "3.7.0", + "deasync": "0.1.13", "deep-freeze": "0.0.1", "doctrine": "2.1.0", "eslint-plugin-jest": "21.5.0", From 4fe36ccb4a32c789e8cbaf8014c0309c161e2ffd Mon Sep 17 00:00:00 2001 From: Mukesh Panchal <mukeshpanchal27@users.noreply.github.com> Date: Mon, 7 Jan 2019 21:06:43 +0530 Subject: [PATCH 111/691] Added missing "Code is Poetry" block in some packages (#13101) * docs: added correct name for font site 50 * Remove old changes * Added missing "Code is Poetry" block in some packages documents * Revert "Remove old changes" This reverts commit c7622d9e7e12feb3df2767f495ef72df2dda69cb. * Revert "docs: added correct name for font site 50" This reverts commit b30c96a11763dfc5a09677178ef9460d98ddeb2f. * Fix display issue in wordcount package * Fix end tag for js block --- packages/annotations/README.md | 2 ++ packages/babel-plugin-makepot/README.md | 2 ++ packages/data/README.md | 2 ++ packages/editor/README.md | 2 ++ packages/nux/README.md | 2 ++ packages/plugins/README.md | 2 ++ packages/viewport/README.md | 2 ++ packages/wordcount/README.md | 3 +++ 8 files changed, 17 insertions(+) diff --git a/packages/annotations/README.md b/packages/annotations/README.md index a1585de3106cb..1c20f8956e587 100644 --- a/packages/annotations/README.md +++ b/packages/annotations/README.md @@ -13,3 +13,5 @@ npm install @wordpress/annotations --save _This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. Learn more about it in [Babel docs](https://babeljs.io/docs/en/next/caveats)._ ## Usage + +<br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/babel-plugin-makepot/README.md b/packages/babel-plugin-makepot/README.md index 06e8ceaf341d5..9f43abf09a00e 100644 --- a/packages/babel-plugin-makepot/README.md +++ b/packages/babel-plugin-makepot/README.md @@ -15,3 +15,5 @@ Install the module: ```bash npm install @wordpress/babel-plugin-makepot --save-dev ``` + +<br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/data/README.md b/packages/data/README.md index 630d30ef9df6c..8e3ef328516ec 100644 --- a/packages/data/README.md +++ b/packages/data/README.md @@ -416,3 +416,5 @@ Specific implementation differences from Redux and React Redux: - In `@wordpress/data`, a `withSelect` mapping function can return `undefined` if it has no props to inject. - In React Redux, the `mapDispatchToProps` argument can be defined as an object or a function. - In `@wordpress/data`, the `withDispatch` higher-order component creator must be passed a function. + +<br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/editor/README.md b/packages/editor/README.md index 0b908871df924..ca608c4d579d1 100644 --- a/packages/editor/README.md +++ b/packages/editor/README.md @@ -120,3 +120,5 @@ Example: window.wp.element ); ``` + +<br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/nux/README.md b/packages/nux/README.md index 7cf0ce1d4bd07..f946c7ec85e38 100644 --- a/packages/nux/README.md +++ b/packages/nux/README.md @@ -100,3 +100,5 @@ console.log( 'Tips in this guide:', guide.tipIds ); console.log( 'Currently showing:', guide.currentTipId ); console.log( 'Next to show:', guide.nextTipId ); ``` + +<br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/plugins/README.md b/packages/plugins/README.md index a4ace1daf8f3c..4c19538984892 100644 --- a/packages/plugins/README.md +++ b/packages/plugins/README.md @@ -162,3 +162,5 @@ const Layout = () => ( ); ``` {% end %} + +<br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/viewport/README.md b/packages/viewport/README.md index 08e2d33fb748f..3431d99b216de 100644 --- a/packages/viewport/README.md +++ b/packages/viewport/README.md @@ -72,3 +72,5 @@ function MyComponent( { isMobile } ) { MyComponent = withViewportMatch( { isMobile: '< small' } )( MyComponent ); ``` + +<br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/wordcount/README.md b/packages/wordcount/README.md index a4366a8b417e4..eb17c4331280f 100644 --- a/packages/wordcount/README.md +++ b/packages/wordcount/README.md @@ -27,3 +27,6 @@ count accepts three parameters: ```JS import { count } from '@wordpress/wordcount'; const numberOfWords = count( 'Words to count', 'words', {} ) +``` + +<br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> From 71be7e60faac03624d37d436e91a258b72457131 Mon Sep 17 00:00:00 2001 From: Zebulan Stanphill <zebulanstanphill@gmail.com> Date: Mon, 7 Jan 2019 12:39:39 -0600 Subject: [PATCH 112/691] Categories block: use align supports flag + wide align support (#13215) --- packages/block-library/src/categories/edit.js | 23 ++++--------------- .../block-library/src/categories/index.js | 11 +-------- 2 files changed, 6 insertions(+), 28 deletions(-) diff --git a/packages/block-library/src/categories/edit.js b/packages/block-library/src/categories/edit.js index 8b23a832018cb..b67eb0982e0bf 100644 --- a/packages/block-library/src/categories/edit.js +++ b/packages/block-library/src/categories/edit.js @@ -6,16 +6,12 @@ import { times, unescape } from 'lodash'; /** * WordPress dependencies */ -import { Component, Fragment } from '@wordpress/element'; import { PanelBody, Placeholder, Spinner, ToggleControl } from '@wordpress/components'; +import { compose, withInstanceId } from '@wordpress/compose'; import { withSelect } from '@wordpress/data'; +import { InspectorControls } from '@wordpress/editor'; +import { Component, Fragment } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { withInstanceId, compose } from '@wordpress/compose'; -import { - InspectorControls, - BlockControls, - BlockAlignmentToolbar, -} from '@wordpress/editor'; class CategoriesEdit extends Component { constructor() { @@ -149,8 +145,8 @@ class CategoriesEdit extends Component { } render() { - const { attributes, setAttributes, isRequesting } = this.props; - const { align, displayAsDropdown, showHierarchy, showPostCounts } = attributes; + const { attributes, isRequesting } = this.props; + const { displayAsDropdown, showHierarchy, showPostCounts } = attributes; const inspectorControls = ( <InspectorControls> @@ -191,15 +187,6 @@ class CategoriesEdit extends Component { return ( <Fragment> { inspectorControls } - <BlockControls> - <BlockAlignmentToolbar - value={ align } - onChange={ ( nextAlign ) => { - setAttributes( { align: nextAlign } ); - } } - controls={ [ 'left', 'center', 'right', 'full' ] } - /> - </BlockControls> <div className={ this.props.className }> { displayAsDropdown ? diff --git a/packages/block-library/src/categories/index.js b/packages/block-library/src/categories/index.js index ab9c198eff461..90e396b433b70 100644 --- a/packages/block-library/src/categories/index.js +++ b/packages/block-library/src/categories/index.js @@ -21,9 +21,6 @@ export const settings = { category: 'widgets', attributes: { - align: { - type: 'string', - }, displayAsDropdown: { type: 'boolean', default: false, @@ -39,16 +36,10 @@ export const settings = { }, supports: { + align: true, html: false, }, - getEditWrapperProps( attributes ) { - const { align } = attributes; - if ( [ 'left', 'center', 'right', 'full' ].includes( align ) ) { - return { 'data-align': align }; - } - }, - edit, save() { From cded0f974461955d2ccffee1d9dcb6985d45abfc Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Tue, 8 Jan 2019 08:39:17 +0100 Subject: [PATCH 113/691] Performance: Optimize the isEditedPostEmpty selector (#13086) * Performance: Optimize the isEditedPostEmpty selector * Change variable name for more clarity * Editor: Add tests for getBlocksForSerialization * Editor: Account for getBlocksForSerialization in optimized isEditedPostEmpty --- packages/editor/src/store/selectors.js | 38 +++-- packages/editor/src/store/test/selectors.js | 171 +++++++++++++++++--- 2 files changed, 177 insertions(+), 32 deletions(-) diff --git a/packages/editor/src/store/selectors.js b/packages/editor/src/store/selectors.js index 06ee816ce1cc0..1f79f47401802 100644 --- a/packages/editor/src/store/selectors.js +++ b/packages/editor/src/store/selectors.js @@ -28,6 +28,7 @@ import { getBlockTypes, hasBlockSupport, hasChildBlocksWithInserterSupport, + getDefaultBlockName, getFreeformContentHandlerName, isUnmodifiedDefaultBlock, } from '@wordpress/blocks'; @@ -454,27 +455,37 @@ export function isEditedPostSaveable( state ) { * @return {boolean} Whether post has content. */ export function isEditedPostEmpty( state ) { - const blocks = getBlocksForSerialization( state ); - // While the condition of truthy content string is sufficient to determine // emptiness, testing saveable blocks length is a trivial operation. Since // this function can be called frequently, optimize for the fast case as a // condition of the mere existence of blocks. Note that the value of edited - // content is used in place of blocks, thus allowed to fall through. - if ( blocks.length && ! ( 'content' in getPostEdits( state ) ) ) { + // content takes precedent over block content, and must fall through to the + // default logic. + const rootClientIds = getBlockOrder( state ); + if ( rootClientIds.length && ! ( 'content' in getPostEdits( state ) ) ) { // Pierce the abstraction of the serializer in knowing that blocks are // joined with with newlines such that even if every individual block // produces an empty save result, the serialized content is non-empty. - if ( blocks.length > 1 ) { + if ( rootClientIds.length > 1 ) { return false; } - // Freeform and unregistered blocks omit comment delimiters in their - // output. The freeform block specifically may produce an empty string - // to save. In the case of a single freeform block, fall through to the - // full serialize. Otherwise, the single block is assumed non-empty by - // virtue of its comment delimiters. - if ( blocks[ 0 ].name !== getFreeformContentHandlerName() ) { + // There are two conditions under which the optimization cannot be + // assumed, and a fallthrough to getEditedPostContent must occur: + // + // 1. getBlocksForSerialization has special treatment in omitting a + // single unmodified default block. + // 2. Comment delimiters are omitted for a freeform or unregistered + // block in its serialization. The freeform block specifically may + // produce an empty string in its saved output. + // + // For all other content, the single block is assumed to make a post + // non-empty, if only by virtue of its own comment delimiters. + const blockName = getBlockName( state, rootClientIds[ 0 ] ); + if ( + blockName !== getDefaultBlockName() && + blockName !== getFreeformContentHandlerName() + ) { return false; } } @@ -1666,6 +1677,11 @@ export function getSuggestedPostFormat( state ) { export function getBlocksForSerialization( state ) { const blocks = getBlocks( state ); + // WARNING: Any changes to the logic of this function should be verified + // against the implementation of isEditedPostEmpty, which bypasses this + // function for performance' sake, in an assumption of this current logic + // being irrelevant to the optimized condition of emptiness. + // A single unmodified default block is assumed to be equivalent to an // empty post. const isSingleUnmodifiedDefaultBlock = ( diff --git a/packages/editor/src/store/test/selectors.js b/packages/editor/src/store/test/selectors.js index cfe66ed4c43c2..6e8f963129dc7 100644 --- a/packages/editor/src/store/test/selectors.js +++ b/packages/editor/src/store/test/selectors.js @@ -10,7 +10,6 @@ import { registerBlockType, unregisterBlockType, createBlock, - getBlockTypes, getDefaultBlockName, setDefaultBlockName, setFreeformContentHandlerName, @@ -89,6 +88,7 @@ const { didPostSaveRequestSucceed, didPostSaveRequestFail, getSuggestedPostFormat, + getBlocksForSerialization, getEditedPostContent, __experimentalGetReusableBlock: getReusableBlock, __experimentalIsSavingReusableBlock: isSavingReusableBlock, @@ -171,7 +171,20 @@ describe( 'selectors', () => { }, } ); + registerBlockType( 'core/test-default', { + category: 'common', + title: 'default', + attributes: { + modified: { + type: 'boolean', + default: false, + }, + }, + save: () => null, + } ); + setFreeformContentHandlerName( 'core/test-freeform' ); + setDefaultBlockName( 'core/test-default' ); cachedSelectors.forEach( ( { clear } ) => clear() ); } ); @@ -182,8 +195,10 @@ describe( 'selectors', () => { unregisterBlockType( 'core/test-block-b' ); unregisterBlockType( 'core/test-block-c' ); unregisterBlockType( 'core/test-freeform' ); + unregisterBlockType( 'core/test-default' ); setFreeformContentHandlerName( undefined ); + setDefaultBlockName( undefined ); } ); describe( 'hasEditorUndo', () => { @@ -1554,6 +1569,41 @@ describe( 'selectors', () => { expect( isEditedPostEmpty( state ) ).toBe( false ); } ); + it( 'should account for filtering logic of getBlocksForSerialization', () => { + // Note: As an optimization, isEditedPostEmpty avoids using the + // getBlocksForSerialization selector and instead makes assumptions + // about its filtering. The behavior should still be reflected. + // + // See: https://github.com/WordPress/gutenberg/pull/13086 + const state = { + editor: { + present: { + blocks: { + byClientId: { + block1: { + clientId: 'block1', + name: 'core/test-default', + }, + }, + attributes: { + block1: { + modified: false, + }, + }, + order: { + '': [ 'block1' ], + }, + }, + edits: {}, + }, + }, + initialEdits: {}, + currentPost: {}, + }; + + expect( isEditedPostEmpty( state ) ).toBe( true ); + } ); + it( 'should return true if blocks, but empty content edit', () => { const state = { editor: { @@ -3995,33 +4045,109 @@ describe( 'selectors', () => { } ); } ); - describe( 'getEditedPostContent', () => { - let originalDefaultBlockName; + describe( 'getBlocksForSerialization', () => { + it( 'should return blocks', () => { + const state = { + editor: { + present: { + blocks: { + byClientId: { + block1: { + clientId: 'block1', + name: 'core/test-default', + }, + block2: { + clientId: 'block2', + name: 'core/heading', + }, + }, + attributes: { + block1: { + modified: false, + }, + block2: {}, + }, + order: { + '': [ 'block1', 'block2' ], + }, + }, + edits: {}, + }, + }, + initialEdits: {}, + currentPost: {}, + }; - beforeAll( () => { - originalDefaultBlockName = getDefaultBlockName(); + expect( getBlocksForSerialization( state ) ).toEqual( [ + { clientId: 'block1', name: 'core/test-default', attributes: { modified: false }, innerBlocks: [] }, + { clientId: 'block2', name: 'core/heading', attributes: {}, innerBlocks: [] }, + ] ); + } ); - registerBlockType( 'core/default', { - category: 'common', - title: 'default', - attributes: { - modified: { - type: 'boolean', - default: false, + it( 'should return an empty set if content is a single unmodified default block', () => { + const state = { + editor: { + present: { + blocks: { + byClientId: { + block1: { + clientId: 'block1', + name: 'core/test-default', + }, + }, + attributes: { + block1: { + modified: false, + }, + }, + order: { + '': [ 'block1' ], + }, + }, + edits: {}, }, }, - save: () => null, - } ); - setDefaultBlockName( 'core/default' ); + initialEdits: {}, + currentPost: {}, + }; + + expect( getBlocksForSerialization( state ) ).toEqual( [] ); } ); - afterAll( () => { - setDefaultBlockName( originalDefaultBlockName ); - getBlockTypes().forEach( ( block ) => { - unregisterBlockType( block.name ); - } ); + it( 'should return a set including a single modified default block', () => { + const state = { + editor: { + present: { + blocks: { + byClientId: { + block1: { + clientId: 'block1', + name: 'core/test-default', + }, + }, + attributes: { + block1: { + modified: true, + }, + }, + order: { + '': [ 'block1' ], + }, + }, + edits: {}, + }, + }, + initialEdits: {}, + currentPost: {}, + }; + + expect( getBlocksForSerialization( state ) ).toEqual( [ + { clientId: 'block1', name: 'core/test-default', attributes: { modified: true }, innerBlocks: [] }, + ] ); } ); + } ); + describe( 'getEditedPostContent', () => { it( 'defers to returning an edited post attribute', () => { const block = createBlock( 'core/block' ); @@ -4205,7 +4331,7 @@ describe( 'selectors', () => { const content = getEditedPostContent( state ); - expect( content ).toBe( '<!-- wp:default {\"modified\":true} /-->' ); + expect( content ).toBe( '<!-- wp:test-default {\"modified\":true} /-->' ); } ); } ); @@ -4643,6 +4769,7 @@ describe( 'selectors', () => { 'core/block/1', 'core/test-block-b', 'core/test-freeform', + 'core/test-default', 'core/test-block-a', ] ); } ); @@ -4701,6 +4828,7 @@ describe( 'selectors', () => { expect( firstBlockFirstCall.map( ( item ) => item.id ) ).toEqual( [ 'core/test-block-b', 'core/test-freeform', + 'core/test-default', 'core/test-block-a', 'core/block/1', 'core/block/2', @@ -4711,6 +4839,7 @@ describe( 'selectors', () => { expect( secondBlockFirstCall.map( ( item ) => item.id ) ).toEqual( [ 'core/test-block-b', 'core/test-freeform', + 'core/test-default', 'core/test-block-a', 'core/block/1', 'core/block/2', From b2051d672db19272622c6a4954518e7e7dab144c Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Tue, 8 Jan 2019 07:36:59 -0800 Subject: [PATCH 114/691] Convert Gutenberg term to edtior, WP, or project (#13090) --- docs/designers-developers/developers/README.md | 8 ++++---- .../developers/backward-compatibility/deprecations.md | 2 +- docs/designers-developers/developers/block-api/README.md | 2 +- docs/designers-developers/developers/filters/README.md | 4 ++-- .../developers/filters/autocomplete-filters.md | 2 +- .../developers/filters/block-filters.md | 2 +- .../developers/filters/editor-filters.md | 2 +- .../developers/internationalization.md | 4 ++-- docs/designers-developers/developers/packages.md | 2 +- docs/designers-developers/readme.md | 2 +- 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/designers-developers/developers/README.md b/docs/designers-developers/developers/README.md index 8dbc72195d05c..860e6f3b07310 100644 --- a/docs/designers-developers/developers/README.md +++ b/docs/designers-developers/developers/README.md @@ -1,10 +1,10 @@ # Developer Documentation -Gutenberg is highly flexible, like most of WordPress. You can build custom blocks, modify the editor's appearance, add special plugins, and much more. +The new editor is highly flexible, like most of WordPress. You can build custom blocks, modify the editor's appearance, add special plugins, and much more. ## Creating Blocks -Gutenberg is about blocks, and the main extensibility API of Gutenberg is the Block API. It allows you to create your own static blocks, dynamic blocks rendered on the server and also blocks capable of saving data to Post Meta for more structured content. +The editor is about blocks, and the main extensibility API is the Block API. It allows you to create your own static blocks, dynamic blocks rendered on the server and also blocks capable of saving data to Post Meta for more structured content. If you want to learn more about block creation, the [Blocks Tutorial](../../../docs/designers-developers/developers/tutorials/block-tutorial/readme.md) is the best place to start. @@ -24,9 +24,9 @@ You can also filter certain aspects of the editor; this is documented on the [Ed ## Meta Boxes -**Porting PHP meta boxes to blocks and Gutenberg plugins is highly encouraged!** +**Porting PHP meta boxes to blocks or sidebar plugins is highly encouraged!** -Discover how [Meta Box](../../../docs/designers-developers/developers/backward-compatibility/meta-box.md) support works in Gutenberg. +Discover how [Meta Box](../../../docs/designers-developers/developers/backward-compatibility/meta-box.md) support works in the new editor. ## Theme Support diff --git a/docs/designers-developers/developers/backward-compatibility/deprecations.md b/docs/designers-developers/developers/backward-compatibility/deprecations.md index 7b690e995c516..cf98cdb9fe1dd 100644 --- a/docs/designers-developers/developers/backward-compatibility/deprecations.md +++ b/docs/designers-developers/developers/backward-compatibility/deprecations.md @@ -1,6 +1,6 @@ # Deprecations -Gutenberg's deprecation policy is intended to support backward compatibility for releases, when possible. The current deprecations are listed below and are grouped by _the version at which they will be removed completely_. If your plugin depends on these behaviors, you must update to the recommended alternative before the noted version. +The Gutenberg project's deprecation policy is intended to support backward compatibility for releases, when possible. The current deprecations are listed below and are grouped by _the version at which they will be removed completely_. If your plugin depends on these behaviors, you must update to the recommended alternative before the noted version. ## 4.5.0 - `Dropdown.refresh()` has been deprecated as the contained `Popover` is now automatically refreshed. diff --git a/docs/designers-developers/developers/block-api/README.md b/docs/designers-developers/developers/block-api/README.md index 2c18a261d8fc5..af153d59ba38a 100644 --- a/docs/designers-developers/developers/block-api/README.md +++ b/docs/designers-developers/developers/block-api/README.md @@ -1,6 +1,6 @@ # Block API Reference -Blocks are the fundamental element of the Gutenberg editor. They are the primary way in which plugins and themes can register their own functionality and extend the capabilities of the editor. +Blocks are the fundamental element of the editor. They are the primary way in which plugins and themes can register their own functionality and extend the capabilities of the editor. ## Registering a block diff --git a/docs/designers-developers/developers/filters/README.md b/docs/designers-developers/developers/filters/README.md index 9715e15885935..f6da621ac74be 100644 --- a/docs/designers-developers/developers/filters/README.md +++ b/docs/designers-developers/developers/filters/README.md @@ -1,7 +1,7 @@ # Filter Reference -[Hooks](https://developer.wordpress.org/plugins/hooks/) are a way for one piece of code to interact/modify another piece of code. They provide one way for plugins and themes interact with Gutenberg, but they’re also used extensively by WordPress Core itself. +[Hooks](https://developer.wordpress.org/plugins/hooks/) are a way for one piece of code to interact/modify another piece of code. They provide one way for plugins and themes interact with the editor, but they’re also used extensively by WordPress Core itself. -There are two types of hooks: [Actions](https://developer.wordpress.org/plugins/hooks/actions/) and [Filters](https://developer.wordpress.org/plugins/hooks/filters/). In addition to PHP actions and filters, Gutenberg also provides a mechanism for registering and executing hooks in JavaScript. This functionality is also available on npm as the [@wordpress/hooks](https://www.npmjs.com/package/@wordpress/hooks) package, for general purpose use. +There are two types of hooks: [Actions](https://developer.wordpress.org/plugins/hooks/actions/) and [Filters](https://developer.wordpress.org/plugins/hooks/filters/). In addition to PHP actions and filters, WordPress also provides a mechanism for registering and executing hooks in JavaScript. This functionality is also available on npm as the [@wordpress/hooks](https://www.npmjs.com/package/@wordpress/hooks) package, for general purpose use. You can also learn more about both APIs: [PHP](https://codex.wordpress.org/Plugin_API/) and [JavaScript](https://github.com/WordPress/packages/tree/master/packages/hooks). diff --git a/docs/designers-developers/developers/filters/autocomplete-filters.md b/docs/designers-developers/developers/filters/autocomplete-filters.md index 2b9d60476de80..4086139c60a19 100644 --- a/docs/designers-developers/developers/filters/autocomplete-filters.md +++ b/docs/designers-developers/developers/filters/autocomplete-filters.md @@ -1,6 +1,6 @@ # Autocomplete -Gutenberg provides an `editor.Autocomplete.completers` filter for extending and overriding the list of autocompleters used by blocks. +The `editor.Autocomplete.completers` filter is for extending and overriding the list of autocompleters used by blocks. The `Autocomplete` component found in `@wordpress/editor` applies this filter. The `@wordpress/components` package provides the foundational `Autocomplete` component that does not apply such a filter, but blocks should generally use the component provided by `@wordpress/editor`. diff --git a/docs/designers-developers/developers/filters/block-filters.md b/docs/designers-developers/developers/filters/block-filters.md index 3214fa1ba2da5..33384edd58153 100644 --- a/docs/designers-developers/developers/filters/block-filters.md +++ b/docs/designers-developers/developers/filters/block-filters.md @@ -1,6 +1,6 @@ # Block Filters -To modify the behavior of existing blocks, Gutenberg exposes several APIs: +To modify the behavior of existing blocks, WordPress exposes several APIs: ### Block Style Variations diff --git a/docs/designers-developers/developers/filters/editor-filters.md b/docs/designers-developers/developers/filters/editor-filters.md index a205d583103b0..2ddb34f39f957 100644 --- a/docs/designers-developers/developers/filters/editor-filters.md +++ b/docs/designers-developers/developers/filters/editor-filters.md @@ -1,6 +1,6 @@ # Editor Filters (Experimental) -To modify the behavior of the editor experience, Gutenberg exposes the following Filters: +To modify the behavior of the editor experience, the following Filters are exposed: ### `editor.PostFeaturedImage.imageSize` diff --git a/docs/designers-developers/developers/internationalization.md b/docs/designers-developers/developers/internationalization.md index 68cafea0e4257..5dcd88f011261 100644 --- a/docs/designers-developers/developers/internationalization.md +++ b/docs/designers-developers/developers/internationalization.md @@ -1,6 +1,6 @@ # Internationalization -This document aims to give an overview of the possibilities for both internationalization and localization when developing with Gutenberg. +This document aims to give an overview of the possibilities for both internationalization and localization when developing with WordPress. ## PHP @@ -13,7 +13,7 @@ For years, WordPress has been providing the necessary tools and functions to int - `_e( 'Hello World', 'my-text-domain' )`: Translate and print a certain string. - `esc_html__( 'Hello World', 'my-text-domain' )`: Translate a certain string and escape it for safe use in HTML output. - `esc_html_e( 'Hello World', 'my-text-domain' )`: Translate a certain string, escape it for safe use in HTML output, and print it. -- `_n( '%s Comment', '%s Comments', $number, 'my-text-domain' )`: Translate and retrieve the singular or plural form based on the supplied number. +- `_n( '%s Comment', '%s Comments', $number, 'my-text-domain' )`: Translate and retrieve the singular or plural form based on the supplied number. Usually used in combination with `sprintf()` and `number_format_i18n()`. ## JavaScript diff --git a/docs/designers-developers/developers/packages.md b/docs/designers-developers/developers/packages.md index 7096f67115a28..66235b7fa17b8 100644 --- a/docs/designers-developers/developers/packages.md +++ b/docs/designers-developers/developers/packages.md @@ -1,6 +1,6 @@ # Packages -Gutenberg exposes a list of JavaScript packages and tools for WordPress development. +WordPress exposes a list of JavaScript packages and tools for WordPress development. ## Using the packages via WordPress global diff --git a/docs/designers-developers/readme.md b/docs/designers-developers/readme.md index 107bad052f309..7b8da012e063f 100644 --- a/docs/designers-developers/readme.md +++ b/docs/designers-developers/readme.md @@ -1,6 +1,6 @@ # Designer & Developer Handbook -Gutenberg is a transformation of the WordPress editor for working with content. +The Gutenberg project is transforming the way content is created on WordPress. A block editor was the first product launched creating a new methodology for working with content. This handbook provides documentation for how designers and developers can extend the editor. ![Gutenberg Demo](https://cldup.com/kZXGDcGPMU.gif) From 8cce5f2b3910f88cc576b27b3290bbcc89e8fd0a Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Tue, 8 Jan 2019 07:41:22 -0800 Subject: [PATCH 115/691] Add filter to disable PostAuthor (#12687) * Add filter to replace PostAuthor component Adds the ability for a developer to switch out the PostAuthor component with their own using the filter `editor.PostAuthor` * Add readme for PostAuthor component, with hook documented * Remove documentation here, included in component readme --- .../src/components/post-author/README.md | 59 +++++++++++++++++++ .../src/components/post-author/index.js | 2 + 2 files changed, 61 insertions(+) create mode 100644 packages/editor/src/components/post-author/README.md diff --git a/packages/editor/src/components/post-author/README.md b/packages/editor/src/components/post-author/README.md new file mode 100644 index 0000000000000..99cbb105ce31d --- /dev/null +++ b/packages/editor/src/components/post-author/README.md @@ -0,0 +1,59 @@ +PostAuthor +=========== + +`PostAuthor` is a React component used to render the Post Author selection tool. + +## Setup + +It includes a `wp.hooks` filter `editor.PostAuthor` that enables developers to replace or extend it. + +_Examples:_ + +Replace the contents of the panel: + +```js +function replacePostAuthor() { + return function() { + return wp.element.createElement( + 'div', + {}, + 'My Post Author component.' + ); + } +} + +wp.hooks.addFilter( + 'editor.PostAuthor', + 'my-plugin/replace-post-author', + replacePostAuthor +); +``` + +Prepend and append to the panel contents: + +```js +var el = wp.element.createElement; + +function wrapPostAuthor( OriginalComponent ) { + return function( props ) { + return ( + el( + wp.element.Fragment, + {}, + 'Prepend above', + el( + OriginalComponent, + props + ), + 'Append below' + ) + ); + } +} + +wp.hooks.addFilter( + 'editor.PostAuthor', + 'my-plugin/wrap-post-author', + wrapPostAuthor +); +``` diff --git a/packages/editor/src/components/post-author/index.js b/packages/editor/src/components/post-author/index.js index 5bb31bf294fd6..a7e64527b8ef1 100644 --- a/packages/editor/src/components/post-author/index.js +++ b/packages/editor/src/components/post-author/index.js @@ -4,6 +4,7 @@ import { __ } from '@wordpress/i18n'; import { withInstanceId, compose } from '@wordpress/compose'; import { Component } from '@wordpress/element'; +import { withFilters } from '@wordpress/components'; import { withSelect, withDispatch } from '@wordpress/data'; /** @@ -63,4 +64,5 @@ export default compose( [ }, } ) ), withInstanceId, + withFilters( 'editor.PostAuthor' ), ] )( PostAuthor ); From f911685889a9ac4e37799a80de57b33ba6b5d553 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Tue, 8 Jan 2019 08:56:58 -0800 Subject: [PATCH 116/691] Revert PR#12687, we do not want additional withFilters (#13242) --- .../src/components/post-author/README.md | 59 ------------------- .../src/components/post-author/index.js | 2 - 2 files changed, 61 deletions(-) delete mode 100644 packages/editor/src/components/post-author/README.md diff --git a/packages/editor/src/components/post-author/README.md b/packages/editor/src/components/post-author/README.md deleted file mode 100644 index 99cbb105ce31d..0000000000000 --- a/packages/editor/src/components/post-author/README.md +++ /dev/null @@ -1,59 +0,0 @@ -PostAuthor -=========== - -`PostAuthor` is a React component used to render the Post Author selection tool. - -## Setup - -It includes a `wp.hooks` filter `editor.PostAuthor` that enables developers to replace or extend it. - -_Examples:_ - -Replace the contents of the panel: - -```js -function replacePostAuthor() { - return function() { - return wp.element.createElement( - 'div', - {}, - 'My Post Author component.' - ); - } -} - -wp.hooks.addFilter( - 'editor.PostAuthor', - 'my-plugin/replace-post-author', - replacePostAuthor -); -``` - -Prepend and append to the panel contents: - -```js -var el = wp.element.createElement; - -function wrapPostAuthor( OriginalComponent ) { - return function( props ) { - return ( - el( - wp.element.Fragment, - {}, - 'Prepend above', - el( - OriginalComponent, - props - ), - 'Append below' - ) - ); - } -} - -wp.hooks.addFilter( - 'editor.PostAuthor', - 'my-plugin/wrap-post-author', - wrapPostAuthor -); -``` diff --git a/packages/editor/src/components/post-author/index.js b/packages/editor/src/components/post-author/index.js index a7e64527b8ef1..5bb31bf294fd6 100644 --- a/packages/editor/src/components/post-author/index.js +++ b/packages/editor/src/components/post-author/index.js @@ -4,7 +4,6 @@ import { __ } from '@wordpress/i18n'; import { withInstanceId, compose } from '@wordpress/compose'; import { Component } from '@wordpress/element'; -import { withFilters } from '@wordpress/components'; import { withSelect, withDispatch } from '@wordpress/data'; /** @@ -64,5 +63,4 @@ export default compose( [ }, } ) ), withInstanceId, - withFilters( 'editor.PostAuthor' ), ] )( PostAuthor ); From 6ad03d5e1cc9efdaefa48d45fd56be522a032c0f Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Wed, 9 Jan 2019 09:04:26 +0000 Subject: [PATCH 117/691] Fix: Missing margin in blocks with child blocks (#13227) --- packages/editor/src/components/inserter/style.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/editor/src/components/inserter/style.scss b/packages/editor/src/components/inserter/style.scss index 5423cae2cc32c..9237ac2ed0bc2 100644 --- a/packages/editor/src/components/inserter/style.scss +++ b/packages/editor/src/components/inserter/style.scss @@ -134,4 +134,8 @@ $block-inserter-search-height: 38px; h2 { font-size: 13px; } + + .editor-block-icon { + margin-right: $grid-size; + } } From ce864a6f9aff4eea9b4bd3994b2cf4bae30105cb Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Wed, 9 Jan 2019 01:05:36 -0800 Subject: [PATCH 118/691] Fix link to comment delimiters in documentation (#13253) * Fix link to comment delimiters Fixes #12988 * One more level --- .../developers/block-api/block-attributes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/designers-developers/developers/block-api/block-attributes.md b/docs/designers-developers/developers/block-api/block-attributes.md index 9bab342a93500..48a9b36ef83b5 100644 --- a/docs/designers-developers/developers/block-api/block-attributes.md +++ b/docs/designers-developers/developers/block-api/block-attributes.md @@ -4,7 +4,7 @@ Attribute sources are used to define the strategy by which block attribute values are extracted from saved post content. They provide a mechanism to map from the saved markup to a JavaScript representation of a block. -If no attribute source is specified, the attribute will be saved to (and read from) the block's [comment delimiter](../language.md). +If no attribute source is specified, the attribute will be saved to (and read from) the block's [comment delimiter](../../../../docs/designers-developers/key-concepts.md#delimiters-and-parsing-expression-grammar). Each source accepts an optional selector as the first argument. If a selector is specified, the source behavior will be run against the corresponding element(s) contained within the block. Otherwise it will be run against the block's root node. From f8761e78bf11378c7e1f199ad5268e1ac7dd59eb Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Wed, 9 Jan 2019 09:06:31 +0000 Subject: [PATCH 119/691] Fix: Ampersand in post tag causes editor crash (#12889) --- .../post-taxonomies/flat-term-selector.js | 51 ++++++++++++++++--- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/packages/editor/src/components/post-taxonomies/flat-term-selector.js b/packages/editor/src/components/post-taxonomies/flat-term-selector.js index c42e7e15ffbd2..411890ae5d615 100644 --- a/packages/editor/src/components/post-taxonomies/flat-term-selector.js +++ b/packages/editor/src/components/post-taxonomies/flat-term-selector.js @@ -1,7 +1,17 @@ /** * External dependencies */ -import { isEmpty, get, unescape as unescapeString, find, throttle, uniqBy, invoke } from 'lodash'; +import { + escape as escapeString, + find, + get, + invoke, + isEmpty, + map, + throttle, + unescape as unescapeString, + uniqBy, +} from 'lodash'; /** * WordPress dependencies @@ -26,6 +36,33 @@ const DEFAULT_QUERY = { const MAX_TERMS_SUGGESTIONS = 20; const isSameTermName = ( termA, termB ) => termA.toLowerCase() === termB.toLowerCase(); +/** + * Returns a term object with name unescaped. + * The unescape of the name propery is done using lodash unescape function. + * + * @param {Object} term The term object to unescape. + * + * @return {Object} Term object with name property unescaped. + */ +const unescapeTerm = ( term ) => { + return { + ...term, + name: unescapeString( term.name ), + }; +}; + +/** + * Returns an array of term objects with names unescaped. + * The unescape of each term is performed using the unescapeTerm function. + * + * @param {Object[]} terms Array of term objects to unescape. + * + * @return {Object[]} Array of therm objects unscaped. + */ +const unescapeTerms = ( terms ) => { + return map( terms, unescapeTerm ); +}; + class FlatTermSelector extends Component { constructor() { super( ...arguments ); @@ -79,7 +116,7 @@ class FlatTermSelector extends Component { const request = apiFetch( { path: addQueryArgs( `/wp/v2/${ taxonomy.rest_base }`, query ), } ); - request.then( ( terms ) => { + request.then( unescapeTerms ).then( ( terms ) => { this.setState( ( state ) => ( { availableTerms: state.availableTerms.concat( terms.filter( ( term ) => ! find( state.availableTerms, ( availableTerm ) => availableTerm.id === term.id ) ) @@ -107,24 +144,25 @@ class FlatTermSelector extends Component { findOrCreateTerm( termName ) { const { taxonomy } = this.props; + const termNameEscaped = escapeString( termName ); // Tries to create a term or fetch it if it already exists. return apiFetch( { path: `/wp/v2/${ taxonomy.rest_base }`, method: 'POST', - data: { name: termName }, + data: { name: termNameEscaped }, } ).catch( ( error ) => { const errorCode = error.code; if ( errorCode === 'term_exists' ) { // If the terms exist, fetch it instead of creating a new one. this.addRequest = apiFetch( { - path: addQueryArgs( `/wp/v2/${ taxonomy.rest_base }`, { ...DEFAULT_QUERY, search: termName } ), - } ); + path: addQueryArgs( `/wp/v2/${ taxonomy.rest_base }`, { ...DEFAULT_QUERY, search: termNameEscaped } ), + } ).then( unescapeTerms ); return this.addRequest.then( ( searchResult ) => { return find( searchResult, ( result ) => isSameTermName( result.name, termName ) ); } ); } return Promise.reject( error ); - } ); + } ).then( unescapeTerm ); } onChange( termNames ) { @@ -189,7 +227,6 @@ class FlatTermSelector extends Component { return ( <FormTokenField value={ selectedTerms } - displayTransform={ unescapeString } suggestions={ termNames } onChange={ this.onChange } onInputChange={ this.searchTerms } From 204f7a05bc9a60297530380f0f905b9ce4327d7e Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Wed, 9 Jan 2019 09:08:22 +0000 Subject: [PATCH 120/691] Fix: Remove alignundefined class from gallery block edit markup (#13192) --- packages/block-library/src/gallery/edit.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/gallery/edit.js b/packages/block-library/src/gallery/edit.js index 003570d3bdf7d..5fee7758973d5 100644 --- a/packages/block-library/src/gallery/edit.js +++ b/packages/block-library/src/gallery/edit.js @@ -1,6 +1,7 @@ /** * External Dependencies */ +import classnames from 'classnames'; import { filter, pick, map, get } from 'lodash'; /** @@ -261,7 +262,16 @@ class GalleryEdit extends Component { </PanelBody> </InspectorControls> { noticeUI } - <ul className={ `${ className } align${ align } columns-${ columns } ${ imageCrop ? 'is-cropped' : '' }` }> + <ul + className={ classnames( + className, + { + [ `align${ align }` ]: align, + [ `columns-${ columns }` ]: columns, + 'is-cropped': imageCrop, + } + ) } + > { dropZone } { images.map( ( img, index ) => { /* translators: %1$d is the order number of the image, %2$d is the total number of images. */ From b62e0f5e541bf47027bb3e1629ae985622459632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Wed, 9 Jan 2019 10:09:23 +0100 Subject: [PATCH 121/691] Fix PostPublishButton toggle (#13194) Because we're using aria-disabled instead of the disabled property the button is clickable. We need to back off from firing the onToggle event if the toggle is disabled. --- .../src/components/post-publish-button/index.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/editor/src/components/post-publish-button/index.js b/packages/editor/src/components/post-publish-button/index.js index 18eba3606e711..0418f9b17c606 100644 --- a/packages/editor/src/components/post-publish-button/index.js +++ b/packages/editor/src/components/post-publish-button/index.js @@ -72,7 +72,7 @@ export class PostPublishButton extends Component { publishStatus = 'publish'; } - const onClick = () => { + const onClickButton = () => { if ( isButtonDisabled ) { return; } @@ -81,13 +81,20 @@ export class PostPublishButton extends Component { onSave(); }; + const onClickToggle = () => { + if ( isToggleDisabled ) { + return; + } + onToggle(); + }; + const buttonProps = { 'aria-disabled': isButtonDisabled, className: 'editor-post-publish-button', isBusy: isSaving && isPublished, isLarge: true, isPrimary: true, - onClick, + onClick: onClickButton, }; const toggleProps = { @@ -96,7 +103,7 @@ export class PostPublishButton extends Component { className: 'editor-post-publish-panel__toggle', isBusy: isSaving && isPublished, isPrimary: true, - onClick: onToggle, + onClick: onClickToggle, }; const toggleChildren = isBeingScheduled ? __( 'Schedule…' ) : __( 'Publish…' ); From e96995457dbdb80fe715bb2aab677a80b55470c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Wrede?= <soerenwrede@gmail.com> Date: Wed, 9 Jan 2019 10:13:46 +0100 Subject: [PATCH 122/691] Fix Typos (#13251) --- assets/stylesheets/_mixins.scss | 2 +- docs/designers-developers/designers/block-design.md | 6 +++--- docs/designers-developers/designers/menu-item.md | 2 +- packages/format-library/src/link/inline.js | 2 +- packages/rich-text/src/apply-format.js | 2 +- packages/shortcode/src/test/index.js | 2 +- packages/wordcount/README.md | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/assets/stylesheets/_mixins.scss b/assets/stylesheets/_mixins.scss index 3cbfe6b3a0441..090854559f257 100644 --- a/assets/stylesheets/_mixins.scss +++ b/assets/stylesheets/_mixins.scss @@ -252,7 +252,7 @@ } } - .auto-fold #{$selector} { /* Auto fold is when on smaller breakpoints, nav menu auto colllapses. */ + .auto-fold #{$selector} { /* Auto fold is when on smaller breakpoints, nav menu auto collapses. */ @include break-medium() { left: $admin-sidebar-width-collapsed; } diff --git a/docs/designers-developers/designers/block-design.md b/docs/designers-developers/designers/block-design.md index 22fc9de64ef4e..8b267c0d754da 100644 --- a/docs/designers-developers/designers/block-design.md +++ b/docs/designers-developers/designers/block-design.md @@ -21,7 +21,7 @@ The sidebar is not visible by default on a small / mobile screen, and may also b ## Setup state vs. live preview state -Setup states, sometimes referred to as "placeholders", can be used to walk users through an initial process before shoing the live preview state of the block. The setup process gathers information from the user that is needed to render the block. A block’s setup state is indicated with a grey background to provide clear differentiation for the user. Not all blocks have setup states — for example, the paragraph block. +Setup states, sometimes referred to as "placeholders", can be used to walk users through an initial process before showing the live preview state of the block. The setup process gathers information from the user that is needed to render the block. A block’s setup state is indicated with a grey background to provide clear differentiation for the user. Not all blocks have setup states — for example, the paragraph block. ![An example of a gallery block’s setup state on a grey background](https://make.wordpress.org/design/files/2018/12/gallery-setup.png) @@ -46,11 +46,11 @@ When the block is selected, additional controls may be revealed to customize the In most cases, a block’s setup state is only shown once and then further customization is done via the live preview state. However, in some cases it might be desirable to allow the user to return to the setup state — for example, if all the block content has been deleted or via a link from the block’s toolbar or sidebar. -## Do's and Don'ts +## Do's and Don'ts ### Blocks -A block should have a straightforward, short name so users can easily find it in the Block Library. A block named "YouTube" is easy to find and understand. The same block, named "Embedded Video (YouTube)", would be less clear and harder to find in the Block Library. +A block should have a straightforward, short name so users can easily find it in the Block Library. A block named "YouTube" is easy to find and understand. The same block, named "Embedded Video (YouTube)", would be less clear and harder to find in the Block Library. Blocks should have an identifying icon, ideally using a single color. Try to avoid using the same icon used by an existing block. The core block icons are based on [Material Design Icons](https://material.io/tools/icons/). Look to that icon set, or to [Dashicons](https://developer.wordpress.org/resource/dashicons/) for style inspiration. diff --git a/docs/designers-developers/designers/menu-item.md b/docs/designers-developers/designers/menu-item.md index ebd9108c6ae4f..246a8fde1d457 100644 --- a/docs/designers-developers/designers/menu-item.md +++ b/docs/designers-developers/designers/menu-item.md @@ -16,7 +16,7 @@ ### Usage -A `MenuGroup` contiaining `MenuItem`s can be used within a `Dropdown`. A `MenuGroup` can also have other `MenuGroup`s within it so menus can be nested. +A `MenuGroup` containing `MenuItem`s can be used within a `Dropdown`. A `MenuGroup` can also have other `MenuGroup`s within it so menus can be nested. ## Development guidelines diff --git a/packages/format-library/src/link/inline.js b/packages/format-library/src/link/inline.js index b2ef0beaecc68..cb2e4ea1025d9 100644 --- a/packages/format-library/src/link/inline.js +++ b/packages/format-library/src/link/inline.js @@ -223,7 +223,7 @@ class InlineLinkUI extends Component { onClickOutside( event ) { // The autocomplete suggestions list renders in a separate popover (in a portal), - // so onClickOutside fails to detect that a click on a suggestion occured in the + // so onClickOutside fails to detect that a click on a suggestion occurred in the // LinkContainer. Detect clicks on autocomplete suggestions using a ref here, and // return to avoid the popover being closed. const autocompleteElement = this.autocompleteRef.current; diff --git a/packages/rich-text/src/apply-format.js b/packages/rich-text/src/apply-format.js index 7580ebaee2086..5f0f8dd00c386 100644 --- a/packages/rich-text/src/apply-format.js +++ b/packages/rich-text/src/apply-format.js @@ -30,7 +30,7 @@ export function applyFormat( ) { const newFormats = formats.slice( 0 ); - // The selection is collpased. + // The selection is collapsed. if ( startIndex === endIndex ) { const startFormat = find( newFormats[ startIndex ], { type: format.type } ); diff --git a/packages/shortcode/src/test/index.js b/packages/shortcode/src/test/index.js index ad76974581dd3..9e3f376426103 100644 --- a/packages/shortcode/src/test/index.js +++ b/packages/shortcode/src/test/index.js @@ -59,7 +59,7 @@ describe( 'shortcode', () => { expect( result2.index ).toBe( 14 ); } ); - it( 'should still work when there are not equal ammounts of square brackets', () => { + it( 'should still work when there are not equal amounts of square brackets', () => { const result1 = next( 'foo', 'this has the [[foo] shortcode' ); expect( result1.index ).toBe( 14 ); diff --git a/packages/wordcount/README.md b/packages/wordcount/README.md index eb17c4331280f..076a25ac81421 100644 --- a/packages/wordcount/README.md +++ b/packages/wordcount/README.md @@ -12,7 +12,7 @@ npm install @wordpress/wordcount --save _This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. Learn more about it in [Babel docs](https://babeljs.io/docs/en/next/caveats)._ -## Accepted Paramaters +## Accepted Parameters ```JS count( text, type, userSettings ) From 14374e5f08ff7cd91d281cbfc4102cec39d45ed8 Mon Sep 17 00:00:00 2001 From: Andrea Fercia <a.fercia@gmail.com> Date: Wed, 9 Jan 2019 15:52:33 +0100 Subject: [PATCH 123/691] Set a default icon margin for buttons with icon and text. (#12901) --- .../src/audio/test/__snapshots__/index.js.snap | 2 +- .../src/cover/test/__snapshots__/index.js.snap | 2 +- .../src/gallery/test/__snapshots__/index.js.snap | 2 +- .../src/video/test/__snapshots__/index.js.snap | 2 +- packages/components/src/icon-button/index.js | 4 +++- packages/components/src/icon-button/style.scss | 6 +++--- .../plugin-more-menu-item/test/__snapshots__/index.js.snap | 2 +- 7 files changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/block-library/src/audio/test/__snapshots__/index.js.snap b/packages/block-library/src/audio/test/__snapshots__/index.js.snap index ec88637dd0579..76405487e3a2a 100644 --- a/packages/block-library/src/audio/test/__snapshots__/index.js.snap +++ b/packages/block-library/src/audio/test/__snapshots__/index.js.snap @@ -38,7 +38,7 @@ exports[`core/audio block edit matches snapshot 1`] = ` class="components-form-file-upload" > <button - class="components-button components-icon-button editor-media-placeholder__button is-button is-default is-large" + class="components-button components-icon-button editor-media-placeholder__button has-text is-button is-default is-large" type="button" > <svg diff --git a/packages/block-library/src/cover/test/__snapshots__/index.js.snap b/packages/block-library/src/cover/test/__snapshots__/index.js.snap index 8ed40b5ba4265..e1c2749b03b89 100644 --- a/packages/block-library/src/cover/test/__snapshots__/index.js.snap +++ b/packages/block-library/src/cover/test/__snapshots__/index.js.snap @@ -38,7 +38,7 @@ exports[`core/cover block edit matches snapshot 1`] = ` class="components-form-file-upload" > <button - class="components-button components-icon-button editor-media-placeholder__button is-button is-default is-large" + class="components-button components-icon-button editor-media-placeholder__button has-text is-button is-default is-large" type="button" > <svg diff --git a/packages/block-library/src/gallery/test/__snapshots__/index.js.snap b/packages/block-library/src/gallery/test/__snapshots__/index.js.snap index 221e6659c92ea..6f00bba1cb56e 100644 --- a/packages/block-library/src/gallery/test/__snapshots__/index.js.snap +++ b/packages/block-library/src/gallery/test/__snapshots__/index.js.snap @@ -38,7 +38,7 @@ exports[`core/gallery block edit matches snapshot 1`] = ` class="components-form-file-upload" > <button - class="components-button components-icon-button editor-media-placeholder__button is-button is-default is-large" + class="components-button components-icon-button editor-media-placeholder__button has-text is-button is-default is-large" type="button" > <svg diff --git a/packages/block-library/src/video/test/__snapshots__/index.js.snap b/packages/block-library/src/video/test/__snapshots__/index.js.snap index 0ae056f34736c..e69b765f795ae 100644 --- a/packages/block-library/src/video/test/__snapshots__/index.js.snap +++ b/packages/block-library/src/video/test/__snapshots__/index.js.snap @@ -38,7 +38,7 @@ exports[`core/video block edit matches snapshot 1`] = ` class="components-form-file-upload" > <button - class="components-button components-icon-button editor-media-placeholder__button is-button is-default is-large" + class="components-button components-icon-button editor-media-placeholder__button has-text is-button is-default is-large" type="button" > <svg diff --git a/packages/components/src/icon-button/index.js b/packages/components/src/icon-button/index.js index 27bcc08f79b65..7b0a4ae3541a9 100644 --- a/packages/components/src/icon-button/index.js +++ b/packages/components/src/icon-button/index.js @@ -22,7 +22,9 @@ class IconButton extends Component { render() { const { icon, children, label, className, tooltip, shortcut, labelPosition, ...additionalProps } = this.props; const { 'aria-pressed': ariaPressed } = this.props; - const classes = classnames( 'components-icon-button', className ); + const classes = classnames( 'components-icon-button', className, { + 'has-text': children, + } ); const tooltipText = tooltip || label; // Should show the tooltip if... diff --git a/packages/components/src/icon-button/style.scss b/packages/components/src/icon-button/style.scss index cc3ee2f89564b..074ae90b634f4 100644 --- a/packages/components/src/icon-button/style.scss +++ b/packages/components/src/icon-button/style.scss @@ -19,10 +19,10 @@ svg { fill: currentColor; outline: none; + } - &:not:only-child { - margin-right: 4px; - } + &.has-text svg { + margin-right: 4px; } &:not(:disabled):not([aria-disabled="true"]):not(.is-default):hover { diff --git a/packages/edit-post/src/components/header/plugin-more-menu-item/test/__snapshots__/index.js.snap b/packages/edit-post/src/components/header/plugin-more-menu-item/test/__snapshots__/index.js.snap index 33e7e8889c3e5..692b032d744f0 100644 --- a/packages/edit-post/src/components/header/plugin-more-menu-item/test/__snapshots__/index.js.snap +++ b/packages/edit-post/src/components/header/plugin-more-menu-item/test/__snapshots__/index.js.snap @@ -18,7 +18,7 @@ exports[`PluginMoreMenuItem renders menu item as button properly 1`] = ` > <button aria-label="My plugin button menu item" - className="components-button components-icon-button components-menu-item__button has-icon" + className="components-button components-icon-button components-menu-item__button has-icon has-text" onClick={[Function]} role="menuitem" type="button" From 2e6a60d54a49cd466ff0d496b5bbb3543f990d2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Wrede?= <soerenwrede@gmail.com> Date: Wed, 9 Jan 2019 16:58:47 +0100 Subject: [PATCH 124/691] Docs: Fix typos (#13255) --- .../developers/data/data-core-editor.md | 2 +- packages/block-library/src/columns/editor.scss | 2 +- packages/components/src/date-time/style.scss | 2 +- .../src/higher-order/with-filters/README.md | 14 +++++++------- packages/editor/src/store/actions.js | 2 +- packages/editor/src/store/effects/posts.js | 2 +- packages/editor/src/store/reducer.js | 2 +- packages/editor/src/store/selectors.js | 2 +- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/designers-developers/developers/data/data-core-editor.md b/docs/designers-developers/developers/data/data-core-editor.md index 800537ba65d22..5f1c0500fe8df 100644 --- a/docs/designers-developers/developers/data/data-core-editor.md +++ b/docs/designers-developers/developers/data/data-core-editor.md @@ -1622,7 +1622,7 @@ be inserted, optionally at a specific index respective a root block list. * blocks: Block objects to insert. * index: Index at which block should be inserted. - * rootClientId: Optional root cliente ID of block list on which to insert. + * rootClientId: Optional root client ID of block list on which to insert. * updateSelection: If true block selection will be updated. If false, block selection will not change. Defaults to true. ### showInsertionPoint diff --git a/packages/block-library/src/columns/editor.scss b/packages/block-library/src/columns/editor.scss index 50b21f2352331..ebd2cbf5fdb9d 100644 --- a/packages/block-library/src/columns/editor.scss +++ b/packages/block-library/src/columns/editor.scss @@ -125,7 +125,7 @@ // The empty state of a columns block has the default appenders. // Since those appenders are not blocks, the parent, actual block, appears "hovered" when hovering the appenders. - // Because the column shouldn't be hovered as part of this temporary passhthrough, we unset the hover style. + // Because the column shouldn't be hovered as part of this temporary passthrough, we unset the hover style. &.is-hovered { > .editor-block-list__block-edit::before { content: none; diff --git a/packages/components/src/date-time/style.scss b/packages/components/src/date-time/style.scss index e564636246f2b..530b397c9144e 100644 --- a/packages/components/src/date-time/style.scss +++ b/packages/components/src/date-time/style.scss @@ -142,7 +142,7 @@ } // Makes the month appear before the day if time format uses AM/PM - // We are assuming MM-DD-YYY corelates with AM/PM + // We are assuming MM-DD-YYY correlates with AM/PM &.is-12-hour { .components-datetime__time-field-day input { margin: 0 -4px 0 0 !important; diff --git a/packages/components/src/higher-order/with-filters/README.md b/packages/components/src/higher-order/with-filters/README.md index 7c2840ad90eac..88eac8ba8a29a 100644 --- a/packages/components/src/higher-order/with-filters/README.md +++ b/packages/components/src/higher-order/with-filters/README.md @@ -14,7 +14,7 @@ const MyComponent = ( { title } ) => <h1>{ title }</h1>; const ComponentToAppend = () => <div>Appended component</div>; -function withComponentApended( FilteredComponent ) { +function withComponentAppended( FilteredComponent ) { return ( props ) => ( <Fragment> <FilteredComponent { ...props } /> @@ -26,7 +26,7 @@ function withComponentApended( FilteredComponent ) { addFilter( 'MyHookName', 'my-plugin/with-component-appended', - withComponentApended + withComponentAppended ); const MyComponentWithFilters = withFilters( 'MyHookName' )( MyComponent ); @@ -44,22 +44,22 @@ const MyComponent = ( { hint, title } ) => ( <Fragment> <h1>{ title }</h1> <p>{ hint }</p> - </Fragment> + </Fragment> ); -function withHintOverriden( FilteredComponent ) { +function withHintOverridden( FilteredComponent ) { return ( props ) => ( <FilteredComponent { ...props } - hint="Overriden hint" + hint="Overridden hint" /> ); } addFilter( 'MyHookName', - 'my-plugin/with-hint-overriden', - withHintOverriden + 'my-plugin/with-hint-overridden', + withHintOverridden ); const MyComponentWithFilters = withFilters( 'MyHookName' )( MyComponent ); diff --git a/packages/editor/src/store/actions.js b/packages/editor/src/store/actions.js index 6302ce8894756..c12a0cb0c6bf7 100644 --- a/packages/editor/src/store/actions.js +++ b/packages/editor/src/store/actions.js @@ -307,7 +307,7 @@ export function insertBlock( block, index, rootClientId, updateSelection = true * * @param {Object[]} blocks Block objects to insert. * @param {?number} index Index at which block should be inserted. - * @param {?string} rootClientId Optional root cliente ID of block list on which to insert. + * @param {?string} rootClientId Optional root client ID of block list on which to insert. * @param {?boolean} updateSelection If true block selection will be updated. If false, block selection will not change. Defaults to true. * * @return {Object} Action object. diff --git a/packages/editor/src/store/effects/posts.js b/packages/editor/src/store/effects/posts.js index 4469d8dbfd326..f9cd28fd0adbb 100644 --- a/packages/editor/src/store/effects/posts.js +++ b/packages/editor/src/store/effects/posts.js @@ -51,7 +51,7 @@ export const requestPostUpdate = async ( action, store ) => { const state = getState(); // Prevent save if not saveable. - // We don't check for dirtiness here as this can be overriden in the UI. + // We don't check for dirtiness here as this can be overridden in the UI. if ( ! isEditedPostSaveable( state ) ) { return; } diff --git a/packages/editor/src/store/reducer.js b/packages/editor/src/store/reducer.js index 06061290396de..a268e2670fc65 100644 --- a/packages/editor/src/store/reducer.js +++ b/packages/editor/src/store/reducer.js @@ -680,7 +680,7 @@ export const editor = flow( [ /** * Reducer returning the initial edits state. With matching shape to that of * `editor.edits`, the initial edits are those applied programmatically, are - * not considered in prmopting the user for unsaved changes, and are included + * not considered in prompting the user for unsaved changes, and are included * in (and reset by) the next save payload. * * @param {Object} state Current state. diff --git a/packages/editor/src/store/selectors.js b/packages/editor/src/store/selectors.js index 1f79f47401802..85c8c690d03cb 100644 --- a/packages/editor/src/store/selectors.js +++ b/packages/editor/src/store/selectors.js @@ -1819,7 +1819,7 @@ export const canInsertBlockType = createSelector( * @param {string} id A string which identifies the insert, e.g. 'core/block/12' * * @return {?{ time: number, count: number }} An object containing `time` which is when the last - * insert occured as a UNIX epoch, and `count` which is + * insert occurred as a UNIX epoch, and `count` which is * the number of inserts that have occurred. */ function getInsertUsage( state, id ) { From 36b17584439e7747a6c7800d18c94fe605b85e50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Wed, 9 Jan 2019 18:18:49 +0100 Subject: [PATCH 125/691] Add new section on scoping JS code (#13041) --- .../developers/tutorials/javascript/readme.md | 1 + .../tutorials/javascript/scope-your-code.md | 117 ++++++++++++++++++ docs/manifest.json | 6 + docs/toc.json | 11 +- 4 files changed, 130 insertions(+), 5 deletions(-) create mode 100644 docs/designers-developers/developers/tutorials/javascript/scope-your-code.md diff --git a/docs/designers-developers/developers/tutorials/javascript/readme.md b/docs/designers-developers/developers/tutorials/javascript/readme.md index 8d60b35b6faa1..11fecdff4d269 100644 --- a/docs/designers-developers/developers/tutorials/javascript/readme.md +++ b/docs/designers-developers/developers/tutorials/javascript/readme.md @@ -16,3 +16,4 @@ The Block Editor introduced in WordPress 5.0 is written entirely in JavaScript, 3. [Extending the Block Editor](../../../../../docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md) 4. [Troubleshooting](../../../../../docs/designers-developers/developers/tutorials/javascript/troubleshooting.md) 5. [JavaScript Versions and Building](../../../../../docs/designers-developers/developers/tutorials/javascript/versions-and-building.md) +6. [Scope your code](../../../../../docs/designers-developers/developers/tutorials/javascript/scope-your-code.md) \ No newline at end of file diff --git a/docs/designers-developers/developers/tutorials/javascript/scope-your-code.md b/docs/designers-developers/developers/tutorials/javascript/scope-your-code.md new file mode 100644 index 0000000000000..58ddcbcaf0553 --- /dev/null +++ b/docs/designers-developers/developers/tutorials/javascript/scope-your-code.md @@ -0,0 +1,117 @@ +# Scope your code + +Historically, JavaScript files loaded in a web page share the same scope. This means that a global variable declared in one file will be seen by the code in other files. + +To see how this works, create a web page that loads three JavaScript files. The `first.js` file will be: + +```js +var pluginName = 'MyPlugin'; +console.log( 'Plugin name is ', pluginName ); +``` + +Let's create `second.js` as: + +```js +var pluginName = 'DifferentPlugin'; +console.log( 'Plugin name is ', pluginName ); +``` + +And, finally, `third.js`: + +```js +console.log( 'Plugin name is ', pluginName ); +``` + +When loaded on the same page, `first.js` and `second.js` will output the plugin name declared within itself. They will override the value of the global `pluginName` variable if one was already declared. It's not known what gets printed in the console when `third.js` is executed, though - it depends on the value of the global `pluginName` variable when `third.js` is executed, which will depend on the order the files are loaded. + +This behavior can be problematic, and is the reason we need to scope the code. By scoping the code—ensuring each file is isolated from each other—we can prevent values unexpectedly changing. + +## Scoping code within a function + +In JavaScript, you can scope your code by writing it within a function. Functions have "local scope", or a scope that is specific only to that function. Aditionally, in JavaScript you can write anonymous functions, functions without a name, which will also prevent your function name from being overridden in the global scope. + +Taking advantage of these two JavaScript features, `first.js` could be scoped as: + +```js +function() { + var pluginName = 'MyPlugin'; + console.log( 'Plugin name is ', pluginName ); +} +``` + +`second.js` as: + +```js +function() { + var pluginName = 'DifferentPlugin'; + console.log( 'Plugin name is ', pluginName ); +} +``` + +And `third.js`: + +```js +function() { + console.log( 'Plugin name is ', pluginName ); +} +``` + +With this trick, the different files won't override each other's variables. Unfortunately, they also won't work as expected, because these functions are being called by no one. We've only _defined_ the functions; we haven't _executed_ them yet. + +## Automatically execute anonymous functions + +It turns out there are a few ways to execute anonymous functions in JavaScript, but the most popular is this: + +```js +( function() { + // your code goes here +} )( ) +``` + +You wrap your function between parentheses, and then call it like any other named function. This pattern is known as [Immediately-Invoked Function Expression](http://benalman.com/news/2010/11/immediately-invoked-function-expression/), or IIFE for short. + +This is `first.js` written as an IIFE: + +```js +( function() { + var pluginName = 'MyPlugin'; + console.log( 'Plugin name is ', pluginName ); +} )( ) +``` + +And this is `second.js`: + +```js +( function() { + var pluginName = 'DifferentPlugin'; + console.log( 'Plugin name is ', pluginName ); +} )( ) +``` + +And this is `third.js`: + +```js +( function() { + console.log( 'Plugin name is ', pluginName ); +} )( ) +``` + +The code in `first.js` and `second.js` is unaffected by other variables in the global scope, so it's safe and deterministic. + +On the other hand, `third.js` doesn't declare a `pluginName` variable, but needs to be provided one. IIFEs still allow you to take a variable from the global scope and pass it into your function. Provided that there was a global `window.pluginName` variable, we could rewrite `third.js` as: + +```js +( function( name ) { + console.log( 'Plugin name is ', name ); +} )( window.pluginName ) +``` + +## Future changes + +At the beginning we mentioned that: + +> Historically, JavaScript files loaded in a web page share the same scope. + +Notice the _historically_. + +JavaScript has evolved quite a bit since its creation. As of 2015, the language supports modules, also known as _ES6 modules_, that introduce separate scope per file: a global variable in `first.js` wouldn't be exposed to `second.js`. This feature is already [supported by modern browsers](https://caniuse.com/#feat=es6-module), but not all of them do. If your code needs to run in browsers that don't support modules, your last resort is using IIFEs. diff --git a/docs/manifest.json b/docs/manifest.json index 40506ab5e932e..989677c1205a5 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -233,6 +233,12 @@ "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/versions-and-building.md", "parent": "javascript" }, + { + "title": "Scope your code", + "slug": "scope-your-code", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/scope-your-code.md", + "parent": "javascript" + }, { "title": "Designer Documentation", "slug": "designers", diff --git a/docs/toc.json b/docs/toc.json index c106d9a499ca6..fede03372f5d9 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -37,12 +37,13 @@ {"docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md" :[]}, {"docs/designers-developers/developers/tutorials/block-tutorial/generate-blocks-with-wp-cli.md" :[]} ]}, - { "docs/designers-developers/developers/tutorials/javascript/readme.md": [ + {"docs/designers-developers/developers/tutorials/javascript/readme.md": [ {"docs/designers-developers/developers/tutorials/javascript/plugins-background.md": []}, - { "docs/designers-developers/developers/tutorials/javascript/loading-javascript.md": []}, - { "docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md": []}, - { "docs/designers-developers/developers/tutorials/javascript/troubleshooting.md": []}, - { "docs/designers-developers/developers/tutorials/javascript/versions-and-building.md": []} + {"docs/designers-developers/developers/tutorials/javascript/loading-javascript.md": []}, + {"docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md": []}, + {"docs/designers-developers/developers/tutorials/javascript/troubleshooting.md": []}, + {"docs/designers-developers/developers/tutorials/javascript/versions-and-building.md": []}, + {"docs/designers-developers/developers/tutorials/javascript/scope-your-code.md": []} ]} ]} ]}, From 8ff1015db4bfe4343b475a65c21265fbc558afde Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Thu, 10 Jan 2019 08:48:06 +0100 Subject: [PATCH 126/691] Backport g-minor back to master (changelog and package version updates) (#13263) * chore(release): publish - @wordpress/annotations@1.0.5 - @wordpress/api-fetch@2.2.7 - @wordpress/block-library@2.2.12 - @wordpress/block-serialization-default-parser@2.0.3 - @wordpress/blocks@6.0.5 - @wordpress/components@7.0.5 - @wordpress/core-data@2.0.16 - @wordpress/data@4.2.0 - @wordpress/deprecated@2.0.4 - @wordpress/dom@2.0.8 - @wordpress/edit-post@3.1.7 - @wordpress/editor@9.0.7 - @wordpress/format-library@1.2.10 - @wordpress/hooks@2.0.4 - @wordpress/list-reusable-blocks@1.1.18 - @wordpress/notices@1.1.2 - @wordpress/nux@3.0.6 - @wordpress/plugins@2.0.10 - @wordpress/rich-text@3.0.4 - @wordpress/url@2.3.3 - @wordpress/viewport@2.1.0 * update package changelogs post npm release * chore(release): publish - @wordpress/annotations@1.0.6 - @wordpress/api-fetch@2.2.8 - @wordpress/babel-plugin-import-jsx-pragma@1.1.3 - @wordpress/babel-plugin-makepot@2.1.3 - @wordpress/babel-preset-default@3.0.2 - @wordpress/block-library@2.2.13 - @wordpress/block-serialization-default-parser@2.0.4 - @wordpress/block-serialization-spec-parser@2.0.3 - @wordpress/blocks@6.0.6 - @wordpress/browserslist-config@2.2.3 - @wordpress/components@7.0.6 - @wordpress/compose@3.0.1 - @wordpress/core-data@2.0.17 - @wordpress/custom-templated-path-webpack-plugin@1.1.6 - @wordpress/data@4.2.1 - @wordpress/deprecated@2.0.5 - @wordpress/edit-post@3.1.8 - @wordpress/editor@9.0.8 - @wordpress/element@2.1.9 - @wordpress/eslint-plugin@1.0.1 - @wordpress/format-library@1.2.11 - @wordpress/hooks@2.0.5 - @wordpress/i18n@3.1.1 - @wordpress/is-shallow-equal@1.1.5 - @wordpress/keycodes@2.0.6 - @wordpress/library-export-default-webpack-plugin@1.0.5 - @wordpress/list-reusable-blocks@1.1.19 - @wordpress/notices@1.1.3 - @wordpress/npm-package-json-lint-config@1.1.6 - @wordpress/nux@3.0.7 - @wordpress/plugins@2.0.11 - @wordpress/postcss-themes@1.0.5 - @wordpress/redux-routine@3.0.4 - @wordpress/rich-text@3.0.5 - @wordpress/scripts@2.5.0 - @wordpress/viewport@2.1.1 * Update changelogs post 5.1.0 beta pacakges release --- packages/annotations/CHANGELOG.md | 2 ++ packages/annotations/package.json | 2 +- packages/api-fetch/package.json | 2 +- packages/babel-plugin-import-jsx-pragma/package.json | 2 +- packages/babel-plugin-makepot/package.json | 2 +- packages/babel-preset-default/package.json | 2 +- packages/block-library/CHANGELOG.md | 2 ++ packages/block-library/package.json | 2 +- packages/block-serialization-default-parser/CHANGELOG.md | 2 ++ packages/block-serialization-default-parser/package.json | 2 +- packages/block-serialization-spec-parser/package.json | 2 +- packages/blocks/CHANGELOG.md | 2 ++ packages/blocks/package.json | 2 +- packages/browserslist-config/package.json | 2 +- packages/components/CHANGELOG.md | 2 ++ packages/components/package.json | 2 +- packages/compose/package.json | 2 +- packages/core-data/CHANGELOG.md | 2 +- packages/core-data/package.json | 2 +- packages/custom-templated-path-webpack-plugin/package.json | 2 +- packages/data/CHANGELOG.md | 2 +- packages/data/package.json | 2 +- packages/deprecated/CHANGELOG.md | 2 ++ packages/deprecated/package.json | 2 +- packages/dom/CHANGELOG.md | 2 ++ packages/dom/package.json | 2 +- packages/edit-post/CHANGELOG.md | 2 ++ packages/edit-post/package.json | 2 +- packages/editor/CHANGELOG.md | 4 +++- packages/editor/package.json | 2 +- packages/element/package.json | 2 +- packages/eslint-plugin/package.json | 2 +- packages/format-library/CHANGELOG.md | 2 ++ packages/format-library/package.json | 2 +- packages/hooks/CHANGELOG.md | 2 ++ packages/hooks/package.json | 2 +- packages/i18n/package.json | 2 +- packages/is-shallow-equal/package.json | 2 +- packages/keycodes/package.json | 2 +- packages/library-export-default-webpack-plugin/package.json | 2 +- packages/list-reusable-blocks/CHANGELOG.md | 2 ++ packages/list-reusable-blocks/package.json | 2 +- packages/notices/CHANGELOG.md | 2 ++ packages/notices/package.json | 2 +- packages/npm-package-json-lint-config/package.json | 2 +- packages/nux/CHANGELOG.md | 2 ++ packages/nux/package.json | 2 +- packages/plugins/CHANGELOG.md | 2 ++ packages/plugins/package.json | 2 +- packages/postcss-themes/package.json | 2 +- packages/redux-routine/package.json | 2 +- packages/rich-text/CHANGELOG.md | 2 ++ packages/rich-text/package.json | 2 +- packages/scripts/CHANGELOG.md | 2 +- packages/scripts/package.json | 2 +- packages/url/CHANGELOG.md | 2 +- packages/url/package.json | 2 +- packages/viewport/CHANGELOG.md | 2 +- packages/viewport/package.json | 2 +- 59 files changed, 76 insertions(+), 44 deletions(-) diff --git a/packages/annotations/CHANGELOG.md b/packages/annotations/CHANGELOG.md index 9d8994d0b4861..cb15ba170bbe4 100644 --- a/packages/annotations/CHANGELOG.md +++ b/packages/annotations/CHANGELOG.md @@ -1,3 +1,5 @@ +## 1.0.5 (2019-01-03) + ## 1.0.4 (2018-12-12) ## 1.0.3 (2018-11-21) diff --git a/packages/annotations/package.json b/packages/annotations/package.json index 3e909c7a0852c..6a89aa196b265 100644 --- a/packages/annotations/package.json +++ b/packages/annotations/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/annotations", - "version": "1.0.4", + "version": "1.0.6", "description": "Annotate content in the Gutenberg editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/api-fetch/package.json b/packages/api-fetch/package.json index 0c3ab7e8e9bbc..33eb220c8f857 100644 --- a/packages/api-fetch/package.json +++ b/packages/api-fetch/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/api-fetch", - "version": "2.2.6", + "version": "2.2.8", "description": "Utility to make WordPress REST API requests.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/babel-plugin-import-jsx-pragma/package.json b/packages/babel-plugin-import-jsx-pragma/package.json index 1c2bb7de9bb95..12637993733e6 100644 --- a/packages/babel-plugin-import-jsx-pragma/package.json +++ b/packages/babel-plugin-import-jsx-pragma/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/babel-plugin-import-jsx-pragma", - "version": "1.1.2", + "version": "1.1.3", "description": "Babel transform plugin for automatically injecting an import to be used as the pragma for the React JSX Transform plugin.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/babel-plugin-makepot/package.json b/packages/babel-plugin-makepot/package.json index 819415f3b9732..27797796bc920 100644 --- a/packages/babel-plugin-makepot/package.json +++ b/packages/babel-plugin-makepot/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/babel-plugin-makepot", - "version": "2.1.2", + "version": "2.1.3", "description": "WordPress Babel internationalization (i18n) plugin.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/babel-preset-default/package.json b/packages/babel-preset-default/package.json index 95bc302227f14..1a22f93300864 100644 --- a/packages/babel-preset-default/package.json +++ b/packages/babel-preset-default/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/babel-preset-default", - "version": "3.0.1", + "version": "3.0.2", "description": "Default Babel preset for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-library/CHANGELOG.md b/packages/block-library/CHANGELOG.md index 7431ba83e3e71..65cbad9ef8dc4 100644 --- a/packages/block-library/CHANGELOG.md +++ b/packages/block-library/CHANGELOG.md @@ -1,3 +1,5 @@ +## 2.2.12 (2019-01-03) + ## 2.2.11 (2018-12-18) ## 2.2.10 (2018-12-12) diff --git a/packages/block-library/package.json b/packages/block-library/package.json index 54846d2fafef8..03123f6c4aadd 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-library", - "version": "2.2.11", + "version": "2.2.13", "description": "Block library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-serialization-default-parser/CHANGELOG.md b/packages/block-serialization-default-parser/CHANGELOG.md index aaa75a2df97c0..1fd5900744ecc 100644 --- a/packages/block-serialization-default-parser/CHANGELOG.md +++ b/packages/block-serialization-default-parser/CHANGELOG.md @@ -1,3 +1,5 @@ +## 2.0.3 (2019-01-03) + ## 2.0.2 (2018-12-12) ## 2.0.1 (2018-11-30) diff --git a/packages/block-serialization-default-parser/package.json b/packages/block-serialization-default-parser/package.json index b5482f3d1efd4..777bad8d74fe8 100644 --- a/packages/block-serialization-default-parser/package.json +++ b/packages/block-serialization-default-parser/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-serialization-default-parser", - "version": "2.0.2", + "version": "2.0.4", "description": "Block serialization specification parser for WordPress posts.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-serialization-spec-parser/package.json b/packages/block-serialization-spec-parser/package.json index d7bafbb41539c..16ca515258977 100644 --- a/packages/block-serialization-spec-parser/package.json +++ b/packages/block-serialization-spec-parser/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-serialization-spec-parser", - "version": "2.0.2", + "version": "2.0.3", "description": "Block serialization specification parser for WordPress posts.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/blocks/CHANGELOG.md b/packages/blocks/CHANGELOG.md index e268a8260c14f..1f40a53235f4c 100644 --- a/packages/blocks/CHANGELOG.md +++ b/packages/blocks/CHANGELOG.md @@ -1,3 +1,5 @@ +## 6.0.5 (2019-01-03) + ## 6.0.4 (2018-12-12) ## 6.0.3 (2018-11-30) diff --git a/packages/blocks/package.json b/packages/blocks/package.json index afe971954c18c..f2d0534fe8ae1 100644 --- a/packages/blocks/package.json +++ b/packages/blocks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/blocks", - "version": "6.0.4", + "version": "6.0.6", "description": "Block API for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/browserslist-config/package.json b/packages/browserslist-config/package.json index 928f2c2dffb23..81baf916c0726 100644 --- a/packages/browserslist-config/package.json +++ b/packages/browserslist-config/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/browserslist-config", - "version": "2.2.2", + "version": "2.2.3", "description": "WordPress Browserslist shared configuration.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 8dfd644e15ea0..eb67ff9e79332 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -1,3 +1,5 @@ +## 7.0.5 (2019-01-03) + ## 7.0.4 (2018-12-12) ## 7.0.3 (2018-11-30) diff --git a/packages/components/package.json b/packages/components/package.json index 2425f1bf79dcb..2b97622f3da9c 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/components", - "version": "7.0.4", + "version": "7.0.6", "description": "UI components for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/compose/package.json b/packages/compose/package.json index ace35bcce8ba3..dc744f79ba6f2 100644 --- a/packages/compose/package.json +++ b/packages/compose/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/compose", - "version": "3.0.0", + "version": "3.0.1", "description": "WordPress higher-order components (HOCs).", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/core-data/CHANGELOG.md b/packages/core-data/CHANGELOG.md index 45a5c0b714228..5b1952e7ac6fa 100644 --- a/packages/core-data/CHANGELOG.md +++ b/packages/core-data/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.0.16 (Unreleased) +## 2.0.16 (2019-01-03) ### Bug Fixes diff --git a/packages/core-data/package.json b/packages/core-data/package.json index 316ac6907f9ab..b28d5c2eb0c24 100644 --- a/packages/core-data/package.json +++ b/packages/core-data/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/core-data", - "version": "2.0.15", + "version": "2.0.17", "description": "Access to and manipulation of core WordPress entities.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/custom-templated-path-webpack-plugin/package.json b/packages/custom-templated-path-webpack-plugin/package.json index f1d922ffd5949..62c7effedda55 100644 --- a/packages/custom-templated-path-webpack-plugin/package.json +++ b/packages/custom-templated-path-webpack-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/custom-templated-path-webpack-plugin", - "version": "1.1.5", + "version": "1.1.6", "description": "Webpack plugin for creating custom path template tags.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/data/CHANGELOG.md b/packages/data/CHANGELOG.md index 7ebc677c61e07..f3b1f067ed648 100644 --- a/packages/data/CHANGELOG.md +++ b/packages/data/CHANGELOG.md @@ -1,4 +1,4 @@ -## 4.2.0 (Unreleased) +## 4.2.0 (2019-01-03) ### Enhancements diff --git a/packages/data/package.json b/packages/data/package.json index 825ddb8f71295..7186a0c69fba5 100644 --- a/packages/data/package.json +++ b/packages/data/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/data", - "version": "4.1.0", + "version": "4.2.1", "description": "Data module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/deprecated/CHANGELOG.md b/packages/deprecated/CHANGELOG.md index 95f4b61429325..0ea8fe0294d33 100644 --- a/packages/deprecated/CHANGELOG.md +++ b/packages/deprecated/CHANGELOG.md @@ -1,3 +1,5 @@ +## 2.0.4 (2019-01-03) + ## 2.0.0 (2018-09-05) ### Breaking Change diff --git a/packages/deprecated/package.json b/packages/deprecated/package.json index 3d9d6c148d230..b54678dc9969a 100644 --- a/packages/deprecated/package.json +++ b/packages/deprecated/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/deprecated", - "version": "2.0.3", + "version": "2.0.5", "description": "Deprecation utility for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/dom/CHANGELOG.md b/packages/dom/CHANGELOG.md index 9e07c5fa5ef3c..fa0529b1f69f2 100644 --- a/packages/dom/CHANGELOG.md +++ b/packages/dom/CHANGELOG.md @@ -1,3 +1,5 @@ +## 2.0.8 (2019-01-03) + ## 2.0.7 (2018-11-20) ## 2.0.6 (2018-11-09) diff --git a/packages/dom/package.json b/packages/dom/package.json index 4da707b746bfe..88e637933eae4 100644 --- a/packages/dom/package.json +++ b/packages/dom/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/dom", - "version": "2.0.7", + "version": "2.0.8", "description": "DOM utilities module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/edit-post/CHANGELOG.md b/packages/edit-post/CHANGELOG.md index 21953d9218761..858a6f3bd017a 100644 --- a/packages/edit-post/CHANGELOG.md +++ b/packages/edit-post/CHANGELOG.md @@ -1,3 +1,5 @@ +## 3.1.7 (2019-01-03) + ## 3.1.6 (2018-12-18) ## 3.1.5 (2018-12-12) diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json index 2e3d2d44c7106..fc44436ec4792 100644 --- a/packages/edit-post/package.json +++ b/packages/edit-post/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-post", - "version": "3.1.6", + "version": "3.1.8", "description": "Edit Post module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/editor/CHANGELOG.md b/packages/editor/CHANGELOG.md index 672be5a61d0a0..026353023b61d 100644 --- a/packages/editor/CHANGELOG.md +++ b/packages/editor/CHANGELOG.md @@ -1,9 +1,11 @@ -## 9.0.7 (Unreleased) +## 9.0.8 (Unreleased) ### Internal - Removed `jQuery` dependency +## 9.0.7 (2019-01-03) + ## 9.0.6 (2018-12-18) ### Bug Fixes diff --git a/packages/editor/package.json b/packages/editor/package.json index a1b72033210d4..79684c95217c3 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/editor", - "version": "9.0.6", + "version": "9.0.8", "description": "Building blocks for WordPress editors.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/element/package.json b/packages/element/package.json index 19707938c6a67..94c33e94f0cb5 100644 --- a/packages/element/package.json +++ b/packages/element/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/element", - "version": "2.1.8", + "version": "2.1.9", "description": "Element React module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 13fbae86bfd68..125acdac5189e 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/eslint-plugin", - "version": "1.0.0", + "version": "1.0.1", "description": "ESLint plugin for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/format-library/CHANGELOG.md b/packages/format-library/CHANGELOG.md index 9726c7ff3bb56..df1eeff7c6dbf 100644 --- a/packages/format-library/CHANGELOG.md +++ b/packages/format-library/CHANGELOG.md @@ -1,3 +1,5 @@ +## 1.2.10 (2019-01-03) + ## 1.2.9 (2018-12-18) ## 1.2.8 (2018-12-12) diff --git a/packages/format-library/package.json b/packages/format-library/package.json index c471b5507b3ea..a18456013c62c 100644 --- a/packages/format-library/package.json +++ b/packages/format-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/format-library", - "version": "1.2.9", + "version": "1.2.11", "description": "Format library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/hooks/CHANGELOG.md b/packages/hooks/CHANGELOG.md index f74e2c1da17a8..4e0dbdce40e7d 100644 --- a/packages/hooks/CHANGELOG.md +++ b/packages/hooks/CHANGELOG.md @@ -1,3 +1,5 @@ +## 2.0.4 (2019-01-03) + ## 2.0.0 (2018-09-05) ### Breaking Change diff --git a/packages/hooks/package.json b/packages/hooks/package.json index cef3b444b83d7..4e4cf9e95cf19 100644 --- a/packages/hooks/package.json +++ b/packages/hooks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/hooks", - "version": "2.0.3", + "version": "2.0.5", "description": "WordPress hooks library.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/i18n/package.json b/packages/i18n/package.json index a1005eac73e6e..57535a5b81f79 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/i18n", - "version": "3.1.0", + "version": "3.1.1", "description": "WordPress internationalization (i18n) library.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/is-shallow-equal/package.json b/packages/is-shallow-equal/package.json index f5553ceb41a47..5826f4e45b8f7 100644 --- a/packages/is-shallow-equal/package.json +++ b/packages/is-shallow-equal/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/is-shallow-equal", - "version": "1.1.4", + "version": "1.1.5", "description": "Test for shallow equality between two objects or arrays.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/keycodes/package.json b/packages/keycodes/package.json index 69970f60f3890..132759c8ed323 100644 --- a/packages/keycodes/package.json +++ b/packages/keycodes/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/keycodes", - "version": "2.0.5", + "version": "2.0.6", "description": "Keycodes utilities for WordPress. Used to check for keyboard events across browsers/operating systems.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/library-export-default-webpack-plugin/package.json b/packages/library-export-default-webpack-plugin/package.json index 52d28f6e9e184..c43a22704ef85 100644 --- a/packages/library-export-default-webpack-plugin/package.json +++ b/packages/library-export-default-webpack-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/library-export-default-webpack-plugin", - "version": "1.0.4", + "version": "1.0.5", "description": "Webpack plugin for exporting default property for selected libraries.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/list-reusable-blocks/CHANGELOG.md b/packages/list-reusable-blocks/CHANGELOG.md index dd73de16abca4..f92feaacc6ecb 100644 --- a/packages/list-reusable-blocks/CHANGELOG.md +++ b/packages/list-reusable-blocks/CHANGELOG.md @@ -1,3 +1,5 @@ +## 1.1.18 (2019-01-03) + ## 1.1.17 (2018-12-12) ## 1.1.16 (2018-11-30) diff --git a/packages/list-reusable-blocks/package.json b/packages/list-reusable-blocks/package.json index 2ddb7b4afdf53..9a51dc4dd0957 100644 --- a/packages/list-reusable-blocks/package.json +++ b/packages/list-reusable-blocks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/list-reusable-blocks", - "version": "1.1.17", + "version": "1.1.19", "description": "Adding Export/Import support to the reusable blocks listing.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/notices/CHANGELOG.md b/packages/notices/CHANGELOG.md index 76f6ad9cadaaa..02d757332e51e 100644 --- a/packages/notices/CHANGELOG.md +++ b/packages/notices/CHANGELOG.md @@ -1,3 +1,5 @@ +## 1.1.2 (2019-01-03) + ## 1.1.1 (2018-12-12) ## 1.1.0 (2018-11-20) diff --git a/packages/notices/package.json b/packages/notices/package.json index c7b0b8c623294..6e3ad5374faba 100644 --- a/packages/notices/package.json +++ b/packages/notices/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/notices", - "version": "1.1.1", + "version": "1.1.3", "description": "State management for notices.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/npm-package-json-lint-config/package.json b/packages/npm-package-json-lint-config/package.json index ddf65a0f15c8c..6aa099ab26dde 100644 --- a/packages/npm-package-json-lint-config/package.json +++ b/packages/npm-package-json-lint-config/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/npm-package-json-lint-config", - "version": "1.1.5", + "version": "1.1.6", "description": "WordPress npm-package-json-lint shareable configuration.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/nux/CHANGELOG.md b/packages/nux/CHANGELOG.md index 41aaa82bc4ced..546a22e7930b4 100644 --- a/packages/nux/CHANGELOG.md +++ b/packages/nux/CHANGELOG.md @@ -1,3 +1,5 @@ +## 3.0.6 (2019-01-03) + ## 3.0.5 (2018-12-12) ## 3.0.4 (2018-11-30) diff --git a/packages/nux/package.json b/packages/nux/package.json index c9db7b58c0fe0..b0015ff5c201f 100644 --- a/packages/nux/package.json +++ b/packages/nux/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/nux", - "version": "3.0.5", + "version": "3.0.7", "description": "NUX (New User eXperience) module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/plugins/CHANGELOG.md b/packages/plugins/CHANGELOG.md index 11b1389222d31..ae2a7d777e863 100644 --- a/packages/plugins/CHANGELOG.md +++ b/packages/plugins/CHANGELOG.md @@ -1,3 +1,5 @@ +## 2.0.10 (2019-01-03) + ## 2.0.9 (2018-11-15) ## 2.0.8 (2018-11-09) diff --git a/packages/plugins/package.json b/packages/plugins/package.json index 14810ef0814d0..2475c6e9a557f 100644 --- a/packages/plugins/package.json +++ b/packages/plugins/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/plugins", - "version": "2.0.9", + "version": "2.0.11", "description": "Plugins module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/postcss-themes/package.json b/packages/postcss-themes/package.json index 88727a2f2fe09..30b5a6b85572f 100644 --- a/packages/postcss-themes/package.json +++ b/packages/postcss-themes/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/postcss-themes", - "version": "1.0.4", + "version": "1.0.5", "description": "PostCSS plugin to generate theme colors.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/redux-routine/package.json b/packages/redux-routine/package.json index bd28bcde065c7..a60b67801d0c1 100644 --- a/packages/redux-routine/package.json +++ b/packages/redux-routine/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/redux-routine", - "version": "3.0.3", + "version": "3.0.4", "description": "Redux middleware for generator coroutines.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/rich-text/CHANGELOG.md b/packages/rich-text/CHANGELOG.md index 38b48d8af8766..b0cb3b8617db7 100644 --- a/packages/rich-text/CHANGELOG.md +++ b/packages/rich-text/CHANGELOG.md @@ -1,3 +1,5 @@ +## 3.0.4 (2019-01-03) + ## 3.0.3 (2018-12-12) ### Internal diff --git a/packages/rich-text/package.json b/packages/rich-text/package.json index 18b5352a78a91..7c475baec9a52 100644 --- a/packages/rich-text/package.json +++ b/packages/rich-text/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/rich-text", - "version": "3.0.3", + "version": "3.0.5", "description": "Rich text value and manipulation API.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index 3ff87bef238a4..406db778448f6 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.5.0 (Unreleased) +## 2.5.0 (2019-01-09) ### New Features diff --git a/packages/scripts/package.json b/packages/scripts/package.json index e6350aa34da1d..4c6b0b918b8ad 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/scripts", - "version": "2.4.4", + "version": "2.5.0", "description": "Collection of reusable scripts for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/url/CHANGELOG.md b/packages/url/CHANGELOG.md index 14f0b24650bd5..3c0ec6a60d906 100644 --- a/packages/url/CHANGELOG.md +++ b/packages/url/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.3.3 (Unreleased) +## 2.3.3 (2019-01-03) ### Bug Fixes diff --git a/packages/url/package.json b/packages/url/package.json index 43b919a19eb78..18bdecd4c1d96 100644 --- a/packages/url/package.json +++ b/packages/url/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/url", - "version": "2.3.2", + "version": "2.3.3", "description": "WordPress URL utilities.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/viewport/CHANGELOG.md b/packages/viewport/CHANGELOG.md index 73ded0598caa8..e3a0fbd033c08 100644 --- a/packages/viewport/CHANGELOG.md +++ b/packages/viewport/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.1.0 (Unreleased) +## 2.1.0 (2019-01-03) ### Improvements diff --git a/packages/viewport/package.json b/packages/viewport/package.json index e74015bd91349..a1c94c73801c4 100644 --- a/packages/viewport/package.json +++ b/packages/viewport/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/viewport", - "version": "2.0.13", + "version": "2.1.1", "description": "Viewport module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", From f7315b0bdac03874842ed1732cf9b7ca2a5b540a Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Thu, 10 Jan 2019 06:42:43 -0800 Subject: [PATCH 127/691] Improve documentation for unregisterBlockStyle (#13272) * Add additional info around unregistering, include link to JS tutorial * Dont use ES6 syntax * Update docs/designers-developers/developers/filters/block-filters.md Co-Authored-By: mkaz <marcus@mkaz.com> * Update docs/designers-developers/developers/filters/block-filters.md Co-Authored-By: mkaz <marcus@mkaz.com> * Simplify link * Add link for race condition definition --- .../developers/filters/block-filters.md | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/docs/designers-developers/developers/filters/block-filters.md b/docs/designers-developers/developers/filters/block-filters.md index 33384edd58153..4f28cf17a9f18 100644 --- a/docs/designers-developers/developers/filters/block-filters.md +++ b/docs/designers-developers/developers/filters/block-filters.md @@ -4,7 +4,7 @@ To modify the behavior of existing blocks, WordPress exposes several APIs: ### Block Style Variations -Block Style Variations allow providing alternative styles to existing blocks. They work by adding a className to the block's wrapper. This className can be used to provide an alternative styling for the block if the style variation is selected. +Block Style Variations allow providing alternative styles to existing blocks. They work by adding a className to the block's wrapper. This className can be used to provide an alternative styling for the block if the style variation is selected. See the [Getting Started with JavaScript tutorial](/docs/designers-developers/developers/tutorials/javascript/) for a full example. _Example:_ @@ -24,10 +24,34 @@ To remove a block style variation use `wp.blocks.unregisterBlockStyle()`. _Example:_ ```js -wp.blocks.unregisterBlockStyle( 'core/quote', 'fancy-quote' ); +wp.blocks.unregisterBlockStyle( 'core/quote', 'large' ); ``` -The above removes the variation named `fancy-quote` from the `core/quote` block. +The above removes the variation named `large` from the `core/quote` block. + +**Important:** When unregistering a block style, there can be a [race condition](https://en.wikipedia.org/wiki/Race_condition) on which code runs first: registering the style, or unregistering the style. You want your unregister code to run last. The way to do that is specify the component that is registering the style as a dependency, in this case `wp-edit-post`. Additionally, using `wp.domReady()` ensures the unregister code runs once the dom is loaded. + +Enqueue your JavaScript with the following PHP code: + +```php +function myguten_enqueue() { + wp_enqueue_script( + 'myguten-script', + plugins_url( 'myguten.js', __FILE__ ), + array( 'wp-blocks', 'wp-dom-ready', 'wp-edit-post' ), + filemtime( plugin_dir_path( __FILE__ ) . '/myguten.js' ) + ); +} +add_action( 'enqueue_block_editor_assets', 'myguten_enqueue' ); +``` + +The JavaScript code in `myguten.js`: + +```js +wp.domReady( function() { + wp.blocks.unregisterBlockStyle( 'core/quote', 'large' ); +} ); +``` ### Filters From a96bff1f051474372ca2b525bdc16d6512d85b06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Thu, 10 Jan 2019 18:21:30 +0100 Subject: [PATCH 128/691] Add a tutorial on how to add a plugin sidebar (#12944) --- .../assets/sidebar-style-and-controls.png | Bin 0 -> 10403 bytes .../assets/sidebar-up-and-running.png | Bin 0 -> 306275 bytes .../sidebar-tutorial/plugin-sidebar-0.md | 12 ++ .../plugin-sidebar-1-up-and-running.md | 56 +++++ .../plugin-sidebar-2-styles-and-controls.md | 97 +++++++++ .../plugin-sidebar-3-register-meta.md | 21 ++ .../plugin-sidebar-4-initialize-input.md | 118 +++++++++++ .../plugin-sidebar-5-update-meta.md | 94 +++++++++ .../plugin-sidebar-6-finishing-touches.md | 198 ++++++++++++++++++ docs/manifest.json | 42 ++++ docs/toc.json | 8 + 11 files changed, 646 insertions(+) create mode 100644 docs/designers-developers/assets/sidebar-style-and-controls.png create mode 100644 docs/designers-developers/assets/sidebar-up-and-running.png create mode 100644 docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md create mode 100644 docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-1-up-and-running.md create mode 100644 docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-2-styles-and-controls.md create mode 100644 docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-3-register-meta.md create mode 100644 docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-4-initialize-input.md create mode 100644 docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-5-update-meta.md create mode 100644 docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md diff --git a/docs/designers-developers/assets/sidebar-style-and-controls.png b/docs/designers-developers/assets/sidebar-style-and-controls.png new file mode 100644 index 0000000000000000000000000000000000000000..725ddfdd87a4bd130039779520201972f6752efd GIT binary patch literal 10403 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 literal 0 HcmV?d00001 diff --git a/docs/designers-developers/assets/sidebar-up-and-running.png b/docs/designers-developers/assets/sidebar-up-and-running.png new file mode 100644 index 0000000000000000000000000000000000000000..12bc2947d48a37cfef186f2703adb91f4f4871dc GIT binary patch literal 306275 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%0AH(4n|M-)ie)Rb8 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%0Dku2php~c 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 aPOb}$a{!p$6OCX1e5@_(%*kin@B9~;0BrsM literal 0 HcmV?d00001 diff --git a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md new file mode 100644 index 0000000000000..9e7efca41b793 --- /dev/null +++ b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md @@ -0,0 +1,12 @@ +# Creating a sidebar for your plugin + +This tutorial starts with you having an existing plugin setup and ready to add PHP and JavaScript code. Please, refer to [Getting started with JavaScript](../../../../../docs/designers-developers/developers/tutorials/javascript/) tutorial for an introduction to WordPress plugins and how to use JavaScript to extend the block editor. + + In the next sections, you're going to create a custom sidebar for a plugin that contains a text control so the user can update a value that is stored in the `post_meta` table. + +1. [Get a sidebar up and running](../../../../../docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-1-up-and-running.md) +2. [Tweak the sidebar style and add controls](../../../../../docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-2-styles-and-controls.md) +3. [Register a new meta field](../../../../../docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-3-register-meta.md) +4. [Initialize the input control with the meta field value](../../../../../docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-4-initialize-input.md) +5. [Update the meta field value when input's content changes](../../../../../docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-5-update-meta.md) +6. [Finishing touches](../../../../../docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md) diff --git a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-1-up-and-running.md b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-1-up-and-running.md new file mode 100644 index 0000000000000..54fc46066f272 --- /dev/null +++ b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-1-up-and-running.md @@ -0,0 +1,56 @@ +# Get a sidebar up and running + +The first step in the journey is to tell the editor that there is a new plugin that will have its own sidebar. You can do so by using the [registerPlugin](../../../../../docs/designers-developers/developers/packages/packages-plugins/), [PluginSidebar](../../../../../docs/designers-developers/developers/packages/packages-edit-post/#pluginsidebar), and [createElement](../../../../../docs/designers-developers/developers/packages/packages-element/) utilities provided by WordPress, to be found in the `@wordpress/plugins`, `@wordpress/edit-post`, and `@wordpress/element` [packages](../../../../../docs/designers-developers/developers/packages/), respectively. + +Add the following code to a JavaScript file called `plugin-sidebar.js` and save it within your plugin's directory: + +```js +( function( wp ) { + var registerPlugin = wp.plugins.registerPlugin; + var PluginSidebar = wp.editPost.PluginSidebar; + var el = wp.element.createElement; + + registerPlugin( 'my-plugin-sidebar', { + render: function() { + return el( PluginSidebar, + { + name: 'my-plugin-sidebar', + icon: 'admin-post', + title: 'My plugin sidebar', + }, + 'Meta field' + ); + }, + } ); +} )( window.wp ); +``` + +For this code to work, those utilities need to be available in the browser, so you tell WordPress to enqueue the packages that include them by introducing `wp-plugins`, `wp-edit-post`, and `wp-element` as dependencies of your script. + +Copy this code to a PHP file within your plugin's directory: + +```php +<?php + +/* +Plugin Name: Sidebar plugin +*/ + +function sidebar_plugin_register() { + wp_register_script( + 'plugin-sidebar-js', + plugins_url( 'plugin-sidebar.js', __FILE__ ), + array( 'wp-plugins', 'wp-edit-post', 'wp-element' ) + ); +} +add_action( 'init', 'sidebar_plugin_register' ); + +function sidebar_plugin_script_enqueue() { + wp_enqueue_script( 'plugin-sidebar-js' ); +} +add_action( 'enqueue_block_editor_assets', 'sidebar_plugin_script_enqueue' ); +``` + +After installing and activating this plugin, there is a new icon resembling a tack in the top-right of the editor. Upon clicking it, the plugin's sidebar will be opened: + +![Sidebar Up and Running](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/assets/sidebar-up-and-running.png) diff --git a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-2-styles-and-controls.md b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-2-styles-and-controls.md new file mode 100644 index 0000000000000..27e05ba9b2216 --- /dev/null +++ b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-2-styles-and-controls.md @@ -0,0 +1,97 @@ +# Tweak the sidebar style and add controls + +After the sidebar is up and running, the next step is to fill it up with the necessary components and basic styling. + +To visualize and edit the meta field value you'll use an input component. The `@wordpress/components` package contains many components available for you to reuse, and, specifically, the [TextControl](../../../../../docs/designers-developers/developers/components/text-control/) is aimed at creating an input field: + +```js +( function( wp ) { + var registerPlugin = wp.plugins.registerPlugin; + var PluginSidebar = wp.editPost.PluginSidebar; + var el = wp.element.createElement; + var Text = wp.components.TextControl; + + registerPlugin( 'my-plugin-sidebar', { + render: function() { + return el( PluginSidebar, + { + name: 'my-plugin-sidebar', + icon: 'admin-post', + title: 'My plugin sidebar', + }, + el( 'div', + { className: 'plugin-sidebar-content' }, + el( Text, { + label: 'Meta Block Field', + value: 'Initial value', + onChange: function( content ) { + console.log( 'content changed to ', content ); + }, + } ) + ) + ); + } + } ); +} )( window.wp ); +``` + +Update the `plugin-sidebar.js` with this new code. Notice that it uses a new utility called `wp.components` from the `@wordpress/components` package. Go ahead and add it as `wp-components` in the PHP dependencies array. + +It introduces a few changes from the previous section: + +* Added the CSS class `plugin-sidebar-content` to the `div` element to be able to add some styles. +* Substituted the raw _Meta field_ text with a `TextControl` component wrapped within the `div` element. + +With the new CSS class available you can now give the sidebar a bit of breath. Create a new file in your plugin directory called `plugin-sidebar.css` with the following contents: + +```css +.plugin-sidebar-content { + padding: 16px; +} +``` + +For WordPress to load this stylesheet in the editor and front-end, you need to tell it to enqueue it by using the [enqueue_block_editor_assets](https://developer.wordpress.org/reference/hooks/enqueue_block_editor_assets/) action hook. + +After those changes, the PHP code should look like this: + +```php +<?php + +/* +Plugin Name: Sidebar example +*/ + +function sidebar_plugin_register() { + wp_register_script( + 'plugin-sidebar-js', + plugins_url( 'plugin-sidebar.js', __FILE__ ), + array( + 'wp-plugins', + 'wp-edit-post', + 'wp-element', + 'wp-components' + ) + ); + wp_register_style( + 'plugin-sidebar-css', + plugins_url( 'plugin-sidebar.css', __FILE__ ) + ); +} +add_action( 'init', 'sidebar_plugin_register' ); + +function sidebar_plugin_script_enqueue() { + wp_enqueue_script( 'plugin-sidebar-js' ); +} +add_action( 'enqueue_block_editor_assets', 'sidebar_plugin_script_enqueue' ); + +function sidebar_plugin_style_enqueue() { + wp_enqueue_style( 'plugin-sidebar-css' ); +} +add_action( 'enqueue_block_assets', 'sidebar_plugin_style_enqueue' ); +``` + +Reload the editor and open the sidebar: + +![Sidebar with style and controls](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/assets/sidebar-style-and-controls.png) + +With the input control and the styling the sidebar looks nicer. This code doesn't let users to store or retrieve data just yet, so the next steps will focus on how to connect it to the meta block field. diff --git a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-3-register-meta.md b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-3-register-meta.md new file mode 100644 index 0000000000000..a4d150401e519 --- /dev/null +++ b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-3-register-meta.md @@ -0,0 +1,21 @@ +# Register the meta field + +To work with fields in the `post_meta` table, WordPress has a function called [register_meta](https://developer.wordpress.org/reference/functions/register_meta/). You're going to use it to register a new field called `sidebar_plugin_meta_block_field`, which will be a single string. Note that this field needs to be available through the [REST API](https://developer.wordpress.org/rest-api/) because that's how the block editor access data. + +Add this to the PHP code, within the `init` callback function: + +```php +register_meta( 'post', 'sidebar_plugin_meta_block_field', array( + 'show_in_rest' => true, + 'single' => true, + 'type' => 'string', +) ); +``` + +To make sure the field has been loaded, query the block editor [internal data structures](../../../../../docs/designers-developers/developers/data/), also known as _stores_. Open your browser's console, and execute this piece of code: + +```js +wp.data.select( 'core/editor' ).getCurrentPost().meta; +``` + +Before adding the `register_meta` function to the plugin, this code returns a void array, because WordPress hasn't been told to load any meta field yet. After registering the field, the same code will return an object containing the registered meta field you registered. diff --git a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-4-initialize-input.md b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-4-initialize-input.md new file mode 100644 index 0000000000000..38e09a2529548 --- /dev/null +++ b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-4-initialize-input.md @@ -0,0 +1,118 @@ +# Initialize the input control + +Now that the field is available in the editor store, it can be surfaced to the UI. The first step will be to extract the input control to a separate function so you can expand its functionality while the code stays clear. + +```js +( function( wp ) { + var registerPlugin = wp.plugins.registerPlugin; + var PluginSidebar = wp.editPost.PluginSidebar; + var el = wp.element.createElement; + var Text = wp.components.TextControl; + + var MetaBlockField = function() { + return el( Text, { + label: 'Meta Block Field', + value: 'Initial value', + onChange: function( content ) { + console.log( 'content changed to ', content ); + }, + } ); + } + + registerPlugin( 'my-plugin-sidebar', { + render: function() { + return el( PluginSidebar, + { + name: 'my-plugin-sidebar', + icon: 'admin-post', + title: 'My plugin sidebar', + }, + el( 'div', + { className: 'plugin-sidebar-content' }, + el( MetaBlockField ) + ) + ); + } + } ); +} )( window.wp ); +``` + +Now you can focus solely on the `MetaBlockField` component. The goal is to initialize it with the value of `sidebar_plugin_meta_block_field`, but also to keep it updated when that value changes. + +WordPress has [some utilities to work with data](../../../../../docs/designers-developers/developers/packages/packages-data/) from the stores. The first you're going to use is [withSelect](../../../../../docs/designers-developers/developers/packages/packages-data/#withselect-mapselecttoprops-function-function), whose signature is: + +```js +withSelect( + // a function that takes `select` as input + // and returns an object containing data +)( + // a function that takes the previous data as input + // and returns a component +); +``` + +`withSelect` is used to pass data to other components, and update them when the original data changes. Let's update the code to use it: + +```js +( function( wp ) { + var registerPlugin = wp.plugins.registerPlugin; + var PluginSidebar = wp.editPost.PluginSidebar; + var el = wp.element.createElement; + var Text = wp.components.TextControl; + var withSelect = wp.data.withSelect; + + var mapSelectToProps = function( select ) { + return { + metaFieldValue: select( 'core/editor' ) + .getEditedPostAttribute( 'meta' ) + [ 'sidebar_plugin_meta_block_field' ] + } + } + + var MetaBlockField = function( props ) { + return el( Text, { + label: 'Meta Block Field', + value: props.metaFieldValue, + onChange: function( content ) { + console.log( 'content has changed to ', content ); + }, + } ); + } + + var MetaBlockFieldWithData = withSelect( mapSelectToProps )( MetaBlockField ); + + registerPlugin( 'my-plugin-sidebar', { + render: function() { + return el( PluginSidebar, + { + name: 'my-plugin-sidebar', + icon: 'admin-post', + title: 'My plugin sidebar', + }, + el( 'div', + { className: 'plugin-sidebar-content' }, + el( MetaBlockFieldWithData ) + ) + ); + } + } ); +} )( window.wp ); +``` + +Copy this code to the JavaScript file. Note that it now uses the `wp.data.withSelect` utility to be found in the `@wordpress/data` package. Go ahead and add `wp-data` as a dependency in the PHP script. + +This is how the code changes from the previous section: + +* The `MetaBlockField` function has now a `props` argument as input. It contains the data object returned by the `mapSelectToProps` function, which it uses to initialize its value property. +* The component rendered within the `div` element was also updated, the plugin now uses `MetaBlockFieldWithData`. This will be updated every time the original data changes. +* [getEditedPostAttribute](../../../../../docs/designers-developers/developers/data/data-core-editor/#geteditedpostattribute) is used to retrieve data instead of [getCurrentPost](../../../../../docs/designers-developers/developers/data/data-core-editor/#getcurrentpost) because it returns the most recent values of the post, including user editions that haven't been yet saved. + +Update the code and open the sidebar. The input's content is no longer `Initial value` but a void string. Users can't type values yet, but let's check that the component is updated if the value in the store changes. Open the browser's console, execute + +```js +wp.data.dispatch( 'core/editor' ).editPost( + { meta: { sidebar_plugin_meta_block_field: 'hello world!' } } +); +``` + +and observe how the contents of the input component change! diff --git a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-5-update-meta.md b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-5-update-meta.md new file mode 100644 index 0000000000000..0cb56a29b7e85 --- /dev/null +++ b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-5-update-meta.md @@ -0,0 +1,94 @@ +# Update the meta field when the input's content changes + +The last step in the journey is to update the meta field when the input content changes. To do that, you'll use another utility from the `@wordpress/data` package, [withDispatch](../../../../../docs/designers-developers/developers/packages/packages-data/#withdispatch-mapdispatchtoprops-function-function). + +`withDispatch` works similarly to `withSelect`. It takes two functions, the first returns an object with data, and the second takes that data object as input and returns a new UI component. Let's see how to use it: + +```js +( function( wp ) { + var registerPlugin = wp.plugins.registerPlugin; + var PluginSidebar = wp.editPost.PluginSidebar; + var el = wp.element.createElement; + var Text = wp.components.TextControl; + var withSelect = wp.data.withSelect; + var withDispatch = wp.data.withDispatch; + + var mapSelectToProps = function( select ) { + return { + metaFieldValue: select( 'core/editor' ) + .getEditedPostAttribute( 'meta' ) + [ 'sidebar_plugin_meta_block_field' ], + } + } + + var mapDispatchToProps = function( dispatch ) { + return { + setMetaFieldValue: function( value ) { + dispatch( 'core/editor' ).editPost( + { meta: { sidebar_plugin_meta_block_field: value } } + ); + } + } + } + + var MetaBlockField = function( props ) { + return el( Text, { + label: 'Meta Block Field', + value: props.metaFieldValue, + onChange: function( content ) { + props.setMetaFieldValue( content ); + }, + } ); + } + + var MetaBlockFieldWithData = withSelect( mapSelectToProps )( MetaBlockField ); + var MetaBlockFieldWithDataAndActions = withDispatch( mapDispatchToProps )( MetaBlockFieldWithData ); + + registerPlugin( 'my-plugin-sidebar', { + render: function() { + return el( PluginSidebar, + { + name: 'my-plugin-sidebar', + icon: 'admin-post', + title: 'My plugin sidebar', + }, + el( 'div', + { className: 'plugin-sidebar-content' }, + el( MetaBlockFieldWithDataAndActions ) + ) + ); + } + } ); +} )( window.wp ); +``` + +Here's how it changed from the previous section: + +* Added a new `mapDispatchToProps` function that will be passed to `withDispatch`. It takes `dispatch` as input and returns an object containing functions to update the internal data structures of the editor. These functions are also known as _actions_. +* By calling `setMetaFieldValue` every time the user types something within the input control, we're effectively updating the editor store on each key stroke. +* The `props` argument to the `MetaBlockField` component contains now the data passed by `mapSelectToProps` and the actions passed by `mapDispatchToProps`. + +Copy this new code to the JavaScript file, load the sidebar and see how the input value gets updated as you type. You may want to check that the internal data structures are updated as well. Type something in the input control, and execute the following instruction in your browser's console: + +```js +wp.data.select( 'core/editor' ).getEditedPostAttribute( 'meta' )[ + 'sidebar_plugin_meta_block_field' +]; +``` + +The message displayed should be what you typed in the input. + +Now, after doing some changes, hit the "Save draft" button or publish the post. Then, reload the editor page. The browser has now new content, fresh from the database. You want to confirm that what you typed was stored properly in the database, and has been reloaded in the current post data structure. Open the sidebar and make sure the input control is initialized with the last value you typed. + +One last check. At this point, because you haven't edited the input yet, the current post and the edited attributes should be the same. Confirm that by executing this code in your browser's console: + +```js +wp.data.select( 'core/editor' ).getCurrentPost()[ 'meta' ][ + 'sidebar_plugin_meta_block_field' +]; +wp.data.select( 'core/editor' ).getEditedPostAttribute( 'meta' )[ + 'sidebar_plugin_meta_block_field' +]; +``` + +This is it! You now have a custom sidebar that updates `post_meta` contents. diff --git a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md new file mode 100644 index 0000000000000..e15b452d59762 --- /dev/null +++ b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md @@ -0,0 +1,198 @@ +# Finishing touches + +Your JavaScript code now works as expected, here are a few ways to simplify and make it easier to change in the future. + +The first step is to convert the functions `mapSelectToProps` and `mapDispatchToProps` to anonymous functions that get passed directly to `withSelect` and `withData`, respectively: + +```js +( function( wp ) { + var registerPlugin = wp.plugins.registerPlugin; + var PluginSidebar = wp.editPost.PluginSidebar; + var el = wp.element.createElement; + var Text = wp.components.TextControl; + var withSelect = wp.data.withSelect; + var withDispatch = wp.data.withDispatch; + + var MetaBlockField = function( props ) { + return el( Text, { + label: 'Meta Block Field', + value: props.metaFieldValue, + onChange: function( content ) { + props.setMetaFieldValue( content ); + }, + } ); + } + + var MetaBlockFieldWithData = withSelect( function( select ) { + return { + metaFieldValue: select( 'core/editor' ) + .getEditedPostAttribute( 'meta' ) + [ 'sidebar_plugin_meta_block_field' ], + } + } )( MetaBlockField ); + + var MetaBlockFieldWithDataAndActions = withDispatch( + function( dispatch ) { + return { + setMetaFieldValue: function( value ) { + dispatch( 'core/editor' ).editPost( + { meta: { sidebar_plugin_meta_block_field: value } } + ); + } + } + } + )( MetaBlockFieldWithData ); + + registerPlugin( 'my-plugin-sidebar', { + render: function() { + return el( PluginSidebar, + { + name: 'my-plugin-sidebar', + icon: 'admin-post', + title: 'My plugin sidebar', + }, + el( 'div', + { className: 'plugin-sidebar-content' }, + el( MetaBlockFieldWithDataAndActions ) + ) + ); + } + } ); +} )( window.wp ); +``` + +Next, merge `MetaBlockField`, `MetaBlockFieldWithData`, and `MetaBlockFieldWithDataAndActions` into one function called `MetaBlockField` that gets passed to the `div` element. The `@wordpress/compose` package offers an utility to concatenate functions called `compose`. Don't forget adding `wp-compose` to the dependencies array in the PHP script. + +```js +( function( wp ) { + var registerPlugin = wp.plugins.registerPlugin; + var PluginSidebar = wp.editPost.PluginSidebar; + var el = wp.element.createElement; + var Text = wp.components.TextControl; + var withSelect = wp.data.withSelect; + var withDispatch = wp.data.withDispatch; + var compose = wp.compose.compose; + + var MetaBlockField = compose( + withDispatch( function( dispatch ) { + return { + setMetaFieldValue: function( value ) { + dispatch( 'core/editor' ).editPost( + { meta: { sidebar_plugin_meta_block_field: value } } + ); + } + } + } ), + withSelect( function( select ) { + return { + metaFieldValue: select( 'core/editor' ) + .getEditedPostAttribute( 'meta' ) + [ 'sidebar_plugin_meta_block_field' ], + } + } ) + )( function( props ) { + return el( Text, { + label: 'Meta Block Field', + value: props.metaFieldValue, + onChange: function( content ) { + props.setMetaFieldValue( content ); + }, + } ); + } ); + + registerPlugin( 'my-plugin-sidebar', { + render: function() { + return el( PluginSidebar, + { + name: 'my-plugin-sidebar', + icon: 'admin-post', + title: 'My plugin sidebar', + }, + el( 'div', + { className: 'plugin-sidebar-content' }, + el( MetaBlockField ) + ) + ); + } + } ); +} )( window.wp ); +``` + +Finally, extract the meta field name (`sidebar_plugin_meta_block_field`) from the `withSelect` and `withDispatch` functions to a single place, so it's easier to change in the future. You can leverage the fact that `withSelect` and `withDispatch` first functions can take the props of the UI component they wrap as a second argument. For example: + +```js +// ... +var MetaBlockFieldWithData = withSelect( + function( select, props ) { + console.log( props.metaFieldName ); + } +)( MetaBlockField ); + +// ... + el( + MetaBlockFieldWithData, + { metaFieldName: 'sidebar_plugin_meta_block_field' } + ) +// ... +``` + +Notice how the `metaFieldName` can be accessed within `withSelect`. Let's change the code to take advantage of that: + +```js +( function( wp ) { + var registerPlugin = wp.plugins.registerPlugin; + var PluginSidebar = wp.editPost.PluginSidebar; + var el = wp.element.createElement; + var Text = wp.components.TextControl; + var withSelect = wp.data.withSelect; + var withDispatch = wp.data.withDispatch; + var compose = wp.compose.compose; + + var MetaBlockField = compose( + withDispatch( function( dispatch, props ) { + return { + setMetaFieldValue: function( value ) { + dispatch( 'core/editor' ).editPost( + { meta: { [ props.fieldName ]: value } } + ); + } + } + } ), + withSelect( function( select, props ) { + return { + metaFieldValue: select( 'core/editor' ) + .getEditedPostAttribute( 'meta' ) + [ props.fieldName ], + } + } ) + )( function( props ) { + return el( Text, { + label: 'Meta Block Field', + value: props.metaFieldValue, + onChange: function( content ) { + props.setMetaFieldValue( content ); + }, + } ); + } ); + + registerPlugin( 'my-plugin-sidebar', { + render: function() { + return el( PluginSidebar, + { + name: 'my-plugin-sidebar', + icon: 'admin-post', + title: 'My plugin sidebar', + }, + el( 'div', + { className: 'plugin-sidebar-content' }, + el( MetaBlockField, + { fieldName: 'sidebar_plugin_meta_block_field' } + ) + ) + ); + } + } ); +} )( window.wp ); +``` + +That's it. You have now a compact version of the original code. Go ahead and add more functionality to your plugin, review other tutorials, or create your next gig! diff --git a/docs/manifest.json b/docs/manifest.json index 989677c1205a5..69a3be09a4e72 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -239,6 +239,48 @@ "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/scope-your-code.md", "parent": "javascript" }, + { + "title": "Creating a sidebar for your plugin", + "slug": "plugin-sidebar-0", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md", + "parent": "tutorials" + }, + { + "title": "Get a sidebar up and running", + "slug": "plugin-sidebar-1-up-and-running", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-1-up-and-running.md", + "parent": "plugin-sidebar-0" + }, + { + "title": "Tweak the sidebar style and add controls", + "slug": "plugin-sidebar-2-styles-and-controls", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-2-styles-and-controls.md", + "parent": "plugin-sidebar-0" + }, + { + "title": "Register the meta field", + "slug": "plugin-sidebar-3-register-meta", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-3-register-meta.md", + "parent": "plugin-sidebar-0" + }, + { + "title": "Initialize the input control", + "slug": "plugin-sidebar-4-initialize-input", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-4-initialize-input.md", + "parent": "plugin-sidebar-0" + }, + { + "title": "Update the meta field when the input's content changes", + "slug": "plugin-sidebar-5-update-meta", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-5-update-meta.md", + "parent": "plugin-sidebar-0" + }, + { + "title": "Finishing touches", + "slug": "plugin-sidebar-6-finishing-touches", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md", + "parent": "plugin-sidebar-0" + }, { "title": "Designer Documentation", "slug": "designers", diff --git a/docs/toc.json b/docs/toc.json index fede03372f5d9..5f30be816d41c 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -44,6 +44,14 @@ {"docs/designers-developers/developers/tutorials/javascript/troubleshooting.md": []}, {"docs/designers-developers/developers/tutorials/javascript/versions-and-building.md": []}, {"docs/designers-developers/developers/tutorials/javascript/scope-your-code.md": []} + ]}, + {"docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md": [ + {"docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-1-up-and-running.md": []}, + {"docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-2-styles-and-controls.md": []}, + {"docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-3-register-meta.md": []}, + {"docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-4-initialize-input.md": []}, + {"docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-5-update-meta.md": []}, + {"docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md": []} ]} ]} ]}, From 4f61457bee2f46ffe2fea9dee450d51e0a86a39e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Thu, 10 Jan 2019 18:24:37 +0100 Subject: [PATCH 129/691] Allow plugins to add classes to the sidebar panel (#12969) --- packages/edit-post/CHANGELOG.md | 6 ++++++ packages/edit-post/README.md | 8 ++++++++ .../src/components/sidebar/plugin-sidebar/index.js | 3 ++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/edit-post/CHANGELOG.md b/packages/edit-post/CHANGELOG.md index 858a6f3bd017a..ee41b7e7fb9f1 100644 --- a/packages/edit-post/CHANGELOG.md +++ b/packages/edit-post/CHANGELOG.md @@ -1,3 +1,9 @@ +## 3.2.0 (Next) + +### Polish + +* Expose the `className` property to style the `PluginSidebar` component. + ## 3.1.7 (2019-01-03) ## 3.1.6 (2018-12-18) diff --git a/packages/edit-post/README.md b/packages/edit-post/README.md index 48e54d76ae36e..ca4fc9e947dd2 100644 --- a/packages/edit-post/README.md +++ b/packages/edit-post/README.md @@ -112,6 +112,7 @@ The callback function to be executed when the user clicks the menu item. Renders a sidebar when activated. The contents within the `PluginSidebar` will appear as content within the sidebar. If you wish to display the sidebar, you can with use the [`PluginSidebarMoreMenuItem`](#pluginsidebarmoremenuitem) component or the `wp.data.dispatch` API: + ```js wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/sidebar-name' ); ``` @@ -174,6 +175,13 @@ A string identifying the sidebar. Must be unique for every sidebar registered wi - Type: `String` - Required: Yes +##### className + +An optional class name added to the sidebar body. + +- Type: `String` +- Required: No + ##### title Title displayed at the top of the sidebar. diff --git a/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js b/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js index 3594ad34666bc..72c0cc5da1c6b 100644 --- a/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js +++ b/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js @@ -25,6 +25,7 @@ import SidebarHeader from '../sidebar-header'; function PluginSidebar( props ) { const { children, + className, icon, isActive, isPinnable = true, @@ -66,7 +67,7 @@ function PluginSidebar( props ) { /> ) } </SidebarHeader> - <Panel> + <Panel className={ className }> { children } </Panel> </Sidebar> From 69f6a45f8c84a36e2086443eed3ca4376c5b9023 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Thu, 10 Jan 2019 18:58:29 +0100 Subject: [PATCH 130/691] Update Tutorials intro page (#13280) --- docs/designers-developers/developers/tutorials/readme.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/designers-developers/developers/tutorials/readme.md b/docs/designers-developers/developers/tutorials/readme.md index 543f560c8d81c..a5e857fb9bcfd 100644 --- a/docs/designers-developers/developers/tutorials/readme.md +++ b/docs/designers-developers/developers/tutorials/readme.md @@ -1,6 +1,7 @@ # Tutorials -* If you want to learn more about block creation, the [Blocks Tutorial](../../../../docs/designers-developers/developers/tutorials/block-tutorial/readme.md) is the best place to start. +* See the [Getting Started with JavaScript Tutorial](/docs/designers-developers/developers/tutorials/javascript/readme.md) to learn about how to use JavaScript within WordPress. -* See the [Getting Started with JavaScript Tutorial](../../../../docs/designers-developers/developers/tutorials/javascript/readme.md) to learn about how to use JavaScript within WordPress. +* The [Blocks Tutorial](/docs/designers-developers/developers/tutorials/block-tutorial/readme.md) is the best place to start to learn more about block creation. +* The [Sidebar Tutorial](/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md) will walk you through the steps of creating a sidebar to update data from the `post_meta` table. From 2b418102b59ac1e10540c7c730c62c30571516e4 Mon Sep 17 00:00:00 2001 From: William Earnhardt <wearnhardt@gmail.com> Date: Thu, 10 Jan 2019 14:01:24 -0500 Subject: [PATCH 131/691] Account for pending status when evaluating post date floating (#13178) --- packages/editor/src/store/selectors.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor/src/store/selectors.js b/packages/editor/src/store/selectors.js index 85c8c690d03cb..7a1ab754c45a5 100644 --- a/packages/editor/src/store/selectors.js +++ b/packages/editor/src/store/selectors.js @@ -583,7 +583,7 @@ export function isEditedPostDateFloating( state ) { const date = getEditedPostAttribute( state, 'date' ); const modified = getEditedPostAttribute( state, 'modified' ); const status = getEditedPostAttribute( state, 'status' ); - if ( status === 'draft' || status === 'auto-draft' ) { + if ( status === 'draft' || status === 'auto-draft' || status === 'pending' ) { return date === modified; } return false; From 63e55335e4e32a1195dc97aa4eafc87d0a7f453c Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Thu, 10 Jan 2019 19:15:27 +0000 Subject: [PATCH 132/691] Fix: Warning on end 2 end metabox test. Add: Test cases for the excerpt in meta logic. (#13174) --- packages/tests-e2e/plugins/meta-box.php | 2 + packages/tests-e2e/specs/meta-boxes.test.js | 62 +++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/packages/tests-e2e/plugins/meta-box.php b/packages/tests-e2e/plugins/meta-box.php index 0e3d921396934..44989eaa638d4 100644 --- a/packages/tests-e2e/plugins/meta-box.php +++ b/packages/tests-e2e/plugins/meta-box.php @@ -33,6 +33,8 @@ function gutenberg_test_meta_box_add_meta_box() { * Print excerpt in <meta> tag in wp_head */ function gutenberg_test_meta_box_render_head() { + global $post; + setup_postdata( $post ); // Emulates what plugins like Yoast do with meta data on the front end. // Tests that our excerpt processing does not interfere with dynamic blocks. $excerpt = wp_strip_all_tags( get_the_excerpt() ); diff --git a/packages/tests-e2e/specs/meta-boxes.test.js b/packages/tests-e2e/specs/meta-boxes.test.js index c7d73a8f71fe2..2dfecddf85431 100644 --- a/packages/tests-e2e/specs/meta-boxes.test.js +++ b/packages/tests-e2e/specs/meta-boxes.test.js @@ -5,7 +5,9 @@ import { activatePlugin, createNewPost, deactivatePlugin, + findSidebarPanelWithTitle, insertBlock, + openDocumentSettingsSidebar, publishPost, } from '../support/utils'; @@ -63,4 +65,64 @@ describe( 'Meta boxes', () => { // Check the the dynamic block appears. await page.waitForSelector( '.wp-block-latest-posts' ); } ); + + it( 'Should render the excerpt in meta based on post content if no explicit excerpt exists', async () => { + await createNewPost(); + await insertBlock( 'Paragraph' ); + await page.keyboard.type( 'Excerpt from content.' ); + await page.type( '.editor-post-title__input', 'A published post' ); + await publishPost(); + + // View the post. + const viewPostLinks = await page.$x( "//a[contains(text(), 'View Post')]" ); + await viewPostLinks[ 0 ].click(); + await page.waitForNavigation(); + + // Retrieve the excerpt used as meta + const metaExcerpt = await page.evaluate( () => { + return document.querySelector( + 'meta[property="gutenberg:hello"]' + ).getAttribute( + 'content' + ); + } ); + + expect( metaExcerpt ).toEqual( 'Excerpt from content.' ); + } ); + + it( 'Should render the explicitly set excerpt in meta instead of the content based one', async () => { + await createNewPost(); + await insertBlock( 'Paragraph' ); + await page.keyboard.type( 'Excerpt from content.' ); + await page.type( '.editor-post-title__input', 'A published post' ); + + // Open the excerpt panel + await openDocumentSettingsSidebar(); + const excerptButton = await findSidebarPanelWithTitle( 'Excerpt' ); + if ( excerptButton ) { + await excerptButton.click( 'button' ); + } + + await page.waitForSelector( '.editor-post-excerpt textarea' ); + + await page.type( '.editor-post-excerpt textarea', 'Explicitly set excerpt.' ); + + await publishPost(); + + // View the post. + const viewPostLinks = await page.$x( "//a[contains(text(), 'View Post')]" ); + await viewPostLinks[ 0 ].click(); + await page.waitForNavigation(); + + // Retrieve the excerpt used as meta + const metaExcerpt = await page.evaluate( () => { + return document.querySelector( + 'meta[property="gutenberg:hello"]' + ).getAttribute( + 'content' + ); + } ); + + expect( metaExcerpt ).toEqual( 'Explicitly set excerpt.' ); + } ); } ); From 162b4bdcd4d1f5913b952844604beedeb44ca2ed Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Fri, 11 Jan 2019 10:31:59 +0000 Subject: [PATCH 133/691] Don't send title during mediaUploads. Fix: Image Uploads don't retrieve the title from exif metadata. (#13193) --- packages/editor/src/utils/media-upload/media-upload.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/editor/src/utils/media-upload/media-upload.js b/packages/editor/src/utils/media-upload/media-upload.js index 47486bc5671e4..3cba5212f6271 100644 --- a/packages/editor/src/utils/media-upload/media-upload.js +++ b/packages/editor/src/utils/media-upload/media-upload.js @@ -210,7 +210,6 @@ function createMediaFromFile( file, additionalData ) { // Create upload payload const data = new window.FormData(); data.append( 'file', file, file.name || file.type.replace( '/', '.' ) ); - data.append( 'title', file.name ? file.name.replace( /\.[^.]+$/, '' ) : file.type.replace( '/', '.' ) ); forEach( additionalData, ( ( value, key ) => data.append( key, value ) ) ); return apiFetch( { path: '/wp/v2/media', From 7f3b8c7cd34d8a4ded2d6376b1d3e32cbc9b5a3f Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Fri, 11 Jan 2019 10:43:21 +0000 Subject: [PATCH 134/691] Fix: Font size picker does not work on mobile (#13257) --- assets/stylesheets/_z-index.scss | 1 + packages/editor/src/components/font-sizes/style.scss | 3 +++ packages/editor/src/style.scss | 1 + 3 files changed, 5 insertions(+) create mode 100644 packages/editor/src/components/font-sizes/style.scss diff --git a/assets/stylesheets/_z-index.scss b/assets/stylesheets/_z-index.scss index 85b0099665715..8e4774f262def 100644 --- a/assets/stylesheets/_z-index.scss +++ b/assets/stylesheets/_z-index.scss @@ -85,6 +85,7 @@ $z-layers: ( // Shows above edit post sidebar; Specificity needs to be higher than 3 classes. ".block-editor__container .components-popover.components-color-palette__picker.is-bottom": 100001, + ".block-editor__container .components-popover.components-font-size-picker__dropdown-content.is-bottom": 100001, ".edit-post-post-visibility__dialog.components-popover.is-bottom": 100001, ".components-autocomplete__results": 1000000, diff --git a/packages/editor/src/components/font-sizes/style.scss b/packages/editor/src/components/font-sizes/style.scss new file mode 100644 index 0000000000000..71949da0aab76 --- /dev/null +++ b/packages/editor/src/components/font-sizes/style.scss @@ -0,0 +1,3 @@ +.block-editor__container .components-popover.components-font-size-picker__dropdown-content.is-bottom { + z-index: z-index(".block-editor__container .components-popover.components-font-size-picker__dropdown-content.is-bottom"); +} diff --git a/packages/editor/src/style.scss b/packages/editor/src/style.scss index 5b79e1b52407a..028e9094d629b 100644 --- a/packages/editor/src/style.scss +++ b/packages/editor/src/style.scss @@ -18,6 +18,7 @@ @import "./components/default-block-appender/style.scss"; @import "./components/document-outline/style.scss"; @import "./components/error-boundary/style.scss"; +@import "./components/font-sizes/style.scss"; @import "./components/inner-blocks/style.scss"; @import "./components/inserter-with-shortcuts/style.scss"; @import "./components/inserter/style.scss"; From 171fedc2846a98b4b5dc397c265e54173ece09cf Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Fri, 11 Jan 2019 16:11:34 +0000 Subject: [PATCH 135/691] Fix: Handle of quote paste by correcting schema and isMatch function (#12252) --- packages/block-library/src/quote/index.js | 32 ++++++-- test/integration/blocks-raw-handling.spec.js | 79 ++++++++++++++++++++ 2 files changed, 106 insertions(+), 5 deletions(-) diff --git a/packages/block-library/src/quote/index.js b/packages/block-library/src/quote/index.js index d3f020a220c3d..525042540a2cb 100644 --- a/packages/block-library/src/quote/index.js +++ b/packages/block-library/src/quote/index.js @@ -100,18 +100,40 @@ export const settings = { }, { type: 'raw', - isMatch: ( node ) => ( - node.nodeName === 'BLOCKQUOTE' && + isMatch: ( node ) => { + const isParagraphOrSingleCite = ( () => { + let hasCitation = false; + return ( child ) => { + // Child is a paragraph. + if ( child.nodeName === 'P' ) { + return true; + } + // Child is a cite and no other cite child exists before it. + if ( + ! hasCitation && + child.nodeName === 'CITE' + ) { + hasCitation = true; + return true; + } + }; + } )(); + return node.nodeName === 'BLOCKQUOTE' && // The quote block can only handle multiline paragraph - // content. - Array.from( node.childNodes ).every( ( child ) => child.nodeName === 'P' ) - ), + // content with an optional cite child. + Array.from( node.childNodes ).every( + isParagraphOrSingleCite + ); + }, schema: { blockquote: { children: { p: { children: getPhrasingContentSchema(), }, + cite: { + children: getPhrasingContentSchema(), + }, }, }, }, diff --git a/test/integration/blocks-raw-handling.spec.js b/test/integration/blocks-raw-handling.spec.js index 3f0e951026537..364658b1ebf9f 100644 --- a/test/integration/blocks-raw-handling.spec.js +++ b/test/integration/blocks-raw-handling.spec.js @@ -122,6 +122,85 @@ describe( 'Blocks raw handling', () => { expect( console ).toHaveLogged(); } ); + it( 'should correctly handle quotes with one paragraphs and no citation', () => { + const filtered = pasteHandler( { + HTML: '<blockquote><p>chicken</p></blockquote>', + mode: 'AUTO', + } ).map( getBlockContent ).join( '' ); + + expect( filtered ).toBe( '<blockquote class="wp-block-quote"><p>chicken</p></blockquote>' ); + expect( console ).toHaveLogged(); + } ); + it( 'should correctly handle quotes with multiple paragraphs and no citation', () => { + const filtered = pasteHandler( { + HTML: '<blockquote><p>chicken</p><p>ribs</p></blockquote>', + mode: 'AUTO', + } ).map( getBlockContent ).join( '' ); + + expect( filtered ).toBe( '<blockquote class="wp-block-quote"><p>chicken</p><p>ribs</p></blockquote>' ); + expect( console ).toHaveLogged(); + } ); + + it( 'should correctly handle quotes with paragraph and citation at the end', () => { + const filtered = pasteHandler( { + HTML: '<blockquote><p>chicken</p><cite>ribs</cite></blockquote>', + mode: 'AUTO', + } ).map( getBlockContent ).join( '' ); + + expect( filtered ).toBe( '<blockquote class="wp-block-quote"><p>chicken</p><cite>ribs</cite></blockquote>' ); + expect( console ).toHaveLogged(); + } ); + + it( 'should handle a citation before the content', () => { + const filtered = pasteHandler( { + HTML: '<blockquote><cite>ribs</cite><p>ribs</p></blockquote>', + mode: 'AUTO', + } ).map( getBlockContent ).join( '' ); + + expect( filtered ).toBe( '<blockquote class="wp-block-quote"><p>ribs</p><cite>ribs</cite></blockquote>' ); + expect( console ).toHaveLogged(); + } ); + + it( 'should handle a citation in the middle of the content', () => { + const filtered = pasteHandler( { + HTML: '<blockquote><p>chicken</p><cite>ribs</cite><p>ribs</p></blockquote>', + mode: 'AUTO', + } ).map( getBlockContent ).join( '' ); + + expect( filtered ).toBe( '<blockquote class="wp-block-quote"><p>chicken</p><p>ribs</p><cite>ribs</cite></blockquote>' ); + expect( console ).toHaveLogged(); + } ); + + it( 'should correctly handle quotes with only a citation', () => { + const filtered = pasteHandler( { + HTML: '<blockquote><cite>ribs</cite></blockquote>', + mode: 'AUTO', + } ).map( getBlockContent ).join( '' ); + + expect( filtered ).toBe( '<blockquote class="wp-block-quote"><p></p><cite>ribs</cite></blockquote>' ); + expect( console ).toHaveLogged(); + } ); + + it( 'should convert to paragraph quotes with more than one cite', () => { + const filtered = pasteHandler( { + HTML: '<blockquote><cite>ribs</cite><cite>ribs</cite></blockquote>', + mode: 'AUTO', + } ).map( getBlockContent ).join( '' ); + + expect( filtered ).toBe( '<p>ribsribs</p>' ); + expect( console ).toHaveLogged(); + } ); + + it( 'should convert to paragraph quotes with more than one cite and at least one paragraph', () => { + const filtered = pasteHandler( { + HTML: '<blockquote><p>chicken</p><cite>ribs</cite><cite>ribs</cite></blockquote>', + mode: 'AUTO', + } ).map( getBlockContent ).join( '' ); + + expect( filtered ).toBe( '<p>chickenribsribs</p>' ); + expect( console ).toHaveLogged(); + } ); + describe( 'pasteHandler', () => { [ 'plain', From cfabfabfc134066a8dbb9c774f12234f4a6ae52b Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 11 Jan 2019 10:57:21 -0600 Subject: [PATCH 136/691] Data: Use type-specific isShallowEqual for withSelect (#12825) * Data: Use type-specific isShallowEqual for withSelect * is-shallow-equal: Expose typed variants on module --- packages/data/src/components/with-select/index.js | 8 ++++---- packages/is-shallow-equal/CHANGELOG.md | 6 ++++++ packages/is-shallow-equal/README.md | 4 ++-- packages/is-shallow-equal/index.js | 2 ++ 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/data/src/components/with-select/index.js b/packages/data/src/components/with-select/index.js index d1219479d3e46..37be4f3d2b2da 100644 --- a/packages/data/src/components/with-select/index.js +++ b/packages/data/src/components/with-select/index.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { Component } from '@wordpress/element'; -import isShallowEqual from '@wordpress/is-shallow-equal'; +import { isShallowEqualObjects } from '@wordpress/is-shallow-equal'; import { createHigherOrderComponent } from '@wordpress/compose'; /** @@ -84,7 +84,7 @@ const withSelect = ( mapSelectToProps ) => createHigherOrderComponent( ( Wrapped // `mergeProps` to rendered component if and only if updated. const hasPropsChanged = ( hasRegistryChanged || - ! isShallowEqual( this.props.ownProps, nextProps.ownProps ) + ! isShallowEqualObjects( this.props.ownProps, nextProps.ownProps ) ); // Only render if props have changed or merge props have been updated @@ -95,7 +95,7 @@ const withSelect = ( mapSelectToProps ) => createHigherOrderComponent( ( Wrapped if ( hasPropsChanged ) { const nextMergeProps = getNextMergeProps( nextProps ); - if ( ! isShallowEqual( this.mergeProps, nextMergeProps ) ) { + if ( ! isShallowEqualObjects( this.mergeProps, nextMergeProps ) ) { // If merge props change as a result of the incoming props, // they should be reflected as such in the upcoming render. // While side effects are discouraged in lifecycle methods, @@ -120,7 +120,7 @@ const withSelect = ( mapSelectToProps ) => createHigherOrderComponent( ( Wrapped } const nextMergeProps = getNextMergeProps( this.props ); - if ( isShallowEqual( this.mergeProps, nextMergeProps ) ) { + if ( isShallowEqualObjects( this.mergeProps, nextMergeProps ) ) { return; } diff --git a/packages/is-shallow-equal/CHANGELOG.md b/packages/is-shallow-equal/CHANGELOG.md index 0cecb31b29946..cc266ecb20c85 100644 --- a/packages/is-shallow-equal/CHANGELOG.md +++ b/packages/is-shallow-equal/CHANGELOG.md @@ -1,3 +1,9 @@ +## 1.2.0 (Unreleased) + +### New Feature + +- Type-specific variants are now exposed from the module root. In a WordPress context, this has the effect of making them available as `wp.isShallowEqual.isShallowEqualObjects` and `wp.isShallowEqual.isShallowEqualArrays`. + ## 1.1.0 (2018-07-12) ### New Feature diff --git a/packages/is-shallow-equal/README.md b/packages/is-shallow-equal/README.md index e6e0c4ab4d4c8..ac68c2997ac73 100644 --- a/packages/is-shallow-equal/README.md +++ b/packages/is-shallow-equal/README.md @@ -25,8 +25,8 @@ isShallowEqual( [ 1 ], [ 1 ] ); You can import a specific implementation if you already know the types of values you are working with: ```js -import isShallowEqualArrays from '@wordpress/is-shallow-equal/arrays'; -import isShallowEqualObjects from '@wordpress/is-shallow-equal/objects'; +import { isShallowEqualArrays } from '@wordpress/is-shallow-equal'; +import { isShallowEqualObjects } from '@wordpress/is-shallow-equal'; ``` ## Rationale diff --git a/packages/is-shallow-equal/index.js b/packages/is-shallow-equal/index.js index 8ab40b0cf25d7..f24dd1ea07a90 100644 --- a/packages/is-shallow-equal/index.js +++ b/packages/is-shallow-equal/index.js @@ -30,3 +30,5 @@ function isShallowEqual( a, b ) { } module.exports = isShallowEqual; +module.exports.isShallowEqualObjects = isShallowEqualObjects; +module.exports.isShallowEqualArrays = isShallowEqualArrays; From 64b58378ea87eb641398116c4633a6c4e7e46574 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Fri, 11 Jan 2019 09:22:35 -0800 Subject: [PATCH 137/691] Add a tutorial for creating a meta block (#12834) * Add a tutorial for creating a meta block * Update docs/designers-developers/developers/tutorials/metabox/add-meta-block.md Co-Authored-By: mkaz <marcus@mkaz.com> * Improving wording * Batch of Edits and updates * Rebase * Fix headers * Switch out to use TextControl components * Simplify and edits * minor updates * Reorganize tutorial, add example usage * Address @swisspidy comment and add example using render_callback * Remove create plugin part and link to JavaScript tutorial, so we are not duplicating content, allows for updating in one spot if things change. * Change filenames to match flow, easier to edit and follow * Fix link * Add meta block tutorial to TOC, Manifest and Tutorials Links * Fix whitespace * Add section regarding protected key * Simplify links * Update per feedback, clarification around PHP and JS files * Update output, clarify PHP code. Add check for value exists * Update docs/designers-developers/developers/tutorials/metabox/readme.md Co-Authored-By: mkaz <marcus@mkaz.com> * Update docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md Co-Authored-By: mkaz <marcus@mkaz.com> * Update docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md Co-Authored-By: mkaz <marcus@mkaz.com> * Update docs/designers-developers/developers/tutorials/metabox/meta-block-4-use-data.md Co-Authored-By: mkaz <marcus@mkaz.com> * Update docs/designers-developers/developers/tutorials/metabox/meta-block-4-use-data.md Co-Authored-By: mkaz <marcus@mkaz.com> * Update docs/designers-developers/developers/tutorials/metabox/readme.md Co-Authored-By: mkaz <marcus@mkaz.com> * Update docs/designers-developers/developers/tutorials/metabox/readme.md Co-Authored-By: mkaz <marcus@mkaz.com> * Update docs/designers-developers/developers/tutorials/metabox/meta-block-4-use-data.md Co-Authored-By: mkaz <marcus@mkaz.com> * Update docs/designers-developers/developers/tutorials/metabox/meta-block-4-use-data.md Co-Authored-By: mkaz <marcus@mkaz.com> * Fix newlines * Update docs/designers-developers/developers/tutorials/metabox/meta-block-2-register-meta.md Co-Authored-By: mkaz <marcus@mkaz.com> * Update docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md Co-Authored-By: mkaz <marcus@mkaz.com> * Update docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md Co-Authored-By: mkaz <marcus@mkaz.com> * Update docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md Co-Authored-By: mkaz <marcus@mkaz.com> * Update docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md Co-Authored-By: mkaz <marcus@mkaz.com> * Update docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md Co-Authored-By: mkaz <marcus@mkaz.com> * Update docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md Co-Authored-By: mkaz <marcus@mkaz.com> * Update docs/designers-developers/developers/tutorials/metabox/meta-block-1-intro.md Co-Authored-By: mkaz <marcus@mkaz.com> * Update docs/designers-developers/developers/tutorials/metabox/meta-block-1-intro.md Co-Authored-By: mkaz <marcus@mkaz.com> * Update docs/designers-developers/developers/tutorials/metabox/meta-block-2-register-meta.md Co-Authored-By: mkaz <marcus@mkaz.com> * Add link to DevHub * Remove newline --- .../tutorials/metabox/meta-block-1-intro.md | 17 ++++ .../metabox/meta-block-2-register-meta.md | 37 +++++++++ .../tutorials/metabox/meta-block-3-add.md | 76 ++++++++++++++++++ .../metabox/meta-block-4-use-data.md | 42 ++++++++++ .../metabox/meta-block-5-finishing.md | 20 +++++ .../tutorials/metabox/meta-block.png | Bin 0 -> 16966 bytes .../developers/tutorials/metabox/readme.md | 18 +++++ .../developers/tutorials/readme.md | 2 + docs/manifest.json | 36 +++++++++ docs/toc.json | 7 ++ 10 files changed, 255 insertions(+) create mode 100644 docs/designers-developers/developers/tutorials/metabox/meta-block-1-intro.md create mode 100644 docs/designers-developers/developers/tutorials/metabox/meta-block-2-register-meta.md create mode 100644 docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md create mode 100644 docs/designers-developers/developers/tutorials/metabox/meta-block-4-use-data.md create mode 100644 docs/designers-developers/developers/tutorials/metabox/meta-block-5-finishing.md create mode 100644 docs/designers-developers/developers/tutorials/metabox/meta-block.png create mode 100644 docs/designers-developers/developers/tutorials/metabox/readme.md diff --git a/docs/designers-developers/developers/tutorials/metabox/meta-block-1-intro.md b/docs/designers-developers/developers/tutorials/metabox/meta-block-1-intro.md new file mode 100644 index 0000000000000..c89c757a92209 --- /dev/null +++ b/docs/designers-developers/developers/tutorials/metabox/meta-block-1-intro.md @@ -0,0 +1,17 @@ +# Store Post Meta with a Block + +Typically, blocks store their attribute values in the serialised block HTML. However, you can also create a block that saves its attribute values as post meta, which can be accessed programatically anywhere in your template. + +In this short tutorial you will create one of these blocks, which will prompt a user for a single value, and save it as post meta. + +For background around the thinking of blocks as the interface, please see the [Principles section](/docs/contributors/principles.md) of the handbook. + +Before starting this tutorial, you will need a plugin to hold your code. Please take a look at the first two steps of [the JavaScript tutorial](/docs/designers-developers/developers/tutorials/javascript/readme.md) for information setting up a plugin. + +## Table of Contents + +1. [Register Meta Field](/docs/designers-developers/developers/tutorials/metabox/meta-block-2-register-meta.md) +2. [Add Meta Block](/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md) +3. [Use Post Meta Data](/docs/designers-developers/developers/tutorials/metabox/meta-block-4-use-data.md) +4. [Finishing Touches](/docs/designers-developers/developers/tutorials/metabox/meta-block-5-finishing.md) + diff --git a/docs/designers-developers/developers/tutorials/metabox/meta-block-2-register-meta.md b/docs/designers-developers/developers/tutorials/metabox/meta-block-2-register-meta.md new file mode 100644 index 0000000000000..e58889c7599df --- /dev/null +++ b/docs/designers-developers/developers/tutorials/metabox/meta-block-2-register-meta.md @@ -0,0 +1,37 @@ +# Register Meta Field + +A post meta field is a WordPress object used to store extra data about a post. You need to first register a new meta field prior to use. See Managing [Post Metadata](https://developer.wordpress.org/plugins/metadata/managing-post-metadata/) to learn more about post meta. + +When registering the field, note the `show_in_rest` parameter. This ensures the data will be included in the REST API, which the Block Editor uses to load and save meta data. See the [`register_meta`](https://developer.wordpress.org/reference/functions/register_meta/) function definition for extra information. + +To register the field, create a PHP plugin file called `myguten-meta-block.php` including: + +```php +<?php +/** + * Plugin Name: Meta Block + */ + +// register custom meta tag field +function myguten_register_meta() { + register_meta( 'post', 'myguten_meta_block_field', array( + 'show_in_rest' => true, + 'single' => true, + 'type' => 'string', + ) ); +} +add_action( 'init', 'myguten_register_meta' ); +``` + +**Note:** If the meta key name starts with an underscore WordPress considers it a protected field. Editing this field requires passing a permission check, which is set as the `auth_callback` in the `register_meta` function. Here is an example: + +```php +register_meta( 'post', '_myguten_protected_key', array( + 'show_in_rest' => true, + 'single' => true, + 'type' => 'string', + 'auth_callback' => function() { + return current_user_can( 'edit_posts' ); + } +) ); +``` diff --git a/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md b/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md new file mode 100644 index 0000000000000..5dfe773dd5d87 --- /dev/null +++ b/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md @@ -0,0 +1,76 @@ +# Create Meta Block + +With the meta field registered in the previous step, next you will create a new block used to display the field value to the user. See the [Block Tutorial](/docs/designers-developers/developers/tutorials/block-tutorial/readme.md) for a deeper understanding of creating custom blocks. + +For this block, you will use the TextControl component, which is similar to an HTML input text field. For additional components, check out the [components](https://github.com/WordPress/gutenberg/tree/master/packages/components/src) and [editor](https://github.com/WordPress/gutenberg/tree/master/packages/editor/src/components) packages repositories. + +Attributes are the information displayed in blocks. As shown in the block tutorial, the source of attributes come from the text or HTML a user writes in the editor. For your meta block, the attribute will come from the post meta field. + +By specifying the source of the attributes as `meta`, the Block Editor automatically handles the loading and storing of the data; no REST API or data functions are needed. + +Add this code to your JavaScript file (this tutorial will call the file `myguten.js`): + +```js +var el = wp.element.createElement; +var registerBlockType = wp.blocks.registerBlockType; +var TextField = wp.components.TextControl; + +registerBlockType("myguten/meta-block", { + title: "Meta Block", + icon: "smiley", + category: "common", + + attributes: { + blockValue: { + type: "string", + source: "meta", + meta: "myguten_meta_block_field" + } + }, + + edit: function(props) { + var className = props.className; + var setAttributes = props.setAttributes; + + function updateBlockValue( blockValue ) { + setAttributes({ blockValue }); + } + + return el( + "div", + { className: className }, + el( TextField, { + label: "Meta Block Field", + value: props.attributes.blockValue, + onChange: updateBlockValue + } ) + ); + }, + + // No information saved to the block + // Data is saved to post meta via attributes + save: function() { + return null; + } +}); +``` + +**Important:** Before you test, you need to enqueue your JavaScript file and its dependencies. Note the WordPress packages used above are `wp.element`, `wp.blocks`, and `wp.components`. Each of these need to be included in the array of dependencies. Update the `myguten-meta-block.php` file adding the enqueue function: + +```php +function myguten_enqueue() { + wp_enqueue_script( + 'myguten-script', + plugins_url( 'myguten.js', __FILE__ ), + array( 'wp-blocks', 'wp-element', 'wp-components' ) + ); +} +add_action( 'enqueue_block_editor_assets', 'myguten_enqueue' ); +``` + +You can now edit a draft post and add a Meta Block to the post. You will see your field that you can type a value in. When you save the post, either as a draft or published, the post meta value will be saved too. You can verify by saving and reloading your draft, the form will still be filled in on reload. + +![Meta Block](/docs/designers-developers/developers/tutorials/metabox/meta-block.png) + +You can now use the post meta data in a template, or another block. See next section for [using post meta data](/docs/designers-developers/developers/tutorials/metabox/meta-block-4-use-data.md). You could also confirm the data is saved by checking the database table `wp_postmeta` and confirm the new post id contains the new field data. + diff --git a/docs/designers-developers/developers/tutorials/metabox/meta-block-4-use-data.md b/docs/designers-developers/developers/tutorials/metabox/meta-block-4-use-data.md new file mode 100644 index 0000000000000..d0ee92d00720c --- /dev/null +++ b/docs/designers-developers/developers/tutorials/metabox/meta-block-4-use-data.md @@ -0,0 +1,42 @@ +# Use Post Meta Data + +You can use the post meta data stored in the last step in multiple ways. + +## Use Post Meta in PHP + +The first example uses the value from the post meta field and appends it to the end of the post content wrapped in a H4 tag. This method in PHP is unchanged in WordPress 5.0. + +```php +function myguten_content_filter( $content ) { + $value = get_post_meta( get_the_ID(), 'myguten_meta_block_field', true ); + if ( $value ) { + return sprintf( "%s <h4> %s </h4>", $content, esc_html( $value ) ); + } else { + return $content; + } +} +add_filter( 'the_content', 'myguten_content_filter' ); +``` + +## Use Post Meta in Block + +You can also use the post meta data in other blocks. For this example the data is loaded at the end of every paragraph block when it is rendered, ie. shown to the user. You can replace this for any core or custom block types as needed. + +In PHP, use the [register_block_type](https://developer.wordpress.org/reference/functions/register_block_type/) function to set a callback when the block is rendered to include the meta value. + +```php +function myguten_render_paragraph( $attributes, $content ) { + $value = get_post_meta( get_the_ID(), 'myguten_meta_block_field', true ); + // check value is set before outputting + if ( $value ) { + return sprintf( "%s (%s)", $content, esc_html( $value ) ); + } else { + return $content; + } +} + +register_block_type( 'core/paragraph', array( + 'render_callback' => 'myguten_render_paragraph', +) ); +``` + diff --git a/docs/designers-developers/developers/tutorials/metabox/meta-block-5-finishing.md b/docs/designers-developers/developers/tutorials/metabox/meta-block-5-finishing.md new file mode 100644 index 0000000000000..b8f41f104b8c8 --- /dev/null +++ b/docs/designers-developers/developers/tutorials/metabox/meta-block-5-finishing.md @@ -0,0 +1,20 @@ +# Finishing Touches + +One problem using a meta block is the block is easy for an author to forget, since it requires being added to each post. You solve this by using [block templates](/docs/designers-developers/developers/block-api/block-templates.md). A block template is a predefined list of block items per post type. Templates allow you to specify a default initial state for a post type. + +For this example, you use a template to automatically insert the meta block at the top of a post. + +Add the following code to the `myguten-meta-block.php` file: + +```php +function myguten_register_template() { + $post_type_object = get_post_type_object( 'post' ); + $post_type_object->template = array( + array( 'myguten/meta-block' ), + ); +} +add_action( 'init', 'myguten_register_template' ); +``` + +You can also add other block types in the array, including placeholders, or even lock down a post to a set of specific blocks. Templates are a powerful tool for controlling the editing experience, see the documentation linked above for more. + diff --git a/docs/designers-developers/developers/tutorials/metabox/meta-block.png b/docs/designers-developers/developers/tutorials/metabox/meta-block.png new file mode 100644 index 0000000000000000000000000000000000000000..4fe3ebfbc3662d25f755db31871f9abd75dcfb51 GIT binary patch literal 16966 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 MgI7I^H60@VKV;9yV*mgE literal 0 HcmV?d00001 diff --git a/docs/designers-developers/developers/tutorials/metabox/readme.md b/docs/designers-developers/developers/tutorials/metabox/readme.md new file mode 100644 index 0000000000000..aa13df05ff580 --- /dev/null +++ b/docs/designers-developers/developers/tutorials/metabox/readme.md @@ -0,0 +1,18 @@ +# Meta Boxes + +Prior to the block editor, custom meta boxes were used to extend the editor. With the new editor there are new ways to extend, giving more power to the developer and a better experience for the authors. Porting older custom meta boxes to one of these new methods is encouraged as to create a more unified and consistent experience for those using the editor. + +The new block editor does support most existing meta boxes, see [this backward compatibility article](/docs/designers-developers/developers/backward-compatibility/meta-box.md) for more support details . + +Here are two mini-tutorials for creating similar functionality to meta boxes in the Block Editor. + +## Use Blocks to Store Meta + +The first method is to use Blocks to store extra data with a post. The data is stored in a post meta field, similar to how meta boxes store information. + +* [Store Post Meta with a Block](/docs/designers-developers/developers/tutorials/metabox/meta-block-1-intro.md) + +## Sidebar Plugin + +If you are interested in working with the post meta outside the editor, check out the [Sidebar Tutorial](/docs/designers-developers/developers/tutorials/plugin-sidebar-0/). + diff --git a/docs/designers-developers/developers/tutorials/readme.md b/docs/designers-developers/developers/tutorials/readme.md index a5e857fb9bcfd..4d80e195fb287 100644 --- a/docs/designers-developers/developers/tutorials/readme.md +++ b/docs/designers-developers/developers/tutorials/readme.md @@ -4,4 +4,6 @@ * The [Blocks Tutorial](/docs/designers-developers/developers/tutorials/block-tutorial/readme.md) is the best place to start to learn more about block creation. +* See the [Meta Boxes Tutorial](/docs/designers-developers/developers/tutorials/metabox/readme.md) for new ways of extending the editor storing and using post meta data. + * The [Sidebar Tutorial](/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md) will walk you through the steps of creating a sidebar to update data from the `post_meta` table. diff --git a/docs/manifest.json b/docs/manifest.json index 69a3be09a4e72..a6e908c687806 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -239,6 +239,42 @@ "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/scope-your-code.md", "parent": "javascript" }, + { + "title": "Meta Boxes", + "slug": "metabox", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/metabox/readme.md", + "parent": "tutorials" + }, + { + "title": "Store Post Meta with a Block", + "slug": "meta-block-1-intro", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/metabox/meta-block-1-intro.md", + "parent": "metabox" + }, + { + "title": "Register Meta Field", + "slug": "meta-block-2-register-meta", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/metabox/meta-block-2-register-meta.md", + "parent": "metabox" + }, + { + "title": "Create Meta Block", + "slug": "meta-block-3-add", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md", + "parent": "metabox" + }, + { + "title": "Use Post Meta Data", + "slug": "meta-block-4-use-data", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/metabox/meta-block-4-use-data.md", + "parent": "metabox" + }, + { + "title": "Finishing Touches", + "slug": "meta-block-5-finishing", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/metabox/meta-block-5-finishing.md", + "parent": "metabox" + }, { "title": "Creating a sidebar for your plugin", "slug": "plugin-sidebar-0", diff --git a/docs/toc.json b/docs/toc.json index 5f30be816d41c..37c24577f8041 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -45,6 +45,13 @@ {"docs/designers-developers/developers/tutorials/javascript/versions-and-building.md": []}, {"docs/designers-developers/developers/tutorials/javascript/scope-your-code.md": []} ]}, + {"docs/designers-developers/developers/tutorials/metabox/readme.md": [ + {"docs/designers-developers/developers/tutorials/metabox/meta-block-1-intro.md": []}, + {"docs/designers-developers/developers/tutorials/metabox/meta-block-2-register-meta.md": []}, + {"docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md": []}, + {"docs/designers-developers/developers/tutorials/metabox/meta-block-4-use-data.md": []}, + {"docs/designers-developers/developers/tutorials/metabox/meta-block-5-finishing.md": []} + ]}, {"docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md": [ {"docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-1-up-and-running.md": []}, {"docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-2-styles-and-controls.md": []}, From d08f71d92fe5a2f2c674e3999ce4656591ba8e2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Fri, 11 Jan 2019 17:40:07 +0000 Subject: [PATCH 138/691] Use select in withDispatch to avoid using unused props (#13288) * Use select in withDispatch to avoid using unesed props * Address feedback from code review --- .../src/components/keyboard-shortcuts/index.js | 6 +++--- .../plugins/keyboard-shortcuts-help-menu-item/index.js | 2 +- .../editor-global-keyboard-shortcuts/index.js | 10 ++++------ packages/editor/src/components/inserter/menu.js | 10 ++++------ 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/packages/edit-post/src/components/keyboard-shortcuts/index.js b/packages/edit-post/src/components/keyboard-shortcuts/index.js index 30bd8d02a4d11..3fd18ca87a09d 100644 --- a/packages/edit-post/src/components/keyboard-shortcuts/index.js +++ b/packages/edit-post/src/components/keyboard-shortcuts/index.js @@ -58,14 +58,14 @@ export default compose( [ isRichEditingEnabled: select( 'core/editor' ).getEditorSettings().richEditingEnabled, mode: select( 'core/edit-post' ).getEditorMode(), isEditorSidebarOpen: select( 'core/edit-post' ).isEditorSidebarOpened(), - hasBlockSelection: !! select( 'core/editor' ).getBlockSelectionStart(), } ) ), - withDispatch( ( dispatch, { hasBlockSelection } ) => ( { + withDispatch( ( dispatch, ownProps, { select } ) => ( { switchMode( mode ) { dispatch( 'core/edit-post' ).switchEditorMode( mode ); }, openSidebar() { - const sidebarToOpen = hasBlockSelection ? 'edit-post/block' : 'edit-post/document'; + const { getBlockSelectionStart } = select( 'core/editor' ); + const sidebarToOpen = getBlockSelectionStart() ? 'edit-post/block' : 'edit-post/document'; dispatch( 'core/edit-post' ).openGeneralSidebar( sidebarToOpen ); }, closeSidebar: dispatch( 'core/edit-post' ).closeGeneralSidebar, diff --git a/packages/edit-post/src/plugins/keyboard-shortcuts-help-menu-item/index.js b/packages/edit-post/src/plugins/keyboard-shortcuts-help-menu-item/index.js index 2581e4f05eaf9..b8c84c0645e59 100644 --- a/packages/edit-post/src/plugins/keyboard-shortcuts-help-menu-item/index.js +++ b/packages/edit-post/src/plugins/keyboard-shortcuts-help-menu-item/index.js @@ -20,7 +20,7 @@ export function KeyboardShortcutsHelpMenuItem( { openModal, onSelect } ) { ); } -export default withDispatch( ( dispatch, ) => { +export default withDispatch( ( dispatch ) => { const { openModal, } = dispatch( 'core/edit-post' ); diff --git a/packages/editor/src/components/editor-global-keyboard-shortcuts/index.js b/packages/editor/src/components/editor-global-keyboard-shortcuts/index.js index 844697def5941..ed2a4039755b1 100644 --- a/packages/editor/src/components/editor-global-keyboard-shortcuts/index.js +++ b/packages/editor/src/components/editor-global-keyboard-shortcuts/index.js @@ -152,7 +152,6 @@ export default compose( [ getBlockOrder, getMultiSelectedBlockClientIds, hasMultiSelection, - isEditedPostDirty, getBlockRootClientId, getTemplateLock, getSelectedBlockClientId, @@ -167,11 +166,10 @@ export default compose( [ selectedBlockClientIds, ( clientId ) => !! getTemplateLock( getBlockRootClientId( clientId ) ) ), - isDirty: isEditedPostDirty(), selectedBlockClientIds, }; } ), - withDispatch( ( dispatch, ownProps ) => { + withDispatch( ( dispatch, ownProps, { select } ) => { const { clearSelectedBlock, multiSelect, @@ -185,11 +183,11 @@ export default compose( [ onSave() { // TODO: This should be handled in the `savePost` effect in // considering `isSaveable`. See note on `isEditedPostSaveable` - // selector about dirtiness and meta-boxes. When removing, also - // remember to remove `isDirty` prop passing from `withSelect`. + // selector about dirtiness and meta-boxes. // // See: `isEditedPostSaveable` - if ( ! ownProps.isDirty ) { + const { isEditedPostDirty } = select( 'core/editor' ); + if ( ! isEditedPostDirty() ) { return; } diff --git a/packages/editor/src/components/inserter/menu.js b/packages/editor/src/components/inserter/menu.js index 57131b5d2e7d2..9f6dea181e14c 100644 --- a/packages/editor/src/components/inserter/menu.js +++ b/packages/editor/src/components/inserter/menu.js @@ -347,8 +347,6 @@ export class InserterMenu extends Component { export default compose( withSelect( ( select, { rootClientId } ) => { const { - getEditedPostAttribute, - getSelectedBlock, getInserterItems, getBlockName, } = select( 'core/editor' ); @@ -359,14 +357,12 @@ export default compose( const rootBlockName = getBlockName( rootClientId ); return { - selectedBlock: getSelectedBlock(), rootChildBlocks: getChildBlockNames( rootBlockName ), - title: getEditedPostAttribute( 'title' ), items: getInserterItems( rootClientId ), rootClientId, }; } ), - withDispatch( ( dispatch, ownProps ) => { + withDispatch( ( dispatch, ownProps, { select } ) => { const { __experimentalFetchReusableBlocks: fetchReusableBlocks, showInsertionPoint, @@ -382,9 +378,11 @@ export default compose( replaceBlocks, insertBlock, } = dispatch( 'core/editor' ); - const { selectedBlock, index, rootClientId } = ownProps; + const { getSelectedBlock } = select( 'core/editor' ); + const { index, rootClientId } = ownProps; const { name, initialAttributes } = item; + const selectedBlock = getSelectedBlock(); const insertedBlock = createBlock( name, initialAttributes ); if ( selectedBlock && isUnmodifiedDefaultBlock( selectedBlock ) ) { replaceBlocks( selectedBlock.clientId, insertedBlock ); From 1d171438da117114d53f38254c02290a072d2465 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Fri, 11 Jan 2019 18:30:06 +0000 Subject: [PATCH 139/691] chore: Fix: Links pointing to the block icon documentation. (#13246) --- .../tutorials/block-tutorial/writing-your-first-block-type.md | 2 +- packages/blocks/src/api/registration.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md b/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md index e696b73e4e911..4a7c8449763bc 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md @@ -82,7 +82,7 @@ registerBlockType( 'gutenberg-boilerplate-esnext/hello-world-step-01', { ``` {% end %} -Once a block is registered, you should immediately see that it becomes available as an option in the editor inserter dialog, using values from `title`, `icon`, and `category` to organize its display. You can choose an icon from any included in the built-in [Dashicons icon set](https://developer.wordpress.org/resource/dashicons/), or provide a [custom svg element](https://wordpress.org/gutenberg/handbook/block-api/#icon-optional). +Once a block is registered, you should immediately see that it becomes available as an option in the editor inserter dialog, using values from `title`, `icon`, and `category` to organize its display. You can choose an icon from any included in the built-in [Dashicons icon set](https://developer.wordpress.org/resource/dashicons/), or provide a [custom svg element](https://wordpress.org/gutenberg/handbook/designers-developers/developers/block-api/block-registration/#icon-optional). A block name must be prefixed with a namespace specific to your plugin. This helps prevent conflicts when more than one plugin registers a block with the same name. diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js index d0d21a897c526..14cf00652b728 100644 --- a/packages/blocks/src/api/registration.js +++ b/packages/blocks/src/api/registration.js @@ -141,7 +141,7 @@ export function registerBlockType( name, settings ) { if ( ! isValidIcon( settings.icon.src ) ) { console.error( 'The icon passed is invalid. ' + - 'The icon should be a string, an element, a function, or an object following the specifications documented in https://wordpress.org/gutenberg/handbook/block-api/#icon-optional' + 'The icon should be a string, an element, a function, or an object following the specifications documented in https://wordpress.org/gutenberg/handbook/designers-developers/developers/block-api/block-registration/#icon-optional' ); return; } From aaec6ffcd709ef6d85ca06d0e067f0e810ae1606 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Fri, 11 Jan 2019 11:28:52 -0800 Subject: [PATCH 140/691] Update image link, /docs/ doesnt work requires full URL (#13296) --- .../developers/tutorials/metabox/meta-block-3-add.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md b/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md index 5dfe773dd5d87..6f0b449f27961 100644 --- a/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md +++ b/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md @@ -70,7 +70,7 @@ add_action( 'enqueue_block_editor_assets', 'myguten_enqueue' ); You can now edit a draft post and add a Meta Block to the post. You will see your field that you can type a value in. When you save the post, either as a draft or published, the post meta value will be saved too. You can verify by saving and reloading your draft, the form will still be filled in on reload. -![Meta Block](/docs/designers-developers/developers/tutorials/metabox/meta-block.png) +![Meta Block](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/metabox/meta-block.png) You can now use the post meta data in a template, or another block. See next section for [using post meta data](/docs/designers-developers/developers/tutorials/metabox/meta-block-4-use-data.md). You could also confirm the data is saved by checking the database table `wp_postmeta` and confirm the new post id contains the new field data. From ad4c9e745d1b4f3bca1f38bf4e8274967b345542 Mon Sep 17 00:00:00 2001 From: Alda Vigdis Skarphedinsdottir <191583+aldavigdis@users.noreply.github.com> Date: Sat, 12 Jan 2019 00:10:04 +0100 Subject: [PATCH 141/691] Adding a short paragraph to the button component readme (#12773) Explains that the Button component and the Button block may sometimes be confused together. --- packages/components/src/button/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/components/src/button/README.md b/packages/components/src/button/README.md index cd68b438d3eee..6f45cf5db7a64 100644 --- a/packages/components/src/button/README.md +++ b/packages/components/src/button/README.md @@ -4,6 +4,8 @@ Buttons express what action will occur when the user clicks or taps it. Buttons The presence of a `href` prop determines whether an `anchor` element is rendered instead of a `button`. +Note that this component may sometimes be confused with the Button block, which has semantically different use cases and functionality. + ## Usage Renders a button with default style. From 338d889062df7c9c136dff86071ff4540e12cac0 Mon Sep 17 00:00:00 2001 From: Andrea Fercia <a.fercia@gmail.com> Date: Mon, 14 Jan 2019 13:21:30 +0100 Subject: [PATCH 142/691] Add tooltip to the block list appender. (#13306) --- .../src/components/block-list-appender/index.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/editor/src/components/block-list-appender/index.js b/packages/editor/src/components/block-list-appender/index.js index 3f0bb11a22652..b306c2945654f 100644 --- a/packages/editor/src/components/block-list-appender/index.js +++ b/packages/editor/src/components/block-list-appender/index.js @@ -9,7 +9,7 @@ import { last } from 'lodash'; import { withSelect } from '@wordpress/data'; import { getDefaultBlockName } from '@wordpress/blocks'; import { __ } from '@wordpress/i18n'; -import { Button, Dashicon } from '@wordpress/components'; +import { IconButton } from '@wordpress/components'; /** * Internal dependencies @@ -44,16 +44,15 @@ function BlockListAppender( { <Inserter rootClientId={ rootClientId } renderToggle={ ( { onToggle, disabled, isOpen } ) => ( - <Button - aria-label={ __( 'Add block' ) } + <IconButton + label={ __( 'Add block' ) } + icon="insert" onClick={ onToggle } className="block-list-appender__toggle" aria-haspopup="true" aria-expanded={ isOpen } disabled={ disabled } - > - <Dashicon icon="insert" /> - </Button> + /> ) } /> </div> From 16b94bb47abb2fd12c0a788b25bbe4e9740eebb7 Mon Sep 17 00:00:00 2001 From: Andrea Fercia <a.fercia@gmail.com> Date: Mon, 14 Jan 2019 14:24:46 +0100 Subject: [PATCH 143/691] Improve color contrast of the default block inserter shortcuts. (#12928) * Improve color contrast of the default block inserter shortcuts. * Restore fade in effect. --- .../src/components/block-list/style.scss | 4 ++-- .../default-block-appender/style.scss | 20 ++++--------------- .../inserter-with-shortcuts/style.scss | 4 ++-- 3 files changed, 8 insertions(+), 20 deletions(-) diff --git a/packages/editor/src/components/block-list/style.scss b/packages/editor/src/components/block-list/style.scss index 3ecb8dcc263b8..b89d2ac389494 100644 --- a/packages/editor/src/components/block-list/style.scss +++ b/packages/editor/src/components/block-list/style.scss @@ -250,12 +250,12 @@ &.is-typing .editor-block-list__empty-block-inserter, &.is-typing .editor-block-list__side-inserter { opacity: 0; + animation: none; } .editor-block-list__empty-block-inserter, .editor-block-list__side-inserter { - opacity: 1; - transition: opacity 0.2s; + @include edit-post__fade-in-animation; } // Reusable blocks diff --git a/packages/editor/src/components/default-block-appender/style.scss b/packages/editor/src/components/default-block-appender/style.scss index 9f30f7f31b596..55bcac1feae3b 100644 --- a/packages/editor/src/components/default-block-appender/style.scss +++ b/packages/editor/src/components/default-block-appender/style.scss @@ -26,28 +26,17 @@ } } - // Show quick insertion icons faded until hover. - .editor-inserter-with-shortcuts { - opacity: 0.5; - transition: opacity 0.2s; - - .components-icon-button:not(:hover) { - // Use opacity to work in various editor styles. - color: $dark-opacity-500; - .is-dark-theme & { - color: $light-opacity-500; - } - } - } - // Don't show the inserter until mousing over. .editor-inserter__toggle:not([aria-expanded="true"]) { opacity: 0; + transition: opacity 0.2s; } + &:hover { .editor-inserter-with-shortcuts { - opacity: 1; + @include edit-post__fade-in-animation; } + .editor-inserter__toggle { opacity: 1; } @@ -103,7 +92,6 @@ } .editor-inserter__toggle { - transition: opacity 0.2s; border-radius: 50%; width: $block-side-ui-width; height: $block-side-ui-width; diff --git a/packages/editor/src/components/inserter-with-shortcuts/style.scss b/packages/editor/src/components/inserter-with-shortcuts/style.scss index eba75a72c7771..67d4af314f872 100644 --- a/packages/editor/src/components/inserter-with-shortcuts/style.scss +++ b/packages/editor/src/components/inserter-with-shortcuts/style.scss @@ -19,8 +19,8 @@ padding-top: 8px; // Use opacity to work in various editor styles. - color: $dark-opacity-light-700; + color: $dark-opacity-500; .is-dark-theme & { - color: $light-opacity-light-700; + color: $light-opacity-500; } } From a4d2ff222ad8d14d489984cf89cbe266cd807b5e Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Mon, 14 Jan 2019 06:31:09 -0800 Subject: [PATCH 144/691] Add IIFE wrapping of JS, for scoping (#13301) --- .../tutorials/metabox/meta-block-3-add.md | 84 ++++++++++--------- 1 file changed, 43 insertions(+), 41 deletions(-) diff --git a/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md b/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md index 6f0b449f27961..3bfc68e09262b 100644 --- a/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md +++ b/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md @@ -11,48 +11,50 @@ By specifying the source of the attributes as `meta`, the Block Editor automatic Add this code to your JavaScript file (this tutorial will call the file `myguten.js`): ```js -var el = wp.element.createElement; -var registerBlockType = wp.blocks.registerBlockType; -var TextField = wp.components.TextControl; - -registerBlockType("myguten/meta-block", { - title: "Meta Block", - icon: "smiley", - category: "common", - - attributes: { - blockValue: { - type: "string", - source: "meta", - meta: "myguten_meta_block_field" +( function( wp ) { + var el = wp.element.createElement; + var registerBlockType = wp.blocks.registerBlockType; + var TextField = wp.components.TextControl; + + registerBlockType("myguten/meta-block", { + title: "Meta Block", + icon: "smiley", + category: "common", + + attributes: { + blockValue: { + type: "string", + source: "meta", + meta: "myguten_meta_block_field" + } + }, + + edit: function(props) { + var className = props.className; + var setAttributes = props.setAttributes; + + function updateBlockValue( blockValue ) { + setAttributes({ blockValue }); + } + + return el( + "div", + { className: className }, + el( TextField, { + label: "Meta Block Field", + value: props.attributes.blockValue, + onChange: updateBlockValue + } ) + ); + }, + + // No information saved to the block + // Data is saved to post meta via attributes + save: function() { + return null; } - }, - - edit: function(props) { - var className = props.className; - var setAttributes = props.setAttributes; - - function updateBlockValue( blockValue ) { - setAttributes({ blockValue }); - } - - return el( - "div", - { className: className }, - el( TextField, { - label: "Meta Block Field", - value: props.attributes.blockValue, - onChange: updateBlockValue - } ) - ); - }, - - // No information saved to the block - // Data is saved to post meta via attributes - save: function() { - return null; - } -}); + }); +})( window.wp ); ``` **Important:** Before you test, you need to enqueue your JavaScript file and its dependencies. Note the WordPress packages used above are `wp.element`, `wp.blocks`, and `wp.components`. Each of these need to be included in the array of dependencies. Update the `myguten-meta-block.php` file adding the enqueue function: From 35ff656521bee78fd3dd20e3fab5e226cb295f82 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell@kjellr.com> Date: Mon, 14 Jan 2019 13:00:18 -0500 Subject: [PATCH 145/691] Update ButtonGroup readme (#13064) * Create ButtonGroup readme Adding documentation to describe the use and functionality of the ButtonGroup component. Thanks @cburton4 and @melchoyce for help drafting this. * Migrate changes over to the original button-group README file. --- .../components/src/button-group/README.md | 51 ++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/packages/components/src/button-group/README.md b/packages/components/src/button-group/README.md index 2cdf23d037aba..700260e593eb8 100644 --- a/packages/components/src/button-group/README.md +++ b/packages/components/src/button-group/README.md @@ -1,6 +1,50 @@ # ButtonGroup -## Usage +ButtonGroup can be used to group any related buttons together. To emphasize related buttons, a group should share a common container. + +![ButtonGroup component](https://wordpress.org/gutenberg/files/2018/12/s_96EC471FE9C9D91A996770229947AAB54A03351BDE98F444FD3C1BF0CED365EA_1541792995815_ButtonGroup.png) + +## Table of contents + +1. [Design guidelines](#design-guidelines) +2. [Development guidelines](#development-guidelines) +3. [Related components](#related-components) + +## Design guidelines + +### Usage + +#### Selected action + +![ButtonGroup selection](https://wordpress.org/gutenberg/files/2018/12/s_96EC471FE9C9D91A996770229947AAB54A03351BDE98F444FD3C1BF0CED365EA_1544127594329_ButtonGroup-Do.png) + +**Do** +Only one option in a button group can be selected and active at a time. Selecting one option deselects any other. + +### Best practices + +Button groups should: + +- **Be clearly and accurately labeled.** +- **Clearly communicate that clicking or tapping will trigger an action.** +- **Use established colors appropriately.** For example, only use red buttons for actions that are difficult or impossible to undo. +- **Have consistent locations in the interface.** + +### States + +![ButtonGroup component](https://wordpress.org/gutenberg/files/2018/12/s_96EC471FE9C9D91A996770229947AAB54A03351BDE98F444FD3C1BF0CED365EA_1541792995815_ButtonGroup.png) + +**Active and available button groups** + +A button group’s state makes it clear which button is active. Hover and focus states express the available selection options for buttons in a button group. + +**Disabled button groups** + +Button groups that cannot be selected can either be given a disabled state, or be hidden. + +## Development guidelines + +### Usage ```jsx import { Button, ButtonGroup } from '@wordpress/components'; @@ -12,3 +56,8 @@ const MyButtonGroup = () => ( </ButtonGroup> ); ``` + +## Related components + +- For individual buttons, use a `Button` component. +- To group `IconButtons` use a `Toolbar` component. From b61c372f98592bf5bec8b0ec63bf83a92d5a5e78 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell@kjellr.com> Date: Mon, 14 Jan 2019 13:00:54 -0500 Subject: [PATCH 146/691] Update IconButton readme (#13063) * Create icon-button readme Adding documentation to describe the use and functionality of the IconButton component. * Migrate changes over to the original icon-button readme file. --- packages/components/src/icon-button/README.md | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/packages/components/src/icon-button/README.md b/packages/components/src/icon-button/README.md index 2d47a5bf3a203..9637c3b046398 100644 --- a/packages/components/src/icon-button/README.md +++ b/packages/components/src/icon-button/README.md @@ -1,6 +1,38 @@ # IconButton -## Usage +Icon buttons tell users what actions they can take and give them a way to interact with the interface. You’ll find them throughout a UI, particularly in places like: + +- Button groups +- Toolbars + +![IconButton component](https://wordpress.org/gutenberg/files/2018/12/s_1D98B71431B26D39301DAD9ADD4189EA9DDF0B98AC93C77E7DE58172FFC06323_1541793686578_IconButton.png) + +![Toolbar component composed of attached IconButtons](https://wordpress.org/gutenberg/files/2018/12/s_1D98B71431B26D39301DAD9ADD4189EA9DDF0B98AC93C77E7DE58172FFC06323_1541793628237_Toolbar.png) + +## Table of contents + +1. [Design guidelines](#design-guidelines) +2. [Development guidelines](#development-guidelines) +3. [Related components](#related-components) + +# Design guidelines + +## Best practices + +Icon buttons should: + +- **Choose a clear and accurate icon to represent the action.** +- **Clearly communicate that clicking or tapping will trigger an action.** +- **Use established colors appropriately.** For example, only use red buttons for actions that are difficult or impossible to undo. +- **Have consistent locations in the interface.** + +## Anatomy + +Icon buttons have low emphasis. They don’t stand out much on the page, so they’re used for less-important actions. What’s less important can vary based on context, but it’s usually a supplementary action to the main action we want someone to take. Icon buttons are also useful when you don’t want to distract from the content. + +# Development guidelines + +## Usage ```jsx import { IconButton } from '@wordpress/components'; @@ -12,3 +44,7 @@ const MyIconButton = () => ( /> ); ``` + +# Related components + +- The `Toolbar` component is composed of `IconButton` components. From 0c9d40d5aeb94b0d0df844b0a4fd15f83cd53e0e Mon Sep 17 00:00:00 2001 From: Derek Sifford <dereksifford@gmail.com> Date: Mon, 14 Jan 2019 14:44:05 -0500 Subject: [PATCH 147/691] [docs] fix incorrect es5 in transform example (#13053) Should be self-explanatory. Let me know if there's any further details/clarification you'd like from me. --- .../developers/block-api/block-registration.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/designers-developers/developers/block-api/block-registration.md b/docs/designers-developers/developers/block-api/block-registration.md index af8aaa36a75ed..90d0752ff170e 100644 --- a/docs/designers-developers/developers/block-api/block-registration.md +++ b/docs/designers-developers/developers/block-api/block-registration.md @@ -184,9 +184,9 @@ transforms: { { type: 'block', blocks: [ 'core/paragraph' ], - transform: function ( content ) { + transform: function ( attributes ) { return createBlock( 'core/heading', { - content, + content: attributes.content, } ); }, }, @@ -284,9 +284,9 @@ transforms: { { type: 'block', blocks: [ 'core/paragraph' ], - transform: function( content ) { + transform: function( attributes ) { return createBlock( 'core/paragraph', { - content, + content: attributes.content, } ); }, }, @@ -321,12 +321,12 @@ transforms: { { type: 'block', blocks: [ 'core/paragraph' ], - isMatch: function( attribute ) { + isMatch: function( attributes ) { return attributes.isText; }, - transform: function( content ) { + transform: function( attributes ) { return createBlock( 'core/paragraph', { - content, + content: attributes.content, } ); }, }, From 14aa7d7d8adf4a3683bdaaf6a7cf154cd598f5fd Mon Sep 17 00:00:00 2001 From: Mel Choyce <melchoyce@users.noreply.github.com> Date: Mon, 14 Jan 2019 15:51:02 -0500 Subject: [PATCH 148/691] Update Documentation: SelectControl readme (#11872) Adding documentation to describe the use and functionality of the SelectControl component. --- .../components/src/select-control/README.md | 183 ++++++++++++------ 1 file changed, 128 insertions(+), 55 deletions(-) diff --git a/packages/components/src/select-control/README.md b/packages/components/src/select-control/README.md index 83f958c9684d9..59e5bd5e40e61 100644 --- a/packages/components/src/select-control/README.md +++ b/packages/components/src/select-control/README.md @@ -1,89 +1,162 @@ # SelectControl -SelectControl component is used to generate select input fields. +SelectControl allow users to select from a single-option menu. It functions as a wrapper around the browser's native `<select>` element. +![A “Link To” select with “none” selected.](https://wordpress.org/gutenberg/files/2018/12/select.png) -## Usage +## Table of contents + +1. [Design guidelines](http://##design-guidelines) +2. [Development guidelines](http://##development-guidelines) +3. [Related components](http://##related-components) + +## Design guidelines + +### Usage + +**When to use** **a select control** + +Use a select control when: + +- You want users to select a single option from a list. +- There is a strong default option. +- There is little available space. +- The contents of the hidden part of the menu are obvious from its label and the one selected item. For example, if you have an option menu labelled “Month:” with the item “January” selected, the user might reasonably infer that the menu contains the 12 months of the year without having to look. + +If you have a shorter list of options, or need all of the options visible to the user, consider using RadioControl instead. + +![](https://wordpress.org/gutenberg/files/2018/12/select-do-multiple.png) + +**Do** +Use selects when you have multiple options, and can only choose one. + +![](https://wordpress.org/gutenberg/files/2018/12/select-dont-binary.png) + +**Don’t** +Use selects for binary questions. + +### Behavior + +A SelectControl includes a double-arrow indicator. The menu appears layered over the select. + +**Opening and Closing** + +Once the menu is displayed onscreen, it remains open until the user chooses a menu item, clicks outside of the menu, or switches to another browser tab. + +### Content Guidelines + +**Labels** + +Label the SelectControl with a text label above it, or to its left, using sentence capitalization. Clicking the label allows the user to focus directly on the select. + +![](https://wordpress.org/gutenberg/files/2018/12/select-do-position.png) + +**Do** +Position the label above, or to the left of, the select. + +![](https://wordpress.org/gutenberg/files/2018/12/select-dont-position.png) + +**Don’t** +Position the label centered over the select, or right aligned against the side of the select. + +**Menu Items** + +- Menu items should be short — ideally, single words — and use sentence capitalization. +- Do not use full sentences inside menu items. +- Ensure that menu items are ordered in a way that is most useful to users. Alphabetical or recency ordering is preferred. + +![](https://wordpress.org/gutenberg/files/2018/12/select-do-options.png) + +**Do** +Use short menu items. + +![](https://wordpress.org/gutenberg/files/2018/12/select-dont-options.png) + +**Don’t** +Use sentences in your menu. + +## Development guidelines + +### Usage Render a user interface to select the size of an image. -```jsx -import { SelectControl } from '@wordpress/components'; -import { withState } from '@wordpress/compose'; - -const MySelectControl = withState( { - size: '50%', -} )( ( { size, setState } ) => ( - <SelectControl - label="Size" - value={ size } - options={ [ - { label: 'Big', value: '100%' }, - { label: 'Medium', value: '50%' }, - { label: 'Small', value: '25%' }, - ] } - onChange={ ( size ) => { setState( { size } ) } } - /> -) ); -``` + + import { SelectControl } from '@wordpress/components'; + import { withState } from '@wordpress/compose'; + + const MySelectControl = withState( { + size: '50%', + } )( ( { size, setState } ) => ( + <SelectControl + label="Size" + value={ size } + options={ [ + { label: 'Big', value: '100%' }, + { label: 'Medium', value: '50%' }, + { label: 'Small', value: '25%' }, + ] } + onChange={ ( size ) => { setState( { size } ) } } + /> + ) ); Render a user interface to select multiple users from a list. -```jsx - <SelectControl - multiple - label={ __( 'Select some users:' ) } - value={ this.state.users } // e.g: value = [ 'a', 'c' ] - onChange={ ( users ) => { this.setState( { users } ) } } - options={ [ - { value: 'a', label: 'User A' }, - { value: 'b', label: 'User B' }, - { value: 'c', label: 'User c' }, - ] } - /> -``` - -## Props - -The set of props accepted by the component will be specified below. -Props not included in this set will be applied to the select element. -One important prop to refer is value, if multiple is true, -value should be an array with the values of the selected options. -If multiple is false value should be equal to the value of the selected option. - -### label -If this property is added, a label will be generated using label property as the content. + <SelectControl + multiple + label={ __( 'Select some users:' ) } + value={ this.state.users } // e.g: value = [ 'a', 'c' ] + onChange={ ( users ) => { this.setState( { users } ) } } + options={ [ + { value: 'a', label: 'User A' }, + { value: 'b', label: 'User B' }, + { value: 'c', label: 'User c' }, + ] } + /> + +### Props + +- The set of props accepted by the component will be specified below. +- Props not included in this set will be applied to the select element. +- One important prop to refer is `value`. If `multiple` is `true`, `value` should be an array with the values of the selected options. +- If `multiple` is `false`, `value` should be equal to the value of the selected option. + +**label** + +If this property is added, a label will be generated using label property as the content. - Type: `String` - Required: No -### help +**help** If this property is added, a help text will be generated using help property as the content. - - Type: `String` - Required: No -### multiple +**multiple** If this property is added, multiple values can be selected. The value passed should be an array. - - Type: `Boolean` - Required: No -### options +**options** An array of objects containing the following properties: -* `label`: (string) The label to be shown to the user. -* `value`: (Object) The internal value used to choose the selected value. This is also the value passed to onChange when the option is selected. - +- `label`: (string) The label to be shown to the user. +- `value`: (Object) The internal value used to choose the selected value. This is also the value passed to onChange when the option is selected. - Type: `Array` - Required: No -### onChange +**onChange** A function that receives the value of the new option that is being selected as input. If multiple is true the value received is an array of the selected value. If multiple is false the value received is a single value with the new selected value. - - Type: `function` - Required: Yes + +## Related components + +- To select one option from a set, and you want to show them all the available options at once, use the `Radio` component. +- To select one or more items from a set, use the `CheckboxControl` component. +- To toggle a single setting on or off, use the `ToggleControl` component. From 65d9e9a0ee58a49d6b805563964d2b9209a01e65 Mon Sep 17 00:00:00 2001 From: Joen Asmussen <joen@automattic.com> Date: Tue, 15 Jan 2019 16:18:56 +0100 Subject: [PATCH 149/691] Remove no longer used z-index. (#13322) Fixes #13308. The z-index was initially added to address some issues with the Edit button on a reusable block having a microscopic hit area. This no longer appears to be the case, perhaps due to other refactors. This PR removes the redundant rule. Please test for regressions around reusable blocks. --- assets/stylesheets/_z-index.scss | 3 --- packages/block-library/src/block/edit-panel/style.scss | 1 - 2 files changed, 4 deletions(-) diff --git a/assets/stylesheets/_z-index.scss b/assets/stylesheets/_z-index.scss index 8e4774f262def..b21bff104a6ca 100644 --- a/assets/stylesheets/_z-index.scss +++ b/assets/stylesheets/_z-index.scss @@ -37,9 +37,6 @@ $z-layers: ( // Active pill button ".components-button.is-button {:focus or .is-primary}": 1, - // Reusable blocks UI, needs to be above sibling inserter. - ".editor-block-list__layout .reusable-block-edit-panel": 7, - // The draggable element should show up above the entire UI ".components-draggable__clone": 1000000000, diff --git a/packages/block-library/src/block/edit-panel/style.scss b/packages/block-library/src/block/edit-panel/style.scss index 854cfe03f282f..4573abd8ad81c 100644 --- a/packages/block-library/src/block/edit-panel/style.scss +++ b/packages/block-library/src/block/edit-panel/style.scss @@ -11,7 +11,6 @@ margin: 0 (-$block-padding); padding: $grid-size $block-padding; position: relative; - z-index: z-index(".editor-block-list__layout .reusable-block-edit-panel"); // Show a smaller padding when nested. .editor-block-list__layout & { From ec1176dee12805988afbec6520581dadc655b684 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Tue, 15 Jan 2019 15:21:05 +0000 Subject: [PATCH 150/691] Components: Optimize withFilters, avoiding per-instance bindings (#13231) * Components: Use single hook delegator for withFilters * Component: Share component definition for withFilters instances * Components: Use slash for withFilters subgrouping * Components: Assign FilteredComponent only once renderer constructed * Component: Handle withFilters forced update through delegator Ensures that FilteredComponent definition is only computed once per animation frame debounce --- packages/components/CHANGELOG.md | 7 ++ .../src/higher-order/with-filters/index.js | 113 +++++++++++++----- 2 files changed, 88 insertions(+), 32 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index eb67ff9e79332..93591222faad9 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -1,3 +1,10 @@ +## 7.1.0 (Unreleased) + +### Improvements + +- `withFilters` has been optimized to avoid binding hook handlers for each mounted instance of the component, instead using a single centralized hook delegator. +- `withFilters` has been optimized to reuse a single shared component definition for all filtered instances of the component. + ## 7.0.5 (2019-01-03) ## 7.0.4 (2018-12-12) diff --git a/packages/components/src/higher-order/with-filters/index.js b/packages/components/src/higher-order/with-filters/index.js index 6c621c679b377..e44a41cee35af 100644 --- a/packages/components/src/higher-order/with-filters/index.js +++ b/packages/components/src/higher-order/with-filters/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { debounce, uniqueId } from 'lodash'; +import { debounce, without } from 'lodash'; /** * WordPress dependencies @@ -24,45 +24,94 @@ const ANIMATION_FRAME_PERIOD = 16; */ export default function withFilters( hookName ) { return createHigherOrderComponent( ( OriginalComponent ) => { - return class FilteredComponent extends Component { - /** @inheritdoc */ - constructor( props ) { - super( props ); - - this.onHooksUpdated = this.onHooksUpdated.bind( this ); - this.Component = applyFilters( hookName, OriginalComponent ); - this.namespace = uniqueId( 'core/with-filters/component-' ); - this.throttledForceUpdate = debounce( () => { - this.Component = applyFilters( hookName, OriginalComponent ); - this.forceUpdate(); - }, ANIMATION_FRAME_PERIOD ); - - addAction( 'hookRemoved', this.namespace, this.onHooksUpdated ); - addAction( 'hookAdded', this.namespace, this.onHooksUpdated ); + const namespace = 'core/with-filters/' + hookName; + + /** + * The component definition with current filters applied. Each instance + * reuse this shared reference as an optimization to avoid excessive + * calls to `applyFilters` when many instances exist. + * + * @type {?Component} + */ + let FilteredComponent; + + /** + * Initializes the FilteredComponent variable once, if not already + * assigned. Subsequent calls are effectively a noop. + */ + function ensureFilteredComponent() { + if ( FilteredComponent === undefined ) { + FilteredComponent = applyFilters( hookName, OriginalComponent ); } + } - /** @inheritdoc */ - componentWillUnmount() { - this.throttledForceUpdate.cancel(); - removeAction( 'hookRemoved', this.namespace ); - removeAction( 'hookAdded', this.namespace ); + class FilteredComponentRenderer extends Component { + constructor() { + super( ...arguments ); + + ensureFilteredComponent(); + } + + componentDidMount() { + FilteredComponentRenderer.instances.push( this ); + + // If there were previously no mounted instances for components + // filtered on this hook, add the hook handler. + if ( FilteredComponentRenderer.instances.length === 1 ) { + addAction( 'hookRemoved', namespace, onHooksUpdated ); + addAction( 'hookAdded', namespace, onHooksUpdated ); + } } - /** - * When a filter is added or removed for the matching hook name, the wrapped component should re-render. - * - * @param {string} updatedHookName Name of the hook that was updated. - */ - onHooksUpdated( updatedHookName ) { - if ( updatedHookName === hookName ) { - this.throttledForceUpdate(); + componentWillUnmount() { + FilteredComponentRenderer.instances = without( + FilteredComponentRenderer.instances, + this + ); + + // If this was the last of the mounted components filtered on + // this hook, remove the hook handler. + if ( FilteredComponentRenderer.instances.length === 0 ) { + removeAction( 'hookRemoved', namespace ); + removeAction( 'hookAdded', namespace ); } } - /** @inheritdoc */ render() { - return <this.Component { ...this.props } />; + return <FilteredComponent { ...this.props } />; + } + } + + FilteredComponentRenderer.instances = []; + + /** + * Updates the FilteredComponent definition, forcing a render for each + * mounted instance. This occurs a maximum of once per animation frame. + */ + const throttledForceUpdate = debounce( () => { + // Recreate the filtered component, only after delay so that it's + // computed once, even if many filters added. + FilteredComponent = applyFilters( hookName, OriginalComponent ); + + // Force each instance to render. + FilteredComponentRenderer.instances.forEach( ( instance ) => { + instance.forceUpdate(); + } ); + }, ANIMATION_FRAME_PERIOD ); + + /** + * When a filter is added or removed for the matching hook name, each + * mounted instance should re-render with the new filters having been + * applied to the original component. + * + * @param {string} updatedHookName Name of the hook that was updated. + */ + function onHooksUpdated( updatedHookName ) { + if ( updatedHookName === hookName ) { + throttledForceUpdate(); } - }; + } + + return FilteredComponentRenderer; }, 'withFilters' ); } From 69a336f7a071d6fb572a94e386d8960da7644c24 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Tue, 15 Jan 2019 15:21:33 +0000 Subject: [PATCH 151/691] Underline text when cmd + u is pressed (#13117) --- packages/format-library/src/index.js | 2 + .../format-library/src/underline/index.js | 40 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 packages/format-library/src/underline/index.js diff --git a/packages/format-library/src/index.js b/packages/format-library/src/index.js index 387c2666f7e0e..05adec8abf40a 100644 --- a/packages/format-library/src/index.js +++ b/packages/format-library/src/index.js @@ -7,6 +7,7 @@ import { image } from './image'; import { italic } from './italic'; import { link } from './link'; import { strikethrough } from './strikethrough'; +import { underline } from './underline'; /** * WordPress dependencies @@ -22,4 +23,5 @@ import { italic, link, strikethrough, + underline, ].forEach( ( { name, ...settings } ) => registerFormatType( name, settings ) ); diff --git a/packages/format-library/src/underline/index.js b/packages/format-library/src/underline/index.js new file mode 100644 index 0000000000000..3aee9cf943877 --- /dev/null +++ b/packages/format-library/src/underline/index.js @@ -0,0 +1,40 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Fragment } from '@wordpress/element'; +import { toggleFormat } from '@wordpress/rich-text'; +import { RichTextShortcut } from '@wordpress/editor'; + +const name = 'core/underline'; + +export const underline = { + name, + title: __( 'Underline' ), + tagName: 'span', + className: null, + attributes: { + style: 'style', + }, + edit( { value, onChange } ) { + const onToggle = () => { + onChange( + toggleFormat( value, { + type: name, + attributes: { + style: 'text-decoration: underline;', + }, + } ) ); + }; + + return ( + <Fragment> + <RichTextShortcut + type="primary" + character="u" + onUse={ onToggle } + /> + </Fragment> + ); + }, +}; From feb8bfc5535e8c8aff4a32d3b266e66772341629 Mon Sep 17 00:00:00 2001 From: John <johng75@gmail.com> Date: Tue, 15 Jan 2019 15:43:32 +0000 Subject: [PATCH 152/691] Autop: Fix RegExp escaping copy/paste error (#12614) When RegExp is used the escape regular expression characters need double-escaping --- packages/autop/CHANGELOG.md | 6 ++++++ packages/autop/src/index.js | 10 +++++----- packages/autop/src/test/index.test.js | 7 +++++++ .../full-content/fixtures/core__freeform.json | 4 ++-- .../fixtures/core__freeform.serialized.html | 4 ++-- .../fixtures/core__freeform__undelimited.json | 4 ++-- .../core__freeform__undelimited.serialized.html | 4 ++-- 7 files changed, 26 insertions(+), 13 deletions(-) diff --git a/packages/autop/CHANGELOG.md b/packages/autop/CHANGELOG.md index 04917d2c7de86..184ca54078a38 100644 --- a/packages/autop/CHANGELOG.md +++ b/packages/autop/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.0.1 (Unreleased) + +### Bug Fix + +- `autop` correctly matches whitespace preceding and following block-level elements. + ## 2.0.0 (2018-09-05) ### Breaking Change diff --git a/packages/autop/src/index.js b/packages/autop/src/index.js index 432a567a67a8a..e3b9fbca887b7 100644 --- a/packages/autop/src/index.js +++ b/packages/autop/src/index.js @@ -162,7 +162,7 @@ export function autop( text, br = true ) { const allBlocks = '(?:table|thead|tfoot|caption|col|colgroup|tbody|tr|td|th|div|dl|dd|dt|ul|ol|li|pre|form|map|area|blockquote|address|math|style|p|h[1-6]|hr|fieldset|legend|section|article|aside|hgroup|header|footer|nav|figure|figcaption|details|menu|summary)'; // Add a double line break above block-level opening tags. - text = text.replace( new RegExp( '(<' + allBlocks + '[\s\/>])', 'g' ), '\n\n$1' ); + text = text.replace( new RegExp( '(<' + allBlocks + '[\\s\/>])', 'g' ), '\n\n$1' ); // Add a double line break below block-level closing tags. text = text.replace( new RegExp( '(<\/' + allBlocks + '>)', 'g' ), '$1\n\n' ); @@ -226,7 +226,7 @@ export function autop( text, br = true ) { text = text.replace( /<p>([^<]+)<\/(div|address|form)>/g, '<p>$1</p></$2>' ); // If an opening or closing block element tag is wrapped in a <p>, unwrap it. - text = text.replace( new RegExp( '<p>\s*(<\/?' + allBlocks + '[^>]*>)\s*<\/p>', 'g' ), '$1' ); + text = text.replace( new RegExp( '<p>\\s*(<\/?' + allBlocks + '[^>]*>)\\s*<\/p>', 'g' ), '$1' ); // In some cases <li> may get wrapped in <p>, fix them. text = text.replace( /<p>(<li.+?)<\/p>/g, '$1' ); @@ -236,10 +236,10 @@ export function autop( text, br = true ) { text = text.replace( /<\/blockquote><\/p>/g, '</p></blockquote>' ); // If an opening or closing block element tag is preceded by an opening <p> tag, remove it. - text = text.replace( new RegExp( '<p>\s*(<\/?' + allBlocks + '[^>]*>)', 'g' ), '$1' ); + text = text.replace( new RegExp( '<p>\\s*(<\/?' + allBlocks + '[^>]*>)', 'g' ), '$1' ); // If an opening or closing block element tag is followed by a closing <p> tag, remove it. - text = text.replace( new RegExp( '(<\/?' + allBlocks + '[^>]*>)\s*<\/p>', 'g' ), '$1' ); + text = text.replace( new RegExp( '(<\/?' + allBlocks + '[^>]*>)\\s*<\/p>', 'g' ), '$1' ); // Optionally insert line breaks. if ( br ) { @@ -257,7 +257,7 @@ export function autop( text, br = true ) { } // If a <br /> tag is after an opening or closing block tag, remove it. - text = text.replace( new RegExp( '(<\/?' + allBlocks + '[^>]*>)\s*<br \/>', 'g' ), '$1' ); + text = text.replace( new RegExp( '(<\/?' + allBlocks + '[^>]*>)\\s*<br \/>', 'g' ), '$1' ); // If a <br /> tag is before a subset of opening or closing block tags, remove it. text = text.replace( /<br \/>(\s*<\/?(?:p|li|div|dl|dd|dt|th|pre|td|ul|ol)[^>]*>)/g, '$1' ); diff --git a/packages/autop/src/test/index.test.js b/packages/autop/src/test/index.test.js index ea8e4c605343e..9e945d3d9e8ce 100644 --- a/packages/autop/src/test/index.test.js +++ b/packages/autop/src/test/index.test.js @@ -495,3 +495,10 @@ test( 'that autop doses not add extra closing p in figure', () => { expect( autop( content1 ).trim() ).toBe( expected1 ); expect( autop( content2 ).trim() ).toBe( expected2 ); } ); + +test( 'that autop correctly adds a start and end tag when followed by a div', () => { + const content = 'Testing autop with a div\n<div class="wp-some-class">content</div>'; + const expected = '<p>Testing autop with a div</p>\n<div class="wp-some-class">content</div>'; + + expect( autop( content ).trim() ).toBe( expected ); +} ); diff --git a/test/integration/full-content/fixtures/core__freeform.json b/test/integration/full-content/fixtures/core__freeform.json index 72341f093f2fb..6b97ab677f227 100644 --- a/test/integration/full-content/fixtures/core__freeform.json +++ b/test/integration/full-content/fixtures/core__freeform.json @@ -4,9 +4,9 @@ "name": "core/freeform", "isValid": true, "attributes": { - "content": "<p>Testing freeform block with some\n</p><div class=\"wp-some-class\">\n\tHTML <span style=\"color: red;\">content</span>\n</div>" + "content": "<p>Testing freeform block with some</p>\n<div class=\"wp-some-class\">\n\tHTML <span style=\"color: red;\">content</span>\n</div>" }, "innerBlocks": [], - "originalContent": "<p>Testing freeform block with some\n<div class=\"wp-some-class\">\n\tHTML <span style=\"color: red;\">content</span>\n</div>" + "originalContent": "<p>Testing freeform block with some</p>\n<div class=\"wp-some-class\">\n\tHTML <span style=\"color: red;\">content</span>\n</div>" } ] diff --git a/test/integration/full-content/fixtures/core__freeform.serialized.html b/test/integration/full-content/fixtures/core__freeform.serialized.html index 4fed04550b031..632a89b49e626 100644 --- a/test/integration/full-content/fixtures/core__freeform.serialized.html +++ b/test/integration/full-content/fixtures/core__freeform.serialized.html @@ -1,4 +1,4 @@ -<p>Testing freeform block with some -</p><div class="wp-some-class"> +<p>Testing freeform block with some</p> +<div class="wp-some-class"> HTML <span style="color: red;">content</span> </div> diff --git a/test/integration/full-content/fixtures/core__freeform__undelimited.json b/test/integration/full-content/fixtures/core__freeform__undelimited.json index 72341f093f2fb..6b97ab677f227 100644 --- a/test/integration/full-content/fixtures/core__freeform__undelimited.json +++ b/test/integration/full-content/fixtures/core__freeform__undelimited.json @@ -4,9 +4,9 @@ "name": "core/freeform", "isValid": true, "attributes": { - "content": "<p>Testing freeform block with some\n</p><div class=\"wp-some-class\">\n\tHTML <span style=\"color: red;\">content</span>\n</div>" + "content": "<p>Testing freeform block with some</p>\n<div class=\"wp-some-class\">\n\tHTML <span style=\"color: red;\">content</span>\n</div>" }, "innerBlocks": [], - "originalContent": "<p>Testing freeform block with some\n<div class=\"wp-some-class\">\n\tHTML <span style=\"color: red;\">content</span>\n</div>" + "originalContent": "<p>Testing freeform block with some</p>\n<div class=\"wp-some-class\">\n\tHTML <span style=\"color: red;\">content</span>\n</div>" } ] diff --git a/test/integration/full-content/fixtures/core__freeform__undelimited.serialized.html b/test/integration/full-content/fixtures/core__freeform__undelimited.serialized.html index 4fed04550b031..632a89b49e626 100644 --- a/test/integration/full-content/fixtures/core__freeform__undelimited.serialized.html +++ b/test/integration/full-content/fixtures/core__freeform__undelimited.serialized.html @@ -1,4 +1,4 @@ -<p>Testing freeform block with some -</p><div class="wp-some-class"> +<p>Testing freeform block with some</p> +<div class="wp-some-class"> HTML <span style="color: red;">content</span> </div> From b786aad41b4670190b6cac4d99bab3abd3f85049 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Tue, 15 Jan 2019 16:30:37 +0000 Subject: [PATCH 153/691] Tests: Extract e2e test utils to their own package (#13228) * Tests: Move e2e tests dependent on plugins to their own folder * Tests: Extract e2e test utils to their own package --- .eslintignore | 2 +- .eslintrc.js | 2 +- .travis.yml | 8 ++-- docker-compose.yml | 8 ++-- docs/manifest.json | 18 +++++--- jsconfig.json | 2 +- package-lock.json | 33 +++++++++------ package.json | 5 ++- packages/{tests-e2e => e2e-test-utils}/.npmrc | 0 packages/e2e-test-utils/CHANGELOG.md | 3 ++ packages/e2e-test-utils/README.md | 13 ++++++ packages/e2e-test-utils/package.json | 39 ++++++++++++++++++ .../src}/activate-plugin.js | 0 .../src}/are-pre-publish-checks-enabled.js | 0 .../src}/clear-local-storage.js | 0 .../src}/click-block-appender.js | 0 .../src}/click-button.js | 0 .../src}/click-on-close-modal-button.js | 0 .../src}/click-on-more-menu-item.js | 0 .../src}/create-new-post.js | 0 .../src}/create-url.js | 0 .../src}/deactivate-plugin.js | 0 .../src}/disable-pre-publish-checks.js | 0 .../src}/enable-page-dialog-accept.js | 0 .../src}/enable-pre-publish-checks.js | 0 .../src}/ensure-sidebar-opened.js | 0 .../src}/find-sidebar-panel-with-title.js | 0 .../src}/get-all-blocks.js | 0 .../src}/get-available-block-transforms.js | 0 .../src}/get-edited-post-content.js | 0 .../src}/has-block-switcher.js | 0 .../utils => e2e-test-utils/src}/index.js | 0 .../src}/insert-block.js | 0 .../src}/install-plugin.js | 0 .../src}/is-current-url.js | 0 .../src}/login-user.js | 0 .../src}/mocks/create-embedding-matcher.js | 0 .../src}/mocks/create-json-response.js | 0 .../src}/mocks/create-url-matcher.js | 0 .../src}/mocks/index.js | 0 .../src}/mocks/mock-or-transform.js | 0 .../src}/mocks/set-up-response-mocking.js | 0 .../src}/observe-focus-loss.js | 0 .../src}/open-document-settings-sidebar.js | 0 .../src}/open-publish-panel.js | 0 .../src}/press-key-times.js | 0 .../src}/press-key-with-modifier.js | 0 ...h-post-with-pre-publish-checks-disabled.js | 0 .../src}/publish-post.js | 0 .../src}/save-draft.js | 0 .../src}/search-for-block.js | 0 .../src}/select-block-by-client-id.js | 0 .../src}/set-browser-viewport.js | 0 .../src}/set-post-content.js | 0 .../src}/shared/config.js | 0 .../src}/shared/get-json-response.js | 0 .../src}/switch-editor-mode-to.js | 0 .../src}/switch-user-to-admin.js | 0 .../src}/switch-user-to-test.js | 0 .../src}/toggle-screen-option.js | 0 .../src}/transform-block-to.js | 0 .../src}/uninstall-plugin.js | 0 .../src}/visit-admin-page.js | 0 .../src}/wait-for-window-dimensions.js | 0 packages/e2e-tests/.npmrc | 1 + packages/e2e-tests/CHANGELOG.md | 3 ++ packages/{tests-e2e => e2e-tests}/README.md | 4 +- .../assets/10x10_e2e_test_image_z9T8jK.png | Bin .../assets/greeting-reusable-block.json | 0 .../config}/setup-test-framework.js | 6 +-- packages/e2e-tests/jest.config.js | 4 ++ .../mu-plugins/disable-login-autofocus.php | 0 .../{tests-e2e => e2e-tests}/package.json | 16 ++++--- .../plugins/align-hook.php | 0 .../plugins/align-hook/index.js | 0 .../plugins/block-icons.php | 0 .../plugins/block-icons/index.js | 0 .../plugins/container-without-paragraph.php | 0 .../container-without-paragraph/index.js | 0 .../plugins/custom-post-types.php | 0 .../plugins/default-post-content.php | 0 .../plugins/deprecated-node-matcher.php | 0 .../plugins/deprecated-node-matcher/index.js | 0 .../plugins/format-api.php | 0 .../plugins/format-api/index.js | 0 .../plugins/hooks-api.php | 0 .../plugins/hooks-api/index.js | 0 .../plugins/inner-blocks-templates.php | 0 .../plugins/inner-blocks-templates/index.js | 0 .../plugins/meta-attribute-block.php | 0 .../plugins/meta-attribute-block/index.js | 0 .../plugins/meta-box.php | 0 .../plugins/plugins-api.php | 0 .../plugins-api/annotations-sidebar.js | 0 .../plugins/plugins-api/post-status-info.js | 0 .../plugins/plugins-api/publish-panel.js | 0 .../plugins/plugins-api/sidebar.js | 0 .../plugins/post-formats.php | 0 .../plugins/templates.php | 0 .../plugins/wp-editor-metabox.php | 0 .../__snapshots__/adding-blocks.test.js.snap | 0 .../__snapshots__/block-deletion.test.js.snap | 0 .../block-hierarchy-navigation.test.js.snap | 0 .../compatibility-classic-editor.test.js.snap | 0 .../convert-block-type.test.js.snap | 0 .../__snapshots__/embedding.test.js.snap | 0 .../font-size-picker.test.js.snap | 0 .../specs/__snapshots__/links.test.js.snap | 0 .../specs/__snapshots__/mentions.test.js.snap | 0 .../reusable-blocks.test.js.snap | 0 .../__snapshots__/rich-text.test.js.snap | 0 .../splitting-merging.test.js.snap | 0 .../style-variation.test.js.snap | 0 .../specs/__snapshots__/undo.test.js.snap | 0 .../__snapshots__/writing-flow.test.js.snap | 0 .../specs/a11y.test.js | 4 +- .../specs/adding-blocks.test.js | 4 +- .../specs/adding-inline-tokens.test.js | 4 +- .../specs/block-deletion.test.js | 4 +- .../specs/block-hierarchy-navigation.test.js | 4 +- .../specs/block-mover.test.js | 4 +- .../specs/block-switcher.test.js | 4 +- .../blocks/__snapshots__/classic.test.js.snap | 0 .../blocks/__snapshots__/code.test.js.snap | 0 .../blocks/__snapshots__/heading.test.js.snap | 0 .../blocks/__snapshots__/html.test.js.snap | 0 .../blocks/__snapshots__/list.test.js.snap | 0 .../blocks/__snapshots__/quote.test.js.snap | 0 .../__snapshots__/separator.test.js.snap | 0 .../specs/blocks/classic.test.js | 4 +- .../specs/blocks/code.test.js | 4 +- .../specs/blocks/heading.test.js | 4 +- .../specs/blocks/html.test.js | 4 +- .../specs/blocks/list.test.js | 4 +- .../specs/blocks/quote.test.js | 4 +- .../specs/blocks/separator.test.js | 4 +- .../specs/change-detection.test.js | 4 +- .../compatibility-classic-editor.test.js | 4 +- .../specs/convert-block-type.test.js | 4 +- .../specs/datepicker.test.js | 4 +- .../specs/demo.test.js | 4 +- .../specs/editor-modes.test.js | 4 +- .../specs/embedding.test.js | 4 +- .../specs/font-size-picker.test.js | 4 +- .../specs/fullscreen-mode.test.js | 4 +- .../specs/invalid-block.test.js | 4 +- .../specs/links.test.js | 4 +- .../specs/manage-reusable-blocks.test.js | 4 +- .../specs/mentions.test.js | 4 +- .../specs/multi-block-selection.test.js | 4 +- .../specs/navigable-toolbar.test.js | 4 +- .../specs/new-post-default-content.test.js | 4 +- .../specs/new-post.test.js | 4 +- .../specs/nux.test.js | 4 +- .../__snapshots__/align-hook.test.js.snap | 0 .../container-blocks.test.js.snap | 0 .../deprecated-node-matcher.test.js.snap | 0 .../__snapshots__/format-api.test.js.snap | 0 .../meta-attribute-block.test.js.snap | 0 .../__snapshots__/plugins-api.test.js.snap | 0 .../__snapshots__/templates.test.js.snap | 0 .../wp-editor-meta-box.test.js.snap | 0 .../specs/plugins}/align-hook.test.js | 4 +- .../specs/plugins}/annotations.test.js | 4 +- .../specs/plugins}/block-icons.test.js | 4 +- .../specs/plugins}/container-blocks.test.js | 4 +- .../plugins}/deprecated-node-matcher.test.js | 4 +- .../specs/plugins}/format-api.test.js | 4 +- .../specs/plugins}/hooks-api.test.js | 4 +- .../plugins}/meta-attribute-block.test.js | 4 +- .../specs/plugins}/meta-boxes.test.js | 4 +- .../specs/plugins}/plugins-api.test.js | 4 +- .../specs/plugins}/templates.test.js | 4 +- .../specs/plugins}/wp-editor-meta-box.test.js | 4 +- .../specs/popovers.test.js | 4 +- .../specs/post-visibility.test.js | 4 +- .../specs/preferences.test.js | 4 +- .../specs/preview.test.js | 4 +- .../specs/publish-button.test.js | 2 +- .../specs/publish-panel.test.js | 2 +- .../specs/publishing.test.js | 4 +- .../specs/reusable-blocks.test.js | 4 +- .../specs/rich-text.test.js | 4 +- .../specs/shortcut-help.test.js | 4 +- .../specs/sidebar-permalink-panel.test.js | 4 +- .../specs/sidebar.test.js | 4 +- .../specs/splitting-merging.test.js | 4 +- .../specs/style-variation.test.js | 4 +- .../specs/taxonomies.test.js | 4 +- .../specs/undo.test.js | 4 +- .../specs/writing-flow.test.js | 4 +- test/unit/jest.config.json | 2 +- 192 files changed, 241 insertions(+), 168 deletions(-) rename packages/{tests-e2e => e2e-test-utils}/.npmrc (100%) create mode 100644 packages/e2e-test-utils/CHANGELOG.md create mode 100644 packages/e2e-test-utils/README.md create mode 100644 packages/e2e-test-utils/package.json rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/activate-plugin.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/are-pre-publish-checks-enabled.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/clear-local-storage.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/click-block-appender.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/click-button.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/click-on-close-modal-button.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/click-on-more-menu-item.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/create-new-post.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/create-url.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/deactivate-plugin.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/disable-pre-publish-checks.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/enable-page-dialog-accept.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/enable-pre-publish-checks.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/ensure-sidebar-opened.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/find-sidebar-panel-with-title.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/get-all-blocks.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/get-available-block-transforms.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/get-edited-post-content.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/has-block-switcher.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/index.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/insert-block.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/install-plugin.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/is-current-url.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/login-user.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/mocks/create-embedding-matcher.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/mocks/create-json-response.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/mocks/create-url-matcher.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/mocks/index.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/mocks/mock-or-transform.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/mocks/set-up-response-mocking.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/observe-focus-loss.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/open-document-settings-sidebar.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/open-publish-panel.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/press-key-times.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/press-key-with-modifier.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/publish-post-with-pre-publish-checks-disabled.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/publish-post.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/save-draft.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/search-for-block.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/select-block-by-client-id.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/set-browser-viewport.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/set-post-content.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/shared/config.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/shared/get-json-response.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/switch-editor-mode-to.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/switch-user-to-admin.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/switch-user-to-test.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/toggle-screen-option.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/transform-block-to.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/uninstall-plugin.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/visit-admin-page.js (100%) rename packages/{tests-e2e/support/utils => e2e-test-utils/src}/wait-for-window-dimensions.js (100%) create mode 100644 packages/e2e-tests/.npmrc create mode 100644 packages/e2e-tests/CHANGELOG.md rename packages/{tests-e2e => e2e-tests}/README.md (78%) rename packages/{tests-e2e => e2e-tests}/assets/10x10_e2e_test_image_z9T8jK.png (100%) rename packages/{tests-e2e => e2e-tests}/assets/greeting-reusable-block.json (100%) rename packages/{tests-e2e/support => e2e-tests/config}/setup-test-framework.js (98%) create mode 100644 packages/e2e-tests/jest.config.js rename packages/{tests-e2e => e2e-tests}/mu-plugins/disable-login-autofocus.php (100%) rename packages/{tests-e2e => e2e-tests}/package.json (73%) rename packages/{tests-e2e => e2e-tests}/plugins/align-hook.php (100%) rename packages/{tests-e2e => e2e-tests}/plugins/align-hook/index.js (100%) rename packages/{tests-e2e => e2e-tests}/plugins/block-icons.php (100%) rename packages/{tests-e2e => e2e-tests}/plugins/block-icons/index.js (100%) rename packages/{tests-e2e => e2e-tests}/plugins/container-without-paragraph.php (100%) rename packages/{tests-e2e => e2e-tests}/plugins/container-without-paragraph/index.js (100%) rename packages/{tests-e2e => e2e-tests}/plugins/custom-post-types.php (100%) rename packages/{tests-e2e => e2e-tests}/plugins/default-post-content.php (100%) rename packages/{tests-e2e => e2e-tests}/plugins/deprecated-node-matcher.php (100%) rename packages/{tests-e2e => e2e-tests}/plugins/deprecated-node-matcher/index.js (100%) rename packages/{tests-e2e => e2e-tests}/plugins/format-api.php (100%) rename packages/{tests-e2e => e2e-tests}/plugins/format-api/index.js (100%) rename packages/{tests-e2e => e2e-tests}/plugins/hooks-api.php (100%) rename packages/{tests-e2e => e2e-tests}/plugins/hooks-api/index.js (100%) rename packages/{tests-e2e => e2e-tests}/plugins/inner-blocks-templates.php (100%) rename packages/{tests-e2e => e2e-tests}/plugins/inner-blocks-templates/index.js (100%) rename packages/{tests-e2e => e2e-tests}/plugins/meta-attribute-block.php (100%) rename packages/{tests-e2e => e2e-tests}/plugins/meta-attribute-block/index.js (100%) rename packages/{tests-e2e => e2e-tests}/plugins/meta-box.php (100%) rename packages/{tests-e2e => e2e-tests}/plugins/plugins-api.php (100%) rename packages/{tests-e2e => e2e-tests}/plugins/plugins-api/annotations-sidebar.js (100%) rename packages/{tests-e2e => e2e-tests}/plugins/plugins-api/post-status-info.js (100%) rename packages/{tests-e2e => e2e-tests}/plugins/plugins-api/publish-panel.js (100%) rename packages/{tests-e2e => e2e-tests}/plugins/plugins-api/sidebar.js (100%) rename packages/{tests-e2e => e2e-tests}/plugins/post-formats.php (100%) rename packages/{tests-e2e => e2e-tests}/plugins/templates.php (100%) rename packages/{tests-e2e => e2e-tests}/plugins/wp-editor-metabox.php (100%) rename packages/{tests-e2e => e2e-tests}/specs/__snapshots__/adding-blocks.test.js.snap (100%) rename packages/{tests-e2e => e2e-tests}/specs/__snapshots__/block-deletion.test.js.snap (100%) rename packages/{tests-e2e => e2e-tests}/specs/__snapshots__/block-hierarchy-navigation.test.js.snap (100%) rename packages/{tests-e2e => e2e-tests}/specs/__snapshots__/compatibility-classic-editor.test.js.snap (100%) rename packages/{tests-e2e => e2e-tests}/specs/__snapshots__/convert-block-type.test.js.snap (100%) rename packages/{tests-e2e => e2e-tests}/specs/__snapshots__/embedding.test.js.snap (100%) rename packages/{tests-e2e => e2e-tests}/specs/__snapshots__/font-size-picker.test.js.snap (100%) rename packages/{tests-e2e => e2e-tests}/specs/__snapshots__/links.test.js.snap (100%) rename packages/{tests-e2e => e2e-tests}/specs/__snapshots__/mentions.test.js.snap (100%) rename packages/{tests-e2e => e2e-tests}/specs/__snapshots__/reusable-blocks.test.js.snap (100%) rename packages/{tests-e2e => e2e-tests}/specs/__snapshots__/rich-text.test.js.snap (100%) rename packages/{tests-e2e => e2e-tests}/specs/__snapshots__/splitting-merging.test.js.snap (100%) rename packages/{tests-e2e => e2e-tests}/specs/__snapshots__/style-variation.test.js.snap (100%) rename packages/{tests-e2e => e2e-tests}/specs/__snapshots__/undo.test.js.snap (100%) rename packages/{tests-e2e => e2e-tests}/specs/__snapshots__/writing-flow.test.js.snap (100%) rename packages/{tests-e2e => e2e-tests}/specs/a11y.test.js (96%) rename packages/{tests-e2e => e2e-tests}/specs/adding-blocks.test.js (98%) rename packages/{tests-e2e => e2e-tests}/specs/adding-inline-tokens.test.js (96%) rename packages/{tests-e2e => e2e-tests}/specs/block-deletion.test.js (98%) rename packages/{tests-e2e => e2e-tests}/specs/block-hierarchy-navigation.test.js (98%) rename packages/{tests-e2e => e2e-tests}/specs/block-mover.test.js (93%) rename packages/{tests-e2e => e2e-tests}/specs/block-switcher.test.js (97%) rename packages/{tests-e2e => e2e-tests}/specs/blocks/__snapshots__/classic.test.js.snap (100%) rename packages/{tests-e2e => e2e-tests}/specs/blocks/__snapshots__/code.test.js.snap (100%) rename packages/{tests-e2e => e2e-tests}/specs/blocks/__snapshots__/heading.test.js.snap (100%) rename packages/{tests-e2e => e2e-tests}/specs/blocks/__snapshots__/html.test.js.snap (100%) rename packages/{tests-e2e => e2e-tests}/specs/blocks/__snapshots__/list.test.js.snap (100%) rename packages/{tests-e2e => e2e-tests}/specs/blocks/__snapshots__/quote.test.js.snap (100%) rename packages/{tests-e2e => e2e-tests}/specs/blocks/__snapshots__/separator.test.js.snap (100%) rename packages/{tests-e2e => e2e-tests}/specs/blocks/classic.test.js (97%) rename packages/{tests-e2e => e2e-tests}/specs/blocks/code.test.js (87%) rename packages/{tests-e2e => e2e-tests}/specs/blocks/heading.test.js (91%) rename packages/{tests-e2e => e2e-tests}/specs/blocks/html.test.js (91%) rename packages/{tests-e2e => e2e-tests}/specs/blocks/list.test.js (99%) rename packages/{tests-e2e => e2e-tests}/specs/blocks/quote.test.js (98%) rename packages/{tests-e2e => e2e-tests}/specs/blocks/separator.test.js (86%) rename packages/{tests-e2e => e2e-tests}/specs/change-detection.test.js (99%) rename packages/{tests-e2e => e2e-tests}/specs/compatibility-classic-editor.test.js (88%) rename packages/{tests-e2e => e2e-tests}/specs/convert-block-type.test.js (92%) rename packages/{tests-e2e => e2e-tests}/specs/datepicker.test.js (95%) rename packages/{tests-e2e => e2e-tests}/specs/demo.test.js (94%) rename packages/{tests-e2e => e2e-tests}/specs/editor-modes.test.js (98%) rename packages/{tests-e2e => e2e-tests}/specs/embedding.test.js (99%) rename packages/{tests-e2e => e2e-tests}/specs/font-size-picker.test.js (98%) rename packages/{tests-e2e => e2e-tests}/specs/fullscreen-mode.test.js (90%) rename packages/{tests-e2e => e2e-tests}/specs/invalid-block.test.js (95%) rename packages/{tests-e2e => e2e-tests}/specs/links.test.js (99%) rename packages/{tests-e2e => e2e-tests}/specs/manage-reusable-blocks.test.js (93%) rename packages/{tests-e2e => e2e-tests}/specs/mentions.test.js (89%) rename packages/{tests-e2e => e2e-tests}/specs/multi-block-selection.test.js (98%) rename packages/{tests-e2e => e2e-tests}/specs/navigable-toolbar.test.js (93%) rename packages/{tests-e2e => e2e-tests}/specs/new-post-default-content.test.js (95%) rename packages/{tests-e2e => e2e-tests}/specs/new-post.test.js (97%) rename packages/{tests-e2e => e2e-tests}/specs/nux.test.js (99%) rename packages/{tests-e2e/specs => e2e-tests/specs/plugins}/__snapshots__/align-hook.test.js.snap (100%) rename packages/{tests-e2e/specs => e2e-tests/specs/plugins}/__snapshots__/container-blocks.test.js.snap (100%) rename packages/{tests-e2e/specs => e2e-tests/specs/plugins}/__snapshots__/deprecated-node-matcher.test.js.snap (100%) rename packages/{tests-e2e/specs => e2e-tests/specs/plugins}/__snapshots__/format-api.test.js.snap (100%) rename packages/{tests-e2e/specs => e2e-tests/specs/plugins}/__snapshots__/meta-attribute-block.test.js.snap (100%) rename packages/{tests-e2e/specs => e2e-tests/specs/plugins}/__snapshots__/plugins-api.test.js.snap (100%) rename packages/{tests-e2e/specs => e2e-tests/specs/plugins}/__snapshots__/templates.test.js.snap (100%) rename packages/{tests-e2e/specs => e2e-tests/specs/plugins}/__snapshots__/wp-editor-meta-box.test.js.snap (100%) rename packages/{tests-e2e/specs => e2e-tests/specs/plugins}/align-hook.test.js (98%) rename packages/{tests-e2e/specs => e2e-tests/specs/plugins}/annotations.test.js (98%) rename packages/{tests-e2e/specs => e2e-tests/specs/plugins}/block-icons.test.js (98%) rename packages/{tests-e2e/specs => e2e-tests/specs/plugins}/container-blocks.test.js (97%) rename packages/{tests-e2e/specs => e2e-tests/specs/plugins}/deprecated-node-matcher.test.js (94%) rename packages/{tests-e2e/specs => e2e-tests/specs/plugins}/format-api.test.js (94%) rename packages/{tests-e2e/specs => e2e-tests/specs/plugins}/hooks-api.test.js (95%) rename packages/{tests-e2e/specs => e2e-tests/specs/plugins}/meta-attribute-block.test.js (95%) rename packages/{tests-e2e/specs => e2e-tests/specs/plugins}/meta-boxes.test.js (98%) rename packages/{tests-e2e/specs => e2e-tests/specs/plugins}/plugins-api.test.js (97%) rename packages/{tests-e2e/specs => e2e-tests/specs/plugins}/templates.test.js (98%) rename packages/{tests-e2e/specs => e2e-tests/specs/plugins}/wp-editor-meta-box.test.js (93%) rename packages/{tests-e2e => e2e-tests}/specs/popovers.test.js (87%) rename packages/{tests-e2e => e2e-tests}/specs/post-visibility.test.js (92%) rename packages/{tests-e2e => e2e-tests}/specs/preferences.test.js (94%) rename packages/{tests-e2e => e2e-tests}/specs/preview.test.js (98%) rename packages/{tests-e2e => e2e-tests}/specs/publish-button.test.js (97%) rename packages/{tests-e2e => e2e-tests}/specs/publish-panel.test.js (98%) rename packages/{tests-e2e => e2e-tests}/specs/publishing.test.js (98%) rename packages/{tests-e2e => e2e-tests}/specs/reusable-blocks.test.js (99%) rename packages/{tests-e2e => e2e-tests}/specs/rich-text.test.js (98%) rename packages/{tests-e2e => e2e-tests}/specs/shortcut-help.test.js (95%) rename packages/{tests-e2e => e2e-tests}/specs/sidebar-permalink-panel.test.js (97%) rename packages/{tests-e2e => e2e-tests}/specs/sidebar.test.js (98%) rename packages/{tests-e2e => e2e-tests}/specs/splitting-merging.test.js (99%) rename packages/{tests-e2e => e2e-tests}/specs/style-variation.test.js (94%) rename packages/{tests-e2e => e2e-tests}/specs/taxonomies.test.js (97%) rename packages/{tests-e2e => e2e-tests}/specs/undo.test.js (97%) rename packages/{tests-e2e => e2e-tests}/specs/writing-flow.test.js (99%) diff --git a/.eslintignore b/.eslintignore index 57cc5ea6a6336..416bce0a8e056 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,6 +1,6 @@ build build-module node_modules -packages/tests-e2e/plugins +packages/e2e-tests/plugins vendor packages/block-serialization-spec-parser/parser.js diff --git a/.eslintrc.js b/.eslintrc.js index 9bce17b6e61f9..235fe1af10b07 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -182,7 +182,7 @@ module.exports = { }, overrides: [ { - files: [ 'packages/tests-e2e/**/*.js' ], + files: [ 'packages/e2e-test*/**/*.js' ], env: { browser: true, }, diff --git a/.travis.yml b/.travis.yml index 250eb24ac2af5..55fd93ba4a1ef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -67,7 +67,7 @@ jobs: install: - ./bin/setup-local-env.sh script: - - $( npm bin )/wp-scripts test-e2e --testPathPattern=/packages/tests-e2e/specs/ --listTests > ~/.jest-e2e-tests + - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 2 == 0' < ~/.jest-e2e-tests ) - name: E2E tests (Admin with plugins) (2/2) @@ -75,7 +75,7 @@ jobs: install: - ./bin/setup-local-env.sh script: - - $( npm bin )/wp-scripts test-e2e --testPathPattern=/packages/tests-e2e/specs/ --listTests > ~/.jest-e2e-tests + - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 2 == 1' < ~/.jest-e2e-tests ) - name: E2E tests (Author without plugins) (1/2) @@ -83,7 +83,7 @@ jobs: install: - ./bin/setup-local-env.sh script: - - $( npm bin )/wp-scripts test-e2e --testPathPattern=/packages/tests-e2e/specs/ --listTests > ~/.jest-e2e-tests + - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 2 == 0' < ~/.jest-e2e-tests ) - name: E2E tests (Author without plugins) (2/2) @@ -91,5 +91,5 @@ jobs: install: - ./bin/setup-local-env.sh script: - - $( npm bin )/wp-scripts test-e2e --testPathPattern=/packages/tests-e2e/specs/ --listTests > ~/.jest-e2e-tests + - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 2 == 1' < ~/.jest-e2e-tests ) diff --git a/docker-compose.yml b/docker-compose.yml index 234da4726c5d0..9a42742d389df 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,8 +12,8 @@ services: volumes: - wordpress:/var/www/html - .:/var/www/html/wp-content/plugins/gutenberg - - ./packages/tests-e2e/plugins:/var/www/html/wp-content/plugins/gutenberg-test-plugins - - ./packages/tests-e2e/mu-plugins:/var/www/html/wp-content/mu-plugins + - ./packages/e2e-tests/plugins:/var/www/html/wp-content/plugins/gutenberg-test-plugins + - ./packages/e2e-tests/mu-plugins:/var/www/html/wp-content/mu-plugins depends_on: - mysql @@ -59,8 +59,8 @@ services: volumes: - wordpress_e2e_tests:/var/www/html - .:/var/www/html/wp-content/plugins/gutenberg - - ./packages/tests-e2e/plugins:/var/www/html/wp-content/plugins/gutenberg-test-plugins - - ./test/e2e/mu-plugins:/var/www/html/wp-content/mu-plugins + - ./packages/e2e-tests/plugins:/var/www/html/wp-content/plugins/gutenberg-test-plugins + - ./packages/e2e-tests/mu-plugins:/var/www/html/wp-content/mu-plugins cli_e2e_tests: image: wordpress:cli diff --git a/docs/manifest.json b/docs/manifest.json index a6e908c687806..51741605a75c9 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -593,6 +593,18 @@ "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/dom/README.md", "parent": "packages" }, + { + "title": "@wordpress/e2e-test-utils", + "slug": "packages-e2e-test-utils", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/e2e-test-utils/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/e2e-tests", + "slug": "packages-e2e-tests", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/e2e-tests/README.md", + "parent": "packages" + }, { "title": "@wordpress/edit-post", "slug": "packages-edit-post", @@ -737,12 +749,6 @@ "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/shortcode/README.md", "parent": "packages" }, - { - "title": "@wordpress/tests-e2e", - "slug": "packages-tests-e2e", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/tests-e2e/README.md", - "parent": "packages" - }, { "title": "@wordpress/token-list", "slug": "packages-token-list", diff --git a/jsconfig.json b/jsconfig.json index c3f3e3de874e5..51f5ce09b49e0 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -9,7 +9,7 @@ "build", "build-module", "node_modules", - "packages/tests-e2e/plugins", + "packages/e2e-tests/plugins", "vendor" ] } diff --git a/package-lock.json b/package-lock.json index 55c478e4e3279..dc9076c17fc2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2505,6 +2505,27 @@ "@babel/runtime": "^7.0.0" } }, + "@wordpress/e2e-test-utils": { + "version": "file:packages/e2e-test-utils", + "dev": true, + "requires": { + "@wordpress/keycodes": "file:packages/keycodes", + "@wordpress/url": "file:packages/url", + "lodash": "^4.17.10", + "node-fetch": "^1.7.3" + } + }, + "@wordpress/e2e-tests": { + "version": "file:packages/e2e-tests", + "dev": true, + "requires": { + "@wordpress/e2e-test-utils": "file:packages/e2e-test-utils", + "@wordpress/jest-console": "file:packages/jest-console", + "@wordpress/scripts": "file:packages/scripts", + "expect-puppeteer": "^3.2.0", + "lodash": "^4.17.10" + } + }, "@wordpress/edit-post": { "version": "file:packages/edit-post", "requires": { @@ -2787,18 +2808,6 @@ "memize": "^1.0.5" } }, - "@wordpress/tests-e2e": { - "version": "file:packages/tests-e2e", - "dev": true, - "requires": { - "@wordpress/jest-console": "file:packages/jest-console", - "@wordpress/keycodes": "file:packages/keycodes", - "@wordpress/url": "file:packages/url", - "expect-puppeteer": "^3.2.0", - "lodash": "^4.17.10", - "node-fetch": "^1.7.3" - } - }, "@wordpress/token-list": { "version": "file:packages/token-list", "requires": { diff --git a/package.json b/package.json index 012664ba79945..ac8589c049a56 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,8 @@ "@wordpress/babel-preset-default": "file:packages/babel-preset-default", "@wordpress/browserslist-config": "file:packages/browserslist-config", "@wordpress/custom-templated-path-webpack-plugin": "file:packages/custom-templated-path-webpack-plugin", + "@wordpress/e2e-test-utils": "file:packages/e2e-test-utils", + "@wordpress/e2e-tests": "file:packages/e2e-tests", "@wordpress/eslint-plugin": "file:packages/eslint-plugin", "@wordpress/jest-console": "file:packages/jest-console", "@wordpress/jest-preset-default": "file:packages/jest-preset-default", @@ -67,7 +69,6 @@ "@wordpress/npm-package-json-lint-config": "file:packages/npm-package-json-lint-config", "@wordpress/postcss-themes": "file:packages/postcss-themes", "@wordpress/scripts": "file:packages/scripts", - "@wordpress/tests-e2e": "file:packages/tests-e2e", "babel-loader": "8.0.0", "benchmark": "2.1.4", "browserslist": "3.2.8", @@ -176,7 +177,7 @@ "publish:prod": "npm run build:packages && lerna publish", "test": "npm run lint && npm run test-unit", "pretest-e2e": "concurrently \"./bin/reset-e2e-tests.sh\" \"npm run build\"", - "test-e2e": "wp-scripts test-e2e --setupTestFrameworkScriptFile=./packages/tests-e2e/support/setup-test-framework.js --testPathPattern=/packages/tests-e2e/specs/", + "test-e2e": "wp-scripts test-e2e --config packages/e2e-tests/jest.config.js", "test-e2e:watch": "npm run test-e2e -- --watch", "test-php": "npm run lint-php && npm run test-unit-php", "test-unit": "wp-scripts test-unit-js --config test/unit/jest.config.json", diff --git a/packages/tests-e2e/.npmrc b/packages/e2e-test-utils/.npmrc similarity index 100% rename from packages/tests-e2e/.npmrc rename to packages/e2e-test-utils/.npmrc diff --git a/packages/e2e-test-utils/CHANGELOG.md b/packages/e2e-test-utils/CHANGELOG.md new file mode 100644 index 0000000000000..e2467d78c92eb --- /dev/null +++ b/packages/e2e-test-utils/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 (Unreleased) + +- Initial release. diff --git a/packages/e2e-test-utils/README.md b/packages/e2e-test-utils/README.md new file mode 100644 index 0000000000000..bfc0ea26c4fa7 --- /dev/null +++ b/packages/e2e-test-utils/README.md @@ -0,0 +1,13 @@ +# E2E Test Utils + +End-To-End (E2E) test utils for WordPress. + +## Installation + +Install the module + +```bash +npm install @wordpress/e2e-test-utils --save-dev +``` + +<br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/e2e-test-utils/package.json b/packages/e2e-test-utils/package.json new file mode 100644 index 0000000000000..6b8d4c5698774 --- /dev/null +++ b/packages/e2e-test-utils/package.json @@ -0,0 +1,39 @@ +{ + "name": "@wordpress/e2e-test-utils", + "version": "1.0.0-alpha.0", + "description": "End-To-End (E2E) test utils for WordPress.", + "author": "The WordPress Contributors", + "license": "GPL-2.0-or-later", + "keywords": [ + "wordpress", + "e2e", + "utils" + ], + "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/e2e-test-utils/README.md", + "repository": { + "type": "git", + "url": "https://github.com/WordPress/gutenberg.git" + }, + "bugs": { + "url": "https://github.com/WordPress/gutenberg/issues" + }, + "files": [ + "build", + "build-module" + ], + "main": "build/index.js", + "module": "build-module/index.js", + "dependencies": { + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/url": "file:../url", + "lodash": "^4.17.10", + "node-fetch": "^1.7.3" + }, + "peerDependencies": { + "jest": ">=23.0.0", + "puppeteer": ">=1.6.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/tests-e2e/support/utils/activate-plugin.js b/packages/e2e-test-utils/src/activate-plugin.js similarity index 100% rename from packages/tests-e2e/support/utils/activate-plugin.js rename to packages/e2e-test-utils/src/activate-plugin.js diff --git a/packages/tests-e2e/support/utils/are-pre-publish-checks-enabled.js b/packages/e2e-test-utils/src/are-pre-publish-checks-enabled.js similarity index 100% rename from packages/tests-e2e/support/utils/are-pre-publish-checks-enabled.js rename to packages/e2e-test-utils/src/are-pre-publish-checks-enabled.js diff --git a/packages/tests-e2e/support/utils/clear-local-storage.js b/packages/e2e-test-utils/src/clear-local-storage.js similarity index 100% rename from packages/tests-e2e/support/utils/clear-local-storage.js rename to packages/e2e-test-utils/src/clear-local-storage.js diff --git a/packages/tests-e2e/support/utils/click-block-appender.js b/packages/e2e-test-utils/src/click-block-appender.js similarity index 100% rename from packages/tests-e2e/support/utils/click-block-appender.js rename to packages/e2e-test-utils/src/click-block-appender.js diff --git a/packages/tests-e2e/support/utils/click-button.js b/packages/e2e-test-utils/src/click-button.js similarity index 100% rename from packages/tests-e2e/support/utils/click-button.js rename to packages/e2e-test-utils/src/click-button.js diff --git a/packages/tests-e2e/support/utils/click-on-close-modal-button.js b/packages/e2e-test-utils/src/click-on-close-modal-button.js similarity index 100% rename from packages/tests-e2e/support/utils/click-on-close-modal-button.js rename to packages/e2e-test-utils/src/click-on-close-modal-button.js diff --git a/packages/tests-e2e/support/utils/click-on-more-menu-item.js b/packages/e2e-test-utils/src/click-on-more-menu-item.js similarity index 100% rename from packages/tests-e2e/support/utils/click-on-more-menu-item.js rename to packages/e2e-test-utils/src/click-on-more-menu-item.js diff --git a/packages/tests-e2e/support/utils/create-new-post.js b/packages/e2e-test-utils/src/create-new-post.js similarity index 100% rename from packages/tests-e2e/support/utils/create-new-post.js rename to packages/e2e-test-utils/src/create-new-post.js diff --git a/packages/tests-e2e/support/utils/create-url.js b/packages/e2e-test-utils/src/create-url.js similarity index 100% rename from packages/tests-e2e/support/utils/create-url.js rename to packages/e2e-test-utils/src/create-url.js diff --git a/packages/tests-e2e/support/utils/deactivate-plugin.js b/packages/e2e-test-utils/src/deactivate-plugin.js similarity index 100% rename from packages/tests-e2e/support/utils/deactivate-plugin.js rename to packages/e2e-test-utils/src/deactivate-plugin.js diff --git a/packages/tests-e2e/support/utils/disable-pre-publish-checks.js b/packages/e2e-test-utils/src/disable-pre-publish-checks.js similarity index 100% rename from packages/tests-e2e/support/utils/disable-pre-publish-checks.js rename to packages/e2e-test-utils/src/disable-pre-publish-checks.js diff --git a/packages/tests-e2e/support/utils/enable-page-dialog-accept.js b/packages/e2e-test-utils/src/enable-page-dialog-accept.js similarity index 100% rename from packages/tests-e2e/support/utils/enable-page-dialog-accept.js rename to packages/e2e-test-utils/src/enable-page-dialog-accept.js diff --git a/packages/tests-e2e/support/utils/enable-pre-publish-checks.js b/packages/e2e-test-utils/src/enable-pre-publish-checks.js similarity index 100% rename from packages/tests-e2e/support/utils/enable-pre-publish-checks.js rename to packages/e2e-test-utils/src/enable-pre-publish-checks.js diff --git a/packages/tests-e2e/support/utils/ensure-sidebar-opened.js b/packages/e2e-test-utils/src/ensure-sidebar-opened.js similarity index 100% rename from packages/tests-e2e/support/utils/ensure-sidebar-opened.js rename to packages/e2e-test-utils/src/ensure-sidebar-opened.js diff --git a/packages/tests-e2e/support/utils/find-sidebar-panel-with-title.js b/packages/e2e-test-utils/src/find-sidebar-panel-with-title.js similarity index 100% rename from packages/tests-e2e/support/utils/find-sidebar-panel-with-title.js rename to packages/e2e-test-utils/src/find-sidebar-panel-with-title.js diff --git a/packages/tests-e2e/support/utils/get-all-blocks.js b/packages/e2e-test-utils/src/get-all-blocks.js similarity index 100% rename from packages/tests-e2e/support/utils/get-all-blocks.js rename to packages/e2e-test-utils/src/get-all-blocks.js diff --git a/packages/tests-e2e/support/utils/get-available-block-transforms.js b/packages/e2e-test-utils/src/get-available-block-transforms.js similarity index 100% rename from packages/tests-e2e/support/utils/get-available-block-transforms.js rename to packages/e2e-test-utils/src/get-available-block-transforms.js diff --git a/packages/tests-e2e/support/utils/get-edited-post-content.js b/packages/e2e-test-utils/src/get-edited-post-content.js similarity index 100% rename from packages/tests-e2e/support/utils/get-edited-post-content.js rename to packages/e2e-test-utils/src/get-edited-post-content.js diff --git a/packages/tests-e2e/support/utils/has-block-switcher.js b/packages/e2e-test-utils/src/has-block-switcher.js similarity index 100% rename from packages/tests-e2e/support/utils/has-block-switcher.js rename to packages/e2e-test-utils/src/has-block-switcher.js diff --git a/packages/tests-e2e/support/utils/index.js b/packages/e2e-test-utils/src/index.js similarity index 100% rename from packages/tests-e2e/support/utils/index.js rename to packages/e2e-test-utils/src/index.js diff --git a/packages/tests-e2e/support/utils/insert-block.js b/packages/e2e-test-utils/src/insert-block.js similarity index 100% rename from packages/tests-e2e/support/utils/insert-block.js rename to packages/e2e-test-utils/src/insert-block.js diff --git a/packages/tests-e2e/support/utils/install-plugin.js b/packages/e2e-test-utils/src/install-plugin.js similarity index 100% rename from packages/tests-e2e/support/utils/install-plugin.js rename to packages/e2e-test-utils/src/install-plugin.js diff --git a/packages/tests-e2e/support/utils/is-current-url.js b/packages/e2e-test-utils/src/is-current-url.js similarity index 100% rename from packages/tests-e2e/support/utils/is-current-url.js rename to packages/e2e-test-utils/src/is-current-url.js diff --git a/packages/tests-e2e/support/utils/login-user.js b/packages/e2e-test-utils/src/login-user.js similarity index 100% rename from packages/tests-e2e/support/utils/login-user.js rename to packages/e2e-test-utils/src/login-user.js diff --git a/packages/tests-e2e/support/utils/mocks/create-embedding-matcher.js b/packages/e2e-test-utils/src/mocks/create-embedding-matcher.js similarity index 100% rename from packages/tests-e2e/support/utils/mocks/create-embedding-matcher.js rename to packages/e2e-test-utils/src/mocks/create-embedding-matcher.js diff --git a/packages/tests-e2e/support/utils/mocks/create-json-response.js b/packages/e2e-test-utils/src/mocks/create-json-response.js similarity index 100% rename from packages/tests-e2e/support/utils/mocks/create-json-response.js rename to packages/e2e-test-utils/src/mocks/create-json-response.js diff --git a/packages/tests-e2e/support/utils/mocks/create-url-matcher.js b/packages/e2e-test-utils/src/mocks/create-url-matcher.js similarity index 100% rename from packages/tests-e2e/support/utils/mocks/create-url-matcher.js rename to packages/e2e-test-utils/src/mocks/create-url-matcher.js diff --git a/packages/tests-e2e/support/utils/mocks/index.js b/packages/e2e-test-utils/src/mocks/index.js similarity index 100% rename from packages/tests-e2e/support/utils/mocks/index.js rename to packages/e2e-test-utils/src/mocks/index.js diff --git a/packages/tests-e2e/support/utils/mocks/mock-or-transform.js b/packages/e2e-test-utils/src/mocks/mock-or-transform.js similarity index 100% rename from packages/tests-e2e/support/utils/mocks/mock-or-transform.js rename to packages/e2e-test-utils/src/mocks/mock-or-transform.js diff --git a/packages/tests-e2e/support/utils/mocks/set-up-response-mocking.js b/packages/e2e-test-utils/src/mocks/set-up-response-mocking.js similarity index 100% rename from packages/tests-e2e/support/utils/mocks/set-up-response-mocking.js rename to packages/e2e-test-utils/src/mocks/set-up-response-mocking.js diff --git a/packages/tests-e2e/support/utils/observe-focus-loss.js b/packages/e2e-test-utils/src/observe-focus-loss.js similarity index 100% rename from packages/tests-e2e/support/utils/observe-focus-loss.js rename to packages/e2e-test-utils/src/observe-focus-loss.js diff --git a/packages/tests-e2e/support/utils/open-document-settings-sidebar.js b/packages/e2e-test-utils/src/open-document-settings-sidebar.js similarity index 100% rename from packages/tests-e2e/support/utils/open-document-settings-sidebar.js rename to packages/e2e-test-utils/src/open-document-settings-sidebar.js diff --git a/packages/tests-e2e/support/utils/open-publish-panel.js b/packages/e2e-test-utils/src/open-publish-panel.js similarity index 100% rename from packages/tests-e2e/support/utils/open-publish-panel.js rename to packages/e2e-test-utils/src/open-publish-panel.js diff --git a/packages/tests-e2e/support/utils/press-key-times.js b/packages/e2e-test-utils/src/press-key-times.js similarity index 100% rename from packages/tests-e2e/support/utils/press-key-times.js rename to packages/e2e-test-utils/src/press-key-times.js diff --git a/packages/tests-e2e/support/utils/press-key-with-modifier.js b/packages/e2e-test-utils/src/press-key-with-modifier.js similarity index 100% rename from packages/tests-e2e/support/utils/press-key-with-modifier.js rename to packages/e2e-test-utils/src/press-key-with-modifier.js diff --git a/packages/tests-e2e/support/utils/publish-post-with-pre-publish-checks-disabled.js b/packages/e2e-test-utils/src/publish-post-with-pre-publish-checks-disabled.js similarity index 100% rename from packages/tests-e2e/support/utils/publish-post-with-pre-publish-checks-disabled.js rename to packages/e2e-test-utils/src/publish-post-with-pre-publish-checks-disabled.js diff --git a/packages/tests-e2e/support/utils/publish-post.js b/packages/e2e-test-utils/src/publish-post.js similarity index 100% rename from packages/tests-e2e/support/utils/publish-post.js rename to packages/e2e-test-utils/src/publish-post.js diff --git a/packages/tests-e2e/support/utils/save-draft.js b/packages/e2e-test-utils/src/save-draft.js similarity index 100% rename from packages/tests-e2e/support/utils/save-draft.js rename to packages/e2e-test-utils/src/save-draft.js diff --git a/packages/tests-e2e/support/utils/search-for-block.js b/packages/e2e-test-utils/src/search-for-block.js similarity index 100% rename from packages/tests-e2e/support/utils/search-for-block.js rename to packages/e2e-test-utils/src/search-for-block.js diff --git a/packages/tests-e2e/support/utils/select-block-by-client-id.js b/packages/e2e-test-utils/src/select-block-by-client-id.js similarity index 100% rename from packages/tests-e2e/support/utils/select-block-by-client-id.js rename to packages/e2e-test-utils/src/select-block-by-client-id.js diff --git a/packages/tests-e2e/support/utils/set-browser-viewport.js b/packages/e2e-test-utils/src/set-browser-viewport.js similarity index 100% rename from packages/tests-e2e/support/utils/set-browser-viewport.js rename to packages/e2e-test-utils/src/set-browser-viewport.js diff --git a/packages/tests-e2e/support/utils/set-post-content.js b/packages/e2e-test-utils/src/set-post-content.js similarity index 100% rename from packages/tests-e2e/support/utils/set-post-content.js rename to packages/e2e-test-utils/src/set-post-content.js diff --git a/packages/tests-e2e/support/utils/shared/config.js b/packages/e2e-test-utils/src/shared/config.js similarity index 100% rename from packages/tests-e2e/support/utils/shared/config.js rename to packages/e2e-test-utils/src/shared/config.js diff --git a/packages/tests-e2e/support/utils/shared/get-json-response.js b/packages/e2e-test-utils/src/shared/get-json-response.js similarity index 100% rename from packages/tests-e2e/support/utils/shared/get-json-response.js rename to packages/e2e-test-utils/src/shared/get-json-response.js diff --git a/packages/tests-e2e/support/utils/switch-editor-mode-to.js b/packages/e2e-test-utils/src/switch-editor-mode-to.js similarity index 100% rename from packages/tests-e2e/support/utils/switch-editor-mode-to.js rename to packages/e2e-test-utils/src/switch-editor-mode-to.js diff --git a/packages/tests-e2e/support/utils/switch-user-to-admin.js b/packages/e2e-test-utils/src/switch-user-to-admin.js similarity index 100% rename from packages/tests-e2e/support/utils/switch-user-to-admin.js rename to packages/e2e-test-utils/src/switch-user-to-admin.js diff --git a/packages/tests-e2e/support/utils/switch-user-to-test.js b/packages/e2e-test-utils/src/switch-user-to-test.js similarity index 100% rename from packages/tests-e2e/support/utils/switch-user-to-test.js rename to packages/e2e-test-utils/src/switch-user-to-test.js diff --git a/packages/tests-e2e/support/utils/toggle-screen-option.js b/packages/e2e-test-utils/src/toggle-screen-option.js similarity index 100% rename from packages/tests-e2e/support/utils/toggle-screen-option.js rename to packages/e2e-test-utils/src/toggle-screen-option.js diff --git a/packages/tests-e2e/support/utils/transform-block-to.js b/packages/e2e-test-utils/src/transform-block-to.js similarity index 100% rename from packages/tests-e2e/support/utils/transform-block-to.js rename to packages/e2e-test-utils/src/transform-block-to.js diff --git a/packages/tests-e2e/support/utils/uninstall-plugin.js b/packages/e2e-test-utils/src/uninstall-plugin.js similarity index 100% rename from packages/tests-e2e/support/utils/uninstall-plugin.js rename to packages/e2e-test-utils/src/uninstall-plugin.js diff --git a/packages/tests-e2e/support/utils/visit-admin-page.js b/packages/e2e-test-utils/src/visit-admin-page.js similarity index 100% rename from packages/tests-e2e/support/utils/visit-admin-page.js rename to packages/e2e-test-utils/src/visit-admin-page.js diff --git a/packages/tests-e2e/support/utils/wait-for-window-dimensions.js b/packages/e2e-test-utils/src/wait-for-window-dimensions.js similarity index 100% rename from packages/tests-e2e/support/utils/wait-for-window-dimensions.js rename to packages/e2e-test-utils/src/wait-for-window-dimensions.js diff --git a/packages/e2e-tests/.npmrc b/packages/e2e-tests/.npmrc new file mode 100644 index 0000000000000..43c97e719a5a8 --- /dev/null +++ b/packages/e2e-tests/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/packages/e2e-tests/CHANGELOG.md b/packages/e2e-tests/CHANGELOG.md new file mode 100644 index 0000000000000..e2467d78c92eb --- /dev/null +++ b/packages/e2e-tests/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 (Unreleased) + +- Initial release. diff --git a/packages/tests-e2e/README.md b/packages/e2e-tests/README.md similarity index 78% rename from packages/tests-e2e/README.md rename to packages/e2e-tests/README.md index d2d79debc91bb..956f714e76f75 100644 --- a/packages/tests-e2e/README.md +++ b/packages/e2e-tests/README.md @@ -1,4 +1,4 @@ -# Tests E2E +# E2E Tests End-To-End (E2E) tests for WordPress. @@ -7,7 +7,7 @@ End-To-End (E2E) tests for WordPress. Install the module ```bash -npm install @wordpress/tests-e2e --save-dev +npm install @wordpress/e2e-tests --save-dev ``` <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/tests-e2e/assets/10x10_e2e_test_image_z9T8jK.png b/packages/e2e-tests/assets/10x10_e2e_test_image_z9T8jK.png similarity index 100% rename from packages/tests-e2e/assets/10x10_e2e_test_image_z9T8jK.png rename to packages/e2e-tests/assets/10x10_e2e_test_image_z9T8jK.png diff --git a/packages/tests-e2e/assets/greeting-reusable-block.json b/packages/e2e-tests/assets/greeting-reusable-block.json similarity index 100% rename from packages/tests-e2e/assets/greeting-reusable-block.json rename to packages/e2e-tests/assets/greeting-reusable-block.json diff --git a/packages/tests-e2e/support/setup-test-framework.js b/packages/e2e-tests/config/setup-test-framework.js similarity index 98% rename from packages/tests-e2e/support/setup-test-framework.js rename to packages/e2e-tests/config/setup-test-framework.js index ce775d4da2513..20d96e29fb570 100644 --- a/packages/tests-e2e/support/setup-test-framework.js +++ b/packages/e2e-tests/config/setup-test-framework.js @@ -8,16 +8,12 @@ import { get } from 'lodash'; * WordPress dependencies */ import '@wordpress/jest-console'; - -/** - * Internal dependencies - */ import { clearLocalStorage, enablePageDialogAccept, setBrowserViewport, visitAdminPage, -} from './utils'; +} from '@wordpress/e2e-test-utils'; /** * Environment variables diff --git a/packages/e2e-tests/jest.config.js b/packages/e2e-tests/jest.config.js new file mode 100644 index 0000000000000..9984044fd0ccd --- /dev/null +++ b/packages/e2e-tests/jest.config.js @@ -0,0 +1,4 @@ +module.exports = { + ...require( '@wordpress/scripts/config/jest-e2e.config' ), + setupTestFrameworkScriptFile: '<rootDir>/config/setup-test-framework.js', +}; diff --git a/packages/tests-e2e/mu-plugins/disable-login-autofocus.php b/packages/e2e-tests/mu-plugins/disable-login-autofocus.php similarity index 100% rename from packages/tests-e2e/mu-plugins/disable-login-autofocus.php rename to packages/e2e-tests/mu-plugins/disable-login-autofocus.php diff --git a/packages/tests-e2e/package.json b/packages/e2e-tests/package.json similarity index 73% rename from packages/tests-e2e/package.json rename to packages/e2e-tests/package.json index b2cbd88c6685f..889b2fccf093b 100644 --- a/packages/tests-e2e/package.json +++ b/packages/e2e-tests/package.json @@ -1,16 +1,15 @@ { - "name": "@wordpress/tests-e2e", - "private": true, + "name": "@wordpress/e2e-tests", "version": "1.0.0-alpha.0", "description": "End-To-End (E2E) tests for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", "keywords": [ "wordpress", - "tests", - "e2e" + "e2e", + "tests" ], - "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/tests-e2e/README.md", + "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/e2e-tests/README.md", "repository": { "type": "git", "url": "https://github.com/WordPress/gutenberg.git" @@ -19,12 +18,11 @@ "url": "https://github.com/WordPress/gutenberg/issues" }, "dependencies": { + "@wordpress/e2e-test-utils": "file:../e2e-test-utils", "@wordpress/jest-console": "file:../jest-console", - "@wordpress/keycodes": "file:../keycodes", - "@wordpress/url": "file:../url", + "@wordpress/scripts": "file:../scripts", "expect-puppeteer": "^3.2.0", - "lodash": "^4.17.10", - "node-fetch": "^1.7.3" + "lodash": "^4.17.10" }, "peerDependencies": { "jest": ">=23.0.0", diff --git a/packages/tests-e2e/plugins/align-hook.php b/packages/e2e-tests/plugins/align-hook.php similarity index 100% rename from packages/tests-e2e/plugins/align-hook.php rename to packages/e2e-tests/plugins/align-hook.php diff --git a/packages/tests-e2e/plugins/align-hook/index.js b/packages/e2e-tests/plugins/align-hook/index.js similarity index 100% rename from packages/tests-e2e/plugins/align-hook/index.js rename to packages/e2e-tests/plugins/align-hook/index.js diff --git a/packages/tests-e2e/plugins/block-icons.php b/packages/e2e-tests/plugins/block-icons.php similarity index 100% rename from packages/tests-e2e/plugins/block-icons.php rename to packages/e2e-tests/plugins/block-icons.php diff --git a/packages/tests-e2e/plugins/block-icons/index.js b/packages/e2e-tests/plugins/block-icons/index.js similarity index 100% rename from packages/tests-e2e/plugins/block-icons/index.js rename to packages/e2e-tests/plugins/block-icons/index.js diff --git a/packages/tests-e2e/plugins/container-without-paragraph.php b/packages/e2e-tests/plugins/container-without-paragraph.php similarity index 100% rename from packages/tests-e2e/plugins/container-without-paragraph.php rename to packages/e2e-tests/plugins/container-without-paragraph.php diff --git a/packages/tests-e2e/plugins/container-without-paragraph/index.js b/packages/e2e-tests/plugins/container-without-paragraph/index.js similarity index 100% rename from packages/tests-e2e/plugins/container-without-paragraph/index.js rename to packages/e2e-tests/plugins/container-without-paragraph/index.js diff --git a/packages/tests-e2e/plugins/custom-post-types.php b/packages/e2e-tests/plugins/custom-post-types.php similarity index 100% rename from packages/tests-e2e/plugins/custom-post-types.php rename to packages/e2e-tests/plugins/custom-post-types.php diff --git a/packages/tests-e2e/plugins/default-post-content.php b/packages/e2e-tests/plugins/default-post-content.php similarity index 100% rename from packages/tests-e2e/plugins/default-post-content.php rename to packages/e2e-tests/plugins/default-post-content.php diff --git a/packages/tests-e2e/plugins/deprecated-node-matcher.php b/packages/e2e-tests/plugins/deprecated-node-matcher.php similarity index 100% rename from packages/tests-e2e/plugins/deprecated-node-matcher.php rename to packages/e2e-tests/plugins/deprecated-node-matcher.php diff --git a/packages/tests-e2e/plugins/deprecated-node-matcher/index.js b/packages/e2e-tests/plugins/deprecated-node-matcher/index.js similarity index 100% rename from packages/tests-e2e/plugins/deprecated-node-matcher/index.js rename to packages/e2e-tests/plugins/deprecated-node-matcher/index.js diff --git a/packages/tests-e2e/plugins/format-api.php b/packages/e2e-tests/plugins/format-api.php similarity index 100% rename from packages/tests-e2e/plugins/format-api.php rename to packages/e2e-tests/plugins/format-api.php diff --git a/packages/tests-e2e/plugins/format-api/index.js b/packages/e2e-tests/plugins/format-api/index.js similarity index 100% rename from packages/tests-e2e/plugins/format-api/index.js rename to packages/e2e-tests/plugins/format-api/index.js diff --git a/packages/tests-e2e/plugins/hooks-api.php b/packages/e2e-tests/plugins/hooks-api.php similarity index 100% rename from packages/tests-e2e/plugins/hooks-api.php rename to packages/e2e-tests/plugins/hooks-api.php diff --git a/packages/tests-e2e/plugins/hooks-api/index.js b/packages/e2e-tests/plugins/hooks-api/index.js similarity index 100% rename from packages/tests-e2e/plugins/hooks-api/index.js rename to packages/e2e-tests/plugins/hooks-api/index.js diff --git a/packages/tests-e2e/plugins/inner-blocks-templates.php b/packages/e2e-tests/plugins/inner-blocks-templates.php similarity index 100% rename from packages/tests-e2e/plugins/inner-blocks-templates.php rename to packages/e2e-tests/plugins/inner-blocks-templates.php diff --git a/packages/tests-e2e/plugins/inner-blocks-templates/index.js b/packages/e2e-tests/plugins/inner-blocks-templates/index.js similarity index 100% rename from packages/tests-e2e/plugins/inner-blocks-templates/index.js rename to packages/e2e-tests/plugins/inner-blocks-templates/index.js diff --git a/packages/tests-e2e/plugins/meta-attribute-block.php b/packages/e2e-tests/plugins/meta-attribute-block.php similarity index 100% rename from packages/tests-e2e/plugins/meta-attribute-block.php rename to packages/e2e-tests/plugins/meta-attribute-block.php diff --git a/packages/tests-e2e/plugins/meta-attribute-block/index.js b/packages/e2e-tests/plugins/meta-attribute-block/index.js similarity index 100% rename from packages/tests-e2e/plugins/meta-attribute-block/index.js rename to packages/e2e-tests/plugins/meta-attribute-block/index.js diff --git a/packages/tests-e2e/plugins/meta-box.php b/packages/e2e-tests/plugins/meta-box.php similarity index 100% rename from packages/tests-e2e/plugins/meta-box.php rename to packages/e2e-tests/plugins/meta-box.php diff --git a/packages/tests-e2e/plugins/plugins-api.php b/packages/e2e-tests/plugins/plugins-api.php similarity index 100% rename from packages/tests-e2e/plugins/plugins-api.php rename to packages/e2e-tests/plugins/plugins-api.php diff --git a/packages/tests-e2e/plugins/plugins-api/annotations-sidebar.js b/packages/e2e-tests/plugins/plugins-api/annotations-sidebar.js similarity index 100% rename from packages/tests-e2e/plugins/plugins-api/annotations-sidebar.js rename to packages/e2e-tests/plugins/plugins-api/annotations-sidebar.js diff --git a/packages/tests-e2e/plugins/plugins-api/post-status-info.js b/packages/e2e-tests/plugins/plugins-api/post-status-info.js similarity index 100% rename from packages/tests-e2e/plugins/plugins-api/post-status-info.js rename to packages/e2e-tests/plugins/plugins-api/post-status-info.js diff --git a/packages/tests-e2e/plugins/plugins-api/publish-panel.js b/packages/e2e-tests/plugins/plugins-api/publish-panel.js similarity index 100% rename from packages/tests-e2e/plugins/plugins-api/publish-panel.js rename to packages/e2e-tests/plugins/plugins-api/publish-panel.js diff --git a/packages/tests-e2e/plugins/plugins-api/sidebar.js b/packages/e2e-tests/plugins/plugins-api/sidebar.js similarity index 100% rename from packages/tests-e2e/plugins/plugins-api/sidebar.js rename to packages/e2e-tests/plugins/plugins-api/sidebar.js diff --git a/packages/tests-e2e/plugins/post-formats.php b/packages/e2e-tests/plugins/post-formats.php similarity index 100% rename from packages/tests-e2e/plugins/post-formats.php rename to packages/e2e-tests/plugins/post-formats.php diff --git a/packages/tests-e2e/plugins/templates.php b/packages/e2e-tests/plugins/templates.php similarity index 100% rename from packages/tests-e2e/plugins/templates.php rename to packages/e2e-tests/plugins/templates.php diff --git a/packages/tests-e2e/plugins/wp-editor-metabox.php b/packages/e2e-tests/plugins/wp-editor-metabox.php similarity index 100% rename from packages/tests-e2e/plugins/wp-editor-metabox.php rename to packages/e2e-tests/plugins/wp-editor-metabox.php diff --git a/packages/tests-e2e/specs/__snapshots__/adding-blocks.test.js.snap b/packages/e2e-tests/specs/__snapshots__/adding-blocks.test.js.snap similarity index 100% rename from packages/tests-e2e/specs/__snapshots__/adding-blocks.test.js.snap rename to packages/e2e-tests/specs/__snapshots__/adding-blocks.test.js.snap diff --git a/packages/tests-e2e/specs/__snapshots__/block-deletion.test.js.snap b/packages/e2e-tests/specs/__snapshots__/block-deletion.test.js.snap similarity index 100% rename from packages/tests-e2e/specs/__snapshots__/block-deletion.test.js.snap rename to packages/e2e-tests/specs/__snapshots__/block-deletion.test.js.snap diff --git a/packages/tests-e2e/specs/__snapshots__/block-hierarchy-navigation.test.js.snap b/packages/e2e-tests/specs/__snapshots__/block-hierarchy-navigation.test.js.snap similarity index 100% rename from packages/tests-e2e/specs/__snapshots__/block-hierarchy-navigation.test.js.snap rename to packages/e2e-tests/specs/__snapshots__/block-hierarchy-navigation.test.js.snap diff --git a/packages/tests-e2e/specs/__snapshots__/compatibility-classic-editor.test.js.snap b/packages/e2e-tests/specs/__snapshots__/compatibility-classic-editor.test.js.snap similarity index 100% rename from packages/tests-e2e/specs/__snapshots__/compatibility-classic-editor.test.js.snap rename to packages/e2e-tests/specs/__snapshots__/compatibility-classic-editor.test.js.snap diff --git a/packages/tests-e2e/specs/__snapshots__/convert-block-type.test.js.snap b/packages/e2e-tests/specs/__snapshots__/convert-block-type.test.js.snap similarity index 100% rename from packages/tests-e2e/specs/__snapshots__/convert-block-type.test.js.snap rename to packages/e2e-tests/specs/__snapshots__/convert-block-type.test.js.snap diff --git a/packages/tests-e2e/specs/__snapshots__/embedding.test.js.snap b/packages/e2e-tests/specs/__snapshots__/embedding.test.js.snap similarity index 100% rename from packages/tests-e2e/specs/__snapshots__/embedding.test.js.snap rename to packages/e2e-tests/specs/__snapshots__/embedding.test.js.snap diff --git a/packages/tests-e2e/specs/__snapshots__/font-size-picker.test.js.snap b/packages/e2e-tests/specs/__snapshots__/font-size-picker.test.js.snap similarity index 100% rename from packages/tests-e2e/specs/__snapshots__/font-size-picker.test.js.snap rename to packages/e2e-tests/specs/__snapshots__/font-size-picker.test.js.snap diff --git a/packages/tests-e2e/specs/__snapshots__/links.test.js.snap b/packages/e2e-tests/specs/__snapshots__/links.test.js.snap similarity index 100% rename from packages/tests-e2e/specs/__snapshots__/links.test.js.snap rename to packages/e2e-tests/specs/__snapshots__/links.test.js.snap diff --git a/packages/tests-e2e/specs/__snapshots__/mentions.test.js.snap b/packages/e2e-tests/specs/__snapshots__/mentions.test.js.snap similarity index 100% rename from packages/tests-e2e/specs/__snapshots__/mentions.test.js.snap rename to packages/e2e-tests/specs/__snapshots__/mentions.test.js.snap diff --git a/packages/tests-e2e/specs/__snapshots__/reusable-blocks.test.js.snap b/packages/e2e-tests/specs/__snapshots__/reusable-blocks.test.js.snap similarity index 100% rename from packages/tests-e2e/specs/__snapshots__/reusable-blocks.test.js.snap rename to packages/e2e-tests/specs/__snapshots__/reusable-blocks.test.js.snap diff --git a/packages/tests-e2e/specs/__snapshots__/rich-text.test.js.snap b/packages/e2e-tests/specs/__snapshots__/rich-text.test.js.snap similarity index 100% rename from packages/tests-e2e/specs/__snapshots__/rich-text.test.js.snap rename to packages/e2e-tests/specs/__snapshots__/rich-text.test.js.snap diff --git a/packages/tests-e2e/specs/__snapshots__/splitting-merging.test.js.snap b/packages/e2e-tests/specs/__snapshots__/splitting-merging.test.js.snap similarity index 100% rename from packages/tests-e2e/specs/__snapshots__/splitting-merging.test.js.snap rename to packages/e2e-tests/specs/__snapshots__/splitting-merging.test.js.snap diff --git a/packages/tests-e2e/specs/__snapshots__/style-variation.test.js.snap b/packages/e2e-tests/specs/__snapshots__/style-variation.test.js.snap similarity index 100% rename from packages/tests-e2e/specs/__snapshots__/style-variation.test.js.snap rename to packages/e2e-tests/specs/__snapshots__/style-variation.test.js.snap diff --git a/packages/tests-e2e/specs/__snapshots__/undo.test.js.snap b/packages/e2e-tests/specs/__snapshots__/undo.test.js.snap similarity index 100% rename from packages/tests-e2e/specs/__snapshots__/undo.test.js.snap rename to packages/e2e-tests/specs/__snapshots__/undo.test.js.snap diff --git a/packages/tests-e2e/specs/__snapshots__/writing-flow.test.js.snap b/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap similarity index 100% rename from packages/tests-e2e/specs/__snapshots__/writing-flow.test.js.snap rename to packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap diff --git a/packages/tests-e2e/specs/a11y.test.js b/packages/e2e-tests/specs/a11y.test.js similarity index 96% rename from packages/tests-e2e/specs/a11y.test.js rename to packages/e2e-tests/specs/a11y.test.js index 93745d38116c2..1c87cea4af54f 100644 --- a/packages/tests-e2e/specs/a11y.test.js +++ b/packages/e2e-tests/specs/a11y.test.js @@ -1,10 +1,10 @@ /** - * Internal dependencies + * WordPress dependencies */ import { createNewPost, pressKeyWithModifier, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; function isCloseButtonFocused() { return page.$eval( ':focus', ( focusedElement ) => { diff --git a/packages/tests-e2e/specs/adding-blocks.test.js b/packages/e2e-tests/specs/adding-blocks.test.js similarity index 98% rename from packages/tests-e2e/specs/adding-blocks.test.js rename to packages/e2e-tests/specs/adding-blocks.test.js index a9688ff154deb..01ad864bda188 100644 --- a/packages/tests-e2e/specs/adding-blocks.test.js +++ b/packages/e2e-tests/specs/adding-blocks.test.js @@ -1,12 +1,12 @@ /** - * Internal dependencies + * WordPress dependencies */ import { createNewPost, insertBlock, getEditedPostContent, pressKeyTimes, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'adding blocks', () => { beforeEach( async () => { diff --git a/packages/tests-e2e/specs/adding-inline-tokens.test.js b/packages/e2e-tests/specs/adding-inline-tokens.test.js similarity index 96% rename from packages/tests-e2e/specs/adding-inline-tokens.test.js rename to packages/e2e-tests/specs/adding-inline-tokens.test.js index 14f504d1db471..ee2572e1556b7 100644 --- a/packages/tests-e2e/specs/adding-inline-tokens.test.js +++ b/packages/e2e-tests/specs/adding-inline-tokens.test.js @@ -7,14 +7,14 @@ import os from 'os'; import uuid from 'uuid/v4'; /** - * Internal dependencies + * WordPress dependencies */ import { clickBlockAppender, getEditedPostContent, insertBlock, createNewPost, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'adding inline tokens', () => { beforeAll( async () => { diff --git a/packages/tests-e2e/specs/block-deletion.test.js b/packages/e2e-tests/specs/block-deletion.test.js similarity index 98% rename from packages/tests-e2e/specs/block-deletion.test.js rename to packages/e2e-tests/specs/block-deletion.test.js index 76e9d94d2616d..b08eacd1f6d62 100644 --- a/packages/tests-e2e/specs/block-deletion.test.js +++ b/packages/e2e-tests/specs/block-deletion.test.js @@ -1,12 +1,12 @@ /** - * Internal dependencies + * WordPress dependencies */ import { clickBlockAppender, getEditedPostContent, createNewPost, pressKeyWithModifier, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; const addThreeParagraphsToNewPost = async () => { await createNewPost(); diff --git a/packages/tests-e2e/specs/block-hierarchy-navigation.test.js b/packages/e2e-tests/specs/block-hierarchy-navigation.test.js similarity index 98% rename from packages/tests-e2e/specs/block-hierarchy-navigation.test.js rename to packages/e2e-tests/specs/block-hierarchy-navigation.test.js index 377e6b963c922..e93a3dfb21854 100644 --- a/packages/tests-e2e/specs/block-hierarchy-navigation.test.js +++ b/packages/e2e-tests/specs/block-hierarchy-navigation.test.js @@ -1,5 +1,5 @@ /** - * Internal dependencies + * WordPress dependencies */ import { createNewPost, @@ -7,7 +7,7 @@ import { getEditedPostContent, pressKeyTimes, pressKeyWithModifier, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; async function openBlockNavigator() { return pressKeyWithModifier( 'access', 'o' ); diff --git a/packages/tests-e2e/specs/block-mover.test.js b/packages/e2e-tests/specs/block-mover.test.js similarity index 93% rename from packages/tests-e2e/specs/block-mover.test.js rename to packages/e2e-tests/specs/block-mover.test.js index 05a204f5afd74..b9011ee7d0101 100644 --- a/packages/tests-e2e/specs/block-mover.test.js +++ b/packages/e2e-tests/specs/block-mover.test.js @@ -1,7 +1,7 @@ /** - * Internal dependencies + * WordPress dependencies */ -import { createNewPost } from '../support/utils'; +import { createNewPost } from '@wordpress/e2e-test-utils'; describe( 'block mover', () => { beforeEach( async () => { diff --git a/packages/tests-e2e/specs/block-switcher.test.js b/packages/e2e-tests/specs/block-switcher.test.js similarity index 97% rename from packages/tests-e2e/specs/block-switcher.test.js rename to packages/e2e-tests/specs/block-switcher.test.js index 50cd91db7358d..0d2fcf2adfe9e 100644 --- a/packages/tests-e2e/specs/block-switcher.test.js +++ b/packages/e2e-tests/specs/block-switcher.test.js @@ -1,5 +1,5 @@ /** - * Internal dependencies + * WordPress dependencies */ import { hasBlockSwitcher, @@ -7,7 +7,7 @@ import { createNewPost, insertBlock, pressKeyWithModifier, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'adding blocks', () => { beforeEach( async () => { diff --git a/packages/tests-e2e/specs/blocks/__snapshots__/classic.test.js.snap b/packages/e2e-tests/specs/blocks/__snapshots__/classic.test.js.snap similarity index 100% rename from packages/tests-e2e/specs/blocks/__snapshots__/classic.test.js.snap rename to packages/e2e-tests/specs/blocks/__snapshots__/classic.test.js.snap diff --git a/packages/tests-e2e/specs/blocks/__snapshots__/code.test.js.snap b/packages/e2e-tests/specs/blocks/__snapshots__/code.test.js.snap similarity index 100% rename from packages/tests-e2e/specs/blocks/__snapshots__/code.test.js.snap rename to packages/e2e-tests/specs/blocks/__snapshots__/code.test.js.snap diff --git a/packages/tests-e2e/specs/blocks/__snapshots__/heading.test.js.snap b/packages/e2e-tests/specs/blocks/__snapshots__/heading.test.js.snap similarity index 100% rename from packages/tests-e2e/specs/blocks/__snapshots__/heading.test.js.snap rename to packages/e2e-tests/specs/blocks/__snapshots__/heading.test.js.snap diff --git a/packages/tests-e2e/specs/blocks/__snapshots__/html.test.js.snap b/packages/e2e-tests/specs/blocks/__snapshots__/html.test.js.snap similarity index 100% rename from packages/tests-e2e/specs/blocks/__snapshots__/html.test.js.snap rename to packages/e2e-tests/specs/blocks/__snapshots__/html.test.js.snap diff --git a/packages/tests-e2e/specs/blocks/__snapshots__/list.test.js.snap b/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap similarity index 100% rename from packages/tests-e2e/specs/blocks/__snapshots__/list.test.js.snap rename to packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap diff --git a/packages/tests-e2e/specs/blocks/__snapshots__/quote.test.js.snap b/packages/e2e-tests/specs/blocks/__snapshots__/quote.test.js.snap similarity index 100% rename from packages/tests-e2e/specs/blocks/__snapshots__/quote.test.js.snap rename to packages/e2e-tests/specs/blocks/__snapshots__/quote.test.js.snap diff --git a/packages/tests-e2e/specs/blocks/__snapshots__/separator.test.js.snap b/packages/e2e-tests/specs/blocks/__snapshots__/separator.test.js.snap similarity index 100% rename from packages/tests-e2e/specs/blocks/__snapshots__/separator.test.js.snap rename to packages/e2e-tests/specs/blocks/__snapshots__/separator.test.js.snap diff --git a/packages/tests-e2e/specs/blocks/classic.test.js b/packages/e2e-tests/specs/blocks/classic.test.js similarity index 97% rename from packages/tests-e2e/specs/blocks/classic.test.js rename to packages/e2e-tests/specs/blocks/classic.test.js index 9c5b26de26773..2df2d1f2c8982 100644 --- a/packages/tests-e2e/specs/blocks/classic.test.js +++ b/packages/e2e-tests/specs/blocks/classic.test.js @@ -7,14 +7,14 @@ import os from 'os'; import uuid from 'uuid/v4'; /** - * Internal dependencies + * WordPress dependencies */ import { getEditedPostContent, createNewPost, insertBlock, pressKeyWithModifier, -} from '../../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'Classic', () => { beforeEach( async () => { diff --git a/packages/tests-e2e/specs/blocks/code.test.js b/packages/e2e-tests/specs/blocks/code.test.js similarity index 87% rename from packages/tests-e2e/specs/blocks/code.test.js rename to packages/e2e-tests/specs/blocks/code.test.js index f3d9c8e1c4fc7..018ce71f9794d 100644 --- a/packages/tests-e2e/specs/blocks/code.test.js +++ b/packages/e2e-tests/specs/blocks/code.test.js @@ -1,11 +1,11 @@ /** - * Internal dependencies + * WordPress dependencies */ import { clickBlockAppender, getEditedPostContent, createNewPost, -} from '../../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'Code', () => { beforeEach( async () => { diff --git a/packages/tests-e2e/specs/blocks/heading.test.js b/packages/e2e-tests/specs/blocks/heading.test.js similarity index 91% rename from packages/tests-e2e/specs/blocks/heading.test.js rename to packages/e2e-tests/specs/blocks/heading.test.js index 284161ec76c40..a31a77be3adb7 100644 --- a/packages/tests-e2e/specs/blocks/heading.test.js +++ b/packages/e2e-tests/specs/blocks/heading.test.js @@ -1,11 +1,11 @@ /** - * Internal dependencies + * WordPress dependencies */ import { clickBlockAppender, getEditedPostContent, createNewPost, -} from '../../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'Separator', () => { beforeEach( async () => { diff --git a/packages/tests-e2e/specs/blocks/html.test.js b/packages/e2e-tests/specs/blocks/html.test.js similarity index 91% rename from packages/tests-e2e/specs/blocks/html.test.js rename to packages/e2e-tests/specs/blocks/html.test.js index 2026a2fb1ae92..69370b4f62208 100644 --- a/packages/tests-e2e/specs/blocks/html.test.js +++ b/packages/e2e-tests/specs/blocks/html.test.js @@ -1,11 +1,11 @@ /** - * Internal dependencies + * WordPress dependencies */ import { clickBlockAppender, getEditedPostContent, createNewPost, -} from '../../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'HTML block', () => { beforeEach( async () => { diff --git a/packages/tests-e2e/specs/blocks/list.test.js b/packages/e2e-tests/specs/blocks/list.test.js similarity index 99% rename from packages/tests-e2e/specs/blocks/list.test.js rename to packages/e2e-tests/specs/blocks/list.test.js index 27b313d44081d..1401698c43a11 100644 --- a/packages/tests-e2e/specs/blocks/list.test.js +++ b/packages/e2e-tests/specs/blocks/list.test.js @@ -1,5 +1,5 @@ /** - * Internal dependencies + * WordPress dependencies */ import { clickBlockAppender, @@ -9,7 +9,7 @@ import { transformBlockTo, pressKeyWithModifier, insertBlock, -} from '../../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'List', () => { beforeEach( async () => { diff --git a/packages/tests-e2e/specs/blocks/quote.test.js b/packages/e2e-tests/specs/blocks/quote.test.js similarity index 98% rename from packages/tests-e2e/specs/blocks/quote.test.js rename to packages/e2e-tests/specs/blocks/quote.test.js index a5168f8cb743c..11f1fd5e08638 100644 --- a/packages/tests-e2e/specs/blocks/quote.test.js +++ b/packages/e2e-tests/specs/blocks/quote.test.js @@ -1,5 +1,5 @@ /** - * Internal dependencies + * WordPress dependencies */ import { clickBlockAppender, @@ -8,7 +8,7 @@ import { pressKeyTimes, transformBlockTo, insertBlock, -} from '../../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'Quote', () => { beforeEach( async () => { diff --git a/packages/tests-e2e/specs/blocks/separator.test.js b/packages/e2e-tests/specs/blocks/separator.test.js similarity index 86% rename from packages/tests-e2e/specs/blocks/separator.test.js rename to packages/e2e-tests/specs/blocks/separator.test.js index 1b3883b51b92e..1b24a7ec35716 100644 --- a/packages/tests-e2e/specs/blocks/separator.test.js +++ b/packages/e2e-tests/specs/blocks/separator.test.js @@ -1,11 +1,11 @@ /** - * Internal dependencies + * WordPress dependencies */ import { clickBlockAppender, getEditedPostContent, createNewPost, -} from '../../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'Separator', () => { beforeEach( async () => { diff --git a/packages/tests-e2e/specs/change-detection.test.js b/packages/e2e-tests/specs/change-detection.test.js similarity index 99% rename from packages/tests-e2e/specs/change-detection.test.js rename to packages/e2e-tests/specs/change-detection.test.js index 857e2b166c3ed..b8f2377ce2c91 100644 --- a/packages/tests-e2e/specs/change-detection.test.js +++ b/packages/e2e-tests/specs/change-detection.test.js @@ -1,5 +1,5 @@ /** - * Internal dependencies + * WordPress dependencies */ import { clickBlockAppender, @@ -7,7 +7,7 @@ import { pressKeyWithModifier, ensureSidebarOpened, publishPost, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'Change detection', () => { let handleInterceptedRequest, hadInterceptedSave; diff --git a/packages/tests-e2e/specs/compatibility-classic-editor.test.js b/packages/e2e-tests/specs/compatibility-classic-editor.test.js similarity index 88% rename from packages/tests-e2e/specs/compatibility-classic-editor.test.js rename to packages/e2e-tests/specs/compatibility-classic-editor.test.js index a3a062731c316..6134361af73fe 100644 --- a/packages/tests-e2e/specs/compatibility-classic-editor.test.js +++ b/packages/e2e-tests/specs/compatibility-classic-editor.test.js @@ -1,7 +1,7 @@ /** - * Internal dependencies + * WordPress dependencies */ -import { createNewPost, insertBlock, publishPost } from '../support/utils'; +import { createNewPost, insertBlock, publishPost } from '@wordpress/e2e-test-utils'; describe( 'Compatibility with Classic Editor', () => { beforeEach( async () => { diff --git a/packages/tests-e2e/specs/convert-block-type.test.js b/packages/e2e-tests/specs/convert-block-type.test.js similarity index 92% rename from packages/tests-e2e/specs/convert-block-type.test.js rename to packages/e2e-tests/specs/convert-block-type.test.js index 0d32512b4c54f..b99c8541d632f 100644 --- a/packages/tests-e2e/specs/convert-block-type.test.js +++ b/packages/e2e-tests/specs/convert-block-type.test.js @@ -1,12 +1,12 @@ /** - * Internal dependencies + * WordPress dependencies */ import { getEditedPostContent, createNewPost, insertBlock, transformBlockTo, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'Code block', () => { beforeEach( async () => { diff --git a/packages/tests-e2e/specs/datepicker.test.js b/packages/e2e-tests/specs/datepicker.test.js similarity index 95% rename from packages/tests-e2e/specs/datepicker.test.js rename to packages/e2e-tests/specs/datepicker.test.js index 7a12f4b82c230..e3ce2540d39f2 100644 --- a/packages/tests-e2e/specs/datepicker.test.js +++ b/packages/e2e-tests/specs/datepicker.test.js @@ -1,7 +1,7 @@ /** - * Internal dependencies + * WordPress dependencies */ -import { createNewPost } from '../support/utils'; +import { createNewPost } from '@wordpress/e2e-test-utils'; describe( 'Datepicker', () => { beforeEach( async () => { diff --git a/packages/tests-e2e/specs/demo.test.js b/packages/e2e-tests/specs/demo.test.js similarity index 94% rename from packages/tests-e2e/specs/demo.test.js rename to packages/e2e-tests/specs/demo.test.js index 30b4b3f887d95..80d3fbfeb2939 100644 --- a/packages/tests-e2e/specs/demo.test.js +++ b/packages/e2e-tests/specs/demo.test.js @@ -1,7 +1,7 @@ /** - * Internal dependencies + * WordPress dependencies */ -import { visitAdminPage, createURLMatcher, setUpResponseMocking, mockOrTransform } from '../support/utils'; +import { visitAdminPage, createURLMatcher, setUpResponseMocking, mockOrTransform } from '@wordpress/e2e-test-utils'; const MOCK_VIMEO_RESPONSE = { url: 'https://vimeo.com/22439234', diff --git a/packages/tests-e2e/specs/editor-modes.test.js b/packages/e2e-tests/specs/editor-modes.test.js similarity index 98% rename from packages/tests-e2e/specs/editor-modes.test.js rename to packages/e2e-tests/specs/editor-modes.test.js index e8aee11aaa1c8..a8a3d86a31c56 100644 --- a/packages/tests-e2e/specs/editor-modes.test.js +++ b/packages/e2e-tests/specs/editor-modes.test.js @@ -1,7 +1,7 @@ /** - * Internal dependencies + * WordPress dependencies */ -import { clickBlockAppender, createNewPost, switchEditorModeTo } from '../support/utils'; +import { clickBlockAppender, createNewPost, switchEditorModeTo } from '@wordpress/e2e-test-utils'; describe( 'Editing modes (visual/HTML)', () => { beforeEach( async () => { diff --git a/packages/tests-e2e/specs/embedding.test.js b/packages/e2e-tests/specs/embedding.test.js similarity index 99% rename from packages/tests-e2e/specs/embedding.test.js rename to packages/e2e-tests/specs/embedding.test.js index 46d7bd4986d82..3ea51358b14b2 100644 --- a/packages/tests-e2e/specs/embedding.test.js +++ b/packages/e2e-tests/specs/embedding.test.js @@ -1,5 +1,5 @@ /** - * Internal dependencies + * WordPress dependencies */ import { clickBlockAppender, @@ -9,7 +9,7 @@ import { createJSONResponse, getEditedPostContent, clickButton, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; const MOCK_EMBED_WORDPRESS_SUCCESS_RESPONSE = { url: 'https://wordpress.org/gutenberg/handbook/block-api/attributes/', diff --git a/packages/tests-e2e/specs/font-size-picker.test.js b/packages/e2e-tests/specs/font-size-picker.test.js similarity index 98% rename from packages/tests-e2e/specs/font-size-picker.test.js rename to packages/e2e-tests/specs/font-size-picker.test.js index 78b78068b764b..648a6d4739833 100644 --- a/packages/tests-e2e/specs/font-size-picker.test.js +++ b/packages/e2e-tests/specs/font-size-picker.test.js @@ -1,12 +1,12 @@ /** - * Internal dependencies + * WordPress dependencies */ import { clickBlockAppender, getEditedPostContent, createNewPost, pressKeyTimes, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'Font Size Picker', () => { beforeEach( async () => { diff --git a/packages/tests-e2e/specs/fullscreen-mode.test.js b/packages/e2e-tests/specs/fullscreen-mode.test.js similarity index 90% rename from packages/tests-e2e/specs/fullscreen-mode.test.js rename to packages/e2e-tests/specs/fullscreen-mode.test.js index a960a4a1dd27d..ef70e1948d641 100644 --- a/packages/tests-e2e/specs/fullscreen-mode.test.js +++ b/packages/e2e-tests/specs/fullscreen-mode.test.js @@ -1,10 +1,10 @@ /** - * Internal dependencies + * WordPress dependencies */ import { createNewPost, clickOnMoreMenuItem, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'Fullscreen Mode', () => { beforeAll( async () => { diff --git a/packages/tests-e2e/specs/invalid-block.test.js b/packages/e2e-tests/specs/invalid-block.test.js similarity index 95% rename from packages/tests-e2e/specs/invalid-block.test.js rename to packages/e2e-tests/specs/invalid-block.test.js index c06d1f8752efd..a068781ef42ac 100644 --- a/packages/tests-e2e/specs/invalid-block.test.js +++ b/packages/e2e-tests/specs/invalid-block.test.js @@ -1,10 +1,10 @@ /** - * Internal dependencies + * WordPress dependencies */ import { createNewPost, clickBlockAppender, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'invalid blocks', () => { beforeEach( async () => { diff --git a/packages/tests-e2e/specs/links.test.js b/packages/e2e-tests/specs/links.test.js similarity index 99% rename from packages/tests-e2e/specs/links.test.js rename to packages/e2e-tests/specs/links.test.js index aa439447fcf5a..85ba99badcf24 100644 --- a/packages/tests-e2e/specs/links.test.js +++ b/packages/e2e-tests/specs/links.test.js @@ -1,5 +1,5 @@ /** - * Internal dependencies + * WordPress dependencies */ import { clickBlockAppender, @@ -8,7 +8,7 @@ import { pressKeyWithModifier, pressKeyTimes, insertBlock, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; /** * The modifier keys needed to invoke a 'select the next word' keyboard shortcut. diff --git a/packages/tests-e2e/specs/manage-reusable-blocks.test.js b/packages/e2e-tests/specs/manage-reusable-blocks.test.js similarity index 93% rename from packages/tests-e2e/specs/manage-reusable-blocks.test.js rename to packages/e2e-tests/specs/manage-reusable-blocks.test.js index acd97baa175ed..2eaa22e219f25 100644 --- a/packages/tests-e2e/specs/manage-reusable-blocks.test.js +++ b/packages/e2e-tests/specs/manage-reusable-blocks.test.js @@ -4,9 +4,9 @@ import path from 'path'; /** - * Internal dependencies + * WordPress dependencies */ -import { visitAdminPage } from '../support/utils'; +import { visitAdminPage } from '@wordpress/e2e-test-utils'; describe( 'Managing reusable blocks', () => { beforeAll( async () => { diff --git a/packages/tests-e2e/specs/mentions.test.js b/packages/e2e-tests/specs/mentions.test.js similarity index 89% rename from packages/tests-e2e/specs/mentions.test.js rename to packages/e2e-tests/specs/mentions.test.js index a289a8491b298..2d2f220bc4740 100644 --- a/packages/tests-e2e/specs/mentions.test.js +++ b/packages/e2e-tests/specs/mentions.test.js @@ -1,11 +1,11 @@ /** - * Internal dependencies + * WordPress dependencies */ import { createNewPost, getEditedPostContent, clickBlockAppender, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'autocomplete mentions', () => { beforeAll( async () => { diff --git a/packages/tests-e2e/specs/multi-block-selection.test.js b/packages/e2e-tests/specs/multi-block-selection.test.js similarity index 98% rename from packages/tests-e2e/specs/multi-block-selection.test.js rename to packages/e2e-tests/specs/multi-block-selection.test.js index b8d6c4fa2de7e..3f1fb30370ef6 100644 --- a/packages/tests-e2e/specs/multi-block-selection.test.js +++ b/packages/e2e-tests/specs/multi-block-selection.test.js @@ -1,12 +1,12 @@ /** - * Internal dependencies + * WordPress dependencies */ import { clickBlockAppender, insertBlock, createNewPost, pressKeyWithModifier, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'Multi-block selection', () => { beforeEach( async () => { diff --git a/packages/tests-e2e/specs/navigable-toolbar.test.js b/packages/e2e-tests/specs/navigable-toolbar.test.js similarity index 93% rename from packages/tests-e2e/specs/navigable-toolbar.test.js rename to packages/e2e-tests/specs/navigable-toolbar.test.js index e3e58aa059928..e14a45e122442 100644 --- a/packages/tests-e2e/specs/navigable-toolbar.test.js +++ b/packages/e2e-tests/specs/navigable-toolbar.test.js @@ -4,9 +4,9 @@ import { forEach } from 'lodash'; /** - * Internal dependencies + * WordPress dependencies */ -import { createNewPost, pressKeyWithModifier } from '../support/utils'; +import { createNewPost, pressKeyWithModifier } from '@wordpress/e2e-test-utils'; describe( 'block toolbar', () => { forEach( { diff --git a/packages/tests-e2e/specs/new-post-default-content.test.js b/packages/e2e-tests/specs/new-post-default-content.test.js similarity index 95% rename from packages/tests-e2e/specs/new-post-default-content.test.js rename to packages/e2e-tests/specs/new-post-default-content.test.js index df5334319e307..7460b2aaeac31 100644 --- a/packages/tests-e2e/specs/new-post-default-content.test.js +++ b/packages/e2e-tests/specs/new-post-default-content.test.js @@ -1,5 +1,5 @@ /** - * Internal dependencies + * WordPress dependencies */ import { activatePlugin, @@ -8,7 +8,7 @@ import { findSidebarPanelWithTitle, getEditedPostContent, openDocumentSettingsSidebar, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'new editor filtered state', () => { beforeAll( async () => { diff --git a/packages/tests-e2e/specs/new-post.test.js b/packages/e2e-tests/specs/new-post.test.js similarity index 97% rename from packages/tests-e2e/specs/new-post.test.js rename to packages/e2e-tests/specs/new-post.test.js index c8dc83b6a2535..c4cb3fa082dca 100644 --- a/packages/tests-e2e/specs/new-post.test.js +++ b/packages/e2e-tests/specs/new-post.test.js @@ -1,11 +1,11 @@ /** - * Internal dependencies + * WordPress dependencies */ import { activatePlugin, createNewPost, deactivatePlugin, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'new editor state', () => { beforeAll( async () => { diff --git a/packages/tests-e2e/specs/nux.test.js b/packages/e2e-tests/specs/nux.test.js similarity index 99% rename from packages/tests-e2e/specs/nux.test.js rename to packages/e2e-tests/specs/nux.test.js index 23fad060527ba..d8a64be089eca 100644 --- a/packages/tests-e2e/specs/nux.test.js +++ b/packages/e2e-tests/specs/nux.test.js @@ -1,5 +1,5 @@ /** - * Internal dependencies + * WordPress dependencies */ import { clickBlockAppender, @@ -7,7 +7,7 @@ import { createNewPost, saveDraft, toggleScreenOption, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'New User Experience (NUX)', () => { async function clickAllTips( page ) { diff --git a/packages/tests-e2e/specs/__snapshots__/align-hook.test.js.snap b/packages/e2e-tests/specs/plugins/__snapshots__/align-hook.test.js.snap similarity index 100% rename from packages/tests-e2e/specs/__snapshots__/align-hook.test.js.snap rename to packages/e2e-tests/specs/plugins/__snapshots__/align-hook.test.js.snap diff --git a/packages/tests-e2e/specs/__snapshots__/container-blocks.test.js.snap b/packages/e2e-tests/specs/plugins/__snapshots__/container-blocks.test.js.snap similarity index 100% rename from packages/tests-e2e/specs/__snapshots__/container-blocks.test.js.snap rename to packages/e2e-tests/specs/plugins/__snapshots__/container-blocks.test.js.snap diff --git a/packages/tests-e2e/specs/__snapshots__/deprecated-node-matcher.test.js.snap b/packages/e2e-tests/specs/plugins/__snapshots__/deprecated-node-matcher.test.js.snap similarity index 100% rename from packages/tests-e2e/specs/__snapshots__/deprecated-node-matcher.test.js.snap rename to packages/e2e-tests/specs/plugins/__snapshots__/deprecated-node-matcher.test.js.snap diff --git a/packages/tests-e2e/specs/__snapshots__/format-api.test.js.snap b/packages/e2e-tests/specs/plugins/__snapshots__/format-api.test.js.snap similarity index 100% rename from packages/tests-e2e/specs/__snapshots__/format-api.test.js.snap rename to packages/e2e-tests/specs/plugins/__snapshots__/format-api.test.js.snap diff --git a/packages/tests-e2e/specs/__snapshots__/meta-attribute-block.test.js.snap b/packages/e2e-tests/specs/plugins/__snapshots__/meta-attribute-block.test.js.snap similarity index 100% rename from packages/tests-e2e/specs/__snapshots__/meta-attribute-block.test.js.snap rename to packages/e2e-tests/specs/plugins/__snapshots__/meta-attribute-block.test.js.snap diff --git a/packages/tests-e2e/specs/__snapshots__/plugins-api.test.js.snap b/packages/e2e-tests/specs/plugins/__snapshots__/plugins-api.test.js.snap similarity index 100% rename from packages/tests-e2e/specs/__snapshots__/plugins-api.test.js.snap rename to packages/e2e-tests/specs/plugins/__snapshots__/plugins-api.test.js.snap diff --git a/packages/tests-e2e/specs/__snapshots__/templates.test.js.snap b/packages/e2e-tests/specs/plugins/__snapshots__/templates.test.js.snap similarity index 100% rename from packages/tests-e2e/specs/__snapshots__/templates.test.js.snap rename to packages/e2e-tests/specs/plugins/__snapshots__/templates.test.js.snap diff --git a/packages/tests-e2e/specs/__snapshots__/wp-editor-meta-box.test.js.snap b/packages/e2e-tests/specs/plugins/__snapshots__/wp-editor-meta-box.test.js.snap similarity index 100% rename from packages/tests-e2e/specs/__snapshots__/wp-editor-meta-box.test.js.snap rename to packages/e2e-tests/specs/plugins/__snapshots__/wp-editor-meta-box.test.js.snap diff --git a/packages/tests-e2e/specs/align-hook.test.js b/packages/e2e-tests/specs/plugins/align-hook.test.js similarity index 98% rename from packages/tests-e2e/specs/align-hook.test.js rename to packages/e2e-tests/specs/plugins/align-hook.test.js index d22725cdc62e5..1fbe802f84142 100644 --- a/packages/tests-e2e/specs/align-hook.test.js +++ b/packages/e2e-tests/specs/plugins/align-hook.test.js @@ -1,5 +1,5 @@ /** - * Internal dependencies + * WordPress dependencies */ import { activatePlugin, @@ -10,7 +10,7 @@ import { insertBlock, selectBlockByClientId, setPostContent, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'Align Hook Works As Expected', () => { beforeAll( async () => { diff --git a/packages/tests-e2e/specs/annotations.test.js b/packages/e2e-tests/specs/plugins/annotations.test.js similarity index 98% rename from packages/tests-e2e/specs/annotations.test.js rename to packages/e2e-tests/specs/plugins/annotations.test.js index 3cdd39877afb1..5fe99a352eec6 100644 --- a/packages/tests-e2e/specs/annotations.test.js +++ b/packages/e2e-tests/specs/plugins/annotations.test.js @@ -1,12 +1,12 @@ /** - * Internal dependencies + * WordPress dependencies */ import { activatePlugin, clickOnMoreMenuItem, createNewPost, deactivatePlugin, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; const clickOnBlockSettingsMenuItem = async ( buttonLabel ) => { await expect( page ).toClick( '.editor-block-settings-menu__toggle' ); diff --git a/packages/tests-e2e/specs/block-icons.test.js b/packages/e2e-tests/specs/plugins/block-icons.test.js similarity index 98% rename from packages/tests-e2e/specs/block-icons.test.js rename to packages/e2e-tests/specs/plugins/block-icons.test.js index c523bb156ea80..0e7a15f69580d 100644 --- a/packages/tests-e2e/specs/block-icons.test.js +++ b/packages/e2e-tests/specs/plugins/block-icons.test.js @@ -1,5 +1,5 @@ /** - * Internal dependencies + * WordPress dependencies */ import { activatePlugin, @@ -8,7 +8,7 @@ import { insertBlock, pressKeyWithModifier, searchForBlock, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; const INSERTER_BUTTON_SELECTOR = '.components-popover__content .editor-block-types-list__item'; const INSERTER_ICON_WRAPPER_SELECTOR = `${ INSERTER_BUTTON_SELECTOR } .editor-block-types-list__item-icon`; diff --git a/packages/tests-e2e/specs/container-blocks.test.js b/packages/e2e-tests/specs/plugins/container-blocks.test.js similarity index 97% rename from packages/tests-e2e/specs/container-blocks.test.js rename to packages/e2e-tests/specs/plugins/container-blocks.test.js index b5aa2b84096cc..2d32224bb3503 100644 --- a/packages/tests-e2e/specs/container-blocks.test.js +++ b/packages/e2e-tests/specs/plugins/container-blocks.test.js @@ -1,5 +1,5 @@ /** - * Internal dependencies + * WordPress dependencies */ import { activatePlugin, @@ -8,7 +8,7 @@ import { getEditedPostContent, insertBlock, switchEditorModeTo, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'InnerBlocks Template Sync', () => { beforeAll( async () => { diff --git a/packages/tests-e2e/specs/deprecated-node-matcher.test.js b/packages/e2e-tests/specs/plugins/deprecated-node-matcher.test.js similarity index 94% rename from packages/tests-e2e/specs/deprecated-node-matcher.test.js rename to packages/e2e-tests/specs/plugins/deprecated-node-matcher.test.js index f59ad9892846f..21b587a00c28b 100644 --- a/packages/tests-e2e/specs/deprecated-node-matcher.test.js +++ b/packages/e2e-tests/specs/plugins/deprecated-node-matcher.test.js @@ -1,5 +1,5 @@ /** - * Internal dependencies + * WordPress dependencies */ import { activatePlugin, @@ -8,7 +8,7 @@ import { getEditedPostContent, insertBlock, pressKeyWithModifier, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'Deprecated Node Matcher', () => { beforeAll( async () => { diff --git a/packages/tests-e2e/specs/format-api.test.js b/packages/e2e-tests/specs/plugins/format-api.test.js similarity index 94% rename from packages/tests-e2e/specs/format-api.test.js rename to packages/e2e-tests/specs/plugins/format-api.test.js index 915d23b679a1e..c8d22a60f356d 100644 --- a/packages/tests-e2e/specs/format-api.test.js +++ b/packages/e2e-tests/specs/plugins/format-api.test.js @@ -1,5 +1,5 @@ /** - * Internal dependencies + * WordPress dependencies */ import { activatePlugin, @@ -8,7 +8,7 @@ import { deactivatePlugin, getEditedPostContent, pressKeyWithModifier, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'Using Format API', () => { beforeAll( async () => { diff --git a/packages/tests-e2e/specs/hooks-api.test.js b/packages/e2e-tests/specs/plugins/hooks-api.test.js similarity index 95% rename from packages/tests-e2e/specs/hooks-api.test.js rename to packages/e2e-tests/specs/plugins/hooks-api.test.js index 3f4ec35837a96..7ad6f518c1e69 100644 --- a/packages/tests-e2e/specs/hooks-api.test.js +++ b/packages/e2e-tests/specs/plugins/hooks-api.test.js @@ -1,12 +1,12 @@ /** - * Internal dependencies + * WordPress dependencies */ import { activatePlugin, clickBlockAppender, createNewPost, deactivatePlugin, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'Using Hooks API', () => { beforeAll( async () => { diff --git a/packages/tests-e2e/specs/meta-attribute-block.test.js b/packages/e2e-tests/specs/plugins/meta-attribute-block.test.js similarity index 95% rename from packages/tests-e2e/specs/meta-attribute-block.test.js rename to packages/e2e-tests/specs/plugins/meta-attribute-block.test.js index 6dc54ea8c1849..905807501774f 100644 --- a/packages/tests-e2e/specs/meta-attribute-block.test.js +++ b/packages/e2e-tests/specs/plugins/meta-attribute-block.test.js @@ -1,5 +1,5 @@ /** - * Internal dependencies + * WordPress dependencies */ import { activatePlugin, @@ -8,7 +8,7 @@ import { getEditedPostContent, insertBlock, saveDraft, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'Block with a meta attribute', () => { beforeAll( async () => { diff --git a/packages/tests-e2e/specs/meta-boxes.test.js b/packages/e2e-tests/specs/plugins/meta-boxes.test.js similarity index 98% rename from packages/tests-e2e/specs/meta-boxes.test.js rename to packages/e2e-tests/specs/plugins/meta-boxes.test.js index 2dfecddf85431..94a0c04c81bcb 100644 --- a/packages/tests-e2e/specs/meta-boxes.test.js +++ b/packages/e2e-tests/specs/plugins/meta-boxes.test.js @@ -1,5 +1,5 @@ /** - * Internal dependencies + * WordPress dependencies */ import { activatePlugin, @@ -9,7 +9,7 @@ import { insertBlock, openDocumentSettingsSidebar, publishPost, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'Meta boxes', () => { beforeAll( async () => { diff --git a/packages/tests-e2e/specs/plugins-api.test.js b/packages/e2e-tests/specs/plugins/plugins-api.test.js similarity index 97% rename from packages/tests-e2e/specs/plugins-api.test.js rename to packages/e2e-tests/specs/plugins/plugins-api.test.js index 3da0a0a1314b9..d264bb736ed08 100644 --- a/packages/tests-e2e/specs/plugins-api.test.js +++ b/packages/e2e-tests/specs/plugins/plugins-api.test.js @@ -1,5 +1,5 @@ /** - * Internal dependencies + * WordPress dependencies */ import { activatePlugin, @@ -10,7 +10,7 @@ import { openDocumentSettingsSidebar, openPublishPanel, publishPost, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'Using Plugins API', () => { beforeAll( async () => { diff --git a/packages/tests-e2e/specs/templates.test.js b/packages/e2e-tests/specs/plugins/templates.test.js similarity index 98% rename from packages/tests-e2e/specs/templates.test.js rename to packages/e2e-tests/specs/plugins/templates.test.js index a2e06eb0a8751..749842554fe66 100644 --- a/packages/tests-e2e/specs/templates.test.js +++ b/packages/e2e-tests/specs/plugins/templates.test.js @@ -1,5 +1,5 @@ /** - * Internal dependencies + * WordPress dependencies */ import { activatePlugin, @@ -12,7 +12,7 @@ import { switchUserToAdmin, switchUserToTest, visitAdminPage, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'templates', () => { describe( 'Using a CPT with a predefined template', () => { diff --git a/packages/tests-e2e/specs/wp-editor-meta-box.test.js b/packages/e2e-tests/specs/plugins/wp-editor-meta-box.test.js similarity index 93% rename from packages/tests-e2e/specs/wp-editor-meta-box.test.js rename to packages/e2e-tests/specs/plugins/wp-editor-meta-box.test.js index 579097afcf91e..c99da39b7cfe0 100644 --- a/packages/tests-e2e/specs/wp-editor-meta-box.test.js +++ b/packages/e2e-tests/specs/plugins/wp-editor-meta-box.test.js @@ -1,12 +1,12 @@ /** - * Internal dependencies + * WordPress dependencies */ import { activatePlugin, createNewPost, deactivatePlugin, publishPost, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'WP Editor Meta Boxes', () => { beforeAll( async () => { diff --git a/packages/tests-e2e/specs/popovers.test.js b/packages/e2e-tests/specs/popovers.test.js similarity index 87% rename from packages/tests-e2e/specs/popovers.test.js rename to packages/e2e-tests/specs/popovers.test.js index dff7692aa9e9d..5a1e17817dabf 100644 --- a/packages/tests-e2e/specs/popovers.test.js +++ b/packages/e2e-tests/specs/popovers.test.js @@ -1,7 +1,7 @@ /** - * Internal dependencies + * WordPress dependencies */ -import { createNewPost } from '../support/utils'; +import { createNewPost } from '@wordpress/e2e-test-utils'; describe( 'popovers', () => { beforeEach( async () => { diff --git a/packages/tests-e2e/specs/post-visibility.test.js b/packages/e2e-tests/specs/post-visibility.test.js similarity index 92% rename from packages/tests-e2e/specs/post-visibility.test.js rename to packages/e2e-tests/specs/post-visibility.test.js index 70ec9dcff905c..0c93dcdef0f89 100644 --- a/packages/tests-e2e/specs/post-visibility.test.js +++ b/packages/e2e-tests/specs/post-visibility.test.js @@ -1,11 +1,11 @@ /** - * Internal dependencies + * WordPress dependencies */ import { setBrowserViewport, createNewPost, openDocumentSettingsSidebar, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'Post visibility', () => { [ 'large', 'small' ].forEach( ( viewport ) => { diff --git a/packages/tests-e2e/specs/preferences.test.js b/packages/e2e-tests/specs/preferences.test.js similarity index 94% rename from packages/tests-e2e/specs/preferences.test.js rename to packages/e2e-tests/specs/preferences.test.js index 52e5e2c3c8558..24cf347d96501 100644 --- a/packages/tests-e2e/specs/preferences.test.js +++ b/packages/e2e-tests/specs/preferences.test.js @@ -1,7 +1,7 @@ /** - * Internal dependencies + * WordPress dependencies */ -import { createNewPost } from '../support/utils'; +import { createNewPost } from '@wordpress/e2e-test-utils'; describe( 'preferences', () => { beforeAll( async () => { diff --git a/packages/tests-e2e/specs/preview.test.js b/packages/e2e-tests/specs/preview.test.js similarity index 98% rename from packages/tests-e2e/specs/preview.test.js rename to packages/e2e-tests/specs/preview.test.js index 0d0e00385f4ce..8488ffe1ec1e3 100644 --- a/packages/tests-e2e/specs/preview.test.js +++ b/packages/e2e-tests/specs/preview.test.js @@ -5,14 +5,14 @@ import { last } from 'lodash'; import { parse } from 'url'; /** - * Internal dependencies + * WordPress dependencies */ import { createNewPost, createURL, publishPost, saveDraft, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'Preview', () => { beforeEach( async () => { diff --git a/packages/tests-e2e/specs/publish-button.test.js b/packages/e2e-tests/specs/publish-button.test.js similarity index 97% rename from packages/tests-e2e/specs/publish-button.test.js rename to packages/e2e-tests/specs/publish-button.test.js index d70ca1998fe13..da5cf1d616124 100644 --- a/packages/tests-e2e/specs/publish-button.test.js +++ b/packages/e2e-tests/specs/publish-button.test.js @@ -3,7 +3,7 @@ import { disablePrePublishChecks, enablePrePublishChecks, createNewPost, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'PostPublishButton', () => { let werePrePublishChecksEnabled; diff --git a/packages/tests-e2e/specs/publish-panel.test.js b/packages/e2e-tests/specs/publish-panel.test.js similarity index 98% rename from packages/tests-e2e/specs/publish-panel.test.js rename to packages/e2e-tests/specs/publish-panel.test.js index 274051666701f..026c3d8ee35fd 100644 --- a/packages/tests-e2e/specs/publish-panel.test.js +++ b/packages/e2e-tests/specs/publish-panel.test.js @@ -6,7 +6,7 @@ import { openPublishPanel, pressKeyWithModifier, publishPost, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'PostPublishPanel', () => { let werePrePublishChecksEnabled; diff --git a/packages/tests-e2e/specs/publishing.test.js b/packages/e2e-tests/specs/publishing.test.js similarity index 98% rename from packages/tests-e2e/specs/publishing.test.js rename to packages/e2e-tests/specs/publishing.test.js index e2face65187a3..b484428e4fcc5 100644 --- a/packages/tests-e2e/specs/publishing.test.js +++ b/packages/e2e-tests/specs/publishing.test.js @@ -1,5 +1,5 @@ /** - * Internal dependencies + * WordPress dependencies */ import { createNewPost, @@ -9,7 +9,7 @@ import { disablePrePublishChecks, arePrePublishChecksEnabled, setBrowserViewport, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'Publishing', () => { [ 'post', 'page' ].forEach( ( postType ) => { diff --git a/packages/tests-e2e/specs/reusable-blocks.test.js b/packages/e2e-tests/specs/reusable-blocks.test.js similarity index 99% rename from packages/tests-e2e/specs/reusable-blocks.test.js rename to packages/e2e-tests/specs/reusable-blocks.test.js index a73f6917440dd..f1ef297f311ba 100644 --- a/packages/tests-e2e/specs/reusable-blocks.test.js +++ b/packages/e2e-tests/specs/reusable-blocks.test.js @@ -1,5 +1,5 @@ /** - * Internal dependencies + * WordPress dependencies */ import { insertBlock, @@ -7,7 +7,7 @@ import { pressKeyWithModifier, searchForBlock, getEditedPostContent, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; function waitForAndAcceptDialog() { return new Promise( ( resolve ) => { diff --git a/packages/tests-e2e/specs/rich-text.test.js b/packages/e2e-tests/specs/rich-text.test.js similarity index 98% rename from packages/tests-e2e/specs/rich-text.test.js rename to packages/e2e-tests/specs/rich-text.test.js index 6b472f2085c6d..0773f295566ad 100644 --- a/packages/tests-e2e/specs/rich-text.test.js +++ b/packages/e2e-tests/specs/rich-text.test.js @@ -1,5 +1,5 @@ /** - * Internal dependencies + * WordPress dependencies */ import { createNewPost, @@ -7,7 +7,7 @@ import { insertBlock, clickBlockAppender, pressKeyWithModifier, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'RichText', () => { beforeEach( async () => { diff --git a/packages/tests-e2e/specs/shortcut-help.test.js b/packages/e2e-tests/specs/shortcut-help.test.js similarity index 95% rename from packages/tests-e2e/specs/shortcut-help.test.js rename to packages/e2e-tests/specs/shortcut-help.test.js index 064e8466bc3aa..03c53949b436d 100644 --- a/packages/tests-e2e/specs/shortcut-help.test.js +++ b/packages/e2e-tests/specs/shortcut-help.test.js @@ -1,12 +1,12 @@ /** - * Internal dependencies + * WordPress dependencies */ import { createNewPost, clickOnMoreMenuItem, clickOnCloseModalButton, pressKeyWithModifier, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'keyboard shortcut help modal', () => { beforeAll( async () => { diff --git a/packages/tests-e2e/specs/sidebar-permalink-panel.test.js b/packages/e2e-tests/specs/sidebar-permalink-panel.test.js similarity index 97% rename from packages/tests-e2e/specs/sidebar-permalink-panel.test.js rename to packages/e2e-tests/specs/sidebar-permalink-panel.test.js index 95e18e3df49bd..16de106f570e6 100644 --- a/packages/tests-e2e/specs/sidebar-permalink-panel.test.js +++ b/packages/e2e-tests/specs/sidebar-permalink-panel.test.js @@ -1,5 +1,5 @@ /** - * Internal dependencies + * WordPress dependencies */ import { activatePlugin, @@ -8,7 +8,7 @@ import { findSidebarPanelWithTitle, openDocumentSettingsSidebar, publishPost, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; // This tests are not together with the remaining sidebar tests, // because we need to publish/save a post, to correctly test the permalink panel. diff --git a/packages/tests-e2e/specs/sidebar.test.js b/packages/e2e-tests/specs/sidebar.test.js similarity index 98% rename from packages/tests-e2e/specs/sidebar.test.js rename to packages/e2e-tests/specs/sidebar.test.js index 81cd4dfd57c64..def5d0f501141 100644 --- a/packages/tests-e2e/specs/sidebar.test.js +++ b/packages/e2e-tests/specs/sidebar.test.js @@ -1,5 +1,5 @@ /** - * Internal dependencies + * WordPress dependencies */ import { findSidebarPanelWithTitle, @@ -8,7 +8,7 @@ import { openDocumentSettingsSidebar, pressKeyWithModifier, setBrowserViewport, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; const SIDEBAR_SELECTOR = '.edit-post-sidebar'; const ACTIVE_SIDEBAR_TAB_SELECTOR = '.edit-post-sidebar__panel-tab.is-active'; diff --git a/packages/tests-e2e/specs/splitting-merging.test.js b/packages/e2e-tests/specs/splitting-merging.test.js similarity index 99% rename from packages/tests-e2e/specs/splitting-merging.test.js rename to packages/e2e-tests/specs/splitting-merging.test.js index e45a62fff6507..b5b24ce6345cd 100644 --- a/packages/tests-e2e/specs/splitting-merging.test.js +++ b/packages/e2e-tests/specs/splitting-merging.test.js @@ -1,5 +1,5 @@ /** - * Internal dependencies + * WordPress dependencies */ import { createNewPost, @@ -7,7 +7,7 @@ import { getEditedPostContent, pressKeyTimes, pressKeyWithModifier, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'splitting and merging blocks', () => { beforeEach( async () => { diff --git a/packages/tests-e2e/specs/style-variation.test.js b/packages/e2e-tests/specs/style-variation.test.js similarity index 94% rename from packages/tests-e2e/specs/style-variation.test.js rename to packages/e2e-tests/specs/style-variation.test.js index 660d779b646a4..0d1c2e758b042 100644 --- a/packages/tests-e2e/specs/style-variation.test.js +++ b/packages/e2e-tests/specs/style-variation.test.js @@ -1,7 +1,7 @@ /** - * Internal dependencies + * WordPress dependencies */ -import { createNewPost, insertBlock, getEditedPostContent } from '../support/utils'; +import { createNewPost, insertBlock, getEditedPostContent } from '@wordpress/e2e-test-utils'; describe( 'adding blocks', () => { beforeAll( async () => { diff --git a/packages/tests-e2e/specs/taxonomies.test.js b/packages/e2e-tests/specs/taxonomies.test.js similarity index 97% rename from packages/tests-e2e/specs/taxonomies.test.js rename to packages/e2e-tests/specs/taxonomies.test.js index 6ee40e925b2d4..adb596e9d296a 100644 --- a/packages/tests-e2e/specs/taxonomies.test.js +++ b/packages/e2e-tests/specs/taxonomies.test.js @@ -1,12 +1,12 @@ /** - * Internal dependencies + * WordPress dependencies */ import { findSidebarPanelWithTitle, createNewPost, openDocumentSettingsSidebar, publishPost, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'Taxonomies', () => { const canCreatTermInTaxonomy = ( taxonomy ) => { diff --git a/packages/tests-e2e/specs/undo.test.js b/packages/e2e-tests/specs/undo.test.js similarity index 97% rename from packages/tests-e2e/specs/undo.test.js rename to packages/e2e-tests/specs/undo.test.js index 5a9eb0351ec41..71caccf09b3c3 100644 --- a/packages/tests-e2e/specs/undo.test.js +++ b/packages/e2e-tests/specs/undo.test.js @@ -1,12 +1,12 @@ /** - * Internal dependencies + * WordPress dependencies */ import { clickBlockAppender, getEditedPostContent, createNewPost, pressKeyWithModifier, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'undo', () => { beforeEach( async () => { diff --git a/packages/tests-e2e/specs/writing-flow.test.js b/packages/e2e-tests/specs/writing-flow.test.js similarity index 99% rename from packages/tests-e2e/specs/writing-flow.test.js rename to packages/e2e-tests/specs/writing-flow.test.js index 24e21642862f6..ee5b2b2d1bc2f 100644 --- a/packages/tests-e2e/specs/writing-flow.test.js +++ b/packages/e2e-tests/specs/writing-flow.test.js @@ -1,5 +1,5 @@ /** - * Internal dependencies + * WordPress dependencies */ import { clickBlockAppender, @@ -7,7 +7,7 @@ import { createNewPost, pressKeyTimes, pressKeyWithModifier, -} from '../support/utils'; +} from '@wordpress/e2e-test-utils'; describe( 'adding blocks', () => { beforeEach( async () => { diff --git a/test/unit/jest.config.json b/test/unit/jest.config.json index 90efca5890533..ed9e9ebc52774 100644 --- a/test/unit/jest.config.json +++ b/test/unit/jest.config.json @@ -11,7 +11,7 @@ "testURL": "http://localhost", "testPathIgnorePatterns": [ "/node_modules/", - "/packages/tests-e2e", + "/packages/e2e-tests", "<rootDir>/.*/build/", "<rootDir>/.*/build-module/" ], From 4fd299c07e6a67e65a891e3966332a27a3f26cb7 Mon Sep 17 00:00:00 2001 From: Mel Choyce <melchoyce@users.noreply.github.com> Date: Tue, 15 Jan 2019 12:13:25 -0500 Subject: [PATCH 154/691] Update TextareaControl readme (#13320) Updating documentation to describe the use and functionality of the TextareaControl component. Thanks @sarahmonster for helping me draft this. --- .../components/src/textarea-control/README.md | 120 ++++++++++++++---- 1 file changed, 97 insertions(+), 23 deletions(-) diff --git a/packages/components/src/textarea-control/README.md b/packages/components/src/textarea-control/README.md index c43458971a474..f68c3103c04f0 100644 --- a/packages/components/src/textarea-control/README.md +++ b/packages/components/src/textarea-control/README.md @@ -1,46 +1,117 @@ # TextareaControl -TextareaControl is used to generate textarea input fields. +TextareaControls are TextControls that allow for multiple lines of text, and wrap overflow text onto a new line. They are a fixed height and scroll vertically when the cursor reaches the bottom of the field. +![An empty TextareaControl, and a focused TextareaControl with some content entered.](https://wordpress.org/gutenberg/files/2019/01/TextareaControl.png) -## Usage +## Table of contents -```jsx -import { TextareaControl } from '@wordpress/components'; -import { withState } from '@wordpress/compose'; +1. [Design guidelines](http://#design-guidelines) +2. [Development guidelines](http://#development-guidelines) +3. [Related components](http://#related-components) -const MyTextareaControl = withState( { - text: '', -} )( ( { text, setState } ) => ( - <TextareaControl - label="Text" - help="Enter some text" - value={ text } - onChange={ ( text ) => setState( { text } ) } - /> -) ); -``` +## Design guidelines -## Props +### Usage + +**When to use TextareaControl** + +Use TextareaControl when you need to encourage users enter an amount of text that’s longer than a single line. (A bigger box can encourage people to be more verbose, where a smaller one encourages them to be succinct.) + +TextareaControl should: + +- Stand out from the background of the page and indicate that users can input information. +- Have clearly differentiated active/inactive states, including focus styling. +- Make it easy to understand and address any errors via clear and direct error notices. +- Make it easy to understand the requested information by using a clear and descriptive label. + +**When not to use TextareaControl** + +Do not use TextareaControl if you need to let users enter shorter answers (no longer than a single line), such as a phone number or name. In this case, you should use `Text Control`. + +![](https://wordpress.org/gutenberg/files/2019/01/TextareaControl-Answers-Do.png) + +**Do** + +Use TextareaControl to let users to enter text longer than a single line. + +![](https://wordpress.org/gutenberg/files/2019/01/TextareaControl-Answers-Dont.png) + +**Don’t** + +Use TextareaControl for shorter answers. + +## Anatomy + +![](https://wordpress.org/gutenberg/files/2019/01/TextareaControl-Anatomy.png) + +1. Container +2. Label + +**Containers** + +Containers improve the discoverability of text fields by creating contrast between the text field and surrounding content. + +![](https://wordpress.org/gutenberg/files/2019/01/TextareaControl-Stroke-Do.png) + +**Do** +Use a stroke around the container, which clearly indicates that users can input information. + +![](https://wordpress.org/gutenberg/files/2019/01/TextareaControl-Stroke-Dont.png) + +**Don’t** +Use unclear visual markers to indicate a text field. + +**Label text** + +Label text is used to inform users as to what information is requested for a text field. Every text field should have a label. Label text should be above the input field, and always visible. Write labels in sentence capitalization. + +**Error text** + +When text input isn’t accepted, an error message can display instructions on how to fix it. Error messages are displayed below the input line, replacing helper text until fixed. + +![](https://wordpress.org/gutenberg/files/2019/01/TextareaControl-Error.png) + +## Development guidelines + +### Usage + + import { TextareaControl } from '@wordpress/components'; + import { withState } from '@wordpress/compose'; + + const MyTextareaControl = withState( { + text: '', + } )( ( { text, setState } ) => ( + <TextareaControl + label="Text" + help="Enter some text" + value={ text } + onChange={ ( text ) => setState( { text } ) } + /> + ) ); + + +### Props + +The set of props accepted by the component will be specified below. -The set of props accepted by the component will be specified below. Props not included in this set will be applied to the textarea element. -### label +**label** If this property is added, a label will be generated using label property as the content. - Type: `String` - Required: No -### help +**help** If this property is added, a help text will be generated using help property as the content. - Type: `String` - Required: No -### rows +**rows** The number of rows the textarea should contain. Defaults to four. @@ -48,16 +119,19 @@ The number of rows the textarea should contain. Defaults to four. - Required: No - Default: 4 -### value +**value** The current value of the textarea. - Type: `String` - Required: Yes -### onChange +**onChange** A function that receives the new value of the textarea each time it changes. - Type: `function` - Required: Yes +# Related components + +- For a field where users only enter one line of text, use the `TextControl` component. From 2368662b73dc34d1487d7640b92a777990e48691 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Tue, 15 Jan 2019 18:30:34 +0000 Subject: [PATCH 155/691] Axe API integration with Jest and Puppeteer (#13241) * Axe API integration with Jest and Puppeteer * Implement expect matcher for accessibility checks * Docs: Add documentation for jest-puppeteer-axe package * Apply suggestions from code review Co-Authored-By: gziolo <grzegorz@gziolo.pl> --- docs/contributors/testing-overview.md | 12 +- docs/manifest.json | 6 + package-lock.json | 22 ++++ package.json | 1 + packages/e2e-test-utils/package.json | 4 +- packages/e2e-tests/package.json | 4 +- packages/jest-console/README.md | 2 +- packages/jest-preset-default/README.md | 4 +- packages/jest-puppeteer-axe/.npmrc | 1 + packages/jest-puppeteer-axe/CHANGELOG.md | 3 + packages/jest-puppeteer-axe/README.md | 61 +++++++++++ packages/jest-puppeteer-axe/package.json | 38 +++++++ packages/jest-puppeteer-axe/src/index.js | 103 ++++++++++++++++++ .../npm-package-json-lint-config/package.json | 2 +- packages/scripts/README.md | 4 +- 15 files changed, 251 insertions(+), 16 deletions(-) create mode 100644 packages/jest-puppeteer-axe/.npmrc create mode 100644 packages/jest-puppeteer-axe/CHANGELOG.md create mode 100644 packages/jest-puppeteer-axe/README.md create mode 100644 packages/jest-puppeteer-axe/package.json create mode 100644 packages/jest-puppeteer-axe/src/index.js diff --git a/docs/contributors/testing-overview.md b/docs/contributors/testing-overview.md index c54ef5a7e6033..495109b5f0712 100644 --- a/docs/contributors/testing-overview.md +++ b/docs/contributors/testing-overview.md @@ -19,7 +19,7 @@ When writing tests consider the following: ## JavaScript Testing -Tests for JavaScript use [Jest](http://facebook.github.io/jest/) as the test runner and its API for [globals](https://facebook.github.io/jest/docs/en/api.html) (`describe`, `test`, `beforeEach` and so on) [assertions](http://facebook.github.io/jest/docs/en/expect.html), [mocks](http://facebook.github.io/jest/docs/en/mock-functions.html), [spies](http://facebook.github.io/jest/docs/en/jest-object.html#jestspyonobject-methodname) and [mock functions](https://facebook.github.io/jest/docs/en/mock-function-api.html). If needed, you can also use [Enzyme](https://github.com/airbnb/enzyme) for React component testing. +Tests for JavaScript use [Jest](https://jestjs.io/) as the test runner and its API for [globals](https://jestjs.io/docs/en/api.html) (`describe`, `test`, `beforeEach` and so on) [assertions](https://jestjs.io/docs/en/expect.html), [mocks](https://jestjs.io/docs/en/mock-functions.html), [spies](https://jestjs.io/docs/en/jest-object.html#jestspyonobject-methodname) and [mock functions](https://jestjs.io/docs/en/mock-function-api.html). If needed, you can also use [Enzyme](https://github.com/airbnb/enzyme) for React component testing. Assuming you've followed the [instructions](https://github.com/WordPress/gutenberg/blob/master/CONTRIBUTING.md) to install Node and project dependencies, tests can be run from the command-line with NPM: @@ -88,9 +88,9 @@ describe( 'CheckboxWithLabel', () => { ### Setup and Teardown methods -The Jest API includes some nifty [setup and teardown methods](https://facebook.github.io/jest/docs/en/setup-teardown.html) that allow you to perform tasks *before* and *after* each or all of your tests, or tests within a specific `describe` block. +The Jest API includes some nifty [setup and teardown methods](https://jestjs.io/docs/en/setup-teardown.html) that allow you to perform tasks *before* and *after* each or all of your tests, or tests within a specific `describe` block. -These methods can handle asynchronous code to allow setup that you normally cannot do inline. As with [individual test cases](https://facebook.github.io/jest/docs/en/asynchronous.html#promises), you can return a Promise and Jest will wait for it to resolve: +These methods can handle asynchronous code to allow setup that you normally cannot do inline. As with [individual test cases](https://jestjs.io/docs/en/asynchronous.html#promises), you can return a Promise and Jest will wait for it to resolve: ```javascript // one-time setup for *all* tests @@ -188,7 +188,7 @@ describe( 'The bilbo module', () => { ### Testing globals -We can use [Jest spies](http://facebook.github.io/jest/docs/en/jest-object.html#jestspyonobject-methodname) to test code that calls global methods. +We can use [Jest spies](https://jestjs.io/docs/en/jest-object.html#jestspyonobject-methodname) to test code that calls global methods. ```javascript import { myModuleFunctionThatOpensANewWindow } from '../my-module'; @@ -374,5 +374,5 @@ Code style in PHP is enforced using [PHP_CodeSniffer](https://github.com/squizla To run unit tests only, without the linter, use `npm run test-unit-php` instead. -[snapshot testing]: https://facebook.github.io/jest/docs/en/snapshot-testing.html -[update snapshots]: https://facebook.github.io/jest/docs/en/snapshot-testing.html#updating-snapshots +[snapshot testing]: https://jestjs.io/docs/en/snapshot-testing.html +[update snapshots]: https://jestjs.io/docs/en/snapshot-testing.html#updating-snapshots diff --git a/docs/manifest.json b/docs/manifest.json index 51741605a75c9..fb69aae37a09b 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -677,6 +677,12 @@ "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/jest-preset-default/README.md", "parent": "packages" }, + { + "title": "@wordpress/jest-puppeteer-axe", + "slug": "packages-jest-puppeteer-axe", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/jest-puppeteer-axe/README.md", + "parent": "packages" + }, { "title": "@wordpress/keycodes", "slug": "packages-keycodes", diff --git a/package-lock.json b/package-lock.json index dc9076c17fc2a..07c5034f78385 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2684,6 +2684,13 @@ "jest-enzyme": "^6.0.2" } }, + "@wordpress/jest-puppeteer-axe": { + "version": "file:packages/jest-puppeteer-axe", + "dev": true, + "requires": { + "axe-puppeteer": "^0.1.0" + } + }, "@wordpress/keycodes": { "version": "file:packages/keycodes", "requires": { @@ -3345,6 +3352,21 @@ "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==", "dev": true }, + "axe-core": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-3.1.2.tgz", + "integrity": "sha512-e1WVs0SQu3tM29J9a/mISGvlo2kdCStE+yffIAJF6eb42FS+eUFEVz9j4rgDeV2TAfPJmuOZdRetWYycIbK7Vg==", + "dev": true + }, + "axe-puppeteer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/axe-puppeteer/-/axe-puppeteer-0.1.0.tgz", + "integrity": "sha512-9pWYjivWC2lSvTCCUCgTc/62S8UKKkPFb0gYg0zYVcTsyRcIab1o0YoQYYLsI00QVES29x2VrBoid0ROPKk/RQ==", + "dev": true, + "requires": { + "axe-core": "^3.1.2" + } + }, "babel-code-frame": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", diff --git a/package.json b/package.json index ac8589c049a56..57e7805e49726 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "@wordpress/eslint-plugin": "file:packages/eslint-plugin", "@wordpress/jest-console": "file:packages/jest-console", "@wordpress/jest-preset-default": "file:packages/jest-preset-default", + "@wordpress/jest-puppeteer-axe": "file:packages/jest-puppeteer-axe", "@wordpress/library-export-default-webpack-plugin": "file:packages/library-export-default-webpack-plugin", "@wordpress/npm-package-json-lint-config": "file:packages/npm-package-json-lint-config", "@wordpress/postcss-themes": "file:packages/postcss-themes", diff --git a/packages/e2e-test-utils/package.json b/packages/e2e-test-utils/package.json index 6b8d4c5698774..c726fdc74dbb1 100644 --- a/packages/e2e-test-utils/package.json +++ b/packages/e2e-test-utils/package.json @@ -30,8 +30,8 @@ "node-fetch": "^1.7.3" }, "peerDependencies": { - "jest": ">=23.0.0", - "puppeteer": ">=1.6.0" + "jest": ">=23", + "puppeteer": ">=1.6" }, "publishConfig": { "access": "public" diff --git a/packages/e2e-tests/package.json b/packages/e2e-tests/package.json index 889b2fccf093b..98923f477d2b5 100644 --- a/packages/e2e-tests/package.json +++ b/packages/e2e-tests/package.json @@ -25,8 +25,8 @@ "lodash": "^4.17.10" }, "peerDependencies": { - "jest": ">=23.0.0", - "puppeteer": ">=1.6.0" + "jest": ">=23", + "puppeteer": ">=1.6" }, "publishConfig": { "access": "public" diff --git a/packages/jest-console/README.md b/packages/jest-console/README.md index 18c3cb1e7bb15..8ba0a5346a7ef 100644 --- a/packages/jest-console/README.md +++ b/packages/jest-console/README.md @@ -1,6 +1,6 @@ # Jest Console -Custom [Jest](http://facebook.github.io/jest/) matchers for the [Console](https://developer.mozilla.org/en-US/docs/Web/API/Console) +Custom [Jest](https://jestjs.io/) matchers for the [Console](https://developer.mozilla.org/en-US/docs/Web/API/Console) object to test JavaScript code in WordPress. This package converts `console.error`, `console.info`, `console.log` and `console.warn` functions into mocks and tracks their calls. diff --git a/packages/jest-preset-default/README.md b/packages/jest-preset-default/README.md index ee32878ae3fe8..059bdfc9a8859 100644 --- a/packages/jest-preset-default/README.md +++ b/packages/jest-preset-default/README.md @@ -1,6 +1,6 @@ # Jest Preset Default -Default [Jest](https://facebook.github.io/jest/) preset for WordPress development. +Default [Jest](https://jestjs.io/) preset for WordPress development. ## Installation @@ -29,7 +29,7 @@ npm install @wordpress/jest-preset-default --save-dev * `setupFiles` - runs code before each test which sets up global variables required in the testing environment. * `setupTestFrameworkScriptFile` - runs code which adds improved support for `Console` object and `React` components to the testing framework before each test. * `testMatch`- includes `/test/` subfolder in the glob patterns Jest uses to detect test files. It detects only test files containing `.js` extension. -* `timers` - use of [fake timers](https://facebook.github.io/jest/docs/en/timer-mocks.html) for functions such as `setTimeout` is enabled. +* `timers` - use of [fake timers](https://jestjs.io/docs/en/timer-mocks.html) for functions such as `setTimeout` is enabled. * `transform` - adds support for [PEG.js]( https://github.com/pegjs/pegjs#javascript-api) transformed necessary for WordPress blocks. It also keeps the default [babel-jest](https://github.com/facebook/jest/tree/master/packages/babel-jest) transformer. * `verbose` - each individual test won't be reported during the run. diff --git a/packages/jest-puppeteer-axe/.npmrc b/packages/jest-puppeteer-axe/.npmrc new file mode 100644 index 0000000000000..43c97e719a5a8 --- /dev/null +++ b/packages/jest-puppeteer-axe/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/packages/jest-puppeteer-axe/CHANGELOG.md b/packages/jest-puppeteer-axe/CHANGELOG.md new file mode 100644 index 0000000000000..16c97e5a1bf32 --- /dev/null +++ b/packages/jest-puppeteer-axe/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 (Unreleased) + +- Initial release. diff --git a/packages/jest-puppeteer-axe/README.md b/packages/jest-puppeteer-axe/README.md new file mode 100644 index 0000000000000..fc85725e27e9a --- /dev/null +++ b/packages/jest-puppeteer-axe/README.md @@ -0,0 +1,61 @@ +# Jest Puppeteer Axe + +[Axe](https://www.deque.com/axe/) (the Accessibility Engine) API integration with [Jest](https://jestjs.io/) and [Puppeteer](https://pptr.dev/). + +Defines Jest async matcher to check whether a given Puppeteer's page instance passes [Axe](https://www.deque.com/axe/) accessibility tests. + +## Installation + +Install the module + +```bash +npm install @wordpress/jest-puppeteer-axe --save-dev +``` + +### Setup + +The simplest setup is to use Jest's `setupTestFrameworkScriptFile` config option: + +```js +"jest": { + "setupTestFrameworkScriptFile": "./node_modules/@wordpress/jest-puppeteer-axe/build/index.js" +}, +``` + +If your project already has a script file which sets up the test framework, you will need the following import statement: + +```js +import '@wordpress/jest-puppeteer-axe'; +``` + +## Usage + +In your Jest test suite add the following code to the test's body: + +```js +test( 'checks the test page with Axe', async () => { + // First, run some code which loads the content of the page. + loadTestPage(); + + await expect( page ).toPassAxeTests(); +} ); +``` + +It is also possible to pass optional Axe API options to perform customized check: +- `include` - CSS selector to to add the list of elements to include in analysis. +- `exclude` - CSS selector to to add the list of elements to exclude from analysis. + +```js +test( 'checks the test component with Axe excluding some button', async () => { + + // First, run some code which loads the content of the page. + loadPageWithTestComponent(); + + await expect( page ).toPassAxeTests( { + include: '.test-component', + exclude: '.some-button', + } ); +} ); +``` + +<br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/jest-puppeteer-axe/package.json b/packages/jest-puppeteer-axe/package.json new file mode 100644 index 0000000000000..5006bbf5344a1 --- /dev/null +++ b/packages/jest-puppeteer-axe/package.json @@ -0,0 +1,38 @@ +{ + "name": "@wordpress/jest-puppeteer-axe", + "version": "1.0.0-alpha.0", + "description": "Axe API integration with Jest and Puppeteer.", + "author": "The WordPress Contributors", + "license": "GPL-2.0-or-later", + "keywords": [ + "wordpress", + "jest", + "puppeteer", + "axe", + "accessibility" + ], + "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/jest-puppeteer-axe/README.md", + "repository": { + "type": "git", + "url": "https://github.com/WordPress/gutenberg.git" + }, + "bugs": { + "url": "https://github.com/WordPress/gutenberg/issues" + }, + "files": [ + "build", + "build-module" + ], + "main": "build/index.js", + "module": "build-module/index.js", + "dependencies": { + "axe-puppeteer": "^0.1.0" + }, + "peerDependencies": { + "jest": ">=23", + "puppeteer": ">=1.6" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/jest-puppeteer-axe/src/index.js b/packages/jest-puppeteer-axe/src/index.js new file mode 100644 index 0000000000000..ccf1be7cf0a64 --- /dev/null +++ b/packages/jest-puppeteer-axe/src/index.js @@ -0,0 +1,103 @@ +/** + * External dependencies + */ +import AxePuppeteer from 'axe-puppeteer'; + +/** + * Formats the list of violations object returned by Axe analysis. + * + * @param {Object} violations The object with the errors found by Axe. + * + * @return {string} The user friendly message to display when the matcher fails. + */ +function formatViolations( violations ) { + return violations.map( ( { help, id, nodes } ) => { + let output = `Rule: ${ id } (${ help })\n` + + 'Affected Nodes:\n'; + + nodes.forEach( ( node ) => { + if ( node.any.length ) { + output += ` ${ node.target }\n`; + output += ' Fix ANY of the following:\n'; + node.any.forEach( ( item ) => { + output += ` - ${ item.message }\n`; + } ); + } + + if ( node.all.length ) { + output += ` ${ node.target }\n`; + output += ' Fix ALL of the following:\n'; + node.all.forEach( ( item ) => { + output += ` - ${ item.message }.\n`; + } ); + } + + if ( node.none.length ) { + output += ` ${ node.target }\n`; + output += ' Fix ALL of the following:\n'; + node.none.forEach( ( item ) => { + output += ` - ${ item.message }.\n`; + } ); + } + } ); + return output; + } ).join( '\n' ); +} + +/** + * Defines async matcher to check whether a given Puppeteer's page instance passes Axe accessibility tests. + * + * @see https://www.deque.com/axe/ + * It is possible to pass optional Axe API options to perform customized check. + * + * @see https://github.com/dequelabs/axe-puppeteer + * + * @param {Page} page Puppeteer's page instance. + * @param {?Object} params Optional Axe API options. + * @param {?string} params.include CSS selector to add to the list of elements + * to include in analysis. + * @param {?string} params.exclude CSS selector to add to the list of elements + * to exclude from analysis. + * + * @return {Object} A matcher object with two keys `pass` and `message`. + */ +async function toPassAxeTests( page, { include, exclude } = {} ) { + const axe = new AxePuppeteer( page ); + + if ( include ) { + axe.include( include ); + } + + if ( exclude ) { + axe.exclude( exclude ); + } + + const { violations } = await axe.analyze(); + + const pass = violations.length === 0; + const message = pass ? + () => { + return this.utils.matcherHint( '.not.toPassAxeTests' ) + + '\n\n' + + 'Expected page to contain accessibility check violations.\n' + + 'No violations found.'; + } : + () => { + return this.utils.matcherHint( '.toPassAxeTests' ) + + '\n\n' + + 'Expected page to pass Axe accessibility tests.\n' + + 'Violations found:\n' + + this.utils.RECEIVED_COLOR( + formatViolations( violations ) + ); + }; + + return { + message, + pass, + }; +} + +expect.extend( { + toPassAxeTests, +} ); diff --git a/packages/npm-package-json-lint-config/package.json b/packages/npm-package-json-lint-config/package.json index 6aa099ab26dde..6fdb45fd6c612 100644 --- a/packages/npm-package-json-lint-config/package.json +++ b/packages/npm-package-json-lint-config/package.json @@ -19,7 +19,7 @@ }, "main": "index.js", "peerDependencies": { - "npm-package-json-lint": ">= 3.3.1" + "npm-package-json-lint": ">=3.3.1" }, "publishConfig": { "access": "public" diff --git a/packages/scripts/README.md b/packages/scripts/README.md index 15a7405340dbe..063d95184e5cb 100644 --- a/packages/scripts/README.md +++ b/packages/scripts/README.md @@ -127,7 +127,7 @@ This is how you execute the script with presented setup: ### `test-e2e` -Launches the End-To-End (E2E) test runner. It uses [Jest](https://facebook.github.io/jest/) behind the scenes and you are able to utilize all of its [CLI options](https://facebook.github.io/jest/docs/en/cli.html). You can also run `./node_modules/.bin/wp-scripts test:e2e --help` or `npm run test:e2e:help` (as presented below) to view all of the available options. +Launches the End-To-End (E2E) test runner. It uses [Jest](https://jestjs.io/) behind the scenes and you are able to utilize all of its [CLI options](https://jestjs.io/docs/en/cli.html). You can also run `./node_modules/.bin/wp-scripts test:e2e --help` or `npm run test:e2e:help` (as presented below) to view all of the available options. Writing tests can be done using Puppeteer API: @@ -157,7 +157,7 @@ We enforce that all tests run serially in the current process using [--runInBand _Alias_: `test-unit-jest` -Launches the unit test runner. It uses [Jest](https://facebook.github.io/jest/) behind the scenes and you are able to utilize all of its [CLI options](https://facebook.github.io/jest/docs/en/cli.html). You can also run `./node_modules/.bin/wp-scripts test-unit-js --help` or `npm run test:unit:help` (as presented below) to view all of the available options. By default, it uses the set of recommended options defined in [@wordpress/jest-preset-default](https://www.npmjs.com/package/@wordpress/jest-preset-default) npm package. You can override them with your own options as described in [Jest documentation](https://jestjs.io/docs/en/configuration). +Launches the unit test runner. It uses [Jest](https://jestjs.io/) behind the scenes and you are able to utilize all of its [CLI options](https://jestjs.io/docs/en/cli.html). You can also run `./node_modules/.bin/wp-scripts test-unit-js --help` or `npm run test:unit:help` (as presented below) to view all of the available options. By default, it uses the set of recommended options defined in [@wordpress/jest-preset-default](https://www.npmjs.com/package/@wordpress/jest-preset-default) npm package. You can override them with your own options as described in [Jest documentation](https://jestjs.io/docs/en/configuration). _Example:_ From 3c5fd224e4bf3b1cc8dcda0407371ec18f953f11 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Tue, 15 Jan 2019 18:45:47 +0000 Subject: [PATCH 156/691] Async mode in the data module (#13056) --- docs/manifest.json | 6 ++ lib/packages-dependencies.php | 2 + package-lock.json | 7 +++ package.json | 1 + packages/data/package.json | 1 + .../components/async-mode-provider/index.js | 10 +++ .../data/src/components/with-select/index.js | 42 ++++++++++--- packages/data/src/index.js | 1 + .../plugins/meta-attribute-block.test.js | 11 +++- .../editor/src/components/block-list/index.js | 62 ++++++++++++++----- packages/priority-queue/.npmrc | 1 + packages/priority-queue/CHANGELOG.md | 3 + packages/priority-queue/README.md | 32 ++++++++++ packages/priority-queue/package.json | 29 +++++++++ packages/priority-queue/src/index.js | 50 +++++++++++++++ 15 files changed, 231 insertions(+), 27 deletions(-) create mode 100644 packages/data/src/components/async-mode-provider/index.js create mode 100644 packages/priority-queue/.npmrc create mode 100644 packages/priority-queue/CHANGELOG.md create mode 100644 packages/priority-queue/README.md create mode 100644 packages/priority-queue/package.json create mode 100644 packages/priority-queue/src/index.js diff --git a/docs/manifest.json b/docs/manifest.json index fb69aae37a09b..2958928a39777 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -731,6 +731,12 @@ "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/postcss-themes/README.md", "parent": "packages" }, + { + "title": "@wordpress/priority-queue", + "slug": "packages-priority-queue", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/priority-queue/README.md", + "parent": "packages" + }, { "title": "@wordpress/redux-routine", "slug": "packages-redux-routine", diff --git a/lib/packages-dependencies.php b/lib/packages-dependencies.php index ada718023b33c..f1b785f15b011 100644 --- a/lib/packages-dependencies.php +++ b/lib/packages-dependencies.php @@ -91,6 +91,7 @@ 'wp-compose', 'wp-element', 'wp-is-shallow-equal', + 'wp-priority-queue', 'wp-redux-routine', ), 'wp-date' => array( @@ -211,6 +212,7 @@ 'wp-element', 'wp-hooks', ), + 'wp-priority-queue' => array(), 'wp-redux-routine' => array(), 'wp-rich-text' => array( 'lodash', diff --git a/package-lock.json b/package-lock.json index 07c5034f78385..1566caa308054 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2469,6 +2469,7 @@ "@wordpress/compose": "file:packages/compose", "@wordpress/element": "file:packages/element", "@wordpress/is-shallow-equal": "file:packages/is-shallow-equal", + "@wordpress/priority-queue": "file:packages/priority-queue", "@wordpress/redux-routine": "file:packages/redux-routine", "equivalent-key-map": "^0.2.2", "is-promise": "^2.1.0", @@ -2766,6 +2767,12 @@ "postcss-color-function": "^4.0.1" } }, + "@wordpress/priority-queue": { + "version": "file:packages/priority-queue", + "requires": { + "@babel/runtime": "^7.0.0" + } + }, "@wordpress/redux-routine": { "version": "file:packages/redux-routine", "requires": { diff --git a/package.json b/package.json index 57e7805e49726..05f1da17f29ba 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "@wordpress/notices": "file:packages/notices", "@wordpress/nux": "file:packages/nux", "@wordpress/plugins": "file:packages/plugins", + "@wordpress/priority-queue": "file:packages/priority-queue", "@wordpress/redux-routine": "file:packages/redux-routine", "@wordpress/rich-text": "file:packages/rich-text", "@wordpress/shortcode": "file:packages/shortcode", diff --git a/packages/data/package.json b/packages/data/package.json index 7186a0c69fba5..399cc933e689a 100644 --- a/packages/data/package.json +++ b/packages/data/package.json @@ -25,6 +25,7 @@ "@wordpress/compose": "file:../compose", "@wordpress/element": "file:../element", "@wordpress/is-shallow-equal": "file:../is-shallow-equal", + "@wordpress/priority-queue": "file:../priority-queue", "@wordpress/redux-routine": "file:../redux-routine", "equivalent-key-map": "^0.2.2", "is-promise": "^2.1.0", diff --git a/packages/data/src/components/async-mode-provider/index.js b/packages/data/src/components/async-mode-provider/index.js new file mode 100644 index 0000000000000..30e877d0f1ed3 --- /dev/null +++ b/packages/data/src/components/async-mode-provider/index.js @@ -0,0 +1,10 @@ +/** + * WordPress dependencies + */ +import { createContext } from '@wordpress/element'; + +const { Consumer, Provider } = createContext( false ); + +export const AsyncModeConsumer = Consumer; + +export default Provider; diff --git a/packages/data/src/components/with-select/index.js b/packages/data/src/components/with-select/index.js index 37be4f3d2b2da..8820238e9f390 100644 --- a/packages/data/src/components/with-select/index.js +++ b/packages/data/src/components/with-select/index.js @@ -4,11 +4,15 @@ import { Component } from '@wordpress/element'; import { isShallowEqualObjects } from '@wordpress/is-shallow-equal'; import { createHigherOrderComponent } from '@wordpress/compose'; +import { createQueue } from '@wordpress/priority-queue'; /** * Internal dependencies */ import { RegistryConsumer } from '../registry-provider'; +import { AsyncModeConsumer } from '../async-mode-provider'; + +const renderQueue = createQueue(); /** * Higher-order component used to inject state-derived props using registered @@ -70,16 +74,23 @@ const withSelect = ( mapSelectToProps ) => createHigherOrderComponent( ( Wrapped componentWillUnmount() { this.canRunSelection = false; this.unsubscribe(); + renderQueue.flush( this ); } shouldComponentUpdate( nextProps, nextState ) { // Cycle subscription if registry changes. const hasRegistryChanged = nextProps.registry !== this.props.registry; + const hasSyncRenderingChanged = nextProps.isAsync !== this.props.isAsync; + if ( hasRegistryChanged ) { this.unsubscribe(); this.subscribe( nextProps.registry ); } + if ( hasSyncRenderingChanged ) { + renderQueue.flush( this ); + } + // Treat a registry change as equivalent to `ownProps`, to reflect // `mergeProps` to rendered component if and only if updated. const hasPropsChanged = ( @@ -89,11 +100,11 @@ const withSelect = ( mapSelectToProps ) => createHigherOrderComponent( ( Wrapped // Only render if props have changed or merge props have been updated // from the store subscriber. - if ( this.state === nextState && ! hasPropsChanged ) { + if ( this.state === nextState && ! hasPropsChanged && ! hasSyncRenderingChanged ) { return false; } - if ( hasPropsChanged ) { + if ( hasPropsChanged || hasSyncRenderingChanged ) { const nextMergeProps = getNextMergeProps( nextProps ); if ( ! isShallowEqualObjects( this.mergeProps, nextMergeProps ) ) { // If merge props change as a result of the incoming props, @@ -137,7 +148,13 @@ const withSelect = ( mapSelectToProps ) => createHigherOrderComponent( ( Wrapped } subscribe( registry ) { - this.unsubscribe = registry.subscribe( this.onStoreChange ); + this.unsubscribe = registry.subscribe( () => { + if ( this.props.isAsync ) { + renderQueue.add( this, this.onStoreChange ); + } else { + this.onStoreChange(); + } + } ); } render() { @@ -146,14 +163,19 @@ const withSelect = ( mapSelectToProps ) => createHigherOrderComponent( ( Wrapped } return ( ownProps ) => ( - <RegistryConsumer> - { ( registry ) => ( - <ComponentWithSelect - ownProps={ ownProps } - registry={ registry } - /> + <AsyncModeConsumer> + { ( isAsync ) => ( + <RegistryConsumer> + { ( registry ) => ( + <ComponentWithSelect + ownProps={ ownProps } + registry={ registry } + isAsync={ isAsync } + /> + ) } + </RegistryConsumer> ) } - </RegistryConsumer> + </AsyncModeConsumer> ); }, 'withSelect' ); diff --git a/packages/data/src/index.js b/packages/data/src/index.js index e1b9a5fcb1e55..6574f002d47e4 100644 --- a/packages/data/src/index.js +++ b/packages/data/src/index.js @@ -12,6 +12,7 @@ import * as plugins from './plugins'; export { default as withSelect } from './components/with-select'; export { default as withDispatch } from './components/with-dispatch'; export { default as RegistryProvider, RegistryConsumer } from './components/registry-provider'; +export { default as __experimentalAsyncModeProvider } from './components/async-mode-provider'; export { createRegistry } from './registry'; export { plugins }; diff --git a/packages/e2e-tests/specs/plugins/meta-attribute-block.test.js b/packages/e2e-tests/specs/plugins/meta-attribute-block.test.js index 905807501774f..45c8256159979 100644 --- a/packages/e2e-tests/specs/plugins/meta-attribute-block.test.js +++ b/packages/e2e-tests/specs/plugins/meta-attribute-block.test.js @@ -40,9 +40,14 @@ describe( 'Block with a meta attribute', () => { await insertBlock( 'Test Meta Attribute Block' ); await page.keyboard.type( 'Meta Value' ); - const persistedValues = await page.evaluate( () => Array.from( document.querySelectorAll( '.my-meta-input' ) ).map( ( input ) => input.value ) ); - persistedValues.forEach( ( val ) => { - expect( val ).toBe( 'Meta Value' ); + const inputs = await page.$$( '.my-meta-input' ); + await inputs.forEach( async ( input ) => { + // Clicking the input selects the block, + // and selecting the block enables the sync data mode + // as otherwise the asynchronous rerendering of unselected blocks + // may cause the input to have not yet been updated for the other blocks + await input.click(); + expect( await input.getProperty( 'value' ) ).toBe( 'Meta Value' ); } ); } ); } ); diff --git a/packages/editor/src/components/block-list/index.js b/packages/editor/src/components/block-list/index.js index b8e7e2926225f..7e5a1e7eedfb1 100644 --- a/packages/editor/src/components/block-list/index.js +++ b/packages/editor/src/components/block-list/index.js @@ -14,7 +14,11 @@ import { * WordPress dependencies */ import { Component } from '@wordpress/element'; -import { withSelect, withDispatch } from '@wordpress/data'; +import { + withSelect, + withDispatch, + __experimentalAsyncModeProvider as AsyncModeProvider, +} from '@wordpress/data'; import { compose } from '@wordpress/compose'; /** @@ -24,6 +28,13 @@ import BlockListBlock from './block'; import BlockListAppender from '../block-list-appender'; import { getBlockDOMNode } from '../../utils/dom'; +const forceSyncUpdates = ( WrappedComponent ) => ( props ) => { + return ( + <AsyncModeProvider value={ false }> + <WrappedComponent { ...props } /> + </AsyncModeProvider> + ); +}; class BlockList extends Component { constructor( props ) { super( props ); @@ -182,23 +193,36 @@ class BlockList extends Component { blockClientIds, rootClientId, isDraggable, + selectedBlockClientId, + multiSelectedBlockClientIds, + hasMultiSelection, } = this.props; return ( <div className="editor-block-list__layout"> - { map( blockClientIds, ( clientId, blockIndex ) => ( - <BlockListBlock - key={ 'block-' + clientId } - index={ blockIndex } - clientId={ clientId } - blockRef={ this.setBlockRef } - onSelectionStart={ this.onSelectionStart } - rootClientId={ rootClientId } - isFirst={ blockIndex === 0 } - isLast={ blockIndex === blockClientIds.length - 1 } - isDraggable={ isDraggable } - /> - ) ) } + { map( blockClientIds, ( clientId, blockIndex ) => { + const isBlockInSelection = hasMultiSelection ? + multiSelectedBlockClientIds.includes( clientId ) : + selectedBlockClientId === clientId; + + return ( + <AsyncModeProvider + key={ 'block-' + clientId } + value={ ! isBlockInSelection } + > + <BlockListBlock + clientId={ clientId } + index={ blockIndex } + blockRef={ this.setBlockRef } + onSelectionStart={ this.onSelectionStart } + rootClientId={ rootClientId } + isFirst={ blockIndex === 0 } + isLast={ blockIndex === blockClientIds.length - 1 } + isDraggable={ isDraggable } + /> + </AsyncModeProvider> + ); + } ) } <BlockListAppender rootClientId={ rootClientId } /> </div> ); @@ -206,6 +230,10 @@ class BlockList extends Component { } export default compose( [ + // This component needs to always be synchronous + // as it's the one changing the async mode + // depending on the block selection. + forceSyncUpdates, withSelect( ( select, ownProps ) => { const { getBlockOrder, @@ -213,6 +241,9 @@ export default compose( [ isMultiSelecting, getMultiSelectedBlocksStartClientId, getMultiSelectedBlocksEndClientId, + getSelectedBlockClientId, + getMultiSelectedBlockClientIds, + hasMultiSelection, } = select( 'core/editor' ); const { rootClientId } = ownProps; @@ -222,6 +253,9 @@ export default compose( [ selectionEnd: getMultiSelectedBlocksEndClientId(), isSelectionEnabled: isSelectionEnabled(), isMultiSelecting: isMultiSelecting(), + selectedBlockClientId: getSelectedBlockClientId(), + multiSelectedBlockClientIds: getMultiSelectedBlockClientIds(), + hasMultiSelection: hasMultiSelection(), }; } ), withDispatch( ( dispatch ) => { diff --git a/packages/priority-queue/.npmrc b/packages/priority-queue/.npmrc new file mode 100644 index 0000000000000..43c97e719a5a8 --- /dev/null +++ b/packages/priority-queue/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/packages/priority-queue/CHANGELOG.md b/packages/priority-queue/CHANGELOG.md new file mode 100644 index 0000000000000..2297c60f404ea --- /dev/null +++ b/packages/priority-queue/CHANGELOG.md @@ -0,0 +1,3 @@ +### 1.0.0 (Unreleased) + +Initial release. diff --git a/packages/priority-queue/README.md b/packages/priority-queue/README.md new file mode 100644 index 0000000000000..2405c6fbd6c99 --- /dev/null +++ b/packages/priority-queue/README.md @@ -0,0 +1,32 @@ +# Priority Queue + +This module allows you to run a queue of callback while on the browser's idle time making sure the higher-priority work is performed first. + +## Installation + +Install the module + +```bash +npm install @wordpress/priority-queue --save +``` + +_This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. Learn more about it in [Babel docs](https://babeljs.io/docs/en/next/caveats). + +## Usage + +```js +import { createQueue } from '@wordpress/priority-queue'; + +const queue = createQueue(); + +// Context objects. +const ctx1 = {}; +const ctx2 = {}; + +// For a given context in the queue, only the last callback is executed. +queue.add( ctx1, () => console.log( 'This will be printed first' ) ); +queue.add( ctx2, () => console.log( 'This won\'t be printed' ) ); +queue.add( ctx2, () => console.log( 'This will be printed second' ) ); +``` + +<br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/priority-queue/package.json b/packages/priority-queue/package.json new file mode 100644 index 0000000000000..5b0ba8a400fb8 --- /dev/null +++ b/packages/priority-queue/package.json @@ -0,0 +1,29 @@ +{ + "name": "@wordpress/priority-queue", + "version": "1.0.0-alpha.0", + "description": "Generic browser priority queue.", + "author": "The WordPress Contributors", + "license": "GPL-2.0-or-later", + "keywords": [ + "wordpress", + "browser", + "async" + ], + "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/priority-queue/README.md", + "repository": { + "type": "git", + "url": "https://github.com/WordPress/gutenberg.git" + }, + "bugs": { + "url": "https://github.com/WordPress/gutenberg/issues" + }, + "main": "build/index.js", + "module": "build-module/index.js", + "react-native": "src/index", + "dependencies": { + "@babel/runtime": "^7.0.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/priority-queue/src/index.js b/packages/priority-queue/src/index.js new file mode 100644 index 0000000000000..5934fe74875ef --- /dev/null +++ b/packages/priority-queue/src/index.js @@ -0,0 +1,50 @@ +const requestIdleCallback = window.requestIdleCallback ? window.requestIdleCallback : window.requestAnimationFrame; + +export const createQueue = () => { + const waitingList = []; + const elementsMap = new WeakMap(); + let isRunning = false; + + const runWaitingList = ( deadline ) => { + do { + if ( waitingList.length === 0 ) { + isRunning = false; + return; + } + + const nextElement = waitingList.shift(); + elementsMap.get( nextElement )(); + elementsMap.delete( nextElement ); + } while ( deadline && deadline.timeRemaining && deadline.timeRemaining() > 0 ); + + requestIdleCallback( runWaitingList ); + }; + + const add = ( element, item ) => { + if ( ! elementsMap.has( element ) ) { + waitingList.push( element ); + } + elementsMap.set( element, item ); + if ( ! isRunning ) { + isRunning = true; + requestIdleCallback( runWaitingList ); + } + }; + + const flush = ( element ) => { + if ( ! elementsMap.has( element ) ) { + return false; + } + + elementsMap.delete( element ); + const index = waitingList.indexOf( element ); + waitingList.splice( index, 1 ); + + return true; + }; + + return { + add, + flush, + }; +}; From 42ae8713fba012b36805f2e1f176430e2422d5dc Mon Sep 17 00:00:00 2001 From: etoledom <etoledom@icloud.com> Date: Wed, 16 Jan 2019 15:24:52 +0100 Subject: [PATCH 157/691] [Mobile] Image height (#13096) * [rnmobile]: Convert image-block into class * [rnmobile]: Creating mobile version of `image-size`. * [rnmobile]: Adding util to image-block to share math between image-size components. * [rnmobile]: Adding newline at the end of image-size.native.js file * [rnmobile]: Replace wrong tabs characters * [rnmobile]: Clear image size when url changes. * [rnmobile]: Fixed lint issues * [rnmobile]: Removing unnecessary binding calls * [rnmobile]: Added necessary this.onLayout.bind( this ) * [rnmobile]: Adding missing bind to `onMediaLibraryPress`. --- .../block-library/src/image/edit.native.js | 129 +++++++++++------- .../block-library/src/image/image-size.js | 11 +- .../src/image/image-size.native.js | 87 ++++++++++++ packages/block-library/src/image/utils.js | 9 ++ 4 files changed, 185 insertions(+), 51 deletions(-) create mode 100644 packages/block-library/src/image/image-size.native.js create mode 100644 packages/block-library/src/image/utils.js diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index 5b4132e19a2b2..476dea2981b15 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -9,67 +9,104 @@ import RNReactNativeGutenbergBridge from 'react-native-gutenberg-bridge'; */ import { MediaPlaceholder, RichText, BlockControls } from '@wordpress/editor'; import { Toolbar, ToolbarButton } from '@wordpress/components'; +import { Component } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; +import ImageSize from './image-size'; -export default function ImageEdit( props ) { - const { attributes, isSelected, setAttributes } = props; - const { url, caption } = attributes; +class ImageEdit extends Component { + constructor() { + super( ...arguments ); + this.onMediaLibraryPress = this.onMediaLibraryPress.bind( this ); + } - const onUploadPress = () => { + onUploadPress() { // This method should present an image picker from // the device. //TODO: Implement upload image method. - }; + } - const onMediaLibraryPress = () => { + onMediaLibraryPress() { RNReactNativeGutenbergBridge.onMediaLibraryPress( ( mediaUrl ) => { if ( mediaUrl ) { - setAttributes( { url: mediaUrl } ); + this.props.setAttributes( { url: mediaUrl } ); } } ); - }; + } - if ( ! url ) { + toolbarEditButton() { return ( - <MediaPlaceholder - onUploadPress={ onUploadPress } - onMediaLibraryPress={ onMediaLibraryPress } - /> + <Toolbar> + <ToolbarButton + className="components-toolbar__control" + label={ __( 'Edit image' ) } + icon="edit" + onClick={ this.onMediaLibraryPress } + /> + </Toolbar> ); } - const toolbarEditButton = ( - <Toolbar> - <ToolbarButton - className="components-toolbar__control" - label={ __( 'Edit image' ) } - icon="edit" - onClick={ onMediaLibraryPress } - /> - </Toolbar> - ); + render() { + const { attributes, isSelected, setAttributes } = this.props; + const { url, caption, height, width } = attributes; + + if ( ! url ) { + return ( + <MediaPlaceholder + onUploadPress={ this.onUploadPress } + onMediaLibraryPress={ this.onMediaLibraryPress } + /> + ); + } + + return ( + <View style={ { flex: 1 } }> + <BlockControls> + { this.toolbarEditButton() } + </BlockControls> + <ImageSize src={ url } > + { ( sizes ) => { + const { + imageWidthWithinContainer, + imageHeightWithinContainer, + } = sizes; - return ( - <View style={ { flex: 1 } }> - <BlockControls> - { toolbarEditButton } - </BlockControls> - <Image - style={ { width: '100%', height: 200 } } - resizeMethod="scale" - source={ { uri: url } } - /> - { ( ! RichText.isEmpty( caption ) > 0 || isSelected ) && ( - <View style={ { padding: 12, flex: 1 } }> - <TextInput - style={ { textAlign: 'center' } } - underlineColorAndroid="transparent" - value={ caption } - placeholder={ 'Write caption…' } - onChangeText={ ( newCaption ) => setAttributes( { caption: newCaption } ) } - /> - </View> - ) } - </View> - ); + let finalHeight = imageHeightWithinContainer; + if ( height > 0 && height < imageHeightWithinContainer ) { + finalHeight = height; + } + + let finalWidth = imageWidthWithinContainer; + if ( width > 0 && width < imageWidthWithinContainer ) { + finalWidth = width; + } + + return ( + <View style={ { flex: 1 } } > + <Image + style={ { width: finalWidth, height: finalHeight } } + resizeMethod="scale" + source={ { uri: url } } + key={ url } + /> + </View> + ); + } } + </ImageSize> + { ( ! RichText.isEmpty( caption ) > 0 || isSelected ) && ( + <View style={ { padding: 12, flex: 1 } }> + <TextInput + style={ { textAlign: 'center' } } + underlineColorAndroid="transparent" + value={ caption } + placeholder={ 'Write caption…' } + onChangeText={ ( newCaption ) => setAttributes( { caption: newCaption } ) } + /> + </View> + ) } + </View> + ); + } } + +export default ImageEdit; diff --git a/packages/block-library/src/image/image-size.js b/packages/block-library/src/image/image-size.js index 0a1b88aed12d7..bc7ebaeaa59b4 100644 --- a/packages/block-library/src/image/image-size.js +++ b/packages/block-library/src/image/image-size.js @@ -9,6 +9,11 @@ import { noop } from 'lodash'; import { withGlobalEvents } from '@wordpress/compose'; import { Component } from '@wordpress/element'; +/** + * Internal dependencies + */ +import { calculatePreferedImageSize } from './utils'; + class ImageSize extends Component { constructor() { super( ...arguments ); @@ -55,11 +60,7 @@ class ImageSize extends Component { } calculateSize() { - const maxWidth = this.container.clientWidth; - const exceedMaxWidth = this.image.width > maxWidth; - const ratio = this.image.height / this.image.width; - const width = exceedMaxWidth ? maxWidth : this.image.width; - const height = exceedMaxWidth ? maxWidth * ratio : this.image.height; + const { width, height } = calculatePreferedImageSize( this.image, this.container ); this.setState( { width, height } ); } diff --git a/packages/block-library/src/image/image-size.native.js b/packages/block-library/src/image/image-size.native.js new file mode 100644 index 0000000000000..8b29b9607e81e --- /dev/null +++ b/packages/block-library/src/image/image-size.native.js @@ -0,0 +1,87 @@ +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; + +/** +* External dependencies +*/ +import { View, Image } from 'react-native'; + +/** + * Internal dependencies + */ +import { calculatePreferedImageSize } from './utils'; + +class ImageSize extends Component { + constructor() { + super( ...arguments ); + this.state = { + width: undefined, + height: undefined, + }; + this.onLayout = this.onLayout.bind( this ); + } + + componentDidUpdate( prevProps ) { + if ( this.props.src !== prevProps.src ) { + this.image = {}; + + this.setState( { + width: undefined, + height: undefined, + } ); + this.fetchImageSize(); + } + + if ( this.props.dirtynessTrigger !== prevProps.dirtynessTrigger ) { + this.calculateSize(); + } + } + + componentDidMount() { + this.fetchImageSize(); + } + + fetchImageSize() { + Image.getSize( this.props.src, ( width, height ) => { + this.image = { width, height }; + this.calculateSize(); + } ); + } + + calculateSize() { + if ( this.image === undefined || this.container === undefined ) { + return; + } + const { width, height } = calculatePreferedImageSize( this.image, this.container ); + this.setState( { width, height } ); + } + + onLayout( event ) { + const { width, height } = event.nativeEvent.layout; + this.container = { + clientWidth: width, + clientHeight: height, + }; + this.calculateSize(); + } + + render() { + const sizes = { + imageWidth: this.image && this.image.width, + imageHeight: this.image && this.image.height, + containerWidth: this.container && this.container.width, + containerHeight: this.container && this.container.height, + imageWidthWithinContainer: this.state.width, + imageHeightWithinContainer: this.state.height, + }; + return ( + <View onLayout={ this.onLayout }> + { this.props.children( sizes ) } + </View> + ); + } +} + +export default ImageSize; diff --git a/packages/block-library/src/image/utils.js b/packages/block-library/src/image/utils.js new file mode 100644 index 0000000000000..32bdb9d71564c --- /dev/null +++ b/packages/block-library/src/image/utils.js @@ -0,0 +1,9 @@ + +export function calculatePreferedImageSize( image, container ) { + const maxWidth = container.clientWidth; + const exceedMaxWidth = image.width > maxWidth; + const ratio = image.height / image.width; + const width = exceedMaxWidth ? maxWidth : image.width; + const height = exceedMaxWidth ? maxWidth * ratio : image.height; + return { width, height }; +} From fbe0b25928db23a64ad610e6a43aacd7b7bd7488 Mon Sep 17 00:00:00 2001 From: Darren Ethier <darren@roughsmootheng.in> Date: Wed, 16 Jan 2019 09:56:03 -0500 Subject: [PATCH 158/691] Fix/unhandled promise rejection warning when returning null from action generator (#13314) Fix unhandled promise rejection warning when returning null from action generator Note: a couple other things introduced in this pull: - added `expect.hasAssertions()` to tests in `redux-routine/src/test/index.js` that had assertions happening inside a callback. This improves the test. - I added a test for catching the error but there's a known issue documented inline with the test. I'm not sure how much time we want to try resolving that issue (see the code). --- packages/redux-routine/CHANGELOG.md | 6 ++++++ packages/redux-routine/src/runtime.js | 4 ++-- packages/redux-routine/src/test/index.js | 27 ++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/packages/redux-routine/CHANGELOG.md b/packages/redux-routine/CHANGELOG.md index 7e64304c2f2c3..b820720f392bc 100644 --- a/packages/redux-routine/CHANGELOG.md +++ b/packages/redux-routine/CHANGELOG.md @@ -1,3 +1,9 @@ +## 3.0.4 (Unreleased) + +### Bug Fixes + +- Fix unhandled promise rejection error caused by returning null from registered generator ([#13314](https://github.com/WordPress/gutenberg/pull/13314) + ## 3.0.3 (2018-10-19) ## 3.0.2 (2018-10-18) diff --git a/packages/redux-routine/src/runtime.js b/packages/redux-routine/src/runtime.js index 93bea8801c1fa..484d65ac6c035 100644 --- a/packages/redux-routine/src/runtime.js +++ b/packages/redux-routine/src/runtime.js @@ -2,7 +2,7 @@ * External dependencies */ import { create } from 'rungen'; -import { map, isString } from 'lodash'; +import { map } from 'lodash'; import isPromise from 'is-promise'; /** @@ -51,7 +51,7 @@ export default function createRuntime( controls = {}, dispatch ) { return ( action ) => new Promise( ( resolve, reject ) => rungenRuntime( action, ( result ) => { - if ( typeof result === 'object' && isString( result.type ) ) { + if ( isAction( result ) ) { dispatch( result ); } resolve( result ); diff --git a/packages/redux-routine/src/test/index.js b/packages/redux-routine/src/test/index.js index 13019f8aac3ef..98aedf7bbef47 100644 --- a/packages/redux-routine/src/test/index.js +++ b/packages/redux-routine/src/test/index.js @@ -50,6 +50,7 @@ describe( 'createMiddleware', () => { } ); it( 'should throw if promise rejects', async () => { + expect.hasAssertions(); const middleware = createMiddleware( { WAIT_FAIL: () => new Promise( ( resolve, reject ) => reject( 'Message' ) @@ -68,6 +69,7 @@ describe( 'createMiddleware', () => { } ); it( 'should throw if promise throws', () => { + expect.hasAssertions(); const middleware = createMiddleware( { WAIT_FAIL: () => new Promise( () => { throw new Error( 'Message' ); @@ -85,6 +87,31 @@ describe( 'createMiddleware', () => { return store.dispatch( createAction() ); } ); + // Currently this test will not error even under conditions producing it but + // instead will have an uncaught error/warning printed in the cli console: + // - (node:37109) UnhandledPromiseRejectionWarning: TypeError: Cannot read + // property 'type' of null (and others) + // See this github thread for context: + // https://github.com/facebook/jest/issues/3251 + it( 'should handle a null returned from a caught promise error', () => { + expect.hasAssertions(); + const middleware = createMiddleware( { + WAIT_FAIL: () => new Promise( () => { + throw new Error( 'Message' ); + } ), + } ); + const store = createStoreWithMiddleware( middleware ); + function* createAction() { + try { + yield { type: 'WAIT_FAIL' }; + } catch ( error ) { + expect( error.message ).toBe( 'Message' ); + return null; + } + } + store.dispatch( createAction() ); + } ); + it( 'assigns sync controlled return value into yield assignment', () => { const middleware = createMiddleware( { RETURN_TWO: () => 2, From 626d618c268355cb07ef0da61227f14a49c83e84 Mon Sep 17 00:00:00 2001 From: Andrea Fercia <a.fercia@gmail.com> Date: Wed, 16 Jan 2019 16:26:18 +0100 Subject: [PATCH 159/691] Make Safari + VoiceOver announce the screen-reader-text within buttons in the correct order (#13221) * Add height auto to screen reader text within buttons. * Add reference links to the inline comment. --- packages/components/src/button/style.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/components/src/button/style.scss b/packages/components/src/button/style.scss index c8a688f40cbc3..0f49a9625519b 100644 --- a/packages/components/src/button/style.scss +++ b/packages/components/src/button/style.scss @@ -223,6 +223,12 @@ color: color(theme(outlines) shade(25%)); } } + + // Fixes a Safari+VoiceOver bug, where the screen reader text is announced not respecting the source order. + // See https://core.trac.wordpress.org/ticket/42006 and https://github.com/h5bp/html5-boilerplate/issues/1985 + .screen-reader-text { + height: auto; + } } @keyframes components-button__busy-animation { From 9cc139ed676813ee51301124d138210b49cef2ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Wed, 16 Jan 2019 17:07:35 +0100 Subject: [PATCH 160/691] Format API: add tutorial and document primitives (#13247) --- .../assets/toolbar-with-custom-button.png | Bin 0 -> 7677 bytes .../tutorials/format-api/1-register-format.md | 48 ++++++++++++++++++ .../tutorials/format-api/2-toolbar-button.md | 37 ++++++++++++++ .../tutorials/format-api/3-apply-format.md | 43 ++++++++++++++++ .../developers/tutorials/format-api/README.md | 13 +++++ .../developers/tutorials/readme.md | 2 + docs/manifest.json | 24 +++++++++ docs/toc.json | 5 ++ .../editor/src/components/rich-text/README.md | 45 ++++++++++++++++ packages/rich-text/README.md | 20 ++++++++ 10 files changed, 237 insertions(+) create mode 100644 docs/designers-developers/assets/toolbar-with-custom-button.png create mode 100644 docs/designers-developers/developers/tutorials/format-api/1-register-format.md create mode 100644 docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md create mode 100644 docs/designers-developers/developers/tutorials/format-api/3-apply-format.md create mode 100644 docs/designers-developers/developers/tutorials/format-api/README.md diff --git a/docs/designers-developers/assets/toolbar-with-custom-button.png b/docs/designers-developers/assets/toolbar-with-custom-button.png new file mode 100644 index 0000000000000000000000000000000000000000..3b22afaca318ac9a9c2e04d9c447cd4964d5a237 GIT binary patch literal 7677 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 S*BT2zgVdC@5oL-Gg8v1S*_#yr literal 0 HcmV?d00001 diff --git a/docs/designers-developers/developers/tutorials/format-api/1-register-format.md b/docs/designers-developers/developers/tutorials/format-api/1-register-format.md new file mode 100644 index 0000000000000..80d091d214ba6 --- /dev/null +++ b/docs/designers-developers/developers/tutorials/format-api/1-register-format.md @@ -0,0 +1,48 @@ +# Register a new format + +The first thing you're going to do in this tutorial is to register the new format that the plugin intends to apply. WordPress has the [`registerFormatType`](/packages/rich-text/README.md#registerFormatType) function to do so. + +Let's prepare a minimal plugin to make this work. Create a new folder and a file named `my-custom-format.php` within it containing the necessary PHP code to register and enqueue the JavaScript assets: + +```php +<?php +/** + * Plugin Name: My custom format + */ + +function my_custom_format_script_register() { + wp_register_script( + 'my-custom-format-js', + plugins_url( 'my-custom-format.js', __FILE__ ), + array( 'wp-rich-text' ) + ); +} +add_action( 'init', 'my_custom_format_script_register' ); + +function my_custom_format_enqueue_assets_editor() { + wp_enqueue_script( 'my-custom-format-js' ); +} +add_action( 'enqueue_block_editor_assets', 'my_custom_format_enqueue_assets_editor' ); +``` + +Then add a new file named `my-custom-format.js` with the following contents: + +```js +( function( wp ) { + wp.richText.registerFormatType( + 'my-custom-format/sample-output', { + title: 'Sample output', + tagName: 'samp', + className: null, + } + ); +} )( window.wp ); +``` + +Make that plugin available in your WordPress setup and activate it. Then, load a new page/post. + +The list of available format types is maintained in the `core/rich-text` store. You can query the store to check that your custom format is now avaliable. To do so, run this code in your browser's console: + + wp.data.select( 'core/rich-text' ).getFormatTypes(); + +It'll return an array containing the format types, including your own. diff --git a/docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md b/docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md new file mode 100644 index 0000000000000..19ae587159c76 --- /dev/null +++ b/docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md @@ -0,0 +1,37 @@ +# Add a button to the toolbar + +Now that the format is avaible, the next step is to surface it to the UI. You can make use of the [`RichTextToolbarButton`](/packages/editor/src/components/rich-text/README.md#RichTextToolbarButton) component to extend the format toolbar. + +Paste this code in `my-custom-format.js`: + +```js +( function( wp ) { + var MyCustomButton = function( props ) { + return wp.element.createElement( + wp.editor.RichTextToolbarButton, { + icon: 'editor-code', + title: 'Sample output', + onClick: function() { + console.log( 'toggle format' ); + }, + } + ); + } + wp.richText.registerFormatType( + 'my-custom-format/sample-output', { + title: 'Sample output', + tagName: 'samp', + className: null, + edit: MyCustomButton, + } + ); +} )( window.wp ); +``` + +**Important**: note that this code is using two new utilities (`wp.element.createElement`, and `wp.editor.RichTextToolbarButton`) so don't forget adding the corresponding `wp-element` and `wp-editor` packages to the dependencies array in the PHP file along with the existing `wp-rich-text`. + +Let's check that everything is working as expected. Reload the post/page and select a text block. Make sure that the new button was added to the format toolbar, it uses the [editor-code dashicon](https://developer.wordpress.org/resource/dashicons/#editor-code), and the hover text is what you set in the title: + +![Toolbar with custom button](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/assets/toolbar-with-custom-button.png) + +You may also want to check that upon clicking the button the `toggle format` message is shown in your browser's console. diff --git a/docs/designers-developers/developers/tutorials/format-api/3-apply-format.md b/docs/designers-developers/developers/tutorials/format-api/3-apply-format.md new file mode 100644 index 0000000000000..0fbcd031db5f2 --- /dev/null +++ b/docs/designers-developers/developers/tutorials/format-api/3-apply-format.md @@ -0,0 +1,43 @@ +# Apply the format when the button is clicked + +So far, your custom button doesn't modify the text selected, it only renders a message in the console. Let's change that. + +The [rich-text package](/packages/rich-text/README.md) offers a few utilities to work with formats: [applyFormat](/packages/rich-text/README.md#applyFormat), [removeFormat](/packages/rich-text/README.md#removeFormat), and [toggleFormat](/packages/rich-text/README.md#toggleFormat). In this particular example, the format you want to apply (the `<samp>` tag) may be considered binary - either a text selection has the tag or not. Taking that into account, the `toggleFormat` primitive seems more convenient. + +Update `my-custom-format.js` with this new code: + +```js +( function( wp ) { + var MyCustomButton = function( props ) { + return wp.element.createElement( + wp.editor.RichTextToolbarButton, { + icon: 'editor-code', + title: 'Sample output', + onClick: function() { + props.onChange( wp.richText.toggleFormat( + props.value, + { type: 'my-custom-format/sample-output' } + ) ); + }, + isActive: props.isActive, + } + ); + } + wp.richText.registerFormatType( + 'my-custom-format/sample-output', { + title: 'Sample output', + tagName: 'samp', + className: null, + edit: MyCustomButton, + } + ); +} )( window.wp ); +``` + +Now, let's check that is working as intended: reload the post/page, make a text selection, click the button, and then change to HTML view to confirm that the tag was effectively applied. + +The expected behavior is that the format will be toggled, meaning that the text selected will be wrapped by a `<samp>` tag if it isn't yet, or the tag will be removed if the selection is already wrapped with the tag. Notice that the button renders a different style depending on whether the selection has the tag or not as well - this is controlled by the `isActive` property of the `RichTextToolbarButton` component. + +Your browser may have already displayed the selection differently once the tag was applied, but you may want to use a special style of your own. You can use the `className` option in [`registerFormatType`](/packages/rich-text/README.md#registerFormatType) to target the new element by class name: if `className` is set, it'll be added to the new element. + +That's it. This is all that is necessary to make a custom format available in the new editor. From here, you may want to check out other [tutorials](/docs/designers-developers/developers/tutorials/) or apply your new knowledge to your next plugin! diff --git a/docs/designers-developers/developers/tutorials/format-api/README.md b/docs/designers-developers/developers/tutorials/format-api/README.md new file mode 100644 index 0000000000000..2f41423a3f92c --- /dev/null +++ b/docs/designers-developers/developers/tutorials/format-api/README.md @@ -0,0 +1,13 @@ +# Introduction to the Format API + +The purpose of this tutorial is to introduce you to the Format API. The Format API makes it possible for developers to add custom buttons to the formatting toolbar and have them apply a _format_ to a text selection. Bold is an example of a standard button in the formatting toolbar. + +In WordPress lingo, a _format_ is a [HTML tag with text-level semantics](https://www.w3.org/TR/html5/textlevel-semantics.html#text-level-semantics-usage-summary) used to give some special meaning to a text selection. For example, in this tutorial, the button to be hooked into the format toolbar will let users wrap a particular text selection with the [`<samp>` HTML tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/samp). + +If you are unfamiliar with how to work with WordPress plugins and JavaScript, you may want to check the [JavaScript Tutorial](/docs/designers-developers/developers/tutorials/javascript/readme.md) first. + +## Table of contents + +1. [Register a new format](/docs/designers-developers/developers/tutorials/format-api/1-register-format.md) +2. [Add a button to the toolbar](/docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md) +3. [Apply the format when the button is clicked](/docs/designers-developers/developers/tutorials/format-api/3-apply-format.md) diff --git a/docs/designers-developers/developers/tutorials/readme.md b/docs/designers-developers/developers/tutorials/readme.md index 4d80e195fb287..9151ec49150c2 100644 --- a/docs/designers-developers/developers/tutorials/readme.md +++ b/docs/designers-developers/developers/tutorials/readme.md @@ -7,3 +7,5 @@ * See the [Meta Boxes Tutorial](/docs/designers-developers/developers/tutorials/metabox/readme.md) for new ways of extending the editor storing and using post meta data. * The [Sidebar Tutorial](/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md) will walk you through the steps of creating a sidebar to update data from the `post_meta` table. + +* Learn how to add customized buttons to the toolbar with the [Format API tutorial](/docs/designers-developers/developers/tutorials/format-api/0-intro.md). diff --git a/docs/manifest.json b/docs/manifest.json index 2958928a39777..dfa85b7d12f34 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -317,6 +317,30 @@ "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md", "parent": "plugin-sidebar-0" }, + { + "title": "Introduction to the Format API", + "slug": "format-api", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/format-api/README.md", + "parent": "tutorials" + }, + { + "title": "Register a new format", + "slug": "1-register-format", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/format-api/1-register-format.md", + "parent": "format-api" + }, + { + "title": "Add a button to the toolbar", + "slug": "2-toolbar-button", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md", + "parent": "format-api" + }, + { + "title": "Apply the format when the button is clicked", + "slug": "3-apply-format", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/format-api/3-apply-format.md", + "parent": "format-api" + }, { "title": "Designer Documentation", "slug": "designers", diff --git a/docs/toc.json b/docs/toc.json index 37c24577f8041..ceb639a213cf9 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -59,6 +59,11 @@ {"docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-4-initialize-input.md": []}, {"docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-5-update-meta.md": []}, {"docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md": []} + ]}, + {"docs/designers-developers/developers/tutorials/format-api/README.md": [ + {"docs/designers-developers/developers/tutorials/format-api/1-register-format.md": []}, + {"docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md": []}, + {"docs/designers-developers/developers/tutorials/format-api/3-apply-format.md": []} ]} ]} ]}, diff --git a/packages/editor/src/components/rich-text/README.md b/packages/editor/src/components/rich-text/README.md index 19ddd35bd2666..4e543219177e4 100644 --- a/packages/editor/src/components/rich-text/README.md +++ b/packages/editor/src/components/rich-text/README.md @@ -122,3 +122,48 @@ registerBlockType( /* ... */, { } ); ``` {% end %} + +## RichTextToolbarButton + +Slot to extend the format toolbar. Use it in the edit function of a `registerFormatType` call to surface the format to the UI. + +### Example + +{% codetabs %} +{% ES5 %} +```js +wp.richText.registerFormatType( /* ... */, { + /* ... */ + edit: function( props ) { + return wp.element.createElement( + wp.editor.RichTextToolbarButton, { + icon: 'editor-code', + title: 'My formatting button', + onClick: function() { /* ... */ } + isActive: props.isActive, + } ); + }, + /* ... */ +} ); +``` +{% ESNext %} +```js +import { registerFormatType } from 'wp-rich-text'; +import { richTextToolbarButton } from 'wp-editor'; + +registerFormatType( /* ... */, { + /* ... */ + edit( { isActive } ) { + return ( + <RichTextToolbarButton + icon={ 'editor-code' } + title={ 'My formatting button' } + onClick={ /* ... */ } + isActive={ isActive } + /> + ); + }, + /* ... */ +} ); +``` +{% end %} diff --git a/packages/rich-text/README.md b/packages/rich-text/README.md index 1b8f714627415..c769039253f9c 100644 --- a/packages/rich-text/README.md +++ b/packages/rich-text/README.md @@ -91,6 +91,13 @@ removeFormat( value: Object, formatType: string, ?startIndex: number, ?endIndex: Remove any format object from a Rich Text value by type from the given `startIndex` to the given `endIndex`. Indices are retrieved from the selection if none are provided. +### toggleFormat + +```js +toggleFormat( value: Object, format: Object ): Object +``` + +Toggles a format object to a Rich Text value at the current selection, and returns a new value with the format applied or removed. ### getActiveFormat @@ -132,6 +139,19 @@ insert( value: Object, valueToInsert: Object | string, ?startIndex: number, ?end Insert a Rich Text value, an HTML string, or a plain text string, into a Rich Text value at the given `startIndex`. Any content between `startIndex` and `endIndex` will be removed. Indices are retrieved from the selection if none are provided. +### registerFormatType + +```js +registerFormatType( name: String, settings: Object ): ?WPformat +``` + +Registers a new format provided a unique name and an object defining its behavior. Settings object: + +- `tagName`: String. The HTML tag this format will wrap the selection with. +- `className`: String || null. A class to match the format. +- `title`: String. Name of the format. +- `edit`: function. Should return a component for the user to interact with the new registered format. + ### remove ```js From b8890a47a05d3ec94117b6d87d95eb4f2b78291e Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell@kjellr.com> Date: Wed, 16 Jan 2019 12:02:08 -0500 Subject: [PATCH 161/691] Update TabPanel readme (#13317) * Update TabPanel readme Adding documentation to describe the use and functionality of the TabPanel component. Thanks @melchoyce and @alexislloyd for drafting this. * Remove extra space. * Decrease headling levels. The page title should be the only H1, so everything else needs to get pushed down one level. --- packages/components/src/tab-panel/README.md | 70 +++++++++++++++++---- 1 file changed, 58 insertions(+), 12 deletions(-) diff --git a/packages/components/src/tab-panel/README.md b/packages/components/src/tab-panel/README.md index 1e9eb275d26f9..89c6b09572250 100644 --- a/packages/components/src/tab-panel/README.md +++ b/packages/components/src/tab-panel/README.md @@ -1,12 +1,58 @@ # TabPanel -TabPanel is a React component to render an ARIA-compliant TabPanel. It has two sections: a list of tabs, and the view to show when tabs are chosen. When the list of tabs gets focused, the active tab gets focus (the first tab if there isn't one already). Use the arrow keys to navigate between tabs AND select the newly focused tab at the same time. +TabPanel is a React component to render an ARIA-compliant TabPanel. -TabPanel is a Function-as-Children component. The function takes the active tab object as as an argument. +TabPanels organize content across different screens, data sets, and interactions. It has two sections: a list of tabs, and the view to show when tabs are chosen. -## Usage +![The “Document” tab selected in the sidebar TabPanel.](https://wordpress.org/gutenberg/files/2019/01/s_E36D9C9B8FFA15A1A8CE224E422535A12B016F88884089575F9998E52016A49F_1541785098230_TabPanel.png) -Renders a TabPanel with each tab representing a paragraph with its title. +## Table of contents + +1. Design guidelines +2. Development guidelines + +## Design guidelines + +### Usage + +TabPanels organize and allow navigation between groups of content that are related and at the same level of hierarchy. + +#### Tabs in a set +As a set, all tabs are unified by a shared topic. For clarity, each tab should contain content that is distinct from all the other tabs in a set. + +### Anatomy + +![](https://wordpress.org/gutenberg/files/2019/01/s_E36D9C9B8FFA15A1A8CE224E422535A12B016F88884089575F9998E52016A49F_1541787297310_TabPanelAnatomy.png) + +1. Container +2. Active text label +3. Active tab indicator +4. Inactive text label +5. Tab item + +#### Labels + +Tab labels appear in a single row, in the same typeface and size. Use text labels that clearly and succinctly describe the content of a tab, and make sure that a set of tabs contains a cohesive group of items that share a common characteristic. + +Tab labels can wrap to a second line, but do not add a second row of tabs. + +#### Active tab indicators + +To differentiate an active tab from an inactive tab, apply an underline and color change to the active tab’s text and icon. + +![An underline and color change differentiate an active tab from the inactive ones.](https://wordpress.org/gutenberg/files/2019/01/s_E36D9C9B8FFA15A1A8CE224E422535A12B016F88884089575F9998E52016A49F_1541787691601_TabPanelActiveTab.png) + +### Behavior + +Users can navigate between tabs by tapping the tab key on the keyboard. + +### Placement + +Place tabs above content. Tabs control the UI region displayed below them. + +## Development guidelines + +### Usage ```jsx import { TabPanel } from '@wordpress/components'; @@ -38,11 +84,11 @@ const MyTabPanel = () => ( ); ``` -## Props +### Props The component accepts the following props: -### className +#### className The class to give to the outer container for the TabPanel @@ -50,7 +96,7 @@ The class to give to the outer container for the TabPanel - Required: No - Default: '' -### orientation +#### orientation The orientation of the tablist (`vertical` or `horizontal`) @@ -58,7 +104,7 @@ The orientation of the tablist (`vertical` or `horizontal`) - Required: No - Default: `horizontal` -### onSelect +#### onSelect The function called when a tab has been selected. It is passed the `tabName` as an argument. @@ -66,7 +112,7 @@ The function called when a tab has been selected. It is passed the `tabName` as - Required: No - Default: `noop` -### tabs +#### tabs A list of tabs where each tab is defined by an object with the following fields: @@ -79,7 +125,7 @@ Other fields may be added to the object and accessed from the child function if - Type: Array - Required: Yes -### activeClass +#### activeClass The class to add to the active tab @@ -87,7 +133,7 @@ The class to add to the active tab - Required: No - Default: `is-active` -### initialTabName +#### initialTabName Optionally provide a tab name for a tab to be selected upon mounting of component. If this prop is not set, the first tab will be selected by default. @@ -95,7 +141,7 @@ Optionally provide a tab name for a tab to be selected upon mounting of componen - Required: No - Default: none -### children +#### children A function which renders the tabviews given the selected tab. The function is passed the active tab object as an argument as defined the the tabs prop. The element to which the tooltip should anchor. From cda45efe45292f9440b27233ce8ab4f9ff6d7b01 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell@kjellr.com> Date: Wed, 16 Jan 2019 12:02:25 -0500 Subject: [PATCH 162/691] Update Toolbar component readme (#13316) * UpdateToolbar component readme Adding documentation to describe the use and functionality of the Toolbar component. Thanks @cburton4 and @alexislloyd for drafting this. * Update heading levels There should only be one H1: the page title. Adjusting the rest of the levels to match. * Switch from bold text to headings. --- packages/components/src/toolbar/README.md | 41 ++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/packages/components/src/toolbar/README.md b/packages/components/src/toolbar/README.md index 968d8909fa6bc..7458cb2e00936 100644 --- a/packages/components/src/toolbar/README.md +++ b/packages/components/src/toolbar/README.md @@ -1,6 +1,45 @@ # Toolbar -## Usage +Toolbar can be used to group related options. To emphasize groups of related icon buttons, a toolbar should share a common container. + +![Toolbar component above an image block](https://wordpress.org/gutenberg/files/2019/01/s_96EC471FE9C9D91A996770229947AAB54A03351BDE98F444FD3C1BF0CED365EA_1541782974545_ButtonGroup.png) + +## Table of contents + +1. [Design guidelines](#design-guidelines) +2. [Development guidelines](#development-guidelines) + +## Design guidelines + +### Usage + +#### Selected action + +Only one option in a toolbar can be selected and active at a time. Selecting one option deselects any other. + +### Best practices + +Toolbars should: + +- **Clearly communicate that clicking or tapping will trigger an action.** +- **Use established colors appropriately.** For example, only use red for actions that are difficult or impossible to undo. +- When used with a block, have a consistent location above the block. **Otherwise, have a consistent location in the interface.** + +![A toolbar attached to the top left side of a block. (1. Toolbar, 2. Block)](https://wordpress.org/gutenberg/files/2019/01/s_D8D19E5A314C2D056B8CCC92B2DB5E27164936A0C5ED98A4C2DFDA650BE2A771_1542388042335_toolbar-block.png) + +### States + +#### Active and available toolbars +A toolbar’s state makes it clear which icon button is active. Hover and focus states express the available selection options for icon buttons in a toolbar. + +![Toolbar component](https://wordpress.org/gutenberg/files/2019/01/s_96EC471FE9C9D91A996770229947AAB54A03351BDE98F444FD3C1BF0CED365EA_1541784539545_ButtonGroup.png) + +#### Disabled toolbars +Toolbars that cannot be selected can either be given a disabled state, or be hidden. + +## Development guidelines + +### Usage ```jsx import { Toolbar } from '@wordpress/components'; From 18a6cc1d791086481940702e81f769bcc1b8d7bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Wed, 16 Jan 2019 18:44:18 +0100 Subject: [PATCH 163/691] Fix link to Format API tutorial (#13344) --- docs/designers-developers/developers/tutorials/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/designers-developers/developers/tutorials/readme.md b/docs/designers-developers/developers/tutorials/readme.md index 9151ec49150c2..f63254cd3fbb5 100644 --- a/docs/designers-developers/developers/tutorials/readme.md +++ b/docs/designers-developers/developers/tutorials/readme.md @@ -8,4 +8,4 @@ * The [Sidebar Tutorial](/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md) will walk you through the steps of creating a sidebar to update data from the `post_meta` table. -* Learn how to add customized buttons to the toolbar with the [Format API tutorial](/docs/designers-developers/developers/tutorials/format-api/0-intro.md). +* Learn how to add customized buttons to the toolbar with the [Format API tutorial](/docs/designers-developers/developers/tutorials/format-api/). From 5575b90d076e96bf7aa2cde224aefe165fbd7d16 Mon Sep 17 00:00:00 2001 From: Mel Choyce <melchoyce@users.noreply.github.com> Date: Wed, 16 Jan 2019 16:28:11 -0500 Subject: [PATCH 164/691] Documentation: Update heading levels in SelectControl and TextareaControl (#13334) * Update bold to use h3/h4 where appropriate * Remove inline bolding from headings --- .../components/src/select-control/README.md | 16 ++++++------- .../components/src/textarea-control/README.md | 23 ++++++++++--------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/packages/components/src/select-control/README.md b/packages/components/src/select-control/README.md index 59e5bd5e40e61..31c8dc5132a48 100644 --- a/packages/components/src/select-control/README.md +++ b/packages/components/src/select-control/README.md @@ -14,7 +14,7 @@ SelectControl allow users to select from a single-option menu. It functions as a ### Usage -**When to use** **a select control** +#### When to use a select control Use a select control when: @@ -39,13 +39,13 @@ Use selects for binary questions. A SelectControl includes a double-arrow indicator. The menu appears layered over the select. -**Opening and Closing** +#### Opening and Closing Once the menu is displayed onscreen, it remains open until the user chooses a menu item, clicks outside of the menu, or switches to another browser tab. ### Content Guidelines -**Labels** +#### Labels Label the SelectControl with a text label above it, or to its left, using sentence capitalization. Clicking the label allows the user to focus directly on the select. @@ -121,25 +121,25 @@ Render a user interface to select multiple users from a list. - One important prop to refer is `value`. If `multiple` is `true`, `value` should be an array with the values of the selected options. - If `multiple` is `false`, `value` should be equal to the value of the selected option. -**label** +#### label If this property is added, a label will be generated using label property as the content. - Type: `String` - Required: No -**help** +#### help If this property is added, a help text will be generated using help property as the content. - Type: `String` - Required: No -**multiple** +#### multiple If this property is added, multiple values can be selected. The value passed should be an array. - Type: `Boolean` - Required: No -**options** +#### options An array of objects containing the following properties: - `label`: (string) The label to be shown to the user. @@ -147,7 +147,7 @@ An array of objects containing the following properties: - Type: `Array` - Required: No -**onChange** +#### onChange A function that receives the value of the new option that is being selected as input. If multiple is true the value received is an array of the selected value. diff --git a/packages/components/src/textarea-control/README.md b/packages/components/src/textarea-control/README.md index f68c3103c04f0..672d13c5b6b1c 100644 --- a/packages/components/src/textarea-control/README.md +++ b/packages/components/src/textarea-control/README.md @@ -14,7 +14,7 @@ TextareaControls are TextControls that allow for multiple lines of text, and wra ### Usage -**When to use TextareaControl** +#### When to use TextareaControl Use TextareaControl when you need to encourage users enter an amount of text that’s longer than a single line. (A bigger box can encourage people to be more verbose, where a smaller one encourages them to be succinct.) @@ -25,7 +25,7 @@ TextareaControl should: - Make it easy to understand and address any errors via clear and direct error notices. - Make it easy to understand the requested information by using a clear and descriptive label. -**When not to use TextareaControl** +#### When not to use TextareaControl Do not use TextareaControl if you need to let users enter shorter answers (no longer than a single line), such as a phone number or name. In this case, you should use `Text Control`. @@ -48,7 +48,7 @@ Use TextareaControl for shorter answers. 1. Container 2. Label -**Containers** +### Containers Containers improve the discoverability of text fields by creating contrast between the text field and surrounding content. @@ -62,11 +62,11 @@ Use a stroke around the container, which clearly indicates that users can input **Don’t** Use unclear visual markers to indicate a text field. -**Label text** +### Label text Label text is used to inform users as to what information is requested for a text field. Every text field should have a label. Label text should be above the input field, and always visible. Write labels in sentence capitalization. -**Error text** +### Error text When text input isn’t accepted, an error message can display instructions on how to fix it. Error messages are displayed below the input line, replacing helper text until fixed. @@ -97,21 +97,21 @@ The set of props accepted by the component will be specified below. Props not included in this set will be applied to the textarea element. -**label** +#### label If this property is added, a label will be generated using label property as the content. - Type: `String` - Required: No -**help** +#### help If this property is added, a help text will be generated using help property as the content. - Type: `String` - Required: No -**rows** +#### rows The number of rows the textarea should contain. Defaults to four. @@ -119,19 +119,20 @@ The number of rows the textarea should contain. Defaults to four. - Required: No - Default: 4 -**value** +#### value The current value of the textarea. - Type: `String` - Required: Yes -**onChange** +#### onChange A function that receives the new value of the textarea each time it changes. - Type: `function` - Required: Yes -# Related components + +## Related components - For a field where users only enter one line of text, use the `TextControl` component. From 480e47742b419e8cddbcc49556b1783c14da7051 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Thu, 17 Jan 2019 06:55:27 -0800 Subject: [PATCH 165/691] Add link to meta block tutorial (#13298) * Add link to meta block tutorial The documentation suggests that meta data be stored using blocks, this PR adds a link to the tutorial showing how to do so. * Update docs/designers-developers/developers/backward-compatibility/meta-box.md Co-Authored-By: mkaz <marcus@mkaz.com> --- .../developers/backward-compatibility/meta-box.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/designers-developers/developers/backward-compatibility/meta-box.md b/docs/designers-developers/developers/backward-compatibility/meta-box.md index 5403ebf674ac3..e6268e6924ea9 100644 --- a/docs/designers-developers/developers/backward-compatibility/meta-box.md +++ b/docs/designers-developers/developers/backward-compatibility/meta-box.md @@ -1,6 +1,6 @@ # Meta Boxes -This is a brief document detailing how meta box support works in Gutenberg. With the superior developer and user experience of blocks, especially once block templates are available, **porting PHP meta boxes to blocks is highly encouraged!** +This is a brief document detailing how meta box support works in Gutenberg. With the superior developer and user experience of blocks, especially once block templates are available, **porting PHP meta boxes to blocks is highly encouraged!** See the [Meta Block tutorial](/docs/designers-developers/developers/tutorials/metabox/meta-block-1-intro.md) for how to store post meta data using blocks. ### Testing, Converting, and Maintaining Existing Meta Boxes From e38b060c347dc55ed9690f5ddbf628f6776c072f Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell@kjellr.com> Date: Thu, 17 Jan 2019 12:25:57 -0500 Subject: [PATCH 166/691] Update Block Design doc to include mention of the "Advanced" sidebar panel (#13348) * Update Block Design doc to make note of the Advanced sidebar panel. This panel is intended to house power user controls (like the "Additional CSS Class" field it ships with by default). The handbook should be clear about that, to promote that panel's usage. * Update docs/designers-developers/designers/block-design.md Break a long paragraph into multiple lines. Co-Authored-By: kjellr <kjell@kjellr.com> * Break "Advanced" panel description into its own paragraph. --- docs/designers-developers/designers/block-design.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/designers-developers/designers/block-design.md b/docs/designers-developers/designers/block-design.md index 8b267c0d754da..64eb736142c9d 100644 --- a/docs/designers-developers/designers/block-design.md +++ b/docs/designers-developers/designers/block-design.md @@ -17,7 +17,11 @@ Basic block settings won’t always make sense in the context of the placeholder ### The block sidebar should only be used for advanced, tertiary controls -The sidebar is not visible by default on a small / mobile screen, and may also be collapsed in a desktop view. Therefore, it should not be relied on for anything that is necessary for the basic operation of the block. Pick good defaults, make important actions available in the block toolbar, and think of the sidebar as something that only power users may discover. In addition, use sections and headers in the block sidebar if there are more than a handful of options, in order to allow users to easily scan and understand the options available. +The sidebar is not visible by default on a small / mobile screen, and may also be collapsed in a desktop view. Therefore, it should not be relied on for anything that is necessary for the basic operation of the block. Pick good defaults, make important actions available in the block toolbar, and think of the sidebar as something that most users should not need to open. + +In addition, use sections and headers in the block sidebar if there are more than a handful of options, in order to allow users to easily scan and understand the options available. + +Each block sidebar comes with an "Advanced" section by default. This area houses an "Additional CSS Class" field, and should be used to house other power user controls. ## Setup state vs. live preview state From df2fad3816d2d4a6a919c3037ef616f5328d0857 Mon Sep 17 00:00:00 2001 From: Pinar Olguc <pinarolguc@gmail.com> Date: Thu, 17 Jan 2019 20:31:43 +0300 Subject: [PATCH 167/691] Add onCaretVerticalPositionChange event (#13323) --- packages/block-library/src/heading/edit.native.js | 1 + packages/block-library/src/paragraph/edit.native.js | 1 + packages/editor/src/components/rich-text/index.native.js | 1 + 3 files changed, 3 insertions(+) diff --git a/packages/block-library/src/heading/edit.native.js b/packages/block-library/src/heading/edit.native.js index e613b9156b824..44289a550ce0b 100644 --- a/packages/block-library/src/heading/edit.native.js +++ b/packages/block-library/src/heading/edit.native.js @@ -58,6 +58,7 @@ class HeadingEdit extends Component { isSelected={ this.props.isSelected } onFocus={ this.props.onFocus } // always assign onFocus as a props onBlur={ this.props.onBlur } // always assign onBlur as a props + onCaretVerticalPositionChange={ this.props.onCaretVerticalPositionChange } style={ { minHeight: Math.max( minHeight, this.state.aztecHeight ), } } diff --git a/packages/block-library/src/paragraph/edit.native.js b/packages/block-library/src/paragraph/edit.native.js index ecb075bca8003..85183973dddea 100644 --- a/packages/block-library/src/paragraph/edit.native.js +++ b/packages/block-library/src/paragraph/edit.native.js @@ -94,6 +94,7 @@ class ParagraphEdit extends Component { isSelected={ this.props.isSelected } onFocus={ this.props.onFocus } // always assign onFocus as a props onBlur={ this.props.onBlur } // always assign onBlur as a props + onCaretVerticalPositionChange={ this.props.onCaretVerticalPositionChange } style={ { ...style, minHeight: Math.max( minHeight, this.state.aztecHeight ), diff --git a/packages/editor/src/components/rich-text/index.native.js b/packages/editor/src/components/rich-text/index.native.js index fd9d074472f5a..692a51f751810 100644 --- a/packages/editor/src/components/rich-text/index.native.js +++ b/packages/editor/src/components/rich-text/index.native.js @@ -379,6 +379,7 @@ export class RichText extends Component { onBackspace={ this.onBackspace } onContentSizeChange={ this.onContentSizeChange } onActiveFormatsChange={ this.onActiveFormatsChange } + onCaretVerticalPositionChange={ this.props.onCaretVerticalPositionChange } isSelected={ this.props.isSelected } blockType={ { tag: tagName } } color={ 'black' } From c2f1059b10ebb3c5457ae61adc05920af4729d58 Mon Sep 17 00:00:00 2001 From: Mel Choyce <melchoyce@users.noreply.github.com> Date: Thu, 17 Jan 2019 14:08:04 -0500 Subject: [PATCH 168/691] Update FormToggle readme (#13353) Updating documentation to describe the use and functionality of the TextareaControl component. Thanks to @drw158 and @sarahmonster for drafting this. --- packages/components/src/form-toggle/README.md | 94 +++++++++++++++++-- 1 file changed, 84 insertions(+), 10 deletions(-) diff --git a/packages/components/src/form-toggle/README.md b/packages/components/src/form-toggle/README.md index 0df538aee47f6..e2bfcefb07196 100644 --- a/packages/components/src/form-toggle/README.md +++ b/packages/components/src/form-toggle/README.md @@ -1,22 +1,57 @@ # FormToggle -The Form Toggle Control is a switch that should be **used when the effect is boolean and instant**. The Form Toggle Control is a complement to the Checkbox Control. +FormToggle switches a single setting on or off. -Use Form Toggle when: +![On and off FormToggles. The top toggle is on, while the bottom toggle is off.](https://wordpress.org/gutenberg/files/2019/01/Toggle.jpg) -- The effect of the switch is immediately visible to the user. For example: when applying a drop-cap to text, the actual drop-cap is immediately activated. -- There are only two states for the switch: on or off. +## Table of contents -Do **not** use: +1. [Design guidelines](#design-guidelines) +2. [Development guidelines](#development-guidelines) +3. [Related components](#related-components) -- When the control is part of a group of other related controls (multiple choice). -- When the effect of flipping the switch is not instantaneous. +## Design guidelines -When Form Toggle component is not appropriate, use the Checkbox Control. +### Usage -Note: it is recommended that you pair the switch control with contextual help text, for example `checked ? __( 'Thumbnails are cropped to align.' ) : __( 'Thumbnails are not cropped.' )`. +#### When to use toggles -## Usage +Use toggles when you want users to: + +- Switch a single option on or off. +- Immediately activate or deactivate something. + +![FormToggle used for a “fixed background” setting](https://wordpress.org/gutenberg/files/2019/01/Toggle-Do.jpg) + +**Do** +Use toggles to switch an option on or off. + +![Radio used for a “fixed background” setting](https://wordpress.org/gutenberg/files/2019/01/Toggle-Dont.jpg) + +**Don’t** +Don’t use radio buttons for settings that toggle on and off. + +Toggles are preferred when the user is not expecting to submit data, as is the case with checkboxes and radio buttons. + +#### State + +When the user slides a toggle thumb (1) to the other side of the track (2) and the state of the toggle changes, it’s been successfully toggled. + +![Diagram showing FormToggle states](https://wordpress.org/gutenberg/files/2019/01/Toggle-Diagram.jpg) + +#### Text label + +Toggles should have clear inline labels so users know exactly what option the toggle controls, and whether the option is enabled or disabled. + +Do not include any text (e.g. “on” or “off”) within the toggle element itself. The toggle alone should be sufficient to communicate the state. + +### Behavior + +When a user switches a toggle, its corresponding action takes effect immediately. + +## Development guidelines + +### Usage ```jsx import { FormToggle } from '@wordpress/components'; @@ -31,3 +66,42 @@ const MyFormToggle = withState( { /> ) ); ``` + +### Props + +The component accepts the following props: + +#### label + +If this property is added, a label will be generated using label property as the content. + +- Type: `String` +- Required: No + +#### help + +If this property is added, a help text will be generated using help property as the content. + +- Type: `String` | `Function` +- Required: No + +#### checked + +If checked is true the toggle will be checked. If checked is false the toggle will be unchecked. +If no value is passed the toggle will be unchecked. + +- Type: `Boolean` +- Required: No + +#### onChange + +A function that receives the checked state (boolean) as input. + +- Type: `function` +- Required: Yes + +## Related components + +- To select one option from a set, and you want to show them all the available options at once, use the `Radio` component. +- To select one or more items from a set, use the `CheckboxControl` component. + From 7aa56f3bd3f63e6eadc5d9147dcd8cc857ee80cc Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.p.richards@gmail.com> Date: Fri, 18 Jan 2019 11:32:34 +0800 Subject: [PATCH 169/691] Add jsdoc @deprecated tag to docs output (#13354) * Update doc generation to include @deprecated jsdoc tag on functions --- docs/tool/generator.js | 8 +++++++- docs/tool/parser.js | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/tool/generator.js b/docs/tool/generator.js index 1f7a0744dbf3e..a632fb40b5268 100644 --- a/docs/tool/generator.js +++ b/docs/tool/generator.js @@ -32,11 +32,17 @@ function generateTableOfContent( parsedNamespaces ) { */ function generateFunctionDocs( parsedFunc, generateDocsForReturn = true ) { return [ - `### ${ parsedFunc.name }`, + `### ${ parsedFunc.name }${ parsedFunc.deprecated ? ' (deprecated)' : '' }`, parsedFunc.description ? [ '', parsedFunc.description, ].join( '\n' ) : null, + parsedFunc.deprecated ? [ + '', + '*Deprecated*', + '', + `Deprecated ${ parsedFunc.deprecated.description }`, + ].join( '\n' ) : null, parsedFunc.params.length ? [ '', '*Parameters*', diff --git a/docs/tool/parser.js b/docs/tool/parser.js index 480e23ffc5702..41b02c87bb314 100644 --- a/docs/tool/parser.js +++ b/docs/tool/parser.js @@ -114,6 +114,7 @@ module.exports = function( config ) { const func = { name, description: docBlock.description, + deprecated: docBlock.tags.find( ( tag ) => tag.title === 'deprecated' ), params: docBlock.tags.filter( ( tag ) => tag.title === 'param' ), return: docBlock.tags.find( ( tag ) => tag.title === 'return' ), }; From 3111e48392b4d6d01a681bc149be7526bdb3e44a Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.p.richards@gmail.com> Date: Fri, 18 Jan 2019 16:34:35 +0800 Subject: [PATCH 170/691] Add background color control for table block (#10611) * Add background and text color controls for table block * Modify fallback styles to use first table row * Make text color control apply to all rows, not only striped row * Simplify implementation of colors for table block. - Remove text color control - Disable custom colors - Apply class names to table element instead of tr element - Remove JS striping * Filter colors in table background color control to only those that contrast with the default text color * Remove errant tab * Export using a single statement * Revert "Filter colors in table background color control to only those that contrast with the default text color" This reverts commit 8a39fbcf4ce4e6066a16c744c6e0a01936bc542f. * Fix issue with row element not being present when fallback styles first applied * Use a predefined set of background colors suitable for a table * Add createCustomColorsHOC HOC builder that creates withColors HOC for usages with custom colors array. Refactor withColorHOC. Ensure withSelect is not used when not needed, and ensure custom color HOC has the right display name Add tests for new createCustomColorsHOC * Use createCustomColorHOC for table backgrounds and refactor to use only class names to set the background color * Remove contrast checker from table block background color selector - table block has custom background colors designed to meet contrast requirements * Remove use of compose for single HOC * Modify use of classnames, pass backgroundColor as an argument instead of using the object form * update CHANGELOG --- packages/block-library/CHANGELOG.md | 6 + packages/block-library/src/table/edit.js | 73 ++++- packages/block-library/src/table/editor.scss | 2 +- packages/block-library/src/table/index.js | 28 +- packages/block-library/src/table/style.scss | 46 +++ packages/editor/CHANGELOG.md | 6 +- .../editor/src/components/colors/index.js | 5 +- .../test/__snapshots__/with-colors.js.snap | 23 ++ .../src/components/colors/test/with-colors.js | 56 ++++ .../src/components/colors/with-colors.js | 277 +++++++++++------- 10 files changed, 399 insertions(+), 123 deletions(-) create mode 100644 packages/editor/src/components/colors/test/__snapshots__/with-colors.js.snap create mode 100644 packages/editor/src/components/colors/test/with-colors.js diff --git a/packages/block-library/CHANGELOG.md b/packages/block-library/CHANGELOG.md index 65cbad9ef8dc4..398b2af5887d9 100644 --- a/packages/block-library/CHANGELOG.md +++ b/packages/block-library/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.3.0 (Unreleased) + +### New Feature + +- Add background color controls for the table block. + ## 2.2.12 (2019-01-03) ## 2.2.11 (2018-12-18) diff --git a/packages/block-library/src/table/edit.js b/packages/block-library/src/table/edit.js index d9358c04845ab..b0c789a724720 100644 --- a/packages/block-library/src/table/edit.js +++ b/packages/block-library/src/table/edit.js @@ -7,7 +7,13 @@ import classnames from 'classnames'; * WordPress dependencies */ import { Fragment, Component } from '@wordpress/element'; -import { InspectorControls, BlockControls, RichText } from '@wordpress/editor'; +import { + InspectorControls, + BlockControls, + RichText, + PanelColorSettings, + createCustomColorsHOC, +} from '@wordpress/editor'; import { __ } from '@wordpress/i18n'; import { PanelBody, @@ -30,7 +36,32 @@ import { deleteColumn, } from './state'; -export default class TableEdit extends Component { +const BACKGROUND_COLORS = [ + { + color: '#f3f4f5', + name: 'Subtle light gray', + slug: 'subtle-light-gray', + }, + { + color: '#e9fbe5', + name: 'Subtle pale green', + slug: 'subtle-pale-green', + }, + { + color: '#e7f5fe', + name: 'Subtle pale blue', + slug: 'subtle-pale-blue', + }, + { + color: '#fcf0ef', + name: 'Subtle pale pink', + slug: 'subtle-pale-pink', + }, +]; + +const withCustomBackgroundColors = createCustomColorsHOC( BACKGROUND_COLORS ); + +export class TableEdit extends Component { constructor() { super( ...arguments ); @@ -314,7 +345,7 @@ export default class TableEdit extends Component { return ( <Tag> - { rows.map( ( { cells }, rowIndex ) => + { rows.map( ( { cells }, rowIndex ) => ( <tr key={ rowIndex }> { cells.map( ( { content, tag: CellTag }, columnIndex ) => { const isSelected = selectedCell && ( @@ -329,12 +360,13 @@ export default class TableEdit extends Component { columnIndex, }; - const classes = classnames( { - 'is-selected': isSelected, - } ); + const cellClasses = classnames( { 'is-selected': isSelected } ); return ( - <CellTag key={ columnIndex } className={ classes }> + <CellTag + key={ columnIndex } + className={ cellClasses } + > <RichText className="wp-block-table__cell-content" value={ content } @@ -345,7 +377,7 @@ export default class TableEdit extends Component { ); } ) } </tr> - ) } + ) ) } </Tag> ); } @@ -360,7 +392,12 @@ export default class TableEdit extends Component { } render() { - const { attributes, className } = this.props; + const { + attributes, + className, + backgroundColor, + setBackgroundColor, + } = this.props; const { initialRowCount, initialColumnCount } = this.state; const { hasFixedLayout, head, body, foot } = attributes; const isEmpty = ! head.length && ! body.length && ! foot.length; @@ -388,8 +425,9 @@ export default class TableEdit extends Component { ); } - const classes = classnames( className, { + const classes = classnames( className, backgroundColor.class, { 'has-fixed-layout': hasFixedLayout, + 'has-background': !! backgroundColor.color, } ); return ( @@ -411,6 +449,19 @@ export default class TableEdit extends Component { onChange={ this.onChangeFixedLayout } /> </PanelBody> + <PanelColorSettings + title={ __( 'Color Settings' ) } + initialOpen={ false } + colorSettings={ [ + { + value: backgroundColor.color, + onChange: setBackgroundColor, + label: __( 'Background Color' ), + disableCustomColors: true, + colors: BACKGROUND_COLORS, + }, + ] } + /> </InspectorControls> <table className={ classes }> <Section type="head" rows={ head } /> @@ -421,3 +472,5 @@ export default class TableEdit extends Component { ); } } + +export default withCustomBackgroundColors( 'backgroundColor' )( TableEdit ); diff --git a/packages/block-library/src/table/editor.scss b/packages/block-library/src/table/editor.scss index 22170648d7450..c6fec7750d23b 100644 --- a/packages/block-library/src/table/editor.scss +++ b/packages/block-library/src/table/editor.scss @@ -27,7 +27,7 @@ td, th { padding: 0; - border: $border-width solid currentColor; + border: $border-width solid $black; } td.is-selected, diff --git a/packages/block-library/src/table/index.js b/packages/block-library/src/table/index.js index aff5c7ca5be8c..c6221d688c392 100644 --- a/packages/block-library/src/table/index.js +++ b/packages/block-library/src/table/index.js @@ -8,8 +8,8 @@ import classnames from 'classnames'; */ import { __, _x } from '@wordpress/i18n'; import { getPhrasingContentSchema } from '@wordpress/blocks'; -import { RichText } from '@wordpress/editor'; import { G, Path, SVG } from '@wordpress/components'; +import { RichText, getColorClassName } from '@wordpress/editor'; /** * Internal dependencies @@ -86,6 +86,9 @@ export const settings = { type: 'boolean', default: false, }, + backgroundColor: { + type: 'string', + }, head: getTableSectionAttributeSchema( 'head' ), body: getTableSectionAttributeSchema( 'body' ), foot: getTableSectionAttributeSchema( 'foot' ), @@ -113,15 +116,24 @@ export const settings = { edit, save( { attributes } ) { - const { hasFixedLayout, head, body, foot } = attributes; + const { + hasFixedLayout, + head, + body, + foot, + backgroundColor, + } = attributes; const isEmpty = ! head.length && ! body.length && ! foot.length; if ( isEmpty ) { return null; } - const classes = classnames( { + const backgroundClass = getColorClassName( 'background-color', backgroundColor ); + + const classes = classnames( backgroundClass, { 'has-fixed-layout': hasFixedLayout, + 'has-background': !! backgroundClass, } ); const Section = ( { type, rows } ) => { @@ -133,13 +145,17 @@ export const settings = { return ( <Tag> - { rows.map( ( { cells }, rowIndex ) => + { rows.map( ( { cells }, rowIndex ) => ( <tr key={ rowIndex }> { cells.map( ( { content, tag }, cellIndex ) => - <RichText.Content tagName={ tag } value={ content } key={ cellIndex } /> + <RichText.Content + tagName={ tag } + value={ content } + key={ cellIndex } + /> ) } </tr> - ) } + ) ) } </Tag> ); }; diff --git a/packages/block-library/src/table/style.scss b/packages/block-library/src/table/style.scss index 0da2a2951fd0d..3fda38e27b860 100644 --- a/packages/block-library/src/table/style.scss +++ b/packages/block-library/src/table/style.scss @@ -1,4 +1,9 @@ .wp-block-table { + $subtle-light-gray: #f3f4f5; + $subtle-pale-green: #e9fbe5; + $subtle-pale-blue: #e7f5fe; + $subtle-pale-pink: #fcf0ef; + // Fixed layout toggle &.has-fixed-layout { table-layout: fixed; @@ -18,15 +23,56 @@ width: auto; } + &.has-subtle-light-gray-background-color { + background-color: $subtle-light-gray; + } + + &.has-subtle-pale-green-background-color { + background-color: $subtle-pale-green; + } + + &.has-subtle-pale-blue-background-color { + background-color: $subtle-pale-blue; + } + + &.has-subtle-pale-pink-background-color { + background-color: $subtle-pale-pink; + } + // "Stripes" style variation. &.is-style-stripes { border-spacing: 0; border-collapse: inherit; + background-color: transparent; tr:nth-child(odd) { background-color: $light-gray-200; } + &.has-subtle-light-gray-background-color { + tr:nth-child(odd) { + background-color: $subtle-light-gray; + } + } + + &.has-subtle-pale-green-background-color { + tr:nth-child(odd) { + background-color: $subtle-pale-green; + } + } + + &.has-subtle-pale-blue-background-color { + tr:nth-child(odd) { + background-color: $subtle-pale-blue; + } + } + + &.has-subtle-pale-pink-background-color { + tr:nth-child(odd) { + background-color: $subtle-pale-pink; + } + } + td { border-color: transparent; } diff --git a/packages/editor/CHANGELOG.md b/packages/editor/CHANGELOG.md index 026353023b61d..e4cc87f80faa4 100644 --- a/packages/editor/CHANGELOG.md +++ b/packages/editor/CHANGELOG.md @@ -1,4 +1,8 @@ -## 9.0.8 (Unreleased) +## 9.1.0 (Unreleased) + +### New Feature + +- Added `createCustomColorsHOC` for creating a higher order `withCustomColors` component. ### Internal diff --git a/packages/editor/src/components/colors/index.js b/packages/editor/src/components/colors/index.js index 6782423e66833..f6b6fac984db8 100644 --- a/packages/editor/src/components/colors/index.js +++ b/packages/editor/src/components/colors/index.js @@ -3,4 +3,7 @@ export { getColorObjectByAttributeValues, getColorObjectByColorValue, } from './utils'; -export { default as withColors } from './with-colors'; +export { + createCustomColorsHOC, + default as withColors, +} from './with-colors'; diff --git a/packages/editor/src/components/colors/test/__snapshots__/with-colors.js.snap b/packages/editor/src/components/colors/test/__snapshots__/with-colors.js.snap new file mode 100644 index 0000000000000..fb33dd8d00235 --- /dev/null +++ b/packages/editor/src/components/colors/test/__snapshots__/with-colors.js.snap @@ -0,0 +1,23 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`createCustomColorsHOC provides the the wrapped component with color values and setter functions as props 1`] = ` +<Component + attributes={ + Object { + "backgroundColor": null, + } + } + backgroundColor={ + Object { + "class": undefined, + "color": undefined, + } + } + colorUtils={ + Object { + "getMostReadableColor": [Function], + } + } + setBackgroundColor={[Function]} +/> +`; diff --git a/packages/editor/src/components/colors/test/with-colors.js b/packages/editor/src/components/colors/test/with-colors.js new file mode 100644 index 0000000000000..07c19ce0c11e7 --- /dev/null +++ b/packages/editor/src/components/colors/test/with-colors.js @@ -0,0 +1,56 @@ +/** + * External dependencies + */ +import { shallow, mount } from 'enzyme'; + +/** + * Internal dependencies + */ +import { createCustomColorsHOC } from '../with-colors'; + +describe( 'createCustomColorsHOC', () => { + it( 'provides the the wrapped component with color values and setter functions as props', () => { + const withCustomColors = createCustomColorsHOC( [ { name: 'Red', slug: 'red', color: 'ff0000' } ] ); + const EnhancedComponent = withCustomColors( 'backgroundColor' )( () => ( + <div /> + ) ); + + const wrapper = shallow( + <EnhancedComponent attributes={ { backgroundColor: null } } /> + ); + + expect( wrapper.dive() ).toMatchSnapshot(); + } ); + + it( 'setting the color to a value in the provided custom color array updated the backgroundColor attribute', () => { + const withCustomColors = createCustomColorsHOC( [ { name: 'Red', slug: 'red', color: 'ff0000' } ] ); + const EnhancedComponent = withCustomColors( 'backgroundColor' )( ( props ) => ( + <button onClick={ () => props.setBackgroundColor( 'ff0000' ) }>Test Me</button> + ) ); + + const setAttributes = jest.fn(); + + const wrapper = mount( + <EnhancedComponent attributes={ { backgroundColor: null } } setAttributes={ setAttributes } /> + ); + + wrapper.find( 'button' ).simulate( 'click' ); + expect( setAttributes ).toHaveBeenCalledWith( { backgroundColor: 'red', customBackgroundColor: undefined } ); + } ); + + it( 'setting the color to a value not in the provided custom color array updates customBackgroundColor attribute', () => { + const withCustomColors = createCustomColorsHOC( [ { name: 'Red', slug: 'red', color: 'ff0000' } ] ); + const EnhancedComponent = withCustomColors( 'backgroundColor' )( ( props ) => ( + <button onClick={ () => props.setBackgroundColor( '000000' ) }>Test Me</button> + ) ); + + const setAttributes = jest.fn(); + + const wrapper = mount( + <EnhancedComponent attributes={ { backgroundColor: null } } setAttributes={ setAttributes } /> + ); + + wrapper.find( 'button' ).simulate( 'click' ); + expect( setAttributes ).toHaveBeenCalledWith( { backgroundColor: undefined, customBackgroundColor: '000000' } ); + } ); +} ); diff --git a/packages/editor/src/components/colors/with-colors.js b/packages/editor/src/components/colors/with-colors.js index 3d1bccc8c2656..3afb5e7d7469c 100644 --- a/packages/editor/src/components/colors/with-colors.js +++ b/packages/editor/src/components/colors/with-colors.js @@ -18,117 +18,186 @@ import { getColorClassName, getColorObjectByColorValue, getColorObjectByAttribut const DEFAULT_COLORS = []; /** - * Higher-order component, which handles color logic for class generation - * color value, retrieval and color attribute setting. + * Higher order component factory for injecting the `colorsArray` argument as + * the colors prop in the `withCustomColors` HOC. * - * @param {...(object|string)} args The arguments can be strings or objects. If the argument is an object, - * it should contain the color attribute name as key and the color context as value. - * If the argument is a string the value should be the color attribute name, - * the color context is computed by applying a kebab case transform to the value. - * Color context represents the context/place where the color is going to be used. - * The class name of the color is generated using 'has' followed by the color name - * and ending with the color context all in kebab case e.g: has-green-background-color. + * @param {Array} colorsArray An array of color objects. * + * @return {function} The higher order component. + */ +const withCustomColorPalette = ( colorsArray ) => createHigherOrderComponent( ( WrappedComponent ) => ( props ) => ( + <WrappedComponent { ...props } colors={ colorsArray } /> +), 'withCustomColorPalette' ); + +/** + * Higher order component factory for injecting the editor colors as the + * `colors` prop in the `withColors` HOC. * - * @return {Function} Higher-order component. + * @return {function} The higher order component. + */ +const withEditorColorPalette = () => withSelect( ( select ) => { + const settings = select( 'core/editor' ).getEditorSettings(); + return { + colors: get( settings, [ 'colors' ], DEFAULT_COLORS ), + }; +} ); + +/** + * Helper function used with `createHigherOrderComponent` to create + * higher order components for managing color logic. + * + * @param {Array} colorTypes An array of color types (e.g. 'backgroundColor, borderColor). + * @param {Function} withColorPalette A HOC for injecting the 'colors' prop into the WrappedComponent. + * + * @return {Component} The component that can be used as a HOC. */ -export default ( ...args ) => { - const colorMap = reduce( args, ( colorObject, arg ) => { +function createColorHOC( colorTypes, withColorPalette ) { + const colorMap = reduce( colorTypes, ( colorObject, colorType ) => { return { ...colorObject, - ...( isString( arg ) ? { [ arg ]: kebabCase( arg ) } : arg ), + ...( isString( colorType ) ? { [ colorType ]: kebabCase( colorType ) } : colorType ), }; }, {} ); - return createHigherOrderComponent( - compose( [ - withSelect( ( select ) => { - const settings = select( 'core/editor' ).getEditorSettings(); - return { - colors: get( settings, [ 'colors' ], DEFAULT_COLORS ), - }; - } ), - ( WrappedComponent ) => { - return class extends Component { - constructor( props ) { - super( props ); - - this.setters = this.createSetters(); - this.colorUtils = { - getMostReadableColor: this.getMostReadableColor.bind( this ), - }; - - this.state = {}; - } - - getMostReadableColor( colorValue ) { - const { colors } = this.props; - return getMostReadableColor( colors, colorValue ); - } - - createSetters() { - return reduce( colorMap, ( settersAccumulator, colorContext, colorAttributeName ) => { - const upperFirstColorAttributeName = upperFirst( colorAttributeName ); - const customColorAttributeName = `custom${ upperFirstColorAttributeName }`; - settersAccumulator[ `set${ upperFirstColorAttributeName }` ] = - this.createSetColor( colorAttributeName, customColorAttributeName ); - return settersAccumulator; - }, {} ); - } - - createSetColor( colorAttributeName, customColorAttributeName ) { - return ( colorValue ) => { - const colorObject = getColorObjectByColorValue( this.props.colors, colorValue ); - this.props.setAttributes( { - [ colorAttributeName ]: colorObject && colorObject.slug ? colorObject.slug : undefined, - [ customColorAttributeName ]: colorObject && colorObject.slug ? undefined : colorValue, - } ); - }; - } - - static getDerivedStateFromProps( { attributes, colors }, previousState ) { - return reduce( colorMap, ( newState, colorContext, colorAttributeName ) => { - const colorObject = getColorObjectByAttributeValues( - colors, - attributes[ colorAttributeName ], - attributes[ `custom${ upperFirst( colorAttributeName ) }` ], - ); - - const previousColorObject = previousState[ colorAttributeName ]; - const previousColor = get( previousColorObject, [ 'color' ] ); - /** - * The "and previousColorObject" condition checks that a previous color object was already computed. - * At the start previousColorObject and colorValue are both equal to undefined - * bus as previousColorObject does not exist we should compute the object. - */ - if ( previousColor === colorObject.color && previousColorObject ) { - newState[ colorAttributeName ] = previousColorObject; - } else { - newState[ colorAttributeName ] = { - ...colorObject, - class: getColorClassName( colorContext, colorObject.slug ), - }; - } - return newState; - }, {} ); - } - - render() { - return ( - <WrappedComponent - { ...{ - ...this.props, - colors: undefined, - ...this.state, - ...this.setters, - colorUtils: this.colorUtils, - } } - /> + return compose( [ + withColorPalette, + ( WrappedComponent ) => { + return class extends Component { + constructor( props ) { + super( props ); + + this.setters = this.createSetters(); + this.colorUtils = { + getMostReadableColor: this.getMostReadableColor.bind( this ), + }; + + this.state = {}; + } + + getMostReadableColor( colorValue ) { + const { colors } = this.props; + return getMostReadableColor( colors, colorValue ); + } + + createSetters() { + return reduce( colorMap, ( settersAccumulator, colorContext, colorAttributeName ) => { + const upperFirstColorAttributeName = upperFirst( colorAttributeName ); + const customColorAttributeName = `custom${ upperFirstColorAttributeName }`; + settersAccumulator[ `set${ upperFirstColorAttributeName }` ] = + this.createSetColor( colorAttributeName, customColorAttributeName ); + return settersAccumulator; + }, {} ); + } + + createSetColor( colorAttributeName, customColorAttributeName ) { + return ( colorValue ) => { + const colorObject = getColorObjectByColorValue( this.props.colors, colorValue ); + this.props.setAttributes( { + [ colorAttributeName ]: colorObject && colorObject.slug ? colorObject.slug : undefined, + [ customColorAttributeName ]: colorObject && colorObject.slug ? undefined : colorValue, + } ); + }; + } + + static getDerivedStateFromProps( { attributes, colors }, previousState ) { + return reduce( colorMap, ( newState, colorContext, colorAttributeName ) => { + const colorObject = getColorObjectByAttributeValues( + colors, + attributes[ colorAttributeName ], + attributes[ `custom${ upperFirst( colorAttributeName ) }` ], ); - } - }; - }, - ] ), - 'withColors' - ); -}; + + const previousColorObject = previousState[ colorAttributeName ]; + const previousColor = get( previousColorObject, [ 'color' ] ); + /** + * The "and previousColorObject" condition checks that a previous color object was already computed. + * At the start previousColorObject and colorValue are both equal to undefined + * bus as previousColorObject does not exist we should compute the object. + */ + if ( previousColor === colorObject.color && previousColorObject ) { + newState[ colorAttributeName ] = previousColorObject; + } else { + newState[ colorAttributeName ] = { + ...colorObject, + class: getColorClassName( colorContext, colorObject.slug ), + }; + } + return newState; + }, {} ); + } + + render() { + return ( + <WrappedComponent + { ...{ + ...this.props, + colors: undefined, + ...this.state, + ...this.setters, + colorUtils: this.colorUtils, + } } + /> + ); + } + }; + }, + ] ); +} + +/** + * A higher-order component factory for creating a 'withCustomColors' HOC, which handles color logic + * for class generation color value, retrieval and color attribute setting. + * + * Use this higher-order component to work with a custom set of colors. + * + * @example + * + * ```jsx + * const CUSTOM_COLORS = [ { name: 'Red', slug: 'red', color: '#ff0000' }, { name: 'Blue', slug: 'blue', color: '#0000ff' } ]; + * const withCustomColors = createCustomColorsHOC( CUSTOM_COLORS ); + * // ... + * export default compose( + * withCustomColors( 'backgroundColor', 'borderColor' ), + * MyColorfulComponent, + * ); + * ``` + * + * @param {Array} colorsArray The array of color objects (name, slug, color, etc... ). + * + * @return {Function} Higher-order component. + */ +export function createCustomColorsHOC( colorsArray ) { + return ( ...colorTypes ) => { + const withColorPalette = withCustomColorPalette( colorsArray ); + return createHigherOrderComponent( createColorHOC( colorTypes, withColorPalette ), 'withCustomColors' ); + }; +} + +/** + * A higher-order component, which handles color logic for class generation color value, retrieval and color attribute setting. + * + * For use with the default editor/theme color palette. + * + * @example + * + * ```jsx + * export default compose( + * withColors( 'backgroundColor', { textColor: 'color' } ), + * MyColorfulComponent, + * ); + * ``` + * + * @param {...(object|string)} colorTypes The arguments can be strings or objects. If the argument is an object, + * it should contain the color attribute name as key and the color context as value. + * If the argument is a string the value should be the color attribute name, + * the color context is computed by applying a kebab case transform to the value. + * Color context represents the context/place where the color is going to be used. + * The class name of the color is generated using 'has' followed by the color name + * and ending with the color context all in kebab case e.g: has-green-background-color. + * + * @return {Function} Higher-order component. + */ +export default function withColors( ...colorTypes ) { + const withColorPalette = withEditorColorPalette(); + return createHigherOrderComponent( createColorHOC( colorTypes, withColorPalette ), 'withColors' ); +} From 9fc611ea932960ed0c608a237d7497b970a97eb4 Mon Sep 17 00:00:00 2001 From: Hiroshi Urabe <mail@torounit.com> Date: Fri, 18 Jan 2019 20:33:57 +0900 Subject: [PATCH 171/691] Custom HTML Block: apply editor-styles to preview mode. (#13080) --- packages/block-library/src/html/edit.js | 79 +++++++++++++++++++ packages/block-library/src/html/index.js | 46 ++--------- packages/components/src/sandbox/index.js | 15 ++-- .../editor/src/components/provider/index.js | 14 +--- packages/editor/src/editor-styles/index.js | 45 ++++++++++- packages/editor/src/index.js | 1 + 6 files changed, 143 insertions(+), 57 deletions(-) create mode 100644 packages/block-library/src/html/edit.js diff --git a/packages/block-library/src/html/edit.js b/packages/block-library/src/html/edit.js new file mode 100644 index 0000000000000..d9bbe4e07211f --- /dev/null +++ b/packages/block-library/src/html/edit.js @@ -0,0 +1,79 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Component } from '@wordpress/element'; +import { BlockControls, PlainText, transformStyles } from '@wordpress/editor'; +import { Disabled, SandBox } from '@wordpress/components'; +import { withSelect } from '@wordpress/data'; + +class HTMLEdit extends Component { + constructor() { + super( ...arguments ); + this.state = { + isPreview: false, + styles: [], + }; + this.switchToHTML = this.switchToHTML.bind( this ); + this.switchToPreview = this.switchToPreview.bind( this ); + } + + componentDidMount() { + const { styles } = this.props; + this.setState( { styles: transformStyles( styles ) } ); + } + + switchToPreview() { + this.setState( { isPreview: true } ); + } + + switchToHTML() { + this.setState( { isPreview: false } ); + } + + render() { + const { attributes, setAttributes } = this.props; + const { isPreview, styles } = this.state; + + return ( + <div className="wp-block-html"> + <BlockControls> + <div className="components-toolbar"> + <button + className={ `components-tab-button ${ ! isPreview ? 'is-active' : '' }` } + onClick={ this.switchToHTML } + > + <span>HTML</span> + </button> + <button + className={ `components-tab-button ${ isPreview ? 'is-active' : '' }` } + onClick={ this.switchToPreview } + > + <span>{ __( 'Preview' ) }</span> + </button> + </div> + </BlockControls> + <Disabled.Consumer> + { ( isDisabled ) => ( + ( isPreview || isDisabled ) ? ( + <SandBox html={ attributes.content } styles={ styles } /> + ) : ( + <PlainText + value={ attributes.content } + onChange={ ( content ) => setAttributes( { content } ) } + placeholder={ __( 'Write HTML…' ) } + aria-label={ __( 'HTML' ) } + /> + ) + ) } + </Disabled.Consumer> + </div> + ); + } +} +export default withSelect( ( select ) => { + const { getEditorSettings } = select( 'core/editor' ); + return { + styles: getEditorSettings().styles, + }; +} )( HTMLEdit ); diff --git a/packages/block-library/src/html/index.js b/packages/block-library/src/html/index.js index c7307aa53e9b4..74233c565036e 100644 --- a/packages/block-library/src/html/index.js +++ b/packages/block-library/src/html/index.js @@ -3,10 +3,13 @@ */ import { RawHTML } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { Disabled, SandBox, SVG, Path } from '@wordpress/components'; +import { SVG, Path } from '@wordpress/components'; import { getPhrasingContentSchema } from '@wordpress/blocks'; -import { BlockControls, PlainText } from '@wordpress/editor'; -import { withState } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import edit from './edit'; export const name = 'core/html'; @@ -56,42 +59,7 @@ export const settings = { ], }, - edit: withState( { - isPreview: false, - } )( ( { attributes, setAttributes, setState, isPreview } ) => ( - <div className="wp-block-html"> - <BlockControls> - <div className="components-toolbar"> - <button - className={ `components-tab-button ${ ! isPreview ? 'is-active' : '' }` } - onClick={ () => setState( { isPreview: false } ) } - > - <span>HTML</span> - </button> - <button - className={ `components-tab-button ${ isPreview ? 'is-active' : '' }` } - onClick={ () => setState( { isPreview: true } ) } - > - <span>{ __( 'Preview' ) }</span> - </button> - </div> - </BlockControls> - <Disabled.Consumer> - { ( isDisabled ) => ( - ( isPreview || isDisabled ) ? ( - <SandBox html={ attributes.content } /> - ) : ( - <PlainText - value={ attributes.content } - onChange={ ( content ) => setAttributes( { content } ) } - placeholder={ __( 'Write HTML…' ) } - aria-label={ __( 'HTML' ) } - /> - ) - ) } - </Disabled.Consumer> - </div> - ) ), + edit: edit, save( { attributes } ) { return <RawHTML>{ attributes.content }</RawHTML>; diff --git a/packages/components/src/sandbox/index.js b/packages/components/src/sandbox/index.js index 1cd516d105c77..1862c00db9754 100644 --- a/packages/components/src/sandbox/index.js +++ b/packages/components/src/sandbox/index.js @@ -110,11 +110,13 @@ class Sandbox extends Component { // the iframe root and interfere with our mechanism for // determining the unconstrained page bounds. function removeViewportStyles( ruleOrNode ) { - [ 'width', 'height', 'minHeight', 'maxHeight' ].forEach( function( style ) { - if ( /^\\d+(vmin|vmax|vh|vw)$/.test( ruleOrNode.style[ style ] ) ) { - ruleOrNode.style[ style ] = ''; - } - } ); + if( ruleOrNode.style ) { + [ 'width', 'height', 'minHeight', 'maxHeight' ].forEach( function( style ) { + if ( /^\\d+(vmin|vmax|vh|vw)$/.test( ruleOrNode.style[ style ] ) ) { + ruleOrNode.style[ style ] = ''; + } + } ); + } } Array.prototype.forEach.call( document.querySelectorAll( '[style]' ), removeViewportStyles ); @@ -165,6 +167,9 @@ class Sandbox extends Component { <head> <title>{ this.props.title }</title> <style dangerouslySetInnerHTML={ { __html: style } } /> + { ( this.props.styles && this.props.styles.map( + ( rules, i ) => <style key={ i } dangerouslySetInnerHTML={ { __html: rules } } /> + ) ) } </head> <body data-resizable-iframe-connected="data-resizable-iframe-connected" className={ this.props.type }> <div dangerouslySetInnerHTML={ { __html: this.props.html } } /> diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index ebe4edf43e653..a889bb01c14ac 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -6,7 +6,6 @@ import { flow, map } from 'lodash'; /** * WordPress Dependencies */ -import { compose } from '@wordpress/compose'; import { createElement, Component } from '@wordpress/element'; import { DropZoneProvider, SlotFillProvider } from '@wordpress/components'; import { withDispatch } from '@wordpress/data'; @@ -15,7 +14,7 @@ import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import { traverse, wrap, urlRewrite } from '../../editor-styles'; +import transformStyles from '../../editor-styles'; class EditorProvider extends Component { constructor( props ) { @@ -51,14 +50,9 @@ class EditorProvider extends Component { return; } - map( this.props.settings.styles, ( { css, baseURL } ) => { - const transforms = [ - wrap( '.editor-styles-wrapper' ), - ]; - if ( baseURL ) { - transforms.push( urlRewrite( baseURL ) ); - } - const updatedCSS = traverse( css, compose( transforms ) ); + const updatedStyles = transformStyles( this.props.settings.styles, '.editor-styles-wrapper' ); + + map( updatedStyles, ( updatedCSS ) => { if ( updatedCSS ) { const node = document.createElement( 'style' ); node.innerHTML = updatedCSS; diff --git a/packages/editor/src/editor-styles/index.js b/packages/editor/src/editor-styles/index.js index 35baab8d590b6..c5de582ce074c 100644 --- a/packages/editor/src/editor-styles/index.js +++ b/packages/editor/src/editor-styles/index.js @@ -1,3 +1,42 @@ -export { default as traverse } from './traverse'; -export { default as urlRewrite } from './transforms/url-rewrite'; -export { default as wrap } from './transforms/wrap'; +/** + * External dependencies + */ +import { map } from 'lodash'; + +/** + * WordPress dependencies + */ +import { compose } from '@wordpress/compose'; + +/** + * External dependencies + */ +import traverse from './traverse'; +import urlRewrite from './transforms/url-rewrite'; +import wrap from './transforms/wrap'; + +/** + * Convert css rules. + * + * @param {Array} styles CSS rules. + * @param {string} wrapperClassName Wrapper Class Name. + * @return {Array} converted rules. + */ +const transformStyles = ( styles, wrapperClassName = '' ) => { + return map( styles, ( { 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; + } ); +}; + +export default transformStyles; diff --git a/packages/editor/src/index.js b/packages/editor/src/index.js index 43cf7442800ca..7a88f75dbccb5 100644 --- a/packages/editor/src/index.js +++ b/packages/editor/src/index.js @@ -16,3 +16,4 @@ import './hooks'; export * from './components'; export * from './utils'; +export { default as transformStyles } from './editor-styles'; From e5f398c7d61c7a79bd106756840e2efb30a6d7e9 Mon Sep 17 00:00:00 2001 From: imath <imathi.eu@outlook.fr> Date: Fri, 18 Jan 2019 13:14:10 +0100 Subject: [PATCH 172/691] Missing Post Type supports in Options Modal (#12394) --- lib/client-assets.php | 33 +++++++++++++------ .../src/components/options-modal/index.js | 29 +++++++++++----- .../test/__snapshots__/index.js.snap | 31 ++++++++++------- 3 files changed, 63 insertions(+), 30 deletions(-) diff --git a/lib/client-assets.php b/lib/client-assets.php index d3e84f1b9779c..42624b3e1c294 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -1013,7 +1013,7 @@ function gutenberg_get_available_image_sizes() { function gutenberg_editor_scripts_and_styles( $hook ) { $is_demo = isset( $_GET['gutenberg-demo'] ); - global $wp_scripts; + global $wp_scripts, $wp_meta_boxes; // Add "wp-hooks" as dependency of "heartbeat". $heartbeat_script = $wp_scripts->query( 'heartbeat', 'registered' ); @@ -1335,15 +1335,18 @@ function gutenberg_editor_scripts_and_styles( $hook ) { $editor_settings['templateLock'] = ! empty( $post_type_object->template_lock ) ? $post_type_object->template_lock : false; } - $init_script = <<<JS - ( function() { - window._wpLoadGutenbergEditor = new Promise( function( resolve ) { - wp.domReady( function() { - resolve( wp.editPost.initializeEditor( 'editor', "%s", %d, %s, %s ) ); - } ); - } ); -} )(); -JS; + $current_screen = get_current_screen(); + $core_meta_boxes = array(); + + // Make sure the current screen is set as well as the normal core metaboxes. + if ( isset( $current_screen->id ) && isset( $wp_meta_boxes[ $current_screen->id ]['normal']['core'] ) ) { + $core_meta_boxes = $wp_meta_boxes[ $current_screen->id ]['normal']['core']; + } + + // Check if the Custom Fields meta box has been removed at some point. + if ( ! isset( $core_meta_boxes['postcustom'] ) || ! $core_meta_boxes['postcustom'] ) { + unset( $editor_settings['enableCustomFields'] ); + } /** * Filters the settings to pass to the block editor. @@ -1355,6 +1358,16 @@ function gutenberg_editor_scripts_and_styles( $hook ) { */ $editor_settings = apply_filters( 'block_editor_settings', $editor_settings, $post ); + $init_script = <<<JS + ( function() { + window._wpLoadGutenbergEditor = new Promise( function( resolve ) { + wp.domReady( function() { + resolve( wp.editPost.initializeEditor( 'editor', "%s", %d, %s, %s ) ); + } ); + } ); +} )(); +JS; + $script = sprintf( $init_script, $post->post_type, diff --git a/packages/edit-post/src/components/options-modal/index.js b/packages/edit-post/src/components/options-modal/index.js index 51b40719eb488..1d6528815b59c 100644 --- a/packages/edit-post/src/components/options-modal/index.js +++ b/packages/edit-post/src/components/options-modal/index.js @@ -10,7 +10,7 @@ import { Modal } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { withSelect, withDispatch } from '@wordpress/data'; import { compose } from '@wordpress/compose'; -import { PostTaxonomies, PostExcerptCheck, PageAttributesCheck } from '@wordpress/editor'; +import { PostTaxonomies, PostExcerptCheck, PageAttributesCheck, PostFeaturedImageCheck, PostTypeSupportCheck } from '@wordpress/editor'; /** * Internal dependencies @@ -25,7 +25,7 @@ import MetaBoxesSection from './meta-boxes-section'; const MODAL_NAME = 'edit-post/options'; -export function OptionsModal( { isModalActive, closeModal } ) { +export function OptionsModal( { isModalActive, isViewable, closeModal } ) { if ( ! isModalActive ) { return null; } @@ -42,7 +42,9 @@ export function OptionsModal( { isModalActive, closeModal } ) { <EnableTipsOption label={ __( 'Enable Tips' ) } /> </Section> <Section title={ __( 'Document Panels' ) }> - <EnablePanelOption label={ __( 'Permalink' ) } panelName="post-link" /> + { isViewable && ( + <EnablePanelOption label={ __( 'Permalink' ) } panelName="post-link" /> + ) } <PostTaxonomies taxonomyWrapper={ ( content, taxonomy ) => ( <EnablePanelOption @@ -51,11 +53,15 @@ export function OptionsModal( { isModalActive, closeModal } ) { /> ) } /> - <EnablePanelOption label={ __( 'Featured Image' ) } panelName="featured-image" /> + <PostFeaturedImageCheck> + <EnablePanelOption label={ __( 'Featured Image' ) } panelName="featured-image" /> + </PostFeaturedImageCheck> <PostExcerptCheck> <EnablePanelOption label={ __( 'Excerpt' ) } panelName="post-excerpt" /> </PostExcerptCheck> - <EnablePanelOption label={ __( 'Discussion' ) } panelName="discussion-panel" /> + <PostTypeSupportCheck supportKeys={ [ 'comments', 'trackbacks' ] }> + <EnablePanelOption label={ __( 'Discussion' ) } panelName="discussion-panel" /> + </PostTypeSupportCheck> <PageAttributesCheck> <EnablePanelOption label={ __( 'Page Attributes' ) } panelName="page-attributes" /> </PageAttributesCheck> @@ -66,9 +72,16 @@ export function OptionsModal( { isModalActive, closeModal } ) { } export default compose( - withSelect( ( select ) => ( { - isModalActive: select( 'core/edit-post' ).isModalActive( MODAL_NAME ), - } ) ), + withSelect( ( select ) => { + const { getEditedPostAttribute } = select( 'core/editor' ); + const { getPostType } = select( 'core' ); + const postType = getPostType( getEditedPostAttribute( 'type' ) ); + + return { + isModalActive: select( 'core/edit-post' ).isModalActive( MODAL_NAME ), + isViewable: get( postType, [ 'viewable' ], false ), + }; + } ), withDispatch( ( dispatch ) => { return { closeModal: () => dispatch( 'core/edit-post' ).closeModal(), diff --git a/packages/edit-post/src/components/options-modal/test/__snapshots__/index.js.snap b/packages/edit-post/src/components/options-modal/test/__snapshots__/index.js.snap index 1367ebd083419..94d989e492e4a 100644 --- a/packages/edit-post/src/components/options-modal/test/__snapshots__/index.js.snap +++ b/packages/edit-post/src/components/options-modal/test/__snapshots__/index.js.snap @@ -25,27 +25,34 @@ exports[`OptionsModal should match snapshot when the modal is active 1`] = ` <Section title="Document Panels" > - <WithSelect(IfCondition(WithDispatch(BaseOption))) - label="Permalink" - panelName="post-link" - /> <WithSelect(PostTaxonomies) taxonomyWrapper={[Function]} /> - <WithSelect(IfCondition(WithDispatch(BaseOption))) - label="Featured Image" - panelName="featured-image" - /> + <PostFeaturedImageCheck> + <WithSelect(IfCondition(WithDispatch(BaseOption))) + label="Featured Image" + panelName="featured-image" + /> + </PostFeaturedImageCheck> <PostExcerptCheck> <WithSelect(IfCondition(WithDispatch(BaseOption))) label="Excerpt" panelName="post-excerpt" /> </PostExcerptCheck> - <WithSelect(IfCondition(WithDispatch(BaseOption))) - label="Discussion" - panelName="discussion-panel" - /> + <WithSelect(PostTypeSupportCheck) + supportKeys={ + Array [ + "comments", + "trackbacks", + ] + } + > + <WithSelect(IfCondition(WithDispatch(BaseOption))) + label="Discussion" + panelName="discussion-panel" + /> + </WithSelect(PostTypeSupportCheck)> <WithSelect(PageAttributesCheck)> <WithSelect(IfCondition(WithDispatch(BaseOption))) label="Page Attributes" From 91f791fbe77d01b569ab960c60429468f8ffd9cf Mon Sep 17 00:00:00 2001 From: Dharmesh Patel <10613171+iamdharmesh@users.noreply.github.com> Date: Fri, 18 Jan 2019 19:08:56 +0530 Subject: [PATCH 173/691] Replaced "MenuGroup" with "NavigableMenu" in Warning Component. (Fixed:#12503) (#12522) --- packages/components/src/menu-group/index.js | 2 +- packages/editor/src/components/warning/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/src/menu-group/index.js b/packages/components/src/menu-group/index.js index 3e6856478d34b..f5b2aff60337e 100644 --- a/packages/components/src/menu-group/index.js +++ b/packages/components/src/menu-group/index.js @@ -35,7 +35,7 @@ export function MenuGroup( { { label && <div className="components-menu-group__label" id={ labelId }>{ label }</div> } - <NavigableMenu orientation="vertical" aria-labelledby={ labelId }> + <NavigableMenu orientation="vertical" aria-labelledby={ label ? labelId : null }> { children } </NavigableMenu> </div> diff --git a/packages/editor/src/components/warning/index.js b/packages/editor/src/components/warning/index.js index 52a1b41043169..1aa0012853287 100644 --- a/packages/editor/src/components/warning/index.js +++ b/packages/editor/src/components/warning/index.js @@ -40,7 +40,7 @@ function Warning( { className, actions, children, secondaryActions } ) { /> ) } renderContent={ () => ( - <MenuGroup label={ __( 'More options' ) }> + <MenuGroup> { secondaryActions.map( ( item, pos ) => <MenuItem onClick={ item.onClick } key={ pos }> { item.title } From 48598b9269c755875c9f08175688f30e8f6b69ae Mon Sep 17 00:00:00 2001 From: John <johng75@gmail.com> Date: Fri, 18 Jan 2019 14:13:26 +0000 Subject: [PATCH 174/691] Add block warning overwrite (#8166) ## Description Adds an option to the invalid block warning menu to fix the problem by re-creating the block with the current content, overwriting anything that is invalid. This was suggested in #7604 This is similar to the 'convert to blocks' option, but ensures the block stays of the same type. Contrast this with 'convert to blocks' which could convert to another block type, and could end in multiple blocks. ![edit_post_ _wordpress_latest_ _wordpress](https://user-images.githubusercontent.com/1277682/44906590-3dee2f00-ad0d-11e8-9a3a-7b2113aebde1.jpg) In a lot of situations 'overwrite' and 'convert to blocks' will result in the same conversion so it's debatable whether this is a useful enough conversion to include but I'm raising it here for opinion. This is the same overwrite function that used to exist in older versions of Gutenberg. ## How has this been tested? 1. Add a paragraph block 2. Edit block HTML and paste `<p>this is a paragraph</p><blockquote>invalid content</blockquote>` 3. Deselect the block to trigger invalid block warning 4. Pick 'Convert to blocks' 5. Verify that the invalid block is converted to two blocks - a corrected paragraph and a blockquote ![edit_post_ _wordpress_latest_ _wordpress](https://user-images.githubusercontent.com/1277682/44906652-6e35cd80-ad0d-11e8-9652-af1cbc19ac1f.jpg) 6. Reset block HTML to (2) and pick 'Overwrite with valid block' 7. Verify that the invalid block is converted to the first paragraph 'this is a paragraph', with the blockquote removed ## Types of changes New feature for invalid block warning. Should not affect anything else ## Checklist: - [ ] My code is tested. - [ ] My code follows the WordPress code style. <!-- Check code: `npm run lint`, Guidelines: https://make.wordpress.org/core/handbook/best-practices/coding-standards/javascript/ --> - [ ] My code follows the accessibility standards. <!-- Guidelines: https://make.wordpress.org/core/handbook/best-practices/coding-standards/accessibility-coding-standards/ --> - [ ] My code has proper inline documentation. <!-- Guidelines: https://make.wordpress.org/core/handbook/best-practices/inline-documentation-standards/javascript/ --> --- .../src/components/block-list/block-invalid-warning.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/editor/src/components/block-list/block-invalid-warning.js b/packages/editor/src/components/block-list/block-invalid-warning.js index bc4555b1a3f0b..265e36c78a2e8 100644 --- a/packages/editor/src/components/block-list/block-invalid-warning.js +++ b/packages/editor/src/components/block-list/block-invalid-warning.js @@ -36,11 +36,12 @@ export class BlockInvalidWarning extends Component { } render() { - const { convertToHTML, convertToBlocks, convertToClassic, block } = this.props; + const { convertToHTML, convertToBlocks, convertToClassic, attemptBlockRecovery, block } = this.props; const hasHTMLBlock = !! getBlockType( 'core/html' ); const { compare } = this.state; const hiddenActions = [ { title: __( 'Convert to Classic Block' ), onClick: convertToClassic }, + { title: __( 'Attempt Block Recovery' ), onClick: attemptBlockRecovery }, ]; if ( compare ) { @@ -96,6 +97,7 @@ const blockToHTML = ( block ) => createBlock( 'core/html', { const blockToBlocks = ( block ) => rawHandler( { HTML: block.originalContent, } ); +const recoverBlock = ( { name, attributes, innerBlocks } ) => createBlock( name, attributes, innerBlocks ); export default compose( [ withSelect( ( select, { clientId } ) => ( { @@ -114,6 +116,9 @@ export default compose( [ convertToBlocks() { replaceBlock( block.clientId, blockToBlocks( block ) ); }, + attemptBlockRecovery() { + replaceBlock( block.clientId, recoverBlock( block ) ); + }, }; } ), ] )( BlockInvalidWarning ); From 5017388df003fbb85faa101d4c4c824d8a2e591c Mon Sep 17 00:00:00 2001 From: Joen Asmussen <joen@automattic.com> Date: Fri, 18 Jan 2019 16:37:42 +0100 Subject: [PATCH 175/691] Try better color swatch selection indicator (#13274) --- packages/components/src/color-palette/index.js | 2 ++ packages/components/src/color-palette/style.scss | 15 ++++++++++++--- .../test/__snapshots__/index.js.snap | 6 ++++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/components/src/color-palette/index.js b/packages/components/src/color-palette/index.js index 5ab52f277df71..3dfe358954e1f 100644 --- a/packages/components/src/color-palette/index.js +++ b/packages/components/src/color-palette/index.js @@ -8,6 +8,7 @@ import { map } from 'lodash'; * WordPress dependencies */ import { __, sprintf } from '@wordpress/i18n'; +import Dashicon from '../dashicon'; /** * Internal dependencies @@ -49,6 +50,7 @@ export default function ColorPalette( { colors, disableCustomColors = false, val aria-pressed={ value === color } /> </Tooltip> + { value === color && <Dashicon icon="saved" /> } </div> ); } ) } diff --git a/packages/components/src/color-palette/style.scss b/packages/components/src/color-palette/style.scss index 6627ad0c12420..b5c5d9686e25f 100644 --- a/packages/components/src/color-palette/style.scss +++ b/packages/components/src/color-palette/style.scss @@ -44,6 +44,13 @@ $color-palette-circle-spacing: 14px; &.is-active { box-shadow: inset 0 0 0 4px; + border: $border-width solid $dark-gray-400; + + & + .dashicons-saved { + position: absolute; + left: 4px; + top: 4px; + } } &::after { @@ -53,7 +60,7 @@ $color-palette-circle-spacing: 14px; left: 0; bottom: 0; right: 0; - border-radius: 50%; + border-radius: $radius-round; box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2); } @@ -61,13 +68,15 @@ $color-palette-circle-spacing: 14px; outline: none; &::after { content: ""; - border: $border-width solid $dark-gray-400; + position: absolute; + border: #{ $border-width * 2 } solid $dark-gray-400; width: 32px; height: 32px; position: absolute; top: -2px; left: -2px; - border-radius: 50%; + border-radius: $radius-round; + box-shadow: inset 0 0 0 2px $white; } } } diff --git a/packages/components/src/color-palette/test/__snapshots__/index.js.snap b/packages/components/src/color-palette/test/__snapshots__/index.js.snap index cb4980913feb5..cbb6798ae5444 100644 --- a/packages/components/src/color-palette/test/__snapshots__/index.js.snap +++ b/packages/components/src/color-palette/test/__snapshots__/index.js.snap @@ -133,6 +133,9 @@ exports[`ColorPalette should allow disabling custom color picker 1`] = ` type="button" /> </Tooltip> + <Dashicon + icon="saved" + /> </div> <div className="components-color-palette__item-wrapper" @@ -212,6 +215,9 @@ exports[`ColorPalette should render a dynamic toolbar of colors 1`] = ` type="button" /> </Tooltip> + <Dashicon + icon="saved" + /> </div> <div className="components-color-palette__item-wrapper" From 0c1f125ed5d2b77ad10673a6bb6970d7b66d98be Mon Sep 17 00:00:00 2001 From: Joen Asmussen <joen@automattic.com> Date: Fri, 18 Jan 2019 16:58:41 +0100 Subject: [PATCH 176/691] Improve scrolling in fullscreen mode, notably for Edge (#13327) In https://github.com/WordPress/gutenberg/pull/12644, we changed the behavior of scrolling for the main content area slightly, to address issues with scrolling on small screens. As part of that, there was one small issue that made it slightly harder to scroll this main content area, only when in fullscreen mode in Microsoft Edge. This PR fixes that. Another side effect of that prior PR was that it unset some of the scrolling rules that prevented CSS bleed (when you scroll to the end of the Block Library and proceed to scroll the body content). This PR restores that. --- packages/edit-post/src/components/layout/style.scss | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/edit-post/src/components/layout/style.scss b/packages/edit-post/src/components/layout/style.scss index 7e31582af07bd..3aa896af7b377 100644 --- a/packages/edit-post/src/components/layout/style.scss +++ b/packages/edit-post/src/components/layout/style.scss @@ -114,11 +114,10 @@ margin-left: $admin-sidebar-width-collapsed; } - // Undo the above rules for fullscreen mode. + // Provide special rules for fullscreen mode. body.is-fullscreen-mode & { margin-left: 0 !important; - position: relative; - top: inherit; + top: $header-height; } } From 837fda74400f6af9810a4ef0b3bb33d22eb0105a Mon Sep 17 00:00:00 2001 From: Sheri Bigelow <sheri@designsimply.com> Date: Fri, 18 Jan 2019 10:58:33 -0700 Subject: [PATCH 177/691] Documentation: mention workflow and high priority labeled issues (#13362) Workflow labels are important for interacting with contributors at all levels and should be encouraged. We should also make sure to communicate well with other groups to ask them to use these workflows, such as the Design Team for the `Needs Design Feedback` label, the Gutenberg Team for the `Needs Decision` label, and all contributors or Support Team members who are developers for the `Needs Technical Feedback` label. This PR adds a note about workflow labels and also mentions that high priority issues should have an assignee and/or be in an active milestone. I also updated the `Accessibility` label to `Needs Accessibility Feedback` as it makes more sense for the description in this document. --- docs/contributors/repository-management.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/contributors/repository-management.md b/docs/contributors/repository-management.md index 132f46d0df1ed..d4e8df0ebf6aa 100644 --- a/docs/contributors/repository-management.md +++ b/docs/contributors/repository-management.md @@ -25,13 +25,17 @@ Any issues that are irrelevant or not actionable should be closed, because they To better organize the issue backlog, all issues should have [one or more labels](https://github.com/WordPress/gutenberg/labels). Here are some you might commonly see: -- [Accessibility](https://github.com/WordPress/gutenberg/labels/Accessibility) - Changes that impact accessibility and need corresponding review (e.g. markup changes). +- [Needs Accessibility Feedback](https://github.com/WordPress/gutenberg/labels/Accessibility) - Changes that impact accessibility and need corresponding review (e.g. markup changes). - [Needs Design Feedback](https://github.com/WordPress/gutenberg/labels/Needs%20Design%20Feedback) - Changes that modify the design or user experience in some way and need sign-off. - [[Type] Bug](https://github.com/WordPress/gutenberg/labels/%5BType%5D%20Bug) - An existing feature is broken in some way. - [[Type] Enhancement](https://github.com/WordPress/gutenberg/labels/%5BType%5D%20Enhancement) - Gutenberg would be better with this improvement added. - [[Type] Plugin / Extension Conflict](https://github.com/WordPress/gutenberg/labels/%5BType%5D%20Plugin%20%2F%20Extension%20Conflict) - Documentation of a conflict between Gutenberg and a plugin or extension. The plugin author should be informed and provided documentation on how to address. - [[Status] Needs More Info](https://github.com/WordPress/gutenberg/labels/%5BStatus%5D%20Needs%20More%20Info) - The issue needs more information in order to be actionable and relevant. Typically this requires follow-up from the original reporter. +Workflow labels may be applied as needed and start with “Needs”. Ideally, each workflow label will have a group that follows it, such as the Accessibility Team for `Needs Accessibility Feedback`, the Testing Team for `Needs Testing`, etc. + +`Priority High` and `Priority OMGWTFBBQ` issues should have an assignee and/or be in an active milestone. + [Check out the label directory](https://github.com/WordPress/gutenberg/labels) for a listing of all labels. ### Milestones From 616e7f7b8ee484c026dd111b29d8fd850e8e3ea7 Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.p.richards@gmail.com> Date: Mon, 21 Jan 2019 16:53:36 +0800 Subject: [PATCH 178/691] Bump plugin version to 4.9-rc.1 (#13396) --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 2df698bd41413..dd6c91af48197 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,7 +3,7 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the new block editor in core. - * Version: 4.8.0 + * Version: 4.9.0-rc.1 * Author: Gutenberg Team * * @package gutenberg diff --git a/package-lock.json b/package-lock.json index 1566caa308054..acb449cec52f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "4.8.0", + "version": "4.9.0-rc.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 05f1da17f29ba..a50ef6c984a70 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "4.8.0", + "version": "4.9.0-rc.1", "private": true, "description": "A new WordPress editor experience", "repository": "git+https://github.com/WordPress/gutenberg.git", From 4560d7bcfcf1823a48ed9556da2e9e9d5c9bb4fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Mon, 21 Jan 2019 10:34:11 +0100 Subject: [PATCH 179/691] Chore: Update browserlist package to fix test errors (#13395) * Chore: Update browserlist package to fix test errors * Update more dependencies to ensure that browserslit uses the latest version --- package-lock.json | 1638 +++++++++++++++++++------- package.json | 2 +- packages/postcss-themes/package.json | 4 +- packages/scripts/package.json | 2 +- 4 files changed, 1209 insertions(+), 437 deletions(-) diff --git a/package-lock.json b/package-lock.json index acb449cec52f5..ab56fd83b7488 100644 --- a/package-lock.json +++ b/package-lock.json @@ -713,40 +713,6 @@ "invariant": "^2.2.2", "js-levenshtein": "^1.1.3", "semver": "^5.3.0" - }, - "dependencies": { - "browserslist": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.1.0.tgz", - "integrity": "sha512-kQBKB8hnq1SRfSpwHDpM1JNHAyk9fydW8hIDvndR2ijTFKIlBPEvkJkCt8JznOugdm12/YCaRgyq/sqDGz9PwA==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30000878", - "electron-to-chromium": "^1.3.61", - "node-releases": "^1.0.0-alpha.11" - } - }, - "caniuse-lite": { - "version": "1.0.30000878", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000878.tgz", - "integrity": "sha512-/dCGTdLCnjVJno1mFRn7Y6eit3AYaeFzSrMQHCoK0LEQaWl5snuLex1Ky4b8/Qu2ig5NgTX4cJx65hH9546puA==", - "dev": true - }, - "electron-to-chromium": { - "version": "1.3.61", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.61.tgz", - "integrity": "sha512-XjTdsm6x71Y48lF9EEvGciwXD70b20g0t+3YbrE+0fPFutqV08DSNrZXkoXAp3QuzX7TpL/OW+/VsNoR9GkuNg==", - "dev": true - }, - "node-releases": { - "version": "1.0.0-alpha.11", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.0.0-alpha.11.tgz", - "integrity": "sha512-CaViu+2FqTNYOYNihXa5uPS/zry92I3vPU4nCB6JB3OeZ2UGtOpF5gRwuN4+m3hbEcL47bOXyun1jX2iC+3uEQ==", - "dev": true, - "requires": { - "semver": "^5.3.0" - } - } } }, "@babel/runtime": { @@ -2109,6 +2075,33 @@ "integrity": "sha512-53ElVDSnZeFUUFIYzI8WLQ25IhWzb6vbddNp8UHlXQyU0ET2RhV5zg0NfubzU7iNMh5bBXb0htCzfvrSVNgzaQ==", "dev": true }, + "@types/unist": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.2.tgz", + "integrity": "sha512-iHI60IbyfQilNubmxsq4zqSjdynlmc2Q/QvH9kjzg9+CCYVVzq1O6tc7VBzSygIwnmOt07w80IG6HDQvjv3Liw==", + "dev": true + }, + "@types/vfile": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/vfile/-/vfile-3.0.2.tgz", + "integrity": "sha512-b3nLFGaGkJ9rzOcuXRfHkZMdjsawuDD0ENL9fzTophtBg8FJHSGbH7daXkEpcwy3v7Xol3pAvsmlYyFhR4pqJw==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/unist": "*", + "@types/vfile-message": "*" + } + }, + "@types/vfile-message": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/vfile-message/-/vfile-message-1.0.1.tgz", + "integrity": "sha512-mlGER3Aqmq7bqR1tTTIVHq8KSAFFRyGbrxuM8C/H82g6k7r2fS+IMEkIu3D7JHzG10NvPdR8DNx0jr0pwpp4dA==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/unist": "*" + } + }, "@webassemblyjs/ast": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.4.3.tgz", @@ -2762,8 +2755,8 @@ "dev": true, "requires": { "@babel/runtime": "^7.0.0", - "autoprefixer": "^8.2.0", - "postcss": "^6.0.16", + "autoprefixer": "^9.4.5", + "postcss": "^7.0.13", "postcss-color-function": "^4.0.1" } }, @@ -2810,7 +2803,7 @@ "puppeteer": "1.6.1", "read-pkg-up": "^1.0.1", "resolve-bin": "^0.4.0", - "stylelint": "^9.5.0", + "stylelint": "^9.10.1", "stylelint-config-wordpress": "^13.1.0" } }, @@ -3329,17 +3322,25 @@ "dev": true }, "autoprefixer": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-8.2.0.tgz", - "integrity": "sha512-xBVQpGAcSNNS1PBnEfT+F9VF8ZJeoKZ121I3OVQ0n1F0SqVuj4oLI6yFeEviPV8Z/GjoqBRXcYis0oSS8zjNEg==", + "version": "9.4.5", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.4.5.tgz", + "integrity": "sha512-M602C0ZxzFpJKqD4V6eq2j+K5CkzlhekCrcQupJmAOrPEZjWJyj/wSeo6qRSNoN6M3/9mtLPQqTTrABfReytQg==", "dev": true, "requires": { - "browserslist": "^3.2.0", - "caniuse-lite": "^1.0.30000817", + "browserslist": "^4.4.0", + "caniuse-lite": "^1.0.30000928", "normalize-range": "^0.1.2", "num2fraction": "^1.2.2", - "postcss": "^6.0.20", - "postcss-value-parser": "^3.2.3" + "postcss": "^7.0.11", + "postcss-value-parser": "^3.3.1" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } } }, "autosize": { @@ -4869,13 +4870,31 @@ } }, "browserslist": { - "version": "3.2.8", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-3.2.8.tgz", - "integrity": "sha512-WHVocJYavUwVgVViC0ORikPHQquXwVh939TaelZ4WDqpWgTX/FsGhl/+P4qBUAGcRvtOgDgC+xftNWWp2RUTAQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.4.1.tgz", + "integrity": "sha512-pEBxEXg7JwaakBXjATYw/D1YZh4QUSCX/Mnd/wnqSRPPSi1U39iDhDoKGoBUcraKdxDlrYqJxSI5nNvD+dWP2A==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30000844", - "electron-to-chromium": "^1.3.47" + "caniuse-lite": "^1.0.30000929", + "electron-to-chromium": "^1.3.103", + "node-releases": "^1.1.3" + }, + "dependencies": { + "electron-to-chromium": { + "version": "1.3.103", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.103.tgz", + "integrity": "sha512-tObPqGmY9X8MUM8i3MEimYmbnLLf05/QV5gPlkR8MQ3Uj8G8B2govE1U4cQcBYtv3ymck9Y8cIOu4waoiykMZQ==", + "dev": true + }, + "node-releases": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.3.tgz", + "integrity": "sha512-6VrvH7z6jqqNFY200kdB6HdzkgM96Oaj9v3dqGfgp6mF+cHmU4wyQKZ2/WPDRVoR0Jz9KqbamaBN0ZhdUaysUQ==", + "dev": true, + "requires": { + "semver": "^5.3.0" + } + } } }, "bser": { @@ -5102,9 +5121,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30000865", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000865.tgz", - "integrity": "sha512-vs79o1mOSKRGv/1pSkp4EXgl4ZviWeYReXw60XfacPU64uQWZwJT6vZNmxRF9O+6zu71sJwMxLK5JXxbzuVrLw==", + "version": "1.0.30000929", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000929.tgz", + "integrity": "sha512-n2w1gPQSsYyorSVYqPMqbSaz1w7o9ZC8VhOEGI9T5MfGDzp7sbopQxG6GaQmYsaq13Xfx/mkxJUWC1Dz3oZfzw==", "dev": true }, "capture-exit": { @@ -6563,6 +6582,25 @@ "requires": { "postcss": "^6.0.0", "timsort": "^0.3.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "css-select": { @@ -6627,6 +6665,25 @@ "cssnano-preset-default": "^4.0.0", "is-resolvable": "^1.0.0", "postcss": "^6.0.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "cssnano-preset-default": { @@ -6665,6 +6722,25 @@ "postcss-reduce-transforms": "^4.0.0", "postcss-svgo": "^4.0.0", "postcss-unique-selectors": "^4.0.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "cssnano-util-get-arguments": { @@ -6686,6 +6762,25 @@ "dev": true, "requires": { "postcss": "^6.0.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "cssnano-util-same-parent": { @@ -8632,6 +8727,12 @@ "write": "^0.2.1" } }, + "flatted": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.0.tgz", + "integrity": "sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg==", + "dev": true + }, "flatten": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz", @@ -13204,9 +13305,9 @@ "dev": true }, "known-css-properties": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.6.1.tgz", - "integrity": "sha512-nQRpMcHm1cQ6gmztdvLcIvxocznSMqH/y6XtERrWrHaymOYdDGroRqetJvJycxGEr1aakXiigDgn7JnzuXlk6A==", + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.11.0.tgz", + "integrity": "sha512-bEZlJzXo5V/ApNNa5z375mJC6Nrz4vG43UgcSCrg2OHC+yuB6j0iDSrY7RQ/+PRofFB03wNIIt9iXIVLr4wc7w==", "dev": true }, "lazy-cache": { @@ -16500,21 +16601,52 @@ "dev": true }, "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.13.tgz", + "integrity": "sha512-h8SY6kQTd1wISHWjz+E6cswdhMuyBZRb16pSTv3W4zYZ3/YbyWeJdNUeOXB5IdZqE1U76OUEjjjqsC3z2f3hVg==", "dev": true, "requires": { - "chalk": "^2.4.1", + "chalk": "^2.4.2", "source-map": "^0.6.1", - "supports-color": "^5.4.0" + "supports-color": "^6.1.0" }, "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, @@ -16528,6 +16660,25 @@ "postcss": "^6.0.0", "postcss-selector-parser": "^2.2.2", "reduce-css-calc": "^2.0.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "postcss-color-function": { @@ -16540,6 +16691,25 @@ "postcss": "^6.0.1", "postcss-message-helpers": "^2.0.0", "postcss-value-parser": "^3.3.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "postcss-colormin": { @@ -16585,6 +16755,23 @@ "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } + }, + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true } } }, @@ -16596,6 +16783,25 @@ "requires": { "postcss": "^6.0.0", "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "postcss-discard-comments": { @@ -16605,6 +16811,25 @@ "dev": true, "requires": { "postcss": "^6.0.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "postcss-discard-duplicates": { @@ -16614,6 +16839,25 @@ "dev": true, "requires": { "postcss": "^6.0.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "postcss-discard-empty": { @@ -16623,6 +16867,25 @@ "dev": true, "requires": { "postcss": "^6.0.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "postcss-discard-overridden": { @@ -16632,16 +16895,35 @@ "dev": true, "requires": { "postcss": "^6.0.0" - } - }, - "postcss-discard-unused": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz", - "integrity": "sha1-vOMLLMWR/8Y0Mitfs0ZLbZNPRDM=", - "dev": true, - "requires": { - "postcss": "^5.0.14", - "uniqs": "^2.0.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "postcss-discard-unused": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz", + "integrity": "sha1-vOMLLMWR/8Y0Mitfs0ZLbZNPRDM=", + "dev": true, + "requires": { + "postcss": "^5.0.14", + "uniqs": "^2.0.0" }, "dependencies": { "ansi-regex": { @@ -16796,111 +17078,200 @@ } }, "postcss-html": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-0.33.0.tgz", - "integrity": "sha512-3keDoRG0o8bJZKe/QzkOPUD3GQQvAmYhIAtsGrgTxIXB6xZnSQq3gwPjCEd2IAUtz9/Fkus70XGm6xJEZ+bAmg==", + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-0.36.0.tgz", + "integrity": "sha512-HeiOxGcuwID0AFsNAL0ox3mW6MHH5cstWN1Z3Y+n6H+g12ih7LHdYxWwEA/QmrebctLjo79xz9ouK3MroHwOJw==", "dev": true, "requires": { - "htmlparser2": "^3.9.2" + "htmlparser2": "^3.10.0" + }, + "dependencies": { + "htmlparser2": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.0.tgz", + "integrity": "sha512-J1nEUGv+MkXS0weHNWVKJJ+UrLfePxRWpN3C9bEi9fLxL2+ggW94DQvgYVXsaT30PGwYRIZKNZXuyMhp3Di4bQ==", + "dev": true, + "requires": { + "domelementtype": "^1.3.0", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.0.6" + } + }, + "readable-stream": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.1.1.tgz", + "integrity": "sha512-DkN66hPyqDhnIQ6Jcsvx9bFjhw214O4poMBcIMgPVpQvNy9a0e0Uhg5SqySyDKAmUlwt8LonTBz1ezOnM8pUdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } } }, "postcss-jsx": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/postcss-jsx/-/postcss-jsx-0.33.0.tgz", - "integrity": "sha512-+ZH4FyxQel2O5uYkNKBnDdW2jCwIb5HwwyFsKuEI164Vmq9Wm07nT2lj65P1qDSRXP2Ik05DrSHzY8Hmt5VP4A==", - "dev": true, - "requires": { - "@babel/core": "^7.0.0-rc.1", - "postcss-styled": ">=0.33.0" - } - }, - "postcss-less": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-less/-/postcss-less-2.0.0.tgz", - "integrity": "sha512-pPNsVnpCB13nBMOcl5GVh8JGmB0JGFjqkLUDzKdVpptFFKEe9wFdEzvh2j4lD2AD+7qcrUfw9Ta+oi5+Fw7jjQ==", + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/postcss-jsx/-/postcss-jsx-0.36.0.tgz", + "integrity": "sha512-/lWOSXSX5jlITCKFkuYU2WLFdrncZmjSVyNpHAunEgirZXLwI8RjU556e3Uz4mv0WVHnJA9d3JWb36lK9Yx99g==", "dev": true, "requires": { - "postcss": "^5.2.16" + "@babel/core": ">=7.1.0" }, "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true + "@babel/core": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.2.2.tgz", + "integrity": "sha512-59vB0RWt09cAct5EIe58+NzGP4TFSD3Bz//2/ELy3ZeTeKF6VTD1AXlH8BGGbCX0PuobZBsIzO7IAI9PH67eKw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.2.2", + "@babel/helpers": "^7.2.0", + "@babel/parser": "^7.2.2", + "@babel/template": "^7.2.2", + "@babel/traverse": "^7.2.2", + "@babel/types": "^7.2.2", + "convert-source-map": "^1.1.0", + "debug": "^4.1.0", + "json5": "^2.1.0", + "lodash": "^4.17.10", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + } }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true + "@babel/generator": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.2.2.tgz", + "integrity": "sha512-I4o675J/iS8k+P38dvJ3IBGqObLXyQLTxtrR4u9cSUJOURvafeEWb/pFMOTwtNrmq73mJzyF6ueTbO1BtN0Zeg==", + "dev": true, + "requires": { + "@babel/types": "^7.2.2", + "jsesc": "^2.5.1", + "lodash": "^4.17.10", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + } }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "@babel/helper-function-name": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", + "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" } }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "@babel/helpers": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.2.0.tgz", + "integrity": "sha512-Fr07N+ea0dMcMN8nFpuK6dUIT7/ivt9yKQdEEnjVS83tG2pHwPi03gYmk/tyuwONnZ+sY+GFFPlWGgCtW1hF9A==", + "dev": true, + "requires": { + "@babel/template": "^7.1.2", + "@babel/traverse": "^7.1.5", + "@babel/types": "^7.2.0" + } + }, + "@babel/parser": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.2.3.tgz", + "integrity": "sha512-0LyEcVlfCoFmci8mXx8A5oIkpkOgyo8dRHtxBnK9RRBwxO2+JZPNsqtVEZQ7mJFPxnXF9lfmU24mHOPI0qnlkA==", "dev": true }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "@babel/template": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.2.2.tgz", + "integrity": "sha512-zRL0IMM02AUDwghf5LMSSDEz7sBCO2YnNmpg3uWTZj/v1rcG2BmQUvaGU8GhU8BvfMh1k2KIAYZ7Ji9KXPUg7g==", "dev": true, "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.2.2", + "@babel/types": "^7.2.2" } }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "@babel/traverse": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.2.3.tgz", + "integrity": "sha512-Z31oUD/fJvEWVR0lNZtfgvVt512ForCTNKYcJBGbPb1QZfve4WGH8Wsy7+Mev33/45fhP/hwQtvgusNdcCMgSw==", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.2.2", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.0.0", + "@babel/parser": "^7.2.3", + "@babel/types": "^7.2.2", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.10" } }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "@babel/types": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.2.tgz", + "integrity": "sha512-fKCuD6UFUMkR541eDWL+2ih/xFZBXPOg/7EQFeTluMDebfqR4jrpaCjLhkWlQS4hT6nRa2PMEgXKbRB5/H2fpg==", "dev": true, "requires": { - "has-flag": "^1.0.0" + "esutils": "^2.0.2", + "lodash": "^4.17.10", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" } + }, + "json5": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", + "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true } } }, + "postcss-less": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/postcss-less/-/postcss-less-3.1.1.tgz", + "integrity": "sha512-yVa0hb03p7xj914Z4qDDA/PGwXYvCEfjJizWVYQvnEQr8SgJ098qejCvbCGk1dDYQpQEGKkvYHQCo66DwTocjg==", + "dev": true, + "requires": { + "postcss": "^7.0.3" + } + }, "postcss-markdown": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/postcss-markdown/-/postcss-markdown-0.33.0.tgz", - "integrity": "sha512-JZtetO15t5nNpymHDbRhuiOF8yJm1btrbUBP3iL39yLTiY8oChCsnCKfQjEuHB9+85fku5MoU/bRgQ8K45klMg==", + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/postcss-markdown/-/postcss-markdown-0.36.0.tgz", + "integrity": "sha512-rl7fs1r/LNSB2bWRhyZ+lM/0bwKv9fhl38/06gF6mKMo/NPnp55+K1dSTosSVjFZc0e1ppBlu+WT91ba0PMBfQ==", "dev": true, "requires": { - "remark": "^9.0.0", + "remark": "^10.0.1", "unist-util-find-all-after": "^1.0.2" } }, @@ -17001,6 +17372,25 @@ "postcss": "^6.0.0", "postcss-value-parser": "^3.0.0", "stylehacks": "^4.0.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "postcss-merge-rules": { @@ -17028,6 +17418,17 @@ "node-releases": "^1.0.0-alpha.10" } }, + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, "postcss-selector-parser": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz", @@ -17038,6 +17439,12 @@ "indexes-of": "^1.0.1", "uniq": "^1.0.1" } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true } } }, @@ -17055,6 +17462,25 @@ "requires": { "postcss": "^6.0.0", "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "postcss-minify-gradients": { @@ -17067,6 +17493,25 @@ "is-color-stop": "^1.0.0", "postcss": "^6.0.0", "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "postcss-minify-params": { @@ -17080,6 +17525,25 @@ "postcss": "^6.0.0", "postcss-value-parser": "^3.0.0", "uniqs": "^2.0.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "postcss-minify-selectors": { @@ -17094,6 +17558,17 @@ "postcss-selector-parser": "^3.0.0" }, "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, "postcss-selector-parser": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz", @@ -17104,6 +17579,12 @@ "indexes-of": "^1.0.1", "uniq": "^1.0.1" } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true } } }, @@ -17114,6 +17595,25 @@ "dev": true, "requires": { "postcss": "^6.0.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "postcss-normalize-display-values": { @@ -17125,6 +17625,25 @@ "cssnano-util-get-match": "^4.0.0", "postcss": "^6.0.0", "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "postcss-normalize-positions": { @@ -17137,6 +17656,25 @@ "has": "^1.0.0", "postcss": "^6.0.0", "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "postcss-normalize-repeat-style": { @@ -17149,6 +17687,25 @@ "cssnano-util-get-match": "^4.0.0", "postcss": "^6.0.0", "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "postcss-normalize-string": { @@ -17160,6 +17717,25 @@ "has": "^1.0.0", "postcss": "^6.0.0", "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "postcss-normalize-timing-functions": { @@ -17171,6 +17747,25 @@ "cssnano-util-get-match": "^4.0.0", "postcss": "^6.0.0", "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "postcss-normalize-unicode": { @@ -17181,6 +17776,25 @@ "requires": { "postcss": "^6.0.0", "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "postcss-normalize-url": { @@ -17200,6 +17814,23 @@ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.2.0.tgz", "integrity": "sha512-WvF3Myk0NhXkG8S9bygFM4IC1KOvnVJGq0QoGeoqOYOBeinBZp5ybW3QuYbTc89lkWBMM9ZBO4QGRoc0353kKA==", "dev": true + }, + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true } } }, @@ -17211,6 +17842,25 @@ "requires": { "postcss": "^6.0.0", "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "postcss-ordered-values": { @@ -17222,6 +17872,25 @@ "cssnano-util-get-arguments": "^4.0.0", "postcss": "^6.0.0", "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "postcss-reduce-idents": { @@ -17327,6 +17996,23 @@ "electron-to-chromium": "^1.3.52", "node-releases": "^1.0.0-alpha.10" } + }, + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true } } }, @@ -17340,18 +18026,45 @@ "has": "^1.0.0", "postcss": "^6.0.0", "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "postcss-reporter": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-5.0.0.tgz", - "integrity": "sha512-rBkDbaHAu5uywbCR2XE8a25tats3xSOsGNx6mppK6Q9kSFGKc/FyAzfci+fWM2l+K402p1D0pNcfDGxeje5IKg==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-6.0.1.tgz", + "integrity": "sha512-LpmQjfRWyabc+fRygxZjpRxfhRf9u/fdlKf4VHG4TSPbV2XNsuISzYW1KL+1aQzx53CAppa1bKG4APIB/DOXXw==", "dev": true, "requires": { - "chalk": "^2.0.1", - "lodash": "^4.17.4", - "log-symbols": "^2.0.0", - "postcss": "^6.0.8" + "chalk": "^2.4.1", + "lodash": "^4.17.11", + "log-symbols": "^2.2.0", + "postcss": "^7.0.7" + }, + "dependencies": { + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + } } }, "postcss-resolve-nested-selector": { @@ -17367,54 +18080,16 @@ "dev": true, "requires": { "postcss": "^7.0.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.2.tgz", - "integrity": "sha512-fmaUY5370keLUTx+CnwRxtGiuFTcNBLQBqr1oE3WZ/euIYmGAo0OAgOhVJ3ByDnVmOR3PK+0V9VebzfjRIUcqw==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, "postcss-sass": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/postcss-sass/-/postcss-sass-0.3.2.tgz", - "integrity": "sha512-0HgxikiZ07VKYr98KT+k7/rAzyMgZlP+3+R8vUti56T2dPdhW0OhPGDQzddxY/N2iDtBVZQqCHRDA09j5I6EWg==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/postcss-sass/-/postcss-sass-0.3.5.tgz", + "integrity": "sha512-B5z2Kob4xBxFjcufFnhQ2HqJQ2y/Zs/ic5EZbCywCkxKd756Q40cIQ/veRDwSrw1BF6+4wUgmpm0sBASqVi65A==", "dev": true, "requires": { - "gonzales-pe": "4.2.3", - "postcss": "6.0.22" - }, - "dependencies": { - "postcss": { - "version": "6.0.22", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.22.tgz", - "integrity": "sha512-Toc9lLoUASwGqxBSJGTVcOQiDqjK+Z2XlWBg+IgYwQMY9vA2f7iMpXVc1GpPcfTSyM5lkxNo0oDwDRO+wm7XHA==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } + "gonzales-pe": "^4.2.3", + "postcss": "^7.0.1" } }, "postcss-scss": { @@ -17424,25 +18099,6 @@ "dev": true, "requires": { "postcss": "^7.0.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.2.tgz", - "integrity": "sha512-fmaUY5370keLUTx+CnwRxtGiuFTcNBLQBqr1oE3WZ/euIYmGAo0OAgOhVJ3ByDnVmOR3PK+0V9VebzfjRIUcqw==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, "postcss-selector-parser": { @@ -17456,12 +18112,6 @@ "uniq": "^1.0.1" } }, - "postcss-styled": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/postcss-styled/-/postcss-styled-0.33.0.tgz", - "integrity": "sha512-ybKIBKYY6q0hADQUECW2F4fDybDFIiAfpMf06/2maxU0yp0FvMTeABrDjzSmKu+99Nj2Gsxe80Xn56FbhzIZZQ==", - "dev": true - }, "postcss-svgo": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.0.tgz", @@ -17472,12 +18122,31 @@ "postcss": "^6.0.0", "postcss-value-parser": "^3.0.0", "svgo": "^1.0.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "postcss-syntax": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/postcss-syntax/-/postcss-syntax-0.33.0.tgz", - "integrity": "sha512-A9ABlaRy7KWUfG5E39GVTUoc5TXNuNTts5GzwDLwnSaVG151CSLCTcr51/m8cHi4KXcYa+5ImLyeSfBOhEYtGw==", + "version": "0.36.2", + "resolved": "https://registry.npmjs.org/postcss-syntax/-/postcss-syntax-0.36.2.tgz", + "integrity": "sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w==", "dev": true }, "postcss-unique-selectors": { @@ -17489,6 +18158,25 @@ "alphanum-sort": "^1.0.0", "postcss": "^6.0.0", "uniqs": "^2.0.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "postcss-value-parser": { @@ -18556,20 +19244,20 @@ } }, "remark": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/remark/-/remark-9.0.0.tgz", - "integrity": "sha512-amw8rGdD5lHbMEakiEsllmkdBP+/KpjW/PRK6NSGPZKCQowh0BT4IWXDAkRMyG3SB9dKPXWMviFjNusXzXNn3A==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/remark/-/remark-10.0.1.tgz", + "integrity": "sha512-E6lMuoLIy2TyiokHprMjcWNJ5UxfGQjaMSMhV+f4idM625UjjK4j798+gPs5mfjzDE6vL0oFKVeZM6gZVSVrzQ==", "dev": true, "requires": { - "remark-parse": "^5.0.0", - "remark-stringify": "^5.0.0", - "unified": "^6.0.0" + "remark-parse": "^6.0.0", + "remark-stringify": "^6.0.0", + "unified": "^7.0.0" } }, "remark-parse": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-5.0.0.tgz", - "integrity": "sha512-b3iXszZLH1TLoyUzrATcTQUZrwNl1rE70rVdSruJFlDaJ9z5aMkhrG43Pp68OgfHndL/ADz6V69Zow8cTQu+JA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-6.0.3.tgz", + "integrity": "sha512-QbDXWN4HfKTUC0hHa4teU463KclLAnwpn/FBn87j9cKYJWWawbiLgMfP2Q4XwhxxuuuOxHlw+pSN0OKuJwyVvg==", "dev": true, "requires": { "collapse-white-space": "^1.0.2", @@ -18590,9 +19278,9 @@ } }, "remark-stringify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-5.0.0.tgz", - "integrity": "sha512-Ws5MdA69ftqQ/yhRF9XhVV29mhxbfGhbz0Rx5bQH+oJcNhhSM6nCu1EpLod+DjrFGrU0BMPs+czVmJZU7xiS7w==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-6.0.4.tgz", + "integrity": "sha512-eRWGdEPMVudijE/psbIDNcnJLRVx3xhfuEsTDGgH4GsFF91dVhw5nhmnBppafJ7+NWINW6C7ZwWbi30ImJzqWg==", "dev": true, "requires": { "ccount": "^1.0.0", @@ -18902,6 +19590,25 @@ "mkdirp": "^0.5.1", "postcss": "^6.0.14", "strip-json-comments": "^2.0.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "run-async": { @@ -19761,9 +20468,9 @@ "dev": true }, "specificity": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/specificity/-/specificity-0.4.0.tgz", - "integrity": "sha512-nGUlURFuoSsmJQ2TBKaO2l7+dBHtRnofSSQdiFKEpd+HBDWXR9/+gtJfgNpe3Nh6o5mqSxDpin/M4YoN7AijGg==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/specificity/-/specificity-0.4.1.tgz", + "integrity": "sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==", "dev": true }, "split": { @@ -20106,17 +20813,17 @@ "browserslist": "^4.0.0", "postcss": "^6.0.0", "postcss-selector-parser": "^3.0.0" - }, - "dependencies": { - "browserslist": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.0.1.tgz", - "integrity": "sha512-QqiiIWchEIkney3wY53/huI7ZErouNAdvOkjorUALAwRcu3tEwOV3Sh6He0DnP38mz1JjBpCBb50jQBmaYuHPw==", + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30000865", - "electron-to-chromium": "^1.3.52", - "node-releases": "^1.0.0-alpha.10" + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" } }, "postcss-selector-parser": { @@ -20129,141 +20836,138 @@ "indexes-of": "^1.0.1", "uniq": "^1.0.1" } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true } } }, "stylelint": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-9.5.0.tgz", - "integrity": "sha512-63R/DGDjMekFwS4xaHSLy26N19pT1Jsxj7u5QNcJrUWBvvPoBCYx3ObINRgsvNMoupzhV7N0PjylxrDHyh4cKQ==", + "version": "9.10.1", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-9.10.1.tgz", + "integrity": "sha512-9UiHxZhOAHEgeQ7oLGwrwoDR8vclBKlSX7r4fH0iuu0SfPwFaLkb1c7Q2j1cqg9P7IDXeAV2TvQML/fRQzGBBQ==", "dev": true, "requires": { "autoprefixer": "^9.0.0", "balanced-match": "^1.0.0", "chalk": "^2.4.1", "cosmiconfig": "^5.0.0", - "debug": "^3.0.0", + "debug": "^4.0.0", "execall": "^1.0.0", - "file-entry-cache": "^2.0.0", + "file-entry-cache": "^4.0.0", "get-stdin": "^6.0.0", - "globby": "^8.0.0", + "global-modules": "^2.0.0", + "globby": "^9.0.0", "globjoin": "^0.1.4", "html-tags": "^2.0.0", - "ignore": "^4.0.0", + "ignore": "^5.0.4", "import-lazy": "^3.1.0", "imurmurhash": "^0.1.4", - "known-css-properties": "^0.6.0", + "known-css-properties": "^0.11.0", + "leven": "^2.1.0", "lodash": "^4.17.4", "log-symbols": "^2.0.0", "mathml-tag-names": "^2.0.1", "meow": "^5.0.0", - "micromatch": "^2.3.11", + "micromatch": "^3.1.10", "normalize-selector": "^0.2.0", "pify": "^4.0.0", - "postcss": "^7.0.0", - "postcss-html": "^0.33.0", - "postcss-jsx": "^0.33.0", - "postcss-less": "^2.0.0", - "postcss-markdown": "^0.33.0", + "postcss": "^7.0.13", + "postcss-html": "^0.36.0", + "postcss-jsx": "^0.36.0", + "postcss-less": "^3.1.0", + "postcss-markdown": "^0.36.0", "postcss-media-query-parser": "^0.2.3", - "postcss-reporter": "^5.0.0", + "postcss-reporter": "^6.0.0", "postcss-resolve-nested-selector": "^0.1.1", "postcss-safe-parser": "^4.0.0", - "postcss-sass": "^0.3.0", + "postcss-sass": "^0.3.5", "postcss-scss": "^2.0.0", "postcss-selector-parser": "^3.1.0", - "postcss-styled": "^0.33.0", - "postcss-syntax": "^0.33.0", + "postcss-syntax": "^0.36.2", "postcss-value-parser": "^3.3.0", "resolve-from": "^4.0.0", "signal-exit": "^3.0.2", - "specificity": "^0.4.0", - "string-width": "^2.1.0", + "slash": "^2.0.0", + "specificity": "^0.4.1", + "string-width": "^3.0.0", "style-search": "^0.1.0", "sugarss": "^2.0.0", "svg-tags": "^1.0.0", - "table": "^4.0.1" + "table": "^5.0.0" }, "dependencies": { - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true, - "requires": { - "arr-flatten": "^1.0.1" - } + "@nodelib/fs.stat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", + "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", + "dev": true }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "ansi-regex": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", + "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", "dev": true }, - "autoprefixer": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.1.1.tgz", - "integrity": "sha512-Q/2zhVEglXXCBNCOFfnihQcQystPYJt4GrIopeWhPjFxRPXya8eOstB89AafW0nWhSscByp+rSXp9EE5X4zgXQ==", + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { - "browserslist": "^4.0.2", - "caniuse-lite": "^1.0.30000876", - "normalize-range": "^0.1.2", - "num2fraction": "^1.2.2", - "postcss": "^7.0.2", - "postcss-value-parser": "^3.2.3" + "ms": "^2.1.1" } }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "dir-glob": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", + "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", "dev": true, "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" + "path-type": "^3.0.0" } }, - "browserslist": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.0.2.tgz", - "integrity": "sha512-lpujC4zv1trcKUUwfD4pFVNga4YSpB3sLB+/I+A8gvGQxno1c0dMB2aCQy0FE5oUNIDjD9puFiFF0zeS6Ji48w==", + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "fast-glob": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.6.tgz", + "integrity": "sha512-0BvMaZc1k9F+MeWWMe8pL6YltFzZYcJsYU7D4JyDA6PAczaXvxqQQ/z+mDF7/4Mw01DeUc+i3CTKajnkANkV4w==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30000876", - "electron-to-chromium": "^1.3.57", - "node-releases": "^1.0.0-alpha.11" + "@mrmlnc/readdir-enhanced": "^2.2.1", + "@nodelib/fs.stat": "^1.1.2", + "glob-parent": "^3.1.0", + "is-glob": "^4.0.0", + "merge2": "^1.2.3", + "micromatch": "^3.1.10" } }, - "caniuse-lite": { - "version": "1.0.30000877", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000877.tgz", - "integrity": "sha512-h04kV/lcuhItU1CZTJOxUEk/9R+1XeJqgc67E+XC8J9TjPM8kzVgOn27ZtRdDUo8O5F8U4QRCzDWJrVym3w3Cg==", - "dev": true - }, - "electron-to-chromium": { - "version": "1.3.58", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.58.tgz", - "integrity": "sha512-AGJxlBEn2wOohxqWZkISVsOjZueKTQljfEODTDSEiMqSpH0S+xzV+/5oEM9AGaqhu7DzrpKOgU7ocQRjj0nJmg==", - "dev": true - }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "file-entry-cache": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-4.0.0.tgz", + "integrity": "sha512-AVSwsnbV8vH/UVbvgEhf3saVQXORNv0ZzSkvkhQIaia5Tia+JhGTaa/ePUSVoPHQyGayQNmYfkzFi3WZV5zcpA==", "dev": true, "requires": { - "is-posix-bracket": "^0.1.0" + "flat-cache": "^2.0.1" } }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", "dev": true, "requires": { - "is-extglob": "^1.0.0" + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" } }, "get-stdin": { @@ -20272,97 +20976,123 @@ "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", "dev": true }, - "globby": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.1.tgz", - "integrity": "sha512-oMrYrJERnKBLXNLVTqhm3vPEdJ/b2ZE28xN4YARiix1NOIOBPEpOUnm844K1iu/BkphCaf2WNFwMszv8Soi1pw==", + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "requires": { - "array-union": "^1.0.1", - "dir-glob": "^2.0.0", - "fast-glob": "^2.0.2", - "glob": "^7.1.2", - "ignore": "^3.3.5", - "pify": "^3.0.0", - "slash": "^1.0.0" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" }, "dependencies": { - "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", - "dev": true - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } } } }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "global-prefix": "^3.0.0" } }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", "dev": true, "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" } }, - "node-releases": { - "version": "1.0.0-alpha.11", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.0.0-alpha.11.tgz", - "integrity": "sha512-CaViu+2FqTNYOYNihXa5uPS/zry92I3vPU4nCB6JB3OeZ2UGtOpF5gRwuN4+m3hbEcL47bOXyun1jX2iC+3uEQ==", - "dev": true, - "requires": { - "semver": "^5.3.0" + "globby": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-9.0.0.tgz", + "integrity": "sha512-q0qiO/p1w/yJ0hk8V9x1UXlgsXUxlGd0AHUOXZVXBO6aznDtpx7M8D1kBrCAItoPm+4l8r6ATXV1JpjY2SBQOw==", + "dev": true, + "requires": { + "array-union": "^1.0.2", + "dir-glob": "^2.2.1", + "fast-glob": "^2.2.6", + "glob": "^7.1.3", + "ignore": "^4.0.3", + "pify": "^4.0.1", + "slash": "^2.0.0" + }, + "dependencies": { + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + } } }, - "pify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.0.tgz", - "integrity": "sha512-zrSP/KDf9DH3K3VePONoCstgPiYJy9z0SCatZuTpOc7YdnWIqwkWdXOuwlr4uDc7em8QZRsFWsT/685x5InjYg==", + "ignore": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.0.4.tgz", + "integrity": "sha512-WLsTMEhsQuXpCiG173+f3aymI43SXa+fB1rSfbzyP4GkPP+ZFVuO0/3sFUGNBtifisPeDcl/uD/Y2NxZ7xFq4g==", "dev": true }, - "postcss": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.2.tgz", - "integrity": "sha512-fmaUY5370keLUTx+CnwRxtGiuFTcNBLQBqr1oE3WZ/euIYmGAo0OAgOhVJ3ByDnVmOR3PK+0V9VebzfjRIUcqw==", + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", "dev": true, "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" + "is-extglob": "^2.1.1" } }, + "merge2": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.2.3.tgz", + "integrity": "sha512-gdUU1Fwj5ep4kplwcmftruWofEFt6lfpkkr3h860CXbAB9c3hGb55EOL2ali0Td5oebvW0E1+3Sr+Ur7XfKpRA==", + "dev": true + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, "postcss-selector-parser": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz", @@ -20380,11 +21110,49 @@ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", "dev": true + }, + "string-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.0.0.tgz", + "integrity": "sha512-rr8CUxBbvOZDUvc5lNIJ+OC1nPVpz+Siw9VBtUjB9b6jZehZLFt0JMCZzShFHIsI8cbhm0EsNIfWJMFV3cu3Ew==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.0.0" + } + }, + "strip-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.0.0.tgz", + "integrity": "sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow==", + "dev": true, + "requires": { + "ansi-regex": "^4.0.0" + } + }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } } } }, @@ -20447,25 +21215,6 @@ "dev": true, "requires": { "postcss": "^7.0.2" - }, - "dependencies": { - "postcss": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.2.tgz", - "integrity": "sha512-fmaUY5370keLUTx+CnwRxtGiuFTcNBLQBqr1oE3WZ/euIYmGAo0OAgOhVJ3ByDnVmOR3PK+0V9VebzfjRIUcqw==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, "supports-color": { @@ -20553,29 +21302,27 @@ "dev": true }, "table": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/table/-/table-4.0.3.tgz", - "integrity": "sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/table/-/table-5.2.1.tgz", + "integrity": "sha512-qmhNs2GEHNqY5fd2Mo+8N1r2sw/rvTAAvBZTaTx+Y7PHLypqyrxr1MdIu0pLw6Xvl/Gi4ONu/sdceP8vvUjkyA==", "dev": true, "requires": { - "ajv": "^6.0.1", - "ajv-keywords": "^3.0.0", - "chalk": "^2.1.0", - "lodash": "^4.17.4", - "slice-ansi": "1.0.0", + "ajv": "^6.6.1", + "lodash": "^4.17.11", + "slice-ansi": "2.0.0", "string-width": "^2.1.1" }, "dependencies": { "ajv": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.2.tgz", - "integrity": "sha512-hOs7GfvI6tUI1LfZddH82ky6mOMyTuY0mk7kE2pWpmhhUSkumzaTO5vbVwij39MdwPQWCV4Zv57Eo06NtL/GVA==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.7.0.tgz", + "integrity": "sha512-RZXPviBTtfmtka9n9sy1N5M5b82CbxWIR6HIis4s3WQTXDJamc/0gpCWNGz6EWdWp4DOfjzJfhz/AS9zVPjjWg==", "dev": true, "requires": { "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.1" + "uri-js": "^4.2.2" } }, "fast-deep-equal": { @@ -20589,6 +21336,23 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "slice-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.0.0.tgz", + "integrity": "sha512-4j2WTWjp3GsZ+AOagyzVbzp4vWGtZ0hEZ/gDY/uTvm6MTxUfTUIsnMIFb1bn8o0RuXiqUw15H1bue8f22Vw2oQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + } } } }, @@ -21153,16 +21917,18 @@ "dev": true }, "unified": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/unified/-/unified-6.2.0.tgz", - "integrity": "sha512-1k+KPhlVtqmG99RaTbAv/usu85fcSRu3wY8X+vnsEhIxNP5VbVIDiXnLqyKIG+UMdyTg0ZX9EI6k2AfjJkHPtA==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/unified/-/unified-7.1.0.tgz", + "integrity": "sha512-lbk82UOIGuCEsZhPj8rNAkXSDXd6p0QLzIuSsCdxrqnqU56St4eyOB+AlXsVgVeRmetPTYydIuvFfpDIed8mqw==", "dev": true, "requires": { + "@types/unist": "^2.0.0", + "@types/vfile": "^3.0.0", "bail": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^1.1.0", "trough": "^1.0.0", - "vfile": "^2.0.0", + "vfile": "^3.0.0", "x-is-string": "^0.1.0" } }, @@ -21501,17 +22267,23 @@ } }, "vfile": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-2.3.0.tgz", - "integrity": "sha512-ASt4mBUHcTpMKD/l5Q+WJXNtshlWxOogYyGYYrg4lt/vuRjC1EFQtlAofL5VmtVNIZJzWYFJjzGWZ0Gw8pzW1w==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-3.0.1.tgz", + "integrity": "sha512-y7Y3gH9BsUSdD4KzHsuMaCzRjglXN0W2EcMf0gpvu6+SbsGhMje7xDc8AEoeXy6mIwCKMI6BkjMsRjzQbhMEjQ==", "dev": true, "requires": { - "is-buffer": "^1.1.4", + "is-buffer": "^2.0.0", "replace-ext": "1.0.0", "unist-util-stringify-position": "^1.0.0", "vfile-message": "^1.0.0" }, "dependencies": { + "is-buffer": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", + "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==", + "dev": true + }, "replace-ext": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", diff --git a/package.json b/package.json index a50ef6c984a70..ccfebf409fcd2 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "@wordpress/scripts": "file:packages/scripts", "babel-loader": "8.0.0", "benchmark": "2.1.4", - "browserslist": "3.2.8", + "browserslist": "4.4.1", "chalk": "2.4.1", "concurrently": "3.5.0", "copy-webpack-plugin": "4.5.2", diff --git a/packages/postcss-themes/package.json b/packages/postcss-themes/package.json index 30b5a6b85572f..ced2f27afbd11 100644 --- a/packages/postcss-themes/package.json +++ b/packages/postcss-themes/package.json @@ -27,8 +27,8 @@ "main": "build/index.js", "dependencies": { "@babel/runtime": "^7.0.0", - "autoprefixer": "^8.2.0", - "postcss": "^6.0.16", + "autoprefixer": "^9.4.5", + "postcss": "^7.0.13", "postcss-color-function": "^4.0.1" }, "publishConfig": { diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 4c6b0b918b8ad..31441fc5fdd49 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -45,7 +45,7 @@ "puppeteer": "1.6.1", "read-pkg-up": "^1.0.1", "resolve-bin": "^0.4.0", - "stylelint": "^9.5.0", + "stylelint": "^9.10.1", "stylelint-config-wordpress": "^13.1.0" }, "publishConfig": { From 44409fb31f2ad9b6b745d2766269381976502227 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Mon, 21 Jan 2019 04:35:22 -0500 Subject: [PATCH 180/691] Block Serialization Default Parser: Use PCRE v6.x subpattern syntax (#13369) --- packages/block-serialization-default-parser/parser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-serialization-default-parser/parser.php b/packages/block-serialization-default-parser/parser.php index 8b565f1a28b9b..eb4c21a973c0f 100644 --- a/packages/block-serialization-default-parser/parser.php +++ b/packages/block-serialization-default-parser/parser.php @@ -410,7 +410,7 @@ function next_token() { * match back in PHP to see which one it was. */ $has_match = preg_match( - '/<!--\s+(?<closer>\/)?wp:(?<namespace>[a-z][a-z0-9_-]*\/)?(?<name>[a-z][a-z0-9_-]*)\s+(?<attrs>{(?:(?:[^}]+|}+(?=})|(?!}\s+\/?-->).)*+)?}\s+)?(?<void>\/)?-->/s', + '/<!--\s+(?P<closer>\/)?wp:(?P<namespace>[a-z][a-z0-9_-]*\/)?(?P<name>[a-z][a-z0-9_-]*)\s+(?P<attrs>{(?:(?:[^}]+|}+(?=})|(?!}\s+\/?-->).)*+)?}\s+)?(?P<void>\/)?-->/s', $this->document, $matches, PREG_OFFSET_CAPTURE, From e1e02af4f87bf988a1df3f24e58f9059298cf50a Mon Sep 17 00:00:00 2001 From: William Earnhardt <wearnhardt@gmail.com> Date: Mon, 21 Jan 2019 05:00:21 -0500 Subject: [PATCH 181/691] Add test case for pending to isEditedPostDateFloating suite (#13256) (#13281) This is just a follow up for #13256. It was opened against the `fix/13176-post-date-pending` branch instead of `master`. It should have been merged there first prior to #13178 getting merged. This just brings that test over to `master`. --- packages/editor/src/store/test/selectors.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/editor/src/store/test/selectors.js b/packages/editor/src/store/test/selectors.js index 6e8f963129dc7..d75c81edbb909 100644 --- a/packages/editor/src/store/test/selectors.js +++ b/packages/editor/src/store/test/selectors.js @@ -1940,6 +1940,24 @@ describe( 'selectors', () => { expect( isEditedPostDateFloating( state ) ).toBe( false ); } ); + + it( 'should return true for pending posts', () => { + const state = { + currentPost: { + date: '2018-09-27T01:23:45.678Z', + modified: '2018-09-27T01:23:45.678Z', + status: 'pending', + }, + editor: { + present: { + edits: {}, + }, + }, + initialEdits: {}, + }; + + expect( isEditedPostDateFloating( state ) ).toBe( true ); + } ); } ); describe( 'getBlockDependantsCacheBust', () => { From 6d5a5374a0d2c42e18f113a5c056d4cfa1481290 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Mon, 21 Jan 2019 06:54:38 -0800 Subject: [PATCH 182/691] Add example usage for MediaPlaceholder (#13389) * Add example usage for MediaPlaceholder Adds an example of how to use the MediaPlaceholder and setting the url for an image attribute. Fixes #13377 * Fixed code style --- .../components/media-placeholder/README.md | 50 ++++++++++++++----- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/packages/editor/src/components/media-placeholder/README.md b/packages/editor/src/components/media-placeholder/README.md index d9d2483a7c71d..2890d2f1f162a 100644 --- a/packages/editor/src/components/media-placeholder/README.md +++ b/packages/editor/src/components/media-placeholder/README.md @@ -3,7 +3,31 @@ MediaPlaceholder `MediaPlaceholder` is a React component used to render either the media associated with a block, or an editing interface to replace the media for a block. -## Setup +## Usage + +An example usage which sets the URL of the selected image to `theImage` attributes. + +``` +const { MediaPlaceholder } = wp.editor; + +... + + edit: ( { attributes, setAttributes } ) { + const mediaPlaceholder = <MediaPlaceholder + onSelect = { + ( el ) => { + setAttributes( { theImage: el.url } ); + } + } + allowedTypes = { [ 'image' ] } + multiple = { false } + labels = { { title: 'The Image' } } + />; + ... + } +``` + +## Extend It includes a `wp.hooks` filter `editor.MediaPlaceholder` that enables developers to replace or extend it. @@ -12,18 +36,18 @@ _Example:_ Replace implementation of the placeholder: ```js -function replaceMediaPlaceholder() { - return function() { - return wp.element.createElement( - 'div', - {}, - 'The replacement contents or components.' - ); - } -} - -wp.hooks.addFilter( - 'editor.MediaPlaceholder', +function replaceMediaPlaceholder() { + return function() { + return wp.element.createElement( + 'div', + {}, + 'The replacement contents or components.' + ); + } +} + +wp.hooks.addFilter( + 'editor.MediaPlaceholder', 'my-plugin/replace-media-placeholder', replaceMediaPlaceholder ); From 705e57eccb1bdcef076cc4505dff19eb47229834 Mon Sep 17 00:00:00 2001 From: Luke Pettway <luke.pettway@gmail.com> Date: Mon, 21 Jan 2019 23:39:11 -0500 Subject: [PATCH 183/691] Fix date picker 24hour tab order (#11863) * removed is24hour class, move markup for month and day to render methods and use some logic to determine logical order * update snapshots to test the is12Hour boolean. --- packages/components/src/date-time/style.scss | 6 - .../date-time/test/__snapshots__/time.js.snap | 689 ++++++++++++++++++ .../components/src/date-time/test/time.js | 26 +- packages/components/src/date-time/time.js | 96 ++- 4 files changed, 761 insertions(+), 56 deletions(-) create mode 100644 packages/components/src/date-time/test/__snapshots__/time.js.snap diff --git a/packages/components/src/date-time/style.scss b/packages/components/src/date-time/style.scss index 530b397c9144e..9412de832e3ea 100644 --- a/packages/components/src/date-time/style.scss +++ b/packages/components/src/date-time/style.scss @@ -152,12 +152,6 @@ border-radius: 0 $radius-round-rectangle $radius-round-rectangle 0 !important; } } - - &.is-24-hour { - .components-datetime__time-field-day { - order: 0 !important; - } - } } .components-datetime__time-legend { diff --git a/packages/components/src/date-time/test/__snapshots__/time.js.snap b/packages/components/src/date-time/test/__snapshots__/time.js.snap new file mode 100644 index 0000000000000..d67e11da54846 --- /dev/null +++ b/packages/components/src/date-time/test/__snapshots__/time.js.snap @@ -0,0 +1,689 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`TimePicker matches the snapshot when the is12hour prop is false 1`] = ` +<div + className="components-datetime__time" +> + <fieldset> + <legend + className="components-datetime__time-legend invisible" + > + Date + </legend> + <div + className="components-datetime__time-wrapper" + > + <div + className="components-datetime__time-field components-datetime__time-field-month" + key="render-month" + > + <select + aria-label="Month" + className="components-datetime__time-field-month-select" + onBlur={[Function]} + onChange={[Function]} + value="10" + > + <option + value="01" + > + January + </option> + <option + value="02" + > + February + </option> + <option + value="03" + > + March + </option> + <option + value="04" + > + April + </option> + <option + value="05" + > + May + </option> + <option + value="06" + > + June + </option> + <option + value="07" + > + July + </option> + <option + value="08" + > + August + </option> + <option + value="09" + > + September + </option> + <option + value="10" + > + October + </option> + <option + value="11" + > + November + </option> + <option + value="12" + > + December + </option> + </select> + </div> + <div + className="components-datetime__time-field components-datetime__time-field-day" + key="render-day" + > + <input + aria-label="Day" + className="components-datetime__time-field-day-input" + min={1} + onBlur={[Function]} + onChange={[Function]} + step={1} + type="number" + value="18" + /> + </div> + <div + className="components-datetime__time-field components-datetime__time-field-year" + > + <input + aria-label="Year" + className="components-datetime__time-field-year-input" + onBlur={[Function]} + onChange={[Function]} + step={1} + type="number" + value="1986" + /> + </div> + </div> + </fieldset> + <fieldset> + <legend + className="components-datetime__time-legend invisible" + > + Time + </legend> + <div + className="components-datetime__time-wrapper" + > + <div + className="components-datetime__time-field components-datetime__time-field-time" + > + <input + aria-label="Hours" + className="components-datetime__time-field-hours-input" + max={23} + min={0} + onBlur={[Function]} + onChange={[Function]} + step={1} + type="number" + value="23" + /> + <span + aria-hidden="true" + className="components-datetime__time-separator" + > + : + </span> + <input + aria-label="Minutes" + className="components-datetime__time-field-minutes-input" + max={59} + min={0} + onBlur={[Function]} + onChange={[Function]} + type="number" + value="00" + /> + </div> + </div> + </fieldset> +</div> +`; + +exports[`TimePicker matches the snapshot when the is12hour prop is specified 1`] = ` +<div + className="components-datetime__time" +> + <fieldset> + <legend + className="components-datetime__time-legend invisible" + > + Date + </legend> + <div + className="components-datetime__time-wrapper" + > + <div + className="components-datetime__time-field components-datetime__time-field-day" + key="render-day" + > + <input + aria-label="Day" + className="components-datetime__time-field-day-input" + min={1} + onBlur={[Function]} + onChange={[Function]} + step={1} + type="number" + value="18" + /> + </div> + <div + className="components-datetime__time-field components-datetime__time-field-month" + key="render-month" + > + <select + aria-label="Month" + className="components-datetime__time-field-month-select" + onBlur={[Function]} + onChange={[Function]} + value="10" + > + <option + value="01" + > + January + </option> + <option + value="02" + > + February + </option> + <option + value="03" + > + March + </option> + <option + value="04" + > + April + </option> + <option + value="05" + > + May + </option> + <option + value="06" + > + June + </option> + <option + value="07" + > + July + </option> + <option + value="08" + > + August + </option> + <option + value="09" + > + September + </option> + <option + value="10" + > + October + </option> + <option + value="11" + > + November + </option> + <option + value="12" + > + December + </option> + </select> + </div> + <div + className="components-datetime__time-field components-datetime__time-field-year" + > + <input + aria-label="Year" + className="components-datetime__time-field-year-input" + onBlur={[Function]} + onChange={[Function]} + step={1} + type="number" + value="1986" + /> + </div> + </div> + </fieldset> + <fieldset> + <legend + className="components-datetime__time-legend invisible" + > + Time + </legend> + <div + className="components-datetime__time-wrapper" + > + <div + className="components-datetime__time-field components-datetime__time-field-time" + > + <input + aria-label="Hours" + className="components-datetime__time-field-hours-input" + max={12} + min={1} + onBlur={[Function]} + onChange={[Function]} + step={1} + type="number" + value="11" + /> + <span + aria-hidden="true" + className="components-datetime__time-separator" + > + : + </span> + <input + aria-label="Minutes" + className="components-datetime__time-field-minutes-input" + max={59} + min={0} + onBlur={[Function]} + onChange={[Function]} + type="number" + value="00" + /> + </div> + <div + className="components-datetime__time-field components-datetime__time-field-am-pm" + > + <ForwardRef(Button) + aria-pressed={false} + className="components-datetime__time-am-button" + isDefault={true} + isToggled={false} + onClick={[Function]} + > + AM + </ForwardRef(Button)> + <ForwardRef(Button) + aria-pressed={true} + className="components-datetime__time-pm-button" + isDefault={true} + isToggled={true} + onClick={[Function]} + > + PM + </ForwardRef(Button)> + </div> + </div> + </fieldset> +</div> +`; + +exports[`TimePicker matches the snapshot when the is12hour prop is true 1`] = ` +<div + className="components-datetime__time" +> + <fieldset> + <legend + className="components-datetime__time-legend invisible" + > + Date + </legend> + <div + className="components-datetime__time-wrapper" + > + <div + className="components-datetime__time-field components-datetime__time-field-day" + key="render-day" + > + <input + aria-label="Day" + className="components-datetime__time-field-day-input" + min={1} + onBlur={[Function]} + onChange={[Function]} + step={1} + type="number" + value="18" + /> + </div> + <div + className="components-datetime__time-field components-datetime__time-field-month" + key="render-month" + > + <select + aria-label="Month" + className="components-datetime__time-field-month-select" + onBlur={[Function]} + onChange={[Function]} + value="10" + > + <option + value="01" + > + January + </option> + <option + value="02" + > + February + </option> + <option + value="03" + > + March + </option> + <option + value="04" + > + April + </option> + <option + value="05" + > + May + </option> + <option + value="06" + > + June + </option> + <option + value="07" + > + July + </option> + <option + value="08" + > + August + </option> + <option + value="09" + > + September + </option> + <option + value="10" + > + October + </option> + <option + value="11" + > + November + </option> + <option + value="12" + > + December + </option> + </select> + </div> + <div + className="components-datetime__time-field components-datetime__time-field-year" + > + <input + aria-label="Year" + className="components-datetime__time-field-year-input" + onBlur={[Function]} + onChange={[Function]} + step={1} + type="number" + value="1986" + /> + </div> + </div> + </fieldset> + <fieldset> + <legend + className="components-datetime__time-legend invisible" + > + Time + </legend> + <div + className="components-datetime__time-wrapper" + > + <div + className="components-datetime__time-field components-datetime__time-field-time" + > + <input + aria-label="Hours" + className="components-datetime__time-field-hours-input" + max={12} + min={1} + onBlur={[Function]} + onChange={[Function]} + step={1} + type="number" + value="11" + /> + <span + aria-hidden="true" + className="components-datetime__time-separator" + > + : + </span> + <input + aria-label="Minutes" + className="components-datetime__time-field-minutes-input" + max={59} + min={0} + onBlur={[Function]} + onChange={[Function]} + type="number" + value="00" + /> + </div> + <div + className="components-datetime__time-field components-datetime__time-field-am-pm" + > + <ForwardRef(Button) + aria-pressed={false} + className="components-datetime__time-am-button" + isDefault={true} + isToggled={false} + onClick={[Function]} + > + AM + </ForwardRef(Button)> + <ForwardRef(Button) + aria-pressed={true} + className="components-datetime__time-pm-button" + isDefault={true} + isToggled={true} + onClick={[Function]} + > + PM + </ForwardRef(Button)> + </div> + </div> + </fieldset> +</div> +`; + +exports[`TimePicker matches the snapshot when the is12hour prop is undefined 1`] = ` +<div + className="components-datetime__time" +> + <fieldset> + <legend + className="components-datetime__time-legend invisible" + > + Date + </legend> + <div + className="components-datetime__time-wrapper" + > + <div + className="components-datetime__time-field components-datetime__time-field-month" + key="render-month" + > + <select + aria-label="Month" + className="components-datetime__time-field-month-select" + onBlur={[Function]} + onChange={[Function]} + value="10" + > + <option + value="01" + > + January + </option> + <option + value="02" + > + February + </option> + <option + value="03" + > + March + </option> + <option + value="04" + > + April + </option> + <option + value="05" + > + May + </option> + <option + value="06" + > + June + </option> + <option + value="07" + > + July + </option> + <option + value="08" + > + August + </option> + <option + value="09" + > + September + </option> + <option + value="10" + > + October + </option> + <option + value="11" + > + November + </option> + <option + value="12" + > + December + </option> + </select> + </div> + <div + className="components-datetime__time-field components-datetime__time-field-day" + key="render-day" + > + <input + aria-label="Day" + className="components-datetime__time-field-day-input" + min={1} + onBlur={[Function]} + onChange={[Function]} + step={1} + type="number" + value="18" + /> + </div> + <div + className="components-datetime__time-field components-datetime__time-field-year" + > + <input + aria-label="Year" + className="components-datetime__time-field-year-input" + onBlur={[Function]} + onChange={[Function]} + step={1} + type="number" + value="1986" + /> + </div> + </div> + </fieldset> + <fieldset> + <legend + className="components-datetime__time-legend invisible" + > + Time + </legend> + <div + className="components-datetime__time-wrapper" + > + <div + className="components-datetime__time-field components-datetime__time-field-time" + > + <input + aria-label="Hours" + className="components-datetime__time-field-hours-input" + max={23} + min={0} + onBlur={[Function]} + onChange={[Function]} + step={1} + type="number" + value="23" + /> + <span + aria-hidden="true" + className="components-datetime__time-separator" + > + : + </span> + <input + aria-label="Minutes" + className="components-datetime__time-field-minutes-input" + max={59} + min={0} + onBlur={[Function]} + onChange={[Function]} + type="number" + value="00" + /> + </div> + </div> + </fieldset> +</div> +`; diff --git a/packages/components/src/date-time/test/time.js b/packages/components/src/date-time/test/time.js index 64c868ea293aa..3d61d058f330c 100644 --- a/packages/components/src/date-time/test/time.js +++ b/packages/components/src/date-time/test/time.js @@ -9,18 +9,24 @@ import { shallow } from 'enzyme'; import TimePicker from '../time'; describe( 'TimePicker', () => { - it( 'should have the correct CSS class if 12-hour clock is specified', () => { - const onChangeSpy = jest.fn(); - const picker = shallow( <TimePicker currentTime="1986-10-18T11:00:00" onChange={ onChangeSpy } is12Hour={ true } /> ); - expect( picker.hasClass( 'is-12-hour' ) ).toBe( true ); + it( 'matches the snapshot when the is12hour prop is true', () => { + const wrapper = shallow( <TimePicker currentTime="1986-10-18T23:00:00" is12Hour={ true } /> ); + expect( wrapper ).toMatchSnapshot(); } ); - it( 'should have the correct CSS class if 24-hour clock is specified', () => { - const onChangeSpy = jest.fn(); - const picker = shallow( - <TimePicker currentTime="1986-10-18T11:00:00" onChange={ onChangeSpy } is12Hour={ false } /> - ); - expect( picker.hasClass( 'is-24-hour' ) ).toBe( true ); + it( 'matches the snapshot when the is12hour prop is false', () => { + const wrapper = shallow( <TimePicker currentTime="1986-10-18T23:00:00" is12Hour={ false } /> ); + expect( wrapper ).toMatchSnapshot(); + } ); + + it( 'matches the snapshot when the is12hour prop is specified', () => { + const wrapper = shallow( <TimePicker currentTime="1986-10-18T23:00:00" is12Hour /> ); + expect( wrapper ).toMatchSnapshot(); + } ); + + it( 'matches the snapshot when the is12hour prop is undefined', () => { + const wrapper = shallow( <TimePicker currentTime="1986-10-18T23:00:00" /> ); + expect( wrapper ).toMatchSnapshot(); } ); it( 'should call onChange with an updated day', () => { diff --git a/packages/components/src/date-time/time.js b/packages/components/src/date-time/time.js index 1db16f6b362d2..8cb80cdee2167 100644 --- a/packages/components/src/date-time/time.js +++ b/packages/components/src/date-time/time.js @@ -43,6 +43,9 @@ class TimePicker extends Component { this.updateMinutes = this.updateMinutes.bind( this ); this.onChangeHours = this.onChangeHours.bind( this ); this.onChangeMinutes = this.onChangeMinutes.bind( this ); + this.renderMonth = this.renderMonth.bind( this ); + this.renderDay = this.renderDay.bind( this ); + this.renderDayMonthFormat = this.renderDayMonthFormat.bind( this ); } componentDidMount() { @@ -189,52 +192,65 @@ class TimePicker extends Component { this.setState( { minutes: event.target.value } ); } + renderMonth( month ) { + return ( + <div key="render-month" className="components-datetime__time-field components-datetime__time-field-month"> + <select + aria-label={ __( 'Month' ) } + className="components-datetime__time-field-month-select" + value={ month } + onChange={ this.onChangeMonth } + onBlur={ this.updateMonth } + > + <option value="01">{ __( 'January' ) }</option> + <option value="02">{ __( 'February' ) }</option> + <option value="03">{ __( 'March' ) }</option> + <option value="04">{ __( 'April' ) }</option> + <option value="05">{ __( 'May' ) }</option> + <option value="06">{ __( 'June' ) }</option> + <option value="07">{ __( 'July' ) }</option> + <option value="08">{ __( 'August' ) }</option> + <option value="09">{ __( 'September' ) }</option> + <option value="10">{ __( 'October' ) }</option> + <option value="11">{ __( 'November' ) }</option> + <option value="12">{ __( 'December' ) }</option> + </select> + </div> + ); + } + + renderDay( day ) { + return ( + <div key="render-day" className="components-datetime__time-field components-datetime__time-field-day"> + <input + aria-label={ __( 'Day' ) } + className="components-datetime__time-field-day-input" + type="number" + value={ day } + step={ 1 } + min={ 1 } + onChange={ this.onChangeDay } + onBlur={ this.updateDay } + /> + </div> + ); + } + + renderDayMonthFormat( is12Hour ) { + const { day, month } = this.state; + const layout = [ this.renderDay( day ), this.renderMonth( month ) ]; + return is12Hour ? layout : layout.reverse(); + } + render() { const { is12Hour } = this.props; - const { day, month, year, minutes, hours, am } = this.state; - + const { year, minutes, hours, am } = this.state; return ( - <div className={ classnames( 'components-datetime__time', { - 'is-12-hour': is12Hour, - 'is-24-hour': ! is12Hour, - } ) }> + <div className={ classnames( 'components-datetime__time' ) }> <fieldset> <legend className="components-datetime__time-legend invisible">{ __( 'Date' ) }</legend> <div className="components-datetime__time-wrapper"> - <div className="components-datetime__time-field components-datetime__time-field-month"> - <select - aria-label={ __( 'Month' ) } - className="components-datetime__time-field-month-select" - value={ month } - onChange={ this.onChangeMonth } - onBlur={ this.updateMonth } - > - <option value="01">{ __( 'January' ) }</option> - <option value="02">{ __( 'February' ) }</option> - <option value="03">{ __( 'March' ) }</option> - <option value="04">{ __( 'April' ) }</option> - <option value="05">{ __( 'May' ) }</option> - <option value="06">{ __( 'June' ) }</option> - <option value="07">{ __( 'July' ) }</option> - <option value="08">{ __( 'August' ) }</option> - <option value="09">{ __( 'September' ) }</option> - <option value="10">{ __( 'October' ) }</option> - <option value="11">{ __( 'November' ) }</option> - <option value="12">{ __( 'December' ) }</option> - </select> - </div> - <div className="components-datetime__time-field components-datetime__time-field-day"> - <input - aria-label={ __( 'Day' ) } - className="components-datetime__time-field-day-input" - type="number" - value={ day } - step={ 1 } - min={ 1 } - onChange={ this.onChangeDay } - onBlur={ this.updateDay } - /> - </div> + { this.renderDayMonthFormat( is12Hour ) } <div className="components-datetime__time-field components-datetime__time-field-year"> <input aria-label={ __( 'Year' ) } From 0bf39b48fe339ddfdd2340ecf3b5fd181d187719 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Tue, 22 Jan 2019 03:31:26 -0500 Subject: [PATCH 184/691] Docs: Fix Notices documentation data handbook reference (#13409) * Docs: Fix Notices documentation data handbook reference * Update link --- packages/notices/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/notices/README.md b/packages/notices/README.md index a32e69725b39c..eeeba5f63d6ae 100644 --- a/packages/notices/README.md +++ b/packages/notices/README.md @@ -20,6 +20,6 @@ When imported, the notices module registers a data store on the `core/notices` n For more information about consuming from a data store, refer to [the `@wordpress/data` documentation on _Data Access and Manipulation_](/packages/data/README.md#data-access-and-manipulation). -For a full list of actions and selectors available in the `core/notices` namespace, refer to the [_Notices Data_ Handbook page](https://wordpress.org/gutenberg/handbook/packages/packages-data/packages-data-core-edit-post/). +For a full list of actions and selectors available in the `core/notices` namespace, refer to the [_Notices Data_ Handbook page](/docs/designers-developers/developers/data/data-core-notices.md). <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> From bd0f6853ae77fd8d524f66a6b545887f3dd56a03 Mon Sep 17 00:00:00 2001 From: Mikael Korpela <mikael@ihminen.org> Date: Tue, 22 Jan 2019 10:43:18 +0200 Subject: [PATCH 185/691] Docs: mention dark colour scheme in design docs (#13375) * Docs: mention dark colour scheme in design docs * Update link --- docs/designers-developers/designers/block-design.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/designers-developers/designers/block-design.md b/docs/designers-developers/designers/block-design.md index 64eb736142c9d..3840c863dd487 100644 --- a/docs/designers-developers/designers/block-design.md +++ b/docs/designers-developers/designers/block-design.md @@ -116,6 +116,10 @@ Because the Drop Cap feature is not necessary for the basic operation of the blo Check how your block looks, feels, and works on as many devices and screen sizes as you can. +### Support Gutenberg's dark background editor scheme + +Check how your block looks with [dark backgrounds](/docs/designers-developers/developers/themes/theme-support.md#dark-backgrounds) in the editor. + ## Examples To demonstrate some of these practices, here are a few annotated examples of default Gutenberg blocks: From 8fdf3cd53773d43661a25ea8e96a91198e41ec9b Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Tue, 22 Jan 2019 00:49:50 -0800 Subject: [PATCH 186/691] Update meta box link to new tutorial (#13302) * Update meta box link to new tutorial * Update README.md Fix link. Simplify URLs. * Update with both links for working with meta data * Fixed links --- docs/designers-developers/developers/README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/designers-developers/developers/README.md b/docs/designers-developers/developers/README.md index 860e6f3b07310..162c45fbcfa4e 100644 --- a/docs/designers-developers/developers/README.md +++ b/docs/designers-developers/developers/README.md @@ -6,40 +6,40 @@ The new editor is highly flexible, like most of WordPress. You can build custom The editor is about blocks, and the main extensibility API is the Block API. It allows you to create your own static blocks, dynamic blocks rendered on the server and also blocks capable of saving data to Post Meta for more structured content. -If you want to learn more about block creation, the [Blocks Tutorial](../../../docs/designers-developers/developers/tutorials/block-tutorial/readme.md) is the best place to start. +If you want to learn more about block creation, the [Blocks Tutorial](/docs/designers-developers/developers/tutorials/block-tutorial/readme.md) is the best place to start. ## Extending Blocks It is also possible to modify the behavior of existing blocks or even remove them completely using filters. -Learn more in the [Block Filters](../../../docs/designers-developers/developers/reference/hooks/block-filters.md) section. +Learn more in the [Block Filters](/docs/designers-developers/developers/filters/block-filters.md) section. ## Extending the Editor UI Extending the editor UI can be accomplished with the `registerPlugin` API, allowing you to define all your plugin's UI elements in one place. -Refer to the [Plugins](https://github.com/WordPress/gutenberg/blob/master/packages/plugins/README.md) and [Edit Post](https://github.com/WordPress/gutenberg/blob/master/packages/edit-post/README.md) section for more information. +Refer to the [Plugins](/packages/plugins/README.md) and [Edit Post](/packages/edit-post/README.md) section for more information. -You can also filter certain aspects of the editor; this is documented on the [Editor Filters](../../../docs/designers-developers/developers/reference/hooks/editor-filters.md) page. +You can also filter certain aspects of the editor; this is documented on the [Editor Filters](/docs/designers-developers/developers/filters/editor-filters.md) page. ## Meta Boxes -**Porting PHP meta boxes to blocks or sidebar plugins is highly encouraged!** +Porting PHP meta boxes to blocks or sidebar plugins is highly encouraged, learn how through these [meta data tutorials](/docs/designers-developers/developers/tutorials/metabox/readme.md). -Discover how [Meta Box](../../../docs/designers-developers/developers/backward-compatibility/meta-box.md) support works in the new editor. +See how the new editor [supports existing Meta Boxes](/docs/designers-developers/developers/backward-compatibility/meta-box.md). ## Theme Support By default, blocks provide their styles to enable basic support for blocks in themes without any change. Themes can add/override these styles, or rely on defaults. -There are some advanced block features which require opt-in support in the theme. See [theme support](../../../docs/designers-developers/developers/themes/theme-support.md). +There are some advanced block features which require opt-in support in the theme. See [theme support](/docs/designers-developers/developers/themes/theme-support.md). ## Autocomplete -Autocompleters within blocks may be extended and overridden. Learn more about the [autocomplete](../../../docs/designers-developers/developers/filters/autocomplete-filters.md) filters. +Autocompleters within blocks may be extended and overridden. Learn more about the [autocomplete](/docs/designers-developers/developers/filters/autocomplete-filters.md) filters. ## Block Parsing and Serialization Posts in the editor move through a couple of different stages between being stored in `post_content` and appearing in the editor. Since the blocks themselves are data structures that live in memory it takes a parsing and serialization step to transform out from and into the stored format in the database. -Customizing the parser is an advanced topic that you can learn more about in the [Extending the Parser](../../../docs/designers-developers/developers/filters/parser-filters.md) section. +Customizing the parser is an advanced topic that you can learn more about in the [Extending the Parser](/docs/designers-developers/developers/filters/parser-filters.md) section. From 21651474f7f6793be730af9adaaa1be28a7a2911 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Tue, 22 Jan 2019 00:51:38 -0800 Subject: [PATCH 187/691] Update docs for unregisterBlockType (#13273) --- .../developers/filters/block-filters.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/designers-developers/developers/filters/block-filters.md b/docs/designers-developers/developers/filters/block-filters.md index 4f28cf17a9f18..cc4f71bb95540 100644 --- a/docs/designers-developers/developers/filters/block-filters.md +++ b/docs/designers-developers/developers/filters/block-filters.md @@ -261,8 +261,9 @@ Adding blocks is easy enough, removing them is as easy. Plugin or theme authors ```js // my-plugin.js - -wp.blocks.unregisterBlockType( 'core/verse' ); +wp.domReady( function() { + wp.blocks.unregisterBlockType( 'core/verse' ); +} ); ``` and load this script in the Editor @@ -275,7 +276,7 @@ function my_plugin_blacklist_blocks() { wp_enqueue_script( 'my-plugin-blacklist-blocks', plugins_url( 'my-plugin.js', __FILE__ ), - array( 'wp-blocks' ) + array( 'wp-blocks', 'wp-dom-ready', 'wp-edit-post' ) ); } add_action( 'enqueue_block_editor_assets', 'my_plugin_blacklist_blocks' ); From 15fc0c13eb33a6e4426ff4ad681b6bfb6d0adaec Mon Sep 17 00:00:00 2001 From: Hiroshi Urabe <mail@torounit.com> Date: Tue, 22 Jan 2019 21:27:02 +0900 Subject: [PATCH 188/691] <CategorySelect> support post_tags and hierarchical = false taxonomy. (#13076) --- .../components/src/query-controls/terms.js | 13 +++- .../src/query-controls/test/terms.js | 61 +++++++++++++++++++ packages/editor/src/utils/terms.js | 13 +++- packages/editor/src/utils/test/terms.js | 13 +++- 4 files changed, 95 insertions(+), 5 deletions(-) create mode 100644 packages/components/src/query-controls/test/terms.js diff --git a/packages/components/src/query-controls/terms.js b/packages/components/src/query-controls/terms.js index cbeb9d0bb4b7a..ba76e68980184 100644 --- a/packages/components/src/query-controls/terms.js +++ b/packages/components/src/query-controls/terms.js @@ -11,7 +11,18 @@ import { groupBy } from 'lodash'; * @return {Array} Array of terms in tree format. */ export function buildTermsTree( flatTerms ) { - const termsByParent = groupBy( flatTerms, 'parent' ); + const flatTermsWithParentAndChildren = flatTerms.map( ( term ) => { + return { + children: [], + parent: null, + ...term, + }; + } ); + + const termsByParent = groupBy( flatTermsWithParentAndChildren, 'parent' ); + if ( termsByParent.null && termsByParent.null.length ) { + return flatTermsWithParentAndChildren; + } const fillWithChildren = ( terms ) => { return terms.map( ( term ) => { const children = termsByParent[ term.id ]; diff --git a/packages/components/src/query-controls/test/terms.js b/packages/components/src/query-controls/test/terms.js new file mode 100644 index 0000000000000..11cf1505d4f27 --- /dev/null +++ b/packages/components/src/query-controls/test/terms.js @@ -0,0 +1,61 @@ +/** + * Internal dependencies + */ +import { buildTermsTree } from '../terms'; + +describe( 'buildTermsTree()', () => { + it( 'Should return same array as input with null parent and empty children added if parent is never specified.', () => { + const input = Object.freeze( [ + { id: 2232, dummy: true }, + { id: 2245, dummy: true }, + ] ); + const output = Object.freeze( [ + { id: 2232, parent: null, children: [], dummy: true }, + { id: 2245, parent: null, children: [], dummy: true }, + ] ); + const termsTreem = buildTermsTree( input ); + expect( termsTreem ).toEqual( output ); + } ); + it( 'Should return same array as input with empty children added if all the elements are top level', () => { + const input = Object.freeze( [ + { id: 2232, parent: 0, dummy: true }, + { id: 2245, parent: 0, dummy: false }, + ] ); + const output = [ + { id: 2232, parent: 0, children: [], dummy: true }, + { id: 2245, parent: 0, children: [], dummy: false }, + ]; + const termsTreem = buildTermsTree( input ); + expect( termsTreem ).toEqual( output ); + } ); + it( 'Should return element with its child if a child exists', () => { + const input = Object.freeze( [ + { id: 2232, parent: 0 }, + { id: 2245, parent: 2232 }, + ] ); + const output = [ + { id: 2232, parent: 0, children: [ + { id: 2245, parent: 2232, children: [] }, + ] }, + ]; + const termsTreem = buildTermsTree( input ); + expect( termsTreem ).toEqual( output ); + } ); + it( 'Should return elements with multiple children and elements with no children', () => { + const input = Object.freeze( [ + { id: 2232, parent: 0 }, + { id: 2245, parent: 2232 }, + { id: 2249, parent: 0 }, + { id: 2246, parent: 2232 }, + ] ); + const output = [ + { id: 2232, parent: 0, children: [ + { id: 2245, parent: 2232, children: [] }, + { id: 2246, parent: 2232, children: [] }, + ] }, + { id: 2249, parent: 0, children: [] }, + ]; + const termsTreem = buildTermsTree( input ); + expect( termsTreem ).toEqual( output ); + } ); +} ); diff --git a/packages/editor/src/utils/terms.js b/packages/editor/src/utils/terms.js index cbeb9d0bb4b7a..ba76e68980184 100644 --- a/packages/editor/src/utils/terms.js +++ b/packages/editor/src/utils/terms.js @@ -11,7 +11,18 @@ import { groupBy } from 'lodash'; * @return {Array} Array of terms in tree format. */ export function buildTermsTree( flatTerms ) { - const termsByParent = groupBy( flatTerms, 'parent' ); + const flatTermsWithParentAndChildren = flatTerms.map( ( term ) => { + return { + children: [], + parent: null, + ...term, + }; + } ); + + const termsByParent = groupBy( flatTermsWithParentAndChildren, 'parent' ); + if ( termsByParent.null && termsByParent.null.length ) { + return flatTermsWithParentAndChildren; + } const fillWithChildren = ( terms ) => { return terms.map( ( term ) => { const children = termsByParent[ term.id ]; diff --git a/packages/editor/src/utils/test/terms.js b/packages/editor/src/utils/test/terms.js index 361cc4b414b7a..11cf1505d4f27 100644 --- a/packages/editor/src/utils/test/terms.js +++ b/packages/editor/src/utils/test/terms.js @@ -4,10 +4,17 @@ import { buildTermsTree } from '../terms'; describe( 'buildTermsTree()', () => { - it( 'Should return empty array if parent is never specified.', () => { - const input = Object.freeze( [ { term: 2232 }, { term: 2245 } ] ); + it( 'Should return same array as input with null parent and empty children added if parent is never specified.', () => { + const input = Object.freeze( [ + { id: 2232, dummy: true }, + { id: 2245, dummy: true }, + ] ); + const output = Object.freeze( [ + { id: 2232, parent: null, children: [], dummy: true }, + { id: 2245, parent: null, children: [], dummy: true }, + ] ); const termsTreem = buildTermsTree( input ); - expect( termsTreem ).toEqual( [] ); + expect( termsTreem ).toEqual( output ); } ); it( 'Should return same array as input with empty children added if all the elements are top level', () => { const input = Object.freeze( [ From 3c38fce7421855489b9a5577a89eea00cdf792bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Corn=C3=A9=20Dorrestijn?= <corne.dorrestijn@gmail.com> Date: Tue, 22 Jan 2019 15:51:17 +0100 Subject: [PATCH 189/691] Fix: :root no longer causes invalid selectors (#13325) --- .../transforms/test/__snapshots__/wrap.js.snap | 6 ++++++ .../editor/src/editor-styles/transforms/test/wrap.js | 11 +++++++++++ packages/editor/src/editor-styles/transforms/wrap.js | 4 ++-- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/packages/editor/src/editor-styles/transforms/test/__snapshots__/wrap.js.snap b/packages/editor/src/editor-styles/transforms/test/__snapshots__/wrap.js.snap index 873091eacb77d..9060dda747029 100644 --- a/packages/editor/src/editor-styles/transforms/test/__snapshots__/wrap.js.snap +++ b/packages/editor/src/editor-styles/transforms/test/__snapshots__/wrap.js.snap @@ -41,3 +41,9 @@ exports[`CSS selector wrap should wrap regular selectors 1`] = ` color: red; }" `; + +exports[`CSS selector wrap should replace :root selectors 1`] = ` +".my-namespace { +--my-color: #ff0000; +}" +`; diff --git a/packages/editor/src/editor-styles/transforms/test/wrap.js b/packages/editor/src/editor-styles/transforms/test/wrap.js index 5500d4a747e41..2a39ff3db3115 100644 --- a/packages/editor/src/editor-styles/transforms/test/wrap.js +++ b/packages/editor/src/editor-styles/transforms/test/wrap.js @@ -61,4 +61,15 @@ describe( 'CSS selector wrap', () => { 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(); + } ); } ); diff --git a/packages/editor/src/editor-styles/transforms/wrap.js b/packages/editor/src/editor-styles/transforms/wrap.js index dad6df2b0c8e6..6c89562d78a9a 100644 --- a/packages/editor/src/editor-styles/transforms/wrap.js +++ b/packages/editor/src/editor-styles/transforms/wrap.js @@ -6,7 +6,7 @@ import { includes } from 'lodash'; /** * @const string IS_ROOT_TAG Regex to check if the selector is a root tag selector. */ -const IS_ROOT_TAG = /^(body|html).*$/; +const IS_ROOT_TAG = /^(body|html|:root).*$/; const wrap = ( namespace, ignore = [] ) => ( node ) => { const updateSelector = ( selector ) => { @@ -20,7 +20,7 @@ const wrap = ( namespace, ignore = [] ) => ( node ) => { }} // HTML and Body elements cannot be contained within our container so lets extract their styles. - return selector.replace( /^(body|html)/, namespace ); + return selector.replace( /^(body|html|:root)/, namespace ); }; if ( node.type === 'rule' ) { From 695860f604548f39f034376db99b6da786b1f4e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Tue, 22 Jan 2019 16:01:54 +0100 Subject: [PATCH 190/691] Docs: Move packages section out of CONTRIBUTING.md (#13418) * Docs: Move packages section out of CONTRIBUTING.md * Update precommit hook to look at new toc.json file --- CONTRIBUTING.md | 142 ++++----------------------------------------- package.json | 2 +- packages/README.md | 128 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 140 insertions(+), 132 deletions(-) create mode 100644 packages/README.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 799c90da66229..2fb8900a49c78 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,11 +2,11 @@ Thank you for thinking about contributing to WordPress' Gutenberg project! If you're unsure of anything, know that you're 💯 welcome to submit an issue or pull request on any topic. The worst that can happen is that you'll be politely directed to the best location to ask your question or to change something in your pull request. We appreciate any sort of contribution and don't want a wall of rules to get in the way of that. -As with all WordPress projects, we want to ensure a welcoming environment for everyone. With that in mind, all contributors are expected to follow our [Code of Conduct](CODE_OF_CONDUCT.md). +As with all WordPress projects, we want to ensure a welcoming environment for everyone. With that in mind, all contributors are expected to follow our [Code of Conduct](/CODE_OF_CONDUCT.md). -Before contributing, we encourage you to read our [Contributing Policy](CONTRIBUTING.md) (you're here already!) and our [Handbook](https://wordpress.org/gutenberg/handbook/). If you have any questions on any of these, please open an issue so we can help clarify them. +Before contributing, we encourage you to read our [Contributing Policy](/CONTRIBUTING.md) (you're here already!) and our [Handbook](https://wordpress.org/gutenberg/handbook/). If you have any questions on any of these, please open an issue so we can help clarify them. -All WordPress projects are [licensed under the GPLv2+](LICENSE.md), and all contributions to Gutenberg will be released under the GPLv2+ license. You maintain copyright over any contribution you make, and by submitting a pull request, you are agreeing to release that contribution under the GPLv2+ license. +All WordPress projects are [licensed under the GPLv2+](/LICENSE.md), and all contributions to Gutenberg will be released under the GPLv2+ license. You maintain copyright over any contribution you make, and by submitting a pull request, you are agreeing to release that contribution under the GPLv2+ license. ## Getting Started @@ -60,7 +60,7 @@ Welcome to... ``` The WordPress installation should be available at `http://localhost:8888` (**Username**: `admin`, **Password**: `password`). -Inside the "docker" directory, you can use any docker command to interact with your containers. If this port is in use, you can override it in your `docker-compose.override.yml` file. If you're running [e2e tests](https://wordpress.org/gutenberg/handbook/reference/testing-overview/#end-to-end-testing), this change will be used correctly. +Inside the "docker" directory, you can use any docker command to interact with your containers. If this port is in use, you can override it in your `docker-compose.override.yml` file. If you're running [e2e tests](/docs/contributors/testing-overview.md#end-to-end-testing), this change will be used correctly. To bring down this local WordPress instance later run: ``` @@ -104,137 +104,17 @@ For example, `add/gallery-block` means you're working on adding a new gallery bl You can pick among all the <a href="https://github.com/WordPress/gutenberg/issues">tickets</a>, or some of the ones labelled <a href="https://github.com/WordPress/gutenberg/labels/Good%20First%20Issue">Good First Issue</a>. -The workflow is documented in greater detail in the [repository management](./docs/contributors/repository-management.md) document. +The workflow is documented in greater detail in the [repository management](/docs/contributors/repository-management.md) document. ## Testing -Gutenberg contains both PHP and JavaScript code and encourages testing and code style linting for both. It also incorporates end-to-end testing using [Google Puppeteer](https://developers.google.com/web/tools/puppeteer/). You can find out more details in [Testing Overview document](./docs/contributors/testing-overview.md). +Gutenberg contains both PHP and JavaScript code and encourages testing and code style linting for both. It also incorporates end-to-end testing using [Google Puppeteer](https://developers.google.com/web/tools/puppeteer/). You can find out more details in [Testing Overview document](/docs/contributors/testing-overview.md). ## Managing Packages -This repository uses [lerna] to manage Gutenberg modules and publish them as packages to [npm]. - -### Creating a New Package - -When creating a new package, you need to provide at least the following: - -1. `package.json` based on the template: - ```json - { - "name": "@wordpress/package-name", - "version": "1.0.0-beta.0", - "description": "Package description.", - "author": "The WordPress Contributors", - "license": "GPL-2.0-or-later", - "keywords": [ - "wordpress" - ], - "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/package-name/README.md", - "repository": { - "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" - }, - "bugs": { - "url": "https://github.com/WordPress/gutenberg/issues" - }, - "main": "build/index.js", - "module": "build-module/index.js", - "react-native": "src/index", - "dependencies": { - "@babel/runtime": "^7.0.0" - }, - "publishConfig": { - "access": "public" - } - } - ``` - This assumes that your code is located in the `src` folder and will be transpiled with `Babel`. -2. `.npmrc` file which disables creating `package-lock.json` file for the package: - ``` - package-lock=false - ``` -3. `README.md` file containing at least: - - Package name - - Package description - - Installation details - - Usage example - - `Code is Poetry` logo (`<br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p>`) - -### Maintaining Changelogs - -Maintaining dozens of npm packages is difficult—it can be tough to keep track of changes. That's why we use `CHANGELOG.md` files for each package to simplify the release process. All packages should follow the [Semantic Versioning (`semver`) specification](https://semver.org/). - -The developer who proposes a change (pull request) is responsible for choosing the correct version increment (`major`, `minor`, or `patch`) according to the following guidelines: - -- Major version X (X.y.z | X > 0) should be changed with any backward incompatible/"breaking" change. This will usually occur at the final stage of deprecating and removing of a feature. -- Minor version Y (x.Y.z | x > 0) should be changed when you add functionality or change functionality in a backward compatible manner. It must be incremented if any public API functionality is marked as deprecated. -- Patch version Z (x.y.Z | x > 0) should be incremented when you make backward compatible bug fixes. - -When in doubt, refer to [Semantic Versioning specification](https://semver.org/). - -_Example:_ - -```md -## v1.2.2 (Unreleased) - -### Bug Fix - -- ... -- ... -``` - -- If you need to add something considered a bug fix, you add the item to `Bug Fix` section and leave the version as 1.2.2. -- If it's a new feature, you add the item to `New Feature` section and change version to 1.3.0. -- If it's a breaking change you want to introduce, add the item to `Breaking Change` section and bump the version to 2.0.0. -- If you struggle to classify a change as one of the above, then it might be not necessary to include it. - -The version bump is only necessary if one of the following applies: - - There are no other unreleased changes. - - The type of change you're introducing is incompatible (more severe) than the other unreleased changes. - -### Releasing Packages - -Lerna automatically releases all outdated packages. To check which packages are outdated and will be released, type `npm run publish:check`. - -If you have the ability to publish packages, you _must_ have [2FA enabled](https://docs.npmjs.com/getting-started/using-two-factor-authentication) on your [npm account][npm]. - -#### Before Releasing - -Confirm that you're logged in to [npm], by running `npm whoami`. If you're not logged in, run `npm adduser` to login. - -If you're publishing a new package, ensure that its `package.json` file contains the correct `publishConfig` settings: - -```json -{ - "publishConfig": { - "access": "public" - } -} -``` - -You can check your package configs by running `npm run lint-pkg-json`. - -#### Development Release - -Run the following command to release a dev version of the outdated packages, replacing `123456` with your 2FA code. Make sure you're using a freshly generated 2FA code, rather than one that's about to timeout. This is a little cumbersome but helps to prevent the release process from dying mid-deploy. - -```bash -NPM_CONFIG_OTP=123456 npm run publish:dev -``` - -Lerna will ask you which version number you want to choose for each package. For a `dev` release, you'll more likely want to choose the "prerelease" option. Repeat the same for all the outdated packages and confirm your version updates. - -Lerna will then publish to [npm], commit the `package.json` changes and create the git tags. - -#### Production Release - -To release a production version for the outdated packages, run the following command, replacing `123456` with your (freshly generated, as above) 2FA code: - -```bash -NPM_CONFIG_OTP=123456 npm run publish:prod -``` +This repository uses [lerna] to manage Gutenberg modules and publish them as packages to [npm]. This enforces certain steps in the workflow which are described in details in [packages](/packages/README.md) documentation. -Choose the correct version based on `CHANGELOG.md` files, confirm your choices and let Lerna do its magic. +Maintaining dozens of npm packages is difficult—it can be tough to keep track of changes. That's why we use `CHANGELOG.md` files for each package to simplify the release process. As a contributor you should add an entry to the aforementioned file each time you contribute adding production code as described in [Maintaining Changelogs](/packages/README.md#maintaining-changelogs) section. ## How Can Designers Contribute? @@ -244,15 +124,15 @@ If you'd like to contribute to the design or front-end, feel free to contribute Documentation is automatically synced from master to the [Gutenberg Documentation Website](https://wordpress.org/gutenberg/handbook/) every 15 minutes. -To add a new documentation page, you'll have to create a Markdown file in the [docs](https://github.com/WordPress/gutenberg/tree/master/docs) folder and add an item to the [root-manifest.json](https://github.com/WordPress/gutenberg/blob/master/docs/root-manifest.json). +To add a new documentation page, you'll have to create a Markdown file in the [docs](https://github.com/WordPress/gutenberg/tree/master/docs) folder and add an item to the [toc.json](/docs/toc.json). ### `@wordpress/component` -If you're contributing to the documentation of any component from the `@wordpress/component` package, take a look at its [guidelines for contributing](./packages/components/CONTRIBUTING.md). +If you're contributing to the documentation of any component from the `@wordpress/component` package, take a look at its [guidelines for contributing](/packages/components/CONTRIBUTING.md). ## Reporting Security Issues -Please see [SECURITY.md](./SECURITY.md). +Please see [SECURITY.md](/SECURITY.md). ## Localizing Gutenberg Plugin diff --git a/package.json b/package.json index ccfebf409fcd2..4a75bd538b9a6 100644 --- a/package.json +++ b/package.json @@ -198,7 +198,7 @@ "*.js": [ "wp-scripts lint-js" ], - "{docs/{root-manifest.json,tool/*.js},packages/{*/README.md,*/src/{actions,selectors}.js,components/src/*/**/README.md}}": [ + "{docs/{toc.json,tool/*.js},packages/{*/README.md,*/src/{actions,selectors}.js,components/src/*/**/README.md}}": [ "npm run docs:build" ] } diff --git a/packages/README.md b/packages/README.md new file mode 100644 index 0000000000000..315a53740bc1c --- /dev/null +++ b/packages/README.md @@ -0,0 +1,128 @@ +## Managing Packages + +This repository uses [lerna] to manage Gutenberg modules and publish them as packages to [npm]. + +### Creating a New Package + +When creating a new package, you need to provide at least the following: + +1. `package.json` based on the template: + ```json + { + "name": "@wordpress/package-name", + "version": "1.0.0-beta.0", + "description": "Package description.", + "author": "The WordPress Contributors", + "license": "GPL-2.0-or-later", + "keywords": [ + "wordpress" + ], + "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/package-name/README.md", + "repository": { + "type": "git", + "url": "https://github.com/WordPress/gutenberg.git" + }, + "bugs": { + "url": "https://github.com/WordPress/gutenberg/issues" + }, + "main": "build/index.js", + "module": "build-module/index.js", + "react-native": "src/index", + "dependencies": { + "@babel/runtime": "^7.0.0" + }, + "publishConfig": { + "access": "public" + } + } + ``` + This assumes that your code is located in the `src` folder and will be transpiled with `Babel`. +2. `.npmrc` file which disables creating `package-lock.json` file for the package: + ``` + package-lock=false + ``` +3. `README.md` file containing at least: + - Package name + - Package description + - Installation details + - Usage example + - `Code is Poetry` logo (`<br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p>`) + +### Maintaining Changelogs + +Maintaining dozens of npm packages is difficult—it can be tough to keep track of changes. That's why we use `CHANGELOG.md` files for each package to simplify the release process. All packages should follow the [Semantic Versioning (`semver`) specification](https://semver.org/). + +The developer who proposes a change (pull request) is responsible for choosing the correct version increment (`major`, `minor`, or `patch`) according to the following guidelines: + +- Major version X (X.y.z | X > 0) should be changed with any backward incompatible/"breaking" change. This will usually occur at the final stage of deprecating and removing of a feature. +- Minor version Y (x.Y.z | x > 0) should be changed when you add functionality or change functionality in a backward compatible manner. It must be incremented if any public API functionality is marked as deprecated. +- Patch version Z (x.y.Z | x > 0) should be incremented when you make backward compatible bug fixes. + +When in doubt, refer to [Semantic Versioning specification](https://semver.org/). + +_Example:_ + +```md +## v1.2.2 (Unreleased) + +### Bug Fix + +- ... +- ... +``` + +- If you need to add something considered a bug fix, you add the item to `Bug Fix` section and leave the version as 1.2.2. +- If it's a new feature, you add the item to `New Feature` section and change version to 1.3.0. +- If it's a breaking change you want to introduce, add the item to `Breaking Change` section and bump the version to 2.0.0. +- If you struggle to classify a change as one of the above, then it might be not necessary to include it. + +The version bump is only necessary if one of the following applies: + - There are no other unreleased changes. + - The type of change you're introducing is incompatible (more severe) than the other unreleased changes. + +### Releasing Packages + +Lerna automatically releases all outdated packages. To check which packages are outdated and will be released, type `npm run publish:check`. + +If you have the ability to publish packages, you _must_ have [2FA enabled](https://docs.npmjs.com/getting-started/using-two-factor-authentication) on your [npm account][npm]. + +#### Before Releasing + +Confirm that you're logged in to [npm], by running `npm whoami`. If you're not logged in, run `npm adduser` to login. + +If you're publishing a new package, ensure that its `package.json` file contains the correct `publishConfig` settings: + +```json +{ + "publishConfig": { + "access": "public" + } +} +``` + +You can check your package configs by running `npm run lint-pkg-json`. + +#### Development Release + +Run the following command to release a dev version of the outdated packages, replacing `123456` with your 2FA code. Make sure you're using a freshly generated 2FA code, rather than one that's about to timeout. This is a little cumbersome but helps to prevent the release process from dying mid-deploy. + +```bash +NPM_CONFIG_OTP=123456 npm run publish:dev +``` + +Lerna will ask you which version number you want to choose for each package. For a `dev` release, you'll more likely want to choose the "prerelease" option. Repeat the same for all the outdated packages and confirm your version updates. + +Lerna will then publish to [npm], commit the `package.json` changes and create the git tags. + +#### Production Release + +To release a production version for the outdated packages, run the following command, replacing `123456` with your (freshly generated, as above) 2FA code: + +```bash +NPM_CONFIG_OTP=123456 npm run publish:prod +``` + +Choose the correct version based on `CHANGELOG.md` files, confirm your choices and let Lerna do its magic. + +[lerna]: https://lernajs.io/ +[npm]: https://www.npmjs.com/ From 6f6181ee61085289cd95b047f4cfd9135d0b8549 Mon Sep 17 00:00:00 2001 From: Mel Choyce <melchoyce@users.noreply.github.com> Date: Tue, 22 Jan 2019 11:49:03 -0500 Subject: [PATCH 191/691] Update DropdownMenu readme (#13410) Adding documentation to describe the use and functionality of the DropdownMenu component. Thanks @sarahmonster and @jasmussen for drafting this. --- .../components/src/dropdown-menu/README.md | 66 +++++++++++++++++-- 1 file changed, 59 insertions(+), 7 deletions(-) diff --git a/packages/components/src/dropdown-menu/README.md b/packages/components/src/dropdown-menu/README.md index 4d934a3ee9204..9346140bb27ef 100644 --- a/packages/components/src/dropdown-menu/README.md +++ b/packages/components/src/dropdown-menu/README.md @@ -1,8 +1,60 @@ -# Dropdown Menu +# DropdownMenu -Dropdown Menu is a React component to render an expandable menu of buttons. It is similar in purpose to a `<select>` element, with the distinction that it does not maintain a value. Instead, each option behaves as an action button. +The DropdownMenu displays a list of actions (each contained in a MenuItem, MenuItemsChoice, or MenuGroup) in a compact way. It appears in a Popover after the user has interacted with an element (a button or icon) or when they perform a specific action. -## Usage +![An expanded DropdownMenu, containing a list of MenuItems.](https://wordpress.org/gutenberg/files/2019/01/DropdownMenuExample.png) + +## Table of contents + +1. [Design guidelines](#design-guidelines) +2. [Development guidelines](#development-guidelines) + +## Anatomy + +![Anatomy of a DropdownMenu.](https://wordpress.org/gutenberg/files/2019/01/DropdownMenuAnatomy.png) + +1. Popover: a container component in which the DropdownMenu is wrapped. +2. Parent button: the icon or button that is used to toggle the display of the Popover containing the DropdownMenu. +3. MenuItem: the list items within the DropdownMenu. + +## Design guidelines + +### Usage + +#### When to use a DropdownMenu + +Use a DropdownMenu when you want users to: + +- Choose an action or change a setting from a list, AND +- Only see the available choices contextually. + +If you need to display all the available options at all times, consider using a Toolbar instead. + +![Use a DropdownMenu to display a list of actions after the user interacts with an icon.](https://wordpress.org/gutenberg/files/2019/01/DropdownMenuDo.png) + +**Do** +Use a DropdownMenu to display a list of actions after the user interacts with an icon. + +![Don’t use a DropdownMenu for important actions that should always be visible. Use a Toolbar instead.](https://wordpress.org/gutenberg/files/2019/01/DropdownMenuDont.png) + +**Don’t** +Don’t use a DropdownMenu for frequently used actions. Use a Toolbar instead. + +#### Behavior + +Generally, the parent button should have a triangular icon to the right of the icon or text to indicate that interacting with it will show a DropdownMenu. In rare cases where the parent button directly indicates that there'll be more content (through the use of an ellipsis or "More" label), this can be omitted. + +The parent button should retain the same visual styling regardless of whether the DropdownMenu is displayed or not. + +#### Placement + +The DropdownMenu should typically appear directly below, or below and to the left of, the parent button. If there isn’t enough space below to display the full DropdownMenu, it can be displayed instead above the parent button. + +## Development guidelines + +DropdownMenu is a React component to render an expandable menu of buttons. It is similar in purpose to a `<select>` element, with the distinction that it does not maintain a value. Instead, each option behaves as an action button. + +### Usage Render a Dropdown Menu with a set of controls: @@ -39,11 +91,11 @@ const MyDropdownMenu = () => ( ); ``` -## Props +### Props The component accepts the following props: -### icon +#### icon The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug to be shown in the collapsed menu button. @@ -53,14 +105,14 @@ The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug to See also: [https://developer.wordpress.org/resource/dashicons/](https://developer.wordpress.org/resource/dashicons/) -### label +#### label A human-readable label to present as accessibility text on the focused collapsed menu button. - Type: `String` - Required: Yes -### controls +#### controls An array of objects describing the options to be shown in the expanded menu. From af9ea75664c29befe35cd8526af0ea8b4f05b070 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Tue, 22 Jan 2019 12:28:12 -0500 Subject: [PATCH 192/691] eslint-plugin: Enforce object-shorthand (#13400) * Packages: Use object shorthand notation when possible * eslint-plugin: Enforce object-shorthand * Docs: Remove coding guidelines merged upstream to WP standard * Docs: Add object shorthand style to coding guidelines --- docs/contributors/coding-guidelines.md | 80 ++++--------------- docs/tool/manifest.js | 6 +- packages/block-library/src/classic/edit.js | 2 +- packages/block-library/src/html/index.js | 2 +- packages/block-library/src/paragraph/index.js | 4 +- packages/block-library/src/verse/index.js | 4 +- .../components/src/button/index.native.js | 8 +- .../components/src/color-palette/index.js | 2 +- .../src/form-token-field/test/index.js | 4 +- .../edit-post/src/components/header/index.js | 2 +- .../src/components/block-draggable/index.js | 4 +- .../src/components/inserter/test/menu.js | 2 +- .../editor/src/editor-styles/ast/parse.js | 14 ++-- packages/eslint-plugin/CHANGELOG.md | 6 ++ packages/eslint-plugin/configs/esnext.js | 1 + packages/hooks/src/createHooks.js | 4 +- packages/rich-text/src/create.js | 2 +- 17 files changed, 54 insertions(+), 93 deletions(-) diff --git a/docs/contributors/coding-guidelines.md b/docs/contributors/coding-guidelines.md index c47fda1ab070a..1b4524ffcf9ac 100644 --- a/docs/contributors/coding-guidelines.md +++ b/docs/contributors/coding-guidelines.md @@ -102,76 +102,30 @@ If an API must be exposed but is clearly not intended to be supported into the f export { __unstableDoAction } from './api'; ``` -### Variable Naming +### Objects -Gutenberg inherits [WordPress' naming conventions of camel-casing](https://make.wordpress.org/core/handbook/best-practices/coding-standards/javascript/#naming-conventions): - ->Variable and function names should be full words, using camel case with a lowercase first letter. This is an area where this standard differs from the WordPress PHP coding standards. -> ->Constructors intended for use with `new` should have a capital first letter (UpperCamelCase). - -However, Gutenberg is more specific about its handling of abbreviations, acronyms, constants, and the ES2015 class construct. - -#### Abbreviations and Acronyms - -[*Abbreviations*](https://en.wikipedia.org/wiki/Abbreviation) must be written as camel case, with an initial capitalized letter followed by lowercase letters. - -[*Acronyms*](https://en.wikipedia.org/wiki/Acronym) must be written with each of its composing letters capitalized. This is intended to reflect that each letter of the acronym is a proper word in its expanded form. - -If an abbreviation or an acronym occurs at the start of a variable name, it must be written to respect the camelcase naming rules covering the first letter of a variable or class definition. For variable assignment, this means writing the abbreviation entirely as lowercase. For class definitions, its initial letter should be capitalized. - -**Examples:** - -```js -// "Id" is an abbreviation of "Identifier": -const userId = 1; - -// "DOM" is an acronym of "Document Object Model": -const currentDOMDocument = window.document; - -// Acronyms and abbreviations at the start of a variable name are consistent -// with camelcase rules covering the first letter of a variable or class. -const domDocument = window.document; -class DOMDocument {} -class IdCollection {} -``` - -#### Class Definition - -A [`class` definition](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) must use the UpperCamelCase convention, regardless of whether it is intended to be used with `new` construction. - -**Example:** +When possible, use [shorthand notation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#New_notations_in_ECMAScript_2015) when defining object property values: ```js -class Earth { - static addHuman( human ) { - Earth.humans.push( human ); - } - - static getHumans() { - return Earth.humans; - } -} - -Earth.humans = []; -``` - -All `@wordpress/element` Components, including stateless function components, should be named using Class Definition naming rules, both for consistency and to reflect the fact that a component may need to be transitioned from a function to a class without breaking compatibility. - -**Examples:** +const a = 10; -```js -class MyComponent extends Component {} +// Bad: +const object = { + a: a, + performAction: function() { + // ... + }, +}; -function MyComponent() {} +// Good: +const object = { + a, + performAction() { + // ... + }, +}; ``` -#### Constants - -An exception to camel case is made for constant values which are never intended to be reassigned or mutated. Such variables must use the [SCREAMING_SNAKE_CASE convention](https://en.wikipedia.org/wiki/Snake_case). - -In almost all cases, a constant should be defined in the top-most scope of a file. It is important to note that [JavaScript's `const` assignment](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const) is conceptually more limited than what is implied here, where a value assigned by `const` in JavaScript can in-fact be mutated, and is only protected against reassignment. A constant as defined in these coding guidelines applies only to values which are expected to never change, and is a strategy for developers to communicate intent more so than it is a technical restriction. - ### Strings String literals should be declared with single-quotes *unless* the string itself contains a single-quote that would need to be escaped–in that case: use a double-quote. If the string contains a single-quote *and* a double-quote, you can use ES6 template strings to avoid escaping the quotes. diff --git a/docs/tool/manifest.js b/docs/tool/manifest.js index 06732edc108dc..56f7a2c8772c4 100644 --- a/docs/tool/manifest.js +++ b/docs/tool/manifest.js @@ -91,10 +91,10 @@ function generateRootManifestFromTOCItems( items, parent = null ) { } pageItems.push( { - title: title, - slug: slug, + title, + slug, markdown_source: `${ baseRepoUrl }\/${ fileName }`, - parent: parent, + parent, } ); if ( Array.isArray( children ) && children.length ) { pageItems = pageItems.concat( generateRootManifestFromTOCItems( children, slug ) ); diff --git a/packages/block-library/src/classic/edit.js b/packages/block-library/src/classic/edit.js index f0eb37ff30ca9..49c1ec2340e50 100644 --- a/packages/block-library/src/classic/edit.js +++ b/packages/block-library/src/classic/edit.js @@ -128,7 +128,7 @@ export default class ClassicEdit extends Component { editor.addButton( 'kitchensink', { tooltip: _x( 'More', 'button to expand options' ), icon: 'dashicon dashicons-editor-kitchensink', - onClick: function() { + onClick() { const button = this; const active = ! button.active(); diff --git a/packages/block-library/src/html/index.js b/packages/block-library/src/html/index.js index 74233c565036e..b537f2a8d6e42 100644 --- a/packages/block-library/src/html/index.js +++ b/packages/block-library/src/html/index.js @@ -59,7 +59,7 @@ export const settings = { ], }, - edit: edit, + edit, save( { attributes } ) { return <RawHTML>{ attributes.content }</RawHTML>; diff --git a/packages/block-library/src/paragraph/index.js b/packages/block-library/src/paragraph/index.js index e315f551cc9f3..934efe8dc1c90 100644 --- a/packages/block-library/src/paragraph/index.js +++ b/packages/block-library/src/paragraph/index.js @@ -174,9 +174,9 @@ export const settings = { 'has-drop-cap': dropCap, } ); const styles = { - backgroundColor: backgroundColor, + backgroundColor, color: textColor, - fontSize: fontSize, + fontSize, textAlign: align, }; diff --git a/packages/block-library/src/verse/index.js b/packages/block-library/src/verse/index.js index 46bfac96d2208..0993bfa4f9c7e 100644 --- a/packages/block-library/src/verse/index.js +++ b/packages/block-library/src/verse/index.js @@ -76,7 +76,7 @@ export const settings = { content: nextContent, } ); } } - style={ { textAlign: textAlign } } + style={ { textAlign } } placeholder={ __( 'Write…' ) } wrapperClassName={ className } onMerge={ mergeBlocks } @@ -91,7 +91,7 @@ export const settings = { return ( <RichText.Content tagName="pre" - style={ { textAlign: textAlign } } + style={ { textAlign } } value={ content } /> ); diff --git a/packages/components/src/button/index.native.js b/packages/components/src/button/index.native.js index cd38fecd9ed2c..176498ecd113b 100644 --- a/packages/components/src/button/index.native.js +++ b/packages/components/src/button/index.native.js @@ -37,16 +37,16 @@ const styles = StyleSheet.create( { fontWeight: 'bold', fontSize: 13, alignSelf: 'flex-end', - marginLeft: marginLeft, - marginBottom: marginBottom, + marginLeft, + marginBottom, }, subscriptActive: { color: 'white', fontWeight: 'bold', fontSize: 13, alignSelf: 'flex-end', - marginLeft: marginLeft, - marginBottom: marginBottom, + marginLeft, + marginBottom, }, } ); diff --git a/packages/components/src/color-palette/index.js b/packages/components/src/color-palette/index.js index 3dfe358954e1f..c70db00206f49 100644 --- a/packages/components/src/color-palette/index.js +++ b/packages/components/src/color-palette/index.js @@ -27,7 +27,7 @@ export default function ColorPalette( { colors, disableCustomColors = false, val return ( <div className={ classes }> { map( colors, ( { color, name } ) => { - const style = { color: color }; + const style = { color }; const itemClasses = classnames( 'components-color-palette__item', { 'is-active': value === color } ); return ( diff --git a/packages/components/src/form-token-field/test/index.js b/packages/components/src/form-token-field/test/index.js index 1fb0992fff556..3199cdfa447cd 100644 --- a/packages/components/src/form-token-field/test/index.js +++ b/packages/components/src/form-token-field/test/index.js @@ -49,7 +49,7 @@ describe( 'FormTokenField', function() { TestUtils.Simulate.keyDown( wrapperElement(), { - keyCode: keyCode, + keyCode, shiftKey: ! ! shiftKey, } ); @@ -58,7 +58,7 @@ describe( 'FormTokenField', function() { function sendKeyPress( charCode ) { TestUtils.Simulate.keyPress( wrapperElement(), - { charCode: charCode } + { charCode } ); } diff --git a/packages/edit-post/src/components/header/index.js b/packages/edit-post/src/components/header/index.js index 0663fcd5f98b3..3f3ed97063cd8 100644 --- a/packages/edit-post/src/components/header/index.js +++ b/packages/edit-post/src/components/header/index.js @@ -92,7 +92,7 @@ export default compose( return { openGeneralSidebar: () => openGeneralSidebar( getBlockSelectionStart() ? 'edit-post/block' : 'edit-post/document' ), - closeGeneralSidebar: closeGeneralSidebar, + closeGeneralSidebar, }; } ), )( Header ); diff --git a/packages/editor/src/components/block-draggable/index.js b/packages/editor/src/components/block-draggable/index.js index a9c3e6cad0d8e..27a44ce2a852b 100644 --- a/packages/editor/src/components/block-draggable/index.js +++ b/packages/editor/src/components/block-draggable/index.js @@ -22,8 +22,8 @@ const BlockDraggable = ( { children, clientId, rootClientId, blockElementId, ind { ( { onDraggableStart, onDraggableEnd } ) => { return children( { - onDraggableStart: onDraggableStart, - onDraggableEnd: onDraggableEnd, + onDraggableStart, + onDraggableEnd, } ); } } diff --git a/packages/editor/src/components/inserter/test/menu.js b/packages/editor/src/components/inserter/test/menu.js index 7538f1639b1fc..0ad4ef34c1a5d 100644 --- a/packages/editor/src/components/inserter/test/menu.js +++ b/packages/editor/src/components/inserter/test/menu.js @@ -93,7 +93,7 @@ const items = [ const DEFAULT_PROPS = { position: 'top center', - items: items, + items, debouncedSpeak: noop, fetchReusableBlocks: noop, setTimeout: noop, diff --git a/packages/editor/src/editor-styles/ast/parse.js b/packages/editor/src/editor-styles/ast/parse.js index 0c91c972f7ba3..4f7e925c9937e 100644 --- a/packages/editor/src/editor-styles/ast/parse.js +++ b/packages/editor/src/editor-styles/ast/parse.js @@ -36,7 +36,7 @@ export default function( css, options ) { */ function position() { - const start = { line: lineno, column: column }; + const start = { line: lineno, column }; return function( node ) { node.position = new Position( start ); whitespace(); @@ -50,7 +50,7 @@ export default function( css, options ) { function Position( start ) { this.start = start; - this.end = { line: lineno, column: column }; + this.end = { line: lineno, column }; this.source = options.source; } @@ -351,8 +351,8 @@ export default function( css, options ) { return pos( { type: 'keyframes', - name: name, - vendor: vendor, + name, + vendor, keyframes: frames, } ); } @@ -382,7 +382,7 @@ export default function( css, options ) { return pos( { type: 'supports', - supports: supports, + supports, rules: style, } ); } @@ -440,7 +440,7 @@ export default function( css, options ) { return pos( { type: 'media', - media: media, + media, rules: style, } ); } @@ -527,7 +527,7 @@ export default function( css, options ) { return pos( { type: 'document', document: doc, - vendor: vendor, + vendor, rules: style, } ); } diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 3d6a36751ddd1..0ab0b2624c58d 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.0.0 (Unreleased) + +### Breaking Changes + +- The `esnext` and `recommended` rulesets now enforce [`object-shorthand`](https://eslint.org/docs/rules/object-shorthand) + ## 1.0.0 (2018-12-12) ### New Features diff --git a/packages/eslint-plugin/configs/esnext.js b/packages/eslint-plugin/configs/esnext.js index 7e01f959fde72..8ea39cb864966 100644 --- a/packages/eslint-plugin/configs/esnext.js +++ b/packages/eslint-plugin/configs/esnext.js @@ -23,6 +23,7 @@ module.exports = { 'no-useless-computed-key': 'error', 'no-useless-constructor': 'error', 'no-var': 'error', + 'object-shorthand': 'error', 'prefer-const': 'error', quotes: [ 'error', 'single', { allowTemplateLiterals: true, avoidEscape: true } ], 'space-unary-ops': [ 'error', { diff --git a/packages/hooks/src/createHooks.js b/packages/hooks/src/createHooks.js index 3e5c0dbf934f0..9915a1167e31f 100644 --- a/packages/hooks/src/createHooks.js +++ b/packages/hooks/src/createHooks.js @@ -34,8 +34,8 @@ function createHooks() { doingFilter: createDoingHook( filters ), didAction: createDidHook( actions ), didFilter: createDidHook( filters ), - actions: actions, - filters: filters, + actions, + filters, }; } diff --git a/packages/rich-text/src/create.js b/packages/rich-text/src/create.js index c45d20ce088be..d320e5fcfcdf2 100644 --- a/packages/rich-text/src/create.js +++ b/packages/rich-text/src/create.js @@ -124,7 +124,7 @@ export function create( { if ( typeof text === 'string' && text.length > 0 ) { return { formats: Array( text.length ), - text: text, + text, }; } From 062857351c4e7b859a9eda021222353ca8d866af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Tue, 22 Jan 2019 18:48:40 +0100 Subject: [PATCH 193/691] Docs: Add section about using link in documentation (#13422) --- CONTRIBUTING.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2fb8900a49c78..41fbdc022d8f6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -126,6 +126,20 @@ Documentation is automatically synced from master to the [Gutenberg Documentatio To add a new documentation page, you'll have to create a Markdown file in the [docs](https://github.com/WordPress/gutenberg/tree/master/docs) folder and add an item to the [toc.json](/docs/toc.json). +### Using links + +It's very likely that at some point you will want to link to other documentation pages. It's worth emphasizing that all documents can be browsed in different contexts: +- Gutenberg Handbook +- GitHub website +- npm website + +That's why it's recommended to use absolute links without the `https://github.com/WordPress/gutenberg` part for all files which match the following patterns: +- `/docs/*.md` +- `/packages/*/README.md` +- `/packages/components/src/**/README.md` + +This way they will be properly handled in all three aforementioned contexts. + ### `@wordpress/component` If you're contributing to the documentation of any component from the `@wordpress/component` package, take a look at its [guidelines for contributing](/packages/components/CONTRIBUTING.md). From 527e25c9d212e2aea3572eec3c0521698d6db98d Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Tue, 22 Jan 2019 13:52:09 -0500 Subject: [PATCH 194/691] Components: Handle multiple Slots by same name (#12882) --- packages/components/CHANGELOG.md | 4 ++ packages/components/src/slot-fill/context.js | 26 ++++++++- packages/components/src/slot-fill/fill.js | 2 +- packages/components/src/slot-fill/slot.js | 2 +- .../slot-fill/test/__snapshots__/slot.js.snap | 54 +++++++++++++++++++ .../components/src/slot-fill/test/slot.js | 44 +++++++++++++++ 6 files changed, 128 insertions(+), 4 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 93591222faad9..ad2e5be8e7121 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -5,6 +5,10 @@ - `withFilters` has been optimized to avoid binding hook handlers for each mounted instance of the component, instead using a single centralized hook delegator. - `withFilters` has been optimized to reuse a single shared component definition for all filtered instances of the component. +### Bug Fixes + +- Resolves a conflict where two instance of Slot would produce an inconsistent or duplicated rendering output. + ## 7.0.5 (2019-01-03) ## 7.0.4 (2018-12-12) diff --git a/packages/components/src/slot-fill/context.js b/packages/components/src/slot-fill/context.js index df4476fea5b9c..a39ac94013e0a 100644 --- a/packages/components/src/slot-fill/context.js +++ b/packages/components/src/slot-fill/context.js @@ -41,12 +41,21 @@ class SlotFillProvider extends Component { } registerSlot( name, slot ) { + const previousSlot = this.slots[ name ]; this.slots[ name ] = slot; this.forceUpdateFills( name ); // Sometimes the fills are registered after the initial render of slot // But before the registerSlot call, we need to rerender the slot this.forceUpdateSlot( name ); + + // If a new instance of a slot is being mounted while another with the + // same name exists, force its update _after_ the new slot has been + // assigned into the instance, such that its own rendering of children + // will be empty (the new Slot will subsume all fills for this name). + if ( previousSlot ) { + previousSlot.forceUpdate(); + } } registerFill( name, instance ) { @@ -57,7 +66,14 @@ class SlotFillProvider extends Component { this.forceUpdateSlot( name ); } - unregisterSlot( name ) { + unregisterSlot( name, instance ) { + // If a previous instance of a Slot by this name unmounts, do nothing, + // as the slot and its fills should only be removed for the current + // known instance. + if ( this.slots[ name ] !== instance ) { + return; + } + delete this.slots[ name ]; this.forceUpdateFills( name ); } @@ -75,7 +91,13 @@ class SlotFillProvider extends Component { return this.slots[ name ]; } - getFills( name ) { + getFills( name, slotInstance ) { + // Fills should only be returned for the current instance of the slot + // in which they occupy. + if ( this.slots[ name ] !== slotInstance ) { + return []; + } + return sortBy( this.fills[ name ], 'occurrence' ); } diff --git a/packages/components/src/slot-fill/fill.js b/packages/components/src/slot-fill/fill.js index 66b025ca00178..7c2b3fb0ad5a2 100644 --- a/packages/components/src/slot-fill/fill.js +++ b/packages/components/src/slot-fill/fill.js @@ -62,7 +62,7 @@ class FillComponent extends Component { let { children } = this.props; const slot = getSlot( name ); - if ( ! slot || ! slot.props.bubblesVirtually ) { + if ( ! slot || ! slot.node || ! slot.props.bubblesVirtually ) { return null; } diff --git a/packages/components/src/slot-fill/slot.js b/packages/components/src/slot-fill/slot.js index 4f53cd6d10300..13a226eab5de5 100644 --- a/packages/components/src/slot-fill/slot.js +++ b/packages/components/src/slot-fill/slot.js @@ -63,7 +63,7 @@ class SlotComponent extends Component { return <div ref={ this.bindNode } />; } - const fills = map( getFills( name ), ( fill ) => { + const fills = map( getFills( name, this ), ( fill ) => { const fillKey = fill.occurrence; const fillChildren = isFunction( fill.props.children ) ? fill.props.children( fillProps ) : fill.props.children; diff --git a/packages/components/src/slot-fill/test/__snapshots__/slot.js.snap b/packages/components/src/slot-fill/test/__snapshots__/slot.js.snap index 709d1522fcbf4..79b43ad4c65c0 100644 --- a/packages/components/src/slot-fill/test/__snapshots__/slot.js.snap +++ b/packages/components/src/slot-fill/test/__snapshots__/slot.js.snap @@ -1,5 +1,59 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Slot bubblesVirtually false should subsume another slot by the same name 1`] = ` +Array [ + <div + data-position="first" + />, + <div + data-position="second" + > + Content + </div>, +] +`; + +exports[`Slot bubblesVirtually false should subsume another slot by the same name 2`] = ` +Array [ + <div + data-position="first" + />, + <div + data-position="second" + > + Content + </div>, +] +`; + +exports[`Slot bubblesVirtually true should subsume another slot by the same name 1`] = ` +Array [ + <div + data-position="first" + > + <div /> + </div>, + <div + data-position="second" + > + <div /> + </div>, +] +`; + +exports[`Slot bubblesVirtually true should subsume another slot by the same name 2`] = ` +Array [ + <div + data-position="first" + />, + <div + data-position="second" + > + <div /> + </div>, +] +`; + exports[`Slot should re-render Slot when not bubbling virtually 1`] = ` Array [ <div> diff --git a/packages/components/src/slot-fill/test/slot.js b/packages/components/src/slot-fill/test/slot.js index a12c9cd99c6ca..47ba2e16de524 100644 --- a/packages/components/src/slot-fill/test/slot.js +++ b/packages/components/src/slot-fill/test/slot.js @@ -209,4 +209,48 @@ describe( 'Slot', () => { expect( testRenderer.toJSON() ).toMatchSnapshot(); } ); + + [ false, true ].forEach( ( bubblesVirtually ) => { + describe( 'bubblesVirtually ' + bubblesVirtually, () => { + it( 'should subsume another slot by the same name', () => { + const testRenderer = ReactTestRenderer.create( + <Provider> + <div data-position="first"> + <Slot name="egg" bubblesVirtually={ bubblesVirtually } /> + </div> + <div data-position="second"></div> + <Fill name="egg">Content</Fill> + </Provider> + ); + + testRenderer.update( + <Provider> + <div data-position="first"> + <Slot name="egg" bubblesVirtually={ bubblesVirtually } /> + </div> + <div data-position="second"> + <Slot name="egg" bubblesVirtually={ bubblesVirtually } /> + </div> + <Fill name="egg">Content</Fill> + </Provider> + ); + + expect( testRenderer.toJSON() ).toMatchSnapshot(); + + testRenderer.update( + <Provider> + <div data-position="first"></div> + <div data-position="second"> + <Slot name="egg" bubblesVirtually={ bubblesVirtually } /> + </div> + <Fill name="egg">Content</Fill> + </Provider> + ); + + expect( testRenderer.toJSON() ).toMatchSnapshot(); + + expect( testRenderer.getInstance().slots ).toHaveProperty( 'egg' ); + } ); + } ); + } ); } ); From 33d2acb098d5399dbba206b979be54aad015497e Mon Sep 17 00:00:00 2001 From: Robert Anderson <robert@noisysocks.com> Date: Wed, 23 Jan 2019 16:41:45 +1100 Subject: [PATCH 195/691] Reusable blocks: Improve UX for non-privileged users (#12378) Improves the UX of creating, editing, and deleting a reusable block when logged in as an author or contributor by disabling the _Add to Reusable Blocks_, _Edit_, and _Remove from Reusable Blocks_ buttons when necessary. This is accomplished under the hood by introducing the `canUser()` selector to `core-data` which allows callers to query whether the REST API supports performing a given action on a given resource, e.g. one can query whether the logged in user can create posts by running `wp.data.select( 'core' ).canUser( 'create', 'posts' )`. The existing `hasUploadPermissions()` selector is changed to use `canUser( 'create', 'media' )` under the hood. --- .../developers/data/data-core.md | 53 ++++++++++- lib/client-assets.php | 1 + lib/packages-dependencies.php | 1 + .../src/block/edit-panel/index.js | 3 +- packages/block-library/src/block/edit.js | 6 +- packages/core-data/package.json | 1 + packages/core-data/src/actions.js | 22 ++++- packages/core-data/src/reducer.js | 18 ++-- packages/core-data/src/resolvers.js | 59 +++++++++++- packages/core-data/src/selectors.js | 45 ++++++++- packages/core-data/src/test/actions.js | 12 ++- packages/core-data/src/test/reducer.js | 28 +++--- packages/core-data/src/test/resolvers.js | 95 ++++++++++++++++++- packages/core-data/src/test/selectors.js | 28 ++++++ .../reusable-block-convert-button.js | 40 ++++---- .../reusable-block-delete-button.js | 26 +++-- .../reusable-block-delete-button.js.snap | 1 + .../test/reusable-block-convert-button.js | 4 +- .../test/reusable-block-delete-button.js | 16 +++- .../src/components/media-placeholder/index.js | 6 +- .../src/components/media-upload/check.js | 9 +- 21 files changed, 400 insertions(+), 74 deletions(-) diff --git a/docs/designers-developers/developers/data/data-core.md b/docs/designers-developers/developers/data/data-core.md index 2a98c3bb5d397..9b01fe325c295 100644 --- a/docs/designers-developers/developers/data/data-core.md +++ b/docs/designers-developers/developers/data/data-core.md @@ -136,17 +136,50 @@ get back from the oEmbed preview API. Is the preview for the URL an oEmbed link fallback. -### hasUploadPermissions +### hasUploadPermissions (deprecated) -Return Upload Permissions. +Returns whether the current user can upload media. + +Calling this may trigger an OPTIONS request to the REST API via the +`canUser()` resolver. + +https://developer.wordpress.org/rest-api/reference/ + +*Deprecated* + +Deprecated since 5.0. Callers should use the more generic `canUser()` selector instead of + `hasUploadPermissions()`, e.g. `canUser( 'create', 'media' )`. *Parameters* - * state: State tree. + * state: Data state. *Returns* -Upload Permissions. +Whether or not the user can upload media. Defaults to `true` if the OPTIONS + request is being made. + +### canUser + +Returns whether the current user can perform the given action on the given +REST resource. + +Calling this may trigger an OPTIONS request to the REST API via the +`canUser()` resolver. + +https://developer.wordpress.org/rest-api/reference/ + +*Parameters* + + * state: Data state. + * action: Action to check. One of: 'create', 'read', 'update', 'delete'. + * resource: REST resource to check, e.g. 'media' or 'posts'. + * id: Optional ID of the rest resource to check. + +*Returns* + +Whether or not the user can perform the action, + or `undefined` if the OPTIONS request is still being made. ## Actions @@ -213,4 +246,14 @@ Returns an action object used in signalling that Upload permissions have been re *Parameters* - * hasUploadPermissions: Does the user have permission to upload files? \ No newline at end of file + * hasUploadPermissions: Does the user have permission to upload files? + +### receiveUserPermission + +Returns an action object used in signalling that the current user has +permission to perform an action on a REST resource. + +*Parameters* + + * key: A key that represents the action and REST resource. + * isAllowed: Whether or not the user can perform the action. \ No newline at end of file diff --git a/lib/client-assets.php b/lib/client-assets.php index 42624b3e1c294..630a50434c72f 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -1086,6 +1086,7 @@ function gutenberg_editor_scripts_and_styles( $hook ) { sprintf( '/wp/v2/types/%s?context=edit', $post_type ), sprintf( '/wp/v2/users/me?post_type=%s&context=edit', $post_type ), array( '/wp/v2/media', 'OPTIONS' ), + array( '/wp/v2/blocks', 'OPTIONS' ), ); /** diff --git a/lib/packages-dependencies.php b/lib/packages-dependencies.php index f1b785f15b011..ef1b2bc22cdb9 100644 --- a/lib/packages-dependencies.php +++ b/lib/packages-dependencies.php @@ -84,6 +84,7 @@ 'lodash', 'wp-api-fetch', 'wp-data', + 'wp-deprecated', 'wp-url', ), 'wp-data' => array( diff --git a/packages/block-library/src/block/edit-panel/index.js b/packages/block-library/src/block/edit-panel/index.js index e91c76cc6bf52..eb620a877b63a 100644 --- a/packages/block-library/src/block/edit-panel/index.js +++ b/packages/block-library/src/block/edit-panel/index.js @@ -53,7 +53,7 @@ class ReusableBlockEditPanel extends Component { } render() { - const { isEditing, title, isSaving, onEdit, instanceId } = this.props; + const { isEditing, title, isSaving, isEditDisabled, onEdit, instanceId } = this.props; return ( <Fragment> @@ -66,6 +66,7 @@ class ReusableBlockEditPanel extends Component { ref={ this.editButton } isLarge className="reusable-block-edit-panel__button" + disabled={ isEditDisabled } onClick={ onEdit } > { __( 'Edit' ) } diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js index d95b07aedb915..62e4acf0640d1 100644 --- a/packages/block-library/src/block/edit.js +++ b/packages/block-library/src/block/edit.js @@ -97,7 +97,7 @@ class ReusableBlockEdit extends Component { } render() { - const { isSelected, reusableBlock, block, isFetching, isSaving } = this.props; + const { isSelected, reusableBlock, block, isFetching, isSaving, canUpdateBlock } = this.props; const { isEditing, title, changedAttributes } = this.state; if ( ! reusableBlock && isFetching ) { @@ -130,6 +130,7 @@ class ReusableBlockEdit extends Component { isEditing={ isEditing } title={ title !== null ? title : reusableBlock.title } isSaving={ isSaving && ! reusableBlock.isTemporary } + isEditDisabled={ ! canUpdateBlock } onEdit={ this.startEditing } onChangeTitle={ this.setTitle } onSave={ this.save } @@ -151,6 +152,8 @@ export default compose( [ __experimentalIsSavingReusableBlock: isSavingReusableBlock, getBlock, } = select( 'core/editor' ); + const { canUser } = select( 'core' ); + const { ref } = ownProps.attributes; const reusableBlock = getReusableBlock( ref ); @@ -159,6 +162,7 @@ export default compose( [ isFetching: isFetchingReusableBlock( ref ), isSaving: isSavingReusableBlock( ref ), block: reusableBlock ? getBlock( reusableBlock.clientId ) : null, + canUpdateBlock: !! reusableBlock && ! reusableBlock.isTemporary && !! canUser( 'update', 'blocks', ref ), }; } ), withDispatch( ( dispatch, ownProps ) => { diff --git a/packages/core-data/package.json b/packages/core-data/package.json index b28d5c2eb0c24..388ab7b45e8f0 100644 --- a/packages/core-data/package.json +++ b/packages/core-data/package.json @@ -24,6 +24,7 @@ "@babel/runtime": "^7.0.0", "@wordpress/api-fetch": "file:../api-fetch", "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", "@wordpress/url": "file:../url", "equivalent-key-map": "^0.2.2", "lodash": "^4.17.10", diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js index ebbcfb7e76150..c15bd647b9fcd 100644 --- a/packages/core-data/src/actions.js +++ b/packages/core-data/src/actions.js @@ -137,7 +137,25 @@ export function* saveEntityRecord( kind, name, record ) { */ export function receiveUploadPermissions( hasUploadPermissions ) { return { - type: 'RECEIVE_UPLOAD_PERMISSIONS', - hasUploadPermissions, + type: 'RECEIVE_USER_PERMISSION', + key: 'create/media', + isAllowed: hasUploadPermissions, + }; +} + +/** + * Returns an action object used in signalling that the current user has + * permission to perform an action on a REST resource. + * + * @param {string} key A key that represents the action and REST resource. + * @param {boolean} isAllowed Whether or not the user can perform the action. + * + * @return {Object} Action object. + */ +export function receiveUserPermission( key, isAllowed ) { + return { + type: 'RECEIVE_USER_PERMISSION', + key, + isAllowed, }; } diff --git a/packages/core-data/src/reducer.js b/packages/core-data/src/reducer.js index bb14a0283b7ca..7785667822d68 100644 --- a/packages/core-data/src/reducer.js +++ b/packages/core-data/src/reducer.js @@ -218,17 +218,21 @@ export function embedPreviews( state = {}, action ) { } /** - * Reducer managing Upload permissions. + * State which tracks whether the user can perform an action on a REST + * resource. * - * @param {Object} state Current state. - * @param {Object} action Dispatched action. + * @param {Object} state Current state. + * @param {Object} action Dispatched action. * * @return {Object} Updated state. */ -export function hasUploadPermissions( state = true, action ) { +export function userPermissions( state = {}, action ) { switch ( action.type ) { - case 'RECEIVE_UPLOAD_PERMISSIONS': - return action.hasUploadPermissions; + case 'RECEIVE_USER_PERMISSION': + return { + ...state, + [ action.key ]: action.isAllowed, + }; } return state; @@ -241,5 +245,5 @@ export default combineReducers( { themeSupports, entities, embedPreviews, - hasUploadPermissions, + userPermissions, } ); diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index b8bd4ed238285..009fe1b7d5b42 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -1,12 +1,13 @@ /** * External dependencies */ -import { find, includes, get, hasIn } from 'lodash'; +import { find, includes, get, hasIn, compact } from 'lodash'; /** * WordPress dependencies */ import { addQueryArgs } from '@wordpress/url'; +import deprecated from '@wordpress/deprecated'; /** * Internal dependencies @@ -16,7 +17,7 @@ import { receiveEntityRecords, receiveThemeSupports, receiveEmbedPreview, - receiveUploadPermissions, + receiveUserPermission, } from './actions'; import { getKindEntities } from './entities'; import { apiFetch } from './controls'; @@ -101,9 +102,57 @@ export function* getEmbedPreview( url ) { /** * Requests Upload Permissions from the REST API. + * + * @deprecated since 5.0. Callers should use the more generic `canUser()` selector instead of + * `hasUploadPermissions()`, e.g. `canUser( 'create', 'media' )`. */ export function* hasUploadPermissions() { - const response = yield apiFetch( { path: '/wp/v2/media', method: 'OPTIONS', parse: false } ); + deprecated( "select( 'core' ).hasUploadPermissions()", { + alternative: "select( 'core' ).canUser( 'create', 'media' )", + } ); + yield* canUser( 'create', 'media' ); +} + +/** + * Checks whether the current user can perform the given action on the given + * REST resource. + * + * @param {string} action Action to check. One of: 'create', 'read', 'update', + * 'delete'. + * @param {string} resource REST resource to check, e.g. 'media' or 'posts'. + * @param {?string} id ID of the rest resource to check. + */ +export function* canUser( action, resource, id ) { + const methods = { + create: 'POST', + read: 'GET', + update: 'PUT', + delete: 'DELETE', + }; + + const method = methods[ action ]; + if ( ! method ) { + throw new Error( `'${ action }' is not a valid action.` ); + } + + const path = id ? `/wp/v2/${ resource }/${ id }` : `/wp/v2/${ resource }`; + + let response; + try { + response = yield apiFetch( { + path, + // Ideally this would always be an OPTIONS request, but unfortunately there's + // a bug in the REST API which causes the Allow header to not be sent on + // OPTIONS requests to /posts/:id routes. + // https://core.trac.wordpress.org/ticket/45753 + method: id ? 'GET' : 'OPTIONS', + parse: false, + } ); + } catch ( error ) { + // Do nothing if our OPTIONS request comes back with an API error (4xx or + // 5xx). The previously determined isAllowed value will remain in the store. + return; + } let allowHeader; if ( hasIn( response, [ 'headers', 'get' ] ) ) { @@ -116,5 +165,7 @@ export function* hasUploadPermissions() { allowHeader = get( response, [ 'headers', 'Allow' ], '' ); } - yield receiveUploadPermissions( includes( allowHeader, 'POST' ) ); + const key = compact( [ action, resource, id ] ).join( '/' ); + const isAllowed = includes( allowHeader, method ); + yield receiveUserPermission( key, isAllowed ); } diff --git a/packages/core-data/src/selectors.js b/packages/core-data/src/selectors.js index 95e9f867aa3c0..b9ed8b575e086 100644 --- a/packages/core-data/src/selectors.js +++ b/packages/core-data/src/selectors.js @@ -2,12 +2,13 @@ * External dependencies */ import createSelector from 'rememo'; -import { map, find, get, filter } from 'lodash'; +import { map, find, get, filter, compact, defaultTo } from 'lodash'; /** * WordPress dependencies */ import { select } from '@wordpress/data'; +import deprecated from '@wordpress/deprecated'; /** * Internal dependencies @@ -171,12 +172,46 @@ export function isPreviewEmbedFallback( state, url ) { } /** - * Return Upload Permissions. + * Returns whether the current user can upload media. * - * @param {Object} state State tree. + * Calling this may trigger an OPTIONS request to the REST API via the + * `canUser()` resolver. * - * @return {boolean} Upload Permissions. + * https://developer.wordpress.org/rest-api/reference/ + * + * @deprecated since 5.0. Callers should use the more generic `canUser()` selector instead of + * `hasUploadPermissions()`, e.g. `canUser( 'create', 'media' )`. + * + * @param {Object} state Data state. + * + * @return {boolean} Whether or not the user can upload media. Defaults to `true` if the OPTIONS + * request is being made. */ export function hasUploadPermissions( state ) { - return state.hasUploadPermissions; + deprecated( "select( 'core' ).hasUploadPermissions()", { + alternative: "select( 'core' ).canUser( 'create', 'media' )", + } ); + return defaultTo( canUser( state, 'create', 'media' ), true ); +} + +/** + * Returns whether the current user can perform the given action on the given + * REST resource. + * + * Calling this may trigger an OPTIONS request to the REST API via the + * `canUser()` resolver. + * + * https://developer.wordpress.org/rest-api/reference/ + * + * @param {Object} state Data state. + * @param {string} action Action to check. One of: 'create', 'read', 'update', 'delete'. + * @param {string} resource REST resource to check, e.g. 'media' or 'posts'. + * @param {string=} id Optional ID of the rest resource to check. + * + * @return {boolean|undefined} Whether or not the user can perform the action, + * or `undefined` if the OPTIONS request is still being made. + */ +export function canUser( state, action, resource, id ) { + const key = compact( [ action, resource, id ] ).join( '/' ); + return get( state, [ 'userPermissions', key ] ); } diff --git a/packages/core-data/src/test/actions.js b/packages/core-data/src/test/actions.js index 86e7f50ed53c1..85c94eeaa5224 100644 --- a/packages/core-data/src/test/actions.js +++ b/packages/core-data/src/test/actions.js @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { saveEntityRecord, receiveEntityRecords } from '../actions'; +import { saveEntityRecord, receiveEntityRecords, receiveUserPermission } from '../actions'; describe( 'saveEntityRecord', () => { it( 'triggers a POST request for a new record', async () => { @@ -58,3 +58,13 @@ describe( 'saveEntityRecord', () => { expect( received ).toEqual( receiveEntityRecords( 'root', 'postType', postType, undefined, true ) ); } ); } ); + +describe( 'receiveUserPermission', () => { + it( 'builds an action object', () => { + expect( receiveUserPermission( 'create/media', true ) ).toEqual( { + type: 'RECEIVE_USER_PERMISSION', + key: 'create/media', + isAllowed: true, + } ); + } ); +} ); diff --git a/packages/core-data/src/test/reducer.js b/packages/core-data/src/test/reducer.js index f6647becf0743..51bc4611ad7d9 100644 --- a/packages/core-data/src/test/reducer.js +++ b/packages/core-data/src/test/reducer.js @@ -7,7 +7,7 @@ import { filter } from 'lodash'; /** * Internal dependencies */ -import { terms, entities, embedPreviews, hasUploadPermissions } from '../reducer'; +import { terms, entities, embedPreviews, userPermissions } from '../reducer'; describe( 'terms()', () => { it( 'returns an empty object by default', () => { @@ -118,21 +118,25 @@ describe( 'embedPreviews()', () => { } ); } ); -describe( 'hasUploadPermissions()', () => { - it( 'returns true by default', () => { - const state = hasUploadPermissions( undefined, {} ); - - expect( state ).toEqual( true ); +describe( 'userPermissions()', () => { + it( 'defaults to an empty object', () => { + const state = userPermissions( undefined, {} ); + expect( state ).toEqual( {} ); } ); - it( 'returns with updated upload permissions value', () => { - const originalState = true; + it( 'updates state with whether an action is allowed', () => { + const original = deepFreeze( { + 'create/media': false, + } ); - const state = hasUploadPermissions( originalState, { - type: 'RECEIVE_UPLOAD_PERMISSIONS', - hasUploadPermissions: false, + const state = userPermissions( original, { + type: 'RECEIVE_USER_PERMISSION', + key: 'create/media', + isAllowed: true, } ); - expect( state ).toEqual( false ); + expect( state ).toEqual( { + 'create/media': true, + } ); } ); } ); diff --git a/packages/core-data/src/test/resolvers.js b/packages/core-data/src/test/resolvers.js index 8b008bc1cad68..325e4ce9c322a 100644 --- a/packages/core-data/src/test/resolvers.js +++ b/packages/core-data/src/test/resolvers.js @@ -1,8 +1,9 @@ /** * Internal dependencies */ -import { getEntityRecord, getEntityRecords, getEmbedPreview } from '../resolvers'; -import { receiveEntityRecords, receiveEmbedPreview } from '../actions'; +import { getEntityRecord, getEntityRecords, getEmbedPreview, canUser } from '../resolvers'; +import { receiveEntityRecords, receiveEmbedPreview, receiveUserPermission } from '../actions'; +import { apiFetch } from '../controls'; describe( 'getEntityRecord', () => { const POST_TYPE = { slug: 'post' }; @@ -68,3 +69,93 @@ describe( 'getEmbedPreview', () => { expect( received ).toEqual( receiveEmbedPreview( UNEMBEDDABLE_URL, UNEMBEDDABLE_RESPONSE ) ); } ); } ); + +describe( 'canUser', () => { + it( 'does nothing when there is an API error', () => { + const generator = canUser( 'create', 'media' ); + + let received = generator.next(); + expect( received.done ).toBe( false ); + expect( received.value ).toEqual( apiFetch( { + path: '/wp/v2/media', + method: 'OPTIONS', + parse: false, + } ) ); + + received = generator.throw( { status: 404 } ); + expect( received.done ).toBe( true ); + expect( received.value ).toBeUndefined(); + } ); + + it( 'receives false when the user is not allowed to perform an action', () => { + const generator = canUser( 'create', 'media' ); + + let received = generator.next(); + expect( received.done ).toBe( false ); + expect( received.value ).toEqual( apiFetch( { + path: '/wp/v2/media', + method: 'OPTIONS', + parse: false, + } ) ); + + received = generator.next( { + headers: { + Allow: 'GET', + }, + } ); + expect( received.done ).toBe( false ); + expect( received.value ).toEqual( receiveUserPermission( 'create/media', false ) ); + + received = generator.next(); + expect( received.done ).toBe( true ); + expect( received.value ).toBeUndefined(); + } ); + + it( 'receives true when the user is allowed to perform an action', () => { + const generator = canUser( 'create', 'media' ); + + let received = generator.next(); + expect( received.done ).toBe( false ); + expect( received.value ).toEqual( apiFetch( { + path: '/wp/v2/media', + method: 'OPTIONS', + parse: false, + } ) ); + + received = generator.next( { + headers: { + Allow: 'POST, GET, PUT, DELETE', + }, + } ); + expect( received.done ).toBe( false ); + expect( received.value ).toEqual( receiveUserPermission( 'create/media', true ) ); + + received = generator.next(); + expect( received.done ).toBe( true ); + expect( received.value ).toBeUndefined(); + } ); + + it( 'receives true when the user is allowed to perform an action on a specific resource', () => { + const generator = canUser( 'update', 'blocks', 123 ); + + let received = generator.next(); + expect( received.done ).toBe( false ); + expect( received.value ).toEqual( apiFetch( { + path: '/wp/v2/blocks/123', + method: 'GET', + parse: false, + } ) ); + + received = generator.next( { + headers: { + Allow: 'POST, GET, PUT, DELETE', + }, + } ); + expect( received.done ).toBe( false ); + expect( received.value ).toEqual( receiveUserPermission( 'update/blocks/123', true ) ); + + received = generator.next(); + expect( received.done ).toBe( true ); + expect( received.value ).toBeUndefined(); + } ); +} ); diff --git a/packages/core-data/src/test/selectors.js b/packages/core-data/src/test/selectors.js index b982ada4f3a9d..f2a2885e77662 100644 --- a/packages/core-data/src/test/selectors.js +++ b/packages/core-data/src/test/selectors.js @@ -11,6 +11,7 @@ import { getEntityRecords, getEmbedPreview, isPreviewEmbedFallback, + canUser, } from '../selectors'; describe( 'getEntityRecord', () => { @@ -117,3 +118,30 @@ describe( 'isPreviewEmbedFallback()', () => { expect( isPreviewEmbedFallback( state, 'http://example.com/' ) ).toEqual( true ); } ); } ); + +describe( 'canUser', () => { + it( 'returns undefined by default', () => { + const state = deepFreeze( { + userPermissions: {}, + } ); + expect( canUser( state, 'create', 'media' ) ).toBe( undefined ); + } ); + + it( 'returns whether an action can be performed', () => { + const state = deepFreeze( { + userPermissions: { + 'create/media': false, + }, + } ); + expect( canUser( state, 'create', 'media' ) ).toBe( false ); + } ); + + it( 'returns whether an action can be performed for a given resource', () => { + const state = deepFreeze( { + userPermissions: { + 'create/media/123': false, + }, + } ); + expect( canUser( state, 'create', 'media', 123 ) ).toBe( false ); + } ); +} ); diff --git a/packages/editor/src/components/block-settings-menu/reusable-block-convert-button.js b/packages/editor/src/components/block-settings-menu/reusable-block-convert-button.js index 6d39538aa5e5a..2e528a01e7d91 100644 --- a/packages/editor/src/components/block-settings-menu/reusable-block-convert-button.js +++ b/packages/editor/src/components/block-settings-menu/reusable-block-convert-button.js @@ -15,7 +15,7 @@ import { compose } from '@wordpress/compose'; export function ReusableBlockConvertButton( { isVisible, - isStaticBlock, + isReusable, onConvertToStatic, onConvertToReusable, } ) { @@ -25,7 +25,7 @@ export function ReusableBlockConvertButton( { return ( <Fragment> - { isStaticBlock && ( + { ! isReusable && ( <MenuItem className="editor-block-settings-menu__control" icon="controls-repeat" @@ -34,7 +34,7 @@ export function ReusableBlockConvertButton( { { __( 'Add to Reusable Blocks' ) } </MenuItem> ) } - { ! isStaticBlock && ( + { isReusable && ( <MenuItem className="editor-block-settings-menu__control" icon="controls-repeat" @@ -54,32 +54,40 @@ export default compose( [ canInsertBlockType, __experimentalGetReusableBlock: getReusableBlock, } = select( 'core/editor' ); + const { canUser } = select( 'core' ); const blocks = getBlocksByClientId( clientIds ); - const isVisible = ( - // Hide 'Add to Reusable Blocks' when Reusable Blocks are disabled, i.e. when - // core/block is not in the allowed_block_types filter. + const isReusable = ( + blocks.length === 1 && + blocks[ 0 ] && + isReusableBlock( blocks[ 0 ] ) && + !! getReusableBlock( blocks[ 0 ].attributes.ref ) + ); + + // Show 'Convert to Regular Block' when selected block is a reusable block + const isVisible = isReusable || ( + // Hide 'Add to Reusable Blocks' when reusable blocks are disabled canInsertBlockType( 'core/block' ) && every( blocks, ( block ) => ( - // Guard against the case where a regular block has *just* been converted to a - // reusable block and doesn't yet exist in the editor store. + // Guard against the case where a regular block has *just* been converted !! block && - // Only show the option to covert to reusable blocks on valid blocks. + + // Hide 'Add to Reusable Blocks' on invalid blocks block.isValid && - // Make sure the block supports being converted into a reusable block (by default that is the case). + + // Hide 'Add to Reusable Blocks' when block doesn't support being made reusable hasBlockSupport( block.name, 'reusable', true ) - ) ) + ) ) && + + // Hide 'Add to Reusable Blocks' when current doesn't have permission to do that + !! canUser( 'create', 'blocks' ) ); return { + isReusable, isVisible, - isStaticBlock: isVisible && ( - blocks.length !== 1 || - ! isReusableBlock( blocks[ 0 ] ) || - ! getReusableBlock( blocks[ 0 ].attributes.ref ) - ), }; } ), withDispatch( ( dispatch, { clientIds, onToggle = noop } ) => { diff --git a/packages/editor/src/components/block-settings-menu/reusable-block-delete-button.js b/packages/editor/src/components/block-settings-menu/reusable-block-delete-button.js index 52f2ad08108f9..5a73912b36ee7 100644 --- a/packages/editor/src/components/block-settings-menu/reusable-block-delete-button.js +++ b/packages/editor/src/components/block-settings-menu/reusable-block-delete-button.js @@ -12,8 +12,8 @@ import { __ } from '@wordpress/i18n'; import { isReusableBlock } from '@wordpress/blocks'; import { withSelect, withDispatch } from '@wordpress/data'; -export function ReusableBlockDeleteButton( { reusableBlock, onDelete } ) { - if ( ! reusableBlock ) { +export function ReusableBlockDeleteButton( { isVisible, isDisabled, onDelete } ) { + if ( ! isVisible ) { return null; } @@ -21,8 +21,8 @@ export function ReusableBlockDeleteButton( { reusableBlock, onDelete } ) { <MenuItem className="editor-block-settings-menu__control" icon="no" - disabled={ reusableBlock.isTemporary } - onClick={ () => onDelete( reusableBlock.id ) } + disabled={ isDisabled } + onClick={ () => onDelete() } > { __( 'Remove from Reusable Blocks' ) } </MenuItem> @@ -35,18 +35,27 @@ export default compose( [ getBlock, __experimentalGetReusableBlock: getReusableBlock, } = select( 'core/editor' ); + const { canUser } = select( 'core' ); + const block = getBlock( clientId ); + + const reusableBlock = block && isReusableBlock( block ) ? + getReusableBlock( block.attributes.ref ) : + null; + return { - reusableBlock: block && isReusableBlock( block ) ? getReusableBlock( block.attributes.ref ) : null, + isVisible: !! reusableBlock && !! canUser( 'delete', 'blocks', reusableBlock.id ), + isDisabled: reusableBlock && reusableBlock.isTemporary, }; } ), - withDispatch( ( dispatch, { onToggle = noop } ) => { + withDispatch( ( dispatch, { clientId, onToggle = noop }, { select } ) => { const { __experimentalDeleteReusableBlock: deleteReusableBlock, } = dispatch( 'core/editor' ); + const { getBlock } = select( 'core/editor' ); return { - onDelete( id ) { + onDelete() { // TODO: Make this a <Confirm /> component or similar // eslint-disable-next-line no-alert const hasConfirmed = window.confirm( __( @@ -55,7 +64,8 @@ export default compose( [ ) ); if ( hasConfirmed ) { - deleteReusableBlock( id ); + const block = getBlock( clientId ); + deleteReusableBlock( block.attributes.ref ); onToggle(); } }, diff --git a/packages/editor/src/components/block-settings-menu/test/__snapshots__/reusable-block-delete-button.js.snap b/packages/editor/src/components/block-settings-menu/test/__snapshots__/reusable-block-delete-button.js.snap index c31a0f8e9f9b4..23e876d36a9c6 100644 --- a/packages/editor/src/components/block-settings-menu/test/__snapshots__/reusable-block-delete-button.js.snap +++ b/packages/editor/src/components/block-settings-menu/test/__snapshots__/reusable-block-delete-button.js.snap @@ -3,6 +3,7 @@ exports[`ReusableBlockDeleteButton matches the snapshot 1`] = ` <WithInstanceId(MenuItem) className="editor-block-settings-menu__control" + disabled={false} icon="no" onClick={[Function]} > diff --git a/packages/editor/src/components/block-settings-menu/test/reusable-block-convert-button.js b/packages/editor/src/components/block-settings-menu/test/reusable-block-convert-button.js index 1aabafc55ef92..c6fba313e31b3 100644 --- a/packages/editor/src/components/block-settings-menu/test/reusable-block-convert-button.js +++ b/packages/editor/src/components/block-settings-menu/test/reusable-block-convert-button.js @@ -27,7 +27,7 @@ describe( 'ReusableBlockConvertButton', () => { const wrapper = getShallowRenderOutput( <ReusableBlockConvertButton isVisible - isStaticBlock + isReusable={ false } onConvertToReusable={ onConvert } /> ); @@ -43,7 +43,7 @@ describe( 'ReusableBlockConvertButton', () => { const wrapper = getShallowRenderOutput( <ReusableBlockConvertButton isVisible - isStaticBlock={ false } + isReusable onConvertToStatic={ onConvert } /> ); diff --git a/packages/editor/src/components/block-settings-menu/test/reusable-block-delete-button.js b/packages/editor/src/components/block-settings-menu/test/reusable-block-delete-button.js index 9da36d7f24e60..39299becf29c2 100644 --- a/packages/editor/src/components/block-settings-menu/test/reusable-block-delete-button.js +++ b/packages/editor/src/components/block-settings-menu/test/reusable-block-delete-button.js @@ -16,11 +16,20 @@ describe( 'ReusableBlockDeleteButton', () => { return renderer.getRenderOutput(); } + it( 'should not render when isVisible is false', () => { + const wrapper = getShallowRenderOutput( + <ReusableBlockDeleteButton isVisible={ false } /> + ); + + expect( wrapper ).toBe( null ); + } ); + it( 'matches the snapshot', () => { const wrapper = getShallowRenderOutput( <ReusableBlockDeleteButton role="menuitem" - reusableBlock={ { id: 123 } } + isVisible + isDisabled={ false } onDelete={ noop } /> ); @@ -32,12 +41,13 @@ describe( 'ReusableBlockDeleteButton', () => { const onDelete = jest.fn(); const wrapper = getShallowRenderOutput( <ReusableBlockDeleteButton - reusableBlock={ { id: 123 } } + isVisible + isDisabled={ false } onDelete={ onDelete } /> ); wrapper.props.onClick(); - expect( onDelete ).toHaveBeenCalledWith( 123 ); + expect( onDelete ).toHaveBeenCalled(); } ); } ); diff --git a/packages/editor/src/components/media-placeholder/index.js b/packages/editor/src/components/media-placeholder/index.js index c1b64eb0d9a64..11b80bcc58b0f 100644 --- a/packages/editor/src/components/media-placeholder/index.js +++ b/packages/editor/src/components/media-placeholder/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { every, get, noop, startsWith } from 'lodash'; +import { every, get, noop, startsWith, defaultTo } from 'lodash'; import classnames from 'classnames'; /** @@ -258,10 +258,10 @@ export class MediaPlaceholder extends Component { } const applyWithSelect = withSelect( ( select ) => { - const { hasUploadPermissions } = select( 'core' ); + const { canUser } = select( 'core' ); return { - hasUploadPermissions: hasUploadPermissions(), + hasUploadPermissions: defaultTo( canUser( 'create', 'media' ), true ), }; } ); diff --git a/packages/editor/src/components/media-upload/check.js b/packages/editor/src/components/media-upload/check.js index d72b48497e0af..5dde4c69fa827 100644 --- a/packages/editor/src/components/media-upload/check.js +++ b/packages/editor/src/components/media-upload/check.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import { defaultTo } from 'lodash'; + /** * WordPress dependencies */ @@ -8,9 +13,9 @@ export function MediaUploadCheck( { hasUploadPermissions, fallback = null, child } export default withSelect( ( select ) => { - const { hasUploadPermissions } = select( 'core' ); + const { canUser } = select( 'core' ); return { - hasUploadPermissions: hasUploadPermissions(), + hasUploadPermissions: defaultTo( canUser( 'create', 'media' ), true ), }; } )( MediaUploadCheck ); From b8b7bffbaf9193145167f8e080e4165d7bb2c452 Mon Sep 17 00:00:00 2001 From: Brent Swisher <brent@brentswisher.com> Date: Wed, 23 Jan 2019 02:41:12 -0500 Subject: [PATCH 196/691] Update the embed block to show no preview for smugmug (#12961) --- packages/block-library/src/embed/constants.js | 2 +- packages/block-library/src/embed/embed-preview.js | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/block-library/src/embed/constants.js b/packages/block-library/src/embed/constants.js index 19a988ce14008..cf26c58f182d8 100644 --- a/packages/block-library/src/embed/constants.js +++ b/packages/block-library/src/embed/constants.js @@ -1,5 +1,5 @@ // These embeds do not work in sandboxes due to the iframe's security restrictions. -export const HOSTS_NO_PREVIEWS = [ 'facebook.com' ]; +export const HOSTS_NO_PREVIEWS = [ 'facebook.com', 'smugmug.com' ]; export const ASPECT_RATIOS = [ // Common video resolutions. diff --git a/packages/block-library/src/embed/embed-preview.js b/packages/block-library/src/embed/embed-preview.js index addc30e0cd341..fc82b84a4d511 100644 --- a/packages/block-library/src/embed/embed-preview.js +++ b/packages/block-library/src/embed/embed-preview.js @@ -28,10 +28,11 @@ const EmbedPreview = ( props ) => { const { scripts } = preview; const html = 'photo' === type ? getPhotoHtml( preview ) : preview.html; - const parsedUrl = parse( url ); - const cannotPreview = includes( HOSTS_NO_PREVIEWS, parsedUrl.host.replace( /^www\./, '' ) ); + const parsedHost = parse( url ).host.split( '.' ); + const parsedHostBaseUrl = parsedHost.splice( parsedHost.length - 2, parsedHost.length - 1 ).join( '.' ); + const cannotPreview = includes( HOSTS_NO_PREVIEWS, parsedHostBaseUrl ); // translators: %s: host providing embed content e.g: www.youtube.com - const iframeTitle = sprintf( __( 'Embedded content from %s' ), parsedUrl.host ); + const iframeTitle = sprintf( __( 'Embedded content from %s' ), parsedHostBaseUrl ); const sandboxClassnames = classnames( type, className, 'wp-block-embed__wrapper' ); const embedWrapper = 'wp-embed' === type ? ( From e117993b7f8f8ecf6f49ad171bc4d10121131c63 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Wed, 23 Jan 2019 08:48:04 +0100 Subject: [PATCH 197/691] Fix HTML preview for themes applying global margins (#13416) --- packages/block-library/src/html/edit.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/html/edit.js b/packages/block-library/src/html/edit.js index d9bbe4e07211f..44aa79544415b 100644 --- a/packages/block-library/src/html/edit.js +++ b/packages/block-library/src/html/edit.js @@ -20,7 +20,22 @@ class HTMLEdit extends Component { componentDidMount() { const { styles } = this.props; - this.setState( { styles: transformStyles( styles ) } ); + + // Default styles used to unset some of the styles + // that might be inherited from the editor style. + const defaultStyles = ` + html,body,:root { + margin: 0 !important; + padding: 0 !important; + overflow: visible !important; + min-height: auto !important; + } + `; + + this.setState( { styles: [ + defaultStyles, + ...transformStyles( styles ), + ] } ); } switchToPreview() { From a6f9625a63603589b33801fdc6ca2c8501ee7067 Mon Sep 17 00:00:00 2001 From: Tammie Lister <tammie@automattic.com> Date: Wed, 23 Jan 2019 07:59:57 +0000 Subject: [PATCH 198/691] Remove feedback form (#10705) This removes the Polldaddy feedback form leaving support just in the forums. This focuses ready for 5.0 release. --- gutenberg.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index dd6c91af48197..794558c4dd70c 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -93,12 +93,6 @@ function gutenberg_menu() { __( 'https://wordpress.org/support/plugin/gutenberg', 'gutenberg' ), ); - $submenu['gutenberg'][] = array( - __( 'Feedback', 'gutenberg' ), - 'edit_posts', - 'http://wordpressdotorg.polldaddy.com/s/gutenberg-support', - ); - $submenu['gutenberg'][] = array( __( 'Documentation', 'gutenberg' ), 'edit_posts', From 6c4a1d1063c918a164c09d2e0bbdc5c2fd737995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Wed, 23 Jan 2019 09:22:33 +0100 Subject: [PATCH 199/691] Scripts: Add support for start and build scripts to the package (#12837) * Scripts: Add build and start scripts to wp-script * Introduce start and build scripts in scripts package * Use Webpack for builds in scripts package * Add documentation for start and build commands * Address feedback from review --- package-lock.json | 6540 +++++++++-------------------- package.json | 6 +- packages/scripts/CHANGELOG.md | 7 + packages/scripts/README.md | 37 +- packages/scripts/package.json | 4 +- packages/scripts/scripts/build.js | 35 + packages/scripts/scripts/start.js | 31 + 7 files changed, 2201 insertions(+), 4459 deletions(-) create mode 100644 packages/scripts/scripts/build.js create mode 100644 packages/scripts/scripts/start.js diff --git a/package-lock.json b/package-lock.json index ab56fd83b7488..66e4036ba60bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2026,22 +2026,8 @@ "dev": true, "requires": { "any-observable": "^0.3.0" - }, - "dependencies": { - "any-observable": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz", - "integrity": "sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==", - "dev": true - } } }, - "@sindresorhus/is": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", - "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==", - "dev": true - }, "@tannin/compile": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@tannin/compile/-/compile-1.0.1.tgz", @@ -2804,7 +2790,9 @@ "read-pkg-up": "^1.0.1", "resolve-bin": "^0.4.0", "stylelint": "^9.10.1", - "stylelint-config-wordpress": "^13.1.0" + "stylelint-config-wordpress": "^13.1.0", + "webpack": "4.8.3", + "webpack-cli": "^3.1.2" } }, "@wordpress/shortcode": { @@ -3037,9 +3025,9 @@ } }, "any-observable": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.2.0.tgz", - "integrity": "sha1-xnhwBYADV5AJCD9UrAq6+1wz0kI=", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz", + "integrity": "sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==", "dev": true }, "anymatch": { @@ -3264,12 +3252,6 @@ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", "dev": true }, - "ast-types": { - "version": "0.11.5", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.11.5.tgz", - "integrity": "sha512-oJjo+5e7/vEc2FBK8gUalV0pba4L3VdBIs2EKhOLHLcOd2FgQIVQN9xb0eZ9IjEWyAL7vq6fGJxOvVvdCHNyMw==", - "dev": true - }, "ast-types-flow": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", @@ -3335,6 +3317,12 @@ "postcss-value-parser": "^3.3.1" }, "dependencies": { + "caniuse-lite": { + "version": "1.0.30000929", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000929.tgz", + "integrity": "sha512-n2w1gPQSsYyorSVYqPMqbSaz1w7o9ZC8VhOEGI9T5MfGDzp7sbopQxG6GaQmYsaq13Xfx/mkxJUWC1Dz3oZfzw==", + "dev": true + }, "postcss-value-parser": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", @@ -3567,156 +3555,6 @@ } } }, - "babel-helper-bindify-decorators": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-bindify-decorators/-/babel-helper-bindify-decorators-6.24.1.tgz", - "integrity": "sha1-FMGeXxQte0fxmlJDHlKxzLxAozA=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-helper-builder-binary-assignment-operator-visitor": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz", - "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=", - "dev": true, - "requires": { - "babel-helper-explode-assignable-expression": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "babel-helper-call-delegate": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz", - "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", - "dev": true, - "requires": { - "babel-helper-hoist-variables": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-helper-define-map": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz", - "integrity": "sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=", - "dev": true, - "requires": { - "babel-helper-function-name": "^6.24.1", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "lodash": "^4.17.4" - } - }, - "babel-helper-explode-assignable-expression": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz", - "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-helper-explode-class": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-explode-class/-/babel-helper-explode-class-6.24.1.tgz", - "integrity": "sha1-fcKjkQ3uAHBW4eMdZAztPVTqqes=", - "dev": true, - "requires": { - "babel-helper-bindify-decorators": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-helper-function-name": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", - "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", - "dev": true, - "requires": { - "babel-helper-get-function-arity": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-helper-get-function-arity": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz", - "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "babel-helper-hoist-variables": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz", - "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "babel-helper-optimise-call-expression": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz", - "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "babel-helper-regex": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz", - "integrity": "sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "lodash": "^4.17.4" - } - }, - "babel-helper-remap-async-to-generator": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz", - "integrity": "sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=", - "dev": true, - "requires": { - "babel-helper-function-name": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-helper-replace-supers": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz", - "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", - "dev": true, - "requires": { - "babel-helper-optimise-call-expression": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, "babel-helpers": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", @@ -3758,15 +3596,6 @@ "babel-runtime": "^6.22.0" } }, - "babel-plugin-check-es2015-constants": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz", - "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, "babel-plugin-istanbul": { "version": "4.1.6", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz", @@ -3785,2206 +3614,1906 @@ "integrity": "sha1-5h+uBaHKiAGq3uV6bWa4zvr0QWc=", "dev": true }, - "babel-plugin-syntax-async-functions": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", - "integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=", - "dev": true - }, - "babel-plugin-syntax-async-generators": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz", - "integrity": "sha1-a8lj67FuzLrmuStZbrfzXDQqi5o=", - "dev": true - }, - "babel-plugin-syntax-class-constructor-call": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-constructor-call/-/babel-plugin-syntax-class-constructor-call-6.18.0.tgz", - "integrity": "sha1-nLnTn+Q8hgC+yBRkVt3L1OGnZBY=", - "dev": true - }, - "babel-plugin-syntax-class-properties": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz", - "integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94=", - "dev": true - }, - "babel-plugin-syntax-decorators": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz", - "integrity": "sha1-MSVjtNvePMgGzuPkFszurd0RrAs=", - "dev": true - }, - "babel-plugin-syntax-dynamic-import": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz", - "integrity": "sha1-jWomIpyDdFqZgqRBBRVyyqF5sdo=", - "dev": true - }, - "babel-plugin-syntax-exponentiation-operator": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz", - "integrity": "sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=", - "dev": true - }, - "babel-plugin-syntax-export-extensions": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-export-extensions/-/babel-plugin-syntax-export-extensions-6.13.0.tgz", - "integrity": "sha1-cKFITw+QiaToStRLrDU8lbmxJyE=", - "dev": true - }, - "babel-plugin-syntax-flow": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz", - "integrity": "sha1-TDqyCiryaqIM0lmVw5jE63AxDI0=", - "dev": true - }, "babel-plugin-syntax-object-rest-spread": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=", "dev": true }, - "babel-plugin-syntax-trailing-function-commas": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz", - "integrity": "sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM=", - "dev": true - }, - "babel-plugin-transform-async-generator-functions": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-generator-functions/-/babel-plugin-transform-async-generator-functions-6.24.1.tgz", - "integrity": "sha1-8FiQAUX9PpkHpt3yjaWfIVJYpds=", - "dev": true, - "requires": { - "babel-helper-remap-async-to-generator": "^6.24.1", - "babel-plugin-syntax-async-generators": "^6.5.0", - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-async-to-generator": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz", - "integrity": "sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=", - "dev": true, - "requires": { - "babel-helper-remap-async-to-generator": "^6.24.1", - "babel-plugin-syntax-async-functions": "^6.8.0", - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-class-constructor-call": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-constructor-call/-/babel-plugin-transform-class-constructor-call-6.24.1.tgz", - "integrity": "sha1-gNwoVQWsBn3LjWxl4vbxGrd2Xvk=", - "dev": true, - "requires": { - "babel-plugin-syntax-class-constructor-call": "^6.18.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, - "babel-plugin-transform-class-properties": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz", - "integrity": "sha1-anl2PqYdM9NvN7YRqp3vgagbRqw=", - "dev": true, - "requires": { - "babel-helper-function-name": "^6.24.1", - "babel-plugin-syntax-class-properties": "^6.8.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, - "babel-plugin-transform-decorators": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-decorators/-/babel-plugin-transform-decorators-6.24.1.tgz", - "integrity": "sha1-eIAT2PjGtSIr33s0Q5Df13Vp4k0=", + "babel-preset-jest": { + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-23.2.0.tgz", + "integrity": "sha1-jsegOhOPABoaj7HoETZSvxpV2kY=", "dev": true, "requires": { - "babel-helper-explode-class": "^6.24.1", - "babel-plugin-syntax-decorators": "^6.13.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-types": "^6.24.1" + "babel-plugin-jest-hoist": "^23.2.0", + "babel-plugin-syntax-object-rest-spread": "^6.13.0" } }, - "babel-plugin-transform-es2015-arrow-functions": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", - "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", + "babel-register": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", + "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-core": "^6.26.0", + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "home-or-tmp": "^2.0.0", + "lodash": "^4.17.4", + "mkdirp": "^0.5.1", + "source-map-support": "^0.4.15" + }, + "dependencies": { + "babel-core": { + "version": "6.26.3", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", + "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-generator": "^6.26.0", + "babel-helpers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-register": "^6.26.0", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "convert-source-map": "^1.5.1", + "debug": "^2.6.9", + "json5": "^0.5.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.4", + "path-is-absolute": "^1.0.1", + "private": "^0.1.8", + "slash": "^1.0.0", + "source-map": "^0.5.7" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } } }, - "babel-plugin-transform-es2015-block-scoped-functions": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz", - "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + } } }, - "babel-plugin-transform-es2015-block-scoping": { + "babel-template": { "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz", - "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", "dev": true, "requires": { "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", "babel-traverse": "^6.26.0", "babel-types": "^6.26.0", + "babylon": "^6.18.0", "lodash": "^4.17.4" } }, - "babel-plugin-transform-es2015-classes": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz", - "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", + "babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", "dev": true, "requires": { - "babel-helper-define-map": "^6.24.1", - "babel-helper-function-name": "^6.24.1", - "babel-helper-optimise-call-expression": "^6.24.1", - "babel-helper-replace-supers": "^6.24.1", + "babel-code-frame": "^6.26.0", "babel-messages": "^6.23.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-plugin-transform-es2015-computed-properties": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz", - "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, - "babel-plugin-transform-es2015-destructuring": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz", - "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-es2015-duplicate-keys": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz", - "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + } } }, - "babel-plugin-transform-es2015-for-of": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz", - "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + }, + "dependencies": { + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true + } } }, - "babel-plugin-transform-es2015-function-name": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz", - "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", - "dev": true, - "requires": { - "babel-helper-function-name": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true }, - "babel-plugin-transform-es2015-literals": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz", - "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } + "bail": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.3.tgz", + "integrity": "sha512-1X8CnjFVQ+a+KW36uBNMTU5s8+v5FzeqrP7hTG5aTb4aPreSbZJlhwPon9VKMuEVgV++JM+SQrALY3kr7eswdg==", + "dev": true }, - "babel-plugin-transform-es2015-modules-amd": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz", - "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=", - "dev": true, - "requires": { - "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true }, - "babel-plugin-transform-es2015-modules-commonjs": { - "version": "6.26.2", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz", - "integrity": "sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q==", + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", "dev": true, "requires": { - "babel-plugin-transform-strict-mode": "^6.24.1", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-types": "^6.26.0" + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } } }, - "babel-plugin-transform-es2015-modules-systemjs": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz", - "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=", - "dev": true, - "requires": { - "babel-helper-hoist-variables": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } + "base64-js": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==", + "dev": true }, - "babel-plugin-transform-es2015-modules-umd": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz", - "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=", + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "dev": true, + "optional": true, "requires": { - "babel-plugin-transform-es2015-modules-amd": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" + "tweetnacl": "^0.14.3" } }, - "babel-plugin-transform-es2015-object-super": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz", - "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", + "benchmark": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/benchmark/-/benchmark-2.1.4.tgz", + "integrity": "sha1-CfPeMckWQl1JjMLuVloOvzwqVik=", "dev": true, "requires": { - "babel-helper-replace-supers": "^6.24.1", - "babel-runtime": "^6.22.0" + "lodash": "^4.17.4", + "platform": "^1.3.3" } }, - "babel-plugin-transform-es2015-parameters": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz", - "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=", + "bfj": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/bfj/-/bfj-6.1.1.tgz", + "integrity": "sha512-+GUNvzHR4nRyGybQc2WpNJL4MJazMuvf92ueIyA0bIkPRwhhQu3IfZQ2PSoVPpCBJfmoSdOxu5rnotfFLlvYRQ==", "dev": true, "requires": { - "babel-helper-call-delegate": "^6.24.1", - "babel-helper-get-function-arity": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" + "bluebird": "^3.5.1", + "check-types": "^7.3.0", + "hoopy": "^0.1.2", + "tryer": "^1.0.0" } }, - "babel-plugin-transform-es2015-shorthand-properties": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz", - "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } + "big.js": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", + "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", + "dev": true }, - "babel-plugin-transform-es2015-spread": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz", - "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } + "binary-extensions": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", + "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", + "dev": true }, - "babel-plugin-transform-es2015-sticky-regex": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz", - "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=", - "dev": true, - "requires": { - "babel-helper-regex": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } + "bindings": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz", + "integrity": "sha1-FK1hE4EtLTfXLme0ystLtyZQXxE=", + "dev": true }, - "babel-plugin-transform-es2015-template-literals": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz", - "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "inherits": "~2.0.0" } }, - "babel-plugin-transform-es2015-typeof-symbol": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz", - "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=", + "bluebird": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", + "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", + "dev": true + }, + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "dev": true + }, + "body": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/body/-/body-5.1.0.tgz", + "integrity": "sha1-5LoM5BCkaTYyM2dgnstOZVMSUGk=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "continuable-cache": "^0.3.1", + "error": "^7.0.0", + "raw-body": "~1.1.0", + "safe-json-parse": "~1.0.1" } }, - "babel-plugin-transform-es2015-unicode-regex": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz", - "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=", + "body-parser": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", + "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", "dev": true, "requires": { - "babel-helper-regex": "^6.24.1", - "babel-runtime": "^6.22.0", - "regexpu-core": "^2.0.0" + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.1", + "http-errors": "~1.6.2", + "iconv-lite": "0.4.19", + "on-finished": "~2.3.0", + "qs": "6.5.1", + "raw-body": "2.3.2", + "type-is": "~1.6.15" }, "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", "dev": true }, - "regexpu-core": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", - "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=", + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { - "regenerate": "^1.2.1", - "regjsgen": "^0.2.0", - "regjsparser": "^0.1.4" + "ms": "2.0.0" } }, - "regjsgen": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", - "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", - "dev": true - }, - "regjsparser": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", - "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", + "iconv-lite": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", + "dev": true + }, + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", + "dev": true + }, + "raw-body": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", + "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", "dev": true, "requires": { - "jsesc": "~0.5.0" + "bytes": "3.0.0", + "http-errors": "1.6.2", + "iconv-lite": "0.4.19", + "unpipe": "1.0.0" + }, + "dependencies": { + "depd": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=", + "dev": true + }, + "http-errors": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", + "dev": true, + "requires": { + "depd": "1.1.1", + "inherits": "2.0.3", + "setprototypeof": "1.0.3", + "statuses": ">= 1.3.1 < 2" + } + } } + }, + "setprototypeof": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=", + "dev": true } } }, - "babel-plugin-transform-exponentiation-operator": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz", - "integrity": "sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=", - "dev": true, - "requires": { - "babel-helper-builder-binary-assignment-operator-visitor": "^6.24.1", - "babel-plugin-syntax-exponentiation-operator": "^6.8.0", - "babel-runtime": "^6.22.0" - } + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true }, - "babel-plugin-transform-export-extensions": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-export-extensions/-/babel-plugin-transform-export-extensions-6.22.0.tgz", - "integrity": "sha1-U3OLR+deghhYnuqUbLvTkQm75lM=", + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "requires": { - "babel-plugin-syntax-export-extensions": "^6.8.0", - "babel-runtime": "^6.22.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "babel-plugin-transform-flow-strip-types": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz", - "integrity": "sha1-hMtnKTXUNxT9wyvOhFaNh0Qc988=", + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "requires": { - "babel-plugin-syntax-flow": "^6.18.0", - "babel-runtime": "^6.22.0" + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } }, - "babel-plugin-transform-object-rest-spread": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz", - "integrity": "sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY=", - "dev": true, - "requires": { - "babel-plugin-syntax-object-rest-spread": "^6.8.0", - "babel-runtime": "^6.26.0" - } + "brcast": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brcast/-/brcast-2.0.2.tgz", + "integrity": "sha512-Tfn5JSE7hrUlFcOoaLzVvkbgIemIorMIyoMr3TgvszWW7jFt2C9PdeMLtysYD9RU0MmU17b69+XJG1eRY2OBRg==" }, - "babel-plugin-transform-regenerator": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz", - "integrity": "sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=", + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "browser-process-hrtime": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.2.tgz", + "integrity": "sha1-Ql1opY00R/AqBKqJQYf86K+Le44=", + "dev": true + }, + "browser-resolve": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", + "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", "dev": true, "requires": { - "regenerator-transform": "^0.10.0" + "resolve": "1.1.7" }, "dependencies": { - "regenerator-transform": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz", - "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==", - "dev": true, - "requires": { - "babel-runtime": "^6.18.0", - "babel-types": "^6.19.0", - "private": "^0.1.6" - } + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true } } }, - "babel-plugin-transform-strict-mode": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", - "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" } }, - "babel-preset-es2015": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz", - "integrity": "sha1-1EBQ1rwsn+6nAqrzjXJ6AhBTiTk=", - "dev": true, - "requires": { - "babel-plugin-check-es2015-constants": "^6.22.0", - "babel-plugin-transform-es2015-arrow-functions": "^6.22.0", - "babel-plugin-transform-es2015-block-scoped-functions": "^6.22.0", - "babel-plugin-transform-es2015-block-scoping": "^6.24.1", - "babel-plugin-transform-es2015-classes": "^6.24.1", - "babel-plugin-transform-es2015-computed-properties": "^6.24.1", - "babel-plugin-transform-es2015-destructuring": "^6.22.0", - "babel-plugin-transform-es2015-duplicate-keys": "^6.24.1", - "babel-plugin-transform-es2015-for-of": "^6.22.0", - "babel-plugin-transform-es2015-function-name": "^6.24.1", - "babel-plugin-transform-es2015-literals": "^6.22.0", - "babel-plugin-transform-es2015-modules-amd": "^6.24.1", - "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", - "babel-plugin-transform-es2015-modules-systemjs": "^6.24.1", - "babel-plugin-transform-es2015-modules-umd": "^6.24.1", - "babel-plugin-transform-es2015-object-super": "^6.24.1", - "babel-plugin-transform-es2015-parameters": "^6.24.1", - "babel-plugin-transform-es2015-shorthand-properties": "^6.24.1", - "babel-plugin-transform-es2015-spread": "^6.22.0", - "babel-plugin-transform-es2015-sticky-regex": "^6.24.1", - "babel-plugin-transform-es2015-template-literals": "^6.22.0", - "babel-plugin-transform-es2015-typeof-symbol": "^6.22.0", - "babel-plugin-transform-es2015-unicode-regex": "^6.24.1", - "babel-plugin-transform-regenerator": "^6.24.1" + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" } }, - "babel-preset-jest": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-23.2.0.tgz", - "integrity": "sha1-jsegOhOPABoaj7HoETZSvxpV2kY=", + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", "dev": true, "requires": { - "babel-plugin-jest-hoist": "^23.2.0", - "babel-plugin-syntax-object-rest-spread": "^6.13.0" + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" } }, - "babel-preset-stage-1": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-preset-stage-1/-/babel-preset-stage-1-6.24.1.tgz", - "integrity": "sha1-dpLNfc1oSZB+auSgqFWJz7niv7A=", + "browserify-rsa": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", "dev": true, "requires": { - "babel-plugin-transform-class-constructor-call": "^6.24.1", - "babel-plugin-transform-export-extensions": "^6.22.0", - "babel-preset-stage-2": "^6.24.1" + "bn.js": "^4.1.0", + "randombytes": "^2.0.1" } }, - "babel-preset-stage-2": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-preset-stage-2/-/babel-preset-stage-2-6.24.1.tgz", - "integrity": "sha1-2eKWD7PXEYfw5k7sYrwHdnIZvcE=", + "browserify-sign": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", + "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", "dev": true, "requires": { - "babel-plugin-syntax-dynamic-import": "^6.18.0", - "babel-plugin-transform-class-properties": "^6.24.1", - "babel-plugin-transform-decorators": "^6.24.1", - "babel-preset-stage-3": "^6.24.1" + "bn.js": "^4.1.1", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.2", + "elliptic": "^6.0.0", + "inherits": "^2.0.1", + "parse-asn1": "^5.0.0" } }, - "babel-preset-stage-3": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-preset-stage-3/-/babel-preset-stage-3-6.24.1.tgz", - "integrity": "sha1-g2raCp56f6N8sTj7kyb4eTSkg5U=", + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", "dev": true, "requires": { - "babel-plugin-syntax-trailing-function-commas": "^6.22.0", - "babel-plugin-transform-async-generator-functions": "^6.24.1", - "babel-plugin-transform-async-to-generator": "^6.24.1", - "babel-plugin-transform-exponentiation-operator": "^6.24.1", - "babel-plugin-transform-object-rest-spread": "^6.22.0" + "pako": "~1.0.5" } }, - "babel-register": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", - "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", + "browserslist": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.4.1.tgz", + "integrity": "sha512-pEBxEXg7JwaakBXjATYw/D1YZh4QUSCX/Mnd/wnqSRPPSi1U39iDhDoKGoBUcraKdxDlrYqJxSI5nNvD+dWP2A==", "dev": true, "requires": { - "babel-core": "^6.26.0", - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "home-or-tmp": "^2.0.0", - "lodash": "^4.17.4", - "mkdirp": "^0.5.1", - "source-map-support": "^0.4.15" + "caniuse-lite": "^1.0.30000929", + "electron-to-chromium": "^1.3.103", + "node-releases": "^1.1.3" }, "dependencies": { - "babel-core": { - "version": "6.26.3", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", - "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-generator": "^6.26.0", - "babel-helpers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-register": "^6.26.0", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "convert-source-map": "^1.5.1", - "debug": "^2.6.9", - "json5": "^0.5.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.4", - "path-is-absolute": "^1.0.1", - "private": "^0.1.8", - "slash": "^1.0.0", - "source-map": "^0.5.7" - } + "caniuse-lite": { + "version": "1.0.30000929", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000929.tgz", + "integrity": "sha512-n2w1gPQSsYyorSVYqPMqbSaz1w7o9ZC8VhOEGI9T5MfGDzp7sbopQxG6GaQmYsaq13Xfx/mkxJUWC1Dz3oZfzw==", + "dev": true }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "electron-to-chromium": { + "version": "1.3.103", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.103.tgz", + "integrity": "sha512-tObPqGmY9X8MUM8i3MEimYmbnLLf05/QV5gPlkR8MQ3Uj8G8B2govE1U4cQcBYtv3ymck9Y8cIOu4waoiykMZQ==", + "dev": true + }, + "node-releases": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.3.tgz", + "integrity": "sha512-6VrvH7z6jqqNFY200kdB6HdzkgM96Oaj9v3dqGfgp6mF+cHmU4wyQKZ2/WPDRVoR0Jz9KqbamaBN0ZhdUaysUQ==", "dev": true, "requires": { - "ms": "2.0.0" + "semver": "^5.3.0" } } } }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "bser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.0.0.tgz", + "integrity": "sha1-mseNPtXZFYBP2HrLFYvHlxR6Fxk=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - }, - "dependencies": { - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - } + "node-int64": "^0.4.0" } }, - "babel-template": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", - "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", + "buffer": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "dev": true, "requires": { - "babel-runtime": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "lodash": "^4.17.4" + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" } }, - "babel-traverse": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", - "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "buffer-from": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.0.tgz", + "integrity": "sha512-c5mRlguI/Pe2dSZmpER62rSCu0ryKmWddzRYsuXc50U2/g8jMOulc31VZMa4mYx31U5xsmSOpDCgH88Vl9cDGQ==", + "dev": true + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true + }, + "builtins": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", + "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=", + "dev": true + }, + "byline": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz", + "integrity": "sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE=", + "dev": true + }, + "byte-size": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/byte-size/-/byte-size-4.0.4.tgz", + "integrity": "sha512-82RPeneC6nqCdSwCX2hZUz3JPOvN5at/nTEw/CMf05Smu3Hrpo9Psb7LjN+k+XndNArG1EY8L4+BM3aTM4BCvw==", + "dev": true + }, + "bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", + "integrity": "sha1-NWnt6Lo0MV+rmcPpLLBMciDeH6g=", + "dev": true + }, + "cacache": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz", + "integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==", "dev": true, "requires": { - "babel-code-frame": "^6.26.0", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "debug": "^2.6.8", - "globals": "^9.18.0", - "invariant": "^2.2.2", - "lodash": "^4.17.4" + "bluebird": "^3.5.1", + "chownr": "^1.0.1", + "glob": "^7.1.2", + "graceful-fs": "^4.1.11", + "lru-cache": "^4.1.1", + "mississippi": "^2.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.2", + "ssri": "^5.2.4", + "unique-filename": "^1.1.0", + "y18n": "^4.0.0" }, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "lru-cache": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", + "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", "dev": true, "requires": { - "ms": "2.0.0" + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" } }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", "dev": true } } }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", "dev": true, "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" - }, - "dependencies": { - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", - "dev": true - } + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" } }, - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true - }, - "bail": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.3.tgz", - "integrity": "sha512-1X8CnjFVQ+a+KW36uBNMTU5s8+v5FzeqrP7hTG5aTb4aPreSbZJlhwPon9VKMuEVgV++JM+SQrALY3kr7eswdg==", - "dev": true - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "call-me-maybe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", + "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", "dev": true }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", "dev": true, "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" + "callsites": "^0.2.0" }, "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true } } }, - "base64-js": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", - "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==", + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", "dev": true }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" + }, + "camelcase-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", + "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", "dev": true, - "optional": true, "requires": { - "tweetnacl": "^0.14.3" + "camelcase": "^4.1.0", + "map-obj": "^2.0.0", + "quick-lru": "^1.0.0" } }, - "benchmark": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/benchmark/-/benchmark-2.1.4.tgz", - "integrity": "sha1-CfPeMckWQl1JjMLuVloOvzwqVik=", + "caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", "dev": true, "requires": { - "lodash": "^4.17.4", - "platform": "^1.3.3" + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + }, + "dependencies": { + "browserslist": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.0.1.tgz", + "integrity": "sha512-QqiiIWchEIkney3wY53/huI7ZErouNAdvOkjorUALAwRcu3tEwOV3Sh6He0DnP38mz1JjBpCBb50jQBmaYuHPw==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30000865", + "electron-to-chromium": "^1.3.52", + "node-releases": "^1.0.0-alpha.10" + } + } } }, - "bfj": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/bfj/-/bfj-6.1.1.tgz", - "integrity": "sha512-+GUNvzHR4nRyGybQc2WpNJL4MJazMuvf92ueIyA0bIkPRwhhQu3IfZQ2PSoVPpCBJfmoSdOxu5rnotfFLlvYRQ==", + "caniuse-db": { + "version": "1.0.30000871", + "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000871.tgz", + "integrity": "sha1-8ZlcH+MYkmSadgWVeoDJJRhCPU0=", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30000865", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000865.tgz", + "integrity": "sha512-vs79o1mOSKRGv/1pSkp4EXgl4ZviWeYReXw60XfacPU64uQWZwJT6vZNmxRF9O+6zu71sJwMxLK5JXxbzuVrLw==", + "dev": true + }, + "capture-exit": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-1.2.0.tgz", + "integrity": "sha1-HF/MSJ/QqwDU8ax64QcuMXP7q28=", "dev": true, "requires": { - "bluebird": "^3.5.1", - "check-types": "^7.3.0", - "hoopy": "^0.1.2", - "tryer": "^1.0.0" + "rsvp": "^3.3.3" } }, - "big.js": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", - "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", "dev": true }, - "binary-extensions": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", - "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", + "ccount": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.0.3.tgz", + "integrity": "sha512-Jt9tIBkRc9POUof7QA/VwWd+58fKkEEfI+/t1/eOlxKM7ZhrczNzMFefge7Ai+39y1pR/pP6cI19guHy3FSLmw==", "dev": true }, - "binaryextensions": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-2.1.1.tgz", - "integrity": "sha512-XBaoWE9RW8pPdPQNibZsW2zh8TW6gcarXp1FZPwT8Uop8ScSNldJEWf2k9l3HeTqdrEwsOsFcq74RiJECW34yA==", + "center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "dev": true, + "optional": true, + "requires": { + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "character-entities": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.2.tgz", + "integrity": "sha512-sMoHX6/nBiy3KKfC78dnEalnpn0Az0oSNvqUWYTtYrhRI5iUIYsROU48G+E+kMFQzqXaJ8kHJZ85n7y6/PHgwQ==", "dev": true }, - "bindings": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz", - "integrity": "sha1-FK1hE4EtLTfXLme0ystLtyZQXxE=", + "character-entities-html4": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-1.1.2.tgz", + "integrity": "sha512-sIrXwyna2+5b0eB9W149izTPJk/KkJTg6mEzDGibwBUkyH1SbDa+nf515Ppdi3MaH35lW0JFJDWeq9Luzes1Iw==", "dev": true }, - "block-stream": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", - "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "character-entities-legacy": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.2.tgz", + "integrity": "sha512-9NB2VbXtXYWdXzqrvAHykE/f0QJxzaKIpZ5QzNZrrgQ7Iyxr2vnfS8fCBNVW9nUEZE0lo57nxKRqnzY/dKrwlA==", + "dev": true + }, + "character-reference-invalid": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.2.tgz", + "integrity": "sha512-7I/xceXfKyUJmSAn/jw8ve/9DyOP7XxufNYLI9Px7CmsKgEUaZLUTax6nZxGQtaoiZCjpu6cHPj20xC/vqRReQ==", + "dev": true + }, + "chardet": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", + "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "dev": true + }, + "check-node-version": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/check-node-version/-/check-node-version-3.2.0.tgz", + "integrity": "sha512-mJu4dADRf+NUeOyGgFTXaLtjyyffD3Eej2RA9IEk1CdHmoVurErLD++e/Ps6uKfsB273ky+0Z9NlOiuplxuNdw==", "dev": true, "requires": { - "inherits": "~2.0.0" + "chalk": "^2.3.0", + "map-values": "^1.0.1", + "minimist": "^1.2.0", + "object-filter": "^1.0.2", + "object.assign": "^4.0.4", + "run-parallel": "^1.1.4", + "semver": "^5.0.3" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } } }, - "bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", - "dev": true - }, - "bn.js": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "check-types": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/check-types/-/check-types-7.4.0.tgz", + "integrity": "sha512-YbulWHdfP99UfZ73NcUDlNJhEIDgm9Doq9GhpyXbF+7Aegi3CVV7qqMCKTTqJxlvEvnQBp9IA+dxsGN6xK/nSg==", "dev": true }, - "body": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/body/-/body-5.1.0.tgz", - "integrity": "sha1-5LoM5BCkaTYyM2dgnstOZVMSUGk=", + "cheerio": { + "version": "1.0.0-rc.2", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.2.tgz", + "integrity": "sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs=", "dev": true, "requires": { - "continuable-cache": "^0.3.1", - "error": "^7.0.0", - "raw-body": "~1.1.0", - "safe-json-parse": "~1.0.1" + "css-select": "~1.2.0", + "dom-serializer": "~0.1.0", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash": "^4.15.0", + "parse5": "^3.0.1" } }, - "body-parser": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", - "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", + "chokidar": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", + "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", "dev": true, "requires": { - "bytes": "3.0.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.1", - "http-errors": "~1.6.2", - "iconv-lite": "0.4.19", - "on-finished": "~2.3.0", - "qs": "6.5.1", - "raw-body": "2.3.2", - "type-is": "~1.6.15" + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "fsevents": "^1.2.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "lodash.debounce": "^4.0.8", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.5" }, "dependencies": { - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", - "dev": true - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", "dev": true, "requires": { - "ms": "2.0.0" + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } } }, - "iconv-lite": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", - "dev": true - }, - "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, - "raw-body": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", - "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "is-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", "dev": true, "requires": { - "bytes": "3.0.0", - "http-errors": "1.6.2", - "iconv-lite": "0.4.19", - "unpipe": "1.0.0" - }, - "dependencies": { - "depd": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", - "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=", - "dev": true - }, - "http-errors": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", - "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", - "dev": true, - "requires": { - "depd": "1.1.1", - "inherits": "2.0.3", - "setprototypeof": "1.0.3", - "statuses": ">= 1.3.1 < 2" - } - } + "is-extglob": "^2.1.1" } - }, - "setprototypeof": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", - "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=", - "dev": true } } }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "chownr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz", + "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=", "dev": true }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "chrome-trace-event": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-0.1.3.tgz", + "integrity": "sha512-sjndyZHrrWiu4RY7AkHgjn80GfAM2ZSzUkZLV/Js59Ldmh6JDThf0SUmOHU53rFu2rVxxfCzJ30Ukcfch3Gb/A==", + "dev": true + }, + "ci-info": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.1.3.tgz", + "integrity": "sha512-SK/846h/Rcy8q9Z9CAwGBLfCJ6EkjJWdpelWDufQpqVDYq2Wnnv8zlSO6AMQap02jvhVruKKpEtQOufo3pFhLg==", + "dev": true + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", "dev": true, "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" } }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, + "circular-json-es6": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/circular-json-es6/-/circular-json-es6-2.0.2.tgz", + "integrity": "sha512-ODYONMMNb3p658Zv+Pp+/XPa5s6q7afhz3Tzyvo+VRh9WIrJ64J76ZC4GQxnlye/NesTn09jvOiuE8+xxfpwhQ==", + "dev": true + }, + "clap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/clap/-/clap-1.2.3.tgz", + "integrity": "sha512-4CoL/A3hf90V3VIEjeuhSvlGFEHKzOz+Wfc2IVZc+FaUgU0ZQafJTP49fvnULipOPcAfqhyI2duwQyns6xqjYA==", "dev": true, "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" + "chalk": "^1.1.3" }, "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true } } }, - "brcast": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brcast/-/brcast-2.0.2.tgz", - "integrity": "sha512-Tfn5JSE7hrUlFcOoaLzVvkbgIemIorMIyoMr3TgvszWW7jFt2C9PdeMLtysYD9RU0MmU17b69+XJG1eRY2OBRg==" - }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", - "dev": true - }, - "browser-process-hrtime": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.2.tgz", - "integrity": "sha1-Ql1opY00R/AqBKqJQYf86K+Le44=", - "dev": true - }, - "browser-resolve": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", - "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", "dev": true, "requires": { - "resolve": "1.1.7" + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" }, "dependencies": { - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", - "dev": true + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } } } }, - "browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "classnames": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.5.tgz", + "integrity": "sha1-+zgB1FNGdknvNgPH1hoCvRKb3m0=" + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", "dev": true, "requires": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "restore-cursor": "^2.0.0" } }, - "browserify-cipher": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "cli-truncate": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz", + "integrity": "sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=", "dev": true, "requires": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" + "slice-ansi": "0.0.4", + "string-width": "^1.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "slice-ansi": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", + "dev": true + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } } }, - "browserify-des": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", - "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", - "dev": true, - "requires": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true }, - "browserify-rsa": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", - "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", - "dev": true, + "clipboard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.1.tgz", + "integrity": "sha512-7yhQBmtN+uYZmfRjjVjKa0dZdWuabzpSKGtyQZN+9C8xlC788SSJjOHWh7tzurfwTqTD5UDYAhIv5fRJg3sHjQ==", "requires": { - "bn.js": "^4.1.0", - "randombytes": "^2.0.1" + "good-listener": "^1.2.2", + "select": "^1.1.2", + "tiny-emitter": "^2.0.0" } }, - "browserify-sign": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", - "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", - "dev": true, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", "requires": { - "bn.js": "^4.1.1", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.2", - "elliptic": "^6.0.0", - "inherits": "^2.0.1", - "parse-asn1": "^5.0.0" + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" } }, - "browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "dev": true, - "requires": { - "pako": "~1.0.5" - } + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true }, - "browserslist": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.4.1.tgz", - "integrity": "sha512-pEBxEXg7JwaakBXjATYw/D1YZh4QUSCX/Mnd/wnqSRPPSi1U39iDhDoKGoBUcraKdxDlrYqJxSI5nNvD+dWP2A==", + "clone-deep": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-2.0.2.tgz", + "integrity": "sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30000929", - "electron-to-chromium": "^1.3.103", - "node-releases": "^1.1.3" + "for-own": "^1.0.0", + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.0", + "shallow-clone": "^1.0.0" }, "dependencies": { - "electron-to-chromium": { - "version": "1.3.103", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.103.tgz", - "integrity": "sha512-tObPqGmY9X8MUM8i3MEimYmbnLLf05/QV5gPlkR8MQ3Uj8G8B2govE1U4cQcBYtv3ymck9Y8cIOu4waoiykMZQ==", - "dev": true - }, - "node-releases": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.3.tgz", - "integrity": "sha512-6VrvH7z6jqqNFY200kdB6HdzkgM96Oaj9v3dqGfgp6mF+cHmU4wyQKZ2/WPDRVoR0Jz9KqbamaBN0ZhdUaysUQ==", + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", "dev": true, "requires": { - "semver": "^5.3.0" + "for-in": "^1.0.1" } } } }, - "bser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.0.0.tgz", - "integrity": "sha1-mseNPtXZFYBP2HrLFYvHlxR6Fxk=", + "clone-regexp": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-1.0.1.tgz", + "integrity": "sha512-Fcij9IwRW27XedRIJnSOEupS7RVcXtObJXbcUOX93UCLqqOdRpkvzKywOOSizmEK/Is3S/RHX9dLdfo6R1Q1mw==", "dev": true, "requires": { - "node-int64": "^0.4.0" + "is-regexp": "^1.0.0", + "is-supported-regexp-flag": "^1.0.0" } }, - "buffer": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", - "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "cmd-shim": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-2.0.2.tgz", + "integrity": "sha1-b8vamUg6j9FdfTChlspp1oii79s=", "dev": true, "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" + "graceful-fs": "^4.1.2", + "mkdirp": "~0.5.0" } }, - "buffer-from": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.0.tgz", - "integrity": "sha512-c5mRlguI/Pe2dSZmpER62rSCu0ryKmWddzRYsuXc50U2/g8jMOulc31VZMa4mYx31U5xsmSOpDCgH88Vl9cDGQ==", - "dev": true - }, - "buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", - "dev": true - }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true - }, - "builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", "dev": true }, - "builtins": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", - "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=", - "dev": true + "coa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.1.tgz", + "integrity": "sha512-5wfTTO8E2/ja4jFSxePXlG5nRu5bBtL/r1HCIpJW/lzT6yDtKl0u0Z4o/Vpz32IpKmBn7HerheEZQgA9N2DarQ==", + "dev": true, + "requires": { + "q": "^1.1.2" + } }, - "byline": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz", - "integrity": "sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE=", - "dev": true + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, - "byte-size": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/byte-size/-/byte-size-4.0.4.tgz", - "integrity": "sha512-82RPeneC6nqCdSwCX2hZUz3JPOvN5at/nTEw/CMf05Smu3Hrpo9Psb7LjN+k+XndNArG1EY8L4+BM3aTM4BCvw==", + "collapse-white-space": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.4.tgz", + "integrity": "sha512-YfQ1tAUZm561vpYD+5eyWN8+UsceQbSrqqlc/6zDY2gtAE+uZLSdkkovhnGpmCThsvKBFakq4EdY/FF93E8XIw==", "dev": true }, - "bytes": { + "collection-visit": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", - "integrity": "sha1-NWnt6Lo0MV+rmcPpLLBMciDeH6g=", - "dev": true - }, - "cacache": { - "version": "10.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz", - "integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", "dev": true, "requires": { - "bluebird": "^3.5.1", - "chownr": "^1.0.1", - "glob": "^7.1.2", - "graceful-fs": "^4.1.11", - "lru-cache": "^4.1.1", - "mississippi": "^2.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.2", - "ssri": "^5.2.4", - "unique-filename": "^1.1.0", - "y18n": "^4.0.0" - }, - "dependencies": { - "lru-cache": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", - "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", - "dev": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "dev": true - } + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" } }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "color": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/color/-/color-0.11.4.tgz", + "integrity": "sha1-bXtcdPtl6EHNSHkq0e1eB7kE12Q=", "dev": true, "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" + "clone": "^1.0.2", + "color-convert": "^1.3.0", + "color-string": "^0.3.0" } }, - "cacheable-request": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-2.1.4.tgz", - "integrity": "sha1-DYCIAbY0KtM8kd+dC0TcCbkeXD0=", + "color-convert": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz", + "integrity": "sha512-3NUJZdhMhcdPn8vJ9v2UQJoH0qqoGUkYTgFEPZaPjEtwmmKUfNV46zZmgB2M5M4DCEQHMaCfWHCxiBflLm04Tg==", "dev": true, "requires": { - "clone-response": "1.0.2", - "get-stream": "3.0.0", - "http-cache-semantics": "3.8.1", - "keyv": "3.0.0", - "lowercase-keys": "1.0.0", - "normalize-url": "2.0.1", - "responselike": "1.0.2" - }, - "dependencies": { - "lowercase-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", - "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=", - "dev": true - } + "color-name": "1.1.1" } }, - "call-me-maybe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", - "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", + "color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=", "dev": true }, - "caller-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", - "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", - "dev": true, + "color-string": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-0.3.0.tgz", + "integrity": "sha1-J9RvtnAlxcL6JZk7+/V55HhBuZE=", + "dev": true, "requires": { - "callsites": "^0.2.0" - }, - "dependencies": { - "callsites": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", - "dev": true - } + "color-name": "^1.0.0" } }, - "callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", - "dev": true - }, - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" - }, - "camelcase-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", - "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", + "colormin": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colormin/-/colormin-1.1.2.tgz", + "integrity": "sha1-6i90IKcrlogaOKrlnsEkpvcpgTM=", "dev": true, "requires": { - "camelcase": "^4.1.0", - "map-obj": "^2.0.0", - "quick-lru": "^1.0.0" + "color": "^0.11.0", + "css-color-names": "0.0.4", + "has": "^1.0.1" } }, - "caniuse-api": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "colors": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.5.1.tgz", + "integrity": "sha1-fQAj6usVTo7p/Oddy5I9DtFmd3Q=", + "dev": true + }, + "columnify": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.5.4.tgz", + "integrity": "sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs=", "dev": true, "requires": { - "browserslist": "^4.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" + "strip-ansi": "^3.0.0", + "wcwidth": "^1.0.0" }, "dependencies": { - "browserslist": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.0.1.tgz", - "integrity": "sha512-QqiiIWchEIkney3wY53/huI7ZErouNAdvOkjorUALAwRcu3tEwOV3Sh6He0DnP38mz1JjBpCBb50jQBmaYuHPw==", + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "caniuse-lite": "^1.0.30000865", - "electron-to-chromium": "^1.3.52", - "node-releases": "^1.0.0-alpha.10" + "ansi-regex": "^2.0.0" } } } }, - "caniuse-db": { - "version": "1.0.30000871", - "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000871.tgz", - "integrity": "sha1-8ZlcH+MYkmSadgWVeoDJJRhCPU0=", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30000929", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000929.tgz", - "integrity": "sha512-n2w1gPQSsYyorSVYqPMqbSaz1w7o9ZC8VhOEGI9T5MfGDzp7sbopQxG6GaQmYsaq13Xfx/mkxJUWC1Dz3oZfzw==", - "dev": true - }, - "capture-exit": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-1.2.0.tgz", - "integrity": "sha1-HF/MSJ/QqwDU8ax64QcuMXP7q28=", - "dev": true, - "requires": { - "rsvp": "^3.3.3" - } - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true - }, - "ccount": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.0.3.tgz", - "integrity": "sha512-Jt9tIBkRc9POUof7QA/VwWd+58fKkEEfI+/t1/eOlxKM7ZhrczNzMFefge7Ai+39y1pR/pP6cI19guHy3FSLmw==", - "dev": true - }, - "center-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", - "dev": true, - "optional": true, - "requires": { - "align-text": "^0.1.3", - "lazy-cache": "^1.0.3" - } - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "combined-stream": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "delayed-stream": "~1.0.0" } }, - "character-entities": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.2.tgz", - "integrity": "sha512-sMoHX6/nBiy3KKfC78dnEalnpn0Az0oSNvqUWYTtYrhRI5iUIYsROU48G+E+kMFQzqXaJ8kHJZ85n7y6/PHgwQ==", - "dev": true - }, - "character-entities-html4": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-1.1.2.tgz", - "integrity": "sha512-sIrXwyna2+5b0eB9W149izTPJk/KkJTg6mEzDGibwBUkyH1SbDa+nf515Ppdi3MaH35lW0JFJDWeq9Luzes1Iw==", - "dev": true - }, - "character-entities-legacy": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.2.tgz", - "integrity": "sha512-9NB2VbXtXYWdXzqrvAHykE/f0QJxzaKIpZ5QzNZrrgQ7Iyxr2vnfS8fCBNVW9nUEZE0lo57nxKRqnzY/dKrwlA==", - "dev": true - }, - "character-reference-invalid": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.2.tgz", - "integrity": "sha512-7I/xceXfKyUJmSAn/jw8ve/9DyOP7XxufNYLI9Px7CmsKgEUaZLUTax6nZxGQtaoiZCjpu6cHPj20xC/vqRReQ==", + "commander": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.16.0.tgz", + "integrity": "sha512-sVXqklSaotK9at437sFlFpyOcJonxe0yST/AG9DkQKUdIE6IqGIMv4SfAQSKaJbSdVEJYItASCrBiVQHq1HQew==", "dev": true }, - "chardet": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", - "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, - "check-node-version": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/check-node-version/-/check-node-version-3.1.1.tgz", - "integrity": "sha512-52fHDe/0pbidY3InI33Beyb/oarySfLANlXxLGBl9lLVrLIW88XWIwu4jGJrQ1imuWzX5ukNGWXUyCgmgVUD8A==", + "compare-func": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-1.3.2.tgz", + "integrity": "sha1-md0LpFfh+bxyKxLAjsM+6rMfpkg=", "dev": true, "requires": { - "chalk": "^2.3.0", - "map-values": "^1.0.1", - "minimist": "^1.2.0", - "object-filter": "^1.0.2", - "object.assign": "^4.0.4", - "run-parallel": "^1.1.4", - "semver": "^5.0.3" + "array-ify": "^1.0.0", + "dot-prop": "^3.0.0" }, "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true + "dot-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-3.0.0.tgz", + "integrity": "sha1-G3CK8JSknJoOfbyteQq6U52sEXc=", + "dev": true, + "requires": { + "is-obj": "^1.0.0" + } } } }, - "check-types": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/check-types/-/check-types-7.4.0.tgz", - "integrity": "sha512-YbulWHdfP99UfZ73NcUDlNJhEIDgm9Doq9GhpyXbF+7Aegi3CVV7qqMCKTTqJxlvEvnQBp9IA+dxsGN6xK/nSg==", + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", "dev": true }, - "cheerio": { - "version": "1.0.0-rc.2", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.2.tgz", - "integrity": "sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs=", + "computed-style": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/computed-style/-/computed-style-0.1.4.tgz", + "integrity": "sha1-fzRP2FhLLkJb7cpKGvwOMAuwXXQ=" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", "dev": true, "requires": { - "css-select": "~1.2.0", - "dom-serializer": "~0.1.0", - "entities": "~1.1.1", - "htmlparser2": "^3.9.1", - "lodash": "^4.15.0", - "parse5": "^3.0.1" + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" } }, - "chokidar": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", - "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "concurrently": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-3.5.0.tgz", + "integrity": "sha1-jPG3cHppFqeKT/W3e7BN7FSzebI=", "dev": true, "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.0", - "braces": "^2.3.0", - "fsevents": "^1.2.2", - "glob-parent": "^3.1.0", - "inherits": "^2.0.1", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "lodash.debounce": "^4.0.8", - "normalize-path": "^2.1.1", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.0.0", - "upath": "^1.0.5" - }, - "dependencies": { - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "chalk": "0.5.1", + "commander": "2.6.0", + "date-fns": "^1.23.0", + "lodash": "^4.5.1", + "rx": "2.3.24", + "spawn-command": "^0.0.2-1", + "supports-color": "^3.2.3", + "tree-kill": "^1.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", + "integrity": "sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk=", + "dev": true + }, + "ansi-styles": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz", + "integrity": "sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94=", + "dev": true + }, + "chalk": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz", + "integrity": "sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ=", "dev": true, "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" + "ansi-styles": "^1.1.0", + "escape-string-regexp": "^1.0.0", + "has-ansi": "^0.1.0", + "strip-ansi": "^0.3.0", + "supports-color": "^0.2.0" }, "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "^2.1.0" - } + "supports-color": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz", + "integrity": "sha1-2S3iaU6z9nMjlz1649i1W0wiGQo=", + "dev": true } } }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "commander": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.6.0.tgz", + "integrity": "sha1-nfflL7Kgyw+4kFjugMMQQiXzfh0=", "dev": true }, - "is-glob": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", - "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "has-ansi": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz", + "integrity": "sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4=", "dev": true, "requires": { - "is-extglob": "^2.1.1" + "ansi-regex": "^0.2.0" } - } - } - }, - "chownr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz", - "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=", - "dev": true - }, - "chrome-trace-event": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-0.1.3.tgz", - "integrity": "sha512-sjndyZHrrWiu4RY7AkHgjn80GfAM2ZSzUkZLV/Js59Ldmh6JDThf0SUmOHU53rFu2rVxxfCzJ30Ukcfch3Gb/A==", - "dev": true - }, - "ci-info": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.1.3.tgz", - "integrity": "sha512-SK/846h/Rcy8q9Z9CAwGBLfCJ6EkjJWdpelWDufQpqVDYq2Wnnv8zlSO6AMQap02jvhVruKKpEtQOufo3pFhLg==", - "dev": true - }, - "cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "circular-json": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", - "dev": true - }, - "circular-json-es6": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/circular-json-es6/-/circular-json-es6-2.0.2.tgz", - "integrity": "sha512-ODYONMMNb3p658Zv+Pp+/XPa5s6q7afhz3Tzyvo+VRh9WIrJ64J76ZC4GQxnlye/NesTn09jvOiuE8+xxfpwhQ==", - "dev": true - }, - "clap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/clap/-/clap-1.2.3.tgz", - "integrity": "sha512-4CoL/A3hf90V3VIEjeuhSvlGFEHKzOz+Wfc2IVZc+FaUgU0ZQafJTP49fvnULipOPcAfqhyI2duwQyns6xqjYA==", - "dev": true, - "requires": { - "chalk": "^1.1.3" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", "dev": true }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "strip-ansi": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz", + "integrity": "sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA=", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" + "ansi-regex": "^0.2.1" } }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "has-flag": "^1.0.0" } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true } } }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "config-chain": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", + "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", "dev": true, "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } + "ini": "^1.3.4", + "proto-list": "~1.2.1" } }, - "classnames": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.5.tgz", - "integrity": "sha1-+zgB1FNGdknvNgPH1hoCvRKb3m0=" - }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "console-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", "dev": true, "requires": { - "restore-cursor": "^2.0.0" + "date-now": "^0.1.4" } }, - "cli-spinners": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-0.1.2.tgz", - "integrity": "sha1-u3ZNiOGF+54eaiofGXcjGPYF4xw=", + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true + }, + "consolidated-events": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/consolidated-events/-/consolidated-events-2.0.2.tgz", + "integrity": "sha512-2/uRVMdRypf5z/TW/ncD/66l75P5hH2vM/GR8Jf8HLc2xnfJtmina6F6du8+v4Z2vTrMo7jC+W1tmEEuuELgkQ==" + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", + "dev": true + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", "dev": true }, - "cli-table": { + "continuable-cache": { "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.1.tgz", - "integrity": "sha1-9TsFJmqLGguTSz0IIebi3FkUriM=", + "resolved": "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz", + "integrity": "sha1-vXJ6f67XfnH/OYWskzUakSczrQ8=", + "dev": true + }, + "conventional-changelog-angular": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.2.tgz", + "integrity": "sha512-yx7m7lVrXmt4nKWQgWZqxSALEiAKZhOAcbxdUaU9575mB0CzXVbgrgpfSnSP7OqWDUTYGD0YVJ0MSRdyOPgAwA==", "dev": true, "requires": { - "colors": "1.0.3" - }, - "dependencies": { - "colors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", - "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", - "dev": true - } + "compare-func": "^1.3.1", + "q": "^1.5.1" } }, - "cli-truncate": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz", - "integrity": "sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=", + "conventional-changelog-core": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-3.1.5.tgz", + "integrity": "sha512-iwqAotS4zk0wA4S84YY1JCUG7X3LxaRjJxuUo6GI4dZuIy243j5nOg/Ora35ExT4DOiw5dQbMMQvw2SUjh6moQ==", "dev": true, "requires": { - "slice-ansi": "0.0.4", - "string-width": "^1.0.1" - }, + "conventional-changelog-writer": "^4.0.2", + "conventional-commits-parser": "^3.0.1", + "dateformat": "^3.0.0", + "get-pkg-repo": "^1.0.0", + "git-raw-commits": "2.0.0", + "git-remote-origin-url": "^2.0.0", + "git-semver-tags": "^2.0.2", + "lodash": "^4.2.1", + "normalize-package-data": "^2.3.5", + "q": "^1.5.1", + "read-pkg": "^3.0.0", + "read-pkg-up": "^3.0.0", + "through2": "^2.0.0" + }, "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", "dev": true, "requires": { - "number-is-nan": "^1.0.0" + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" } }, - "slice-ansi": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", - "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" } }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true } } }, - "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", - "dev": true - }, - "clipboard": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.1.tgz", - "integrity": "sha512-7yhQBmtN+uYZmfRjjVjKa0dZdWuabzpSKGtyQZN+9C8xlC788SSJjOHWh7tzurfwTqTD5UDYAhIv5fRJg3sHjQ==", - "requires": { - "good-listener": "^1.2.2", - "select": "^1.1.2", - "tiny-emitter": "^2.0.0" - } - }, - "cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", - "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" - } - }, - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true - }, - "clone-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", - "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "conventional-changelog-preset-loader": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.0.2.tgz", + "integrity": "sha512-pBY+qnUoJPXAXXqVGwQaVmcye05xi6z231QM98wHWamGAmu/ghkBprQAwmF5bdmyobdVxiLhPY3PrCfSeUNzRQ==", "dev": true }, - "clone-deep": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-2.0.2.tgz", - "integrity": "sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ==", + "conventional-changelog-writer": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.2.tgz", + "integrity": "sha512-d8/FQY/fix2xXEBUhOo8u3DCbyEw3UOQgYHxLsPDw+wHUDma/GQGAGsGtoH876WyNs32fViHmTOUrgRKVLvBug==", "dev": true, "requires": { - "for-own": "^1.0.0", - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.0", - "shallow-clone": "^1.0.0" + "compare-func": "^1.3.1", + "conventional-commits-filter": "^2.0.1", + "dateformat": "^3.0.0", + "handlebars": "^4.0.2", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.2.1", + "meow": "^4.0.0", + "semver": "^5.5.0", + "split": "^1.0.0", + "through2": "^2.0.0" }, "dependencies": { - "for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", "dev": true, "requires": { - "for-in": "^1.0.1" + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "meow": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz", + "integrity": "sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==", + "dev": true, + "requires": { + "camelcase-keys": "^4.0.0", + "decamelize-keys": "^1.0.0", + "loud-rejection": "^1.0.0", + "minimist": "^1.1.3", + "minimist-options": "^3.0.1", + "normalize-package-data": "^2.3.4", + "read-pkg-up": "^3.0.0", + "redent": "^2.0.0", + "trim-newlines": "^2.0.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true } } }, - "clone-regexp": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-1.0.1.tgz", - "integrity": "sha512-Fcij9IwRW27XedRIJnSOEupS7RVcXtObJXbcUOX93UCLqqOdRpkvzKywOOSizmEK/Is3S/RHX9dLdfo6R1Q1mw==", - "dev": true, - "requires": { - "is-regexp": "^1.0.0", - "is-supported-regexp-flag": "^1.0.0" - } - }, - "clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", - "dev": true, - "requires": { - "mimic-response": "^1.0.0" - } - }, - "clone-stats": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", - "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", - "dev": true - }, - "cloneable-readable": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.2.tgz", - "integrity": "sha512-Bq6+4t+lbM8vhTs/Bef5c5AdEMtapp/iFb6+s4/Hh9MVTt8OLKH7ZOOZSCT+Ys7hsHvqv0GuMPJ1lnQJVHvxpg==", + "conventional-commits-filter": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.1.tgz", + "integrity": "sha512-92OU8pz/977udhBjgPEbg3sbYzIxMDFTlQT97w7KdhR9igNqdJvy8smmedAAgn4tPiqseFloKkrVfbXCVd+E7A==", "dev": true, "requires": { - "inherits": "^2.0.1", - "process-nextick-args": "^2.0.0", - "readable-stream": "^2.3.5" + "is-subset": "^0.1.1", + "modify-values": "^1.0.0" } }, - "cmd-shim": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-2.0.2.tgz", - "integrity": "sha1-b8vamUg6j9FdfTChlspp1oii79s=", + "conventional-commits-parser": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.0.1.tgz", + "integrity": "sha512-P6U5UOvDeidUJ8ebHVDIoXzI7gMlQ1OF/id6oUvp8cnZvOXMt1n8nYl74Ey9YMn0uVQtxmCtjPQawpsssBWtGg==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "mkdirp": "~0.5.0" - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, - "coa": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.1.tgz", - "integrity": "sha512-5wfTTO8E2/ja4jFSxePXlG5nRu5bBtL/r1HCIpJW/lzT6yDtKl0u0Z4o/Vpz32IpKmBn7HerheEZQgA9N2DarQ==", - "dev": true, - "requires": { - "q": "^1.1.2" - } - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" - }, - "collapse-white-space": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.4.tgz", - "integrity": "sha512-YfQ1tAUZm561vpYD+5eyWN8+UsceQbSrqqlc/6zDY2gtAE+uZLSdkkovhnGpmCThsvKBFakq4EdY/FF93E8XIw==", - "dev": true - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, - "color": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/color/-/color-0.11.4.tgz", - "integrity": "sha1-bXtcdPtl6EHNSHkq0e1eB7kE12Q=", - "dev": true, - "requires": { - "clone": "^1.0.2", - "color-convert": "^1.3.0", - "color-string": "^0.3.0" - } - }, - "color-convert": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz", - "integrity": "sha512-3NUJZdhMhcdPn8vJ9v2UQJoH0qqoGUkYTgFEPZaPjEtwmmKUfNV46zZmgB2M5M4DCEQHMaCfWHCxiBflLm04Tg==", - "dev": true, - "requires": { - "color-name": "1.1.1" - } - }, - "color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=", - "dev": true - }, - "color-string": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-0.3.0.tgz", - "integrity": "sha1-J9RvtnAlxcL6JZk7+/V55HhBuZE=", - "dev": true, - "requires": { - "color-name": "^1.0.0" - } - }, - "colormin": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colormin/-/colormin-1.1.2.tgz", - "integrity": "sha1-6i90IKcrlogaOKrlnsEkpvcpgTM=", - "dev": true, - "requires": { - "color": "^0.11.0", - "css-color-names": "0.0.4", - "has": "^1.0.1" - } - }, - "colors": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/colors/-/colors-0.5.1.tgz", - "integrity": "sha1-fQAj6usVTo7p/Oddy5I9DtFmd3Q=", - "dev": true - }, - "columnify": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.5.4.tgz", - "integrity": "sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs=", - "dev": true, - "requires": { - "strip-ansi": "^3.0.0", - "wcwidth": "^1.0.0" + "JSONStream": "^1.0.4", + "is-text-path": "^1.0.0", + "lodash": "^4.2.1", + "meow": "^4.0.0", + "split2": "^2.0.0", + "through2": "^2.0.0", + "trim-off-newlines": "^1.0.0" }, "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "meow": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz", + "integrity": "sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==", + "dev": true, + "requires": { + "camelcase-keys": "^4.0.0", + "decamelize-keys": "^1.0.0", + "loud-rejection": "^1.0.0", + "minimist": "^1.1.3", + "minimist-options": "^3.0.1", + "normalize-package-data": "^2.3.4", + "read-pkg-up": "^3.0.0", + "redent": "^2.0.0", + "trim-newlines": "^2.0.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true } } }, - "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.16.0.tgz", - "integrity": "sha512-sVXqklSaotK9at437sFlFpyOcJonxe0yST/AG9DkQKUdIE6IqGIMv4SfAQSKaJbSdVEJYItASCrBiVQHq1HQew==", - "dev": true - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true - }, - "compare-func": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-1.3.2.tgz", - "integrity": "sha1-md0LpFfh+bxyKxLAjsM+6rMfpkg=", + "conventional-recommended-bump": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-4.0.4.tgz", + "integrity": "sha512-9mY5Yoblq+ZMqJpBzgS+RpSq+SUfP2miOR3H/NR9drGf08WCrY9B6HAGJZEm6+ThsVP917VHAahSOjM6k1vhPg==", "dev": true, "requires": { - "array-ify": "^1.0.0", - "dot-prop": "^3.0.0" + "concat-stream": "^1.6.0", + "conventional-changelog-preset-loader": "^2.0.2", + "conventional-commits-filter": "^2.0.1", + "conventional-commits-parser": "^3.0.1", + "git-raw-commits": "2.0.0", + "git-semver-tags": "^2.0.2", + "meow": "^4.0.0", + "q": "^1.5.1" }, "dependencies": { - "dot-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-3.0.0.tgz", - "integrity": "sha1-G3CK8JSknJoOfbyteQq6U52sEXc=", + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", "dev": true, "requires": { - "is-obj": "^1.0.0" + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" } - } - } - }, - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true - }, - "computed-style": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/computed-style/-/computed-style-0.1.4.tgz", - "integrity": "sha1-fzRP2FhLLkJb7cpKGvwOMAuwXXQ=" - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "concurrently": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-3.5.0.tgz", - "integrity": "sha1-jPG3cHppFqeKT/W3e7BN7FSzebI=", - "dev": true, - "requires": { - "chalk": "0.5.1", - "commander": "2.6.0", - "date-fns": "^1.23.0", - "lodash": "^4.5.1", - "rx": "2.3.24", - "spawn-command": "^0.0.2-1", - "supports-color": "^3.2.3", - "tree-kill": "^1.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", - "integrity": "sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk=", - "dev": true - }, - "ansi-styles": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz", - "integrity": "sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94=", - "dev": true - }, - "chalk": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz", - "integrity": "sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ=", - "dev": true, - "requires": { - "ansi-styles": "^1.1.0", - "escape-string-regexp": "^1.0.0", - "has-ansi": "^0.1.0", - "strip-ansi": "^0.3.0", - "supports-color": "^0.2.0" - }, - "dependencies": { - "supports-color": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz", - "integrity": "sha1-2S3iaU6z9nMjlz1649i1W0wiGQo=", - "dev": true - } - } - }, - "commander": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.6.0.tgz", - "integrity": "sha1-nfflL7Kgyw+4kFjugMMQQiXzfh0=", - "dev": true }, - "has-ansi": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz", - "integrity": "sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4=", + "meow": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz", + "integrity": "sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==", "dev": true, "requires": { - "ansi-regex": "^0.2.0" + "camelcase-keys": "^4.0.0", + "decamelize-keys": "^1.0.0", + "loud-rejection": "^1.0.0", + "minimist": "^1.1.3", + "minimist-options": "^3.0.1", + "normalize-package-data": "^2.3.4", + "read-pkg-up": "^3.0.0", + "redent": "^2.0.0", + "trim-newlines": "^2.0.0" } }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, - "strip-ansi": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz", - "integrity": "sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA=", - "dev": true, - "requires": { - "ansi-regex": "^0.2.1" - } - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "^1.0.0" - } - } - } - }, - "config-chain": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", - "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", - "dev": true, - "requires": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, - "console-browserify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", - "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", - "dev": true, - "requires": { - "date-now": "^0.1.4" - } - }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true - }, - "consolidated-events": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/consolidated-events/-/consolidated-events-2.0.2.tgz", - "integrity": "sha512-2/uRVMdRypf5z/TW/ncD/66l75P5hH2vM/GR8Jf8HLc2xnfJtmina6F6du8+v4Z2vTrMo7jC+W1tmEEuuELgkQ==" - }, - "constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", - "dev": true - }, - "content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", - "dev": true - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "dev": true - }, - "continuable-cache": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz", - "integrity": "sha1-vXJ6f67XfnH/OYWskzUakSczrQ8=", - "dev": true - }, - "conventional-changelog-angular": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.2.tgz", - "integrity": "sha512-yx7m7lVrXmt4nKWQgWZqxSALEiAKZhOAcbxdUaU9575mB0CzXVbgrgpfSnSP7OqWDUTYGD0YVJ0MSRdyOPgAwA==", - "dev": true, - "requires": { - "compare-func": "^1.3.1", - "q": "^1.5.1" - } - }, - "conventional-changelog-core": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-3.1.5.tgz", - "integrity": "sha512-iwqAotS4zk0wA4S84YY1JCUG7X3LxaRjJxuUo6GI4dZuIy243j5nOg/Ora35ExT4DOiw5dQbMMQvw2SUjh6moQ==", - "dev": true, - "requires": { - "conventional-changelog-writer": "^4.0.2", - "conventional-commits-parser": "^3.0.1", - "dateformat": "^3.0.0", - "get-pkg-repo": "^1.0.0", - "git-raw-commits": "2.0.0", - "git-remote-origin-url": "^2.0.0", - "git-semver-tags": "^2.0.2", - "lodash": "^4.2.1", - "normalize-package-data": "^2.3.5", - "q": "^1.5.1", - "read-pkg": "^3.0.0", - "read-pkg-up": "^3.0.0", - "through2": "^2.0.0" - }, - "dependencies": { - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - } - }, "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", @@ -6030,73 +5559,87 @@ } } }, - "conventional-changelog-preset-loader": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.0.2.tgz", - "integrity": "sha512-pBY+qnUoJPXAXXqVGwQaVmcye05xi6z231QM98wHWamGAmu/ghkBprQAwmF5bdmyobdVxiLhPY3PrCfSeUNzRQ==", + "convert-source-map": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", + "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=", "dev": true }, - "conventional-changelog-writer": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.2.tgz", - "integrity": "sha512-d8/FQY/fix2xXEBUhOo8u3DCbyEw3UOQgYHxLsPDw+wHUDma/GQGAGsGtoH876WyNs32fViHmTOUrgRKVLvBug==", + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", + "dev": true + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "dev": true + }, + "copy-concurrently": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", + "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", "dev": true, "requires": { - "compare-func": "^1.3.1", - "conventional-commits-filter": "^2.0.1", - "dateformat": "^3.0.0", - "handlebars": "^4.0.2", - "json-stringify-safe": "^5.0.1", - "lodash": "^4.2.1", - "meow": "^4.0.0", - "semver": "^5.5.0", - "split": "^1.0.0", - "through2": "^2.0.0" - }, - "dependencies": { - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - } - }, - "meow": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz", - "integrity": "sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==", + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "copy-webpack-plugin": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-4.5.2.tgz", + "integrity": "sha512-zmC33E8FFSq3AbflTvqvPvBo621H36Afsxlui91d+QyZxPIuXghfnTsa1CuqiAaCPgJoSUWfTFbKJnadZpKEbQ==", + "dev": true, + "requires": { + "cacache": "^10.0.4", + "find-cache-dir": "^1.0.0", + "globby": "^7.1.1", + "is-glob": "^4.0.0", + "loader-utils": "^1.1.0", + "minimatch": "^3.0.4", + "p-limit": "^1.0.0", + "serialize-javascript": "^1.4.0" + }, + "dependencies": { + "globby": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", + "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", "dev": true, "requires": { - "camelcase-keys": "^4.0.0", - "decamelize-keys": "^1.0.0", - "loud-rejection": "^1.0.0", - "minimist": "^1.1.3", - "minimist-options": "^3.0.1", - "normalize-package-data": "^2.3.4", - "read-pkg-up": "^3.0.0", - "redent": "^2.0.0", - "trim-newlines": "^2.0.0" + "array-union": "^1.0.1", + "dir-glob": "^2.0.0", + "glob": "^7.1.2", + "ignore": "^3.3.5", + "pify": "^3.0.0", + "slash": "^1.0.0" } }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, - "parse-json": { + "is-glob": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", "dev": true, "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" + "is-extglob": "^2.1.1" } }, "pify": { @@ -6104,96 +5647,32 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true - }, - "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "dev": true, - "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - } - }, - "read-pkg-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", - "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", - "dev": true, - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^3.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true } } }, - "conventional-commits-filter": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.1.tgz", - "integrity": "sha512-92OU8pz/977udhBjgPEbg3sbYzIxMDFTlQT97w7KdhR9igNqdJvy8smmedAAgn4tPiqseFloKkrVfbXCVd+E7A==", - "dev": true, - "requires": { - "is-subset": "^0.1.1", - "modify-values": "^1.0.0" - } + "core-js": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", + "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==", + "dev": true }, - "conventional-commits-parser": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.0.1.tgz", - "integrity": "sha512-P6U5UOvDeidUJ8ebHVDIoXzI7gMlQ1OF/id6oUvp8cnZvOXMt1n8nYl74Ey9YMn0uVQtxmCtjPQawpsssBWtGg==", + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cosmiconfig": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.0.5.tgz", + "integrity": "sha512-94j37OtvxS5w7qr7Ta6dt67tWdnOxigBVN4VnSxNXFez9o18PGQ0D33SchKP17r9LAcWVTYV72G6vDayAUBFIg==", "dev": true, "requires": { - "JSONStream": "^1.0.4", - "is-text-path": "^1.0.0", - "lodash": "^4.2.1", - "meow": "^4.0.0", - "split2": "^2.0.0", - "through2": "^2.0.0", - "trim-off-newlines": "^1.0.0" + "is-directory": "^0.3.1", + "js-yaml": "^3.9.0", + "parse-json": "^4.0.0" }, "dependencies": { - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - } - }, - "meow": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz", - "integrity": "sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==", - "dev": true, - "requires": { - "camelcase-keys": "^4.0.0", - "decamelize-keys": "^1.0.0", - "loud-rejection": "^1.0.0", - "minimist": "^1.1.3", - "minimist-options": "^3.0.1", - "normalize-package-data": "^2.3.4", - "read-pkg-up": "^3.0.0", - "redent": "^2.0.0", - "trim-newlines": "^2.0.0" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", @@ -6203,268 +5682,13 @@ "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" } - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - }, - "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "dev": true, - "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - } - }, - "read-pkg-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", - "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", - "dev": true, - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^3.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true } } }, - "conventional-recommended-bump": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-4.0.4.tgz", - "integrity": "sha512-9mY5Yoblq+ZMqJpBzgS+RpSq+SUfP2miOR3H/NR9drGf08WCrY9B6HAGJZEm6+ThsVP917VHAahSOjM6k1vhPg==", - "dev": true, - "requires": { - "concat-stream": "^1.6.0", - "conventional-changelog-preset-loader": "^2.0.2", - "conventional-commits-filter": "^2.0.1", - "conventional-commits-parser": "^3.0.1", - "git-raw-commits": "2.0.0", - "git-semver-tags": "^2.0.2", - "meow": "^4.0.0", - "q": "^1.5.1" - }, - "dependencies": { - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - } - }, - "meow": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz", - "integrity": "sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==", - "dev": true, - "requires": { - "camelcase-keys": "^4.0.0", - "decamelize-keys": "^1.0.0", - "loud-rejection": "^1.0.0", - "minimist": "^1.1.3", - "minimist-options": "^3.0.1", - "normalize-package-data": "^2.3.4", - "read-pkg-up": "^3.0.0", - "redent": "^2.0.0", - "trim-newlines": "^2.0.0" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dev": true, - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - }, - "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "dev": true, - "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - } - }, - "read-pkg-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", - "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", - "dev": true, - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^3.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - } - } - }, - "convert-source-map": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", - "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=", - "dev": true - }, - "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", - "dev": true - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", - "dev": true - }, - "copy-concurrently": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", - "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", - "dev": true, - "requires": { - "aproba": "^1.1.1", - "fs-write-stream-atomic": "^1.0.8", - "iferr": "^0.1.5", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.0" - } - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true - }, - "copy-webpack-plugin": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-4.5.2.tgz", - "integrity": "sha512-zmC33E8FFSq3AbflTvqvPvBo621H36Afsxlui91d+QyZxPIuXghfnTsa1CuqiAaCPgJoSUWfTFbKJnadZpKEbQ==", - "dev": true, - "requires": { - "cacache": "^10.0.4", - "find-cache-dir": "^1.0.0", - "globby": "^7.1.1", - "is-glob": "^4.0.0", - "loader-utils": "^1.1.0", - "minimatch": "^3.0.4", - "p-limit": "^1.0.0", - "serialize-javascript": "^1.4.0" - }, - "dependencies": { - "globby": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", - "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "dir-glob": "^2.0.0", - "glob": "^7.1.2", - "ignore": "^3.3.5", - "pify": "^3.0.0", - "slash": "^1.0.0" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-glob": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", - "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } - } - }, - "core-js": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", - "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==", - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "cosmiconfig": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.0.5.tgz", - "integrity": "sha512-94j37OtvxS5w7qr7Ta6dt67tWdnOxigBVN4VnSxNXFez9o18PGQ0D33SchKP17r9LAcWVTYV72G6vDayAUBFIg==", - "dev": true, - "requires": { - "is-directory": "^0.3.1", - "js-yaml": "^3.9.0", - "parse-json": "^4.0.0" - }, - "dependencies": { - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dev": true, - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - } - } - }, - "create-ecdh": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", - "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "create-ecdh": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", + "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", "dev": true, "requires": { "bn.js": "^4.1.0", @@ -6948,15 +6172,6 @@ "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", "dev": true }, - "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "dev": true, - "requires": { - "mimic-response": "^1.0.0" - } - }, "dedent": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", @@ -6972,12 +6187,6 @@ "lodash.isequal": "^3.0" } }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true - }, "deep-freeze": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/deep-freeze/-/deep-freeze-0.0.1.tgz", @@ -7123,12 +6332,6 @@ "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", "dev": true }, - "detect-conflict": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/detect-conflict/-/detect-conflict-1.0.1.tgz", - "integrity": "sha1-CIZXpmqWHAUBnbfEIwiDsca0F24=", - "dev": true - }, "detect-indent": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", @@ -7278,12 +6481,6 @@ "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", "dev": true }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true - }, "duplexify": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.0.tgz", @@ -7306,12 +6503,6 @@ "jsbn": "~0.1.0" } }, - "editions": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/editions/-/editions-1.3.4.tgz", - "integrity": "sha512-gzao+mxnYDzIysXKMQi/+M1mjy/rjestjg6OPoYTtI+3Izp23oiGZitsl9lPDPiTGXbcSIk1iJWhliSaglxnUg==", - "dev": true - }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -7403,12 +6594,6 @@ "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=", "dev": true }, - "envinfo": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-4.4.2.tgz", - "integrity": "sha512-5rfRs+m+6pwoKRCFqpsA5+qsLngFms1aWPrxfKbrObCzQaPc3M3yPloZx+BL9UE3dK58cxw36XVQbFRSCCfGSQ==", - "dev": true - }, "enzyme": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.7.0.tgz", @@ -7913,12 +7098,6 @@ "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", "dev": true }, - "exit-hook": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", - "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", - "dev": true - }, "expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", @@ -8014,15 +7193,6 @@ } } }, - "expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", - "dev": true, - "requires": { - "homedir-polyfill": "^1.0.1" - } - }, "expect": { "version": "23.6.0", "resolved": "https://registry.npmjs.org/expect/-/expect-23.6.0.tgz", @@ -8706,15 +7876,6 @@ } } }, - "first-chunk-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-2.0.0.tgz", - "integrity": "sha1-G97NuOCDwGZLkZRVgVd6Q6nzHXA=", - "dev": true, - "requires": { - "readable-stream": "^2.0.2" - } - }, "flat-cache": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", @@ -8739,12 +7900,6 @@ "integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=", "dev": true }, - "flow-parser": { - "version": "0.76.0", - "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.76.0.tgz", - "integrity": "sha512-p+K8OKiMlq8AIZH8KTydHEGUUd71AqfCL+zTJNsdHtQmX3i3eaeIysF83Ad6Oo7OQcHCj3vocb/EHYiEyq+ZBg==", - "dev": true - }, "flush-write-stream": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz", @@ -9509,9 +8664,9 @@ "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" }, "get-own-enumerable-property-symbols": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-2.0.1.tgz", - "integrity": "sha512-TtY/sbOemiMKPRUDDanGCSgBYe7Mf0vbRsWnBZ+9yghpZ1MvcpSpuZFjHdEeY/LZjZy0vdLjS77L6HosisFiug==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.0.tgz", + "integrity": "sha512-CIJYJC4GGF06TakLg8z4GQKvDsx9EMspVxOYih7LerEL/WosUnFIww45CGfxfeKHqlg3twgUrYRT1O3WQqjGCg==", "dev": true }, "get-pkg-repo": { @@ -9650,55 +8805,6 @@ "safe-buffer": "^5.1.1" } }, - "gh-got": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/gh-got/-/gh-got-6.0.0.tgz", - "integrity": "sha512-F/mS+fsWQMo1zfgG9MD8KWvTWPPzzhuVwY++fhQ5Ggd+0P+CAMHtzMZhNxG+TqGfHDChJKsbh6otfMGqO2AKBw==", - "dev": true, - "requires": { - "got": "^7.0.0", - "is-plain-obj": "^1.1.0" - }, - "dependencies": { - "got": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/got/-/got-7.1.0.tgz", - "integrity": "sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw==", - "dev": true, - "requires": { - "decompress-response": "^3.2.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "is-plain-obj": "^1.1.0", - "is-retry-allowed": "^1.0.0", - "is-stream": "^1.0.0", - "isurl": "^1.0.0-alpha5", - "lowercase-keys": "^1.0.0", - "p-cancelable": "^0.3.0", - "p-timeout": "^1.1.1", - "safe-buffer": "^5.0.1", - "timed-out": "^4.0.0", - "url-parse-lax": "^1.0.0", - "url-to-options": "^1.0.1" - } - }, - "p-cancelable": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz", - "integrity": "sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==", - "dev": true - }, - "p-timeout": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-1.2.1.tgz", - "integrity": "sha1-XrOzU7f86Z8QGhA4iAuwVOu+o4Y=", - "dev": true, - "requires": { - "p-finally": "^1.0.0" - } - } - } - }, "git-raw-commits": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.0.tgz", @@ -9901,15 +9007,6 @@ "ini": "^1.3.2" } }, - "github-username": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/github-username/-/github-username-4.1.0.tgz", - "integrity": "sha1-y+KABBiDIG2kISrp5LXxacML9Bc=", - "dev": true, - "requires": { - "gh-got": "^6.0.0" - } - }, "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", @@ -9924,37 +9021,10 @@ "path-is-absolute": "^1.0.0" } }, - "glob-all": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-all/-/glob-all-3.1.0.tgz", - "integrity": "sha1-iRPd+17hrHgSZWJBsD1SF8ZLAqs=", - "dev": true, - "requires": { - "glob": "^7.0.5", - "yargs": "~1.2.6" - }, - "dependencies": { - "minimist": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.1.0.tgz", - "integrity": "sha1-md9lelJXTCHJBXSX33QnkLK0wN4=", - "dev": true - }, - "yargs": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-1.2.6.tgz", - "integrity": "sha1-nHtKgv1dWVsr8Xq23MQxNUMv40s=", - "dev": true, - "requires": { - "minimist": "^0.1.0" - } - } - } - }, - "glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", "dev": true, "requires": { "glob-parent": "^2.0.0", @@ -9985,29 +9055,11 @@ "is-symbol": "^1.0.1" } }, - "global-modules": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", - "dev": true, - "requires": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" - } - }, - "global-prefix": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", - "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", - "dev": true, - "requires": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" - } + "global-modules-path": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/global-modules-path/-/global-modules-path-2.3.1.tgz", + "integrity": "sha512-y+shkf4InI7mPRHSo2b/k6ix6+NLDtyccYv86whhxrSGX9wjPX1VMITmrDbE1eh7zkzhiWtW2sHklJYoQ62Cxg==", + "dev": true }, "globals": { "version": "11.7.0", @@ -10077,15 +9129,6 @@ "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", "dev": true }, - "grouped-queue": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/grouped-queue/-/grouped-queue-0.3.3.tgz", - "integrity": "sha1-wWfSpTGcWg4JZO9qJbfC34mWyFw=", - "dev": true, - "requires": { - "lodash": "^4.17.2" - } - }, "growly": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", @@ -10180,38 +9223,17 @@ } } }, - "has-color": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", - "integrity": "sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=", - "dev": true - }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, - "has-symbol-support-x": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", - "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==", - "dev": true - }, "has-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=" }, - "has-to-string-tag-x": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", - "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", - "dev": true, - "requires": { - "has-symbol-support-x": "^1.4.1" - } - }, "has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", @@ -10605,16 +9627,6 @@ "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", "dev": true }, - "into-stream": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz", - "integrity": "sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY=", - "dev": true, - "requires": { - "from2": "^2.1.1", - "p-is-promise": "^1.1.0" - } - }, "invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -10909,29 +9921,6 @@ "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", "dev": true }, - "is-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", - "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=", - "dev": true - }, - "is-observable": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-0.2.0.tgz", - "integrity": "sha1-s2ExHYPG5dcmyr9eJQsCNxBvWuI=", - "dev": true, - "requires": { - "symbol-observable": "^0.2.2" - }, - "dependencies": { - "symbol-observable": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-0.2.4.tgz", - "integrity": "sha1-lag9smGG1q9+ehjb2XYKL4bQj0A=", - "dev": true - } - } - }, "is-path-cwd": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", @@ -11019,21 +10008,6 @@ "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", "dev": true }, - "is-retry-allowed": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", - "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=", - "dev": true - }, - "is-scoped": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-scoped/-/is-scoped-1.0.0.tgz", - "integrity": "sha1-RJypgpnnEwOCViieyytUDcQ3yzA=", - "dev": true, - "requires": { - "scoped-regex": "^1.0.0" - } - }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", @@ -11121,12 +10095,6 @@ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true }, - "isbinaryfile": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.2.tgz", - "integrity": "sha1-Sj6XTsDLqQBNP8bN5yCeppNopiE=", - "dev": true - }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -11290,27 +10258,6 @@ "handlebars": "^4.0.3" } }, - "istextorbinary": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-2.2.1.tgz", - "integrity": "sha512-TS+hoFl8Z5FAFMK38nhBkdLt44CclNRgDHWeMgsV8ko3nDlr/9UI2Sf839sW7enijf8oKsZYXRvM8g0it9Zmcw==", - "dev": true, - "requires": { - "binaryextensions": "2", - "editions": "^1.3.3", - "textextensions": "2" - } - }, - "isurl": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", - "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", - "dev": true, - "requires": { - "has-to-string-tag-x": "^1.2.0", - "is-object": "^1.0.1" - } - }, "jest": { "version": "23.6.0", "resolved": "https://registry.npmjs.org/jest/-/jest-23.6.0.tgz", @@ -12966,167 +11913,6 @@ "dev": true, "optional": true }, - "jscodeshift": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.5.1.tgz", - "integrity": "sha512-sRMollbhbmSDrR79JMAnhEjyZJlQQVozeeY9A6/KNuV26DNcuB3mGSCWXp0hks9dcwRNOELbNOiwraZaXXRk5Q==", - "dev": true, - "requires": { - "babel-plugin-transform-flow-strip-types": "^6.8.0", - "babel-preset-es2015": "^6.9.0", - "babel-preset-stage-1": "^6.5.0", - "babel-register": "^6.9.0", - "babylon": "^7.0.0-beta.47", - "colors": "^1.1.2", - "flow-parser": "^0.*", - "lodash": "^4.13.1", - "micromatch": "^2.3.7", - "neo-async": "^2.5.0", - "node-dir": "0.1.8", - "nomnom": "^1.8.1", - "recast": "^0.15.0", - "temp": "^0.8.1", - "write-file-atomic": "^1.2.0" - }, - "dependencies": { - "ansi-styles": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.0.0.tgz", - "integrity": "sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg=", - "dev": true - }, - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true, - "requires": { - "arr-flatten": "^1.0.1" - } - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true - }, - "babylon": { - "version": "7.0.0-beta.47", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.47.tgz", - "integrity": "sha512-+rq2cr4GDhtToEzKFD6KZZMDBXhjFAr9JjPw9pAppZACeEWqNM294j+NdBzkSHYXwzzBmVjZ3nEVJlOhbR2gOQ==", - "dev": true - }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true, - "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" - } - }, - "chalk": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz", - "integrity": "sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=", - "dev": true, - "requires": { - "ansi-styles": "~1.0.0", - "has-color": "~0.1.0", - "strip-ansi": "~0.1.0" - } - }, - "colors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.0.tgz", - "integrity": "sha512-EDpX3a7wHMWFA7PUHWPHNWqOxIIRSJetuwl0AS5Oi/5FMV8kWm69RTlgm00GKjBO1xFHMtBbL49yRtMMdticBw==", - "dev": true - }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "dev": true, - "requires": { - "is-posix-bracket": "^0.1.0" - } - }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true, - "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" - } - }, - "nomnom": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.8.1.tgz", - "integrity": "sha1-IVH3Ikcrp55Qp2/BJbuMjy5Nwqc=", - "dev": true, - "requires": { - "chalk": "~0.4.0", - "underscore": "~1.6.0" - } - }, - "strip-ansi": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz", - "integrity": "sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE=", - "dev": true - }, - "underscore": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", - "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=", - "dev": true - }, - "write-file-atomic": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.3.4.tgz", - "integrity": "sha1-+Aek8LHZ6ROuekgRLmzDrxmRtF8=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "slide": "^1.1.5" - } - } - } - }, "jsdom": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", @@ -13205,12 +11991,6 @@ "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=", "dev": true }, - "json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", - "dev": true - }, "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", @@ -13247,571 +12027,196 @@ "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", "dev": true }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", - "dev": true - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "jsx-ast-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz", - "integrity": "sha1-6AGxs5mF4g//yHtA43SAgOLcrH8=", - "dev": true, - "requires": { - "array-includes": "^3.0.3" - } - }, - "keyv": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz", - "integrity": "sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==", - "dev": true, - "requires": { - "json-buffer": "3.0.0" - } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - }, - "kleur": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-2.0.2.tgz", - "integrity": "sha512-77XF9iTllATmG9lSlIv0qdQ2BQ/h9t0bJllHlbvsQ0zUWfU7Yi0S8L5JXzPZgkefIiajLmBJJ4BsMJmqcf7oxQ==", - "dev": true - }, - "known-css-properties": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.11.0.tgz", - "integrity": "sha512-bEZlJzXo5V/ApNNa5z375mJC6Nrz4vG43UgcSCrg2OHC+yuB6j0iDSrY7RQ/+PRofFB03wNIIt9iXIVLr4wc7w==", - "dev": true - }, - "lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", - "dev": true - }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "requires": { - "invert-kv": "^1.0.0" - } - }, - "leb": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/leb/-/leb-0.3.0.tgz", - "integrity": "sha1-Mr7p+tFoMo1q6oUi2DP0GA7tHaM=", - "dev": true - }, - "left-pad": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", - "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==", - "dev": true - }, - "lerna": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/lerna/-/lerna-3.4.3.tgz", - "integrity": "sha512-tWq1LvpHqkyB+FaJCmkEweivr88yShDMmauofPVdh0M5gU1cVucszYnIgWafulKYu2LMQ3IfUMUU5Pp3+MvADQ==", - "dev": true, - "requires": { - "@lerna/add": "^3.4.1", - "@lerna/bootstrap": "^3.4.1", - "@lerna/changed": "^3.4.1", - "@lerna/clean": "^3.3.2", - "@lerna/cli": "^3.2.0", - "@lerna/create": "^3.4.1", - "@lerna/diff": "^3.3.0", - "@lerna/exec": "^3.3.2", - "@lerna/import": "^3.3.1", - "@lerna/init": "^3.3.0", - "@lerna/link": "^3.3.0", - "@lerna/list": "^3.3.2", - "@lerna/publish": "^3.4.3", - "@lerna/run": "^3.3.2", - "@lerna/version": "^3.4.1", - "import-local": "^1.0.0", - "npmlog": "^4.1.2" - } - }, - "leven": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", - "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=", - "dev": true - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "libnpmaccess": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/libnpmaccess/-/libnpmaccess-3.0.0.tgz", - "integrity": "sha512-SiE4AZAzMpD7pmmXHfgD7rof8QIQGoKaeyAS8exgx2CKA6tzRTbRljq1xM4Tgj8/tIg+KBJPJWkR0ifqKT3irQ==", - "dev": true, - "requires": { - "aproba": "^2.0.0", - "get-stream": "^4.0.0", - "npm-package-arg": "^6.1.0", - "npm-registry-fetch": "^3.8.0" - }, - "dependencies": { - "aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", - "dev": true - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - } - } - }, - "line-height": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/line-height/-/line-height-0.3.1.tgz", - "integrity": "sha1-SxIF7d4YKHKl76PI9iCzGHqcVMk=", - "requires": { - "computed-style": "~0.1.3" - } - }, - "lint-staged": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-7.3.0.tgz", - "integrity": "sha512-AXk40M9DAiPi7f4tdJggwuKIViUplYtVj1os1MVEteW7qOkU50EOehayCfO9TsoGK24o/EsWb41yrEgfJDDjCw==", - "dev": true, - "requires": { - "chalk": "^2.3.1", - "commander": "^2.14.1", - "cosmiconfig": "^5.0.2", - "debug": "^3.1.0", - "dedent": "^0.7.0", - "execa": "^0.9.0", - "find-parent-dir": "^0.3.0", - "is-glob": "^4.0.0", - "is-windows": "^1.0.2", - "jest-validate": "^23.5.0", - "listr": "^0.14.1", - "lodash": "^4.17.5", - "log-symbols": "^2.2.0", - "micromatch": "^3.1.8", - "npm-which": "^3.0.1", - "p-map": "^1.1.1", - "path-is-inside": "^1.0.2", - "pify": "^3.0.0", - "please-upgrade-node": "^3.0.2", - "staged-git-files": "1.1.1", - "string-argv": "^0.0.2", - "stringify-object": "^3.2.2" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "execa": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.9.0.tgz", - "integrity": "sha512-BbUMBiX4hqiHZUA5+JujIjNb6TyAlp2D5KLheMjMluwOuzcnylDL4AxZYLLn1n2AGB49eSWwyKvvEQoRpnAtmA==", - "dev": true, - "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-glob": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", - "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-observable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-1.1.0.tgz", - "integrity": "sha512-NqCa4Sa2d+u7BWc6CukaObG3Fh+CU9bvixbpcXYhy2VvYS7vVGIdAgnIS5Ks3A/cqk4rebLJ9s8zBstT2aKnIA==", - "dev": true, - "requires": { - "symbol-observable": "^1.1.0" - } - }, - "listr": { - "version": "0.14.3", - "resolved": "https://registry.npmjs.org/listr/-/listr-0.14.3.tgz", - "integrity": "sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA==", - "dev": true, - "requires": { - "@samverschueren/stream-to-observable": "^0.3.0", - "is-observable": "^1.1.0", - "is-promise": "^2.1.0", - "is-stream": "^1.1.0", - "listr-silent-renderer": "^1.1.1", - "listr-update-renderer": "^0.5.0", - "listr-verbose-renderer": "^0.5.0", - "p-map": "^2.0.0", - "rxjs": "^6.3.3" - }, - "dependencies": { - "p-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.0.0.tgz", - "integrity": "sha512-GO107XdrSUmtHxVoi60qc9tUl/KkNKm+X2CF4P9amalpGxv5YqVPJNfSb0wcA+syCopkZvYYIzW8OVTQW59x/w==", - "dev": true - } - } - }, - "listr-update-renderer": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz", - "integrity": "sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "cli-truncate": "^0.2.1", - "elegant-spinner": "^1.0.1", - "figures": "^1.7.0", - "indent-string": "^3.0.0", - "log-symbols": "^1.0.2", - "log-update": "^2.3.0", - "strip-ansi": "^3.0.1" - }, - "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "log-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", - "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", - "dev": true, - "requires": { - "chalk": "^1.0.0" - } - } - } - }, - "listr-verbose-renderer": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.5.0.tgz", - "integrity": "sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "cli-cursor": "^2.1.0", - "date-fns": "^1.27.2", - "figures": "^2.0.0" - }, - "dependencies": { - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - } - } - }, - "log-update": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-2.3.0.tgz", - "integrity": "sha1-iDKP19HOeTiykoN0bwsbwSayRwg=", - "dev": true, - "requires": { - "ansi-escapes": "^3.0.0", - "cli-cursor": "^2.0.0", - "wrap-ansi": "^3.0.1" - } - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - }, - "rxjs": { - "version": "6.3.3", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", - "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - }, - "wrap-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz", - "integrity": "sha1-KIoE2H7aXChuBg3+jxNc6NAH+Lo=", - "dev": true, - "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - } + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" } }, - "listr": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/listr/-/listr-0.13.0.tgz", - "integrity": "sha1-ILsLowuuZg7oTMBQPfS+PVYjiH0=", + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", "dev": true, "requires": { - "chalk": "^1.1.3", - "cli-truncate": "^0.2.1", - "figures": "^1.7.0", - "indent-string": "^2.1.0", - "is-observable": "^0.2.0", - "is-promise": "^2.1.0", - "is-stream": "^1.1.0", - "listr-silent-renderer": "^1.1.1", - "listr-update-renderer": "^0.4.0", - "listr-verbose-renderer": "^0.4.0", - "log-symbols": "^1.0.2", - "log-update": "^1.0.2", - "ora": "^0.2.3", - "p-map": "^1.1.1", - "rxjs": "^5.4.2", - "stream-to-observable": "^0.2.0", - "strip-ansi": "^3.0.1" + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "jsx-ast-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz", + "integrity": "sha1-6AGxs5mF4g//yHtA43SAgOLcrH8=", + "dev": true, + "requires": { + "array-includes": "^3.0.3" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "kleur": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-2.0.2.tgz", + "integrity": "sha512-77XF9iTllATmG9lSlIv0qdQ2BQ/h9t0bJllHlbvsQ0zUWfU7Yi0S8L5JXzPZgkefIiajLmBJJ4BsMJmqcf7oxQ==", + "dev": true + }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "dev": true + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "requires": { + "invert-kv": "^1.0.0" + } + }, + "leb": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/leb/-/leb-0.3.0.tgz", + "integrity": "sha1-Mr7p+tFoMo1q6oUi2DP0GA7tHaM=", + "dev": true + }, + "left-pad": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", + "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==", + "dev": true + }, + "lerna": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/lerna/-/lerna-3.4.3.tgz", + "integrity": "sha512-tWq1LvpHqkyB+FaJCmkEweivr88yShDMmauofPVdh0M5gU1cVucszYnIgWafulKYu2LMQ3IfUMUU5Pp3+MvADQ==", + "dev": true, + "requires": { + "@lerna/add": "^3.4.1", + "@lerna/bootstrap": "^3.4.1", + "@lerna/changed": "^3.4.1", + "@lerna/clean": "^3.3.2", + "@lerna/cli": "^3.2.0", + "@lerna/create": "^3.4.1", + "@lerna/diff": "^3.3.0", + "@lerna/exec": "^3.3.2", + "@lerna/import": "^3.3.1", + "@lerna/init": "^3.3.0", + "@lerna/link": "^3.3.0", + "@lerna/list": "^3.3.2", + "@lerna/publish": "^3.4.3", + "@lerna/run": "^3.3.2", + "@lerna/version": "^3.4.1", + "import-local": "^1.0.0", + "npmlog": "^4.1.2" + } + }, + "leven": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", + "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "libnpmaccess": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/libnpmaccess/-/libnpmaccess-3.0.0.tgz", + "integrity": "sha512-SiE4AZAzMpD7pmmXHfgD7rof8QIQGoKaeyAS8exgx2CKA6tzRTbRljq1xM4Tgj8/tIg+KBJPJWkR0ifqKT3irQ==", + "dev": true, + "requires": { + "aproba": "^2.0.0", + "get-stream": "^4.0.0", + "npm-package-arg": "^6.1.0", + "npm-registry-fetch": "^3.8.0" }, "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", "dev": true }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" - } - }, - "indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "dev": true, - "requires": { - "repeating": "^2.0.0" - } - }, - "log-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", - "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", "dev": true, "requires": { - "chalk": "^1.0.0" + "pump": "^3.0.0" } }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "dev": true, "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - }, - "wrap-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz", - "integrity": "sha1-KIoE2H7aXChuBg3+jxNc6NAH+Lo=", - "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "requires": { - "ansi-regex": "^3.0.0" - } - } + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } } } }, - "listr-silent-renderer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz", - "integrity": "sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4=", - "dev": true + "line-height": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/line-height/-/line-height-0.3.1.tgz", + "integrity": "sha1-SxIF7d4YKHKl76PI9iCzGHqcVMk=", + "requires": { + "computed-style": "~0.1.3" + } }, - "listr-update-renderer": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.4.0.tgz", - "integrity": "sha1-NE2YDaLKLosUW6MFkI8yrj9MyKc=", + "lint-staged": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-7.3.0.tgz", + "integrity": "sha512-AXk40M9DAiPi7f4tdJggwuKIViUplYtVj1os1MVEteW7qOkU50EOehayCfO9TsoGK24o/EsWb41yrEgfJDDjCw==", "dev": true, "requires": { - "chalk": "^1.1.3", - "cli-truncate": "^0.2.1", - "elegant-spinner": "^1.0.1", - "figures": "^1.7.0", - "indent-string": "^3.0.0", - "log-symbols": "^1.0.2", - "log-update": "^1.0.2", - "strip-ansi": "^3.0.1" + "chalk": "^2.3.1", + "commander": "^2.14.1", + "cosmiconfig": "^5.0.2", + "debug": "^3.1.0", + "dedent": "^0.7.0", + "execa": "^0.9.0", + "find-parent-dir": "^0.3.0", + "is-glob": "^4.0.0", + "is-windows": "^1.0.2", + "jest-validate": "^23.5.0", + "listr": "^0.14.1", + "lodash": "^4.17.5", + "log-symbols": "^2.2.0", + "micromatch": "^3.1.8", + "npm-which": "^3.0.1", + "p-map": "^1.1.1", + "path-is-inside": "^1.0.2", + "pify": "^3.0.0", + "please-upgrade-node": "^3.0.2", + "staged-git-files": "1.1.1", + "string-argv": "^0.0.2", + "stringify-object": "^3.2.2" }, "dependencies": { "ansi-regex": { @@ -13826,17 +12231,19 @@ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", "dev": true }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "execa": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.9.0.tgz", + "integrity": "sha512-BbUMBiX4hqiHZUA5+JujIjNb6TyAlp2D5KLheMjMluwOuzcnylDL4AxZYLLn1n2AGB49eSWwyKvvEQoRpnAtmA==", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" } }, "figures": { @@ -13849,104 +12256,135 @@ "object-assign": "^4.1.0" } }, - "log-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", - "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", "dev": true, "requires": { - "chalk": "^1.0.0" + "is-extglob": "^2.1.1" } }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "is-observable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-1.1.0.tgz", + "integrity": "sha512-NqCa4Sa2d+u7BWc6CukaObG3Fh+CU9bvixbpcXYhy2VvYS7vVGIdAgnIS5Ks3A/cqk4rebLJ9s8zBstT2aKnIA==", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "symbol-observable": "^1.1.0" } }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "listr-verbose-renderer": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz", - "integrity": "sha1-ggb0z21S3cWCfl/RSYng6WWTOjU=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "cli-cursor": "^1.0.2", - "date-fns": "^1.27.2", - "figures": "^1.7.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "listr": { + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/listr/-/listr-0.14.3.tgz", + "integrity": "sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA==", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" + "@samverschueren/stream-to-observable": "^0.3.0", + "is-observable": "^1.1.0", + "is-promise": "^2.1.0", + "is-stream": "^1.1.0", + "listr-silent-renderer": "^1.1.1", + "listr-update-renderer": "^0.5.0", + "listr-verbose-renderer": "^0.5.0", + "p-map": "^2.0.0", + "rxjs": "^6.3.3" + }, + "dependencies": { + "p-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.0.0.tgz", + "integrity": "sha512-GO107XdrSUmtHxVoi60qc9tUl/KkNKm+X2CF4P9amalpGxv5YqVPJNfSb0wcA+syCopkZvYYIzW8OVTQW59x/w==", + "dev": true + } } }, - "cli-cursor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", - "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "listr-update-renderer": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz", + "integrity": "sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA==", "dev": true, "requires": { - "restore-cursor": "^1.0.1" + "chalk": "^1.1.3", + "cli-truncate": "^0.2.1", + "elegant-spinner": "^1.0.1", + "figures": "^1.7.0", + "indent-string": "^3.0.0", + "log-symbols": "^1.0.2", + "log-update": "^2.3.0", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "log-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", + "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", + "dev": true, + "requires": { + "chalk": "^1.0.0" + } + } } }, - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "listr-verbose-renderer": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.5.0.tgz", + "integrity": "sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw==", "dev": true, "requires": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" + "chalk": "^2.4.1", + "cli-cursor": "^2.1.0", + "date-fns": "^1.27.2", + "figures": "^2.0.0" + }, + "dependencies": { + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + } } }, - "onetime": { - "version": "1.1.0", - "resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", - "dev": true - }, - "restore-cursor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", - "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "log-update": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-2.3.0.tgz", + "integrity": "sha1-iDKP19HOeTiykoN0bwsbwSayRwg=", "dev": true, "requires": { - "exit-hook": "^1.0.0", - "onetime": "^1.0.0" + "ansi-escapes": "^3.0.0", + "cli-cursor": "^2.0.0", + "wrap-ansi": "^3.0.1" } }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -13961,9 +12399,42 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", "dev": true + }, + "wrap-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz", + "integrity": "sha1-KIoE2H7aXChuBg3+jxNc6NAH+Lo=", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } } } }, + "listr-silent-renderer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz", + "integrity": "sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4=", + "dev": true + }, "livereload-js": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.3.0.tgz", @@ -14176,49 +12647,6 @@ "chalk": "^2.0.1" } }, - "log-update": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-1.0.2.tgz", - "integrity": "sha1-GZKfZMQJPS0ucHWh2tivWcKWuNE=", - "dev": true, - "requires": { - "ansi-escapes": "^1.0.0", - "cli-cursor": "^1.0.2" - }, - "dependencies": { - "ansi-escapes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", - "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", - "dev": true - }, - "cli-cursor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", - "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", - "dev": true, - "requires": { - "restore-cursor": "^1.0.1" - } - }, - "onetime": { - "version": "1.1.0", - "resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", - "dev": true - }, - "restore-cursor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", - "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", - "dev": true, - "requires": { - "exit-hook": "^1.0.0", - "onetime": "^1.0.0" - } - } - } - }, "long": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", @@ -14255,12 +12683,6 @@ "signal-exit": "^3.0.0" } }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true - }, "lru-cache": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", @@ -14484,99 +12906,15 @@ "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "dev": true - }, - "mem": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", - "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", - "requires": { - "mimic-fn": "^1.0.0" - } - }, - "mem-fs": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/mem-fs/-/mem-fs-1.1.3.tgz", - "integrity": "sha1-uK6NLj/Lb10/kWXBLUVRoGXZicw=", - "dev": true, - "requires": { - "through2": "^2.0.0", - "vinyl": "^1.1.0", - "vinyl-file": "^2.0.0" - } + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true }, - "mem-fs-editor": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/mem-fs-editor/-/mem-fs-editor-4.0.3.tgz", - "integrity": "sha512-tgWmwI/+6vwu6POan82dTjxEpwAoaj0NAFnghtVo/FcLK2/7IhPUtFUUYlwou4MOY6OtjTUJtwpfH1h+eSUziw==", - "dev": true, + "mem": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", + "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", "requires": { - "commondir": "^1.0.1", - "deep-extend": "^0.6.0", - "ejs": "^2.5.9", - "glob": "^7.0.3", - "globby": "^7.1.1", - "isbinaryfile": "^3.0.2", - "mkdirp": "^0.5.0", - "multimatch": "^2.0.0", - "rimraf": "^2.2.8", - "through2": "^2.0.0", - "vinyl": "^2.0.1" - }, - "dependencies": { - "clone": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", - "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=", - "dev": true - }, - "clone-stats": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", - "dev": true - }, - "globby": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", - "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "dir-glob": "^2.0.0", - "glob": "^7.1.2", - "ignore": "^3.3.5", - "pify": "^3.0.0", - "slash": "^1.0.0" - } - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - }, - "replace-ext": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", - "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", - "dev": true - }, - "vinyl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", - "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", - "dev": true, - "requires": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" - } - } + "mimic-fn": "^1.0.0" } }, "memize": { @@ -14831,12 +13169,6 @@ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" }, - "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true - }, "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -15097,12 +13429,6 @@ "integrity": "sha512-2NpiFHqC87y/zFke0fC0spBXL3bBsoh/p5H1EFhshxjCR5+0g2d6BiXbUFz9v1sAcxsk2htp2eQnNIci2dIYcA==", "dev": true }, - "node-dir": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.8.tgz", - "integrity": "sha1-VfuN62mQcHB/tn+RpGDwRIKUx30=", - "dev": true - }, "node-fetch": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", @@ -15523,25 +13849,6 @@ "integrity": "sha1-0LFF62kRicY6eNIB3E/bEpPvDAM=", "dev": true }, - "normalize-url": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz", - "integrity": "sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==", - "dev": true, - "requires": { - "prepend-http": "^2.0.0", - "query-string": "^5.0.1", - "sort-keys": "^2.0.0" - }, - "dependencies": { - "prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", - "dev": true - } - } - }, "npm-bundled": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.5.tgz", @@ -15925,85 +14232,6 @@ "wordwrap": "~1.0.0" } }, - "ora": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/ora/-/ora-0.2.3.tgz", - "integrity": "sha1-N1J9Igrc1Tw5tzVx11QVbV22V6Q=", - "dev": true, - "requires": { - "chalk": "^1.1.1", - "cli-cursor": "^1.0.2", - "cli-spinners": "^0.1.2", - "object-assign": "^4.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "cli-cursor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", - "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", - "dev": true, - "requires": { - "restore-cursor": "^1.0.1" - } - }, - "onetime": { - "version": "1.1.0", - "resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", - "dev": true - }, - "restore-cursor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", - "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", - "dev": true, - "requires": { - "exit-hook": "^1.0.0", - "onetime": "^1.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, "os-browserify": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", @@ -16042,27 +14270,12 @@ "os-tmpdir": "^1.0.0" } }, - "p-cancelable": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz", - "integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==", - "dev": true - }, "p-defer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", "dev": true }, - "p-each-series": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-1.0.0.tgz", - "integrity": "sha1-kw89Et0fUOdDRFeiLNbwSsatf3E=", - "dev": true, - "requires": { - "p-reduce": "^1.0.0" - } - }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", @@ -16074,12 +14287,6 @@ "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=", "dev": true }, - "p-lazy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-lazy/-/p-lazy-1.0.0.tgz", - "integrity": "sha1-7FPIAvLuOsKPFmzILQsrAt4nqDU=", - "dev": true - }, "p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", @@ -16123,15 +14330,6 @@ "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=", "dev": true }, - "p-timeout": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz", - "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==", - "dev": true, - "requires": { - "p-finally": "^1.0.0" - } - }, "p-try": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", @@ -18285,18 +16483,6 @@ "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", "dev": true }, - "prettier": { - "version": "1.13.7", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.13.7.tgz", - "integrity": "sha512-KIU72UmYPGk4MujZGYMFwinB7lOf2LsDNGSOC8ufevsrPLISrZbNJlWstRi3m0AMuszbH+EFSQ/r6w56RSPK6w==", - "dev": true - }, - "pretty-bytes": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-4.0.2.tgz", - "integrity": "sha1-sr+C5zUNZcbDOqlaqlpPYyf2HNk=", - "dev": true - }, "pretty-format": { "version": "23.6.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", @@ -18528,17 +16714,6 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, - "query-string": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", - "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", - "dev": true, - "requires": { - "decode-uri-component": "^0.2.0", - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" - } - }, "querystring": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", @@ -18901,24 +17076,6 @@ "mute-stream": "~0.0.4" } }, - "read-chunk": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/read-chunk/-/read-chunk-2.1.0.tgz", - "integrity": "sha1-agTAkoAF7Z1C4aasVgDhnLx/9lU=", - "dev": true, - "requires": { - "pify": "^3.0.0", - "safe-buffer": "^5.1.1" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } - } - }, "read-cmd-shim": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-1.0.1.tgz", @@ -19057,41 +17214,6 @@ "util.promisify": "^1.0.0" } }, - "recast": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.15.2.tgz", - "integrity": "sha512-L4f/GqxjlEJ5IZ+tdll/l+6dVi2ylysWbkgFJbMuldD6Jklgfv6zJnCpuAZDfjwHhfcd/De0dDKelsTEPQ29qA==", - "dev": true, - "requires": { - "ast-types": "0.11.5", - "esprima": "~4.0.0", - "private": "~0.1.5", - "source-map": "~0.6.1" - }, - "dependencies": { - "esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", - "dev": true, - "requires": { - "resolve": "^1.1.6" - } - }, "redent": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", @@ -19331,12 +17453,6 @@ "is-finite": "^1.0.0" } }, - "replace-ext": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", - "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", - "dev": true - }, "request": { "version": "2.87.0", "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", @@ -19463,16 +17579,6 @@ "resolve-from": "^3.0.0" } }, - "resolve-dir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", - "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", - "dev": true, - "requires": { - "expand-tilde": "^2.0.0", - "global-modules": "^1.0.0" - } - }, "resolve-from": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", @@ -19485,15 +17591,6 @@ "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", "dev": true }, - "responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", - "dev": true, - "requires": { - "lowercase-keys": "^1.0.0" - } - }, "restore-cursor": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", @@ -19662,20 +17759,12 @@ } }, "rxjs": { - "version": "5.5.11", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.11.tgz", - "integrity": "sha512-3bjO7UwWfA2CV7lmwYMBzj4fQ6Cq+ftHc2MvUe+WMS7wcdJ1LosDWmdjPQanYp2dBRj572p7PeU81JUxHKOcBA==", + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", + "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", "dev": true, "requires": { - "symbol-observable": "1.0.1" - }, - "dependencies": { - "symbol-observable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", - "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=", - "dev": true - } + "tslib": "^1.9.0" } }, "safe-buffer": { @@ -19911,12 +18000,6 @@ } } }, - "scoped-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/scoped-regex/-/scoped-regex-1.0.0.tgz", - "integrity": "sha1-o0a7Gs1CB65wvXwMfKnlZra63bg=", - "dev": true - }, "scss-tokenizer": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz", @@ -20116,17 +18199,6 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" }, - "shelljs": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.2.tgz", - "integrity": "sha512-pRXeNrCA2Wd9itwhvLp5LZQvPJ0wU6bcjaTMywHHGX5XWhVN2nzSu7WV0q+oUY7mGK3mgSkDDzP3MgjqdyIgbQ==", - "dev": true, - "requires": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - } - }, "shellwords": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", @@ -20645,15 +18717,6 @@ "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", "dev": true }, - "stream-to-observable": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/stream-to-observable/-/stream-to-observable-0.2.0.tgz", - "integrity": "sha1-WdbqOT2HwsDdrBCqDVYbxrpvDhA=", - "dev": true, - "requires": { - "any-observable": "^0.2.0" - } - }, "strict-uri-encode": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", @@ -20724,12 +18787,12 @@ } }, "stringify-object": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.2.2.tgz", - "integrity": "sha512-O696NF21oLiDy8PhpWu8AEqoZHw++QW6mUv0UvKZe8gWSdSvMXkiLufK7OmnP27Dro4GU5kb9U7JIO0mBuCRQg==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", "dev": true, "requires": { - "get-own-enumerable-property-symbols": "^2.0.1", + "get-own-enumerable-property-symbols": "^3.0.0", "is-obj": "^1.0.1", "is-regexp": "^1.0.0" } @@ -20751,16 +18814,6 @@ "is-utf8": "^0.2.0" } }, - "strip-bom-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-2.0.0.tgz", - "integrity": "sha1-+H217yYT9paKpUWr/h7HKLaoKco=", - "dev": true, - "requires": { - "first-chunk-stream": "^2.0.0", - "strip-bom": "^2.0.0" - } - }, "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", @@ -21075,6 +19128,12 @@ "is-extglob": "^2.1.1" } }, + "known-css-properties": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.11.0.tgz", + "integrity": "sha512-bEZlJzXo5V/ApNNa5z375mJC6Nrz4vG43UgcSCrg2OHC+yuB6j0iDSrY7RQ/+PRofFB03wNIIt9iXIVLr4wc7w==", + "dev": true + }, "merge2": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.2.3.tgz", @@ -21372,31 +19431,13 @@ }, "tar": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", - "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", - "dev": true, - "requires": { - "block-stream": "*", - "fstream": "^1.0.2", - "inherits": "2" - } - }, - "temp": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz", - "integrity": "sha1-4Ma8TSa5AxJEEOT+2BEDAU38H1k=", - "dev": true, - "requires": { - "os-tmpdir": "^1.0.0", - "rimraf": "~2.2.6" - }, - "dependencies": { - "rimraf": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", - "dev": true - } + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "dev": true, + "requires": { + "block-stream": "*", + "fstream": "^1.0.2", + "inherits": "2" } }, "temp-dir": { @@ -21461,12 +19502,6 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, - "textextensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-2.2.0.tgz", - "integrity": "sha512-j5EMxnryTvKxwH2Cq+Pb43tsf6sdEgw6Pdwxk83mPaq0ToeFJt6WE4J3s5BqY7vmjlLgkgXvhtXUxo80FyBhCA==", - "dev": true - }, "throat": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz", @@ -21489,12 +19524,6 @@ "xtend": "~4.0.1" } }, - "timed-out": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", - "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", - "dev": true - }, "timers-browserify": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", @@ -22112,12 +20141,6 @@ } } }, - "untildify": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-3.0.3.tgz", - "integrity": "sha512-iSk/J8efr8uPT/Z4eSUywnqyrQU7DSdMfdqK4iWEaUVVmcP5JcnpRqmVMwcwcnmI1ATFNgC5V90u09tBynNFKA==", - "dev": true - }, "upath": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", @@ -22155,21 +20178,6 @@ } } }, - "url-parse-lax": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", - "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", - "dev": true, - "requires": { - "prepend-http": "^1.0.1" - } - }, - "url-to-options": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", - "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=", - "dev": true - }, "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", @@ -22212,12 +20220,6 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" }, - "v8-compile-cache": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-1.1.2.tgz", - "integrity": "sha512-ejdrifsIydN1XDH7EuR2hn8ZrkRKUYF7tUcBjBy/lhrCvs2K+zRlbW9UHc0IQ9RsYFZJFqJrieoIHfkCa0DBRA==", - "dev": true - }, "validate-npm-package-license": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", @@ -22307,31 +20309,6 @@ "unist-util-stringify-position": "^1.1.1" } }, - "vinyl": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", - "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", - "dev": true, - "requires": { - "clone": "^1.0.0", - "clone-stats": "^0.0.1", - "replace-ext": "0.0.1" - } - }, - "vinyl-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/vinyl-file/-/vinyl-file-2.0.0.tgz", - "integrity": "sha1-p+v1/779obfRjRQPyweyI++2dRo=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.3.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0", - "strip-bom-stream": "^2.0.0", - "vinyl": "^1.1.0" - } - }, "vm-browserify": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", @@ -22509,15 +20486,15 @@ }, "dependencies": { "ajv": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.2.tgz", - "integrity": "sha512-hOs7GfvI6tUI1LfZddH82ky6mOMyTuY0mk7kE2pWpmhhUSkumzaTO5vbVwij39MdwPQWCV4Zv57Eo06NtL/GVA==", + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.1.tgz", + "integrity": "sha512-ZoJjft5B+EJBjUyu9C9Hc0OZyPZSSlOF+plzouTrg6UlA8f+e/n8NIgBFG/9tppJtpPWfthHakK7juJdNDODww==", "dev": true, "requires": { "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.1" + "uri-js": "^4.2.2" } }, "fast-deep-equal": { @@ -22534,382 +20511,234 @@ } } }, - "webpack-addons": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/webpack-addons/-/webpack-addons-1.1.5.tgz", - "integrity": "sha512-MGO0nVniCLFAQz1qv22zM02QPjcpAoJdy7ED0i3Zy7SY1IecgXCm460ib7H/Wq7e9oL5VL6S2BxaObxwIcag0g==", + "webpack-bundle-analyzer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.0.2.tgz", + "integrity": "sha512-cZG4wSQtKrSpk5RJ33dxiaAyo8bP0V+JvycAyIDFEiDIhw4LHhhVKhn40YT1w6TR9E4scHA00LnIoBtTA13Mow==", "dev": true, "requires": { - "jscodeshift": "^0.4.0" + "acorn": "^5.7.3", + "bfj": "^6.1.1", + "chalk": "^2.4.1", + "commander": "^2.18.0", + "ejs": "^2.6.1", + "express": "^4.16.3", + "filesize": "^3.6.1", + "gzip-size": "^5.0.0", + "lodash": "^4.17.10", + "mkdirp": "^0.5.1", + "opener": "^1.5.1", + "ws": "^6.0.0" }, "dependencies": { - "ansi-styles": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.0.0.tgz", - "integrity": "sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg=", - "dev": true - }, - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true, - "requires": { - "arr-flatten": "^1.0.1" - } - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true - }, - "ast-types": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.10.1.tgz", - "integrity": "sha512-UY7+9DPzlJ9VM8eY0b2TUZcZvF+1pO0hzMtAyjBYKhOmnvRlqYNYnWdtsMj0V16CGaMlpL0G1jnLbLo4AyotuQ==", - "dev": true - }, - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", "dev": true }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true, - "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" - } - }, - "chalk": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz", - "integrity": "sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=", - "dev": true, - "requires": { - "ansi-styles": "~1.0.0", - "has-color": "~0.1.0", - "strip-ansi": "~0.1.0" - } - }, - "colors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.0.tgz", - "integrity": "sha512-EDpX3a7wHMWFA7PUHWPHNWqOxIIRSJetuwl0AS5Oi/5FMV8kWm69RTlgm00GKjBO1xFHMtBbL49yRtMMdticBw==", + "commander": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.18.0.tgz", + "integrity": "sha512-6CYPa+JP2ftfRU2qkDK+UTVeQYosOg/2GbcjIcKPHfinyOLPVGXu/ovN86RP49Re5ndJK1N0kuiidFFuepc4ZQ==", "dev": true - }, - "esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + } + } + }, + "webpack-cli": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.1.2.tgz", + "integrity": "sha512-Cnqo7CeqeSvC6PTdts+dywNi5CRlIPbLx1AoUPK2T6vC1YAugMG3IOoO9DmEscd+Dghw7uRlnzV1KwOe5IrtgQ==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "enhanced-resolve": "^4.1.0", + "global-modules-path": "^2.3.0", + "import-local": "^2.0.0", + "interpret": "^1.1.0", + "loader-utils": "^1.1.0", + "supports-color": "^5.5.0", + "v8-compile-cache": "^2.0.2", + "yargs": "^12.0.2" + }, + "dependencies": { + "camelcase": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", + "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", "dev": true }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "is-posix-bracket": "^0.1.0" + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" } }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "execa": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", + "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", "dev": true, "requires": { - "is-extglob": "^1.0.0" - } - }, - "jscodeshift": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.4.1.tgz", - "integrity": "sha512-iOX6If+hsw0q99V3n31t4f5VlD1TQZddH08xbT65ZqA7T4Vkx68emrDZMUOLVvCEAJ6NpAk7DECe3fjC/t52AQ==", - "dev": true, - "requires": { - "async": "^1.5.0", - "babel-plugin-transform-flow-strip-types": "^6.8.0", - "babel-preset-es2015": "^6.9.0", - "babel-preset-stage-1": "^6.5.0", - "babel-register": "^6.9.0", - "babylon": "^6.17.3", - "colors": "^1.1.2", - "flow-parser": "^0.*", - "lodash": "^4.13.1", - "micromatch": "^2.3.7", - "node-dir": "0.1.8", - "nomnom": "^1.8.1", - "recast": "^0.12.5", - "temp": "^0.8.1", - "write-file-atomic": "^1.2.0" + "cross-spawn": "^6.0.0", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" } }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "locate-path": "^3.0.0" } }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "import-local": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", "dev": true, "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" + "pkg-dir": "^3.0.0", + "resolve-cwd": "^2.0.0" } }, - "nomnom": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.8.1.tgz", - "integrity": "sha1-IVH3Ikcrp55Qp2/BJbuMjy5Nwqc=", - "dev": true, - "requires": { - "chalk": "~0.4.0", - "underscore": "~1.6.0" - } + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true }, - "recast": { - "version": "0.12.9", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.12.9.tgz", - "integrity": "sha512-y7ANxCWmMW8xLOaiopiRDlyjQ9ajKRENBH+2wjntIbk3A6ZR1+BLQttkmSHMY7Arl+AAZFwJ10grg2T6f1WI8A==", + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", "dev": true, "requires": { - "ast-types": "0.10.1", - "core-js": "^2.4.1", - "esprima": "~4.0.0", - "private": "~0.1.5", - "source-map": "~0.6.1" + "invert-kv": "^2.0.0" } }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "strip-ansi": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz", - "integrity": "sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE=", - "dev": true - }, - "underscore": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", - "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=", - "dev": true - }, - "write-file-atomic": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.3.4.tgz", - "integrity": "sha1-+Aek8LHZ6ROuekgRLmzDrxmRtF8=", + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "slide": "^1.1.5" + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" } - } - } - }, - "webpack-bundle-analyzer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.0.2.tgz", - "integrity": "sha512-cZG4wSQtKrSpk5RJ33dxiaAyo8bP0V+JvycAyIDFEiDIhw4LHhhVKhn40YT1w6TR9E4scHA00LnIoBtTA13Mow==", - "dev": true, - "requires": { - "acorn": "^5.7.3", - "bfj": "^6.1.1", - "chalk": "^2.4.1", - "commander": "^2.18.0", - "ejs": "^2.6.1", - "express": "^4.16.3", - "filesize": "^3.6.1", - "gzip-size": "^5.0.0", - "lodash": "^4.17.10", - "mkdirp": "^0.5.1", - "opener": "^1.5.1", - "ws": "^6.0.0" - }, - "dependencies": { - "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", - "dev": true }, - "commander": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.18.0.tgz", - "integrity": "sha512-6CYPa+JP2ftfRU2qkDK+UTVeQYosOg/2GbcjIcKPHfinyOLPVGXu/ovN86RP49Re5ndJK1N0kuiidFFuepc4ZQ==", - "dev": true - } - } - }, - "webpack-cli": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-2.1.3.tgz", - "integrity": "sha512-5AsKoL/Ccn8iTrwk3uErdyhetGH+c7VRQ7Itim2GL0IhBRq5rtojVDk00buMRmFmBpw1RvHXq97Gup965LbozA==", - "dev": true, - "requires": { - "chalk": "^2.3.2", - "cross-spawn": "^6.0.5", - "diff": "^3.5.0", - "enhanced-resolve": "^4.0.0", - "envinfo": "^4.4.2", - "glob-all": "^3.1.0", - "global-modules": "^1.0.0", - "got": "^8.2.0", - "import-local": "^1.0.0", - "inquirer": "^5.1.0", - "interpret": "^1.0.4", - "jscodeshift": "^0.5.0", - "listr": "^0.13.0", - "loader-utils": "^1.1.0", - "lodash": "^4.17.5", - "log-symbols": "^2.2.0", - "mkdirp": "^0.5.1", - "p-each-series": "^1.0.0", - "p-lazy": "^1.0.0", - "prettier": "^1.5.3", - "supports-color": "^5.3.0", - "v8-compile-cache": "^1.1.2", - "webpack-addons": "^1.1.5", - "yargs": "^11.1.0", - "yeoman-environment": "^2.0.0", - "yeoman-generator": "^2.0.4" - }, - "dependencies": { - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "mem": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.0.0.tgz", + "integrity": "sha512-WQxG/5xYc3tMbYLXoXPm81ET2WDULiU5FxbuIoNbJqLOOI8zehXFdZuiUEgfdrU2mVB1pxBZUGlYORSrpuJreA==", "dev": true, "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^1.0.0", + "p-is-promise": "^1.1.0" } }, - "got": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/got/-/got-8.3.2.tgz", - "integrity": "sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw==", + "os-locale": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.0.1.tgz", + "integrity": "sha512-7g5e7dmXPtzcP4bgsZ8ixDVqA7oWYuEz4lOSujeWyliPai4gfVDiFIcwBg3aGCPnmSGfzOKTK3ccPn0CKv3DBw==", "dev": true, "requires": { - "@sindresorhus/is": "^0.7.0", - "cacheable-request": "^2.1.1", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "into-stream": "^3.1.0", - "is-retry-allowed": "^1.1.0", - "isurl": "^1.0.0-alpha5", - "lowercase-keys": "^1.0.0", - "mimic-response": "^1.0.0", - "p-cancelable": "^0.4.0", - "p-timeout": "^2.0.1", - "pify": "^3.0.0", - "safe-buffer": "^5.1.1", - "timed-out": "^4.0.1", - "url-parse-lax": "^3.0.0", - "url-to-options": "^1.0.1" + "execa": "^0.10.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" } }, - "inquirer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-5.2.0.tgz", - "integrity": "sha512-E9BmnJbAKLPGonz0HeWHtbKf+EeSP93paWO3ZYoUpq/aowXvYGjjCSuashhXPpzbArIjBbji39THkxTz9ZeEUQ==", + "p-limit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", + "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", "dev": true, "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.0", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^2.1.0", - "figures": "^2.0.0", - "lodash": "^4.3.0", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rxjs": "^5.5.2", - "string-width": "^2.1.0", - "strip-ansi": "^4.0.0", - "through": "^2.3.6" + "p-try": "^2.0.0" } }, - "pify": { + "p-locate": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } }, - "prepend-http": { + "p-try": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", "dev": true }, - "url-parse-lax": { + "pkg-dir": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "prepend-http": "^2.0.0" + "has-flag": "^3.0.0" } }, + "v8-compile-cache": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.2.tgz", + "integrity": "sha512-1wFuMUIM16MDJRCrpbpuEPTUGmM5QMUg0cr3KFwra2XgOgFcPGDQHDh3CszSCD2Zewc/dh/pamNEW8CbfDebUw==", + "dev": true + }, "yargs": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz", - "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==", + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", "dev": true, "requires": { "cliui": "^4.0.0", - "decamelize": "^1.1.1", - "find-up": "^2.1.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", "get-caller-file": "^1.0.1", - "os-locale": "^2.0.0", + "os-locale": "^3.0.0", "require-directory": "^2.1.1", "require-main-filename": "^1.0.1", "set-blocking": "^2.0.0", "string-width": "^2.0.0", "which-module": "^2.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^9.0.2" + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" } }, "yargs-parser": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", - "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", "dev": true, "requires": { - "camelcase": "^4.1.0" + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" } } } @@ -23728,201 +21557,6 @@ "requires": { "fd-slicer": "~1.0.1" } - }, - "yeoman-environment": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/yeoman-environment/-/yeoman-environment-2.3.0.tgz", - "integrity": "sha512-PHSAkVOqYdcR+C+Uht1SGC4eVD/9OhygYFkYaI66xF8vKIeS1RNYay+umj2ZrQeJ50tF5Q/RSO6qGDz9y3Ifug==", - "dev": true, - "requires": { - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", - "debug": "^3.1.0", - "diff": "^3.3.1", - "escape-string-regexp": "^1.0.2", - "globby": "^8.0.1", - "grouped-queue": "^0.3.3", - "inquirer": "^5.2.0", - "is-scoped": "^1.0.0", - "lodash": "^4.17.10", - "log-symbols": "^2.1.0", - "mem-fs": "^1.1.0", - "strip-ansi": "^4.0.0", - "text-table": "^0.2.0", - "untildify": "^3.0.2" - }, - "dependencies": { - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "globby": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.1.tgz", - "integrity": "sha512-oMrYrJERnKBLXNLVTqhm3vPEdJ/b2ZE28xN4YARiix1NOIOBPEpOUnm844K1iu/BkphCaf2WNFwMszv8Soi1pw==", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "dir-glob": "^2.0.0", - "fast-glob": "^2.0.2", - "glob": "^7.1.2", - "ignore": "^3.3.5", - "pify": "^3.0.0", - "slash": "^1.0.0" - } - }, - "inquirer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-5.2.0.tgz", - "integrity": "sha512-E9BmnJbAKLPGonz0HeWHtbKf+EeSP93paWO3ZYoUpq/aowXvYGjjCSuashhXPpzbArIjBbji39THkxTz9ZeEUQ==", - "dev": true, - "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.0", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^2.1.0", - "figures": "^2.0.0", - "lodash": "^4.3.0", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rxjs": "^5.5.2", - "string-width": "^2.1.0", - "strip-ansi": "^4.0.0", - "through": "^2.3.6" - } - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } - } - }, - "yeoman-generator": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/yeoman-generator/-/yeoman-generator-2.0.5.tgz", - "integrity": "sha512-rV6tJ8oYzm4mmdF2T3wjY+Q42jKF2YiiD0VKfJ8/0ZYwmhCKC9Xs2346HVLPj/xE13i68psnFJv7iS6gWRkeAg==", - "dev": true, - "requires": { - "async": "^2.6.0", - "chalk": "^2.3.0", - "cli-table": "^0.3.1", - "cross-spawn": "^6.0.5", - "dargs": "^5.1.0", - "dateformat": "^3.0.3", - "debug": "^3.1.0", - "detect-conflict": "^1.0.0", - "error": "^7.0.2", - "find-up": "^2.1.0", - "github-username": "^4.0.0", - "istextorbinary": "^2.2.1", - "lodash": "^4.17.10", - "make-dir": "^1.1.0", - "mem-fs-editor": "^4.0.0", - "minimist": "^1.2.0", - "pretty-bytes": "^4.0.2", - "read-chunk": "^2.1.0", - "read-pkg-up": "^3.0.0", - "rimraf": "^2.6.2", - "run-async": "^2.0.0", - "shelljs": "^0.8.0", - "text-table": "^0.2.0", - "through2": "^2.0.0", - "yeoman-environment": "^2.0.5" - }, - "dependencies": { - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "dargs": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/dargs/-/dargs-5.1.0.tgz", - "integrity": "sha1-7H6lDHhWTNNsnV7Bj2Yyn63ieCk=", - "dev": true - }, - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dev": true, - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - }, - "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "dev": true, - "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - } - }, - "read-pkg-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", - "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", - "dev": true, - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^3.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - } - } } } } diff --git a/package.json b/package.json index 4a75bd538b9a6..60146d1f6f48e 100644 --- a/package.json +++ b/package.json @@ -112,9 +112,7 @@ "source-map-loader": "0.2.3", "stylelint-config-wordpress": "13.1.0", "uuid": "3.3.2", - "webpack": "4.8.3", "webpack-bundle-analyzer": "3.0.2", - "webpack-cli": "2.1.3", "webpack-livereload-plugin": "2.1.1", "webpack-rtl-plugin": "github:yoavf/webpack-rtl-plugin#develop" }, @@ -150,13 +148,13 @@ "clean:packages": "rimraf ./packages/*/build ./packages/*/build-module ./packages/*/build-style", "prebuild:packages": "npm run clean:packages && lerna run build && cross-env INCLUDE_PACKAGES=babel-plugin-import-jsx-pragma,postcss-themes,jest-console SKIP_JSX_PRAGMA_TRANSFORM=1 node ./bin/packages/build.js", "build:packages": "cross-env EXCLUDE_PACKAGES=babel-plugin-import-jsx-pragma,jest-console,postcss-themes node ./bin/packages/build.js", - "build": "npm run build:packages && cross-env NODE_ENV=production webpack", + "build": "npm run build:packages && wp-scripts build", "check-engines": "wp-scripts check-engines", "check-licenses": "concurrently \"wp-scripts check-licenses --prod --gpl2\" \"wp-scripts check-licenses --dev\"", "precheck-local-changes": "npm run docs:build", "check-local-changes": "( git diff -U0 | xargs -0 node bin/process-git-diff ) || ( echo \"There are local uncommitted changes after one or both of 'npm install' or 'npm run docs:build'!\" && exit 1 );", "predev": "npm run check-engines", - "dev": "npm run build:packages && concurrently \"cross-env webpack --watch\" \"npm run dev:packages\"", + "dev": "npm run build:packages && concurrently \"wp-scripts start\" \"npm run dev:packages\"", "dev:packages": "node ./bin/packages/watch.js", "docs:build": "node docs/tool", "fixtures:clean": "rimraf \"test/integration/full-content/fixtures/*.+(json|serialized.html)\"", diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index 406db778448f6..40210edd4b139 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -1,3 +1,10 @@ +## 2.6.0 (Unreleased) + +### New Features + +- Added support for `build` script ([#12837](https://github.com/WordPress/gutenberg/pull/12837)) +- Added support for `start` script ([#12837](https://github.com/WordPress/gutenberg/pull/12837)) + ## 2.5.0 (2019-01-09) ### New Features diff --git a/packages/scripts/README.md b/packages/scripts/README.md index 063d95184e5cb..cc3f3b2b04a7c 100644 --- a/packages/scripts/README.md +++ b/packages/scripts/README.md @@ -36,9 +36,27 @@ _Example:_ ## Available Scripts +### `build` + +Builds the code to the designated `build` folder in the configuration file. It correctly bundles code in production mode and optimizes the build for the best performance. Your code is ready to be deployed. It uses [Webpack](https://webpack.js.org/) behind the scenes and you still need to provide your own config as described in the [documentation](https://webpack.js.org/concepts/configuration/). + +_Example:_ + +```json +{ + "scripts": { + "build": "wp-scripts build" + } +} +``` + +This is how you execute the script with presented setup: +* `npm run build` - builds the code for production. + + ### `check-engines` -Check if the current `node`, `npm` (or `yarn`) versions match the given [semantic version](https://semver.org/) ranges. If the given version is not satisfied, information about installing the needed version is printed and the program exits with an error code. It uses [check-node-version](https://www.npmjs.com/package/check-node-version) behind the scenes with the recommended configuration provided. You can specify your own ranges as described in [check-node-version docs](https://www.npmjs.com/package/check-node-version). +Checks if the current `node`, `npm` (or `yarn`) versions match the given [semantic version](https://semver.org/) ranges. If the given version is not satisfied, information about installing the needed version is printed and the program exits with an error code. It uses [check-node-version](https://www.npmjs.com/package/check-node-version) behind the scenes with the recommended configuration provided. You can specify your own ranges as described in [check-node-version docs](https://www.npmjs.com/package/check-node-version). _Example:_ @@ -125,6 +143,23 @@ _Example:_ This is how you execute the script with presented setup: * `npm run lint:css` - lints CSS files in the whole project's directory. +### `start` + +Builds the code for development to the designated `build` folder in the configuration file. The script will automatically rebuild if you make changes to the code. You will see the build errors in the console. It uses [Webpack](https://webpack.js.org/) behind the scenes and you still need to provide your own config as described in the [documentation](https://webpack.js.org/concepts/configuration/). + +_Example:_ + +```json +{ + "scripts": { + "start": "wp-scripts start" + } +} +``` + +This is how you execute the script with presented setup: +* `npm start` - starts the build for development. + ### `test-e2e` Launches the End-To-End (E2E) test runner. It uses [Jest](https://jestjs.io/) behind the scenes and you are able to utilize all of its [CLI options](https://jestjs.io/docs/en/cli.html). You can also run `./node_modules/.bin/wp-scripts test:e2e --help` or `npm run test:e2e:help` (as presented below) to view all of the available options. diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 31441fc5fdd49..86d4211b874d5 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -46,7 +46,9 @@ "read-pkg-up": "^1.0.1", "resolve-bin": "^0.4.0", "stylelint": "^9.10.1", - "stylelint-config-wordpress": "^13.1.0" + "stylelint-config-wordpress": "^13.1.0", + "webpack": "4.8.3", + "webpack-cli": "^3.1.2" }, "publishConfig": { "access": "public" diff --git a/packages/scripts/scripts/build.js b/packages/scripts/scripts/build.js new file mode 100644 index 0000000000000..9470274f76633 --- /dev/null +++ b/packages/scripts/scripts/build.js @@ -0,0 +1,35 @@ +/** + * External dependencies + */ +const { sync: spawn } = require( 'cross-spawn' ); +const { sync: resolveBin } = require( 'resolve-bin' ); + +/** + * Internal dependencies + */ +const { + getCliArgs, + hasCliArg, + hasProjectFile, +} = require( '../utils' ); + +const hasWebpackConfig = hasCliArg( '--config' ) || + hasProjectFile( 'webpack.config.js' ) || + hasProjectFile( 'webpack.config.babel.js' ); + +if ( hasWebpackConfig ) { + // Sets environment to production. + process.env.NODE_ENV = 'production'; + + const { status } = spawn( + resolveBin( 'webpack' ), + getCliArgs(), + { stdio: 'inherit' } + ); + process.exit( status ); +} else { + // eslint-disable-next-line no-console + console.log( 'Webpack config file is missing.' ); + process.exit( 1 ); +} + diff --git a/packages/scripts/scripts/start.js b/packages/scripts/scripts/start.js new file mode 100644 index 0000000000000..db42c33ba447f --- /dev/null +++ b/packages/scripts/scripts/start.js @@ -0,0 +1,31 @@ +/** + * External dependencies + */ +const { sync: spawn } = require( 'cross-spawn' ); +const { sync: resolveBin } = require( 'resolve-bin' ); + +/** + * Internal dependencies + */ +const { + getCliArgs, + hasCliArg, + hasProjectFile, +} = require( '../utils' ); + +const hasWebpackConfig = hasCliArg( '--config' ) || + hasProjectFile( 'webpack.config.js' ) || + hasProjectFile( 'webpack.config.babel.js' ); + +if ( hasWebpackConfig ) { + const { status } = spawn( + resolveBin( 'webpack' ), + [ '--watch', ...getCliArgs() ], + { stdio: 'inherit' } + ); + process.exit( status ); +} else { + // eslint-disable-next-line no-console + console.log( 'Webpack config file is missing.' ); + process.exit( 1 ); +} From 2b303c4430fb349d60d371e3f6042c23a6b4caba Mon Sep 17 00:00:00 2001 From: Paul Sealock <psealock@gmail.com> Date: Wed, 23 Jan 2019 21:48:46 +1300 Subject: [PATCH 200/691] Dropdown: add focusOnMount prop to pass onto Popover component (#12855) --- packages/components/CHANGELOG.md | 4 ++++ packages/components/src/dropdown/README.md | 10 ++++++++++ packages/components/src/dropdown/index.js | 2 ++ 3 files changed, 16 insertions(+) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index ad2e5be8e7121..1ec2b397bcea6 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -9,6 +9,10 @@ - Resolves a conflict where two instance of Slot would produce an inconsistent or duplicated rendering output. +### New Feature + +- `Dropdown` now has a `focusOnMount` prop which is passed directly to the contained `Popover`. + ## 7.0.5 (2019-01-03) ## 7.0.4 (2018-12-12) diff --git a/packages/components/src/dropdown/README.md b/packages/components/src/dropdown/README.md index 552fb2741776e..0e09a6651dcd9 100644 --- a/packages/components/src/dropdown/README.md +++ b/packages/components/src/dropdown/README.md @@ -90,3 +90,13 @@ Opt-in prop to show popovers fullscreen on mobile, pass `false` in this prop to - Type: `String` - Required: No + + ### focusOnMount + + By default, the *first tabblable element* in the popover will receive focus when it mounts. This is the same as setting `focusOnMount` to `"firstElement"`. If you want to focus the container instead, you can set `focusOnMount` to `"container"`. + + Set this prop to `false` to disable focus changing entirely. This should only be set when an appropriately accessible substitute behavior exists. + + - Type: `String` or `Boolean` + - Required: No + - Default: `"firstElement"` diff --git a/packages/components/src/dropdown/index.js b/packages/components/src/dropdown/index.js index 81ebe65da6534..b4d9082af3cdc 100644 --- a/packages/components/src/dropdown/index.js +++ b/packages/components/src/dropdown/index.js @@ -73,6 +73,7 @@ class Dropdown extends Component { contentClassName, expandOnMobile, headerTitle, + focusOnMount, } = this.props; const args = { isOpen, onToggle: this.toggle, onClose: this.close }; @@ -88,6 +89,7 @@ class Dropdown extends Component { onClickOutside={ this.closeIfClickOutside } expandOnMobile={ expandOnMobile } headerTitle={ headerTitle } + focusOnMount={ focusOnMount } > { renderContent( args ) } </Popover> From 5ff463a9a59e217a8de10eb88c140a91a3366947 Mon Sep 17 00:00:00 2001 From: Paul Sealock <psealock@gmail.com> Date: Wed, 23 Jan 2019 21:49:54 +1300 Subject: [PATCH 201/691] DateTimePicker: fix prop warning for (#12933) --- packages/components/src/date-time/date.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/components/src/date-time/date.js b/packages/components/src/date-time/date.js index 0854846b75244..7da1bf4f41823 100644 --- a/packages/components/src/date-time/date.js +++ b/packages/components/src/date-time/date.js @@ -43,7 +43,6 @@ class DatePicker extends Component { return ( <div className="components-datetime__date"> <DayPickerSingleDateController - block date={ momentDate } daySize={ 30 } focused From 5959a2c303ddaf834276bbac69e81d5beb136565 Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.p.richards@gmail.com> Date: Wed, 23 Jan 2019 17:22:49 +0800 Subject: [PATCH 202/691] Update plugin version to 4.9.0 (#13436) --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 794558c4dd70c..09c0b5d67eebc 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,7 +3,7 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the new block editor in core. - * Version: 4.9.0-rc.1 + * Version: 4.9.0 * Author: Gutenberg Team * * @package gutenberg diff --git a/package-lock.json b/package-lock.json index 66e4036ba60bb..307215cb6a5b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "4.9.0-rc.1", + "version": "4.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 60146d1f6f48e..ca66510ec46b6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "4.9.0-rc.1", + "version": "4.9.0", "private": true, "description": "A new WordPress editor experience", "repository": "git+https://github.com/WordPress/gutenberg.git", From 2da2a2c794c502774dff18bf39ce1ee248efd338 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell@kjellr.com> Date: Wed, 23 Jan 2019 04:49:32 -0500 Subject: [PATCH 203/691] Consider making Fullscreen Mode effects visible only on larger screens (#13425) * Hide the fullscreen "exit" icon on small screens When fullscreen mode is active, we place an exit/back button in the upper left corner of the screen to give users a way to exit the editor. This button persists on small screens, even though Fullscreen Mode cannot be toggled at that screen size. This commit hides the exit/back icon at small screen sizes. * Migrate more fullscreen rules to medium+ breakpoints only. * Update style.scss --- .../src/components/fullscreen-mode/style.scss | 45 ++++++++++--------- .../header/fullscreen-mode-close/style.scss | 17 ++++--- .../src/components/header/style.scss | 4 -- .../src/components/layout/style.scss | 4 -- .../src/components/sidebar/style.scss | 18 +++----- .../src/components/text-editor/style.scss | 4 -- packages/edit-post/src/style.scss | 4 -- 7 files changed, 43 insertions(+), 53 deletions(-) diff --git a/packages/edit-post/src/components/fullscreen-mode/style.scss b/packages/edit-post/src/components/fullscreen-mode/style.scss index 8e5b6e2218561..0d044161b9e53 100644 --- a/packages/edit-post/src/components/fullscreen-mode/style.scss +++ b/packages/edit-post/src/components/fullscreen-mode/style.scss @@ -1,29 +1,32 @@ body.js.is-fullscreen-mode { - // Reset the html.wp-topbar padding - // Because this uses negative margins, we have to compensate for the height. - margin-top: -$admin-bar-height-big; - height: calc(100% + #{ $admin-bar-height-big }); - @include break-medium() { - margin-top: -$admin-bar-height; - height: calc(100% + #{ $admin-bar-height }); - } - #adminmenumain, - #wpadminbar { - display: none; - } + @include break-medium { + // Reset the html.wp-topbar padding. + // Because this uses negative margins, we have to compensate for the height. + margin-top: -$admin-bar-height-big; + height: calc(100% + #{ $admin-bar-height-big }); + @include break-medium() { + margin-top: -$admin-bar-height; + height: calc(100% + #{ $admin-bar-height }); + } - #wpcontent, - #wpfooter { - margin-left: 0; - } + #adminmenumain, + #wpadminbar { + display: none; + } + + #wpcontent, + #wpfooter { + margin-left: 0; + } - // Animations - @include edit-post__fade-in-animation(0.3s); + // Animations. + @include edit-post__fade-in-animation(0.3s); - .edit-post-header { - transform: translateY(-100%); - animation: edit-post-fullscreen-mode__slide-in-animation 0.1s forwards; + .edit-post-header { + transform: translateY(-100%); + animation: edit-post-fullscreen-mode__slide-in-animation 0.1s forwards; + } } } diff --git a/packages/edit-post/src/components/header/fullscreen-mode-close/style.scss b/packages/edit-post/src/components/header/fullscreen-mode-close/style.scss index e6a3c7285b886..26d43a35d7458 100644 --- a/packages/edit-post/src/components/header/fullscreen-mode-close/style.scss +++ b/packages/edit-post/src/components/header/fullscreen-mode-close/style.scss @@ -1,7 +1,14 @@ .edit-post-fullscreen-mode-close__toolbar { - border-top: 0; - border-bottom: 0; - border-left: 0; - margin: -9px 10px -9px -10px; - padding: 9px 10px; + // Do not show the toolbar icon on small screens, + // when Fullscreen Mode is not an option in the "More" menu. + display: none; + + @include break-medium() { + display: block; + border-top: 0; + border-bottom: 0; + border-left: 0; + margin: -9px 10px -9px -10px; + padding: 9px 10px; + } } diff --git a/packages/edit-post/src/components/header/style.scss b/packages/edit-post/src/components/header/style.scss index 987dcbfd5e14c..6676d35676b7d 100644 --- a/packages/edit-post/src/components/header/style.scss +++ b/packages/edit-post/src/components/header/style.scss @@ -20,10 +20,6 @@ position: fixed; padding: $grid-size; top: $admin-bar-height-big; - - body.is-fullscreen-mode & { - top: 0; - } } @include break-medium() { diff --git a/packages/edit-post/src/components/layout/style.scss b/packages/edit-post/src/components/layout/style.scss index 3aa896af7b377..963862b7105d7 100644 --- a/packages/edit-post/src/components/layout/style.scss +++ b/packages/edit-post/src/components/layout/style.scss @@ -167,10 +167,6 @@ left: 0; overflow: auto; - body.is-fullscreen-mode & { - top: 0; - } - @include break-medium() { top: $admin-bar-height; left: auto; diff --git a/packages/edit-post/src/components/sidebar/style.scss b/packages/edit-post/src/components/sidebar/style.scss index c33aee329d706..cd8256823d4e7 100644 --- a/packages/edit-post/src/components/sidebar/style.scss +++ b/packages/edit-post/src/components/sidebar/style.scss @@ -17,10 +17,6 @@ height: auto; overflow: auto; -webkit-overflow-scrolling: touch; - - body.is-fullscreen-mode & { - top: $header-height; - } } @include break-medium() { @@ -41,18 +37,18 @@ margin-top: -1px; margin-bottom: -1px; - body.is-fullscreen-mode & { - max-height: calc(100vh - #{ $panel-header-height }); - @include break-small() { - max-height: none; - } - } - @include break-small() { overflow: inherit; height: auto; max-height: none; } + + @include break-medium() { + + body.is-fullscreen-mode & { + max-height: calc(100vh - #{ $panel-header-height }); + } + } } > .components-panel .components-panel__header { diff --git a/packages/edit-post/src/components/text-editor/style.scss b/packages/edit-post/src/components/text-editor/style.scss index b04479300535f..1a5980bc5e022 100644 --- a/packages/edit-post/src/components/text-editor/style.scss +++ b/packages/edit-post/src/components/text-editor/style.scss @@ -3,10 +3,6 @@ @include break-small() { padding-top: ($grid-size * 5) + $admin-bar-height-big; - - body.is-fullscreen-mode & { - padding-top: $grid-size * 5; - } } @include break-medium() { diff --git a/packages/edit-post/src/style.scss b/packages/edit-post/src/style.scss index 44ad95c335165..90fac100e15ee 100644 --- a/packages/edit-post/src/style.scss +++ b/packages/edit-post/src/style.scss @@ -120,10 +120,6 @@ body.block-editor-page { bottom: 0; left: 0; min-height: calc(100vh - #{ $admin-bar-height-big }); - - body.is-fullscreen-mode & { - min-height: 100vh; - } } // The WP header height changes at this breakpoint From 22abc9ed0166635eba0e08da1f088dc0c09645cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Wrede?= <soerenwrede@gmail.com> Date: Wed, 23 Jan 2019 14:13:18 +0100 Subject: [PATCH 204/691] Fix: File block add custom class (#13432) ## Description The custom class should only be added to the wrapper, not every div Fixes #13430 --- packages/block-library/src/file/edit.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/block-library/src/file/edit.js b/packages/block-library/src/file/edit.js index eb707a104eec5..002bd43cf3617 100644 --- a/packages/block-library/src/file/edit.js +++ b/packages/block-library/src/file/edit.js @@ -184,9 +184,9 @@ class FileEdit extends Component { </MediaUploadCheck> </BlockControls> <div className={ classes }> - <div className={ `${ className }__content-wrapper` }> + <div className={ 'wp-block-file__content-wrapper' }> <RichText - wrapperClassName={ `${ className }__textlink` } + wrapperClassName={ 'wp-block-file__textlink' } tagName="div" // must be block-level or else cursor disappears value={ fileName } placeholder={ __( 'Write file name…' ) } @@ -195,11 +195,11 @@ class FileEdit extends Component { onChange={ ( text ) => setAttributes( { fileName: text } ) } /> { showDownloadButton && - <div className={ `${ className }__button-richtext-wrapper` }> + <div className={ 'wp-block-file__button-richtext-wrapper' }> { /* Using RichText here instead of PlainText so that it can be styled like a button */ } <RichText tagName="div" // must be block-level or else cursor disappears - className={ `${ className }__button` } + className={ 'wp-block-file__button' } value={ downloadButtonText } formattingControls={ [] } // disable controls placeholder={ __( 'Add text…' ) } @@ -213,7 +213,7 @@ class FileEdit extends Component { <ClipboardButton isDefault text={ href } - className={ `${ className }__copy-url-button` } + className={ 'wp-block-file__copy-url-button' } onCopy={ this.confirmCopyURL } onFinishCopy={ this.resetCopyConfirmation } disabled={ isBlobURL( href ) } From c501353388eb7e0f6a30f6ee491b7ea73e688031 Mon Sep 17 00:00:00 2001 From: Darren Ethier <darren@roughsmootheng.in> Date: Wed, 23 Jan 2019 08:52:47 -0500 Subject: [PATCH 205/691] Improve castError handling of non strings (#13315) This pull removes castError from reduxRoutine and just passes along the value thrown instead of potentially coercing to an error. - Exposes values to consuming code. - Less opinionated about what happens with the error. --- packages/redux-routine/CHANGELOG.md | 4 +++- packages/redux-routine/src/cast-error.js | 14 -------------- packages/redux-routine/src/runtime.js | 6 +----- packages/redux-routine/src/test/cast-error.js | 18 ------------------ packages/redux-routine/src/test/index.js | 2 +- 5 files changed, 5 insertions(+), 39 deletions(-) delete mode 100644 packages/redux-routine/src/cast-error.js delete mode 100644 packages/redux-routine/src/test/cast-error.js diff --git a/packages/redux-routine/CHANGELOG.md b/packages/redux-routine/CHANGELOG.md index b820720f392bc..dbddb8321016b 100644 --- a/packages/redux-routine/CHANGELOG.md +++ b/packages/redux-routine/CHANGELOG.md @@ -2,7 +2,9 @@ ### Bug Fixes -- Fix unhandled promise rejection error caused by returning null from registered generator ([#13314](https://github.com/WordPress/gutenberg/pull/13314) +- Fix unhandled promise rejection error caused by returning null from registered generator ([#13314](https://github.com/WordPress/gutenberg/pull/13314)) +- The middleware will no longer attempt to coerce an error to an instance of `Error`, and instead passes through the thrown value directly. This resolves issues where an `Error` would be thrown when the underlying values were not of type `Error` or `string` (e.g. a thrown object) and the message would end up not being useful (e.g. `[Object object]`). +([#13315](https://github.com/WordPress/gutenberg/pull/13315)) ## 3.0.3 (2018-10-19) diff --git a/packages/redux-routine/src/cast-error.js b/packages/redux-routine/src/cast-error.js deleted file mode 100644 index 9bc2e22a46d40..0000000000000 --- a/packages/redux-routine/src/cast-error.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Casts value as an error if it's not one. - * - * @param {*} error The value to cast. - * - * @return {Error} The cast error. - */ -export default function castError( error ) { - if ( ! ( error instanceof Error ) ) { - error = new Error( error ); - } - - return error; -} diff --git a/packages/redux-routine/src/runtime.js b/packages/redux-routine/src/runtime.js index 484d65ac6c035..d3de265c3f2b1 100644 --- a/packages/redux-routine/src/runtime.js +++ b/packages/redux-routine/src/runtime.js @@ -8,7 +8,6 @@ import isPromise from 'is-promise'; /** * Internal dependencies */ -import castError from './cast-error'; import { isActionOfType, isAction } from './is-action'; /** @@ -27,10 +26,7 @@ export default function createRuntime( controls = {}, dispatch ) { const routine = control( value ); if ( isPromise( routine ) ) { // Async control routine awaits resolution. - routine.then( - yieldNext, - ( error ) => yieldError( castError( error ) ), - ); + routine.then( yieldNext, yieldError ); } else { next( routine ); } diff --git a/packages/redux-routine/src/test/cast-error.js b/packages/redux-routine/src/test/cast-error.js deleted file mode 100644 index bdca1c7c202b9..0000000000000 --- a/packages/redux-routine/src/test/cast-error.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Internal dependencies - */ -import castError from '../cast-error'; - -describe( 'castError', () => { - it( 'should return error verbatim', () => { - const error = new Error( 'Foo' ); - - expect( castError( error ) ).toBe( error ); - } ); - - it( 'should return string as message of error', () => { - const error = 'Foo'; - - expect( castError( error ) ).toEqual( new Error( 'Foo' ) ); - } ); -} ); diff --git a/packages/redux-routine/src/test/index.js b/packages/redux-routine/src/test/index.js index 98aedf7bbef47..79a35673f70a5 100644 --- a/packages/redux-routine/src/test/index.js +++ b/packages/redux-routine/src/test/index.js @@ -61,7 +61,7 @@ describe( 'createMiddleware', () => { try { yield { type: 'WAIT_FAIL' }; } catch ( error ) { - expect( error.message ).toBe( 'Message' ); + expect( error ).toBe( 'Message' ); } } From 148e816ed15814a08fb99ed8d329fc3ebbe72dfb Mon Sep 17 00:00:00 2001 From: Pinar Olguc <pinarolguc@gmail.com> Date: Wed, 23 Jan 2019 17:13:28 +0300 Subject: [PATCH 206/691] [Mobile] Improve keyboard hide button (#13415) * Allow passing style to toolbar container * Replace right border with left border * Add keyboard-hide icon * Fix lint issue --- packages/components/src/dashicon/index.js | 3 +++ packages/components/src/toolbar/index.js | 4 ++-- packages/components/src/toolbar/style.native.scss | 4 ++-- packages/components/src/toolbar/toolbar-container.native.js | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/components/src/dashicon/index.js b/packages/components/src/dashicon/index.js index b58f77bfe371a..943282bc7d3e0 100644 --- a/packages/components/src/dashicon/index.js +++ b/packages/components/src/dashicon/index.js @@ -568,6 +568,9 @@ export default class Dashicon extends Component { case 'instagram': path = 'M12.67 10A2.67 2.67 0 1 0 10 12.67 2.68 2.68 0 0 0 12.67 10zm1.43 0A4.1 4.1 0 1 1 10 5.9a4.09 4.09 0 0 1 4.1 4.1zm1.13-4.27a1 1 0 1 1-1-1 1 1 0 0 1 1 1zM10 3.44c-1.17 0-3.67-.1-4.72.32a2.67 2.67 0 0 0-1.52 1.52c-.42 1-.32 3.55-.32 4.72s-.1 3.67.32 4.72a2.74 2.74 0 0 0 1.52 1.52c1 .42 3.55.32 4.72.32s3.67.1 4.72-.32a2.83 2.83 0 0 0 1.52-1.52c.42-1.05.32-3.55.32-4.72s.1-3.67-.32-4.72a2.74 2.74 0 0 0-1.52-1.52c-1.05-.42-3.55-.32-4.72-.32zM18 10c0 1.1 0 2.2-.05 3.3a4.84 4.84 0 0 1-1.29 3.36A4.8 4.8 0 0 1 13.3 18H6.7a4.84 4.84 0 0 1-3.36-1.29 4.84 4.84 0 0 1-1.29-3.41C2 12.2 2 11.1 2 10V6.7a4.84 4.84 0 0 1 1.34-3.36A4.8 4.8 0 0 1 6.7 2.05C7.8 2 8.9 2 10 2h3.3a4.84 4.84 0 0 1 3.36 1.29A4.8 4.8 0 0 1 18 6.7V10z'; break; + case 'keyboard-hide': + path = 'M18,0 L2,0 C0.9,0 0.01,0.9 0.01,2 L0,12 C0,13.1 0.9,14 2,14 L18,14 C19.1,14 20,13.1 20,12 L20,2 C20,0.9 19.1,0 18,0 Z M18,12 L2,12 L2,2 L18,2 L18,12 Z M9,3 L11,3 L11,5 L9,5 L9,3 Z M9,6 L11,6 L11,8 L9,8 L9,6 Z M6,3 L8,3 L8,5 L6,5 L6,3 Z M6,6 L8,6 L8,8 L6,8 L6,6 Z M3,6 L5,6 L5,8 L3,8 L3,6 Z M3,3 L5,3 L5,5 L3,5 L3,3 Z M6,9 L14,9 L14,11 L6,11 L6,9 Z M12,6 L14,6 L14,8 L12,8 L12,6 Z M12,3 L14,3 L14,5 L12,5 L12,3 Z M15,6 L17,6 L17,8 L15,8 L15,6 Z M15,3 L17,3 L17,5 L15,5 L15,3 Z M10,20 L14,16 L6,16 L10,20 Z'; + break; case 'laptop': path = 'M3 3h14c.6 0 1 .4 1 1v10c0 .6-.4 1-1 1H3c-.6 0-1-.4-1-1V4c0-.6.4-1 1-1zm13 2H4v8h12V5zm-3 1H5v4zm6 11v-1H1v1c0 .6.5 1 1.1 1h15.8c.6 0 1.1-.4 1.1-1z'; break; diff --git a/packages/components/src/toolbar/index.js b/packages/components/src/toolbar/index.js index 15fc1972b4497..64e4f8359919d 100644 --- a/packages/components/src/toolbar/index.js +++ b/packages/components/src/toolbar/index.js @@ -41,7 +41,7 @@ import ToolbarContainer from './toolbar-container'; * * @return {ReactElement} The rendered toolbar. */ -function Toolbar( { controls = [], children, className, isCollapsed, icon, label } ) { +function Toolbar( { controls = [], children, className, isCollapsed, icon, label, ...otherProps } ) { if ( ( ! controls || ! controls.length ) && ! children @@ -67,7 +67,7 @@ function Toolbar( { controls = [], children, className, isCollapsed, icon, label } return ( - <ToolbarContainer className={ classnames( 'components-toolbar', className ) }> + <ToolbarContainer className={ classnames( 'components-toolbar', className ) } { ...otherProps }> { flatMap( controlSets, ( controlSet, indexOfSet ) => ( controlSet.map( ( control, indexOfControl ) => ( <ToolbarButton diff --git a/packages/components/src/toolbar/style.native.scss b/packages/components/src/toolbar/style.native.scss index aca87d859b8bb..1e0d176e275d8 100644 --- a/packages/components/src/toolbar/style.native.scss +++ b/packages/components/src/toolbar/style.native.scss @@ -1,7 +1,7 @@ .container { flex-direction: row; - border-right-width: 1px; - border-right-color: #e9eff3; + border-left-width: 1px; + border-color: #e9eff3; padding-left: 5px; padding-right: 5px; } diff --git a/packages/components/src/toolbar/toolbar-container.native.js b/packages/components/src/toolbar/toolbar-container.native.js index dba6d5123c1ae..d147c547add6d 100644 --- a/packages/components/src/toolbar/toolbar-container.native.js +++ b/packages/components/src/toolbar/toolbar-container.native.js @@ -6,7 +6,7 @@ import { View } from 'react-native'; import styles from './style.scss'; const ToolbarContainer = ( props ) => ( - <View style={ styles.container }> + <View style={ [ styles.container, props.passedStyle ] }> { props.children } </View> ); From 60bd2a598b98179b2eb5ee183cd73006e99862e9 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Wed, 23 Jan 2019 10:43:49 -0500 Subject: [PATCH 207/691] Framework: Bump minimum required WP to 5.x (#13370) --- gutenberg.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 09c0b5d67eebc..655e883859fda 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -146,7 +146,7 @@ function is_gutenberg_page() { */ function gutenberg_wordpress_version_notice() { echo '<div class="error"><p>'; - _e( 'Gutenberg requires WordPress 4.9.8 or later to function properly. Please upgrade WordPress before activating Gutenberg.', 'gutenberg' ); + _e( 'Gutenberg requires WordPress 5.0.0 or later to function properly. Please upgrade WordPress before activating Gutenberg.', 'gutenberg' ); echo '</p></div>'; deactivate_plugins( array( 'gutenberg/gutenberg.php' ) ); @@ -180,7 +180,7 @@ function gutenberg_pre_init() { // Strip '-src' from the version string. Messes up version_compare(). $version = str_replace( '-src', '', $wp_version ); - if ( version_compare( $version, '4.9.8', '<' ) ) { + if ( version_compare( $version, '5.0.0', '<' ) ) { add_action( 'admin_notices', 'gutenberg_wordpress_version_notice' ); return; } From b9e76863669661b8a6cfdb7562e74cbf802f1dca Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Wed, 23 Jan 2019 11:05:47 -0500 Subject: [PATCH 208/691] Plugin: Remove 5.0-merged block registration functions, integrations (#13412) --- .../backward-compatibility/deprecations.md | 11 + lib/blocks.php | 165 ++------------ lib/class-wp-block-type-registry.php | 182 --------------- lib/class-wp-block-type.php | 209 ------------------ lib/load.php | 6 - phpunit/class-blocks-api-test.php | 29 +-- 6 files changed, 32 insertions(+), 570 deletions(-) delete mode 100644 lib/class-wp-block-type-registry.php delete mode 100644 lib/class-wp-block-type.php diff --git a/docs/designers-developers/developers/backward-compatibility/deprecations.md b/docs/designers-developers/developers/backward-compatibility/deprecations.md index cf98cdb9fe1dd..6db2ed322b6a5 100644 --- a/docs/designers-developers/developers/backward-compatibility/deprecations.md +++ b/docs/designers-developers/developers/backward-compatibility/deprecations.md @@ -2,6 +2,17 @@ The Gutenberg project's deprecation policy is intended to support backward compatibility for releases, when possible. The current deprecations are listed below and are grouped by _the version at which they will be removed completely_. If your plugin depends on these behaviors, you must update to the recommended alternative before the noted version. +## 5.2.0 + +- The PHP function `gutenberg_parse_blocks` has been removed. Use [`parse_blocks`](https://developer.wordpress.org/reference/functions/parse_blocks/) instead. +- The PHP function `get_dynamic_blocks_regex` has been removed. +- The PHP function `gutenberg_render_block` has been removed. Use [`render_block`](https://developer.wordpress.org/reference/functions/render_block/) instead. +- The PHP function `strip_dynamic_blocks` has been removed. For use in excerpt preparation, consider [`excerpt_remove_blocks`](https://developer.wordpress.org/reference/functions/excerpt_remove_blocks/) instead. +- The PHP function `strip_dynamic_blocks_add_filter` has been removed. +- The PHP function `strip_dynamic_blocks_remove_filter` has been removed. +- The PHP function `gutenberg_post_has_blocks` has been removed. Use [`has_blocks`](https://developer.wordpress.org/reference/functions/has_blocks/) instead. +- The PHP function `gutenberg_content_has_blocks` has been removed. Use [`has_blocks`](https://developer.wordpress.org/reference/functions/has_blocks/) instead. + ## 4.5.0 - `Dropdown.refresh()` has been deprecated as the contained `Popover` is now automatically refreshed. - `wp.editor.PostPublishPanelToggle` has been deprecated in favor of `wp.editor.PostPublishButton`. diff --git a/lib/blocks.php b/lib/blocks.php index 0b11041d0576d..3419e81c8bb38 100644 --- a/lib/blocks.php +++ b/lib/blocks.php @@ -9,89 +9,20 @@ die( 'Silence is golden.' ); } -if ( ! function_exists( 'register_block_type' ) ) { - /** - * Registers a block type. - * - * @since 0.1.0 - * @since 0.6.0 Now also accepts a WP_Block_Type instance as first parameter. - * - * @param string|WP_Block_Type $name Block type name including namespace, or alternatively a - * complete WP_Block_Type instance. In case a WP_Block_Type - * is provided, the $args parameter will be ignored. - * @param array $args { - * Optional. Array of block type arguments. Any arguments may be defined, however the - * ones described below are supported by default. Default empty array. - * - * @type callable $render_callback Callback used to render blocks of this block type. - * } - * @return WP_Block_Type|false The registered block type on success, or false on failure. - */ - function register_block_type( $name, $args = array() ) { - return WP_Block_Type_Registry::get_instance()->register( $name, $args ); - } -} - -if ( ! function_exists( 'unregister_block_type' ) ) { - /** - * Unregisters a block type. - * - * @since 0.1.0 - * @since 0.6.0 Now also accepts a WP_Block_Type instance as first parameter. - * - * @param string|WP_Block_Type $name Block type name including namespace, or alternatively a - * complete WP_Block_Type instance. - * @return WP_Block_Type|false The unregistered block type on success, or false on failure. - */ - function unregister_block_type( $name ) { - return WP_Block_Type_Registry::get_instance()->unregister( $name ); - } -} - if ( ! function_exists( 'gutenberg_parse_blocks' ) ) { /** * Parses blocks out of a content string. * * @since 0.5.0 + * @deprecated 5.0.0 parse_blocks() * * @param string $content Post content. * @return array Array of parsed block objects. */ function gutenberg_parse_blocks( $content ) { - /** - * Filter to allow plugins to replace the server-side block parser - * - * @since 3.8.0 - * - * @param string $parser_class Name of block parser class - */ - $parser_class = apply_filters( 'block_parser_class', 'WP_Block_Parser' ); - // Load default block parser for server-side parsing if the default parser class is being used. - if ( 'WP_Block_Parser' === $parser_class ) { - require_once dirname( __FILE__ ) . '/../packages/block-serialization-default-parser/parser.php'; - } - $parser = new $parser_class(); - return $parser->parse( $content ); - } -} - -if ( ! function_exists( 'get_dynamic_block_names' ) ) { - /** - * Returns an array of the names of all registered dynamic block types. - * - * @return array Array of dynamic block names. - */ - function get_dynamic_block_names() { - $dynamic_block_names = array(); + _deprecated_function( __FUNCTION__, '5.0.0', 'parse_blocks()' ); - $block_types = WP_Block_Type_Registry::get_instance()->get_all_registered(); - foreach ( $block_types as $block_type ) { - if ( $block_type->is_dynamic() ) { - $dynamic_block_names[] = $block_type->name; - } - } - - return $dynamic_block_names; + return parse_blocks( $content ); } } @@ -100,10 +31,13 @@ function get_dynamic_block_names() { * Retrieve the dynamic blocks regular expression for searching. * * @since 3.6.0 + * @deprecated 5.0.0 * * @return string */ function get_dynamic_blocks_regex() { + _deprecated_function( __FUNCTION__, '5.0.0' ); + $dynamic_block_names = get_dynamic_block_names(); $dynamic_block_pattern = ( '/<!--\s+wp:(' . @@ -135,85 +69,15 @@ function get_dynamic_blocks_regex() { * @since 1.9.0 * @since 4.4.0 renders full nested tree of blocks before reassembling into HTML string * @global WP_Post $post The post to edit. + * @deprecated 5.0.0 render_block() * * @param array $block A single parsed block object. * @return string String of rendered HTML. */ function gutenberg_render_block( $block ) { - global $post; - - $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] ); - $is_dynamic = $block['blockName'] && null !== $block_type && $block_type->is_dynamic(); - $inner_content = ''; - $index = 0; - - foreach ( $block['innerContent'] as $chunk ) { - $inner_content .= is_string( $chunk ) ? $chunk : gutenberg_render_block( $block['innerBlocks'][ $index++ ] ); - } - - if ( $is_dynamic ) { - $attributes = is_array( $block['attrs'] ) ? (array) $block['attrs'] : array(); - $global_post = $post; - $output = $block_type->render( $attributes, $inner_content ); - $post = $global_post; + _deprecated_function( __FUNCTION__, '5.0.0', 'render_block()' ); - return $output; - } - - return $inner_content; -} - -if ( ! function_exists( 'do_blocks' ) ) { - /** - * Parses dynamic blocks out of `post_content` and re-renders them. - * - * @since 0.1.0 - * @since 4.4.0 performs full parse on input post content - * - * @param string $content Post content. - * @return string Updated post content. - */ - function do_blocks( $content ) { - // If there are blocks in this content, we shouldn't run wpautop() on it later. - $priority = has_filter( 'the_content', 'wpautop' ); - if ( false !== $priority && doing_filter( 'the_content' ) && has_blocks( $content ) ) { - remove_filter( 'the_content', 'wpautop', $priority ); - add_filter( 'the_content', '_restore_wpautop_hook', $priority + 1 ); - } - - $blocks = gutenberg_parse_blocks( $content ); - $output = ''; - - foreach ( $blocks as $block ) { - $output .= gutenberg_render_block( $block ); - } - - return $output; - } - - add_filter( 'the_content', 'do_blocks', 7 ); // BEFORE do_shortcode() and oembed. -} - -if ( ! function_exists( '_restore_wpautop_hook' ) ) { - /** - * If do_blocks() needs to remove wpautop() from the `the_content` filter, - * this re-adds it afterwards, for subsequent `the_content` usage. - * - * @access private - * - * @since 4.6.0 - * - * @param string $content The post content running through this filter. - * @return string The unmodified content. - */ - function _restore_wpautop_hook( $content ) { - $current_priority = has_filter( 'the_content', '_restore_wpautop_hook' ); - - add_filter( 'the_content', 'wpautop', $current_priority - 1 ); - remove_filter( 'the_content', '_restore_wpautop_hook', $current_priority ); - - return $content; - } + return render_block( $block ); } if ( ! function_exists( 'strip_dynamic_blocks' ) ) { @@ -221,11 +85,14 @@ function _restore_wpautop_hook( $content ) { * Remove all dynamic blocks from the given content. * * @since 3.6.0 + * @deprecated 5.0.0 * * @param string $content Content of the current post. * @return string */ function strip_dynamic_blocks( $content ) { + _deprecated_function( __FUNCTION__, '5.0.0' ); + return preg_replace( get_dynamic_blocks_regex(), '', $content ); } } @@ -238,16 +105,18 @@ function strip_dynamic_blocks( $content ) { * can just be called in `wp_trim_excerpt()`. * * @since 3.6.0 + * @deprecated 5.0.0 * * @param string $text Excerpt. * @return string */ function strip_dynamic_blocks_add_filter( $text ) { + _deprecated_function( __FUNCTION__, '5.0.0' ); + add_filter( 'the_content', 'strip_dynamic_blocks', 6 ); return $text; } - add_filter( 'get_the_excerpt', 'strip_dynamic_blocks_add_filter', 9 ); // Before wp_trim_excerpt(). } if ( ! function_exists( 'strip_dynamic_blocks_remove_filter' ) ) { @@ -258,14 +127,16 @@ function strip_dynamic_blocks_add_filter( $text ) { * can just be called in `wp_trim_excerpt()`. * * @since 3.6.0 + * @deprecated 5.0.0 * * @param string $text Excerpt. * @return string */ function strip_dynamic_blocks_remove_filter( $text ) { + _deprecated_function( __FUNCTION__, '5.0.0' ); + remove_filter( 'the_content', 'strip_dynamic_blocks', 6 ); return $text; } - add_filter( 'wp_trim_excerpt', 'strip_dynamic_blocks_remove_filter', 0 ); // Before all other. } diff --git a/lib/class-wp-block-type-registry.php b/lib/class-wp-block-type-registry.php deleted file mode 100644 index f95dec6ce4b9d..0000000000000 --- a/lib/class-wp-block-type-registry.php +++ /dev/null @@ -1,182 +0,0 @@ -<?php -/** - * Blocks API: WP_Block_Type_Registry class - * - * @package gutenberg - * @since 0.6.0 - */ - -/** - * Core class used for interacting with block types. - * - * @since 0.6.0 - */ -final class WP_Block_Type_Registry { - /** - * Registered block types, as `$name => $instance` pairs. - * - * @since 0.6.0 - * @access private - * @var WP_Block_Type[] - */ - private $registered_block_types = array(); - - /** - * Container for the main instance of the class. - * - * @since 0.6.0 - * @access private - * @static - * @var WP_Block_Type_Registry|null - */ - private static $instance = null; - - /** - * Registers a block type. - * - * @since 0.6.0 - * @access public - * - * @param string|WP_Block_Type $name Block type name including namespace, or alternatively a - * complete WP_Block_Type instance. In case a WP_Block_Type - * is provided, the $args parameter will be ignored. - * @param array $args { - * Optional. Array of block type arguments. Any arguments may be defined, however the - * ones described below are supported by default. Default empty array. - * - * @type callable $render_callback Callback used to render blocks of this block type. - * @type array $attributes Block attributes mapping, property name to schema. - * } - * @return WP_Block_Type|false The registered block type on success, or false on failure. - */ - public function register( $name, $args = array() ) { - $block_type = null; - if ( $name instanceof WP_Block_Type ) { - $block_type = $name; - $name = $block_type->name; - } - - if ( ! is_string( $name ) ) { - $message = __( 'Block type names must be strings.', 'gutenberg' ); - _doing_it_wrong( __METHOD__, $message, '0.1.0' ); - return false; - } - - if ( preg_match( '/[A-Z]+/', $name ) ) { - $message = __( 'Block type names must not contain uppercase characters.', 'gutenberg' ); - _doing_it_wrong( __METHOD__, $message, '1.5.0' ); - return false; - } - - $name_matcher = '/^[a-z0-9-]+\/[a-z0-9-]+$/'; - if ( ! preg_match( $name_matcher, $name ) ) { - $message = __( 'Block type names must contain a namespace prefix. Example: my-plugin/my-custom-block-type', 'gutenberg' ); - _doing_it_wrong( __METHOD__, $message, '0.1.0' ); - return false; - } - - if ( $this->is_registered( $name ) ) { - /* translators: 1: block name */ - $message = sprintf( __( 'Block type "%s" is already registered.', 'gutenberg' ), $name ); - _doing_it_wrong( __METHOD__, $message, '0.1.0' ); - return false; - } - - if ( ! $block_type ) { - $block_type = new WP_Block_Type( $name, $args ); - } - - $this->registered_block_types[ $name ] = $block_type; - - return $block_type; - } - - /** - * Unregisters a block type. - * - * @since 0.6.0 - * @access public - * - * @param string|WP_Block_Type $name Block type name including namespace, or alternatively a - * complete WP_Block_Type instance. - * @return WP_Block_Type|false The unregistered block type on success, or false on failure. - */ - public function unregister( $name ) { - if ( $name instanceof WP_Block_Type ) { - $name = $name->name; - } - - if ( ! $this->is_registered( $name ) ) { - /* translators: 1: block name */ - $message = sprintf( __( 'Block type "%s" is not registered.', 'gutenberg' ), $name ); - _doing_it_wrong( __METHOD__, $message, '0.1.0' ); - return false; - } - - $unregistered_block_type = $this->registered_block_types[ $name ]; - unset( $this->registered_block_types[ $name ] ); - - return $unregistered_block_type; - } - - /** - * Retrieves a registered block type. - * - * @since 0.6.0 - * @access public - * - * @param string $name Block type name including namespace. - * @return WP_Block_Type|null The registered block type, or null if it is not registered. - */ - public function get_registered( $name ) { - if ( ! $this->is_registered( $name ) ) { - return null; - } - - return $this->registered_block_types[ $name ]; - } - - /** - * Retrieves all registered block types. - * - * @since 0.6.0 - * @access public - * - * @return WP_Block_Type[] Associative array of `$block_type_name => $block_type` pairs. - */ - public function get_all_registered() { - return $this->registered_block_types; - } - - /** - * Checks if a block type is registered. - * - * @since 0.6.0 - * @access public - * - * @param string $name Block type name including namespace. - * @return bool True if the block type is registered, false otherwise. - */ - public function is_registered( $name ) { - return isset( $this->registered_block_types[ $name ] ); - } - - /** - * Utility method to retrieve the main instance of the class. - * - * The instance will be created if it does not exist yet. - * - * @since 0.6.0 - * @access public - * @static - * - * @return WP_Block_Type_Registry The main instance. - */ - public static function get_instance() { - if ( null === self::$instance ) { - self::$instance = new self(); - } - - return self::$instance; - } -} diff --git a/lib/class-wp-block-type.php b/lib/class-wp-block-type.php deleted file mode 100644 index c186eec88a0a3..0000000000000 --- a/lib/class-wp-block-type.php +++ /dev/null @@ -1,209 +0,0 @@ -<?php -/** - * Blocks API: WP_Block_Type class - * - * @package gutenberg - * @since 0.6.0 - */ - -/** - * Core class representing a block type. - * - * @since 0.6.0 - * - * @see register_block_type() - */ -class WP_Block_Type { - /** - * Block type key. - * - * @since 0.6.0 - * @var string - */ - public $name; - - /** - * Block type render callback. - * - * @since 0.6.0 - * @var callable - */ - public $render_callback; - - /** - * Block type attributes property schemas. - * - * @since 0.10.0 - * @var array - */ - public $attributes; - - /** - * Block type editor script handle. - * - * @since 2.0.0 - * @var string - */ - public $editor_script; - - /** - * Block type front end script handle. - * - * @since 2.0.0 - * @var string - */ - public $script; - - /** - * Block type editor style handle. - * - * @since 2.0.0 - * @var string - */ - public $editor_style; - - /** - * Block type front end style handle. - * - * @since 2.0.0 - * @var string - */ - public $style; - - /** - * Constructor. - * - * Will populate object properties from the provided arguments. - * - * @since 0.6.0 - * - * @see register_block_type() - * - * @param string $block_type Block type name including namespace. - * @param array|string $args Optional. Array or string of arguments for registering a block type. - * Default empty array. - */ - public function __construct( $block_type, $args = array() ) { - $this->name = $block_type; - - $this->set_props( $args ); - } - - /** - * Renders the block type output for given attributes. - * - * @since 0.6.0 - * - * @param array $attributes Optional. Block attributes. Default empty array. - * @param string $content Optional. Block content. Default empty string. - * @return string Rendered block type output. - */ - public function render( $attributes = array(), $content = '' ) { - if ( ! $this->is_dynamic() ) { - return ''; - } - - $attributes = $this->prepare_attributes_for_render( $attributes ); - - return (string) call_user_func( $this->render_callback, $attributes, $content ); - } - - /** - * Returns true if the block type is dynamic, or false otherwise. A dynamic - * block is one which defers its rendering to occur on-demand at runtime. - * - * @return boolean Whether block type is dynamic. - */ - public function is_dynamic() { - return is_callable( $this->render_callback ); - } - - /** - * Validates attributes against the current block schema, populating - * defaulted and missing values. - * - * @param array $attributes Original block attributes. - * @return array Prepared block attributes. - */ - public function prepare_attributes_for_render( $attributes ) { - // If there are no attribute definitions for the block type, skip - // processing and return vebatim. - if ( ! isset( $this->attributes ) ) { - return $attributes; - } - - foreach ( $attributes as $attribute_name => $value ) { - // If the attribute is not defined by the block type, it cannot be - // validated. - if ( ! isset( $this->attributes[ $attribute_name ] ) ) { - continue; - } - - $schema = $this->attributes[ $attribute_name ]; - - // Validate value by JSON schema. An invalid value should revert to - // its default, if one exists. This occurs by virtue of the missing - // attributes loop immediately following. If there is not a default - // assigned, the attribute value should remain unset. - $is_valid = rest_validate_value_from_schema( $value, $schema ); - if ( is_wp_error( $is_valid ) ) { - unset( $attributes[ $attribute_name ] ); - } - } - - // Populate values of any missing attributes for which the block type - // defines a default. - $missing_schema_attributes = array_diff_key( $this->attributes, $attributes ); - foreach ( $missing_schema_attributes as $attribute_name => $schema ) { - if ( isset( $schema['default'] ) ) { - $attributes[ $attribute_name ] = $schema['default']; - } - } - - return $attributes; - } - - /** - * Sets block type properties. - * - * @since 0.6.0 - * - * @param array|string $args Array or string of arguments for registering a block type. - */ - public function set_props( $args ) { - $args = wp_parse_args( - $args, - array( - 'render_callback' => null, - ) - ); - - $args['name'] = $this->name; - - foreach ( $args as $property_name => $property_value ) { - $this->$property_name = $property_value; - } - } - - /** - * Get all available block attributes including possible layout attribute from Columns block. - * - * @return array Array of attributes. - */ - public function get_attributes() { - return is_array( $this->attributes ) ? - array_merge( - $this->attributes, - array( - 'layout' => array( - 'type' => 'string', - ), - ) - ) : - array( - 'layout' => array( - 'type' => 'string', - ), - ); - } -} diff --git a/lib/load.php b/lib/load.php index ee1c973f99568..034b3c9e83ceb 100644 --- a/lib/load.php +++ b/lib/load.php @@ -38,12 +38,6 @@ } require dirname( __FILE__ ) . '/meta-box-partial-page.php'; -if ( ! class_exists( 'WP_Block_Type' ) ) { - require dirname( __FILE__ ) . '/class-wp-block-type.php'; -} -if ( ! class_exists( 'WP_Block_Type_Registry' ) ) { - require dirname( __FILE__ ) . '/class-wp-block-type-registry.php'; -} require dirname( __FILE__ ) . '/blocks.php'; require dirname( __FILE__ ) . '/client-assets.php'; require dirname( __FILE__ ) . '/compat.php'; diff --git a/phpunit/class-blocks-api-test.php b/phpunit/class-blocks-api-test.php index 2c91bb027a92a..1573f980f6368 100644 --- a/phpunit/class-blocks-api-test.php +++ b/phpunit/class-blocks-api-test.php @@ -82,6 +82,9 @@ function tearDown() { * @covers ::strip_dynamic_blocks */ function test_strip_dynamic_blocks() { + $this->setExpectedDeprecated( 'strip_dynamic_blocks' ); + $this->setExpectedDeprecated( 'get_dynamic_blocks_regex' ); + // Simple dynamic block.. $content = '<!-- wp:core/block /-->'; $this->assertEmpty( strip_dynamic_blocks( $content ) ); @@ -89,30 +92,4 @@ function test_strip_dynamic_blocks() { // Dynamic block with options, embedded in other content. $this->assertEquals( $this->filtered_content, strip_dynamic_blocks( $this->content ) ); } - - /** - * Tests that dynamic blocks don't cause an out-of-memory error. - * - * When dynamic blocks happen to generate an excerpt, they can cause an - * infinite loop if that block is part of the post's content. - * - * `wp_trim_excerpt()` applies the `the_content` filter, which has - * `do_blocks` attached to it, trying to render the block which again will - * attempt to return an excerpt of that post. - * - * This infinite loop can be avoided by stripping dynamic blocks before - * `the_content` gets applied, just like shortcodes. - * - * @covers ::strip_dynamic_blocks_add_filter, ::strip_dynamic_blocks_remove_filter - */ - function test_excerpt_infinite_loop() { - $query = new WP_Query( - array( - 'post__in' => array( self::$post_id ), - ) - ); - $query->the_post(); - - $this->assertEmpty( do_blocks( '<!-- wp:core/dummy /-->' ) ); - } } From 0f5891556e8e3ec3e41fcc318533bba87983338b Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Wed, 23 Jan 2019 11:39:24 -0500 Subject: [PATCH 209/691] Framework: Remove 5.0-merged REST API integrations (#13408) * Plugin: Remove 5.0-merged REST API integrations * Re-add line break to deprecations document --- .../backward-compatibility/deprecations.md | 9 + lib/class-wp-rest-autosaves-controller.php | 426 ------------ ...lass-wp-rest-block-renderer-controller.php | 178 ------ lib/class-wp-rest-blocks-controller.php | 89 --- lib/class-wp-rest-post-search-handler.php | 190 ------ lib/class-wp-rest-search-controller.php | 363 ----------- lib/class-wp-rest-search-handler.php | 96 --- lib/class-wp-rest-themes-controller.php | 237 ------- lib/load.php | 22 - lib/rest-api.php | 211 +----- phpunit/bootstrap.php | 1 - phpunit/class-gutenberg-rest-api-test.php | 427 ------------ .../class-rest-autosaves-controller-test.php | 605 ------------------ .../class-wp-rest-dummy-search-handler.php | 90 --- .../class-wp-rest-themes-controller-test.php | 344 ---------- 15 files changed, 36 insertions(+), 3252 deletions(-) delete mode 100644 lib/class-wp-rest-autosaves-controller.php delete mode 100644 lib/class-wp-rest-block-renderer-controller.php delete mode 100644 lib/class-wp-rest-blocks-controller.php delete mode 100644 lib/class-wp-rest-post-search-handler.php delete mode 100644 lib/class-wp-rest-search-controller.php delete mode 100644 lib/class-wp-rest-search-handler.php delete mode 100644 lib/class-wp-rest-themes-controller.php delete mode 100644 phpunit/class-gutenberg-rest-api-test.php delete mode 100644 phpunit/class-rest-autosaves-controller-test.php delete mode 100644 phpunit/class-wp-rest-dummy-search-handler.php delete mode 100644 phpunit/class-wp-rest-themes-controller-test.php diff --git a/docs/designers-developers/developers/backward-compatibility/deprecations.md b/docs/designers-developers/developers/backward-compatibility/deprecations.md index 6db2ed322b6a5..85e82f25549c6 100644 --- a/docs/designers-developers/developers/backward-compatibility/deprecations.md +++ b/docs/designers-developers/developers/backward-compatibility/deprecations.md @@ -12,6 +12,15 @@ The Gutenberg project's deprecation policy is intended to support backward compa - The PHP function `strip_dynamic_blocks_remove_filter` has been removed. - The PHP function `gutenberg_post_has_blocks` has been removed. Use [`has_blocks`](https://developer.wordpress.org/reference/functions/has_blocks/) instead. - The PHP function `gutenberg_content_has_blocks` has been removed. Use [`has_blocks`](https://developer.wordpress.org/reference/functions/has_blocks/) instead. +- The PHP function `gutenberg_register_rest_routes` has been removed. +- The PHP function `gutenberg_add_taxonomy_visibility_field` has been removed. +- The PHP function `gutenberg_get_taxonomy_visibility_data` has been removed. +- The PHP function `gutenberg_add_permalink_template_to_posts` has been removed. +- The PHP function `gutenberg_add_block_format_to_post_content` has been removed. +- The PHP function `gutenberg_add_target_schema_to_links` has been removed. +- The PHP function `gutenberg_register_post_prepare_functions` has been removed. +- The PHP function `gutenberg_silence_rest_errors` has been removed. +- The PHP function `gutenberg_filter_post_type_labels` has been removed. ## 4.5.0 - `Dropdown.refresh()` has been deprecated as the contained `Popover` is now automatically refreshed. diff --git a/lib/class-wp-rest-autosaves-controller.php b/lib/class-wp-rest-autosaves-controller.php deleted file mode 100644 index 6f6496c35790e..0000000000000 --- a/lib/class-wp-rest-autosaves-controller.php +++ /dev/null @@ -1,426 +0,0 @@ -<?php -/** - * REST API: WP_REST_Autosaves_Controller class. - * - * @package WordPress - * @subpackage REST_API - * @since 5.0.0 - */ - -/** - * Core class used to access autosaves via the REST API. - * - * @since 5.0.0 - * - * @see WP_REST_Controller - */ -class WP_REST_Autosaves_Controller extends WP_REST_Revisions_Controller { - - /** - * Parent post type. - * - * @since 5.0.0 - * @var string - */ - private $parent_post_type; - - /** - * Parent post controller. - * - * @since 5.0.0 - * @var WP_REST_Controller - */ - private $parent_controller; - - /** - * Revision controller. - * - * @since 5.0.0 - * @var WP_REST_Controller - */ - private $revisions_controller; - - /** - * The base of the parent controller's route. - * - * @since 5.0.0 - * @var string - */ - private $parent_base; - - /** - * Constructor. - * - * @since 5.0.0 - * - * @param string $parent_post_type Post type of the parent. - */ - public function __construct( $parent_post_type ) { - $this->parent_post_type = $parent_post_type; - $post_type_object = get_post_type_object( $parent_post_type ); - - // Ensure that post type-specific controller logic is available. - $parent_controller_class = ! empty( $post_type_object->rest_controller_class ) ? $post_type_object->rest_controller_class : 'WP_REST_Posts_Controller'; - - $this->parent_controller = new $parent_controller_class( $post_type_object->name ); - $this->revisions_controller = new WP_REST_Revisions_Controller( $parent_post_type ); - $this->rest_namespace = 'wp/v2'; - $this->rest_base = 'autosaves'; - $this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name; - } - - /** - * Registers routes for autosaves. - * - * @since 5.0.0 - * - * @see register_rest_route() - */ - public function register_routes() { - register_rest_route( - $this->rest_namespace, - '/' . $this->parent_base . '/(?P<id>[\d]+)/' . $this->rest_base, - array( - 'args' => array( - 'parent' => array( - 'description' => __( 'The ID for the parent of the object.', 'gutenberg' ), - 'type' => 'integer', - ), - ), - array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_items' ), - 'permission_callback' => array( $this, 'get_items_permissions_check' ), - 'args' => $this->get_collection_params(), - ), - array( - 'methods' => WP_REST_Server::CREATABLE, - 'callback' => array( $this, 'create_item' ), - 'permission_callback' => array( $this, 'create_item_permissions_check' ), - 'args' => $this->parent_controller->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), - ), - 'schema' => array( $this, 'get_public_item_schema' ), - ) - ); - - register_rest_route( - $this->rest_namespace, - '/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base . '/(?P<id>[\d]+)', - array( - 'args' => array( - 'parent' => array( - 'description' => __( 'The ID for the parent of the object.', 'gutenberg' ), - 'type' => 'integer', - ), - 'id' => array( - 'description' => __( 'The ID for the object.', 'gutenberg' ), - 'type' => 'integer', - ), - ), - array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_item' ), - 'permission_callback' => array( $this->revisions_controller, 'get_item_permissions_check' ), - 'args' => array( - 'context' => $this->get_context_param( array( 'default' => 'view' ) ), - ), - ), - 'schema' => array( $this, 'get_public_item_schema' ), - ) - ); - - } - - /** - * Get the parent post. - * - * @since 5.0.0 - * - * @param int $parent_id Supplied ID. - * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise. - */ - protected function get_parent( $parent_id ) { - return $this->revisions_controller->get_parent( $parent_id ); - } - - /** - * Checks if a given request has access to get autosaves. - * - * @since 5.0.0 - * - * @param WP_REST_Request $request Full data about the request. - * @return true|WP_Error True if the request has read access, WP_Error object otherwise. - */ - public function get_items_permissions_check( $request ) { - $parent = $this->get_parent( $request['id'] ); - if ( is_wp_error( $parent ) ) { - return $parent; - } - - $parent_post_type_obj = get_post_type_object( $parent->post_type ); - if ( ! current_user_can( $parent_post_type_obj->cap->edit_post, $parent->ID ) ) { - return new WP_Error( 'rest_cannot_read', __( 'Sorry, you are not allowed to view revisions of this post.', 'gutenberg' ), array( 'status' => rest_authorization_required_code() ) ); - } - - return true; - } - - /** - * Checks if a given request has access to create an autosave revision. - * - * Autosave revisions inherit permissions from the parent post, - * check if the current user has permission to edit the post. - * - * @since 5.0.0 - * - * @param WP_REST_Request $request Full details about the request. - * @return true|WP_Error True if the request has access to create the item, WP_Error object otherwise. - */ - public function create_item_permissions_check( $request ) { - $id = $request->get_param( 'id' ); - if ( empty( $id ) ) { - return new WP_Error( 'rest_post_invalid_id', __( 'Invalid item ID.', 'gutenberg' ), array( 'status' => 404 ) ); - } - - return $this->parent_controller->update_item_permissions_check( $request ); - } - - /** - * Creates, updates or deletes an autosave revision. - * - * @since 5.0.0 - * - * @param WP_REST_Request $request Full details about the request. - * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. - */ - public function create_item( $request ) { - - if ( ! defined( 'DOING_AUTOSAVE' ) ) { - define( 'DOING_AUTOSAVE', true ); - } - - $post = get_post( $request['id'] ); - - if ( is_wp_error( $post ) ) { - return $post; - } - - $prepared_post = $this->parent_controller->prepare_item_for_database( $request ); - $prepared_post->ID = $post->ID; - $user_id = get_current_user_id(); - - if ( ( 'draft' === $post->post_status || 'auto-draft' === $post->post_status ) && $post->post_author == $user_id ) { - // Draft posts for the same author: autosaving updates the post and does not create a revision. - // Convert the post object to an array and add slashes, wp_update_post expects escaped array. - $autosave_id = wp_update_post( wp_slash( (array) $prepared_post ), true ); - } else { - // Non-draft posts: create or update the post autosave. - $autosave_id = $this->create_post_autosave( (array) $prepared_post ); - } - - if ( is_wp_error( $autosave_id ) ) { - return $autosave_id; - } - - $autosave = get_post( $autosave_id ); - $request->set_param( 'context', 'edit' ); - - $response = $this->prepare_item_for_response( $autosave, $request ); - $response = rest_ensure_response( $response ); - - return $response; - } - - /** - * Get the autosave, if the ID is valid. - * - * @since 5.0.0 - * - * @param WP_REST_Request $request Full data about the request. - * @return WP_Post|WP_Error Revision post object if ID is valid, WP_Error otherwise. - */ - public function get_item( $request ) { - $parent_id = (int) $request->get_param( 'parent' ); - - if ( $parent_id <= 0 ) { - return new WP_Error( 'rest_post_invalid_id', __( 'Invalid parent post ID.', 'gutenberg' ), array( 'status' => 404 ) ); - } - - $autosave = wp_get_post_autosave( $parent_id ); - - if ( ! $autosave ) { - return new WP_Error( 'rest_post_no_autosave', __( 'There is no autosave revision for this post.', 'gutenberg' ), array( 'status' => 404 ) ); - } - - $response = $this->prepare_item_for_response( $autosave, $request ); - return $response; - } - - /** - * Gets a collection of autosaves using wp_get_post_autosave. - * - * Contains the user's autosave, for empty if it doesn't exist. - * - * @since 5.0.0 - * - * @param WP_REST_Request $request Full data about the request. - * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. - */ - public function get_items( $request ) { - $parent = $this->get_parent( $request['id'] ); - if ( is_wp_error( $parent ) ) { - return $parent; - } - - $response = array(); - $parent_id = $parent->ID; - $revisions = wp_get_post_revisions( $parent_id, array( 'check_enabled' => false ) ); - - foreach ( $revisions as $revision ) { - if ( false !== strpos( $revision->post_name, "{$parent_id}-autosave" ) ) { - $data = $this->prepare_item_for_response( $revision, $request ); - $response[] = $this->prepare_response_for_collection( $data ); - } - } - - return rest_ensure_response( $response ); - } - - - /** - * Retrieves the autosave's schema, conforming to JSON Schema. - * - * @since 5.0.0 - * - * @return array Item schema data. - */ - public function get_item_schema() { - $schema = $this->revisions_controller->get_item_schema(); - - $schema['properties']['preview_link'] = array( - 'description' => __( 'Preview link for the post.', 'gutenberg' ), - 'type' => 'string', - 'format' => 'uri', - 'context' => array( 'edit' ), - 'readonly' => true, - ); - - return $schema; - } - - /** - * Creates autosave for the specified post. - * - * From wp-admin/post.php. - * - * @since 5.0.0 - * - * @param mixed $post_data Associative array containing the post data. - * @return mixed The autosave revision ID or WP_Error. - */ - public function create_post_autosave( $post_data ) { - - $post_id = (int) $post_data['ID']; - $post = get_post( $post_id ); - - if ( is_wp_error( $post ) ) { - return $post; - } - - $user_id = get_current_user_id(); - - // Store one autosave per author. If there is already an autosave, overwrite it. - $old_autosave = wp_get_post_autosave( $post_id, $user_id ); - - if ( $old_autosave ) { - $new_autosave = _wp_post_revision_data( $post_data, true ); - $new_autosave['ID'] = $old_autosave->ID; - $new_autosave['post_author'] = $user_id; - - // If the new autosave has the same content as the post, delete the autosave. - $autosave_is_different = false; - - foreach ( array_intersect( array_keys( $new_autosave ), array_keys( _wp_post_revision_fields( $post ) ) ) as $field ) { - if ( normalize_whitespace( $new_autosave[ $field ] ) != normalize_whitespace( $post->$field ) ) { - $autosave_is_different = true; - break; - } - } - - if ( ! $autosave_is_different ) { - wp_delete_post_revision( $old_autosave->ID ); - return new WP_Error( 'rest_autosave_no_changes', __( 'There is nothing to save. The autosave and the post content are the same.', 'gutenberg' ), array( 'status' => 400 ) ); - } - - /** - * This filter is documented in wp-admin/post.php. - */ - do_action( 'wp_creating_autosave', $new_autosave ); - - // wp_update_post expects escaped array. - return wp_update_post( wp_slash( $new_autosave ) ); - } - - // Create the new autosave as a special post revision. - return _wp_put_post_revision( $post_data, true ); - } - - /** - * Prepares the revision for the REST response. - * - * @since 5.0.0 - * - * @param WP_Post $post Post revision object. - * @param WP_REST_Request $request Request object. - * - * @return WP_REST_Response Response object. - */ - public function prepare_item_for_response( $post, $request ) { - - $response = $this->revisions_controller->prepare_item_for_response( $post, $request ); - - $schema = $this->get_item_schema(); - - if ( ! empty( $schema['properties']['preview_link'] ) ) { - $parent_id = wp_is_post_autosave( $post ); - $preview_post_id = false === $parent_id ? $post->ID : $parent_id; - $preview_query_args = array(); - - if ( false !== $parent_id ) { - $preview_query_args['preview_id'] = $parent_id; - $preview_query_args['preview_nonce'] = wp_create_nonce( 'post_preview_' . $parent_id ); - } - - $response->data['preview_link'] = get_preview_post_link( $preview_post_id, $preview_query_args ); - } - - $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; - $response->data = $this->filter_response_by_context( $response->data, $context ); - - /** - * Filters a revision returned from the API. - * - * Allows modification of the revision right before it is returned. - * - * @since 5.0.0 - * - * @param WP_REST_Response $response The response object. - * @param WP_Post $post The original revision object. - * @param WP_REST_Request $request Request used to generate the response. - */ - return apply_filters( 'rest_prepare_autosave', $response, $post, $request ); - } - - /** - * Retrieves the query params for the autosaves collection. - * - * @since 5.0.0 - * - * @return array Collection parameters. - */ - public function get_collection_params() { - return array( - 'context' => $this->get_context_param( array( 'default' => 'view' ) ), - ); - } -} diff --git a/lib/class-wp-rest-block-renderer-controller.php b/lib/class-wp-rest-block-renderer-controller.php deleted file mode 100644 index b9839f080376a..0000000000000 --- a/lib/class-wp-rest-block-renderer-controller.php +++ /dev/null @@ -1,178 +0,0 @@ -<?php -/** - * Block Renderer REST API: WP_REST_Block_Renderer_Controller class - * - * @package gutenberg - * @since 2.8.0 - */ - -/** - * Controller which provides REST endpoint for rendering a block. - * - * @since 2.8.0 - * - * @see WP_REST_Controller - */ -class WP_REST_Block_Renderer_Controller extends WP_REST_Controller { - - /** - * Constructs the controller. - * - * @access public - */ - public function __construct() { - $this->namespace = 'wp/v2'; - $this->rest_base = 'block-renderer'; - } - - /** - * Registers the necessary REST API routes, one for each dynamic block. - * - * @access public - */ - public function register_routes() { - $block_types = WP_Block_Type_Registry::get_instance()->get_all_registered(); - foreach ( $block_types as $block_type ) { - if ( ! $block_type->is_dynamic() ) { - continue; - } - - register_rest_route( - $this->namespace, - '/' . $this->rest_base . '/(?P<name>' . $block_type->name . ')', - array( - 'args' => array( - 'name' => array( - 'description' => __( 'Unique registered name for the block.', 'gutenberg' ), - 'type' => 'string', - ), - ), - array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_item' ), - 'permission_callback' => array( $this, 'get_item_permissions_check' ), - 'args' => array( - 'context' => $this->get_context_param( array( 'default' => 'view' ) ), - 'attributes' => array( - /* translators: %s is the name of the block */ - 'description' => sprintf( __( 'Attributes for %s block', 'gutenberg' ), $block_type->name ), - 'type' => 'object', - 'additionalProperties' => false, - 'properties' => $block_type->get_attributes(), - 'default' => array(), - ), - 'post_id' => array( - 'description' => __( 'ID of the post context.', 'gutenberg' ), - 'type' => 'integer', - ), - ), - ), - 'schema' => array( $this, 'get_public_item_schema' ), - ) - ); - } - } - - /** - * Checks if a given request has access to read blocks. - * - * @since 2.8.0 - * @access public - * - * @param WP_REST_Request $request Request. - * @return true|WP_Error True if the request has read access, WP_Error object otherwise. - */ - public function get_item_permissions_check( $request ) { - global $post; - - $post_id = isset( $request['post_id'] ) ? intval( $request['post_id'] ) : 0; - - if ( 0 < $post_id ) { - $post = get_post( $post_id ); - if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) { - return new WP_Error( - 'gutenberg_block_cannot_read', - __( 'Sorry, you are not allowed to read Gutenberg blocks of this post.', 'gutenberg' ), - array( - 'status' => rest_authorization_required_code(), - ) - ); - } - } else { - if ( ! current_user_can( 'edit_posts' ) ) { - return new WP_Error( - 'gutenberg_block_cannot_read', - __( 'Sorry, you are not allowed to read Gutenberg blocks as this user.', 'gutenberg' ), - array( - 'status' => rest_authorization_required_code(), - ) - ); - } - } - - return true; - } - - /** - * Returns block output from block's registered render_callback. - * - * @since 2.8.0 - * @access public - * - * @param WP_REST_Request $request Full details about the request. - * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. - */ - public function get_item( $request ) { - global $post; - - $post_id = isset( $request['post_id'] ) ? intval( $request['post_id'] ) : 0; - - if ( 0 < $post_id ) { - $post = get_post( $post_id ); - - // Set up postdata since this will be needed if post_id was set. - setup_postdata( $post ); - } - $registry = WP_Block_Type_Registry::get_instance(); - $block = $registry->get_registered( $request['name'] ); - - if ( null === $block ) { - return new WP_Error( - 'gutenberg_block_invalid', - __( 'Invalid block.', 'gutenberg' ), - array( - 'status' => 404, - ) - ); - } - - $data = array( - 'rendered' => $block->render( $request->get_param( 'attributes' ) ), - ); - return rest_ensure_response( $data ); - } - - /** - * Retrieves block's output schema, conforming to JSON Schema. - * - * @since 2.8.0 - * @access public - * - * @return array Item schema data. - */ - public function get_item_schema() { - return array( - '$schema' => 'http://json-schema.org/schema#', - 'title' => 'rendered-block', - 'type' => 'object', - 'properties' => array( - 'rendered' => array( - 'description' => __( 'The rendered block.', 'gutenberg' ), - 'type' => 'string', - 'required' => true, - 'context' => array( 'edit' ), - ), - ), - ); - } -} diff --git a/lib/class-wp-rest-blocks-controller.php b/lib/class-wp-rest-blocks-controller.php deleted file mode 100644 index 47882fafbff04..0000000000000 --- a/lib/class-wp-rest-blocks-controller.php +++ /dev/null @@ -1,89 +0,0 @@ -<?php -/** - * Reusable blocks REST API: WP_REST_Blocks_Controller class - * - * @package gutenberg - * @since 0.10.0 - */ - -/** - * Controller which provides a REST endpoint for Gutenberg to read, create, - * edit and delete reusable blocks. Blocks are stored as posts with the wp_block - * post type. - * - * @since 0.10.0 - * - * @see WP_REST_Controller - */ -class WP_REST_Blocks_Controller extends WP_REST_Posts_Controller { - /** - * Checks if a block can be read. - * - * @since 2.1.0 - * - * @param object $post Post object that backs the block. - * @return bool Whether the block can be read. - */ - public function check_read_permission( $post ) { - // Ensure that the user is logged in and has the read_blocks capability. - $post_type = get_post_type_object( $post->post_type ); - if ( ! current_user_can( $post_type->cap->read_post, $post->ID ) ) { - return false; - } - - return parent::check_read_permission( $post ); - } - - /** - * Filters a response based on the context defined in the schema. - * - * @since 4.4.0 - * - * @param array $data Response data to fiter. - * @param string $context Context defined in the schema. - * @return array Filtered response. - */ - public function filter_response_by_context( $data, $context ) { - $data = parent::filter_response_by_context( $data, $context ); - - /* - * Remove `title.rendered` and `content.rendered` from the response. It - * doesn't make sense for a reusable block to have rendered content on its - * own, since rendering a block requires it to be inside a post or a page. - */ - unset( $data['title']['rendered'] ); - unset( $data['content']['rendered'] ); - - return $data; - } - - /** - * Retrieves the block's schema, conforming to JSON Schema. - * - * @since 4.4.0 - * - * @return array Item schema data. - */ - public function get_item_schema() { - $schema = parent::get_item_schema(); - - /* - * Allow all contexts to access `title.raw` and `content.raw`. Clients always - * need the raw markup of a reusable block to do anything useful, e.g. parse - * it or display it in an editor. - */ - $schema['properties']['title']['properties']['raw']['context'] = array( 'view', 'edit' ); - $schema['properties']['content']['properties']['raw']['context'] = array( 'view', 'edit' ); - - /* - * Remove `title.rendered` and `content.rendered` from the schema. It doesn’t - * make sense for a reusable block to have rendered content on its own, since - * rendering a block requires it to be inside a post or a page. - */ - unset( $schema['properties']['title']['properties']['rendered'] ); - unset( $schema['properties']['content']['properties']['rendered'] ); - - return $schema; - } - -} diff --git a/lib/class-wp-rest-post-search-handler.php b/lib/class-wp-rest-post-search-handler.php deleted file mode 100644 index 64d1917432012..0000000000000 --- a/lib/class-wp-rest-post-search-handler.php +++ /dev/null @@ -1,190 +0,0 @@ -<?php -/** - * REST API: WP_REST_Post_Search_Handler class - * - * @package gutenberg - * @since 3.3.0 - */ - -/** - * Core class representing a search handler for posts in the REST API. - * - * @since 3.3.0 - */ -class WP_REST_Post_Search_Handler extends WP_REST_Search_Handler { - - /** - * Constructor. - * - * @since 3.3.0 - */ - public function __construct() { - $this->type = 'post'; - - // Support all public post types except attachments. - $this->subtypes = array_diff( - array_values( - get_post_types( - array( - 'public' => true, - 'show_in_rest' => true, - ), - 'names' - ) - ), - array( 'attachment' ) - ); - } - - /** - * Searches the object type content for a given search request. - * - * @since 3.3.0 - * - * @param WP_REST_Request $request Full REST request. - * @return array Associative array containing an `WP_REST_Search_Handler::RESULT_IDS` containing - * an array of found IDs and `WP_REST_Search_Handler::RESULT_TOTAL` containing the - * total count for the matching search results. - */ - public function search_items( WP_REST_Request $request ) { - - // Get the post types to search for the current request. - $post_types = $request[ WP_REST_Search_Controller::PROP_SUBTYPE ]; - if ( in_array( WP_REST_Search_Controller::TYPE_ANY, $post_types, true ) ) { - $post_types = $this->subtypes; - } - - $query_args = array( - 'post_type' => $post_types, - 'post_status' => 'publish', - 'paged' => (int) $request['page'], - 'posts_per_page' => (int) $request['per_page'], - 'ignore_sticky_posts' => true, - 'fields' => 'ids', - ); - - if ( ! empty( $request['search'] ) ) { - $query_args['s'] = $request['search']; - } - - $query = new WP_Query(); - $found_ids = $query->query( $query_args ); - $total = $query->found_posts; - - return array( - self::RESULT_IDS => $found_ids, - self::RESULT_TOTAL => $total, - ); - } - - /** - * Prepares the search result for a given ID. - * - * @since 3.3.0 - * - * @param int $id Item ID. - * @param array $fields Fields to include for the item. - * @return array Associative array containing all fields for the item. - */ - public function prepare_item( $id, array $fields ) { - $post = get_post( $id ); - - $data = array(); - - if ( in_array( WP_REST_Search_Controller::PROP_ID, $fields, true ) ) { - $data[ WP_REST_Search_Controller::PROP_ID ] = (int) $post->ID; - } - - if ( in_array( WP_REST_Search_Controller::PROP_TITLE, $fields, true ) ) { - if ( post_type_supports( $post->post_type, 'title' ) ) { - add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); - $data[ WP_REST_Search_Controller::PROP_TITLE ] = get_the_title( $post->ID ); - remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); - } else { - $data[ WP_REST_Search_Controller::PROP_TITLE ] = ''; - } - } - - if ( in_array( WP_REST_Search_Controller::PROP_URL, $fields, true ) ) { - $data[ WP_REST_Search_Controller::PROP_URL ] = get_permalink( $post->ID ); - } - - if ( in_array( WP_REST_Search_Controller::PROP_TYPE, $fields, true ) ) { - $data[ WP_REST_Search_Controller::PROP_TYPE ] = $this->type; - } - - if ( in_array( WP_REST_Search_Controller::PROP_SUBTYPE, $fields, true ) ) { - $data[ WP_REST_Search_Controller::PROP_SUBTYPE ] = $post->post_type; - } - - return $data; - } - - /** - * Prepares links for the search result of a given ID. - * - * @since 3.3.0 - * - * @param int $id Item ID. - * @return array Links for the given item. - */ - public function prepare_item_links( $id ) { - $post = get_post( $id ); - - $links = array(); - - $item_route = $this->detect_rest_item_route( $post ); - if ( ! empty( $item_route ) ) { - $links['self'] = array( - 'href' => rest_url( $item_route ), - 'embeddable' => true, - ); - } - - $links['about'] = array( - 'href' => rest_url( 'wp/v2/types/' . $post->post_type ), - ); - - return $links; - } - - /** - * Overwrites the default protected title format. - * - * By default, WordPress will show password protected posts with a title of - * "Protected: %s". As the REST API communicates the protected status of a post - * in a machine readable format, we remove the "Protected: " prefix. - * - * @since 3.3.0 - * - * @return string Protected title format. - */ - public function protected_title_format() { - return '%s'; - } - - /** - * Attempts to detect the route to access a single item. - * - * @since 3.3.0 - * - * @param WP_Post $post Post object. - * @return string REST route relative to the REST base URI, or empty string if unknown. - */ - protected function detect_rest_item_route( $post ) { - $post_type = get_post_type_object( $post->post_type ); - if ( ! $post_type ) { - return ''; - } - - // It's currently impossible to detect the REST URL from a custom controller. - if ( ! empty( $post_type->rest_controller_class ) && 'WP_REST_Posts_Controller' !== $post_type->rest_controller_class ) { - return ''; - } - - $namespace = 'wp/v2'; - $rest_base = ! empty( $post_type->rest_base ) ? $post_type->rest_base : $post_type->name; - - return sprintf( '%s/%s/%d', $namespace, $rest_base, $post->ID ); - } -} diff --git a/lib/class-wp-rest-search-controller.php b/lib/class-wp-rest-search-controller.php deleted file mode 100644 index 69776c5a7be34..0000000000000 --- a/lib/class-wp-rest-search-controller.php +++ /dev/null @@ -1,363 +0,0 @@ -<?php -/** - * REST API: WP_REST_Search_Controller class - * - * @package gutenberg - * @since 3.3.0 - */ - -/** - * Core class to search through all WordPress content via the REST API. - * - * @since 3.3.0 - * - * @see WP_REST_Controller - */ -class WP_REST_Search_Controller extends WP_REST_Controller { - - /** - * ID property name. - */ - const PROP_ID = 'id'; - - /** - * Title property name. - */ - const PROP_TITLE = 'title'; - - /** - * URL property name. - */ - const PROP_URL = 'url'; - - /** - * Type property name. - */ - const PROP_TYPE = 'type'; - - /** - * Subtype property name. - */ - const PROP_SUBTYPE = 'subtype'; - - /** - * Identifier for the 'any' type. - */ - const TYPE_ANY = 'any'; - - /** - * Search handlers used by the controller. - * - * @since 3.3.0 - * @var array - */ - protected $search_handlers = array(); - - /** - * Constructor. - * - * @since 3.3.0 - * - * @param array $search_handlers List of search handlers to use in the controller. Each search - * handler instance must extend the `WP_REST_Search_Handler` class. - */ - public function __construct( array $search_handlers ) { - $this->namespace = 'wp/v2'; - $this->rest_base = 'search'; - - foreach ( $search_handlers as $search_handler ) { - if ( ! $search_handler instanceof WP_REST_Search_Handler ) { - - /* translators: %s: PHP class name */ - _doing_it_wrong( __METHOD__, sprintf( __( 'REST search handlers must extend the %s class.', 'gutenberg' ), 'WP_REST_Search_Handler' ), '3.3.0' ); - continue; - } - - $this->search_handlers[ $search_handler->get_type() ] = $search_handler; - } - } - - /** - * Registers the routes for the objects of the controller. - * - * @since 3.3.0 - * - * @see register_rest_route() - */ - public function register_routes() { - register_rest_route( - $this->namespace, - '/' . $this->rest_base, - array( - array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_items' ), - 'permission_callback' => array( $this, 'get_items_permission_check' ), - 'args' => $this->get_collection_params(), - ), - 'schema' => array( $this, 'get_public_item_schema' ), - ) - ); - } - - /** - * Checks if a given request has access to search content. - * - * @since 3.3.0 - * - * @param WP_REST_Request $request Full details about the request. - * @return true|WP_Error True if the request has search access, WP_Error object otherwise. - */ - public function get_items_permission_check( $request ) { - return true; - } - - /** - * Retrieves a collection of search results. - * - * @since 3.3.0 - * - * @param WP_REST_Request $request Full details about the request. - * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. - */ - public function get_items( $request ) { - $handler = $this->get_search_handler( $request ); - if ( is_wp_error( $handler ) ) { - return $handler; - } - - $result = $handler->search_items( $request ); - - if ( ! isset( $result[ WP_REST_Search_Handler::RESULT_IDS ] ) || ! is_array( $result[ WP_REST_Search_Handler::RESULT_IDS ] ) || ! isset( $result[ WP_REST_Search_Handler::RESULT_TOTAL ] ) ) { - return new WP_Error( 'rest_search_handler_error', __( 'Internal search handler error.', 'gutenberg' ), array( 'status' => 500 ) ); - } - - $ids = array_map( 'absint', $result[ WP_REST_Search_Handler::RESULT_IDS ] ); - - $results = array(); - foreach ( $ids as $id ) { - $data = $this->prepare_item_for_response( $id, $request ); - $results[] = $this->prepare_response_for_collection( $data ); - } - - $total = (int) $result[ WP_REST_Search_Handler::RESULT_TOTAL ]; - $page = (int) $request['page']; - $per_page = (int) $request['per_page']; - $max_pages = ceil( $total / $per_page ); - - if ( $page > $max_pages && $total > 0 ) { - return new WP_Error( 'rest_search_invalid_page_number', __( 'The page number requested is larger than the number of pages available.', 'gutenberg' ), array( 'status' => 400 ) ); - } - - $response = rest_ensure_response( $results ); - $response->header( 'X-WP-Total', $total ); - $response->header( 'X-WP-TotalPages', $max_pages ); - - $request_params = $request->get_query_params(); - $base = add_query_arg( $request_params, rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) ); - - if ( $page > 1 ) { - $prev_link = add_query_arg( 'page', $page - 1, $base ); - $response->link_header( 'prev', $prev_link ); - } - if ( $page < $max_pages ) { - $next_link = add_query_arg( 'page', $page + 1, $base ); - $response->link_header( 'next', $next_link ); - } - - return $response; - } - - /** - * Prepares a single search result for response. - * - * @since 3.3.0 - * - * @param int $id ID of the item to prepare. - * @param WP_REST_Request $request Request object. - * @return WP_REST_Response Response object. - */ - public function prepare_item_for_response( $id, $request ) { - $handler = $this->get_search_handler( $request ); - if ( is_wp_error( $handler ) ) { - return new WP_REST_Response(); - } - - if ( method_exists( $this, 'get_fields_for_response' ) ) { - $fields = $this->get_fields_for_response( $request ); - } else { - $schema = $this->get_item_schema(); - $fields = array_keys( $schema['properties'] ); - } - - $data = $handler->prepare_item( $id, $fields ); - $data = $this->add_additional_fields_to_object( $data, $request ); - - $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; - $data = $this->filter_response_by_context( $data, $context ); - - $response = rest_ensure_response( $data ); - - $links = $handler->prepare_item_links( $id ); - $links['collection'] = array( - 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ), - ); - $response->add_links( $links ); - - return $response; - } - - /** - * Retrieves the item schema, conforming to JSON Schema. - * - * @since 3.3.0 - * - * @return array Item schema data. - */ - public function get_item_schema() { - $types = array(); - $subtypes = array(); - foreach ( $this->search_handlers as $search_handler ) { - $types[] = $search_handler->get_type(); - $subtypes = array_merge( $subtypes, $search_handler->get_subtypes() ); - } - - $types = array_unique( $types ); - $subtypes = array_unique( $subtypes ); - - $schema = array( - '$schema' => 'http://json-schema.org/draft-04/schema#', - 'title' => 'search-result', - 'type' => 'object', - 'properties' => array( - self::PROP_ID => array( - 'description' => __( 'Unique identifier for the object.', 'gutenberg' ), - 'type' => 'integer', - 'context' => array( 'view', 'embed' ), - 'readonly' => true, - ), - self::PROP_TITLE => array( - 'description' => __( 'The title for the object.', 'gutenberg' ), - 'type' => 'string', - 'context' => array( 'view', 'embed' ), - 'readonly' => true, - ), - self::PROP_URL => array( - 'description' => __( 'URL to the object.', 'gutenberg' ), - 'type' => 'string', - 'format' => 'uri', - 'context' => array( 'view', 'embed' ), - 'readonly' => true, - ), - self::PROP_TYPE => array( - 'description' => __( 'Object type.', 'gutenberg' ), - 'type' => 'string', - 'enum' => $types, - 'context' => array( 'view', 'embed' ), - 'readonly' => true, - ), - self::PROP_SUBTYPE => array( - 'description' => __( 'Object subtype.', 'gutenberg' ), - 'type' => 'string', - 'enum' => $subtypes, - 'context' => array( 'view', 'embed' ), - 'readonly' => true, - ), - ), - ); - - return $this->add_additional_fields_schema( $schema ); - } - - /** - * Retrieves the query params for the search results collection. - * - * @since 3.3.0 - * - * @return array Collection parameters. - */ - public function get_collection_params() { - $types = array(); - $subtypes = array(); - foreach ( $this->search_handlers as $search_handler ) { - $types[] = $search_handler->get_type(); - $subtypes = array_merge( $subtypes, $search_handler->get_subtypes() ); - } - - $types = array_unique( $types ); - $subtypes = array_unique( $subtypes ); - - $query_params = parent::get_collection_params(); - - $query_params['context']['default'] = 'view'; - - $query_params[ self::PROP_TYPE ] = array( - 'default' => $types[0], - 'description' => __( 'Limit results to items of an object type.', 'gutenberg' ), - 'type' => 'string', - 'enum' => $types, - ); - - $query_params[ self::PROP_SUBTYPE ] = array( - 'default' => self::TYPE_ANY, - 'description' => __( 'Limit results to items of one or more object subtypes.', 'gutenberg' ), - 'type' => 'array', - 'items' => array( - 'enum' => array_merge( $subtypes, array( self::TYPE_ANY ) ), - 'type' => 'string', - ), - 'sanitize_callback' => array( $this, 'sanitize_subtypes' ), - ); - - return $query_params; - } - - /** - * Sanitizes the list of subtypes, to ensure only subtypes of the passed type are included. - * - * @since 3.3.0 - * - * @param string|array $subtypes One or more subtypes. - * @param WP_REST_Request $request Full details about the request. - * @param string $parameter Parameter name. - * @return array|WP_Error List of valid subtypes, or WP_Error object on failure. - */ - public function sanitize_subtypes( $subtypes, $request, $parameter ) { - $subtypes = wp_parse_slug_list( $subtypes ); - - $subtypes = rest_parse_request_arg( $subtypes, $request, $parameter ); - if ( is_wp_error( $subtypes ) ) { - return $subtypes; - } - - // 'any' overrides any other subtype. - if ( in_array( self::TYPE_ANY, $subtypes, true ) ) { - return array( self::TYPE_ANY ); - } - - $handler = $this->get_search_handler( $request ); - if ( is_wp_error( $handler ) ) { - return $handler; - } - - return array_intersect( $subtypes, $handler->get_subtypes() ); - } - - /** - * Gets the search handler to handle the current request. - * - * @since 3.3.0 - * - * @param WP_REST_Request $request Full details about the request. - * @return WP_REST_Search_Handler|WP_Error Search handler for the request type, or WP_Error object on failure. - */ - protected function get_search_handler( $request ) { - $type = $request->get_param( self::PROP_TYPE ); - - if ( ! $type || ! isset( $this->search_handlers[ $type ] ) ) { - return new WP_Error( 'rest_search_invalid_type', __( 'Invalid type parameter.', 'gutenberg' ), array( 'status' => 400 ) ); - } - - return $this->search_handlers[ $type ]; - } -} diff --git a/lib/class-wp-rest-search-handler.php b/lib/class-wp-rest-search-handler.php deleted file mode 100644 index 6c5351d45b002..0000000000000 --- a/lib/class-wp-rest-search-handler.php +++ /dev/null @@ -1,96 +0,0 @@ -<?php -/** - * REST API: WP_REST_Search_Handler class - * - * @package gutenberg - * @since 3.3.0 - */ - -/** - * Core base class representing a search handler for an object type in the REST API. - * - * @since 3.3.0 - */ -abstract class WP_REST_Search_Handler { - - /** - * Field containing the IDs in the search result. - */ - const RESULT_IDS = 'ids'; - - /** - * Field containing the total count in the search result. - */ - const RESULT_TOTAL = 'total'; - - /** - * Object type managed by this search handler. - * - * @since 3.3.0 - * @var string - */ - protected $type = ''; - - /** - * Object subtypes managed by this search handler. - * - * @since 3.3.0 - * @var array - */ - protected $subtypes = array(); - - /** - * Gets the object type managed by this search handler. - * - * @since 3.3.0 - * - * @return string Object type identifier. - */ - public function get_type() { - return $this->type; - } - - /** - * Gets the object subtypes managed by this search handler. - * - * @since 3.3.0 - * - * @return array Array of object subtype identifiers. - */ - public function get_subtypes() { - return $this->subtypes; - } - - /** - * Searches the object type content for a given search request. - * - * @since 3.3.0 - * - * @param WP_REST_Request $request Full REST request. - * @return array Associative array containing an `WP_REST_Search_Handler::RESULT_IDS` containing - * an array of found IDs and `WP_REST_Search_Handler::RESULT_TOTAL` containing the - * total count for the matching search results. - */ - abstract public function search_items( WP_REST_Request $request ); - - /** - * Prepares the search result for a given ID. - * - * @since 3.3.0 - * - * @param int $id Item ID. - * @param array $fields Fields to include for the item. - * @return array Associative array containing all fields for the item. - */ - abstract public function prepare_item( $id, array $fields ); - - /** - * Prepares links for the search result of a given ID. - * - * @since 3.3.0 - * - * @param int $id Item ID. - * @return array Links for the given item. - */ - abstract public function prepare_item_links( $id ); -} diff --git a/lib/class-wp-rest-themes-controller.php b/lib/class-wp-rest-themes-controller.php deleted file mode 100644 index b2ddf4e0e3910..0000000000000 --- a/lib/class-wp-rest-themes-controller.php +++ /dev/null @@ -1,237 +0,0 @@ -<?php -/** - * REST API: WP_REST_Themes_Controller class - * - * @package WordPress - * @subpackage REST_API - * @since 5.0.0 - */ - -/** - * Core class used to manage themes via the REST API. - * - * @since 5.0.0 - * - * @see WP_REST_Controller - */ -class WP_REST_Themes_Controller extends WP_REST_Controller { - - /** - * Constructor. - * - * @since 5.0.0 - */ - public function __construct() { - $this->namespace = 'wp/v2'; - $this->rest_base = 'themes'; - } - - /** - * Registers the routes for the objects of the controller. - * - * @since 5.0.0 - * - * @see register_rest_route() - */ - public function register_routes() { - register_rest_route( - $this->namespace, - '/' . $this->rest_base, - array( - array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_items' ), - 'permission_callback' => array( $this, 'get_items_permissions_check' ), - 'args' => $this->get_collection_params(), - ), - 'schema' => array( $this, 'get_item_schema' ), - ) - ); - } - - /** - * Checks if a given request has access to read the theme. - * - * @since 5.0.0 - * - * @param WP_REST_Request $request Full details about the request. - * @return true|WP_Error True if the request has read access for the item, otherwise WP_Error object. - */ - public function get_items_permissions_check( $request ) { - if ( ! is_user_logged_in() || ! current_user_can( 'edit_posts' ) ) { - return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you are not allowed to view themes.', 'gutenberg' ), array( 'status' => rest_authorization_required_code() ) ); - } - - return true; - } - - /** - * Retrieves a collection of themes. - * - * @since 5.0.0 - * - * @param WP_REST_Request $request Full details about the request. - * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. - */ - public function get_items( $request ) { - // Retrieve the list of registered collection query parameters. - $registered = $this->get_collection_params(); - $themes = array(); - - if ( isset( $registered['status'], $request['status'] ) && in_array( 'active', $request['status'], true ) ) { - $active_theme = wp_get_theme(); - $active_theme = $this->prepare_item_for_response( $active_theme, $request ); - $themes[] = $this->prepare_response_for_collection( $active_theme ); - } - - $response = rest_ensure_response( $themes ); - - $response->header( 'X-WP-Total', count( $themes ) ); - $response->header( 'X-WP-TotalPages', count( $themes ) ); - - return $response; - } - - /** - * Prepares a single theme output for response. - * - * @since 5.0.0 - * - * @param WP_Theme $theme Theme object. - * @param WP_REST_Request $request Request object. - * @return WP_REST_Response Response object. - */ - public function prepare_item_for_response( $theme, $request ) { - $data = array(); - $fields = $this->get_fields_for_response( $request ); - - if ( in_array( 'theme_supports', $fields, true ) ) { - $formats = get_theme_support( 'post-formats' ); - $formats = is_array( $formats ) ? array_values( $formats[0] ) : array(); - $formats = array_merge( array( 'standard' ), $formats ); - $data['theme_supports']['formats'] = $formats; - - $data['theme_supports']['post-thumbnails'] = false; - $data['theme_supports']['responsive-embeds'] = (bool) get_theme_support( 'responsive-embeds' ); - $post_thumbnails = get_theme_support( 'post-thumbnails' ); - - if ( $post_thumbnails ) { - // $post_thumbnails can contain a nested array of post types. - // e.g. array( array( 'post', 'page' ) ). - $data['theme_supports']['post-thumbnails'] = is_array( $post_thumbnails ) ? $post_thumbnails[0] : true; - } - } - - $data = $this->add_additional_fields_to_object( $data, $request ); - - // Wrap the data in a response object. - $response = rest_ensure_response( $data ); - - /** - * Filters theme data returned from the REST API. - * - * @since 5.0.0 - * - * @param WP_REST_Response $response The response object. - * @param WP_Theme $theme Theme object used to create response. - * @param WP_REST_Request $request Request object. - */ - return apply_filters( 'rest_prepare_theme', $response, $theme, $request ); - } - - /** - * Retrieves the theme's schema, conforming to JSON Schema. - * - * @since 5.0.0 - * - * @return array Item schema data. - */ - public function get_item_schema() { - $schema = array( - '$schema' => 'http://json-schema.org/draft-04/schema#', - 'title' => 'theme', - 'type' => 'object', - 'properties' => array( - 'theme_supports' => array( - 'description' => __( 'Features supported by this theme.', 'gutenberg' ), - 'type' => 'array', - 'readonly' => true, - 'properties' => array( - 'formats' => array( - 'description' => __( 'Post formats supported.', 'gutenberg' ), - 'type' => 'array', - 'readonly' => true, - ), - 'post-thumbnails' => array( - 'description' => __( 'Whether the theme supports post thumbnails.', 'gutenberg' ), - 'type' => array( 'array', 'bool' ), - 'readonly' => true, - ), - 'responsive-embeds' => array( - 'description' => __( 'Whether the theme supports responsive embedded content.', 'gutenberg' ), - 'type' => 'bool', - 'readonly' => true, - ), - ), - ), - ), - ); - - return $this->add_additional_fields_schema( $schema ); - } - - /** - * Retrieves the search params for the themes collection. - * - * @since 5.0.0 - * - * @return array Collection parameters. - */ - public function get_collection_params() { - $query_params = parent::get_collection_params(); - - $query_params['status'] = array( - 'description' => __( 'Limit result set to themes assigned one or more statuses.', 'gutenberg' ), - 'type' => 'array', - 'items' => array( - 'enum' => array( 'active' ), - 'type' => 'string', - ), - 'required' => true, - 'sanitize_callback' => array( $this, 'sanitize_theme_status' ), - ); - - /** - * Filter collection parameters for the themes controller. - * - * @since 5.0.0 - * - * @param array $query_params JSON Schema-formatted collection parameters. - */ - return apply_filters( 'rest_themes_collection_params', $query_params ); - } - - /** - * Sanitizes and validates the list of theme status. - * - * @since 5.0.0 - * - * @param string|array $statuses One or more theme statuses. - * @param WP_REST_Request $request Full details about the request. - * @param string $parameter Additional parameter to pass to validation. - * @return array|WP_Error A list of valid statuses, otherwise WP_Error object. - */ - public function sanitize_theme_status( $statuses, $request, $parameter ) { - $statuses = wp_parse_slug_list( $statuses ); - - foreach ( $statuses as $status ) { - $result = rest_validate_request_arg( $status, $request, $parameter ); - - if ( is_wp_error( $result ) ) { - return $result; - } - } - - return $statuses; - } -} diff --git a/lib/load.php b/lib/load.php index 034b3c9e83ceb..8306f6f030f83 100644 --- a/lib/load.php +++ b/lib/load.php @@ -12,28 +12,6 @@ // These files only need to be loaded if within a rest server instance // which this class will exist if that is the case. if ( class_exists( 'WP_REST_Controller' ) ) { - if ( ! class_exists( 'WP_REST_Blocks_Controller' ) ) { - require dirname( __FILE__ ) . '/class-wp-rest-blocks-controller.php'; - } - if ( ! class_exists( 'WP_REST_Autosaves_Controller' ) ) { - require dirname( __FILE__ ) . '/class-wp-rest-autosaves-controller.php'; - } - if ( ! class_exists( 'WP_REST_Themes_Controller' ) ) { - require dirname( __FILE__ ) . '/class-wp-rest-themes-controller.php'; - } - if ( ! class_exists( 'WP_REST_Block_Renderer_Controller' ) ) { - require dirname( __FILE__ ) . '/class-wp-rest-block-renderer-controller.php'; - } - if ( ! class_exists( 'WP_REST_Search_Controller' ) ) { - require dirname( __FILE__ ) . '/class-wp-rest-search-controller.php'; - } - if ( ! class_exists( 'WP_REST_Search_Handler' ) ) { - require dirname( __FILE__ ) . '/class-wp-rest-search-handler.php'; - } - if ( ! class_exists( 'WP_REST_Post_Search_Handler' ) ) { - require dirname( __FILE__ ) . '/class-wp-rest-post-search-handler.php'; - } - require dirname( __FILE__ ) . '/rest-api.php'; } diff --git a/lib/rest-api.php b/lib/rest-api.php index 2baec1befdd19..616493a371219 100644 --- a/lib/rest-api.php +++ b/lib/rest-api.php @@ -14,42 +14,11 @@ * Registers the REST API routes needed by the Gutenberg editor. * * @since 2.8.0 + * @deprecated 5.0.0 */ function gutenberg_register_rest_routes() { - $controller = new WP_REST_Block_Renderer_Controller(); - $controller->register_routes(); - - /** - * Filters the search handlers to use in the REST search controller. - * - * @since 3.3.0 - * - * @param array $search_handlers List of search handlers to use in the controller. Each search - * handler instance must extend the `WP_REST_Search_Handler` class. - * Default is only a handler for posts. - */ - $search_handlers = apply_filters( 'wp_rest_search_handlers', array( new WP_REST_Post_Search_Handler() ) ); - - $controller = new WP_REST_Search_Controller( $search_handlers ); - $controller->register_routes(); - - foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) { - $class = ! empty( $post_type->rest_controller_class ) ? $post_type->rest_controller_class : 'WP_REST_Posts_Controller'; - - // Check if the class exists and is a subclass of WP_REST_Controller. - if ( ! is_subclass_of( $class, 'WP_REST_Controller' ) ) { - continue; - } - - // Initialize the Autosaves controller. - $autosaves_controller = new WP_REST_Autosaves_Controller( $post_type->name ); - $autosaves_controller->register_routes(); - } - - $themes_controller = new WP_REST_Themes_Controller(); - $themes_controller->register_routes(); + _deprecated_function( __FUNCTION__, '5.0.0' ); } -add_action( 'rest_api_init', 'gutenberg_register_rest_routes' ); /** * Make sure oEmbed REST Requests apply the WP Embed security mechanism for WordPress embeds. @@ -120,105 +89,38 @@ function gutenberg_filter_oembed_result( $response, $handler, $request ) { * Used so private taxonomies are not displayed in the UI. * * @see https://core.trac.wordpress.org/ticket/42707 + * @deprecated 5.0.0 */ function gutenberg_add_taxonomy_visibility_field() { - register_rest_field( - 'taxonomy', - 'visibility', - array( - 'get_callback' => 'gutenberg_get_taxonomy_visibility_data', - 'schema' => array( - 'description' => __( 'The visibility settings for the taxonomy.', 'gutenberg' ), - 'type' => 'object', - 'context' => array( 'edit' ), - 'readonly' => true, - 'properties' => array( - 'public' => array( - 'description' => __( 'Whether a taxonomy is intended for use publicly either via the admin interface or by front-end users.', 'gutenberg' ), - 'type' => 'boolean', - ), - 'publicly_queryable' => array( - 'description' => __( 'Whether the taxonomy is publicly queryable.', 'gutenberg' ), - 'type' => 'boolean', - ), - 'show_ui' => array( - 'description' => __( 'Whether to generate a default UI for managing this taxonomy.', 'gutenberg' ), - 'type' => 'boolean', - ), - 'show_admin_column' => array( - 'description' => __( 'Whether to allow automatic creation of taxonomy columns on associated post-types table.', 'gutenberg' ), - 'type' => 'boolean', - ), - 'show_in_nav_menus' => array( - 'description' => __( 'Whether to make the taxonomy available for selection in navigation menus.', 'gutenberg' ), - 'type' => 'boolean', - ), - 'show_in_quick_edit' => array( - 'description' => __( 'Whether to show the taxonomy in the quick/bulk edit panel.', 'gutenberg' ), - 'type' => 'boolean', - ), - ), - ), - ) - ); + _deprecated_function( __FUNCTION__, '5.0.0' ); } /** * Gets taxonomy visibility property data. * * @see https://core.trac.wordpress.org/ticket/42707 + * @deprecated 5.0.0 * * @param array $object Taxonomy data from REST API. * @return array Array of taxonomy visibility data. */ function gutenberg_get_taxonomy_visibility_data( $object ) { - // Just return existing data for up-to-date Core. - if ( isset( $object['visibility'] ) ) { - return $object['visibility']; - } + _deprecated_function( __FUNCTION__, '5.0.0' ); - $taxonomy = get_taxonomy( $object['slug'] ); - - return array( - 'public' => $taxonomy->public, - 'publicly_queryable' => $taxonomy->publicly_queryable, - 'show_ui' => $taxonomy->show_ui, - 'show_admin_column' => $taxonomy->show_admin_column, - 'show_in_nav_menus' => $taxonomy->show_in_nav_menus, - 'show_in_quick_edit' => $taxonomy->show_ui, - ); + return isset( $object['visibility'] ) ? $object['visibility'] : array(); } -add_action( 'rest_api_init', 'gutenberg_add_taxonomy_visibility_field' ); - /** * Add a permalink template to posts in the post REST API response. * * @see https://core.trac.wordpress.org/ticket/45017 + * @deprecated 5.0.0 * * @param WP_REST_Response $response WP REST API response of a post. - * @param WP_Post $post The post being returned. - * @param WP_REST_Request $request WP REST API request. * @return WP_REST_Response Response containing the permalink_template. */ -function gutenberg_add_permalink_template_to_posts( $response, $post, $request ) { - if ( 'edit' !== $request['context'] ) { - return $response; - } - - $post_type_obj = get_post_type_object( $post->post_type ); - if ( ! is_post_type_viewable( $post_type_obj ) || ! $post_type_obj->public ) { - return $response; - } - - if ( ! function_exists( 'get_sample_permalink' ) ) { - require_once ABSPATH . '/wp-admin/includes/post.php'; - } - - $sample_permalink = get_sample_permalink( $post->ID, $post->post_title, '' ); - - $response->data['permalink_template'] = $sample_permalink[0]; - $response->data['generated_slug'] = $sample_permalink[1]; +function gutenberg_add_permalink_template_to_posts( $response ) { + _deprecated_function( __FUNCTION__, '5.0.0' ); return $response; } @@ -226,25 +128,14 @@ function gutenberg_add_permalink_template_to_posts( $response, $post, $request ) /** * Add the block format version to post content in the post REST API response. * - * @todo This will need to be registered to the schema too. - * * @see https://core.trac.wordpress.org/ticket/43887 + * @deprecated 5.0.0 * * @param WP_REST_Response $response WP REST API response of a post. - * @param WP_Post $post The post being returned. - * @param WP_REST_Request $request WP REST API request. * @return WP_REST_Response Response containing the block_format. */ -function gutenberg_add_block_format_to_post_content( $response, $post, $request ) { - if ( 'edit' !== $request['context'] ) { - return $response; - } - - $response_data = $response->get_data(); - if ( isset( $response_data['content'] ) && is_array( $response_data['content'] ) && isset( $response_data['content']['raw'] ) ) { - $response_data['content']['block_format'] = gutenberg_content_block_version( $response_data['content']['raw'] ); - $response->set_data( $response_data ); - } +function gutenberg_add_block_format_to_post_content( $response ) { + _deprecated_function( __FUNCTION__, '5.0.0' ); return $response; } @@ -253,100 +144,52 @@ function gutenberg_add_block_format_to_post_content( $response, $post, $request * Include target schema attributes to links, based on whether the user can. * * @see https://core.trac.wordpress.org/ticket/45014 + * @deprecated 5.0.0 * * @param WP_REST_Response $response WP REST API response of a post. - * @param WP_Post $post The post being returned. - * @param WP_REST_Request $request WP REST API request. * @return WP_REST_Response Response containing the new links. */ -function gutenberg_add_target_schema_to_links( $response, $post, $request ) { - $new_links = array(); - $orig_links = $response->get_links(); - $post_type = get_post_type_object( $post->post_type ); - $orig_href = ! empty( $orig_links['self'][0]['href'] ) ? $orig_links['self'][0]['href'] : null; - if ( 'edit' === $request['context'] && current_user_can( 'unfiltered_html' ) ) { - $new_links['https://api.w.org/action-unfiltered-html'] = array( - array( - 'title' => __( 'The current user can post HTML markup and JavaScript.', 'gutenberg' ), - 'href' => $orig_href, - 'targetSchema' => array( - 'type' => 'object', - 'properties' => array( - 'unfiltered_html' => array( - 'type' => 'boolean', - ), - ), - ), - ), - ); - } +function gutenberg_add_target_schema_to_links( $response ) { + _deprecated_function( __FUNCTION__, '5.0.0' ); - $response->add_links( $new_links ); return $response; } /** * Whenever a post type is registered, ensure we're hooked into it's WP REST API response. * + * @deprecated 5.0.0 + * * @param string $post_type The newly registered post type. * @return string That same post type. */ function gutenberg_register_post_prepare_functions( $post_type ) { - add_filter( "rest_prepare_{$post_type}", 'gutenberg_add_permalink_template_to_posts', 10, 3 ); - add_filter( "rest_prepare_{$post_type}", 'gutenberg_add_block_format_to_post_content', 10, 3 ); - add_filter( "rest_prepare_{$post_type}", 'gutenberg_add_target_schema_to_links', 10, 3 ); + _deprecated_function( __FUNCTION__, '5.0.0' ); + return $post_type; } -add_filter( 'registered_post_type', 'gutenberg_register_post_prepare_functions' ); - /** * Silence PHP Warnings and Errors in JSON requests * - * @todo This is a temporary measure until errors are properly silenced in REST API responses in core - * * @see https://core.trac.wordpress.org/ticket/44534 + * @deprecated 5.0.0 */ function gutenberg_silence_rest_errors() { - - if ( ( isset( $_SERVER['CONTENT_TYPE'] ) && 'application/json' === $_SERVER['CONTENT_TYPE'] ) || - ( isset( $_SERVER['HTTP_ACCEPT'] ) && strpos( $_SERVER['HTTP_ACCEPT'], 'application/json' ) !== false ) ) { - // @codingStandardsIgnoreStart - @ini_set( 'display_errors', 0 ); - // @codingStandardsIgnoreEnd - } - + _deprecated_function( __FUNCTION__, '5.0.0' ); } /** * Include additional labels for registered post types * * @see https://core.trac.wordpress.org/ticket/45101 + * @deprecated 5.0.0 * - * @param array $args Arguments supplied to register_post_type(). - * @param string $post_type Post type key. + * @param array $args Arguments supplied to register_post_type(). * @return array Arguments supplied to register_post_type() */ -function gutenberg_filter_post_type_labels( $args, $post_type ) { - $registered_labels = ( empty( $args['labels'] ) ) ? array() : $args['labels']; - if ( is_post_type_hierarchical( $post_type ) ) { - $labels = array( - 'item_published' => __( 'Page published.', 'gutenberg' ), - 'item_published_privately' => __( 'Page published privately.', 'gutenberg' ), - 'item_reverted_to_draft' => __( 'Page reverted to draft.', 'gutenberg' ), - 'item_scheduled' => __( 'Page scheduled.', 'gutenberg' ), - 'item_updated' => __( 'Page updated.', 'gutenberg' ), - ); - } else { - $labels = array( - 'item_published' => __( 'Post published.', 'gutenberg' ), - 'item_published_privately' => __( 'Post published privately.', 'gutenberg' ), - 'item_reverted_to_draft' => __( 'Post reverted to draft.', 'gutenberg' ), - 'item_scheduled' => __( 'Post scheduled.', 'gutenberg' ), - 'item_updated' => __( 'Post updated.', 'gutenberg' ), - ); - } - $args['labels'] = array_merge( $labels, $registered_labels ); +function gutenberg_filter_post_type_labels( $args ) { + _deprecated_function( __FUNCTION__, '5.0.0' ); + return $args; } -add_filter( 'register_post_type_args', 'gutenberg_filter_post_type_labels', 10, 2 ); diff --git a/phpunit/bootstrap.php b/phpunit/bootstrap.php index 80d806ee4452a..6043dd1bbd037 100644 --- a/phpunit/bootstrap.php +++ b/phpunit/bootstrap.php @@ -36,7 +36,6 @@ function _manually_load_plugin() { // Require dummy block type class for testing. require_once dirname( __FILE__ ) . '/class-wp-dummy-block-type.php'; - require_once dirname( __FILE__ ) . '/class-wp-rest-dummy-search-handler.php'; } tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' ); diff --git a/phpunit/class-gutenberg-rest-api-test.php b/phpunit/class-gutenberg-rest-api-test.php deleted file mode 100644 index 293dc7fed3526..0000000000000 --- a/phpunit/class-gutenberg-rest-api-test.php +++ /dev/null @@ -1,427 +0,0 @@ -<?php -/** - * WP_Block_Type_Registry Tests - * - * @package Gutenberg - */ - -/** - * Tests for WP_Block_Type_Registry - */ -class Gutenberg_REST_API_Test extends WP_Test_REST_TestCase { - function setUp() { - parent::setUp(); - - $this->administrator = $this->factory->user->create( - array( - 'role' => 'administrator', - ) - ); - $this->author = $this->factory->user->create( - array( - 'role' => 'author', - ) - ); - $this->editor = $this->factory->user->create( - array( - 'role' => 'editor', - ) - ); - $this->contributor = $this->factory->user->create( - array( - 'role' => 'contributor', - ) - ); - $this->subscriber = $this->factory->user->create( - array( - 'role' => 'subscriber', - 'display_name' => 'subscriber', - 'user_email' => 'subscriber@example.com', - ) - ); - } - - function tearDown() { - parent::tearDown(); - } - - /** - * Should return an extra visibility field on response when in edit context. - */ - function test_visibility_field() { - $request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies/category' ); - $request->set_param( 'context', 'edit' ); - - $permitted_users = array( - $this->administrator, - $this->editor, - $this->author, - ); - - foreach ( $permitted_users as $user ) { - wp_set_current_user( $user ); - - $response = rest_do_request( $request ); - $result = $response->get_data(); - - $this->assertTrue( isset( $result['visibility'] ) ); - $this->assertInternalType( 'array', $result['visibility'] ); - $this->assertArrayHasKey( 'public', $result['visibility'] ); - $this->assertArrayHasKey( 'publicly_queryable', $result['visibility'] ); - $this->assertArrayHasKey( 'show_ui', $result['visibility'] ); - $this->assertArrayHasKey( 'show_admin_column', $result['visibility'] ); - $this->assertArrayHasKey( 'show_in_nav_menus', $result['visibility'] ); - $this->assertArrayHasKey( 'show_in_quick_edit', $result['visibility'] ); - } - } - - /** - * Should not return an extra visibility field without context set. - */ - function test_visibility_field_without_context() { - $request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies/category' ); - $response = rest_do_request( $request ); - - $result = $response->get_data(); - - $this->assertFalse( isset( $result['visibility'] ) ); - } - - /** - * Should return an extra viewable field on response when in edit context. - */ - function test_viewable_field() { - wp_set_current_user( $this->administrator ); - $request = new WP_REST_Request( 'GET', '/wp/v2/types/post' ); - $request->set_param( 'context', 'edit' ); - $response = rest_do_request( $request ); - $result = $response->get_data(); - $this->assertTrue( isset( $result['viewable'] ) ); - $this->assertTrue( $result['viewable'] ); - } - - /** - * Should not return viewable field without context set. - */ - function test_viewable_field_without_context() { - $request = new WP_REST_Request( 'GET', '/wp/v2/types/post' ); - $response = rest_do_request( $request ); - $result = $response->get_data(); - $this->assertFalse( isset( $result['viewable'] ) ); - } - - /** - * Only returns wp:action-unfiltered_html when current user can use unfiltered HTML. - * See https://codex.wordpress.org/Roles_and_Capabilities#Capability_vs._Role_Table - */ - function test_link_unfiltered_html() { - $post_id = $this->factory->post->create(); - $check_key = 'https://api.w.org/action-unfiltered-html'; - // admins can in a single site, but can't in a multisite. - wp_set_current_user( $this->administrator ); - $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $post_id ); - $request->set_param( 'context', 'edit' ); - $response = rest_do_request( $request ); - $links = $response->get_links(); - if ( is_multisite() ) { - $this->assertFalse( isset( $links[ $check_key ] ) ); - } else { - $this->assertTrue( isset( $links[ $check_key ] ) ); - } - // authors can't. - wp_set_current_user( $this->author ); - $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $post_id ); - $request->set_param( 'context', 'edit' ); - $response = rest_do_request( $request ); - $links = $response->get_links(); - $this->assertFalse( isset( $links[ $check_key ] ) ); - // editors can in a single site, but can't in a multisite. - wp_set_current_user( $this->editor ); - $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $post_id ); - $request->set_param( 'context', 'edit' ); - $response = rest_do_request( $request ); - $links = $response->get_links(); - if ( is_multisite() ) { - $this->assertFalse( isset( $links[ $check_key ] ) ); - } else { - $this->assertTrue( isset( $links[ $check_key ] ) ); - } - // contributors can't. - wp_set_current_user( $this->contributor ); - $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $post_id ); - $request->set_param( 'context', 'edit' ); - $response = rest_do_request( $request ); - $links = $response->get_links(); - $this->assertFalse( isset( $links[ $check_key ] ) ); - } - - /** - * Only returns wp:action-assign-author when current user can assign author. - */ - function test_link_assign_author_only_appears_for_editor() { - $post_id = $this->factory->post->create(); - $check_key = 'https://api.w.org/action-assign-author'; - // authors cannot assign author. - wp_set_current_user( $this->author ); - $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $post_id ); - $request->set_param( 'context', 'edit' ); - $response = rest_do_request( $request ); - $links = $response->get_links(); - $this->assertFalse( isset( $links[ $check_key ] ) ); - // editors can assign author. - wp_set_current_user( $this->editor ); - $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $post_id ); - $request->set_param( 'context', 'edit' ); - $response = rest_do_request( $request ); - $links = $response->get_links(); - $this->assertTrue( isset( $links[ $check_key ] ) ); - // editors can assign author but not included for context != edit. - wp_set_current_user( $this->editor ); - $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $post_id ); - $request->set_param( 'context', 'view' ); - $response = rest_do_request( $request ); - $links = $response->get_links(); - $this->assertFalse( isset( $links[ $check_key ] ) ); - } - - /** - * Only returns wp:action-publish when current user can publish. - */ - function test_link_publish_only_appears_for_author() { - $post_id = $this->factory->post->create( - array( - 'post_author' => $this->author, - ) - ); - $check_key = 'https://api.w.org/action-publish'; - // contributors cannot sticky. - wp_set_current_user( $this->contributor ); - $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $post_id ); - $request->set_param( 'context', 'edit' ); - $response = rest_do_request( $request ); - $links = $response->get_links(); - $this->assertFalse( isset( $links[ $check_key ] ) ); - // authors can publish. - wp_set_current_user( $this->author ); - $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $post_id ); - $request->set_param( 'context', 'edit' ); - $response = rest_do_request( $request ); - $links = $response->get_links(); - $this->assertTrue( isset( $links[ $check_key ] ) ); - // authors can publish but not included for context != edit. - wp_set_current_user( $this->author ); - $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $post_id ); - $request->set_param( 'context', 'view' ); - $response = rest_do_request( $request ); - $links = $response->get_links(); - $this->assertFalse( isset( $links[ $check_key ] ) ); - } - - /** - * Only returns wp:action-sticky when current user can sticky. - */ - function test_link_sticky_only_appears_for_editor() { - $post_id = $this->factory->post->create(); - $check_key = 'https://api.w.org/action-sticky'; - // authors cannot sticky. - wp_set_current_user( $this->author ); - $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $post_id ); - $request->set_param( 'context', 'edit' ); - $response = rest_do_request( $request ); - $links = $response->get_links(); - $this->assertFalse( isset( $links[ $check_key ] ) ); - // editors can sticky. - wp_set_current_user( $this->editor ); - $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $post_id ); - $request->set_param( 'context', 'edit' ); - $response = rest_do_request( $request ); - $links = $response->get_links(); - $this->assertTrue( isset( $links[ $check_key ] ) ); - // editors can sticky but not included for context != edit. - wp_set_current_user( $this->editor ); - $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $post_id ); - $request->set_param( 'context', 'view' ); - $response = rest_do_request( $request ); - $links = $response->get_links(); - $this->assertFalse( isset( $links[ $check_key ] ) ); - } - - /** - * Only returns term-related actions when current user can do so. - */ - function test_link_term_management_per_user() { - $contributor_post = $this->factory->post->create( - array( - 'post_author' => $this->contributor, - 'post_status' => 'draft', - ) - ); - $author_post = $this->factory->post->create( - array( - 'post_author' => $this->author, - ) - ); - $create_tags = 'https://api.w.org/action-create-tags'; - $assign_tags = 'https://api.w.org/action-assign-tags'; - $create_categories = 'https://api.w.org/action-create-categories'; - $assign_categories = 'https://api.w.org/action-assign-categories'; - // Contributors can create and assign tags, but only assign categories. - wp_set_current_user( $this->contributor ); - $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $contributor_post ); - $request->set_param( 'context', 'edit' ); - $response = rest_do_request( $request ); - $links = $response->get_links(); - $this->assertTrue( isset( $links[ $create_tags ] ) ); - $this->assertTrue( isset( $links[ $assign_tags ] ) ); - $this->assertFalse( isset( $links[ $create_categories ] ) ); - $this->assertTrue( isset( $links[ $assign_categories ] ) ); - // Authors can create and assign tags, but only assign categories. - wp_set_current_user( $this->author ); - $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $author_post ); - $request->set_param( 'context', 'edit' ); - $response = rest_do_request( $request ); - $links = $response->get_links(); - $this->assertTrue( isset( $links[ $create_tags ] ) ); - $this->assertTrue( isset( $links[ $assign_tags ] ) ); - $this->assertFalse( isset( $links[ $create_categories ] ) ); - $this->assertTrue( isset( $links[ $assign_categories ] ) ); - // Editors can do everything. - wp_set_current_user( $this->editor ); - $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $author_post ); - $request->set_param( 'context', 'edit' ); - $response = rest_do_request( $request ); - $links = $response->get_links(); - $this->assertTrue( isset( $links[ $create_tags ] ) ); - $this->assertTrue( isset( $links[ $assign_tags ] ) ); - $this->assertTrue( isset( $links[ $create_categories ] ) ); - $this->assertTrue( isset( $links[ $assign_categories ] ) ); - } - - public function test_get_taxonomies_context_edit() { - wp_set_current_user( $this->contributor ); - $request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies' ); - $request->set_param( 'context', 'edit' ); - $response = rest_get_server()->dispatch( $request ); - $this->assertEquals( 200, $response->get_status() ); - $data = $response->get_data(); - $taxonomies = array(); - foreach ( get_taxonomies( '', 'objects' ) as $taxonomy ) { - if ( ! empty( $taxonomy->show_in_rest ) ) { - $taxonomies[] = $taxonomy; - } - } - $this->assertEquals( count( $taxonomies ), count( $data ) ); - $this->assertEquals( 'Categories', $data['category']['name'] ); - $this->assertEquals( 'category', $data['category']['slug'] ); - $this->assertEquals( true, $data['category']['hierarchical'] ); - $this->assertEquals( 'Tags', $data['post_tag']['name'] ); - $this->assertEquals( 'post_tag', $data['post_tag']['slug'] ); - $this->assertEquals( false, $data['post_tag']['hierarchical'] ); - $this->assertEquals( 'tags', $data['post_tag']['rest_base'] ); - } - - public function test_get_taxonomies_invalid_permission_for_context() { - wp_set_current_user( $this->subscriber ); - $request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies' ); - $request->set_param( 'context', 'edit' ); - $response = rest_get_server()->dispatch( $request ); - $this->assertErrorResponse( 'rest_cannot_view', $response, 403 ); - } - - public function test_create_category_incorrect_permissions_author() { - wp_set_current_user( $this->author ); - $request = new WP_REST_Request( 'POST', '/wp/v2/categories' ); - $request->set_param( 'name', 'Incorrect permissions' ); - $response = rest_get_server()->dispatch( $request ); - $this->assertErrorResponse( 'rest_cannot_create', $response, 403 ); - } - - public function test_create_category_editor() { - wp_set_current_user( $this->editor ); - $request = new WP_REST_Request( 'POST', '/wp/v2/categories' ); - $request->set_param( 'name', 'My Awesome Term' ); - $request->set_param( 'description', 'This term is so awesome.' ); - $request->set_param( 'slug', 'so-awesome' ); - $response = rest_get_server()->dispatch( $request ); - $this->assertEquals( 201, $response->get_status() ); - $headers = $response->get_headers(); - $data = $response->get_data(); - $this->assertContains( '/wp/v2/categories/' . $data['id'], $headers['Location'] ); - $this->assertEquals( 'My Awesome Term', $data['name'] ); - $this->assertEquals( 'This term is so awesome.', $data['description'] ); - $this->assertEquals( 'so-awesome', $data['slug'] ); - } - - public function test_create_tag_incorrect_permissions_subscriber() { - wp_set_current_user( $this->subscriber ); - $request = new WP_REST_Request( 'POST', '/wp/v2/tags' ); - $request->set_param( 'name', 'Incorrect permissions' ); - $response = rest_get_server()->dispatch( $request ); - $this->assertErrorResponse( 'rest_cannot_create', $response, 403 ); - } - - public function test_create_tag_contributor() { - wp_set_current_user( $this->contributor ); - $request = new WP_REST_Request( 'POST', '/wp/v2/tags' ); - $request->set_param( 'name', 'My Awesome Term' ); - $request->set_param( 'description', 'This term is so awesome.' ); - $request->set_param( 'slug', 'so-awesome' ); - $response = rest_get_server()->dispatch( $request ); - $this->assertEquals( 201, $response->get_status() ); - $headers = $response->get_headers(); - $data = $response->get_data(); - $this->assertContains( '/wp/v2/tags/' . $data['id'], $headers['Location'] ); - $this->assertEquals( 'My Awesome Term', $data['name'] ); - $this->assertEquals( 'This term is so awesome.', $data['description'] ); - $this->assertEquals( 'so-awesome', $data['slug'] ); - } - - public function test_get_items_unbounded_per_page() { - wp_set_current_user( $this->author ); - $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); - $request->set_param( 'per_page', '-1' ); - $response = rest_get_server()->dispatch( $request ); - $this->assertEquals( 400, $response->get_status() ); - } - - public function test_get_categories_unbounded_per_page() { - wp_set_current_user( $this->author ); - $this->factory->category->create(); - $request = new WP_REST_Request( 'GET', '/wp/v2/categories' ); - $request->set_param( 'per_page', '-1' ); - $response = rest_get_server()->dispatch( $request ); - $this->assertEquals( 400, $response->get_status() ); - } - - public function test_get_pages_unbounded_per_page() { - wp_set_current_user( $this->author ); - $this->factory->post->create( array( 'post_type' => 'page' ) ); - $request = new WP_REST_Request( 'GET', '/wp/v2/pages' ); - $request->set_param( 'per_page', '-1' ); - $response = rest_get_server()->dispatch( $request ); - $this->assertEquals( 400, $response->get_status() ); - } - - public function test_get_post_links_predecessor_version() { - $post_id = $this->factory->post->create(); - wp_update_post( - array( - 'post_content' => 'This content is marvelous.', - 'ID' => $post_id, - ) - ); - $revisions = wp_get_post_revisions( $post_id ); - $revision_1 = array_pop( $revisions ); - - $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d', $post_id ) ); - $response = rest_get_server()->dispatch( $request ); - - $links = $response->get_links(); - - $this->assertEquals( rest_url( '/wp/v2/posts/' . $post_id . '/revisions' ), $links['version-history'][0]['href'] ); - $this->assertEquals( 1, $links['version-history'][0]['attributes']['count'] ); - - $this->assertEquals( rest_url( '/wp/v2/posts/' . $post_id . '/revisions/' . $revision_1->ID ), $links['predecessor-version'][0]['href'] ); - $this->assertEquals( $revision_1->ID, $links['predecessor-version'][0]['attributes']['id'] ); - } -} diff --git a/phpunit/class-rest-autosaves-controller-test.php b/phpunit/class-rest-autosaves-controller-test.php deleted file mode 100644 index 10d2b23ef9706..0000000000000 --- a/phpunit/class-rest-autosaves-controller-test.php +++ /dev/null @@ -1,605 +0,0 @@ -<?php -/** - * Unit tests covering WP_REST_Autosaves_Controller functionality. - * - * @package WordPress - * @subpackage REST API - */ - -/** - * @group restapi-autosave - * @group restapi - */ -class WP_Test_REST_Autosaves_Controller extends WP_Test_REST_Post_Type_Controller_Testcase { - protected static $post_id; - protected static $page_id; - protected static $draft_page_id; - - protected static $autosave_post_id; - protected static $autosave_page_id; - - protected static $editor_id; - protected static $contributor_id; - - protected static $parent_page_id; - protected static $child_page_id; - protected static $child_draft_page_id; - - protected function set_post_data( $args = array() ) { - $defaults = array( - 'title' => 'Post Title', - 'content' => 'Post content', - 'excerpt' => 'Post excerpt', - 'name' => 'test', - 'author' => get_current_user_id(), - ); - - return wp_parse_args( $args, $defaults ); - } - - protected function check_create_autosave_response( $response ) { - $this->assertNotInstanceOf( 'WP_Error', $response ); - $response = rest_ensure_response( $response ); - $data = $response->get_data(); - - $this->assertArrayHasKey( 'content', $data ); - $this->assertArrayHasKey( 'excerpt', $data ); - $this->assertArrayHasKey( 'title', $data ); - } - - public static function wpSetUpBeforeClass( $factory ) { - self::$post_id = $factory->post->create(); - self::$page_id = $factory->post->create( array( 'post_type' => 'page' ) ); - - self::$editor_id = $factory->user->create( - array( - 'role' => 'editor', - ) - ); - self::$contributor_id = $factory->user->create( - array( - 'role' => 'contributor', - ) - ); - - wp_set_current_user( self::$editor_id ); - - // Create an autosave. - self::$autosave_post_id = wp_create_post_autosave( - array( - 'post_content' => 'This content is better.', - 'post_ID' => self::$post_id, - 'post_type' => 'post', - ) - ); - - self::$autosave_page_id = wp_create_post_autosave( - array( - 'post_content' => 'This content is better.', - 'post_ID' => self::$page_id, - 'post_type' => 'post', - ) - ); - - self::$draft_page_id = $factory->post->create( - array( - 'post_type' => 'page', - 'post_status' => 'draft', - ) - ); - self::$parent_page_id = $factory->post->create( - array( - 'post_type' => 'page', - ) - ); - self::$child_page_id = $factory->post->create( - array( - 'post_type' => 'page', - 'post_parent' => self::$parent_page_id, - ) - ); - self::$child_draft_page_id = $factory->post->create( - array( - 'post_type' => 'page', - 'post_parent' => self::$parent_page_id, - // The "update post" behavior of the autosave endpoint only occurs - // when saving a draft/auto-draft authored by the current user. - 'post_status' => 'draft', - 'post_author' => self::$editor_id, - ) - ); - } - - public static function wpTearDownAfterClass() { - // Also deletes revisions. - wp_delete_post( self::$post_id, true ); - wp_delete_post( self::$page_id, true ); - - self::delete_user( self::$editor_id ); - self::delete_user( self::$contributor_id ); - } - - public function setUp() { - parent::setUp(); - wp_set_current_user( self::$editor_id ); - - $this->post_autosave = wp_get_post_autosave( self::$post_id ); - } - - public function test_register_routes() { - $routes = rest_get_server()->get_routes(); - $this->assertArrayHasKey( '/wp/v2/posts/(?P<id>[\d]+)/autosaves', $routes ); - $this->assertArrayHasKey( '/wp/v2/posts/(?P<parent>[\d]+)/autosaves/(?P<id>[\d]+)', $routes ); - $this->assertArrayHasKey( '/wp/v2/pages/(?P<id>[\d]+)/autosaves', $routes ); - $this->assertArrayHasKey( '/wp/v2/pages/(?P<parent>[\d]+)/autosaves/(?P<id>[\d]+)', $routes ); - } - - public function test_context_param() { - - // Collection. - $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/posts/' . self::$post_id . '/autosaves' ); - $response = rest_get_server()->dispatch( $request ); - $data = $response->get_data(); - $this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] ); - $this->assertEqualSets( array( 'view', 'edit', 'embed' ), $data['endpoints'][0]['args']['context']['enum'] ); - - // Single. - $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id ); - $response = rest_get_server()->dispatch( $request ); - $data = $response->get_data(); - $this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] ); - $this->assertEqualSets( array( 'view', 'edit', 'embed' ), $data['endpoints'][0]['args']['context']['enum'] ); - } - - public function test_registered_query_params() { - $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/posts/' . self::$post_id . '/autosaves' ); - $response = $this->server->dispatch( $request ); - $data = $response->get_data(); - $keys = array_keys( $data['endpoints'][0]['args'] ); - sort( $keys ); - $this->assertEquals( - array( - 'context', - 'parent', - ), - $keys - ); - } - - public function test_get_items() { - wp_set_current_user( self::$editor_id ); - $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves' ); - $response = rest_get_server()->dispatch( $request ); - $data = $response->get_data(); - $this->assertEquals( 200, $response->get_status() ); - $this->assertCount( 1, $data ); - - $this->assertEquals( self::$autosave_post_id, $data[0]['id'] ); - - $this->check_get_autosave_response( $data[0], $this->post_autosave ); - } - - public function test_get_items_no_permission() { - wp_set_current_user( 0 ); - $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves' ); - $response = rest_get_server()->dispatch( $request ); - $this->assertErrorResponse( 'rest_cannot_read', $response, 401 ); - wp_set_current_user( self::$contributor_id ); - $response = rest_get_server()->dispatch( $request ); - $this->assertErrorResponse( 'rest_cannot_read', $response, 403 ); - } - - public function test_get_items_missing_parent() { - wp_set_current_user( self::$editor_id ); - $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/autosaves' ); - $response = rest_get_server()->dispatch( $request ); - $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 ); - } - - public function test_get_items_invalid_parent_post_type() { - wp_set_current_user( self::$editor_id ); - $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$page_id . '/autosaves' ); - $response = rest_get_server()->dispatch( $request ); - $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 ); - } - - public function test_get_item() { - wp_set_current_user( self::$editor_id ); - $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id ); - $response = rest_get_server()->dispatch( $request ); - $this->assertEquals( 200, $response->get_status() ); - $data = $response->get_data(); - - $this->check_get_autosave_response( $response, $this->post_autosave ); - $fields = array( - 'author', - 'date', - 'date_gmt', - 'modified', - 'modified_gmt', - 'guid', - 'id', - 'parent', - 'slug', - 'title', - 'excerpt', - 'content', - ); - $this->assertEqualSets( $fields, array_keys( $data ) ); - $this->assertSame( self::$editor_id, $data['author'] ); - } - - public function test_get_item_embed_context() { - wp_set_current_user( self::$editor_id ); - $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id ); - $request->set_param( 'context', 'embed' ); - $response = rest_get_server()->dispatch( $request ); - $fields = array( - 'author', - 'date', - 'id', - 'parent', - 'slug', - 'title', - 'excerpt', - ); - $data = $response->get_data(); - $this->assertEqualSets( $fields, array_keys( $data ) ); - } - - public function test_get_item_no_permission() { - $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id ); - wp_set_current_user( self::$contributor_id ); - $response = rest_get_server()->dispatch( $request ); - $this->assertErrorResponse( 'rest_cannot_read', $response, 403 ); - } - - public function test_get_item_missing_parent() { - wp_set_current_user( self::$editor_id ); - $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/autosaves/' . self::$autosave_post_id ); - $response = rest_get_server()->dispatch( $request ); - $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 ); - - } - - public function test_get_item_invalid_parent_post_type() { - wp_set_current_user( self::$editor_id ); - $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$page_id . '/autosaves' ); - $response = rest_get_server()->dispatch( $request ); - $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 ); - } - - public function test_delete_item() { - // Doesn't exist. - } - - public function test_prepare_item() { - wp_set_current_user( self::$editor_id ); - $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id ); - $response = rest_get_server()->dispatch( $request ); - $this->assertEquals( 200, $response->get_status() ); - $this->check_get_autosave_response( $response, $this->post_autosave ); - } - - public function test_get_item_schema() { - $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/posts/' . self::$post_id . '/autosaves' ); - $response = rest_get_server()->dispatch( $request ); - $data = $response->get_data(); - $properties = $data['schema']['properties']; - $this->assertEquals( 13, count( $properties ) ); - $this->assertArrayHasKey( 'author', $properties ); - $this->assertArrayHasKey( 'content', $properties ); - $this->assertArrayHasKey( 'date', $properties ); - $this->assertArrayHasKey( 'date_gmt', $properties ); - $this->assertArrayHasKey( 'excerpt', $properties ); - $this->assertArrayHasKey( 'guid', $properties ); - $this->assertArrayHasKey( 'id', $properties ); - $this->assertArrayHasKey( 'modified', $properties ); - $this->assertArrayHasKey( 'modified_gmt', $properties ); - $this->assertArrayHasKey( 'parent', $properties ); - $this->assertArrayHasKey( 'slug', $properties ); - $this->assertArrayHasKey( 'title', $properties ); - $this->assertArrayHasKey( 'preview_link', $properties ); - } - - public function test_create_item() { - wp_set_current_user( self::$editor_id ); - - $request = new WP_REST_Request( 'POST', '/wp/v2/posts/' . self::$post_id . '/autosaves' ); - $request->add_header( 'content-type', 'application/x-www-form-urlencoded' ); - - $params = $this->set_post_data( - array( - 'id' => self::$post_id, - ) - ); - $request->set_body_params( $params ); - $response = rest_get_server()->dispatch( $request ); - - $this->check_create_autosave_response( $response ); - } - - public function test_update_item() { - wp_set_current_user( self::$editor_id ); - $request = new WP_REST_Request( 'POST', '/wp/v2/posts/' . self::$post_id . '/autosaves' ); - $request->add_header( 'content-type', 'application/x-www-form-urlencoded' ); - - $params = $this->set_post_data( - array( - 'id' => self::$post_id, - 'author' => self::$contributor_id, - ) - ); - - $request->set_body_params( $params ); - $response = rest_get_server()->dispatch( $request ); - - $this->check_create_autosave_response( $response ); - } - - public function test_update_item_nopriv() { - wp_set_current_user( self::$contributor_id ); - - $request = new WP_REST_Request( 'POST', '/wp/v2/posts/' . self::$post_id . '/autosaves' ); - $request->add_header( 'content-type', 'application/x-www-form-urlencoded' ); - - $params = $this->set_post_data( - array( - 'id' => self::$post_id, - 'author' => self::$editor_id, - ) - ); - - $request->set_body_params( $params ); - $response = rest_get_server()->dispatch( $request ); - - $this->assertErrorResponse( 'rest_cannot_edit', $response, 403 ); - } - - public function test_rest_autosave_published_post() { - wp_set_current_user( self::$editor_id ); - - $request = new WP_REST_Request( 'POST', '/wp/v2/posts/' . self::$post_id . '/autosaves' ); - $request->add_header( 'content-type', 'application/json' ); - - $current_post = get_post( self::$post_id ); - - $autosave_data = $this->set_post_data( - array( - 'id' => self::$post_id, - 'content' => 'Updated post \ content', - 'excerpt' => $current_post->post_excerpt, - 'title' => $current_post->post_title, - ) - ); - - $request->set_body( wp_json_encode( $autosave_data ) ); - $response = rest_get_server()->dispatch( $request ); - $new_data = $response->get_data(); - - $this->assertEquals( $current_post->ID, $new_data['parent'] ); - $this->assertEquals( $current_post->post_title, $new_data['title']['raw'] ); - $this->assertEquals( $current_post->post_excerpt, $new_data['excerpt']['raw'] ); - - // Updated post_content. - $this->assertNotEquals( $current_post->post_content, $new_data['content']['raw'] ); - - $autosave_post = wp_get_post_autosave( self::$post_id ); - $this->assertEquals( $autosave_data['title'], $autosave_post->post_title ); - $this->assertEquals( $autosave_data['content'], $autosave_post->post_content ); - $this->assertEquals( $autosave_data['excerpt'], $autosave_post->post_excerpt ); - } - - public function test_rest_autosave_draft_post_same_author() { - wp_set_current_user( self::$editor_id ); - - $post_data = array( - 'post_content' => 'Test post content', - 'post_title' => 'Test post title', - 'post_excerpt' => 'Test post excerpt', - ); - $post_id = wp_insert_post( $post_data ); - - $autosave_data = array( - 'id' => $post_id, - 'content' => 'Updated post \ content', - 'title' => 'Updated post title', - ); - - $request = new WP_REST_Request( 'POST', '/wp/v2/posts/' . self::$post_id . '/autosaves' ); - $request->add_header( 'content-type', 'application/json' ); - $request->set_body( wp_json_encode( $autosave_data ) ); - - $response = rest_get_server()->dispatch( $request ); - $new_data = $response->get_data(); - $post = get_post( $post_id ); - - $this->assertEquals( $post_id, $new_data['id'] ); - // The draft post should be updated. - $this->assertEquals( $autosave_data['content'], $new_data['content']['raw'] ); - $this->assertEquals( $autosave_data['title'], $new_data['title']['raw'] ); - $this->assertEquals( $autosave_data['content'], $post->post_content ); - $this->assertEquals( $autosave_data['title'], $post->post_title ); - - // Not updated. - $this->assertEquals( $post_data['post_excerpt'], $post->post_excerpt ); - - wp_delete_post( $post_id ); - } - - public function test_rest_autosave_draft_post_different_author() { - wp_set_current_user( self::$editor_id ); - - $post_data = array( - 'post_content' => 'Test post content', - 'post_title' => 'Test post title', - 'post_excerpt' => 'Test post excerpt', - 'post_author' => self::$editor_id + 1, - ); - $post_id = wp_insert_post( $post_data ); - - $autosave_data = array( - 'id' => $post_id, - 'content' => 'Updated post content', - 'excerpt' => $post_data['post_excerpt'], - 'title' => $post_data['post_title'], - ); - - $request = new WP_REST_Request( 'POST', '/wp/v2/posts/' . self::$post_id . '/autosaves' ); - $request->add_header( 'content-type', 'application/json' ); - $request->set_body( wp_json_encode( $autosave_data ) ); - - $response = rest_get_server()->dispatch( $request ); - $new_data = $response->get_data(); - $current_post = get_post( $post_id ); - - $this->assertEquals( $current_post->ID, $new_data['parent'] ); - - // The draft post shouldn't change. - $this->assertEquals( $current_post->post_title, $post_data['post_title'] ); - $this->assertEquals( $current_post->post_content, $post_data['post_content'] ); - $this->assertEquals( $current_post->post_excerpt, $post_data['post_excerpt'] ); - - $autosave_post = wp_get_post_autosave( $post_id ); - - // No changes. - $this->assertEquals( $current_post->post_title, $autosave_post->post_title ); - $this->assertEquals( $current_post->post_excerpt, $autosave_post->post_excerpt ); - - // Has changes. - $this->assertEquals( $autosave_data['content'], $autosave_post->post_content ); - - wp_delete_post( $post_id ); - } - - public function test_get_additional_field_registration() { - $schema = array( - 'type' => 'integer', - 'description' => 'Some integer of mine', - 'enum' => array( 1, 2, 3, 4 ), - 'context' => array( 'view', 'edit' ), - ); - - register_rest_field( - 'post-revision', - 'my_custom_int', - array( - 'schema' => $schema, - 'get_callback' => array( $this, 'additional_field_get_callback' ), - 'update_callback' => array( $this, 'additional_field_update_callback' ), - ) - ); - - $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/posts/' . self::$post_id . '/autosaves' ); - - $response = rest_get_server()->dispatch( $request ); - $data = $response->get_data(); - - $this->assertArrayHasKey( 'my_custom_int', $data['schema']['properties'] ); - $this->assertEquals( $schema, $data['schema']['properties']['my_custom_int'] ); - - wp_set_current_user( 1 ); - - $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id ); - - $response = rest_get_server()->dispatch( $request ); - $this->assertArrayHasKey( 'my_custom_int', $response->data ); - - global $wp_rest_additional_fields; - $wp_rest_additional_fields = array(); - } - - public function additional_field_get_callback( $object ) { - return get_post_meta( $object['id'], 'my_custom_int', true ); - } - - public function additional_field_update_callback( $value, $post ) { - update_post_meta( $post->ID, 'my_custom_int', $value ); - } - - protected function check_get_autosave_response( $response, $autosave ) { - if ( $response instanceof WP_REST_Response ) { - $links = $response->get_links(); - $response = $response->get_data(); - } else { - $this->assertArrayHasKey( '_links', $response ); - $links = $response['_links']; - } - - $this->assertEquals( $autosave->post_author, $response['author'] ); - - $rendered_content = apply_filters( 'the_content', $autosave->post_content ); - $this->assertEquals( $rendered_content, $response['content']['rendered'] ); - - $this->assertEquals( mysql_to_rfc3339( $autosave->post_date ), $response['date'] ); //@codingStandardsIgnoreLine - $this->assertEquals( mysql_to_rfc3339( $autosave->post_date_gmt ), $response['date_gmt'] ); //@codingStandardsIgnoreLine - - $rendered_guid = apply_filters( 'get_the_guid', $autosave->guid, $autosave->ID ); - $this->assertEquals( $rendered_guid, $response['guid']['rendered'] ); - - $this->assertEquals( $autosave->ID, $response['id'] ); - $this->assertEquals( mysql_to_rfc3339( $autosave->post_modified ), $response['modified'] ); //@codingStandardsIgnoreLine - $this->assertEquals( mysql_to_rfc3339( $autosave->post_modified_gmt ), $response['modified_gmt'] ); //@codingStandardsIgnoreLine - $this->assertEquals( $autosave->post_name, $response['slug'] ); - - $rendered_title = get_the_title( $autosave->ID ); - $this->assertEquals( $rendered_title, $response['title']['rendered'] ); - - $parent = get_post( $autosave->post_parent ); - $parent_controller = new WP_REST_Posts_Controller( $parent->post_type ); - $parent_object = get_post_type_object( $parent->post_type ); - $parent_base = ! empty( $parent_object->rest_base ) ? $parent_object->rest_base : $parent_object->name; - $this->assertEquals( rest_url( '/wp/v2/' . $parent_base . '/' . $autosave->post_parent ), $links['parent'][0]['href'] ); - } - - public function test_get_item_sets_up_postdata() { - wp_set_current_user( self::$editor_id ); - $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id ); - rest_get_server()->dispatch( $request ); - - $post = get_post(); - $parent_post_id = wp_is_post_revision( $post->ID ); - - $this->assertEquals( $post->ID, self::$autosave_post_id ); - $this->assertEquals( $parent_post_id, self::$post_id ); - } - - public function test_update_item_draft_page_with_parent() { - wp_set_current_user( self::$editor_id ); - $request = new WP_REST_Request( 'POST', '/wp/v2/pages/' . self::$child_draft_page_id . '/autosaves' ); - $request->add_header( 'content-type', 'application/x-www-form-urlencoded' ); - - $params = $this->set_post_data( - array( - 'id' => self::$child_draft_page_id, - 'author' => self::$editor_id, - ) - ); - - $request->set_body_params( $params ); - $response = rest_get_server()->dispatch( $request ); - $data = $response->get_data(); - - $this->assertEquals( self::$child_draft_page_id, $data['id'] ); - $this->assertEquals( self::$parent_page_id, $data['parent'] ); - } - - public function test_schema_validation_is_applied() { - wp_set_current_user( self::$editor_id ); - - $request = new WP_REST_Request( 'POST', '/wp/v2/pages/' . self::$draft_page_id . '/autosaves' ); - $request->add_header( 'content-type', 'application/x-www-form-urlencoded' ); - - $params = $this->set_post_data( - array( - 'id' => self::$draft_page_id, - 'comment_status' => 'garbage', - ) - ); - - $request->set_body_params( $params ); - - $response = rest_get_server()->dispatch( $request ); - $this->assertNotEquals( 'garbage', get_post( self::$draft_page_id )->comment_status ); - } -} diff --git a/phpunit/class-wp-rest-dummy-search-handler.php b/phpunit/class-wp-rest-dummy-search-handler.php deleted file mode 100644 index e6cc5b2f475f0..0000000000000 --- a/phpunit/class-wp-rest-dummy-search-handler.php +++ /dev/null @@ -1,90 +0,0 @@ -<?php -/** - * REST API: WP_REST_Dummy_Search_Handler class - * - * @package gutenberg - */ - -/** - * Test class extending WP_REST_Search_Handler - */ -class WP_REST_Dummy_Search_Handler extends WP_REST_Search_Handler { - - protected $items = array(); - - public function __construct( $amount = 10 ) { - $this->type = 'dummy'; - - $this->subtypes = array( 'dummy_first_type', 'dummy_second_type' ); - - $this->items = array(); - for ( $i = 1; $i <= $amount; $i++ ) { - $subtype = $i > $amount / 2 ? 'dummy_second_type' : 'dummy_first_type'; - - $this->items[ $i ] = (object) array( - 'dummy_id' => $i, - 'dummy_title' => sprintf( 'Title %d', $i ), - 'dummy_url' => sprintf( home_url( '/dummies/%d' ), $i ), - 'dummy_type' => $subtype, - ); - } - } - - public function search_items( WP_REST_Request $request ) { - $subtypes = $request[ WP_REST_Search_Controller::PROP_SUBTYPE ]; - if ( in_array( WP_REST_Search_Controller::TYPE_ANY, $subtypes, true ) ) { - $subtypes = $this->subtypes; - } - - $results = array(); - foreach ( $subtypes as $subtype ) { - $results = array_merge( $results, wp_list_filter( array_values( $this->items ), array( 'dummy_type' => $subtype ) ) ); - } - - $results = wp_list_sort( $results, 'dummy_id', 'DESC' ); - - $number = (int) $request['per_page']; - $offset = (int) $request['per_page'] * ( (int) $request['page'] - 1 ); - - $total = count( $results ); - - $results = array_slice( $results, $offset, $number ); - - return array( - self::RESULT_IDS => wp_list_pluck( $results, 'dummy_id' ), - self::RESULT_TOTAL => $total, - ); - } - - public function prepare_item( $id, array $fields ) { - $dummy = $this->items[ $id ]; - - $data = array(); - - if ( in_array( WP_REST_Search_Controller::PROP_ID, $fields, true ) ) { - $data[ WP_REST_Search_Controller::PROP_ID ] = (int) $dummy->dummy_id; - } - - if ( in_array( WP_REST_Search_Controller::PROP_TITLE, $fields, true ) ) { - $data[ WP_REST_Search_Controller::PROP_TITLE ] = $dummy->dummy_title; - } - - if ( in_array( WP_REST_Search_Controller::PROP_URL, $fields, true ) ) { - $data[ WP_REST_Search_Controller::PROP_URL ] = $dummy->dummy_url; - } - - if ( in_array( WP_REST_Search_Controller::PROP_TYPE, $fields, true ) ) { - $data[ WP_REST_Search_Controller::PROP_TYPE ] = $this->type; - } - - if ( in_array( WP_REST_Search_Controller::PROP_SUBTYPE, $fields, true ) ) { - $data[ WP_REST_Search_Controller::PROP_SUBTYPE ] = $dummy->dummy_type; - } - - return $data; - } - - public function prepare_item_links( $id ) { - return array(); - } -} diff --git a/phpunit/class-wp-rest-themes-controller-test.php b/phpunit/class-wp-rest-themes-controller-test.php deleted file mode 100644 index 553799a7beb0b..0000000000000 --- a/phpunit/class-wp-rest-themes-controller-test.php +++ /dev/null @@ -1,344 +0,0 @@ -<?php -/** - * Unit tests covering WP_REST_Themes_Controller functionality. - * - * @package WordPress - * @subpackage REST API - */ - -/** - * @group restapi-themes - * @group restapi - */ -class WP_REST_Themes_Controller_Test extends WP_Test_REST_Controller_Testcase { - /** - * Subscriber user ID. - * - * @since 5.0.0 - * - * @var int $subscriber_id - */ - protected static $subscriber_id; - - /** - * Contributor user ID. - * - * @since 5.0.0 - * - * @var int $contributor_id - */ - protected static $contributor_id; - - /** - * The current theme object. - * - * @since 5.0.0 - * - * @var WP_Theme $current_theme - */ - protected static $current_theme; - - /** - * The REST API route for the active theme. - * - * @since 5.0.0 - * - * @var string $themes_route - */ - protected static $themes_route = '/wp/v2/themes'; - - /** - * Performs a REST API request for the active theme. - * - * @since 5.0.0 - * - * @param string $method Optional. Request method. Default GET. - * @return WP_REST_Response The request's response. - */ - protected function perform_active_theme_request( $method = 'GET' ) { - $request = new WP_REST_Request( $method, self::$themes_route ); - $request->set_param( 'status', 'active' ); - - return rest_get_server()->dispatch( $request ); - } - - /** - * Check that common properties are included in a response. - * - * @since 5.0.0 - * - * @param WP_REST_Response $response Current REST API response. - */ - protected function check_get_theme_response( $response ) { - if ( $response instanceof WP_REST_Response ) { - $headers = $response->get_headers(); - $response = $response->get_data(); - } else { - $headers = array(); - } - - $this->assertArrayHasKey( 'X-WP-Total', $headers ); - $this->assertEquals( 1, $headers['X-WP-Total'] ); - $this->assertArrayHasKey( 'X-WP-TotalPages', $headers ); - $this->assertEquals( 1, $headers['X-WP-TotalPages'] ); - } - - /** - * Set up class test fixtures. - * - * @since 5.0.0 - * - * @param WP_UnitTest_Factory $factory WordPress unit test factory. - */ - public static function wpSetUpBeforeClass( $factory ) { - self::$subscriber_id = $factory->user->create( - array( - 'role' => 'subscriber', - ) - ); - self::$contributor_id = $factory->user->create( - array( - 'role' => 'contributor', - ) - ); - self::$current_theme = wp_get_theme(); - - wp_set_current_user( self::$contributor_id ); - } - - /** - * Clean up test fixtures. - * - * @since 5.0.0 - */ - public static function wpTearDownAfterClass() { - self::delete_user( self::$subscriber_id ); - self::delete_user( self::$contributor_id ); - } - - /** - * Set up each test method. - * - * @since 5.0.0 - */ - public function setUp() { - parent::setUp(); - - wp_set_current_user( self::$contributor_id ); - } - - /** - * Theme routes should be registered correctly. - */ - public function test_register_routes() { - $routes = rest_get_server()->get_routes(); - $this->assertArrayHasKey( self::$themes_route, $routes ); - } - - /** - * Test retrieving a collection of themes. - */ - public function test_get_items() { - $response = self::perform_active_theme_request(); - - $this->assertEquals( 200, $response->get_status() ); - $data = $response->get_data(); - - $this->check_get_theme_response( $response ); - $fields = array( - 'theme_supports', - ); - $this->assertEqualSets( $fields, array_keys( $data[0] ) ); - } - - /** - * An error should be returned when the user does not have the edit_posts capability. - */ - public function test_get_items_no_permission() { - wp_set_current_user( self::$subscriber_id ); - $response = self::perform_active_theme_request(); - $this->assertErrorResponse( 'rest_user_cannot_view', $response, 403 ); - } - - /** - * Test an item is prepared for the response. - */ - public function test_prepare_item() { - $response = self::perform_active_theme_request(); - $this->assertEquals( 200, $response->get_status() ); - $this->check_get_theme_response( $response ); - } - - /** - * Verify the theme schema. - */ - public function test_get_item_schema() { - $response = self::perform_active_theme_request( 'OPTIONS' ); - $data = $response->get_data(); - $properties = $data['schema']['properties']; - $this->assertEquals( 1, count( $properties ) ); - $this->assertArrayHasKey( 'theme_supports', $properties ); - - $this->assertEquals( 3, count( $properties['theme_supports']['properties'] ) ); - $this->assertArrayHasKey( 'formats', $properties['theme_supports']['properties'] ); - $this->assertArrayHasKey( 'post-thumbnails', $properties['theme_supports']['properties'] ); - $this->assertArrayHasKey( 'responsive-embeds', $properties['theme_supports']['properties'] ); - } - - /** - * Should include relevant data in the 'theme_supports' key. - */ - public function test_theme_supports_formats() { - remove_theme_support( 'post-formats' ); - $response = self::perform_active_theme_request(); - $result = $response->get_data(); - $this->assertTrue( isset( $result[0]['theme_supports'] ) ); - $this->assertTrue( isset( $result[0]['theme_supports']['formats'] ) ); - $this->assertSame( array( 'standard' ), $result[0]['theme_supports']['formats'] ); - } - - /** - * Test when a theme only supports some post formats. - */ - public function test_theme_supports_formats_non_default() { - add_theme_support( 'post-formats', array( 'aside', 'video' ) ); - $response = self::perform_active_theme_request(); - $result = $response->get_data(); - $this->assertTrue( isset( $result[0]['theme_supports'] ) ); - $this->assertTrue( isset( $result[0]['theme_supports']['formats'] ) ); - $this->assertSame( array( 'standard', 'aside', 'video' ), $result[0]['theme_supports']['formats'] ); - } - - /** - * Test when a theme does not support responsive embeds. - */ - public function test_theme_supports_responsive_embeds_false() { - remove_theme_support( 'responsive-embeds' ); - $response = self::perform_active_theme_request(); - - $result = $response->get_data(); - $this->assertTrue( isset( $result[0]['theme_supports'] ) ); - $this->assertTrue( isset( $result[0]['theme_supports']['responsive-embeds'] ) ); - $this->assertFalse( $result[0]['theme_supports']['responsive-embeds'] ); - } - - /** - * Test when a theme supports responsive embeds. - */ - public function test_theme_supports_responsive_embeds_true() { - remove_theme_support( 'responsive-embeds' ); - add_theme_support( 'responsive-embeds' ); - $response = self::perform_active_theme_request(); - $result = $response->get_data(); - $this->assertTrue( isset( $result[0]['theme_supports'] ) ); - $this->assertTrue( $result[0]['theme_supports']['responsive-embeds'] ); - } - - /** - * Test when a theme does not support post thumbnails. - */ - public function test_theme_supports_post_thumbnails_false() { - remove_theme_support( 'post-thumbnails' ); - $response = self::perform_active_theme_request(); - - $result = $response->get_data(); - $this->assertTrue( isset( $result[0]['theme_supports'] ) ); - $this->assertTrue( isset( $result[0]['theme_supports']['post-thumbnails'] ) ); - $this->assertFalse( $result[0]['theme_supports']['post-thumbnails'] ); - } - - /** - * Test when a theme supports all post thumbnails. - */ - public function test_theme_supports_post_thumbnails_true() { - remove_theme_support( 'post-thumbnails' ); - add_theme_support( 'post-thumbnails' ); - $response = self::perform_active_theme_request(); - $result = $response->get_data(); - $this->assertTrue( isset( $result[0]['theme_supports'] ) ); - $this->assertTrue( $result[0]['theme_supports']['post-thumbnails'] ); - } - - /** - * Test when a theme only supports post thumbnails for certain post types. - */ - public function test_theme_supports_post_thumbnails_array() { - remove_theme_support( 'post-thumbnails' ); - add_theme_support( 'post-thumbnails', array( 'post' ) ); - $response = self::perform_active_theme_request(); - $result = $response->get_data(); - $this->assertTrue( isset( $result[0]['theme_supports'] ) ); - $this->assertEquals( array( 'post' ), $result[0]['theme_supports']['post-thumbnails'] ); - } - - /** - * It should be possible to register custom fields to the endpoint. - */ - public function test_get_additional_field_registration() { - $schema = array( - 'type' => 'integer', - 'description' => 'Some integer of mine', - 'enum' => array( 1, 2, 3, 4 ), - ); - - register_rest_field( - 'theme', - 'my_custom_int', - array( - 'schema' => $schema, - 'get_callback' => array( $this, 'additional_field_get_callback' ), - ) - ); - - $response = self::perform_active_theme_request( 'OPTIONS' ); - $data = $response->get_data(); - - $this->assertArrayHasKey( 'my_custom_int', $data['schema']['properties'] ); - $this->assertEquals( $schema, $data['schema']['properties']['my_custom_int'] ); - - $response = self::perform_active_theme_request( 'GET' ); - $data = $response->get_data(); - $this->assertArrayHasKey( 'my_custom_int', $data[0] ); - $this->assertSame( 2, $data[0]['my_custom_int'] ); - - global $wp_rest_additional_fields; - $wp_rest_additional_fields = array(); - } - - /** - * Return a value for the custom field. - * - * @since 5.0.0 - * - * @param array $theme Theme data array. - * @return int Additional field value. - */ - public function additional_field_get_callback( $theme ) { - return 2; - } - - /** - * The create_item() method does not exist for themes. - */ - public function test_create_item() {} - - /** - * The update_item() method does not exist for themes. - */ - public function test_update_item() {} - - /** - * The get_item() method does not exist for themes. - */ - public function test_get_item() {} - - /** - * The delete_item() method does not exist for themes. - */ - public function test_delete_item() {} - - /** - * Context is not supported for themes. - */ - public function test_context_param() {} -} From e1d2e66bcea435fceb9b868124a8b51d4a43ff27 Mon Sep 17 00:00:00 2001 From: Karol Gorski <naerriel@gmail.com> Date: Wed, 23 Jan 2019 18:20:58 +0100 Subject: [PATCH 210/691] Dismissible-notices: fix text overlapping icon (X) (#13371) * Dismissible-notices: fix text overlapping icon (X) * Dismissible-notices: fix text overlapping icon (X) - pt.2. --- packages/components/src/notice/style.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/notice/style.scss b/packages/components/src/notice/style.scss index b9f182374e2e4..31b364c7e21a6 100644 --- a/packages/components/src/notice/style.scss +++ b/packages/components/src/notice/style.scss @@ -26,7 +26,7 @@ } .components-notice__content { - margin: 1em 0; + margin: 1em #{ $icon-button-size-small + $border-width } 1em 0; } .components-notice__action.components-button { From 50ab754503b020fde33185dada9e5e06d51f7cb9 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Wed, 23 Jan 2019 09:34:33 -0800 Subject: [PATCH 211/691] Update and Organize Contributors Guide per #12916 (#13352) * Organize and reorder Contributors Guide per #12916 * Remove individual outreach pages, combined into single outreach.md * Principles link, sections heading * Fix link * Fix link, typo * Update docs/contributors/readme.md Co-Authored-By: mkaz <marcus@mkaz.com> * Remove meetups, there are far too many to list, since most meetups now all cover Gutenberg in some degree or other * Update CONTRIBUTING with links to Documentation section * Update CONTRIBUTING.md Co-Authored-By: mkaz <marcus@mkaz.com> * Update CONTRIBUTING.md Co-Authored-By: mkaz <marcus@mkaz.com> * Update CONTRIBUTING.md Co-Authored-By: mkaz <marcus@mkaz.com> * Update docs/contributors/outreach.md Co-Authored-By: mkaz <marcus@mkaz.com> * Update docs/contributors/outreach.md Co-Authored-By: mkaz <marcus@mkaz.com> * Update docs/contributors/document.md Co-Authored-By: mkaz <marcus@mkaz.com> * Update docs/contributors/document.md Co-Authored-By: mkaz <marcus@mkaz.com> * Add some newlines, take some away * Paragraph for resources * Update title in generator * Update docs/contributors/develop.md Co-Authored-By: mkaz <marcus@mkaz.com> --- CONTRIBUTING.md | 24 ++--- bin/generate-public-grammar.js | 2 +- docs/contributors/copy-guide.md | 30 +++--- docs/contributors/design.md | 2 +- docs/contributors/develop.md | 11 ++ docs/contributors/document.md | 34 +++++++ docs/contributors/grammar.md | 2 +- docs/contributors/outreach.md | 64 +++++++++--- docs/contributors/outreach/articles.md | 30 ------ docs/contributors/outreach/meetups.md | 17 ---- docs/contributors/outreach/resources.md | 11 -- docs/contributors/outreach/talks.md | 17 ---- docs/contributors/readme.md | 17 +++- docs/grammar.md | 6 ++ docs/manifest.json | 130 +++++++++++------------- docs/toc.json | 37 ++++--- 16 files changed, 215 insertions(+), 219 deletions(-) create mode 100644 docs/contributors/develop.md create mode 100644 docs/contributors/document.md delete mode 100644 docs/contributors/outreach/articles.md delete mode 100644 docs/contributors/outreach/meetups.md delete mode 100644 docs/contributors/outreach/resources.md delete mode 100644 docs/contributors/outreach/talks.md create mode 100644 docs/grammar.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 41fbdc022d8f6..576e78b27a3ee 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,10 +4,12 @@ Thank you for thinking about contributing to WordPress' Gutenberg project! If yo As with all WordPress projects, we want to ensure a welcoming environment for everyone. With that in mind, all contributors are expected to follow our [Code of Conduct](/CODE_OF_CONDUCT.md). -Before contributing, we encourage you to read our [Contributing Policy](/CONTRIBUTING.md) (you're here already!) and our [Handbook](https://wordpress.org/gutenberg/handbook/). If you have any questions on any of these, please open an issue so we can help clarify them. +Before contributing, we encourage you to review the [Contributor Handbook](https://wordpress.org/gutenberg/handbook/contributors/). If you have any questions, please ask, either in Slack or open an issue in GitHub so we can help clarify. All WordPress projects are [licensed under the GPLv2+](/LICENSE.md), and all contributions to Gutenberg will be released under the GPLv2+ license. You maintain copyright over any contribution you make, and by submitting a pull request, you are agreeing to release that contribution under the GPLv2+ license. +This document covers the technical details around setup, and submitting your contribution to the Gutenberg project. + ## Getting Started Gutenberg is a Node.js-based project, built primarily in JavaScript. @@ -114,7 +116,7 @@ Gutenberg contains both PHP and JavaScript code and encourages testing and code This repository uses [lerna] to manage Gutenberg modules and publish them as packages to [npm]. This enforces certain steps in the workflow which are described in details in [packages](/packages/README.md) documentation. -Maintaining dozens of npm packages is difficult—it can be tough to keep track of changes. That's why we use `CHANGELOG.md` files for each package to simplify the release process. As a contributor you should add an entry to the aforementioned file each time you contribute adding production code as described in [Maintaining Changelogs](/packages/README.md#maintaining-changelogs) section. +Maintaining dozens of npm packages is difficult—it can be tough to keep track of changes. That's why we use `CHANGELOG.md` files for each package to simplify the release process. As a contributor you should add an entry to the aforementioned file each time you contribute adding production code as described in [Maintaining Changelogs](/packages/README.md#maintaining-changelogs) section. ## How Can Designers Contribute? @@ -122,23 +124,9 @@ If you'd like to contribute to the design or front-end, feel free to contribute ## Contribute to the Documentation -Documentation is automatically synced from master to the [Gutenberg Documentation Website](https://wordpress.org/gutenberg/handbook/) every 15 minutes. - -To add a new documentation page, you'll have to create a Markdown file in the [docs](https://github.com/WordPress/gutenberg/tree/master/docs) folder and add an item to the [toc.json](/docs/toc.json). - -### Using links - -It's very likely that at some point you will want to link to other documentation pages. It's worth emphasizing that all documents can be browsed in different contexts: -- Gutenberg Handbook -- GitHub website -- npm website - -That's why it's recommended to use absolute links without the `https://github.com/WordPress/gutenberg` part for all files which match the following patterns: -- `/docs/*.md` -- `/packages/*/README.md` -- `/packages/components/src/**/README.md` +Please see the [Documentation section](/docs/contributors/document.md) of the Contributor Handbook. -This way they will be properly handled in all three aforementioned contexts. +Documentation is automatically synced from `master` to the [Gutenberg Handbook](https://wordpress.org/gutenberg/handbook/) every 15 minutes. ### `@wordpress/component` diff --git a/bin/generate-public-grammar.js b/bin/generate-public-grammar.js index a049d1674f36d..c56ec4398a894 100755 --- a/bin/generate-public-grammar.js +++ b/bin/generate-public-grammar.js @@ -93,7 +93,7 @@ function flatten( expression ) { fs.writeFileSync( path.join( __dirname, '..', 'docs', 'grammar.md' ), ` -# The Gutenberg block grammar +# Block Grammar ${ flatten( grammar ) } ` ); diff --git a/docs/contributors/copy-guide.md b/docs/contributors/copy-guide.md index 5e0066a689604..e5505e30f3acb 100644 --- a/docs/contributors/copy-guide.md +++ b/docs/contributors/copy-guide.md @@ -1,4 +1,4 @@ -# Gutencopy Guidelines +# Copy Guidelines ## Longer Text Guidelines for writing multi-line/step instructions or narrative introductions/orientation to pages or features. @@ -8,7 +8,7 @@ This will obviously vary quite a lot depending on the context, but here are some #### ONE: Contractions are your friends! They’re more conversational, and a simple way to make text sound friendlier and less formal. (And they save a bit of space as well: a win-win.) -#### TWO: Cut phrases that inflate your word count without actually adding meaning. +#### TWO: Cut phrases that inflate your word count without actually adding meaning. This happens frequently in two specific instances. First, when writing in the passive voice: > This block can be used to display single images. @@ -25,7 +25,7 @@ Does it or doesn’t it? We’re making this software: we’re allowed to be dec > The gallery block displays multiple images in an elegant layout. -We also all do this a lot with the phrase “allows you to.” +We also all do this a lot with the phrase “allows you to.” > Preformatted text allows you to keep your tabs and line breaks. @@ -33,9 +33,9 @@ Features don’t allow anyone to do anything; they’re just tools that do speci > Preformatted text preserves your tabs and line breaks. -The more direct sentences are almost always clearer. Scan your copy for the words “can,” “be,” “might,” “allows you to,” and “helps”—they’re the most common culprits, and looking for those words specifically is a way to locate phrasing you can tighten up. +The more direct sentences are almost always clearer. Scan your copy for the words “can,” “be,” “might,” “allows you to,” and “helps”—they’re the most common culprits, and looking for those words specifically is a way to locate phrasing you can tighten up. -#### THREE: Beware of “simple,” “easy,” and “just.” +#### THREE: Beware of “simple,” “easy,” and “just.” It is not for us to decide what is simple: it’s for the user to decide. If we say something is easy and the user doesn’t have an easy experience, it undermines their trust in us and what we’re building. Same goes for “just”—many of us know to avoid “simple,” but still use “just” all the time. “Just click here.” “Just enter your username.” It’s the same thing: it implies that something will be no big deal, but we can’t know what the user will find to be a big deal. It’s also safer and more helpful to be specific. “Easy” and “simple” are shorthand for explanations that we haven’t written; whenever you see them, take a minute to think about what they’re standing in for. Maybe “It’s easy to add a block by hitting ‘enter’” really means “You can add more content to the page without taking your hands off the keyboard.” Great! Say the specific thing instead of relying on “easy.” @@ -43,7 +43,7 @@ It’s also safer and more helpful to be specific. “Easy” and “simple” a This isn’t to say that you should banish these words from your vocabulary. You might want to write a tooltip describing how the cover image block now requires less configuration, or an email about how we’re building a tool for quick creation of custom blocks, and you could legitimately say that the cover image block has been simplified or that we’re working to make custom block creation easier—there, the terms are descriptive and relative. But be on the lookout for ways you might be using (or overusing) them to make absolute claims that something is easy or simple, and use those as opportunities to be more specific and clear. #### FOUR: Look out for “we.” -Any time text or instructions uses “we” a lot, it means the focus of the text is on the people behind the software and not the people using the software. Sometimes that’s what you actually want—but it’s usually not. The focus should typically be on the user, what they need, and how they benefit rather than “what we did” or “what we want.” +Any time text or instructions uses “we” a lot, it means the focus of the text is on the people behind the software and not the people using the software. Sometimes that’s what you actually want—but it’s usually not. The focus should typically be on the user, what they need, and how they benefit rather than “what we did” or “what we want.” We’re the only ones that care about what we did or want; the user just wants software that works. If you see a lot of “we”s, think about whether you should reframe what you’re writing to focus on the benefits to and successes of the user. @@ -51,7 +51,7 @@ We’re the only ones that care about what we did or want; the user just wants s Guidelines for (duh) writing bulleted lists. #### ONE: Keep sentence structures parallel across all bullets. -Parallel structure makes lists easier to read quickly—their predictability takes some cognitive load off the reader. +Parallel structure makes lists easier to read quickly—their predictability takes some cognitive load off the reader. GOOD: > What can you do with this block? Lots of things! @@ -60,7 +60,7 @@ GOOD: > * Display multiple images. > * Create a bulleted list. -Every bullet is a full sentence, and ends with a period. (If your list is a bunch of one- or two-word items, those can often just turn into a single regular sentence—easier to read, and space-saving.) Every line begins with a verb that tells the user what the block can do. The subject of the sentence is always the user. +Every bullet is a full sentence, and ends with a period. (If your list is a bunch of one- or two-word items, those can often just turn into a single regular sentence—easier to read, and space-saving.) Every line begins with a verb that tells the user what the block can do. The subject of the sentence is always the user. A user can absorb this list quickly because once they read the first item, they understand how to read the rest and know what information they’ll find. @@ -101,7 +101,7 @@ If your list is more persuasive (e.g., trying to convince someone to use a featu >* Use it to highlight a link you love—sharing links is the currency of the internet. >* Create a gallery that displays multiple images, and show off your best photos. -These aren’t hard-and-fast rules—you might choose the use the same verb in a persuasive list to be more focused and powerful, for example. But they’re good starting places for solid lists. +These aren’t hard-and-fast rules—you might choose the use the same verb in a persuasive list to be more focused and powerful, for example. But they’re good starting places for solid lists. #### THREE: When something's clearly a list, you don't have to tell us it's a list. @@ -120,7 +120,7 @@ LESS GOOD: Find the balance between being as clear as possible and trusting a user. On one hand, we know that people don’t always read instructions; on the other, redundancy can make the user feel like we think they’re stupid. #### FOUR: Bold is sometimes your friend. -Use it to focus readers on the key information in a bulleted list. This is especially useful when your bullets include some supplemental but ultimately secondary information. +Use it to focus readers on the key information in a bulleted list. This is especially useful when your bullets include some supplemental but ultimately secondary information. “Key information” is, well, key: bold draws the eye, so stick to the most vital piece of information in a given bullet: @@ -136,7 +136,7 @@ On the flipside, bolding too many things creates visual confusion: > * Use it to highlight a **link** you love—sharing **links** is the currency of the internet. > * Create a **gallery** that displays **multiple images**, and show off your best **photos**. -When lists are short and basic, don't bother—bolding just adds busy-ness. +When lists are short and basic, don't bother—bolding just adds busy-ness. > What can you do with this block? Lots of things! > * Add a **quote**. @@ -148,7 +148,7 @@ The lack of words creates its own focus; you don't have to add any more. ## UI Descriptions Guidelines for writing one-line feature descriptions, or short descriptions to clarify options. -#### ONE: Clarity above all! +#### ONE: Clarity above all! If the user doesn't understand what using a particular option will result in, it doesn't matter how clever your pun is. Wordplay and idioms are frequently unclear, and easily misunderstood. If you use them at all, they should be as supplemental information— never to explain the main idea—and they should be something you’re fairly certain will be understandable to a pretty wide range of people. #### TWO: Refer back to section one, and look out for those bulk-adding phrases. @@ -187,7 +187,7 @@ And when something means everything, it actually means nothing. The more specifi #### FOUR: This is still writing. It should have personality and interest. Clarity above all, yes, and space is often limited here—but UI text can still be interesting to read. -Single lines of description can still be complete sentences. +Single lines of description can still be complete sentences. > List. Numbered or bulleted. @@ -195,7 +195,7 @@ vs. > Add a list, either numbered or bulleted. -You can still use contractions. +You can still use contractions. > Add a list. We will provide formatting options. @@ -203,7 +203,7 @@ vs. > Add a bulleted list—we’ll give you some formatting options. -You can still use punctuation—em dashes, colons, semicolons—to control the flow of your words, link ideas, and create pauses. +You can still use punctuation—em dashes, colons, semicolons—to control the flow of your words, link ideas, and create pauses. > List. Numbered or bulleted. diff --git a/docs/contributors/design.md b/docs/contributors/design.md index 7b037fd3a4c94..bd68ba060718e 100644 --- a/docs/contributors/design.md +++ b/docs/contributors/design.md @@ -1,4 +1,4 @@ -# Gutenberg Design Principles & Vision +# Design Principles & Vision This is a living document that outlines the design principles and patterns of the editor interface. Its aim is to explain the background of the design, inform future improvements, and help people design great blocks. diff --git a/docs/contributors/develop.md b/docs/contributors/develop.md new file mode 100644 index 0000000000000..97fa91aab0f78 --- /dev/null +++ b/docs/contributors/develop.md @@ -0,0 +1,11 @@ +# Developer Contributions + +Please see [CONTRIBUTING.md](https://github.com/WordPress/gutenberg/blob/master/CONTRIBUTING.md) for technical details on how to setup and make contributions to the Gutenberg repository. + +The following resources offer additional information for developers who wish to contribute to Gutenberg: + +* [Coding Guidelines](/docs/contributors/coding-guidelines.md) outline additional patterns and conventions used in the Gutenberg project. +* [Gutenberg Block Grammar](/docs/contributors/grammar.md) +* [Testing Overview](/docs/contributors/testing-overview.md) for PHP and JavaScript development in Gutenberg. +* [Scripts](/docs/contributors/scripts.md) a list of vendor and internal scripts available to plugin developers. +* [Gutenberg Release Process](/docs/contributors/release.md) a checklist for the different type of releases for Gutenberg project. diff --git a/docs/contributors/document.md b/docs/contributors/document.md new file mode 100644 index 0000000000000..f51578d99e3c3 --- /dev/null +++ b/docs/contributors/document.md @@ -0,0 +1,34 @@ +# Documentation Contributions + +Documentation for Gutenberg is maintained in the `/docs/` directory in the same Gutenberg Github repository. The docs are published every 15 minutes to the [Gutenberg Handbook site](https://wordpress.org/gutenberg/handbook/). + +## New Document + +To add a new documentation page: + +1. Create a Markdown file in the [docs](https://github.com/WordPress/gutenberg/tree/master/docs) folder +2. Add item to the [toc.json](https://github.com/WordPress/gutenberg/blob/master/docs/toc.json) hierarchy +3. Update manifest.json by running `npm run docs:build` +4. Commit manifest.json with other files updated + +## Using Links + +It's very likely that at some point you will want to link to other documentation pages. It's worth emphasizing that all documents can be browsed in different contexts: + +- Gutenberg Handbook +- GitHub website +- npm website + +To create links that work in all contexts, you should use absolute path links without the `https://github.com/WordPress/gutenberg` prefix. You can reference files using the following patterns: + +- `/docs/*.md` +- `/packages/*/README.md` +- `/packages/components/src/**/README.md` + +This way they will be properly handled in all three aforementioned contexts. + +## Resources + +* [Copy Guidelines](/docs/contributors/copy-guide.md) for writing instructions, documentations, or other contributions to Gutenberg project. + +* [Tone and Voice Guide](https://make.wordpress.org/docs/handbook/documentation-team-handbook/tone-and-voice-guide/) from WordPress Documentation. diff --git a/docs/contributors/grammar.md b/docs/contributors/grammar.md index ac6015b9a2dc7..7d7e9bf73b8c0 100644 --- a/docs/contributors/grammar.md +++ b/docs/contributors/grammar.md @@ -1,5 +1,5 @@ -# The Gutenberg block grammar +# Block Grammar <dl><dt></dt><dd><pre><header>Block_List</header> = $(!Block .)* (Block $(!Block .)*)* $(.*)</pre></dd><dt></dt><dd><pre><header>Block</header> = Block_Void / Block_Balanced</pre></dd><dt></dt><dd><pre><header>Block_Void</header> = "&lt;!--" __ "wp:" Block_Name __ (Block_Attributes __)? "/-->"</pre></dd><dt></dt><dd><pre><header>Block_Balanced</header> = Block_Start (Block / $(!Block_End .))* Block_End</pre></dd><dt></dt><dd><pre><header>Block_Start</header> = "&lt;!--" __ "wp:" Block_Name __ (Block_Attributes __)? "-->"</pre></dd><dt></dt><dd><pre><header>Block_End</header> = "&lt;!--" __ "/wp:" Block_Name __ "-->"</pre></dd><dt></dt><dd><pre><header>Block_Name</header> = Namespaced_Block_Name diff --git a/docs/contributors/outreach.md b/docs/contributors/outreach.md index 9df69e7ac58b6..208a1eaf707e1 100644 --- a/docs/contributors/outreach.md +++ b/docs/contributors/outreach.md @@ -1,8 +1,58 @@ # Outreach -This will include talks, meetups and anything the community is doing to discuss, learn about, and contribute to Gutenberg. This is not an exhaustive list, if we are missing your event just let us know. +This includes articles, talks, demos and anything the community is doing to discuss, learn about, and contribute to Gutenberg. This is not an exhaustive list; if we are missing your event or article, just let us know. + +## Articles + +A short list of useful articles around defining, extending, and contributing to Gutenberg. + +### Overviews of Gutenberg + +- [Gutenberg, or the Ship of Theseus](https://matiasventura.com/post/gutenberg-or-the-ship-of-theseus/), Matías Ventura Bausero (October 2017) +- [We Called It Gutenberg for a Reason](https://ma.tt/2017/08/we-called-it-gutenberg-for-a-reason/), Matt Mullenweg (August 2017) +- [How Gutenberg is Changing WordPress Development](https://riad.blog/2017/10/06/how-gutenberg-is-changing-wordpress-development/), Riad Benguella (October 2017) +- [How Gutenberg Will Shape the Future of WordPress](https://www.linkedin.com/pulse/gutenberg-morten-rand-hendriksen/), Morten Rand-Henrikson (August 2017) + +### Extending Gutenberg + +- [With Gutenberg, what happens to my Custom Fields?](https://riad.blog/2017/12/11/with-gutenberg-what-happens-to-my-custom-fields/), Riad Benguella (December 2017) +- [One thousand and one ways to extend Gutenberg today](https://riad.blog/2017/10/16/one-thousand-and-one-way-to-extend-gutenberg-today/), Riad Benguella (October 2017) +- [Gutenberg Plugin Boilerplate](https://github.com/ahmadawais/Gutenberg-Boilerplate/), Ahmad Awais (August 2017) + +### Community Contribution + +- [Gutenberg Block Library](https://editorblockswp.com/library), Danny Cooper (August 2018) +- [A zero-configuration developer toolkit for building WordPress Gutenberg block plugins](https://ahmadawais.com/create-guten-block-toolkit/), Ahmad Awais (January 2018) +- [Contributing to Gutenberg Without Code](https://wordimpress.com/a-pot-stirrer-amongst-chefs-contributing-to-gutenberg-without-code/), Kevin Hoffman (August 2017) +- [Testing Flow in Gutenberg: Instructions for how to contribute to usability testing](https://make.wordpress.org/test/2017/11/22/testing-flow-in-gutenberg/), Anna Harrison (November 2017) + +### Article Compilations + +- [Curated Collection of Gutenberg Articles, Plugins, Blocks, Tutorials, etc](http://gutenberghub.com/), By Munir Kamal +- [Articles about Gutenberg](https://github.com/WordPress/gutenberg/issues/1419) (Github Issue thread with links) +- [Gutenberg articles on ManageWP.org](https://managewp.org/search?q=gutenberg) +- [Gutenberg Times](https://gutenbergtimes.com/category/updates/) + +## Talks + +Talks given about Gutenberg, including slides and videos as they are available. + +### Slides +- [The new core WordPress editor](http://kimb.me/talk-bigwp-london-new-core-wordpress-editor/) at BigWP London (18. May 2017) +- [Gutenberg Notes](http://haiku2.com/2017/09/bend-wordpress-meetup-gutenberg-notes/) at Bend WordPress Meetup (5. September 2017) +- [Gutenberg and the Future of Content in WordPress](https://www.slideshare.net/andrewmduthie/gutenberg-and-the-future-of-content-in-wordpress) (20. September 2017) +- [Head first into Gutenberg](https://speakerdeck.com/prtksxna/head-first-into-gutenberg) at the [WordPress Goa Meet-up](https://www.meetup.com/WordPressGoa/events/245275573/) (1. December 2017) +- [Gutenberg : vers une approche plus fine du contenu](https://imathi.eu/2018/02/16/gutenberg-vers-une-approche-plus-fine-du-contenu/) at [WP Paris](https://wpparis.fr/) (8. February 2018) + +### Videos +- [All `Gutenberg` tagged Talks at WordPress.tv](https://wordpress.tv/tag/gutenberg/) +- 2018-Jun - [Beyond Gutenberg](https://wordpress.tv/2018/07/09/matias-ventura-beyond-gutenberg/) by Matías Ventura +- 2018-Jun - [Anatomy of a block: Gutenberg design patterns](https://wordpress.tv/2018/07/08/tammie-lister-anatomy-of-a-block-gutenberg-design-patterns/) by Tammie Lister +- 2017-Dec - [State of the Word 2017](https://wordpress.tv/2017/12/04/matt-mullenweg-state-of-the-word-2017/) by Matt Mullenweg (Gutenberg demo by Matías Ventura at 35:00) +- [Gutenberg is Coming (Don’t Be Afraid)](https://training.ithemes.com/webinar/gutenberg-is-coming-dont-be-afraid/) from iThemes Training ## Showcases or demonstrations: + https://wpleeds.co.uk/events/plugins-gutenberg-wordpress-leeds-july-2017/ http://kimb.me/talk-bigwp-london-new-core-wordpress-editor @@ -13,15 +63,3 @@ https://www.meetup.com/WordPress-Melbourne/events/241543639 https://wpmeetups.de/termin/29-wp-meetup-stuttgart-gutenberg-editor-rueckblick-wordcamp-europe/ -## Testing events: -https://www.meetup.com/Turku-WordPress-Meetup/events/241195076/ - -https://www.meetup.com/Vancouver-WordPress-Meetup-Group/events/241575161/ - -## Calls for testing: -https://make.wordpress.org/test/2017/06/27/call-for-testing-gutenberg/ - -http://www.wpswfl.org/new-wordpress-editor-gutenberg-early-beta-needs-testers/ - -https://gutenberg.eastbaywp.com - diff --git a/docs/contributors/outreach/articles.md b/docs/contributors/outreach/articles.md deleted file mode 100644 index fe4bce7e328a8..0000000000000 --- a/docs/contributors/outreach/articles.md +++ /dev/null @@ -1,30 +0,0 @@ -# Articles - -This includes useful articles for those wanting to run a meetup or promote Gutenberg. - -## Overviews of Gutenberg - -- [Gutenberg, or the Ship of Theseus](https://matiasventura.com/post/gutenberg-or-the-ship-of-theseus/), Matías Ventura Bausero (October 2017) -- [We Called It Gutenberg for a Reason](https://ma.tt/2017/08/we-called-it-gutenberg-for-a-reason/), Matt Mullenweg (August 2017) -- [How Gutenberg is Changing WordPress Development](https://riad.blog/2017/10/06/how-gutenberg-is-changing-wordpress-development/), Riad Benguella (October 2017) -- [How Gutenberg Will Shape the Future of WordPress](https://www.linkedin.com/pulse/gutenberg-morten-rand-hendriksen/), Morten Rand-Henrikson (August 2017) - -## Extending Gutenberg - -- [With Gutenberg, what happens to my Custom Fields?](https://riad.blog/2017/12/11/with-gutenberg-what-happens-to-my-custom-fields/), Riad Benguella (December 2017) -- [One thousand and one ways to extend Gutenberg today](https://riad.blog/2017/10/16/one-thousand-and-one-way-to-extend-gutenberg-today/), Riad Benguella (October 2017) -- [Gutenberg Plugin Boilerplate](https://github.com/ahmadawais/Gutenberg-Boilerplate/), Ahmad Awais (August 2017) - -## Community Contribution - -- [Gutenberg Block Library](https://editorblockswp.com/library), Danny Cooper (August 2018) -- [A zero-configuration developer toolkit for building WordPress Gutenberg block plugins](https://ahmadawais.com/create-guten-block-toolkit/), Ahmad Awais (January 2018) -- [Contributing to Gutenberg Without Code](https://wordimpress.com/a-pot-stirrer-amongst-chefs-contributing-to-gutenberg-without-code/), Kevin Hoffman (August 2017) -- [Testing Flow in Gutenberg: Instructions for how to contribute to usability testing](https://make.wordpress.org/test/2017/11/22/testing-flow-in-gutenberg/), Anna Harrison (November 2017) - -## Article Compilations - -- [Curated Collection of Gutenberg Articles, Plugins, Blocks, Tutorials, etc](http://gutenberghub.com/), By Munir Kamal -- [Articles about Gutenberg](https://github.com/WordPress/gutenberg/issues/1419) (Github Issue thread with links) -- [Gutenberg articles on ManageWP.org](https://managewp.org/search?q=gutenberg) -- [Gutenberg Times](https://gutenbergtimes.com/category/updates/) diff --git a/docs/contributors/outreach/meetups.md b/docs/contributors/outreach/meetups.md deleted file mode 100644 index 58cd8bbb72c99..0000000000000 --- a/docs/contributors/outreach/meetups.md +++ /dev/null @@ -1,17 +0,0 @@ -# Meetups - -A list of meetups about Gutenberg so far: - -- [Gutenberg and the Future of WordPress](https://www.meetup.com/Vancouver-WordPress-Meetup-Group/events/241575161/), Vancouver, Canada -- [Page builders and the upcoming Gutenberg Editor](https://www.meetup.com/Turku-WordPress-Meetup/events/241195076/), Turku, Finland -- [Discussion about Gutenberg](https://www.facebook.com/events/278785795934302/), Andria, Italy -- [Plugins and Gutenberg](https://wpleeds.co.uk/events/plugins-gutenberg-wordpress-leeds-july-2017/), Leeds, UK -- [Gutenberg Introduction & Demo](https://www.meetup.com/WordPress-Melbourne/events/241543639/), Melbourne, Australia -- [Gutenberg Editor & Review](https://wpmeetups.de/termin/29-wp-meetup-stuttgart-gutenberg-editor-rueckblick-wordcamp-europe/), WordCamp Europe -- [Diving into Gutenberg by Tammie Lister](https://www.meetup.com/Big-Media-Enterprise-WordPress-London-Meetup/events/243302081/), London, UK -- [What's New In WordPress 4.9 and Gutenberg 1.5](https://www.meetup.com/Tuscaloosa-WordPress-Meetup/events/244584939/), Tuscaloosa, Alabama, USA -- [WordPress & JavaScript: Let's talk Gutenberg!](https://www.meetup.com/WordPress-Lahore/events/246446478/), Lahore, PK -- [The state of Gutenberg](https://www.meetup.com/WP-Porto/events/245585131/), Porto, Portugal -- [Discuss and learn about the new WordPress Editor : Gutenberg](https://www.meetup.com/Pune-WordPress-Knowledge-Exchange/events/248496830/), Pune, India -- [An Introduction to Gutenberg](https://www.meetup.com/Okanagan-WordPress-Meetup/events/249167218/), Vernon, BC, Canada -- [WordPress 5.0 - Gutenberg is upon us](https://www.meetup.com/WordPress-Perth/events/249490075/), Perth, Australia diff --git a/docs/contributors/outreach/resources.md b/docs/contributors/outreach/resources.md deleted file mode 100644 index 3c5dd598f6085..0000000000000 --- a/docs/contributors/outreach/resources.md +++ /dev/null @@ -1,11 +0,0 @@ -# Resources - -All resources here can be used by anyone. Feel free to use the decks to make your own talks or to use the gifs in your blog posts and other resources. - -## Slidedecks to use - -- v1: https://cloudup.com/cqEJppQ8m-5 : August 2017 - -## Gif collection to use - -- https://cloudup.com/c9OKU3OJD9r diff --git a/docs/contributors/outreach/talks.md b/docs/contributors/outreach/talks.md deleted file mode 100644 index a21f9c468c481..0000000000000 --- a/docs/contributors/outreach/talks.md +++ /dev/null @@ -1,17 +0,0 @@ -# Talks - -Talks given about Gutenberg, including slides and videos as they are available. - -## Slides -- [The new core WordPress editor](http://kimb.me/talk-bigwp-london-new-core-wordpress-editor/) at BigWP London (18. May 2017) -- [Gutenberg Notes](http://haiku2.com/2017/09/bend-wordpress-meetup-gutenberg-notes/) at Bend WordPress Meetup (5. September 2017) -- [Gutenberg and the Future of Content in WordPress](https://www.slideshare.net/andrewmduthie/gutenberg-and-the-future-of-content-in-wordpress) (20. September 2017) -- [Head first into Gutenberg](https://speakerdeck.com/prtksxna/head-first-into-gutenberg) at the [WordPress Goa Meet-up](https://www.meetup.com/WordPressGoa/events/245275573/) (1. December 2017) -- [Gutenberg : vers une approche plus fine du contenu](https://imathi.eu/2018/02/16/gutenberg-vers-une-approche-plus-fine-du-contenu/) at [WP Paris](https://wpparis.fr/) (8. February 2018) - -## Videos -- [All `Gutenberg` tagged Talks at WordPress.tv](https://wordpress.tv/tag/gutenberg/) -- 2018-Jun - [Beyond Gutenberg](https://wordpress.tv/2018/07/09/matias-ventura-beyond-gutenberg/) by Matías Ventura -- 2018-Jun - [Anatomy of a block: Gutenberg design patterns](https://wordpress.tv/2018/07/08/tammie-lister-anatomy-of-a-block-gutenberg-design-patterns/) by Tammie Lister -- 2017-Dec - [State of the Word 2017](https://wordpress.tv/2017/12/04/matt-mullenweg-state-of-the-word-2017/) by Matt Mullenweg (Gutenberg demo by Matías Ventura at 35:00) -- [Gutenberg is Coming (Don’t Be Afraid)](https://training.ithemes.com/webinar/gutenberg-is-coming-dont-be-afraid/) from iThemes Training diff --git a/docs/contributors/readme.md b/docs/contributors/readme.md index 1feaadfcbf5b9..e3979c229e25c 100644 --- a/docs/contributors/readme.md +++ b/docs/contributors/readme.md @@ -2,10 +2,17 @@ Welcome to the Gutenberg Project Contributors Guide. -The following guidelines are in place to create consistency across the project and the numerous contributors. See also the [Contributing Documentation](https://github.com/WordPress/gutenberg/blob/master/CONTRIBUTING.md) for technical details around setup, and submitting your contributions. +The following guidelines are in place to create consistency across the project and the numerous contributors. See the [Contributing Documentation](https://github.com/WordPress/gutenberg/blob/master/CONTRIBUTING.md) for technical details around setup, and submitting your contributions. -* [Coding Guidelines](../../docs/contributors/coding-guidelines.md) outline additional patterns and conventions used in the Gutenberg project. -* [Copy Guidelines](../../docs/contributors/copy-guide.md) -* [Design Principles & Vision](../../docs/contributors/design.md) +## Philosophy + +* [Architecturial and UX Principles of Gutenberg](/docs/contributors/principles.md) + +## Sections + +The contributors guide has the following different sections by contribution type: + +* [Design Contributions](/docs/contributors/design.md) +* [Developer Contributions](/docs/contributors/develop.md) +* [Documentation Contributions](/docs/contributors/document.md) -Please see the table of contents on the left side of the Gutenberg Handbook for the full list of contributor resources. diff --git a/docs/grammar.md b/docs/grammar.md new file mode 100644 index 0000000000000..1bbe77dc5ecf0 --- /dev/null +++ b/docs/grammar.md @@ -0,0 +1,6 @@ + +# Block Grammar + +<dl><dt></dt><dd><pre><header>Block_List</header> = $(!Block .)* (Block $(!Block .)*)* $(.*)</pre></dd><dt></dt><dd><pre><header>Block</header> = Block_Void + / Block_Balanced</pre></dd><dt></dt><dd><pre><header>Block_Void</header> = "&lt;!--" __ "wp:" Block_Name __ (Block_Attributes __)? "/-->"</pre></dd><dt></dt><dd><pre><header>Block_Balanced</header> = Block_Start (Block / $(!Block !Block_End .)+)* Block_End</pre></dd><dt></dt><dd><pre><header>Block_Start</header> = "&lt;!--" __ "wp:" Block_Name __ (Block_Attributes __)? "-->"</pre></dd><dt></dt><dd><pre><header>Block_End</header> = "&lt;!--" __ "/wp:" Block_Name __ "-->"</pre></dd><dt></dt><dd><pre><header>Block_Name</header> = Namespaced_Block_Name + / Core_Block_Name</pre></dd><dt></dt><dd><pre><header>Namespaced_Block_Name</header> = $(Block_Name_Part "/" Block_Name_Part)</pre></dd><dt></dt><dd><pre><header>Core_Block_Name</header> = $(Block_Name_Part)</pre></dd><dt></dt><dd><pre><header>Block_Name_Part</header> = $([a-z] [a-z0-9_-]*)</pre></dd><dt>JSON-encoded attributes embedded in a block's opening comment</dt><dd><pre><header>Block_Attributes</header> = $("{" (!("}" __ "" "/"? "-->") .)* "}")</pre></dd><dt></dt><dd><pre><header>__</header> = [ \t\r\n]+</pre></dd></dl> diff --git a/docs/manifest.json b/docs/manifest.json index dfa85b7d12f34..e7d2a88e11cc5 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -365,18 +365,6 @@ "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/designers/design-resources.md", "parent": "designers" }, - { - "title": "Glossary", - "slug": "glossary", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/glossary.md", - "parent": "designers-developers" - }, - { - "title": "Frequently Asked Questions", - "slug": "faq", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/faq.md", - "parent": "designers-developers" - }, { "title": "Contributors Guide", "slug": "contributors", @@ -384,105 +372,105 @@ "parent": null }, { - "title": "Coding Guidelines", - "slug": "coding-guidelines", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/coding-guidelines.md", - "parent": "contributors" - }, - { - "title": "Gutencopy Guidelines", - "slug": "copy-guide", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/copy-guide.md", + "title": "Principles", + "slug": "principles", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/principles.md", "parent": "contributors" }, { - "title": "Gutenberg Design Principles & Vision", + "title": "Design Principles & Vision", "slug": "design", "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/design.md", "parent": "contributors" }, { - "title": "The Gutenberg block grammar", - "slug": "grammar", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/grammar.md", - "parent": "contributors" + "title": "Blocks are the Interface", + "slug": "the-block", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/principles/the-block.md", + "parent": "design" }, { - "title": "History", - "slug": "history", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/history.md", - "parent": "contributors" + "title": "Reference", + "slug": "reference", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/reference.md", + "parent": "design" }, { - "title": "Outreach", - "slug": "outreach", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/outreach.md", + "title": "Developer Contributions", + "slug": "develop", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/develop.md", "parent": "contributors" }, { - "title": "Articles", - "slug": "articles", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/outreach/articles.md", - "parent": "outreach" + "title": "Coding Guidelines", + "slug": "coding-guidelines", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/coding-guidelines.md", + "parent": "develop" }, { - "title": "Meetups", - "slug": "meetups", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/outreach/meetups.md", - "parent": "outreach" + "title": "Block Grammar", + "slug": "grammar", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/grammar.md", + "parent": "develop" }, { - "title": "Resources", - "slug": "resources", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/outreach/resources.md", - "parent": "outreach" + "title": "Testing Overview", + "slug": "testing-overview", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/testing-overview.md", + "parent": "develop" }, { - "title": "Talks", - "slug": "talks", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/outreach/talks.md", - "parent": "outreach" + "title": "Scripts", + "slug": "scripts", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/scripts.md", + "parent": "develop" }, { - "title": "Principles", - "slug": "principles", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/principles.md", + "title": "Gutenberg Release Process", + "slug": "release", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/release.md", + "parent": "develop" + }, + { + "title": "Documentation Contributions", + "slug": "document", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/document.md", "parent": "contributors" }, { - "title": "Blocks are the Interface", - "slug": "the-block", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/principles/the-block.md", - "parent": "principles" + "title": "Copy Guidelines", + "slug": "copy-guide", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/copy-guide.md", + "parent": "document" }, { - "title": "Reference", - "slug": "reference", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/reference.md", + "title": "History", + "slug": "history", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/history.md", "parent": "contributors" }, { - "title": "Gutenberg Release Process", - "slug": "release", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/release.md", + "title": "Glossary", + "slug": "glossary", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/glossary.md", "parent": "contributors" }, { - "title": "Repository Management", - "slug": "repository-management", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/repository-management.md", + "title": "Frequently Asked Questions", + "slug": "faq", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/faq.md", "parent": "contributors" }, { - "title": "Scripts", - "slug": "scripts", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/scripts.md", + "title": "Repository Management", + "slug": "repository-management", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/repository-management.md", "parent": "contributors" }, { - "title": "Testing Overview", - "slug": "testing-overview", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/testing-overview.md", + "title": "Outreach", + "slug": "outreach", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/outreach.md", "parent": "contributors" }, { diff --git a/docs/toc.json b/docs/toc.json index ceb639a213cf9..cdb3fa096507b 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -71,29 +71,28 @@ {"docs/designers-developers/designers/block-design.md": []}, {"docs/designers-developers/designers/design-patterns.md": []}, {"docs/designers-developers/designers/design-resources.md": []} - ]}, - {"docs/designers-developers/glossary.md": []}, - {"docs/designers-developers/faq.md": []} + ]} ]}, {"docs/contributors/readme.md": [ - {"docs/contributors/coding-guidelines.md": []}, - {"docs/contributors/copy-guide.md": []}, - {"docs/contributors/design.md": []}, - {"docs/contributors/grammar.md": []}, - {"docs/contributors/history.md": []}, - {"docs/contributors/outreach.md": [ - {"docs/contributors/outreach/articles.md": []}, - {"docs/contributors/outreach/meetups.md": []}, - {"docs/contributors/outreach/resources.md": []}, - {"docs/contributors/outreach/talks.md": []} + {"docs/contributors/principles.md": []}, + {"docs/contributors/design.md": [ + {"docs/contributors/principles/the-block.md": []}, + {"docs/contributors/reference.md": []} ]}, - {"docs/contributors/principles.md": [ - {"docs/contributors/principles/the-block.md": []} + {"docs/contributors/develop.md": [ + {"docs/contributors/coding-guidelines.md": []}, + {"docs/contributors/grammar.md": []}, + {"docs/contributors/testing-overview.md": []}, + {"docs/contributors/scripts.md": []}, + {"docs/contributors/release.md": []} ]}, - {"docs/contributors/reference.md": []}, - {"docs/contributors/release.md": []}, + {"docs/contributors/document.md": [ + {"docs/contributors/copy-guide.md": []} + ]}, + {"docs/contributors/history.md": []}, + {"docs/designers-developers/glossary.md": []}, + {"docs/designers-developers/faq.md": []}, {"docs/contributors/repository-management.md": []}, - {"docs/contributors/scripts.md": []}, - {"docs/contributors/testing-overview.md": []} + {"docs/contributors/outreach.md": []} ]} ] From 0259f7b2aec9ab66f3a040d08a5aeeb5c65e5756 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Wed, 23 Jan 2019 15:21:16 -0500 Subject: [PATCH 212/691] is-shallow-equal: Use ES5 ruleset from eslint-plugin module (#13428) --- packages/is-shallow-equal/.eslintrc.json | 6 ++++-- packages/is-shallow-equal/CHANGELOG.md | 4 ++++ packages/is-shallow-equal/benchmark/.eslintrc.json | 4 ++++ packages/is-shallow-equal/test/.eslintrc.json | 4 ++++ 4 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 packages/is-shallow-equal/benchmark/.eslintrc.json create mode 100644 packages/is-shallow-equal/test/.eslintrc.json diff --git a/packages/is-shallow-equal/.eslintrc.json b/packages/is-shallow-equal/.eslintrc.json index 118dc091c3c8a..3ab5b55d74cc0 100644 --- a/packages/is-shallow-equal/.eslintrc.json +++ b/packages/is-shallow-equal/.eslintrc.json @@ -1,5 +1,7 @@ { - "rules": { - "no-var": 0 + "root": true, + "extends": "plugin:@wordpress/eslint-plugin/es5", + "env": { + "node": true } } diff --git a/packages/is-shallow-equal/CHANGELOG.md b/packages/is-shallow-equal/CHANGELOG.md index cc266ecb20c85..876fcc4044e97 100644 --- a/packages/is-shallow-equal/CHANGELOG.md +++ b/packages/is-shallow-equal/CHANGELOG.md @@ -4,6 +4,10 @@ - Type-specific variants are now exposed from the module root. In a WordPress context, this has the effect of making them available as `wp.isShallowEqual.isShallowEqualObjects` and `wp.isShallowEqual.isShallowEqualArrays`. +### Internal + +- Development source code linting extends the `@wordpress/eslint-plugin/es5` ruleset. + ## 1.1.0 (2018-07-12) ### New Feature diff --git a/packages/is-shallow-equal/benchmark/.eslintrc.json b/packages/is-shallow-equal/benchmark/.eslintrc.json new file mode 100644 index 0000000000000..93ba810c78562 --- /dev/null +++ b/packages/is-shallow-equal/benchmark/.eslintrc.json @@ -0,0 +1,4 @@ +{ + "root": true, + "extends": "../../../.eslintrc.js" +} diff --git a/packages/is-shallow-equal/test/.eslintrc.json b/packages/is-shallow-equal/test/.eslintrc.json new file mode 100644 index 0000000000000..93ba810c78562 --- /dev/null +++ b/packages/is-shallow-equal/test/.eslintrc.json @@ -0,0 +1,4 @@ +{ + "root": true, + "extends": "../../../.eslintrc.js" +} From 9a35324f1bd06efb6961ee347c9cb48b94b6b313 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Wrede?= <soerenwrede@gmail.com> Date: Thu, 24 Jan 2019 13:49:09 +0100 Subject: [PATCH 213/691] Fix: Categories block: add custom classes only to wrapper (#13439) * add wp-block-categories as prefix * render class names only on the block wrapper * review changes --- packages/block-library/src/categories/edit.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/block-library/src/categories/edit.js b/packages/block-library/src/categories/edit.js index b67eb0982e0bf..5341ad6ff5168 100644 --- a/packages/block-library/src/categories/edit.js +++ b/packages/block-library/src/categories/edit.js @@ -57,8 +57,7 @@ class CategoriesEdit extends Component { } getCategoryListClassName( level ) { - const { className } = this.props; - return `${ className }__list ${ className }__list-level-${ level }`; + return `wp-block-categories__list wp-block-categories__list-level-${ level }`; } renderCategoryName( category ) { @@ -89,7 +88,7 @@ class CategoriesEdit extends Component { <li key={ category.id }> <a href={ category.link } target="_blank">{ this.renderCategoryName( category ) }</a> { showPostCounts && - <span className={ `${ this.props.className }__post-count` }> + <span className="wp-block-categories__post-count"> { ' ' }({ category.count }) </span> } @@ -107,7 +106,7 @@ class CategoriesEdit extends Component { } renderCategoryDropdown() { - const { showHierarchy, instanceId, className } = this.props; + const { showHierarchy, instanceId } = this.props; const parentId = showHierarchy ? 0 : null; const categories = this.getCategories( parentId ); const selectId = `blocks-category-select-${ instanceId }`; @@ -116,7 +115,7 @@ class CategoriesEdit extends Component { <label htmlFor={ selectId } className="screen-reader-text"> { __( 'Categories' ) } </label> - <select id={ selectId } className={ `${ className }__dropdown` }> + <select id={ selectId } className="wp-block-categories__dropdown"> { categories.map( ( category ) => this.renderCategoryDropdownItem( category, 0 ) ) } </select> </Fragment> From 884cabf1af84aa3cc0e07c7b949efeb3daa085ab Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Thu, 24 Jan 2019 08:43:15 -0500 Subject: [PATCH 214/691] Plugin: Deprecate gutenberg_remove_wpcom_markdown_support (#13473) --- .../developers/backward-compatibility/deprecations.md | 1 + lib/plugin-compat.php | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/designers-developers/developers/backward-compatibility/deprecations.md b/docs/designers-developers/developers/backward-compatibility/deprecations.md index 85e82f25549c6..b6f49917371be 100644 --- a/docs/designers-developers/developers/backward-compatibility/deprecations.md +++ b/docs/designers-developers/developers/backward-compatibility/deprecations.md @@ -21,6 +21,7 @@ The Gutenberg project's deprecation policy is intended to support backward compa - The PHP function `gutenberg_register_post_prepare_functions` has been removed. - The PHP function `gutenberg_silence_rest_errors` has been removed. - The PHP function `gutenberg_filter_post_type_labels` has been removed. +- The PHP function `gutenberg_remove_wpcom_markdown_support` has been removed. ## 4.5.0 - `Dropdown.refresh()` has been deprecated as the contained `Popover` is now automatically refreshed. diff --git a/lib/plugin-compat.php b/lib/plugin-compat.php index 963535624de4c..74f6dbb2cb0c8 100644 --- a/lib/plugin-compat.php +++ b/lib/plugin-compat.php @@ -21,14 +21,13 @@ * plugins Jetpack, JP-Markdown, and WP Editor.MD * * @since 1.3.0 + * @deprecated 5.0.0 * * @param array $post Post object which contains content to check for block. * @return array $post Post object. */ function gutenberg_remove_wpcom_markdown_support( $post ) { - if ( class_exists( 'WPCom_Markdown' ) && has_blocks( $post['post_content'] ) ) { - WPCom_Markdown::get_instance()->unload_markdown_for_posts(); - } + _deprecated_function( __FUNCTION__, '5.0.0' ); + return $post; } -add_filter( 'wp_insert_post_data', 'gutenberg_remove_wpcom_markdown_support', 9 ); From eab7642a1da9d3e699aeefdee3e467f5c5949302 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Thu, 24 Jan 2019 09:43:57 -0500 Subject: [PATCH 215/691] Plugin: Avoid calling deprecated gutenberg_silence_rest_errors (#13446) --- gutenberg.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 655e883859fda..29259acfadc27 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -187,10 +187,6 @@ function gutenberg_pre_init() { require_once dirname( __FILE__ ) . '/lib/load.php'; - if ( function_exists( 'gutenberg_silence_rest_errors' ) ) { - gutenberg_silence_rest_errors(); - } - add_filter( 'replace_editor', 'gutenberg_init', 10, 2 ); } From 6f54a83f613563d253fcde75adde75c394e0b58c Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Thu, 24 Jan 2019 09:44:28 -0500 Subject: [PATCH 216/691] Plugin: Deprecate gutenberg_bulk_post_updated_messages (#13472) --- .../backward-compatibility/deprecations.md | 1 + lib/register.php | 21 ++++--------------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/docs/designers-developers/developers/backward-compatibility/deprecations.md b/docs/designers-developers/developers/backward-compatibility/deprecations.md index b6f49917371be..d90cbfec03877 100644 --- a/docs/designers-developers/developers/backward-compatibility/deprecations.md +++ b/docs/designers-developers/developers/backward-compatibility/deprecations.md @@ -22,6 +22,7 @@ The Gutenberg project's deprecation policy is intended to support backward compa - The PHP function `gutenberg_silence_rest_errors` has been removed. - The PHP function `gutenberg_filter_post_type_labels` has been removed. - The PHP function `gutenberg_remove_wpcom_markdown_support` has been removed. +- The PHP function `gutenberg_bulk_post_updated_messages` has been removed. ## 4.5.0 - `Dropdown.refresh()` has been deprecated as the contained `Popover` is now automatically refreshed. diff --git a/lib/register.php b/lib/register.php index b86df4fd7a37f..3dc7264a9dfb0 100644 --- a/lib/register.php +++ b/lib/register.php @@ -538,31 +538,18 @@ function gutenberg_register_post_types() { * Apply the correct labels for Reusable Blocks in the bulk action updated messages. * * @since 4.3.0 + * @deprecated 5.0.0 * - * @param array $messages Arrays of messages, each keyed by the corresponding post type. - * @param array $bulk_counts Array of item counts for each message, used to build internationalized strings. + * @param array $messages Arrays of messages, each keyed by the corresponding post type. * * @return array */ -function gutenberg_bulk_post_updated_messages( $messages, $bulk_counts ) { - $messages['wp_block'] = array( - // translators: Number of blocks updated. - 'updated' => _n( '%s block updated.', '%s blocks updated.', $bulk_counts['updated'], 'gutenberg' ), - // translators: Blocks not updated because they're locked. - 'locked' => ( 1 == $bulk_counts['locked'] ) ? __( '1 block not updated, somebody is editing it.', 'gutenberg' ) : _n( '%s block not updated, somebody is editing it.', '%s blocks not updated, somebody is editing them.', $bulk_counts['locked'], 'gutenberg' ), - // translators: Number of blocks deleted. - 'deleted' => _n( '%s block permanently deleted.', '%s blocks permanently deleted.', $bulk_counts['deleted'], 'gutenberg' ), - // translators: Number of blocks trashed. - 'trashed' => _n( '%s block moved to the Trash.', '%s blocks moved to the Trash.', $bulk_counts['trashed'], 'gutenberg' ), - // translators: Number of blocks untrashed. - 'untrashed' => _n( '%s block restored from the Trash.', '%s blocks restored from the Trash.', $bulk_counts['untrashed'], 'gutenberg' ), - ); +function gutenberg_bulk_post_updated_messages( $messages ) { + _deprecated_function( __FUNCTION__, '5.0.0' ); return $messages; } -add_filter( 'bulk_post_updated_messages', 'gutenberg_bulk_post_updated_messages', 10, 2 ); - /** * Injects a hidden input in the edit form to propagate the information that classic editor is selected. * From 4d8fd5d6539b74cbffa9b2fd5e5f0683ac15db3e Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Thu, 24 Jan 2019 10:01:56 -0500 Subject: [PATCH 217/691] Plugin: Deprecate gutenberg_kses_allowedtags (#13460) --- .../developers/backward-compatibility/deprecations.md | 1 + gutenberg.php | 10 ++++------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/designers-developers/developers/backward-compatibility/deprecations.md b/docs/designers-developers/developers/backward-compatibility/deprecations.md index d90cbfec03877..5e7d0d7555be4 100644 --- a/docs/designers-developers/developers/backward-compatibility/deprecations.md +++ b/docs/designers-developers/developers/backward-compatibility/deprecations.md @@ -23,6 +23,7 @@ The Gutenberg project's deprecation policy is intended to support backward compa - The PHP function `gutenberg_filter_post_type_labels` has been removed. - The PHP function `gutenberg_remove_wpcom_markdown_support` has been removed. - The PHP function `gutenberg_bulk_post_updated_messages` has been removed. +- The PHP function `gutenberg_kses_allowedtags` has been removed. ## 4.5.0 - `Dropdown.refresh()` has been deprecated as the contained `Popover` is now automatically refreshed. diff --git a/gutenberg.php b/gutenberg.php index 29259acfadc27..990418ebf74a8 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -527,19 +527,17 @@ function gutenberg_add_admin_body_class( $classes ) { * Adds attributes to kses allowed tags that aren't in the default list * and that Gutenberg needs to save blocks such as the Gallery block. * + * @deprecated 5.0.0 + * * @param array $tags Allowed HTML. * @return array (Maybe) modified allowed HTML. */ function gutenberg_kses_allowedtags( $tags ) { - if ( isset( $tags['img'] ) ) { - $tags['img']['data-link'] = true; - $tags['img']['data-id'] = true; - } + _deprecated_function( __FUNCTION__, '5.0.0' ); + return $tags; } -add_filter( 'wp_kses_allowed_html', 'gutenberg_kses_allowedtags', 10, 2 ); - /** * Adds the wp-embed-responsive class to the body tag if the theme has opted in to * Gutenberg responsive embeds. From c153cc46f94a36b774ea21d88f05a53716d03922 Mon Sep 17 00:00:00 2001 From: Andrea Fercia <a.fercia@gmail.com> Date: Thu, 24 Jan 2019 16:16:49 +0100 Subject: [PATCH 218/691] Add speak messages to the feature toggle component. (#13385) * Add speak messages to the feature toggle component. * Use plain props. --- .../components/header/feature-toggle/index.js | 21 ++++++++++++++++--- .../components/header/writing-menu/index.js | 15 ++++++++++--- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/packages/edit-post/src/components/header/feature-toggle/index.js b/packages/edit-post/src/components/header/feature-toggle/index.js index 3b811e653fac4..c2de21f3ba713 100644 --- a/packages/edit-post/src/components/header/feature-toggle/index.js +++ b/packages/edit-post/src/components/header/feature-toggle/index.js @@ -1,16 +1,30 @@ +/** + * External dependencies + */ +import { flow } from 'lodash'; + /** * WordPress Dependencies */ import { withSelect, withDispatch } from '@wordpress/data'; import { compose } from '@wordpress/compose'; -import { MenuItem } from '@wordpress/components'; +import { MenuItem, withSpokenMessages } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +function FeatureToggle( { onToggle, isActive, label, info, messageActivated, messageDeactivated, speak } ) { + const speakMessage = () => { + if ( isActive ) { + speak( messageDeactivated || __( 'Feature deactivated' ) ); + } else { + speak( messageActivated || __( 'Feature activated' ) ); + } + }; -function FeatureToggle( { onToggle, isActive, label, info } ) { return ( <MenuItem icon={ isActive && 'yes' } isSelected={ isActive } - onClick={ onToggle } + onClick={ flow( onToggle, speakMessage ) } role="menuitemcheckbox" label={ label } info={ info } @@ -30,4 +44,5 @@ export default compose( [ ownProps.onToggle(); }, } ) ), + withSpokenMessages, ] )( FeatureToggle ); diff --git a/packages/edit-post/src/components/header/writing-menu/index.js b/packages/edit-post/src/components/header/writing-menu/index.js index e7b9c3dc791ad..6a3b6f62c4482 100644 --- a/packages/edit-post/src/components/header/writing-menu/index.js +++ b/packages/edit-post/src/components/header/writing-menu/index.js @@ -19,17 +19,26 @@ function WritingMenu( { onClose } ) { feature="fixedToolbar" label={ __( 'Top Toolbar' ) } info={ __( 'Access all block and document tools in a single place' ) } - onToggle={ onClose } /> + onToggle={ onClose } + messageActivated={ __( 'Top toolbar activated' ) } + messageDeactivated={ __( 'Top toolbar deactivated' ) } + /> <FeatureToggle feature="focusMode" label={ __( 'Spotlight Mode' ) } info={ __( 'Focus on one block at a time' ) } - onToggle={ onClose } /> + onToggle={ onClose } + messageActivated={ __( 'Spotlight mode activated' ) } + messageDeactivated={ __( 'Spotlight mode deactivated' ) } + /> <FeatureToggle feature="fullscreenMode" label={ __( 'Fullscreen Mode' ) } info={ __( 'Work without distraction' ) } - onToggle={ onClose } /> + onToggle={ onClose } + messageActivated={ __( 'Fullscreen mode activated' ) } + messageDeactivated={ __( 'Fullscreen mode deactivated' ) } + /> </MenuGroup> ); } From ff9961bd69c34c0caf488276b79c163bc63f94ff Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Thu, 24 Jan 2019 10:32:47 -0500 Subject: [PATCH 219/691] Plugin: Deprecate gutenberg_add_responsive_body_class (#13461) --- .../developers/backward-compatibility/deprecations.md | 1 + gutenberg.php | 8 +++----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/designers-developers/developers/backward-compatibility/deprecations.md b/docs/designers-developers/developers/backward-compatibility/deprecations.md index 5e7d0d7555be4..fe1ca0b4a287e 100644 --- a/docs/designers-developers/developers/backward-compatibility/deprecations.md +++ b/docs/designers-developers/developers/backward-compatibility/deprecations.md @@ -24,6 +24,7 @@ The Gutenberg project's deprecation policy is intended to support backward compa - The PHP function `gutenberg_remove_wpcom_markdown_support` has been removed. - The PHP function `gutenberg_bulk_post_updated_messages` has been removed. - The PHP function `gutenberg_kses_allowedtags` has been removed. +- The PHP function `gutenberg_add_responsive_body_class` has been removed. ## 4.5.0 - `Dropdown.refresh()` has been deprecated as the contained `Popover` is now automatically refreshed. diff --git a/gutenberg.php b/gutenberg.php index 990418ebf74a8..7e019de8b8a25 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -543,15 +543,13 @@ function gutenberg_kses_allowedtags( $tags ) { * Gutenberg responsive embeds. * * @since 4.1.0 + * @deprecated 5.0.0 * * @param Array $classes Array of classes being added to the body tag. * @return Array The $classes array, with wp-embed-responsive appended. */ function gutenberg_add_responsive_body_class( $classes ) { - if ( current_theme_supports( 'responsive-embeds' ) ) { - $classes[] = 'wp-embed-responsive'; - } + _deprecated_function( __FUNCTION__, '5.0.0' ); + return $classes; } - -add_filter( 'body_class', 'gutenberg_add_responsive_body_class' ); From 996dab882bb9df6d1c5d3f6e0c9fd717dbd9ed8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Thu, 24 Jan 2019 16:57:43 +0100 Subject: [PATCH 220/691] Save package-lock.json file changes (#13481) --- package-lock.json | 47 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index 307215cb6a5b2..d862eeb3aa823 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2427,6 +2427,7 @@ "@babel/runtime": "^7.0.0", "@wordpress/api-fetch": "file:packages/api-fetch", "@wordpress/data": "file:packages/data", + "@wordpress/deprecated": "file:packages/deprecated", "@wordpress/url": "file:packages/url", "equivalent-key-map": "^0.2.2", "lodash": "^4.17.10", @@ -2975,6 +2976,7 @@ "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", "dev": true, + "optional": true, "requires": { "kind-of": "^3.0.2", "longest": "^1.0.1", @@ -2986,6 +2988,7 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, + "optional": true, "requires": { "is-buffer": "^1.1.5" } @@ -8048,7 +8051,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -8069,12 +8073,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -8089,17 +8095,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -8216,7 +8225,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -8228,6 +8238,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -8242,6 +8253,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -8249,12 +8261,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -8273,6 +8287,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -8353,7 +8368,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -8365,6 +8381,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -8450,7 +8467,8 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -8486,6 +8504,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -8505,6 +8524,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -8548,12 +8568,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -12657,7 +12679,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", - "dev": true + "dev": true, + "optional": true }, "longest-streak": { "version": "2.0.2", From 9d7846d9ce69b2239596d2ef924f918c97719efd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Thu, 24 Jan 2019 17:53:40 +0100 Subject: [PATCH 221/691] Scripts: Add missing root flag to the default Eslint config (#13483) * Scripts: Add missing root flag to the default Eslint config * scripts: Instruct ESLint to avoid config discovery in defaulting --- packages/scripts/CHANGELOG.md | 4 ++++ packages/scripts/config/.eslintrc.js | 1 + packages/scripts/scripts/lint-js.js | 5 ++++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index 40210edd4b139..9e297b5b652d2 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -5,6 +5,10 @@ - Added support for `build` script ([#12837](https://github.com/WordPress/gutenberg/pull/12837)) - Added support for `start` script ([#12837](https://github.com/WordPress/gutenberg/pull/12837)) +### Bug Fix + +- Avoid inheriting from ESLint configurations in ancestor directories when using the default configuration ([#13483](https://github.com/WordPress/gutenberg/pull/13483)) + ## 2.5.0 (2019-01-09) ### New Features diff --git a/packages/scripts/config/.eslintrc.js b/packages/scripts/config/.eslintrc.js index 5b59ed86f4d2e..4fa347677ff52 100644 --- a/packages/scripts/config/.eslintrc.js +++ b/packages/scripts/config/.eslintrc.js @@ -1,3 +1,4 @@ module.exports = { + root: true, extends: [ 'plugin:@wordpress/eslint-plugin/recommended' ], }; diff --git a/packages/scripts/scripts/lint-js.js b/packages/scripts/scripts/lint-js.js index 64b6cb2036ffc..c7346fb76ad88 100644 --- a/packages/scripts/scripts/lint-js.js +++ b/packages/scripts/scripts/lint-js.js @@ -26,8 +26,11 @@ const hasLintConfig = hasCliArg( '-c' ) || hasProjectFile( '.eslintrc' ) || hasPackageProp( 'eslintConfig' ); +// When a configuration is not provided by the project, use from the default +// provided with the scripts module. Instruct ESLint to avoid discovering via +// the `--no-eslintrc` flag, as otherwise it will still merge with inherited. const config = ! hasLintConfig ? - [ '--config', fromConfigRoot( '.eslintrc.js' ) ] : + [ '--no-eslintrc', '--config', fromConfigRoot( '.eslintrc.js' ) ] : []; const result = spawn( From a99bc7d6ed9a1d9618436b8fbcb9b585e65cabd0 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Thu, 24 Jan 2019 13:29:56 -0500 Subject: [PATCH 222/691] i18n: Use placeholder for minimum WordPress version notice (#13487) * i18n: Use placeholder for minimum WordPress version notice * Collapse echo sprintf to printf Co-Authored-By: aduth <andrew@andrewduthie.com> --- gutenberg.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gutenberg.php b/gutenberg.php index 7e019de8b8a25..902e76675851b 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -146,7 +146,8 @@ function is_gutenberg_page() { */ function gutenberg_wordpress_version_notice() { echo '<div class="error"><p>'; - _e( 'Gutenberg requires WordPress 5.0.0 or later to function properly. Please upgrade WordPress before activating Gutenberg.', 'gutenberg' ); + /* translators: %s: Minimum required version */ + printf( __( 'Gutenberg requires WordPress %s or later to function properly. Please upgrade WordPress before activating Gutenberg.', 'gutenberg' ), '5.0.0' ); echo '</p></div>'; deactivate_plugins( array( 'gutenberg/gutenberg.php' ) ); From 6054e4adf797cce982805bd574772fa82aefb281 Mon Sep 17 00:00:00 2001 From: Benjamin Intal <bfintal@gmail.com> Date: Fri, 25 Jan 2019 03:07:51 +0800 Subject: [PATCH 223/691] Added @fortawesome license in compatible licenses (#12929) @fortawesome https://github.com/FortAwesome/Font-Awesome --- packages/scripts/scripts/check-licenses.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/scripts/scripts/check-licenses.js b/packages/scripts/scripts/check-licenses.js index cbd291d4dd109..2120d0cbfe75e 100644 --- a/packages/scripts/scripts/check-licenses.js +++ b/packages/scripts/scripts/check-licenses.js @@ -65,6 +65,7 @@ const gpl2CompatibleLicenses = [ 'Zlib', '(MIT AND BSD-3-Clause)', '(MIT AND Zlib)', + '(CC-BY-4.0 AND MIT)', ]; /* From ab0acc399ea3457833b2f7f50fd9a3bff90a57d0 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez <diegoreymendez@users.noreply.github.com> Date: Thu, 24 Jan 2019 18:22:50 -0300 Subject: [PATCH 224/691] Add title to gutenberg mobile (#13199) * temporarily disable link formatting * Make sure RichText resigns focus when unmounted (#13048) * Implements a native version of post-title. * Removes some unnecessary log calls. * Submits a few lint fixes. * Fixes a linting issue. * When focusing the title, any focused block loses its focus. * FocusOut is now wired for post-title for mobile. * Removes unused some code. * Added a file I failed to commit. * Fixes a linting issue. --- .../with-focus-outside/index.native.js | 139 ++++++++++++++++++ packages/components/src/index.native.js | 1 + .../editor/src/components/index.native.js | 1 + .../src/components/post-title/index.native.js | 84 +++++++++++ 4 files changed, 225 insertions(+) create mode 100644 packages/components/src/higher-order/with-focus-outside/index.native.js create mode 100644 packages/editor/src/components/post-title/index.native.js diff --git a/packages/components/src/higher-order/with-focus-outside/index.native.js b/packages/components/src/higher-order/with-focus-outside/index.native.js new file mode 100644 index 0000000000000..7d3ae52eaeb78 --- /dev/null +++ b/packages/components/src/higher-order/with-focus-outside/index.native.js @@ -0,0 +1,139 @@ +/** + * External dependencies + */ +import { includes } from 'lodash'; +import { View } from 'react-native'; + +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; +import { createHigherOrderComponent } from '@wordpress/compose'; + +/** + * Input types which are classified as button types, for use in considering + * whether element is a (focus-normalized) button. + * + * @type {string[]} + */ +const INPUT_BUTTON_TYPES = [ + 'button', + 'submit', +]; + +/** + * Returns true if the given element is a button element subject to focus + * normalization, or false otherwise. + * + * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus + * + * @param {Element} element Element to test. + * + * @return {boolean} Whether element is a button. + */ +function isFocusNormalizedButton( element ) { + switch ( element.nodeName ) { + case 'A': + case 'BUTTON': + return true; + + case 'INPUT': + return includes( INPUT_BUTTON_TYPES, element.type ); + } + + return false; +} + +export default createHigherOrderComponent( + ( WrappedComponent ) => { + return class extends Component { + constructor() { + super( ...arguments ); + + this.bindNode = this.bindNode.bind( this ); + this.cancelBlurCheck = this.cancelBlurCheck.bind( this ); + this.queueBlurCheck = this.queueBlurCheck.bind( this ); + this.normalizeButtonFocus = this.normalizeButtonFocus.bind( this ); + } + + componentWillUnmount() { + this.cancelBlurCheck(); + } + + bindNode( node ) { + if ( node ) { + this.node = node; + } else { + delete this.node; + this.cancelBlurCheck(); + } + } + + queueBlurCheck( event ) { + // React does not allow using an event reference asynchronously + // due to recycling behavior, except when explicitly persisted. + event.persist(); + + // Skip blur check if clicking button. See `normalizeButtonFocus`. + if ( this.preventBlurCheck ) { + return; + } + + this.blurCheckTimeout = setTimeout( () => { + if ( 'function' === typeof this.node.handleFocusOutside ) { + this.node.handleFocusOutside( event ); + } + }, 0 ); + } + + cancelBlurCheck() { + clearTimeout( this.blurCheckTimeout ); + } + + /** + * Handles a mousedown or mouseup event to respectively assign and + * unassign a flag for preventing blur check on button elements. Some + * browsers, namely Firefox and Safari, do not emit a focus event on + * button elements when clicked, while others do. The logic here + * intends to normalize this as treating click on buttons as focus. + * + * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus + * + * @param {MouseEvent} event Event for mousedown or mouseup. + */ + normalizeButtonFocus( event ) { + const { type, target } = event; + + const isInteractionEnd = includes( [ 'mouseup', 'touchend' ], type ); + + if ( isInteractionEnd ) { + this.preventBlurCheck = false; + } else if ( isFocusNormalizedButton( target ) ) { + this.preventBlurCheck = true; + } + } + + render() { + // Disable reason: See `normalizeButtonFocus` for browser-specific + // focus event normalization. + + /* eslint-disable jsx-a11y/no-static-element-interactions */ + return ( + <View + onFocus={ this.cancelBlurCheck } + onMouseDown={ this.normalizeButtonFocus } + onMouseUp={ this.normalizeButtonFocus } + onTouchStart={ this.normalizeButtonFocus } + onTouchEnd={ this.normalizeButtonFocus } + onBlur={ this.queueBlurCheck } + > + <WrappedComponent + ref={ this.bindNode } + { ...this.props } /> + </View> + ); + /* eslint-enable jsx-a11y/no-static-element-interactions */ + } + }; + }, 'withFocusOutside' +); diff --git a/packages/components/src/index.native.js b/packages/components/src/index.native.js index 3e44bdec61e50..cbaae28e089bd 100644 --- a/packages/components/src/index.native.js +++ b/packages/components/src/index.native.js @@ -9,3 +9,4 @@ export { createSlotFill, Slot, Fill, Provider as SlotFillProvider } from './slot // Higher-Order Components export { default as withFilters } from './higher-order/with-filters'; +export { default as withFocusOutside } from './higher-order/with-focus-outside'; diff --git a/packages/editor/src/components/index.native.js b/packages/editor/src/components/index.native.js index 229efa5879cc3..d226b4fe46480 100644 --- a/packages/editor/src/components/index.native.js +++ b/packages/editor/src/components/index.native.js @@ -7,5 +7,6 @@ export { default as BlockFormatControls } from './block-format-controls'; export { default as BlockControls } from './block-controls'; export { default as BlockEdit } from './block-edit'; export { default as DefaultBlockAppender } from './default-block-appender'; +export { default as PostTitle } from './post-title'; export { default as EditorHistoryRedo } from './editor-history/redo'; export { default as EditorHistoryUndo } from './editor-history/undo'; diff --git a/packages/editor/src/components/post-title/index.native.js b/packages/editor/src/components/post-title/index.native.js new file mode 100644 index 0000000000000..61148e0f4e7a6 --- /dev/null +++ b/packages/editor/src/components/post-title/index.native.js @@ -0,0 +1,84 @@ +/** + * External dependencies + */ +import { TextInput } from 'react-native'; + +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; +import { decodeEntities } from '@wordpress/html-entities'; +import { withDispatch } from '@wordpress/data'; +import { withFocusOutside } from '@wordpress/components'; +import { withInstanceId, compose } from '@wordpress/compose'; + +class PostTitle extends Component { + constructor() { + super( ...arguments ); + + this.onChange = this.onChange.bind( this ); + this.onSelect = this.onSelect.bind( this ); + this.onUnselect = this.onUnselect.bind( this ); + + this.state = { + isSelected: false, + }; + } + + handleFocusOutside() { + this.onUnselect(); + } + + onSelect() { + this.setState( { isSelected: true } ); + this.props.clearSelectedBlock(); + } + + onUnselect() { + this.setState( { isSelected: false } ); + } + + onChange( title ) { + this.props.onUpdate( title ); + } + + render() { + const { + placeholder, + style, + title, + } = this.props; + + const decodedPlaceholder = decodeEntities( placeholder ); + + return ( + <TextInput + blurOnSubmit={ true } + textAlignVertical="top" + multiline + numberOfLines={ 0 } + onChangeText={ this.onChange } + onFocus={ this.onSelect } + placeholder={ decodedPlaceholder } + style={ style } + value={ title }> + </TextInput> + ); + } +} + +const applyWithDispatch = withDispatch( ( dispatch ) => { + const { + clearSelectedBlock, + } = dispatch( 'core/editor' ); + + return { + clearSelectedBlock, + }; +} ); + +export default compose( + applyWithDispatch, + withInstanceId, + withFocusOutside +)( PostTitle ); From 4afda934cb11cfea57094258c19464d1df6549d9 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Fri, 25 Jan 2019 10:03:52 +0100 Subject: [PATCH 225/691] Performance: optimize the usage of getBlockIndex (#13067) --- .../src/components/block-actions/index.js | 13 +++-- .../src/components/block-draggable/index.js | 5 +- .../src/components/block-drop-zone/index.js | 11 ++-- .../components/block-list-appender/index.js | 1 + .../editor/src/components/block-list/block.js | 40 +++++++------ .../editor/src/components/block-list/index.js | 1 - .../components/block-list/insertion-point.js | 9 ++- .../default-block-appender/index.js | 2 +- .../test/__snapshots__/index.js.snap | 3 + .../editor/src/components/inserter/index.js | 19 ++----- .../editor/src/components/inserter/menu.js | 57 ++++++++++++++++--- 11 files changed, 102 insertions(+), 59 deletions(-) diff --git a/packages/editor/src/components/block-actions/index.js b/packages/editor/src/components/block-actions/index.js index 590d932a626f9..b90325b0affd5 100644 --- a/packages/editor/src/components/block-actions/index.js +++ b/packages/editor/src/components/block-actions/index.js @@ -33,7 +33,6 @@ export default compose( [ withSelect( ( select, props ) => { const { getBlocksByClientId, - getBlockIndex, getTemplateLock, getBlockRootClientId, } = select( 'core/editor' ); @@ -45,8 +44,6 @@ export default compose( [ const rootClientId = getBlockRootClientId( props.clientIds[ 0 ] ); return { - firstSelectedIndex: getBlockIndex( first( castArray( props.clientIds ) ), rootClientId ), - lastSelectedIndex: getBlockIndex( last( castArray( props.clientIds ) ), rootClientId ), isLocked: !! getTemplateLock( rootClientId ), blocks, canDuplicate, @@ -54,13 +51,11 @@ export default compose( [ extraProps: props, }; } ), - withDispatch( ( dispatch, props ) => { + withDispatch( ( dispatch, props, { select } ) => { const { clientIds, rootClientId, blocks, - firstSelectedIndex, - lastSelectedIndex, isLocked, canDuplicate, } = props; @@ -78,6 +73,8 @@ export default compose( [ return; } + const { getBlockIndex } = select( 'core/editor' ); + const lastSelectedIndex = getBlockIndex( last( castArray( clientIds ) ), rootClientId ); const clonedBlocks = blocks.map( ( block ) => cloneBlock( block ) ); insertBlocks( clonedBlocks, @@ -98,11 +95,15 @@ export default compose( [ }, onInsertBefore() { if ( ! isLocked ) { + const { getBlockIndex } = select( 'core/editor' ); + const firstSelectedIndex = getBlockIndex( first( castArray( clientIds ) ), rootClientId ); insertDefaultBlock( {}, rootClientId, firstSelectedIndex ); } }, onInsertAfter() { if ( ! isLocked ) { + const { getBlockIndex } = select( 'core/editor' ); + const lastSelectedIndex = getBlockIndex( last( castArray( clientIds ) ), rootClientId ); insertDefaultBlock( {}, rootClientId, lastSelectedIndex + 1 ); } }, diff --git a/packages/editor/src/components/block-draggable/index.js b/packages/editor/src/components/block-draggable/index.js index 27a44ce2a852b..2f7c3436819a1 100644 --- a/packages/editor/src/components/block-draggable/index.js +++ b/packages/editor/src/components/block-draggable/index.js @@ -33,8 +33,9 @@ const BlockDraggable = ( { children, clientId, rootClientId, blockElementId, ind export default withSelect( ( select, { clientId } ) => { const { getBlockIndex, getBlockRootClientId } = select( 'core/editor' ); + const rootClientId = getBlockRootClientId( clientId ); return { - index: getBlockIndex( clientId ), - rootClientId: getBlockRootClientId( clientId ), + index: getBlockIndex( clientId, rootClientId ), + rootClientId, }; } )( BlockDraggable ); diff --git a/packages/editor/src/components/block-drop-zone/index.js b/packages/editor/src/components/block-drop-zone/index.js index 3e9c0db427e58..a6f426aa6e266 100644 --- a/packages/editor/src/components/block-drop-zone/index.js +++ b/packages/editor/src/components/block-drop-zone/index.js @@ -55,8 +55,9 @@ class BlockDropZone extends Component { } getInsertIndex( position ) { - const { index } = this.props; - if ( index !== undefined ) { + const { clientId, rootClientId, getBlockIndex } = this.props; + if ( clientId !== undefined ) { + const index = getBlockIndex( clientId, rootClientId ); return position.y === 'top' ? index : index + 1; } } @@ -83,7 +84,7 @@ class BlockDropZone extends Component { } onDrop( event, position ) { - const { rootClientId: dstRootClientId, clientId: dstClientId, index: dstIndex, getClientIdsOfDescendants } = this.props; + const { rootClientId: dstRootClientId, clientId: dstClientId, getClientIdsOfDescendants, getBlockIndex } = this.props; const { srcRootClientId, srcClientId, srcIndex, type } = parseDropEvent( event ); const isBlockDropType = ( dropType ) => dropType === 'block'; @@ -101,6 +102,7 @@ class BlockDropZone extends Component { return; } + const dstIndex = dstClientId ? getBlockIndex( dstClientId, dstRootClientId ) : undefined; const positionIndex = this.getInsertIndex( position ); // If the block is kept at the same level and moved downwards, // subtract to account for blocks shifting upward to occupy its old position. @@ -154,10 +156,11 @@ export default compose( }; } ), withSelect( ( select, { rootClientId } ) => { - const { getClientIdsOfDescendants, getTemplateLock } = select( 'core/editor' ); + const { getClientIdsOfDescendants, getTemplateLock, getBlockIndex } = select( 'core/editor' ); return { isLocked: !! getTemplateLock( rootClientId ), getClientIdsOfDescendants, + getBlockIndex, }; } ), withFilters( 'editor.BlockDropZone' ) diff --git a/packages/editor/src/components/block-list-appender/index.js b/packages/editor/src/components/block-list-appender/index.js index b306c2945654f..21ac587e42cd6 100644 --- a/packages/editor/src/components/block-list-appender/index.js +++ b/packages/editor/src/components/block-list-appender/index.js @@ -54,6 +54,7 @@ function BlockListAppender( { disabled={ disabled } /> ) } + isAppender /> </div> ); diff --git a/packages/editor/src/components/block-list/block.js b/packages/editor/src/components/block-list/block.js index 75267e71e0c0d..87d9d11795767 100644 --- a/packages/editor/src/components/block-list/block.js +++ b/packages/editor/src/components/block-list/block.js @@ -26,7 +26,7 @@ import { KeyboardShortcuts, withFilters } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; import { withDispatch, withSelect } from '@wordpress/data'; import { withViewportMatch } from '@wordpress/viewport'; -import { compose } from '@wordpress/compose'; +import { compose, pure } from '@wordpress/compose'; /** * Internal dependencies @@ -59,7 +59,6 @@ export class BlockListBlock extends Component { this.maybeHover = this.maybeHover.bind( this ); this.forceFocusedContextualToolbar = this.forceFocusedContextualToolbar.bind( this ); this.hideHoverEffects = this.hideHoverEffects.bind( this ); - this.insertBlocksAfter = this.insertBlocksAfter.bind( this ); this.onFocus = this.onFocus.bind( this ); this.preventDrag = this.preventDrag.bind( this ); this.onPointerDown = this.onPointerDown.bind( this ); @@ -233,10 +232,6 @@ export class BlockListBlock extends Component { } } - insertBlocksAfter( blocks ) { - this.props.onInsertBlocks( blocks, this.props.order + 1 ); - } - /** * Marks the block as selected when focused and not already selected. This * specifically handles the case where block does not set focus on its own @@ -361,7 +356,6 @@ export class BlockListBlock extends Component { <HoverArea container={ this.wrapperNode }> { ( { hoverArea } ) => { const { - order, mode, isFocusMode, hasFixedToolbar, @@ -397,11 +391,9 @@ export class BlockListBlock extends Component { // Empty paragraph blocks should always show up as unselected. const showEmptyBlockSideInserter = ( isSelected || isHovered ) && isEmptyDefaultBlock && isValid; - const showSideInserter = - ( isSelected || isHovered ) && isEmptyDefaultBlock; const shouldAppearSelected = ! isFocusMode && - ! showSideInserter && + ! showEmptyBlockSideInserter && isSelected && ! isTypingWithinBlock; const shouldAppearHovered = @@ -420,7 +412,7 @@ export class BlockListBlock extends Component { ! isFocusMode && isHovered && ! isEmptyDefaultBlock; const shouldShowContextualToolbar = ! hasFixedToolbar && - ! showSideInserter && + ! showEmptyBlockSideInserter && ( ( isSelected && ( ! isTypingWithinBlock || isCaretWithinFormattedText ) ) || isFirstMultiSelected ); @@ -474,7 +466,7 @@ export class BlockListBlock extends Component { isSelected={ isSelected } attributes={ attributes } setAttributes={ this.setAttributes } - insertBlocksAfter={ isLocked ? undefined : this.insertBlocksAfter } + insertBlocksAfter={ isLocked ? undefined : this.props.onInsertBlocksAfter } onReplace={ isLocked ? undefined : onReplace } mergeBlocks={ isLocked ? undefined : this.props.onMerge } clientId={ clientId } @@ -521,7 +513,6 @@ export class BlockListBlock extends Component { /> ) } <BlockDropZone - index={ order } clientId={ clientId } rootClientId={ rootClientId } /> @@ -612,6 +603,8 @@ export class BlockListBlock extends Component { <Inserter position="top right" onToggle={ this.selectOnOpen } + rootClientId={ rootClientId } + clientId={ clientId } /> </div> </Fragment> @@ -634,7 +627,6 @@ const applyWithSelect = withSelect( isFirstMultiSelectedBlock, isTyping, isCaretWithinFormattedText, - getBlockIndex, getBlockMode, isSelectionEnabled, getSelectedBlocksInitialCaretPosition, @@ -663,10 +655,9 @@ const applyWithSelect = withSelect( isTypingWithinBlock: ( isSelected || isParentOfSelectedBlock ) && isTyping(), isCaretWithinFormattedText: isCaretWithinFormattedText(), - order: getBlockIndex( clientId, rootClientId ), mode: getBlockMode( clientId ), isSelectionEnabled: isSelectionEnabled(), - initialPosition: getSelectedBlocksInitialCaretPosition(), + initialPosition: isSelected ? getSelectedBlocksInitialCaretPosition() : null, isEmptyDefaultBlock: name && isUnmodifiedDefaultBlock( { name, attributes } ), isMovable: 'all' !== templateLock, @@ -715,8 +706,20 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, { select } ) => { insertBlocks( blocks, index, rootClientId ); }, onInsertDefaultBlockAfter() { - const { order, rootClientId } = ownProps; - insertDefaultBlock( {}, rootClientId, order + 1 ); + const { clientId, rootClientId } = ownProps; + const { + getBlockIndex, + } = select( 'core/editor' ); + const index = getBlockIndex( clientId, rootClientId ); + insertDefaultBlock( {}, rootClientId, index + 1 ); + }, + onInsertBlocksAfter( blocks ) { + const { clientId, rootClientId } = ownProps; + const { + getBlockIndex, + } = select( 'core/editor' ); + const index = getBlockIndex( clientId, rootClientId ); + insertBlocks( blocks, index + 1, rootClientId ); }, onRemove( clientId ) { removeBlock( clientId ); @@ -764,6 +767,7 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, { select } ) => { } ); export default compose( + pure, withViewportMatch( { isLargeViewport: 'medium' } ), applyWithSelect, applyWithDispatch, diff --git a/packages/editor/src/components/block-list/index.js b/packages/editor/src/components/block-list/index.js index 7e5a1e7eedfb1..6ab8cd3956239 100644 --- a/packages/editor/src/components/block-list/index.js +++ b/packages/editor/src/components/block-list/index.js @@ -212,7 +212,6 @@ class BlockList extends Component { > <BlockListBlock clientId={ clientId } - index={ blockIndex } blockRef={ this.setBlockRef } onSelectionStart={ this.onSelectionStart } rootClientId={ rootClientId } diff --git a/packages/editor/src/components/block-list/insertion-point.js b/packages/editor/src/components/block-list/insertion-point.js index 252925cc3a6c0..f44f4b69d86e7 100644 --- a/packages/editor/src/components/block-list/insertion-point.js +++ b/packages/editor/src/components/block-list/insertion-point.js @@ -47,7 +47,7 @@ class BlockInsertionPoint extends Component { const { showInsertionPoint, rootClientId, - insertIndex, + clientId, } = this.props; return ( @@ -73,7 +73,7 @@ class BlockInsertionPoint extends Component { > <Inserter rootClientId={ rootClientId } - index={ insertIndex } + clientId={ clientId } /> </div> </div> @@ -87,13 +87,12 @@ export default withSelect( ( select, { clientId, rootClientId } ) => { isBlockInsertionPointVisible, } = select( 'core/editor' ); const blockIndex = getBlockIndex( clientId, rootClientId ); - const insertIndex = blockIndex; const insertionPoint = getBlockInsertionPoint(); const showInsertionPoint = ( isBlockInsertionPointVisible() && - insertionPoint.index === insertIndex && + insertionPoint.index === blockIndex && insertionPoint.rootClientId === rootClientId ); - return { showInsertionPoint, insertIndex }; + return { showInsertionPoint }; } )( BlockInsertionPoint ); diff --git a/packages/editor/src/components/default-block-appender/index.js b/packages/editor/src/components/default-block-appender/index.js index d4b95c4d961ba..594c357064de6 100644 --- a/packages/editor/src/components/default-block-appender/index.js +++ b/packages/editor/src/components/default-block-appender/index.js @@ -67,7 +67,7 @@ export function DefaultBlockAppender( { value={ showPrompt ? value : '' } /> { hovered && <InserterWithShortcuts rootClientId={ rootClientId } /> } - <Inserter position="top right" /> + <Inserter rootClientId={ rootClientId } position="top right" isAppender /> </div> ); } diff --git a/packages/editor/src/components/default-block-appender/test/__snapshots__/index.js.snap b/packages/editor/src/components/default-block-appender/test/__snapshots__/index.js.snap index dc26d4405d863..c542d5b5dd65e 100644 --- a/packages/editor/src/components/default-block-appender/test/__snapshots__/index.js.snap +++ b/packages/editor/src/components/default-block-appender/test/__snapshots__/index.js.snap @@ -25,6 +25,7 @@ exports[`DefaultBlockAppender should append a default block when input focused 1 value="Start writing or type / to choose a block" /> <WithSelect(IfCondition(Inserter)) + isAppender={true} position="top right" /> </div> @@ -48,6 +49,7 @@ exports[`DefaultBlockAppender should match snapshot 1`] = ` value="Start writing or type / to choose a block" /> <WithSelect(IfCondition(Inserter)) + isAppender={true} position="top right" /> </div> @@ -71,6 +73,7 @@ exports[`DefaultBlockAppender should optionally show without prompt 1`] = ` value="" /> <WithSelect(IfCondition(Inserter)) + isAppender={true} position="top right" /> </div> diff --git a/packages/editor/src/components/inserter/index.js b/packages/editor/src/components/inserter/index.js index 0012fbc8ddb1d..8c181f3cbd24c 100644 --- a/packages/editor/src/components/inserter/index.js +++ b/packages/editor/src/components/inserter/index.js @@ -70,13 +70,14 @@ class Inserter extends Component { * @return {WPElement} Dropdown content element. */ renderContent( { onClose } ) { - const { rootClientId, index } = this.props; + const { rootClientId, clientId, isAppender } = this.props; return ( <InserterMenu onSelect={ onClose } rootClientId={ rootClientId } - index={ index } + clientId={ clientId } + isAppender={ isAppender } /> ); } @@ -100,27 +101,15 @@ class Inserter extends Component { } export default compose( [ - withSelect( ( select, { rootClientId, index } ) => { + withSelect( ( select, { rootClientId } ) => { const { getEditedPostAttribute, - getBlockInsertionPoint, hasInserterItems, } = select( 'core/editor' ); - if ( rootClientId === undefined && index === undefined ) { - // Unless explicitly provided, the default insertion point provided - // by the store occurs immediately following the selected block. - // Otherwise, the default behavior for an undefined index is to - // append block to the end of the rootClientId context. - const insertionPoint = getBlockInsertionPoint(); - ( { rootClientId, index } = insertionPoint ); - } - return { title: getEditedPostAttribute( 'title' ), hasItems: hasInserterItems( rootClientId ), - rootClientId, - index, }; } ), ifCondition( ( { hasItems } ) => hasItems ), diff --git a/packages/editor/src/components/inserter/menu.js b/packages/editor/src/components/inserter/menu.js index 9f6dea181e14c..bae98e911b090 100644 --- a/packages/editor/src/components/inserter/menu.js +++ b/packages/editor/src/components/inserter/menu.js @@ -132,8 +132,7 @@ export class InserterMenu extends Component { const { showInsertionPoint, hideInsertionPoint } = this.props; if ( item ) { - const { rootClientId, index } = this.props; - showInsertionPoint( rootClientId, index ); + showInsertionPoint(); } else { hideInsertionPoint(); } @@ -369,24 +368,68 @@ export default compose( hideInsertionPoint, } = dispatch( 'core/editor' ); + // To avoid duplication, getInsertionPoint is extracted and used in two event handlers + // This breaks the withDispatch not containing any logic rule. + // Since it's a function only called when the event handlers are called, + // it's fine to extract it. + // eslint-disable-next-line no-restricted-syntax + function getInsertionPoint() { + const { + getBlockIndex, + getBlockRootClientId, + getBlockSelectionEnd, + getBlockOrder, + } = select( 'core/editor' ); + const { clientId, rootClientId, isAppender } = ownProps; + + // If the clientId is defined, we insert at the position of the block. + if ( clientId ) { + return { + index: getBlockIndex( clientId, rootClientId ), + rootClientId, + }; + } + + // If there a selected block, we insert after the selected block. + const end = getBlockSelectionEnd(); + if ( ! isAppender && end ) { + const selectedBlockRootClientId = getBlockRootClientId( end ) || undefined; + return { + index: getBlockIndex( end, selectedBlockRootClientId ) + 1, + rootClientId: selectedBlockRootClientId, + }; + } + + // Otherwise, we insert at the end of the current rootClientId + return { + index: getBlockOrder( rootClientId ).length, + rootClientId, + }; + } + return { fetchReusableBlocks, - showInsertionPoint, + showInsertionPoint() { + const { index, rootClientId } = getInsertionPoint(); + showInsertionPoint( rootClientId, index ); + }, hideInsertionPoint, onSelect( item ) { const { replaceBlocks, insertBlock, } = dispatch( 'core/editor' ); - const { getSelectedBlock } = select( 'core/editor' ); - const { index, rootClientId } = ownProps; + const { + getSelectedBlock, + } = select( 'core/editor' ); + const { isAppender } = ownProps; const { name, initialAttributes } = item; - const selectedBlock = getSelectedBlock(); const insertedBlock = createBlock( name, initialAttributes ); - if ( selectedBlock && isUnmodifiedDefaultBlock( selectedBlock ) ) { + if ( ! isAppender && selectedBlock && isUnmodifiedDefaultBlock( selectedBlock ) ) { replaceBlocks( selectedBlock.clientId, insertedBlock ); } else { + const { index, rootClientId } = getInsertionPoint(); insertBlock( insertedBlock, index, rootClientId ); } From be9870de7d289fd5edb9e935cc46f7f8ba228cca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Wrede?= <soerenwrede@gmail.com> Date: Fri, 25 Jan 2019 10:09:41 +0100 Subject: [PATCH 226/691] Add new RSS Block (#7966) --- lib/load.php | 3 + packages/block-library/src/editor.scss | 1 + packages/block-library/src/index.js | 2 + packages/block-library/src/rss/edit.js | 169 ++++++++++++++++++ packages/block-library/src/rss/editor.scss | 6 + packages/block-library/src/rss/index.js | 33 ++++ packages/block-library/src/rss/index.php | 137 ++++++++++++++ packages/block-library/src/rss/style.scss | 35 ++++ packages/block-library/src/style.scss | 1 + .../full-content/fixtures/core__rss.html | 1 + .../full-content/fixtures/core__rss.json | 10 ++ .../fixtures/core__rss.parsed.json | 26 +++ .../fixtures/core__rss.serialized.html | 1 + 13 files changed, 425 insertions(+) create mode 100644 packages/block-library/src/rss/edit.js create mode 100644 packages/block-library/src/rss/editor.scss create mode 100644 packages/block-library/src/rss/index.js create mode 100644 packages/block-library/src/rss/index.php create mode 100644 packages/block-library/src/rss/style.scss create mode 100644 test/integration/full-content/fixtures/core__rss.html create mode 100644 test/integration/full-content/fixtures/core__rss.json create mode 100644 test/integration/full-content/fixtures/core__rss.parsed.json create mode 100644 test/integration/full-content/fixtures/core__rss.serialized.html diff --git a/lib/load.php b/lib/load.php index 8306f6f030f83..ada0850ce4163 100644 --- a/lib/load.php +++ b/lib/load.php @@ -43,6 +43,9 @@ if ( ! function_exists( 'render_block_core_latest_posts' ) ) { require dirname( __FILE__ ) . '/../packages/block-library/src/latest-posts/index.php'; } +if ( ! function_exists( 'render_block_core_rss' ) ) { + require dirname( __FILE__ ) . '/../packages/block-library/src/rss/index.php'; +} if ( ! function_exists( 'render_block_core_shortcode' ) ) { require dirname( __FILE__ ) . '/../packages/block-library/src/shortcode/index.php'; } diff --git a/packages/block-library/src/editor.scss b/packages/block-library/src/editor.scss index eb2dea826189b..9fb94f40bdef7 100644 --- a/packages/block-library/src/editor.scss +++ b/packages/block-library/src/editor.scss @@ -22,6 +22,7 @@ @import "./preformatted/editor.scss"; @import "./pullquote/editor.scss"; @import "./quote/editor.scss"; +@import "./rss/editor.scss"; @import "./shortcode/editor.scss"; @import "./spacer/editor.scss"; @import "./subhead/editor.scss"; diff --git a/packages/block-library/src/index.js b/packages/block-library/src/index.js index ecb354f8c373e..5dd3fee7b2a29 100644 --- a/packages/block-library/src/index.js +++ b/packages/block-library/src/index.js @@ -38,6 +38,7 @@ import * as nextpage from './nextpage'; import * as preformatted from './preformatted'; import * as pullquote from './pullquote'; import * as reusableBlock from './block'; +import * as rss from './rss'; import * as separator from './separator'; import * as shortcode from './shortcode'; import * as spacer from './spacer'; @@ -85,6 +86,7 @@ export const registerCoreBlocks = () => { nextpage, preformatted, pullquote, + rss, separator, reusableBlock, spacer, diff --git a/packages/block-library/src/rss/edit.js b/packages/block-library/src/rss/edit.js new file mode 100644 index 0000000000000..9ebfa94491533 --- /dev/null +++ b/packages/block-library/src/rss/edit.js @@ -0,0 +1,169 @@ +/** + * WordPress dependencies + */ +import { Component, Fragment } from '@wordpress/element'; +import { + Button, + Disabled, + PanelBody, + Placeholder, + RangeControl, + ServerSideRender, + TextControl, + ToggleControl, + Toolbar, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { + BlockControls, + InspectorControls, +} from '@wordpress/editor'; + +const DEFAULT_MIN_ITEMS = 1; +const DEFAULT_MAX_ITEMS = 10; + +class RSSEdit extends Component { + constructor() { + super( ...arguments ); + + this.state = { + editing: ! this.props.attributes.feedURL, + }; + + this.toggleAttribute = this.toggleAttribute.bind( this ); + this.onSubmitURL = this.onSubmitURL.bind( this ); + } + + toggleAttribute( propName ) { + return () => { + const value = this.props.attributes[ propName ]; + const { setAttributes } = this.props; + + setAttributes( { [ propName ]: ! value } ); + }; + } + + onSubmitURL( event ) { + event.preventDefault(); + + const { feedURL } = this.props.attributes; + if ( feedURL ) { + this.setState( { editing: false } ); + } + } + + render() { + const { + blockLayout, + columns, + displayAuthor, + displayExcerpt, + displayDate, + excerptLength, + feedURL, + itemsToShow, + } = this.props.attributes; + const { setAttributes } = this.props; + + if ( this.state.editing ) { + return ( + <Placeholder + icon="rss" + label="RSS" + > + <form onSubmit={ this.onSubmitURL }> + <TextControl + placeholder={ __( 'Enter URL here…' ) } + value={ feedURL } + onChange={ ( value ) => setAttributes( { feedURL: value } ) } + className={ 'components-placeholder__input' } + /> + <Button isLarge type="submit"> + { __( 'Use URL' ) } + </Button> + </form> + </Placeholder> + ); + } + + const toolbarControls = [ + { + icon: 'edit', + title: __( 'Edit RSS URL' ), + onClick: () => this.setState( { editing: true } ), + }, + { + icon: 'list-view', + title: __( 'List View' ), + onClick: () => setAttributes( { blockLayout: 'list' } ), + isActive: blockLayout === 'list', + }, + { + icon: 'grid-view', + title: __( 'Grid View' ), + onClick: () => setAttributes( { blockLayout: 'grid' } ), + isActive: blockLayout === 'grid', + }, + ]; + + return ( + <Fragment> + <BlockControls> + <Toolbar controls={ toolbarControls } /> + </BlockControls> + <InspectorControls> + <PanelBody title={ __( 'RSS Settings' ) }> + <RangeControl + label={ __( 'Number of items' ) } + value={ itemsToShow } + onChange={ ( value ) => setAttributes( { itemsToShow: value } ) } + min={ DEFAULT_MIN_ITEMS } + max={ DEFAULT_MAX_ITEMS } + /> + <ToggleControl + label={ __( 'Display author' ) } + checked={ displayAuthor } + onChange={ this.toggleAttribute( 'displayAuthor' ) } + /> + <ToggleControl + label={ __( 'Display date' ) } + checked={ displayDate } + onChange={ this.toggleAttribute( 'displayDate' ) } + /> + <ToggleControl + label={ __( 'Display excerpt' ) } + checked={ displayExcerpt } + onChange={ this.toggleAttribute( 'displayExcerpt' ) } + /> + { displayExcerpt && + <RangeControl + label={ __( 'Max length of the excerpt' ) } + value={ excerptLength } + onChange={ ( value ) => setAttributes( { excerptLength: value } ) } + min={ 0 } + max={ 100 } + /> + } + { blockLayout === 'grid' && + <RangeControl + label={ __( 'Columns' ) } + value={ columns } + onChange={ ( value ) => setAttributes( { columns: value } ) } + min={ 2 } + max={ 6 } + /> + } + </PanelBody> + </InspectorControls> + <Disabled> + <ServerSideRender + block="core/rss" + attributes={ this.props.attributes } + /> + </Disabled> + </Fragment> + ); + } +} + +export default RSSEdit; diff --git a/packages/block-library/src/rss/editor.scss b/packages/block-library/src/rss/editor.scss new file mode 100644 index 0000000000000..8632c5e101b00 --- /dev/null +++ b/packages/block-library/src/rss/editor.scss @@ -0,0 +1,6 @@ +.block-editor .wp-block-rss { + padding-left: 2.5em; + &.is-grid { + padding-left: 0; + } +} diff --git a/packages/block-library/src/rss/index.js b/packages/block-library/src/rss/index.js new file mode 100644 index 0000000000000..6a0f916c7d6e5 --- /dev/null +++ b/packages/block-library/src/rss/index.js @@ -0,0 +1,33 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import edit from './edit'; + +export const name = 'core/rss'; + +export const settings = { + title: __( 'RSS' ), + + description: __( 'Display entries from any RSS or Atom feed.' ), + + icon: 'rss', + + category: 'widgets', + + keywords: [ __( 'atom' ), __( 'feed' ) ], + + supports: { + html: false, + }, + + edit, + + save() { + return null; + }, +}; diff --git a/packages/block-library/src/rss/index.php b/packages/block-library/src/rss/index.php new file mode 100644 index 0000000000000..b5fe3abaf563b --- /dev/null +++ b/packages/block-library/src/rss/index.php @@ -0,0 +1,137 @@ +<?php +/** + * Server-side rendering of the `core/rss` block. + * + * @package WordPress + */ + +/** + * Renders the `core/rss` block on server. + * + * @param array $attributes The block attributes. + * + * @return string Returns the block content with received rss items. + */ +function render_block_core_rss( $attributes ) { + $rss = fetch_feed( $attributes['feedURL'] ); + + if ( is_wp_error( $rss ) ) { + return '<div class="components-placeholder"><div class="notice notice-error"><strong>' . __( 'RSS Error:' ) . '</strong> ' . $rss->get_error_message() . '</div></div>'; + } + + if ( ! $rss->get_item_quantity() ) { + // PHP 5.2 compatibility. See: http://simplepie.org/wiki/faq/i_m_getting_memory_leaks. + $rss->__destruct(); + unset( $rss ); + + return '<div class="components-placeholder"><div class="notice notice-error">' . __( 'An error has occurred, which probably means the feed is down. Try again later.' ) . '</div></div>'; + } + + $rss_items = $rss->get_items( 0, $attributes['itemsToShow'] ); + $list_items = ''; + foreach ( $rss_items as $item ) { + $title = esc_html( trim( strip_tags( $item->get_title() ) ) ); + if ( empty( $title ) ) { + $title = __( '(Untitled)' ); + } + $link = $item->get_link(); + $link = esc_url( $link ); + if ( $link ) { + $title = "<a href='{$link}'>{$title}</a>"; + } + $title = "<div class='wp-block-rss__item-title'>{$title}</div>"; + + $date = ''; + if ( $attributes['displayDate'] ) { + $date = $item->get_date( 'U' ); + + if ( $date ) { + $date = sprintf( + '<time datetime="%1$s" class="wp-block-rss__item-publish-date">%2$s</time> ', + date_i18n( get_option( 'c' ), $date ), + date_i18n( get_option( 'date_format' ), $date ) + ); + } + } + + $author = ''; + if ( $attributes['displayAuthor'] ) { + $author = $item->get_author(); + if ( is_object( $author ) ) { + $author = $author->get_name(); + $author = '<span class="wp-block-rss__item-author">' . __( 'by' ) . ' ' . esc_html( strip_tags( $author ) ) . '</span>'; + } + } + + $excerpt = ''; + if ( $attributes['displayExcerpt'] ) { + $excerpt = html_entity_decode( $item->get_description(), ENT_QUOTES, get_option( 'blog_charset' ) ); + $excerpt = esc_attr( wp_trim_words( $excerpt, $attributes['excerptLength'], ' [&hellip;]' ) ); + + // Change existing [...] to [&hellip;]. + if ( '[...]' == substr( $excerpt, -5 ) ) { + $excerpt = substr( $excerpt, 0, -5 ) . '[&hellip;]'; + } + + $excerpt = '<div class="wp-block-rss__item-excerpt">' . esc_html( $excerpt ) . '</div>'; + } + + $list_items .= "<li class='wp-block-rss__item'>{$title}{$date}{$author}{$excerpt}</li>"; + } + + $classes = 'grid' === $attributes['blockLayout'] ? ' is-grid columns-' . $attributes['columns'] : ''; + $list_items_markup = "<ul class='wp-block-rss{$classes}'>{$list_items}</ul>"; + + // PHP 5.2 compatibility. See: http://simplepie.org/wiki/faq/i_m_getting_memory_leaks. + $rss->__destruct(); + unset( $rss ); + + return $list_items_markup; +} + +/** + * Registers the `core/rss` block on server. + */ +function register_block_core_rss() { + register_block_type( 'core/rss', + array( + 'attributes' => array( + 'columns' => array( + 'type' => 'number', + 'default' => 2, + ), + 'blockLayout' => array( + 'type' => 'string', + 'default' => 'list', + ), + 'feedURL' => array( + 'type' => 'string', + 'default' => '', + ), + 'itemsToShow' => array( + 'type' => 'number', + 'default' => 5, + ), + 'displayExcerpt' => array( + 'type' => 'boolean', + 'default' => false, + ), + 'displayAuthor' => array( + 'type' => 'boolean', + 'default' => false, + ), + 'displayDate' => array( + 'type' => 'boolean', + 'default' => false, + ), + 'excerptLength' => array( + 'type' => 'number', + 'default' => 55, + ), + ), + 'render_callback' => 'render_block_core_rss', + ) + ); +} + +add_action( 'init', 'register_block_core_rss' ); diff --git a/packages/block-library/src/rss/style.scss b/packages/block-library/src/rss/style.scss new file mode 100644 index 0000000000000..a2ad8d4060dff --- /dev/null +++ b/packages/block-library/src/rss/style.scss @@ -0,0 +1,35 @@ +.wp-block-rss { + &.alignleft { + /*rtl:ignore*/ + margin-right: 2em; + } + &.alignright { + /*rtl:ignore*/ + margin-left: 2em; + } + &.is-grid { + display: flex; + flex-wrap: wrap; + padding: 0; + list-style: none; + + li { + margin: 0 16px 16px 0; + width: 100%; + } + } + + @include break-small { + @for $i from 2 through 6 { + &.columns-#{ $i } li { + width: calc(( 100% / #{ $i } ) - 16px); + } + } + } +} + +.wp-block-rss__item-publish-date, +.wp-block-rss__item-author { + color: $dark-gray-300; + font-size: $default-font-size; +} diff --git a/packages/block-library/src/style.scss b/packages/block-library/src/style.scss index 9d9b2379696b2..09d3a2125e378 100644 --- a/packages/block-library/src/style.scss +++ b/packages/block-library/src/style.scss @@ -15,6 +15,7 @@ @import "./paragraph/style.scss"; @import "./pullquote/style.scss"; @import "./quote/style.scss"; +@import "./rss/style.scss"; @import "./separator/style.scss"; @import "./subhead/style.scss"; @import "./table/style.scss"; diff --git a/test/integration/full-content/fixtures/core__rss.html b/test/integration/full-content/fixtures/core__rss.html new file mode 100644 index 0000000000000..ce652528301c6 --- /dev/null +++ b/test/integration/full-content/fixtures/core__rss.html @@ -0,0 +1 @@ +<!-- wp:rss {"postLayout":"grid","feedURL":"https://wordpress.org/news/","postsToShow":4,"displayExcerpt":true,"displayAuthor":true,"displayDate":true,"excerptLength":20} /--> diff --git a/test/integration/full-content/fixtures/core__rss.json b/test/integration/full-content/fixtures/core__rss.json new file mode 100644 index 0000000000000..89de06cd2d78d --- /dev/null +++ b/test/integration/full-content/fixtures/core__rss.json @@ -0,0 +1,10 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/rss", + "isValid": true, + "attributes": {}, + "innerBlocks": [], + "originalContent": "" + } +] diff --git a/test/integration/full-content/fixtures/core__rss.parsed.json b/test/integration/full-content/fixtures/core__rss.parsed.json new file mode 100644 index 0000000000000..bdc596b09a5a1 --- /dev/null +++ b/test/integration/full-content/fixtures/core__rss.parsed.json @@ -0,0 +1,26 @@ +[ + { + "blockName": "core/rss", + "attrs": { + "postLayout": "grid", + "feedURL": "https://wordpress.org/news/", + "postsToShow": 4, + "displayExcerpt": true, + "displayAuthor": true, + "displayDate": true, + "excerptLength": 20 + }, + "innerBlocks": [], + "innerHTML": "", + "innerContent": [] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/test/integration/full-content/fixtures/core__rss.serialized.html b/test/integration/full-content/fixtures/core__rss.serialized.html new file mode 100644 index 0000000000000..c6d9be22646e7 --- /dev/null +++ b/test/integration/full-content/fixtures/core__rss.serialized.html @@ -0,0 +1 @@ +<!-- wp:rss /--> From 235a1a4bf05801cb8b8597fbd5ce61248b0c9f77 Mon Sep 17 00:00:00 2001 From: Joen Asmussen <joen@automattic.com> Date: Fri, 25 Jan 2019 12:21:00 +0100 Subject: [PATCH 227/691] Fix issue with galleries in Microsoft Edge. (#13326) Fixes #13270. Props @designsimply for proposed fix, extensive debugging, and finding the right solution. Turns out, as Sheri correctly identified, there is a bug with Microsoft Edge where the browser is basically rewriting whatever is inside the `calc` rule. As she noted: > Edge calculates an ever-so-slightly different width as calc(-10.6667px + 33.3333%) served via style.min.css and when Gutenberg is deactivated then Edge calculates the width as calc(-10.66px + 33.33%) served via style.css. I noticed that changing margin-right to be even one pixel smaller, from 16px to 15px The difference between `-10.6667px + 33.3333%` and `-10.66px + 33.33%` is enough to cause three columns to become two. This PR adopts Sheri's proposed fix, and wraps it in an Edge-Only rule. Don't worry, normal browsers ignore that rule, and also do not rewrite `calc` rules. Here's what Chrome shows in the inspector: `width: calc((100% - 16px * 2) / 3);` (as it should). Before: <img width="765" alt="before" src="https://user-images.githubusercontent.com/1204802/51174867-20cf8c80-18b9-11e9-9fd9-537c78809c5d.png"> After: <img width="809" alt="after" src="https://user-images.githubusercontent.com/1204802/51174833-0b5a6280-18b9-11e9-8b3d-3a8a766db713.png"> --- packages/block-library/src/gallery/style.scss | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/block-library/src/gallery/style.scss b/packages/block-library/src/gallery/style.scss index 26053298a3b3b..a62465bb4f225 100644 --- a/packages/block-library/src/gallery/style.scss +++ b/packages/block-library/src/gallery/style.scss @@ -101,6 +101,13 @@ &.columns-#{ $i } .blocks-gallery-item { width: calc((100% - #{ $grid-size-large } * #{ $i - 1 }) / #{ $i }); margin-right: 16px; + + // Rules inside this query are only run by Microsoft Edge. + // Edge miscalculates `calc`, so we have to add some buffer. + // See also https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/15637241/ + @supports (-ms-ime-align:auto) { + width: calc((100% - #{ $grid-size-large } * #{ $i - 1 }) / #{ $i } - 1px); + } } } From 40e79d5015408a45ce9cfde3d2aa04ca5bbdf315 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milan=20Dini=C4=87?= <milan@srpski.biz> Date: Fri, 25 Jan 2019 13:00:08 +0100 Subject: [PATCH 228/691] Use sentence case in toolbar tooltips. (#12239) --- packages/block-library/src/gallery/edit.js | 2 +- packages/block-library/src/table/edit.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/gallery/edit.js b/packages/block-library/src/gallery/edit.js index 5fee7758973d5..4415a3dc563b3 100644 --- a/packages/block-library/src/gallery/edit.js +++ b/packages/block-library/src/gallery/edit.js @@ -202,7 +202,7 @@ class GalleryEdit extends Component { render={ ( { open } ) => ( <IconButton className="components-toolbar__control" - label={ __( 'Edit Gallery' ) } + label={ __( 'Edit gallery' ) } icon="edit" onClick={ open } /> diff --git a/packages/block-library/src/table/edit.js b/packages/block-library/src/table/edit.js index b0c789a724720..175de8277d071 100644 --- a/packages/block-library/src/table/edit.js +++ b/packages/block-library/src/table/edit.js @@ -436,7 +436,7 @@ export class TableEdit extends Component { <Toolbar> <DropdownMenu icon="editor-table" - label={ __( 'Edit Table' ) } + label={ __( 'Edit table' ) } controls={ this.getTableControls() } /> </Toolbar> From 2b49f1e26c45e24ecc622cd92ed00a576a075dbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Wrede?= <soerenwrede@gmail.com> Date: Fri, 25 Jan 2019 13:20:03 +0100 Subject: [PATCH 229/691] Change RSS excerpt length label (#13501) --- packages/block-library/src/rss/edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/rss/edit.js b/packages/block-library/src/rss/edit.js index 9ebfa94491533..8e89146527f60 100644 --- a/packages/block-library/src/rss/edit.js +++ b/packages/block-library/src/rss/edit.js @@ -137,7 +137,7 @@ class RSSEdit extends Component { /> { displayExcerpt && <RangeControl - label={ __( 'Max length of the excerpt' ) } + label={ __( 'Max number of words in excerpt' ) } value={ excerptLength } onChange={ ( value ) => setAttributes( { excerptLength: value } ) } min={ 0 } From f1e827130741bc9536fbdcb4d621f552465d8aaa Mon Sep 17 00:00:00 2001 From: Andrea Fercia <a.fercia@gmail.com> Date: Fri, 25 Jan 2019 13:29:53 +0100 Subject: [PATCH 230/691] Change the inserter search result message from assertive to polite. (#13388) --- packages/editor/src/components/inserter/menu.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor/src/components/inserter/menu.js b/packages/editor/src/components/inserter/menu.js index bae98e911b090..0f5ebb437f9e1 100644 --- a/packages/editor/src/components/inserter/menu.js +++ b/packages/editor/src/components/inserter/menu.js @@ -224,7 +224,7 @@ export class InserterMenu extends Component { resultCount ); - debouncedSpeak( resultsFoundMessage, 'assertive' ); + debouncedSpeak( resultsFoundMessage ); } onKeyDown( event ) { From 306f45480de247a7fd9d2b953e1c6134100fc2cf Mon Sep 17 00:00:00 2001 From: Jeremy Green <endocreative@gmail.com> Date: Fri, 25 Jan 2019 05:36:53 -0700 Subject: [PATCH 231/691] add note about wp-editor dependancy (#12731) * add note about wp-editor dependancy If you don't add wp-editor as a dependency, the example code will throw an error. * Update introducing-attributes-and-editable-fields.md * move sample code below description --- .../introducing-attributes-and-editable-fields.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md b/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md index 3a993ce1c2b71..cd2b40e7d561e 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md @@ -120,6 +120,20 @@ Earlier examples used the `createElement` function to create DOM nodes, but it's The `RichText` component can be considered as a super-powered `textarea` element, enabling rich content editing including bold, italics, hyperlinks, etc. It is not too much unlike the single editor region of the legacy post editor, and is in fact powered by the same TinyMCE library. +To use the `RichText` component, add `wp-editor` to the array of registered script handles when calling `wp_register_script`. + +```php +wp_register_script( + 'gutenberg-boilerplate-es5-step03', + plugins_url( 'step-03/block.js', __FILE__ ), + array( + 'wp-blocks', + 'wp-element', + 'wp-editor', // Note the addition of wp-editor to the dependencies + ) +); +``` + Implementing this behavior as a component enables you as the block implementer to be much more granular about editable fields. Your block may not need `RichText` at all, or it may need many independent `RichText` elements, each operating on a subset of the overall block state. Because `RichText` allows for nested nodes, you'll most often use it in conjunction with the `html` attribute source when extracting the value from saved content. You'll also use `RichText.Content` in the `save` function to output RichText values. From 2d7035bf77a02e1a3447b1ba2228b692a823c786 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell@kjellr.com> Date: Fri, 25 Jan 2019 08:31:05 -0500 Subject: [PATCH 232/691] File Block: Remove the "Show Download Button" toggle help text (#13485) As per discussion in #13429, this help text is redundant and can be removed. There's still some discussion to be had about adding some sort of "on/off" text to clarify the state for toggles like this, but that should be tackled globally. --- packages/block-library/src/file/inspector.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/block-library/src/file/inspector.js b/packages/block-library/src/file/inspector.js index cc3bb8acad78f..4c686ad5afe6d 100644 --- a/packages/block-library/src/file/inspector.js +++ b/packages/block-library/src/file/inspector.js @@ -10,10 +10,6 @@ import { import { Fragment } from '@wordpress/element'; import { InspectorControls } from '@wordpress/editor'; -function getDownloadButtonHelp( checked ) { - return checked ? __( 'The download button is visible.' ) : __( 'The download button is hidden.' ); -} - export default function FileBlockInspector( { hrefs, openInNewWindow, @@ -51,7 +47,6 @@ export default function FileBlockInspector( { <PanelBody title={ __( 'Download Button Settings' ) }> <ToggleControl label={ __( 'Show Download Button' ) } - help={ getDownloadButtonHelp } checked={ showDownloadButton } onChange={ changeShowDownloadButton } /> From 47d6a1dff622f12f8b0f5fe91bc3ab46b83afbce Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Fri, 25 Jan 2019 06:14:58 -0800 Subject: [PATCH 233/691] Add additional explanation and details for compose package (#13496) * Add explanation and example for compose package Uses info from issue #6220 to add additional details and example for compose package. Props to @gziolo for initial write up in ticket. Fixes #6220 * Add back footer, link to flowRight --- packages/compose/README.md | 49 +++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/packages/compose/README.md b/packages/compose/README.md index f17d4ef633fec..93d5cc6b1d742 100644 --- a/packages/compose/README.md +++ b/packages/compose/README.md @@ -2,6 +2,51 @@ The `compose` package is a collection of handy [Higher Order Components](https://facebook.github.io/react/docs/higher-order-components.html) (HOCs) you can use to wrap your WordPress components and provide some basic features like: state, instance id, pure... +The **compose** function is an alias to [flowRight](https://lodash.com/docs/#flowRight) from Lodash. It comes from functional programming world and allows to compose any number of functions. An example that illustrates it for two functions: + +```js +const compose = ( f, g ) => x + => f( g( x ) ); +``` + +Here's a simplified example of **compose** in use from our code, see [plugin-sidebar](https://github.com/WordPress/gutenberg/blob/master/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js) for full code: + +Using compose: + +```js +const applyWithSelect = withSelect( ( select, ownProps ) => { + return doSomething( select, ownProps); +} ); +const applyWithDispatch = withDispatch( ( dispatch, ownProps ) => { + return doSomethingElse( dispatch, ownProps ); +} ); + +export default compose( + withPluginContext, + applyWithSelect, + applyWithDispatch, +)( PluginSidebarMoreMenuItem ); +``` + +Equivalent to the following without compose: + +```js +const applyWithSelect = withSelect( ( select, ownProps ) => { + return doSomething( select, ownProps); +} ); +const applyWithDispatch = withDispatch( ( dispatch, ownProps ) => { + return doSomethingElse( dispatch, ownProps ); +} ); + +export default withPluginContext( + applyWithSelect( + applyWithDispatch( + PluginSidebarMoreMenuItem + ) + ) +); +``` + ## Installation Install the module @@ -14,6 +59,8 @@ _This package assumes that your code will run in an **ES2015+** environment. If ## Usage +An example using the HOC `withInstanceId` from the compose package: + ```js import { withInstanceId } from '@wordpress/compose'; @@ -24,6 +71,6 @@ function WrappedComponent( props ) { const ComponentWithInstanceIdProp = withInstanceId( WrappedComponent ); ``` -Refer to each Higher Order Component's README file for more details. +Refer to each Higher Order Component's README file for more details, see [list of components](https://github.com/WordPress/gutenberg/tree/master/packages/compose/src). <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> From c28712224ab07afdf82da544d0c2df11c32eb6c2 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Fri, 25 Jan 2019 06:58:43 -0800 Subject: [PATCH 234/691] Update compose documentation (#13504) * Update compose documentation A few minor rewording and updates from @chrisvanpatten in #13496 * Update packages/compose/README.md Co-Authored-By: mkaz <marcus@mkaz.com> --- packages/compose/README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/compose/README.md b/packages/compose/README.md index 93d5cc6b1d742..f0f8e25c14f38 100644 --- a/packages/compose/README.md +++ b/packages/compose/README.md @@ -2,14 +2,16 @@ The `compose` package is a collection of handy [Higher Order Components](https://facebook.github.io/react/docs/higher-order-components.html) (HOCs) you can use to wrap your WordPress components and provide some basic features like: state, instance id, pure... -The **compose** function is an alias to [flowRight](https://lodash.com/docs/#flowRight) from Lodash. It comes from functional programming world and allows to compose any number of functions. An example that illustrates it for two functions: +The `compose` function is an alias to [flowRight](https://lodash.com/docs/#flowRight) from Lodash. It comes from functional programming, and allows you to compose any number of functions. You might also think of this as layering functions; `compose` will execute the last function first, then sequentially move back through the previous functions passing the result of each function upward. + +An example that illustrates it for two functions: ```js const compose = ( f, g ) => x => f( g( x ) ); ``` -Here's a simplified example of **compose** in use from our code, see [plugin-sidebar](https://github.com/WordPress/gutenberg/blob/master/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js) for full code: +Here's a simplified example of **compose** in use from Gutenberg's [`PluginSidebar` component](https://github.com/WordPress/gutenberg/blob/master/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js): Using compose: @@ -28,7 +30,7 @@ export default compose( )( PluginSidebarMoreMenuItem ); ``` -Equivalent to the following without compose: +Without `compose`, the code would look like this: ```js const applyWithSelect = withSelect( ( select, ownProps ) => { @@ -45,7 +47,7 @@ export default withPluginContext( ) ) ); -``` + ## Installation @@ -71,6 +73,6 @@ function WrappedComponent( props ) { const ComponentWithInstanceIdProp = withInstanceId( WrappedComponent ); ``` -Refer to each Higher Order Component's README file for more details, see [list of components](https://github.com/WordPress/gutenberg/tree/master/packages/compose/src). +For more details, you can refer to each Higher Order Component's README file. [Available components are located here.](https://github.com/WordPress/gutenberg/tree/master/packages/compose/src) <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> From 5e855199ce1854d86c81a92184641b475a4ab783 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 25 Jan 2019 11:30:29 -0500 Subject: [PATCH 235/691] Plugin: Deprecate gutenberg_preload_api_request (#13453) * Plugin: Deprecate gutenberg_preload_api_request * Plugin: Remove test case covering gutenberg_preload_api_request --- .../backward-compatibility/deprecations.md | 1 + lib/client-assets.php | 62 ++----------------- phpunit/class-admin-test.php | 9 --- 3 files changed, 6 insertions(+), 66 deletions(-) diff --git a/docs/designers-developers/developers/backward-compatibility/deprecations.md b/docs/designers-developers/developers/backward-compatibility/deprecations.md index fe1ca0b4a287e..4bc5c3a89151b 100644 --- a/docs/designers-developers/developers/backward-compatibility/deprecations.md +++ b/docs/designers-developers/developers/backward-compatibility/deprecations.md @@ -21,6 +21,7 @@ The Gutenberg project's deprecation policy is intended to support backward compa - The PHP function `gutenberg_register_post_prepare_functions` has been removed. - The PHP function `gutenberg_silence_rest_errors` has been removed. - The PHP function `gutenberg_filter_post_type_labels` has been removed. +- The PHP function `gutenberg_preload_api_request` has been removed. Use [`rest_preload_api_request`](https://developer.wordpress.org/reference/functions/rest_preload_api_request/) instead. - The PHP function `gutenberg_remove_wpcom_markdown_support` has been removed. - The PHP function `gutenberg_bulk_post_updated_messages` has been removed. - The PHP function `gutenberg_kses_allowedtags` has been removed. diff --git a/lib/client-assets.php b/lib/client-assets.php index 630a50434c72f..093b5a46a35a3 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -510,68 +510,16 @@ function gutenberg_register_scripts_and_styles() { * data to be attached to the page. Expected to be called in the context of * `array_reduce`. * + * @deprecated 5.0.0 rest_preload_api_request + * * @param array $memo Reduce accumulator. * @param string $path REST API path to preload. * @return array Modified reduce accumulator. */ function gutenberg_preload_api_request( $memo, $path ) { + _deprecated_function( __FUNCTION__, '5.0.0', 'rest_preload_api_request' ); - // array_reduce() doesn't support passing an array in PHP 5.2 - // so we need to make sure we start with one. - if ( ! is_array( $memo ) ) { - $memo = array(); - } - - if ( empty( $path ) ) { - return $memo; - } - - $method = 'GET'; - if ( is_array( $path ) && 2 === count( $path ) ) { - $method = end( $path ); - $path = reset( $path ); - - if ( ! in_array( $method, array( 'GET', 'OPTIONS' ), true ) ) { - $method = 'GET'; - } - } - - $path_parts = parse_url( $path ); - if ( false === $path_parts ) { - return $memo; - } - - $request = new WP_REST_Request( $method, $path_parts['path'] ); - if ( ! empty( $path_parts['query'] ) ) { - parse_str( $path_parts['query'], $query_params ); - $request->set_query_params( $query_params ); - } - - $response = rest_do_request( $request ); - if ( 200 === $response->status ) { - $server = rest_get_server(); - $data = (array) $response->get_data(); - $links = $server->get_compact_response_links( $response ); - if ( ! empty( $links ) ) { - $data['_links'] = $links; - } - - if ( 'OPTIONS' === $method ) { - $response = rest_send_allow_header( $response, $server, $request ); - - $memo[ $method ][ $path ] = array( - 'body' => $data, - 'headers' => $response->headers, - ); - } else { - $memo[ $path ] = array( - 'body' => $data, - 'headers' => $response->headers, - ); - } - } - - return $memo; + return rest_preload_api_request( $memo, $path ); } /** @@ -1108,7 +1056,7 @@ function gutenberg_editor_scripts_and_styles( $hook ) { $preload_data = array_reduce( $preload_paths, - 'gutenberg_preload_api_request', + 'rest_preload_api_request', array() ); diff --git a/phpunit/class-admin-test.php b/phpunit/class-admin-test.php index 59991a196366c..b13995af52300 100644 --- a/phpunit/class-admin-test.php +++ b/phpunit/class-admin-test.php @@ -192,13 +192,4 @@ function test_gutenberg_revisions_restore() { $link = apply_filters( 'wp_prepare_revision_for_js', array( 'restoreUrl' => 'http://test.com' ) ); $this->assertEquals( array( 'restoreUrl' => 'http://test.com' ), $link ); } - - /** - * Ensure gutenberg_preload_api_request() works without notices in PHP 5.2. - * - * The array_reduce() function only accepts mixed variables starting with PHP 5.3. - */ - function test_preload_api_request_no_notices_php_52() { - $this->assertTrue( is_array( gutenberg_preload_api_request( 0, '/' ) ) ); - } } From ef1c0e852c92e3f852412114131032ed86c56190 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 25 Jan 2019 11:31:26 -0500 Subject: [PATCH 236/691] Spec Parser: Move generated spec parser to package (#13493) * Spec Parser: Move generated spec parser to package * Spec Parser: Add empty npmignore * Spec Parser: Add CHANGELOG entry --- bin/run-wp-unit-tests.sh | 13 - lib/parser.php | 1681 ---------------- .../.gitignore | 3 + .../.npmignore | 2 + .../CHANGELOG.md | 4 + .../bin}/create-php-parser.js | 4 +- .../package.json | 4 +- .../block-serialization-spec-parser/parser.js | 1791 ----------------- .../test/test-parser.php | 2 +- phpcs.xml.dist | 2 +- 10 files changed, 16 insertions(+), 3490 deletions(-) delete mode 100644 lib/parser.php create mode 100644 packages/block-serialization-spec-parser/.gitignore create mode 100644 packages/block-serialization-spec-parser/.npmignore rename {bin => packages/block-serialization-spec-parser/bin}/create-php-parser.js (71%) delete mode 100644 packages/block-serialization-spec-parser/parser.js diff --git a/bin/run-wp-unit-tests.sh b/bin/run-wp-unit-tests.sh index 8c426abd233a1..8e1ee81db79e3 100755 --- a/bin/run-wp-unit-tests.sh +++ b/bin/run-wp-unit-tests.sh @@ -21,16 +21,6 @@ fi npm run build || exit 1 -# Make sure phpegjs parser is up to date -node bin/create-php-parser.js || exit 1 -if ! git diff --quiet --exit-code lib/parser.php; then - echo 'ERROR: The PEG parser has been updated, but the generated PHP version' - echo ' (lib/parser.php) has not. Run `bin/create-php-parser.js` and' - echo ' commit the resulting changes to resolve this.' - sleep .2 # Otherwise Travis doesn't want to print the whole message - exit 1 -fi - echo Running with the following versions: if [[ $DOCKER = "true" ]]; then docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm wordpress_phpunit php -v @@ -40,9 +30,6 @@ else phpunit --version fi -# Check parser syntax -php lib/parser.php || exit 1 - # Run PHPUnit tests if [[ $DOCKER = "true" ]]; then npm run test-php || exit 1 diff --git a/lib/parser.php b/lib/parser.php deleted file mode 100644 index 8fbcaa19bb458..0000000000000 --- a/lib/parser.php +++ /dev/null @@ -1,1681 +0,0 @@ -<?php -/* - * Generated by PEG.js 0.10.x with phpegjs plugin - * - * http://pegjs.majda.cz/ - */ - - -/* Useful functions: */ - -/* Gutenberg_PEG_chr_unicode - get unicode character from its char code */ -if (!function_exists("Gutenberg_PEG_chr_unicode")) { - function Gutenberg_PEG_chr_unicode($code) { - return html_entity_decode("&#$code;", ENT_QUOTES, "UTF-8"); - } -} -/* Gutenberg_PEG_ord_unicode - get unicode char code from string */ -if (!function_exists("Gutenberg_PEG_ord_unicode")) { - function Gutenberg_PEG_ord_unicode($character) { - if (strlen($character) === 1) { - return ord($character); - } - $json = json_encode($character); - $utf16_1 = hexdec(substr($json, 3, 4)); - if (substr($json, 7, 2) === "\u") { - $utf16_2 = hexdec(substr($json, 9, 4)); - return 0x10000 + (($utf16_1 & 0x3ff) << 10) + ($utf16_2 & 0x3ff); - } else { - return $utf16_1; - } - } -} -/* Gutenberg_PEG_peg_char_class_test - simple character class test */ -if (!function_exists("Gutenberg_PEG_peg_char_class_test")) { - function Gutenberg_PEG_peg_char_class_test($class, $character) { - $code = Gutenberg_PEG_ord_unicode($character); - foreach ($class as $range) { - if ($code >= $range[0] && $code <= $range[1]) { - return true; - } - } - return false; - } -} - -/* Syntax error exception */ -if (!class_exists("Gutenberg_PEG_SyntaxError", false)) { - class Gutenberg_PEG_SyntaxError extends Exception { - public $expected; - public $found; - public $grammarOffset; - public $grammarLine; - public $grammarColumn; - public $name; - public function __construct($message, $expected, $found, $offset, $line, $column) { - parent::__construct($message, 0); - $this->expected = $expected; - $this->found = $found; - $this->grammarOffset = $offset; - $this->grammarLine = $line; - $this->grammarColumn = $column; - $this->name = "Gutenberg_PEG_SyntaxError"; - } - } -} - -class Gutenberg_PEG_Parser { - private $peg_currPos = 0; - private $peg_reportedPos = 0; - private $peg_cachedPos = 0; - private $peg_cachedPosDetails = array('line' => 1, 'column' => 1, 'seenCR' => false ); - private $peg_maxFailPos = 0; - private $peg_maxFailExpected = array(); - private $peg_silentFails = 0; - private $input = array(); - private $input_length = 0; - - private function cleanup_state() { - $this->peg_currPos = 0; - $this->peg_reportedPos = 0; - $this->peg_cachedPos = 0; - $this->peg_cachedPosDetails = array('line' => 1, 'column' => 1, 'seenCR' => false ); - $this->peg_maxFailPos = 0; - $this->peg_maxFailExpected = array(); - $this->peg_silentFails = 0; - $this->input = array(); - $this->input_length = 0; - - } - - private function input_substr($start, $length) { - if ($length === 1 && $start < $this->input_length) { - return $this->input[$start]; - } - $substr = ''; - $max = min($start + $length, $this->input_length); - for ($i = $start; $i < $max; $i++) { - $substr .= $this->input[$i]; - } - return $substr; - } - - - private function text() { - return substr($this->input, $this->peg_reportedPos, $this->peg_reportedPos + $this->peg_currPos); - } - - private function offset() { - return $this->peg_reportedPos; - } - - private function line() { - $compute_pd = $this->peg_computePosDetails($this->peg_reportedPos); - return $compute_pd["line"]; - } - - private function column() { - $compute_pd = $this->peg_computePosDetails($this->peg_reportedPos); - return $compute_pd["column"]; - } - - private function expected($description) { - throw $this->peg_buildException( - null, - array(array("type" => "other", "description" => $description )), - $this->peg_reportedPos - ); - } - - private function error($message) { - throw $this->peg_buildException($message, null, $this->peg_reportedPos); - } - - private function peg_advancePos(&$details, $startPos, $endPos) { - for ($p = $startPos; $p < $endPos; $p++) { - $ch = $this->input_substr($p, 1); - if ($ch === "\n") { - if (!$details["seenCR"]) { $details["line"]++; } - $details["column"] = 1; - $details["seenCR"] = false; - } else if ($ch === "\r" || $ch === "\u2028" || $ch === "\u2029") { - $details["line"]++; - $details["column"] = 1; - $details["seenCR"] = true; - } else { - $details["column"]++; - $details["seenCR"] = false; - } - } - } - - private function peg_computePosDetails($pos) { - if ($this->peg_cachedPos !== $pos) { - if ($this->peg_cachedPos > $pos) { - $this->peg_cachedPos = 0; - $this->peg_cachedPosDetails = array( "line" => 1, "column" => 1, "seenCR" => false ); - } - $this->peg_advancePos($this->peg_cachedPosDetails, $this->peg_cachedPos, $pos); - $this->peg_cachedPos = $pos; - } - - return $this->peg_cachedPosDetails; - } - - private function peg_fail($expected) { - if ($this->peg_currPos < $this->peg_maxFailPos) { return; } - - if ($this->peg_currPos > $this->peg_maxFailPos) { - $this->peg_maxFailPos = $this->peg_currPos; - $this->peg_maxFailExpected = array(); - } - - $this->peg_maxFailExpected[] = $expected; - } - - private function peg_buildException_expectedComparator($a, $b) { - if ($a["description"] < $b["description"]) { - return -1; - } else if ($a["description"] > $b["description"]) { - return 1; - } else { - return 0; - } - } - - private function peg_buildException($message, $expected, $pos) { - $posDetails = $this->peg_computePosDetails($pos); - $found = $pos < $this->input_length ? $this->input[$pos] : null; - - if ($expected !== null) { - usort($expected, array($this, "peg_buildException_expectedComparator")); - $i = 1; - while ($i < count($expected)) { - if ($expected[$i - 1] === $expected[$i]) { - array_splice($expected, $i, 1); - } else { - $i++; - } - } - } - - if ($message === null) { - $expectedDescs = array_fill(0, count($expected), null); - - for ($i = 0; $i < count($expected); $i++) { - $expectedDescs[$i] = $expected[$i]["description"]; - } - - $expectedDesc = count($expected) > 1 - ? join(", ", array_slice($expectedDescs, 0, -1)) - . " or " - . $expectedDescs[count($expected) - 1] - : $expectedDescs[0]; - - $foundDesc = $found ? json_encode($found) : "end of input"; - - $message = "Expected " . $expectedDesc . " but " . $foundDesc . " found."; - } - - return new Gutenberg_PEG_SyntaxError( - $message, - $expected, - $found, - $pos, - $posDetails["line"], - $posDetails["column"] - ); - } - - private $peg_FAILED; - private $peg_c0; - private $peg_c1; - private $peg_c2; - private $peg_c3; - private $peg_c4; - private $peg_c5; - private $peg_c6; - private $peg_c7; - private $peg_c8; - private $peg_c9; - private $peg_c10; - private $peg_c11; - private $peg_c12; - private $peg_c13; - private $peg_c14; - private $peg_c15; - private $peg_c16; - private $peg_c17; - private $peg_c18; - private $peg_c19; - private $peg_c20; - private $peg_c21; - private $peg_c22; - private $peg_c23; - private $peg_c24; - - private function peg_f0($pre, $b, $html) { return array( $b, $html ); } - private function peg_f1($pre, $bs, $post) { return peg_join_blocks( $pre, $bs, $post ); } - private function peg_f2($blockName, $a) { return $a; } - private function peg_f3($blockName, $attrs) { - return array( - 'blockName' => $blockName, - 'attrs' => empty( $attrs ) ? peg_empty_attrs() : $attrs, - 'innerBlocks' => array(), - 'innerHTML' => '', - 'innerContent' => array(), - ); - } - private function peg_f4($s, $children, $e) { - list( $innerHTML, $innerBlocks, $innerContent ) = peg_process_inner_content( $children ); - - return array( - 'blockName' => $s['blockName'], - 'attrs' => empty( $s['attrs'] ) ? peg_empty_attrs() : $s['attrs'], - 'innerBlocks' => $innerBlocks, - 'innerHTML' => $innerHTML, - 'innerContent' => $innerContent, - ); - } - private function peg_f5($blockName, $attrs) { - return array( - 'blockName' => $blockName, - 'attrs' => isset( $attrs ) ? $attrs : array(), - ); - } - private function peg_f6($blockName) { - return array( - 'blockName' => $blockName, - ); - } - private function peg_f7($type) { return "core/$type"; } - private function peg_f8($attrs) { return json_decode( $attrs, true ); } - - private function peg_parseBlock_List() { - - $s0 = $this->peg_currPos; - $s1 = $this->peg_currPos; - $s2 = array(); - $s3 = $this->peg_currPos; - $s4 = $this->peg_currPos; - $this->peg_silentFails++; - $s5 = $this->peg_parseBlock(); - $this->peg_silentFails--; - if ($s5 === $this->peg_FAILED) { - $s4 = null; - } else { - $this->peg_currPos = $s4; - $s4 = $this->peg_FAILED; - } - if ($s4 !== $this->peg_FAILED) { - if ($this->input_length > $this->peg_currPos) { - $s5 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s5 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c0); - } - } - if ($s5 !== $this->peg_FAILED) { - $s4 = array($s4, $s5); - $s3 = $s4; - } else { - $this->peg_currPos = $s3; - $s3 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s3; - $s3 = $this->peg_FAILED; - } - while ($s3 !== $this->peg_FAILED) { - $s2[] = $s3; - $s3 = $this->peg_currPos; - $s4 = $this->peg_currPos; - $this->peg_silentFails++; - $s5 = $this->peg_parseBlock(); - $this->peg_silentFails--; - if ($s5 === $this->peg_FAILED) { - $s4 = null; - } else { - $this->peg_currPos = $s4; - $s4 = $this->peg_FAILED; - } - if ($s4 !== $this->peg_FAILED) { - if ($this->input_length > $this->peg_currPos) { - $s5 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s5 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c0); - } - } - if ($s5 !== $this->peg_FAILED) { - $s4 = array($s4, $s5); - $s3 = $s4; - } else { - $this->peg_currPos = $s3; - $s3 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s3; - $s3 = $this->peg_FAILED; - } - } - if ($s2 !== $this->peg_FAILED) { - $s1 = $this->input_substr($s1, $this->peg_currPos - $s1); - } else { - $s1 = $s2; - } - if ($s1 !== $this->peg_FAILED) { - $s2 = array(); - $s3 = $this->peg_currPos; - $s4 = $this->peg_parseBlock(); - if ($s4 !== $this->peg_FAILED) { - $s5 = $this->peg_currPos; - $s6 = array(); - $s7 = $this->peg_currPos; - $s8 = $this->peg_currPos; - $this->peg_silentFails++; - $s9 = $this->peg_parseBlock(); - $this->peg_silentFails--; - if ($s9 === $this->peg_FAILED) { - $s8 = null; - } else { - $this->peg_currPos = $s8; - $s8 = $this->peg_FAILED; - } - if ($s8 !== $this->peg_FAILED) { - if ($this->input_length > $this->peg_currPos) { - $s9 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s9 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c0); - } - } - if ($s9 !== $this->peg_FAILED) { - $s8 = array($s8, $s9); - $s7 = $s8; - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - while ($s7 !== $this->peg_FAILED) { - $s6[] = $s7; - $s7 = $this->peg_currPos; - $s8 = $this->peg_currPos; - $this->peg_silentFails++; - $s9 = $this->peg_parseBlock(); - $this->peg_silentFails--; - if ($s9 === $this->peg_FAILED) { - $s8 = null; - } else { - $this->peg_currPos = $s8; - $s8 = $this->peg_FAILED; - } - if ($s8 !== $this->peg_FAILED) { - if ($this->input_length > $this->peg_currPos) { - $s9 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s9 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c0); - } - } - if ($s9 !== $this->peg_FAILED) { - $s8 = array($s8, $s9); - $s7 = $s8; - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - } - if ($s6 !== $this->peg_FAILED) { - $s5 = $this->input_substr($s5, $this->peg_currPos - $s5); - } else { - $s5 = $s6; - } - if ($s5 !== $this->peg_FAILED) { - $this->peg_reportedPos = $s3; - $s4 = $this->peg_f0($s1, $s4, $s5); - $s3 = $s4; - } else { - $this->peg_currPos = $s3; - $s3 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s3; - $s3 = $this->peg_FAILED; - } - while ($s3 !== $this->peg_FAILED) { - $s2[] = $s3; - $s3 = $this->peg_currPos; - $s4 = $this->peg_parseBlock(); - if ($s4 !== $this->peg_FAILED) { - $s5 = $this->peg_currPos; - $s6 = array(); - $s7 = $this->peg_currPos; - $s8 = $this->peg_currPos; - $this->peg_silentFails++; - $s9 = $this->peg_parseBlock(); - $this->peg_silentFails--; - if ($s9 === $this->peg_FAILED) { - $s8 = null; - } else { - $this->peg_currPos = $s8; - $s8 = $this->peg_FAILED; - } - if ($s8 !== $this->peg_FAILED) { - if ($this->input_length > $this->peg_currPos) { - $s9 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s9 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c0); - } - } - if ($s9 !== $this->peg_FAILED) { - $s8 = array($s8, $s9); - $s7 = $s8; - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - while ($s7 !== $this->peg_FAILED) { - $s6[] = $s7; - $s7 = $this->peg_currPos; - $s8 = $this->peg_currPos; - $this->peg_silentFails++; - $s9 = $this->peg_parseBlock(); - $this->peg_silentFails--; - if ($s9 === $this->peg_FAILED) { - $s8 = null; - } else { - $this->peg_currPos = $s8; - $s8 = $this->peg_FAILED; - } - if ($s8 !== $this->peg_FAILED) { - if ($this->input_length > $this->peg_currPos) { - $s9 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s9 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c0); - } - } - if ($s9 !== $this->peg_FAILED) { - $s8 = array($s8, $s9); - $s7 = $s8; - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - } - if ($s6 !== $this->peg_FAILED) { - $s5 = $this->input_substr($s5, $this->peg_currPos - $s5); - } else { - $s5 = $s6; - } - if ($s5 !== $this->peg_FAILED) { - $this->peg_reportedPos = $s3; - $s4 = $this->peg_f0($s1, $s4, $s5); - $s3 = $s4; - } else { - $this->peg_currPos = $s3; - $s3 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s3; - $s3 = $this->peg_FAILED; - } - } - if ($s2 !== $this->peg_FAILED) { - $s3 = $this->peg_currPos; - $s4 = array(); - if ($this->input_length > $this->peg_currPos) { - $s5 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s5 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c0); - } - } - while ($s5 !== $this->peg_FAILED) { - $s4[] = $s5; - if ($this->input_length > $this->peg_currPos) { - $s5 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s5 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c0); - } - } - } - if ($s4 !== $this->peg_FAILED) { - $s3 = $this->input_substr($s3, $this->peg_currPos - $s3); - } else { - $s3 = $s4; - } - if ($s3 !== $this->peg_FAILED) { - $this->peg_reportedPos = $s0; - $s1 = $this->peg_f1($s1, $s2, $s3); - $s0 = $s1; - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - - return $s0; - } - - private function peg_parseBlock() { - - $s0 = $this->peg_parseBlock_Void(); - if ($s0 === $this->peg_FAILED) { - $s0 = $this->peg_parseBlock_Balanced(); - } - - return $s0; - } - - private function peg_parseBlock_Void() { - - $s0 = $this->peg_currPos; - if ($this->input_substr($this->peg_currPos, 4) === $this->peg_c1) { - $s1 = $this->peg_c1; - $this->peg_currPos += 4; - } else { - $s1 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c2); - } - } - if ($s1 !== $this->peg_FAILED) { - $s2 = $this->peg_parse__(); - if ($s2 !== $this->peg_FAILED) { - if ($this->input_substr($this->peg_currPos, 3) === $this->peg_c3) { - $s3 = $this->peg_c3; - $this->peg_currPos += 3; - } else { - $s3 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c4); - } - } - if ($s3 !== $this->peg_FAILED) { - $s4 = $this->peg_parseBlock_Name(); - if ($s4 !== $this->peg_FAILED) { - $s5 = $this->peg_parse__(); - if ($s5 !== $this->peg_FAILED) { - $s6 = $this->peg_currPos; - $s7 = $this->peg_parseBlock_Attributes(); - if ($s7 !== $this->peg_FAILED) { - $s8 = $this->peg_parse__(); - if ($s8 !== $this->peg_FAILED) { - $this->peg_reportedPos = $s6; - $s7 = $this->peg_f2($s4, $s7); - $s6 = $s7; - } else { - $this->peg_currPos = $s6; - $s6 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s6; - $s6 = $this->peg_FAILED; - } - if ($s6 === $this->peg_FAILED) { - $s6 = null; - } - if ($s6 !== $this->peg_FAILED) { - if ($this->input_substr($this->peg_currPos, 4) === $this->peg_c5) { - $s7 = $this->peg_c5; - $this->peg_currPos += 4; - } else { - $s7 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c6); - } - } - if ($s7 !== $this->peg_FAILED) { - $this->peg_reportedPos = $s0; - $s1 = $this->peg_f3($s4, $s6); - $s0 = $s1; - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - - return $s0; - } - - private function peg_parseBlock_Balanced() { - - $s0 = $this->peg_currPos; - $s1 = $this->peg_parseBlock_Start(); - if ($s1 !== $this->peg_FAILED) { - $s2 = array(); - $s3 = $this->peg_parseBlock(); - if ($s3 === $this->peg_FAILED) { - $s3 = $this->peg_currPos; - $s4 = array(); - $s5 = $this->peg_currPos; - $s6 = $this->peg_currPos; - $this->peg_silentFails++; - $s7 = $this->peg_parseBlock(); - $this->peg_silentFails--; - if ($s7 === $this->peg_FAILED) { - $s6 = null; - } else { - $this->peg_currPos = $s6; - $s6 = $this->peg_FAILED; - } - if ($s6 !== $this->peg_FAILED) { - $s7 = $this->peg_currPos; - $this->peg_silentFails++; - $s8 = $this->peg_parseBlock_End(); - $this->peg_silentFails--; - if ($s8 === $this->peg_FAILED) { - $s7 = null; - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - if ($s7 !== $this->peg_FAILED) { - if ($this->input_length > $this->peg_currPos) { - $s8 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s8 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c0); - } - } - if ($s8 !== $this->peg_FAILED) { - $s6 = array($s6, $s7, $s8); - $s5 = $s6; - } else { - $this->peg_currPos = $s5; - $s5 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s5; - $s5 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s5; - $s5 = $this->peg_FAILED; - } - if ($s5 !== $this->peg_FAILED) { - while ($s5 !== $this->peg_FAILED) { - $s4[] = $s5; - $s5 = $this->peg_currPos; - $s6 = $this->peg_currPos; - $this->peg_silentFails++; - $s7 = $this->peg_parseBlock(); - $this->peg_silentFails--; - if ($s7 === $this->peg_FAILED) { - $s6 = null; - } else { - $this->peg_currPos = $s6; - $s6 = $this->peg_FAILED; - } - if ($s6 !== $this->peg_FAILED) { - $s7 = $this->peg_currPos; - $this->peg_silentFails++; - $s8 = $this->peg_parseBlock_End(); - $this->peg_silentFails--; - if ($s8 === $this->peg_FAILED) { - $s7 = null; - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - if ($s7 !== $this->peg_FAILED) { - if ($this->input_length > $this->peg_currPos) { - $s8 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s8 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c0); - } - } - if ($s8 !== $this->peg_FAILED) { - $s6 = array($s6, $s7, $s8); - $s5 = $s6; - } else { - $this->peg_currPos = $s5; - $s5 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s5; - $s5 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s5; - $s5 = $this->peg_FAILED; - } - } - } else { - $s4 = $this->peg_FAILED; - } - if ($s4 !== $this->peg_FAILED) { - $s3 = $this->input_substr($s3, $this->peg_currPos - $s3); - } else { - $s3 = $s4; - } - } - while ($s3 !== $this->peg_FAILED) { - $s2[] = $s3; - $s3 = $this->peg_parseBlock(); - if ($s3 === $this->peg_FAILED) { - $s3 = $this->peg_currPos; - $s4 = array(); - $s5 = $this->peg_currPos; - $s6 = $this->peg_currPos; - $this->peg_silentFails++; - $s7 = $this->peg_parseBlock(); - $this->peg_silentFails--; - if ($s7 === $this->peg_FAILED) { - $s6 = null; - } else { - $this->peg_currPos = $s6; - $s6 = $this->peg_FAILED; - } - if ($s6 !== $this->peg_FAILED) { - $s7 = $this->peg_currPos; - $this->peg_silentFails++; - $s8 = $this->peg_parseBlock_End(); - $this->peg_silentFails--; - if ($s8 === $this->peg_FAILED) { - $s7 = null; - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - if ($s7 !== $this->peg_FAILED) { - if ($this->input_length > $this->peg_currPos) { - $s8 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s8 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c0); - } - } - if ($s8 !== $this->peg_FAILED) { - $s6 = array($s6, $s7, $s8); - $s5 = $s6; - } else { - $this->peg_currPos = $s5; - $s5 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s5; - $s5 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s5; - $s5 = $this->peg_FAILED; - } - if ($s5 !== $this->peg_FAILED) { - while ($s5 !== $this->peg_FAILED) { - $s4[] = $s5; - $s5 = $this->peg_currPos; - $s6 = $this->peg_currPos; - $this->peg_silentFails++; - $s7 = $this->peg_parseBlock(); - $this->peg_silentFails--; - if ($s7 === $this->peg_FAILED) { - $s6 = null; - } else { - $this->peg_currPos = $s6; - $s6 = $this->peg_FAILED; - } - if ($s6 !== $this->peg_FAILED) { - $s7 = $this->peg_currPos; - $this->peg_silentFails++; - $s8 = $this->peg_parseBlock_End(); - $this->peg_silentFails--; - if ($s8 === $this->peg_FAILED) { - $s7 = null; - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - if ($s7 !== $this->peg_FAILED) { - if ($this->input_length > $this->peg_currPos) { - $s8 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s8 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c0); - } - } - if ($s8 !== $this->peg_FAILED) { - $s6 = array($s6, $s7, $s8); - $s5 = $s6; - } else { - $this->peg_currPos = $s5; - $s5 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s5; - $s5 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s5; - $s5 = $this->peg_FAILED; - } - } - } else { - $s4 = $this->peg_FAILED; - } - if ($s4 !== $this->peg_FAILED) { - $s3 = $this->input_substr($s3, $this->peg_currPos - $s3); - } else { - $s3 = $s4; - } - } - } - if ($s2 !== $this->peg_FAILED) { - $s3 = $this->peg_parseBlock_End(); - if ($s3 !== $this->peg_FAILED) { - $this->peg_reportedPos = $s0; - $s1 = $this->peg_f4($s1, $s2, $s3); - $s0 = $s1; - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - - return $s0; - } - - private function peg_parseBlock_Start() { - - $s0 = $this->peg_currPos; - if ($this->input_substr($this->peg_currPos, 4) === $this->peg_c1) { - $s1 = $this->peg_c1; - $this->peg_currPos += 4; - } else { - $s1 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c2); - } - } - if ($s1 !== $this->peg_FAILED) { - $s2 = $this->peg_parse__(); - if ($s2 !== $this->peg_FAILED) { - if ($this->input_substr($this->peg_currPos, 3) === $this->peg_c3) { - $s3 = $this->peg_c3; - $this->peg_currPos += 3; - } else { - $s3 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c4); - } - } - if ($s3 !== $this->peg_FAILED) { - $s4 = $this->peg_parseBlock_Name(); - if ($s4 !== $this->peg_FAILED) { - $s5 = $this->peg_parse__(); - if ($s5 !== $this->peg_FAILED) { - $s6 = $this->peg_currPos; - $s7 = $this->peg_parseBlock_Attributes(); - if ($s7 !== $this->peg_FAILED) { - $s8 = $this->peg_parse__(); - if ($s8 !== $this->peg_FAILED) { - $this->peg_reportedPos = $s6; - $s7 = $this->peg_f2($s4, $s7); - $s6 = $s7; - } else { - $this->peg_currPos = $s6; - $s6 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s6; - $s6 = $this->peg_FAILED; - } - if ($s6 === $this->peg_FAILED) { - $s6 = null; - } - if ($s6 !== $this->peg_FAILED) { - if ($this->input_substr($this->peg_currPos, 3) === $this->peg_c7) { - $s7 = $this->peg_c7; - $this->peg_currPos += 3; - } else { - $s7 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c8); - } - } - if ($s7 !== $this->peg_FAILED) { - $this->peg_reportedPos = $s0; - $s1 = $this->peg_f5($s4, $s6); - $s0 = $s1; - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - - return $s0; - } - - private function peg_parseBlock_End() { - - $s0 = $this->peg_currPos; - if ($this->input_substr($this->peg_currPos, 4) === $this->peg_c1) { - $s1 = $this->peg_c1; - $this->peg_currPos += 4; - } else { - $s1 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c2); - } - } - if ($s1 !== $this->peg_FAILED) { - $s2 = $this->peg_parse__(); - if ($s2 !== $this->peg_FAILED) { - if ($this->input_substr($this->peg_currPos, 4) === $this->peg_c9) { - $s3 = $this->peg_c9; - $this->peg_currPos += 4; - } else { - $s3 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c10); - } - } - if ($s3 !== $this->peg_FAILED) { - $s4 = $this->peg_parseBlock_Name(); - if ($s4 !== $this->peg_FAILED) { - $s5 = $this->peg_parse__(); - if ($s5 !== $this->peg_FAILED) { - if ($this->input_substr($this->peg_currPos, 3) === $this->peg_c7) { - $s6 = $this->peg_c7; - $this->peg_currPos += 3; - } else { - $s6 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c8); - } - } - if ($s6 !== $this->peg_FAILED) { - $this->peg_reportedPos = $s0; - $s1 = $this->peg_f6($s4); - $s0 = $s1; - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s0; - $s0 = $this->peg_FAILED; - } - - return $s0; - } - - private function peg_parseBlock_Name() { - - $s0 = $this->peg_parseNamespaced_Block_Name(); - if ($s0 === $this->peg_FAILED) { - $s0 = $this->peg_parseCore_Block_Name(); - } - - return $s0; - } - - private function peg_parseNamespaced_Block_Name() { - - $s0 = $this->peg_currPos; - $s1 = $this->peg_currPos; - $s2 = $this->peg_parseBlock_Name_Part(); - if ($s2 !== $this->peg_FAILED) { - if ($this->input_substr($this->peg_currPos, 1) === $this->peg_c11) { - $s3 = $this->peg_c11; - $this->peg_currPos++; - } else { - $s3 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c12); - } - } - if ($s3 !== $this->peg_FAILED) { - $s4 = $this->peg_parseBlock_Name_Part(); - if ($s4 !== $this->peg_FAILED) { - $s2 = array($s2, $s3, $s4); - $s1 = $s2; - } else { - $this->peg_currPos = $s1; - $s1 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s1; - $s1 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s1; - $s1 = $this->peg_FAILED; - } - if ($s1 !== $this->peg_FAILED) { - $s0 = $this->input_substr($s0, $this->peg_currPos - $s0); - } else { - $s0 = $s1; - } - - return $s0; - } - - private function peg_parseCore_Block_Name() { - - $s0 = $this->peg_currPos; - $s1 = $this->peg_currPos; - $s2 = $this->peg_parseBlock_Name_Part(); - if ($s2 !== $this->peg_FAILED) { - $s1 = $this->input_substr($s1, $this->peg_currPos - $s1); - } else { - $s1 = $s2; - } - if ($s1 !== $this->peg_FAILED) { - $this->peg_reportedPos = $s0; - $s1 = $this->peg_f7($s1); - } - $s0 = $s1; - - return $s0; - } - - private function peg_parseBlock_Name_Part() { - - $s0 = $this->peg_currPos; - $s1 = $this->peg_currPos; - if (Gutenberg_PEG_peg_char_class_test($this->peg_c13, $this->input_substr($this->peg_currPos, 1))) { - $s2 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s2 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c14); - } - } - if ($s2 !== $this->peg_FAILED) { - $s3 = array(); - if (Gutenberg_PEG_peg_char_class_test($this->peg_c15, $this->input_substr($this->peg_currPos, 1))) { - $s4 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s4 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c16); - } - } - while ($s4 !== $this->peg_FAILED) { - $s3[] = $s4; - if (Gutenberg_PEG_peg_char_class_test($this->peg_c15, $this->input_substr($this->peg_currPos, 1))) { - $s4 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s4 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c16); - } - } - } - if ($s3 !== $this->peg_FAILED) { - $s2 = array($s2, $s3); - $s1 = $s2; - } else { - $this->peg_currPos = $s1; - $s1 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s1; - $s1 = $this->peg_FAILED; - } - if ($s1 !== $this->peg_FAILED) { - $s0 = $this->input_substr($s0, $this->peg_currPos - $s0); - } else { - $s0 = $s1; - } - - return $s0; - } - - private function peg_parseBlock_Attributes() { - - $this->peg_silentFails++; - $s0 = $this->peg_currPos; - $s1 = $this->peg_currPos; - $s2 = $this->peg_currPos; - if ($this->input_substr($this->peg_currPos, 1) === $this->peg_c18) { - $s3 = $this->peg_c18; - $this->peg_currPos++; - } else { - $s3 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c19); - } - } - if ($s3 !== $this->peg_FAILED) { - $s4 = array(); - $s5 = $this->peg_currPos; - $s6 = $this->peg_currPos; - $this->peg_silentFails++; - $s7 = $this->peg_currPos; - if ($this->input_substr($this->peg_currPos, 1) === $this->peg_c20) { - $s8 = $this->peg_c20; - $this->peg_currPos++; - } else { - $s8 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c21); - } - } - if ($s8 !== $this->peg_FAILED) { - $s9 = $this->peg_parse__(); - if ($s9 !== $this->peg_FAILED) { - $s10 = $this->peg_c22; - if ($s10 !== $this->peg_FAILED) { - if ($this->input_substr($this->peg_currPos, 1) === $this->peg_c11) { - $s11 = $this->peg_c11; - $this->peg_currPos++; - } else { - $s11 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c12); - } - } - if ($s11 === $this->peg_FAILED) { - $s11 = null; - } - if ($s11 !== $this->peg_FAILED) { - if ($this->input_substr($this->peg_currPos, 3) === $this->peg_c7) { - $s12 = $this->peg_c7; - $this->peg_currPos += 3; - } else { - $s12 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c8); - } - } - if ($s12 !== $this->peg_FAILED) { - $s8 = array($s8, $s9, $s10, $s11, $s12); - $s7 = $s8; - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - $this->peg_silentFails--; - if ($s7 === $this->peg_FAILED) { - $s6 = null; - } else { - $this->peg_currPos = $s6; - $s6 = $this->peg_FAILED; - } - if ($s6 !== $this->peg_FAILED) { - if ($this->input_length > $this->peg_currPos) { - $s7 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s7 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c0); - } - } - if ($s7 !== $this->peg_FAILED) { - $s6 = array($s6, $s7); - $s5 = $s6; - } else { - $this->peg_currPos = $s5; - $s5 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s5; - $s5 = $this->peg_FAILED; - } - while ($s5 !== $this->peg_FAILED) { - $s4[] = $s5; - $s5 = $this->peg_currPos; - $s6 = $this->peg_currPos; - $this->peg_silentFails++; - $s7 = $this->peg_currPos; - if ($this->input_substr($this->peg_currPos, 1) === $this->peg_c20) { - $s8 = $this->peg_c20; - $this->peg_currPos++; - } else { - $s8 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c21); - } - } - if ($s8 !== $this->peg_FAILED) { - $s9 = $this->peg_parse__(); - if ($s9 !== $this->peg_FAILED) { - $s10 = $this->peg_c22; - if ($s10 !== $this->peg_FAILED) { - if ($this->input_substr($this->peg_currPos, 1) === $this->peg_c11) { - $s11 = $this->peg_c11; - $this->peg_currPos++; - } else { - $s11 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c12); - } - } - if ($s11 === $this->peg_FAILED) { - $s11 = null; - } - if ($s11 !== $this->peg_FAILED) { - if ($this->input_substr($this->peg_currPos, 3) === $this->peg_c7) { - $s12 = $this->peg_c7; - $this->peg_currPos += 3; - } else { - $s12 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c8); - } - } - if ($s12 !== $this->peg_FAILED) { - $s8 = array($s8, $s9, $s10, $s11, $s12); - $s7 = $s8; - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s7; - $s7 = $this->peg_FAILED; - } - $this->peg_silentFails--; - if ($s7 === $this->peg_FAILED) { - $s6 = null; - } else { - $this->peg_currPos = $s6; - $s6 = $this->peg_FAILED; - } - if ($s6 !== $this->peg_FAILED) { - if ($this->input_length > $this->peg_currPos) { - $s7 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s7 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c0); - } - } - if ($s7 !== $this->peg_FAILED) { - $s6 = array($s6, $s7); - $s5 = $s6; - } else { - $this->peg_currPos = $s5; - $s5 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s5; - $s5 = $this->peg_FAILED; - } - } - if ($s4 !== $this->peg_FAILED) { - if ($this->input_substr($this->peg_currPos, 1) === $this->peg_c20) { - $s5 = $this->peg_c20; - $this->peg_currPos++; - } else { - $s5 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c21); - } - } - if ($s5 !== $this->peg_FAILED) { - $s3 = array($s3, $s4, $s5); - $s2 = $s3; - } else { - $this->peg_currPos = $s2; - $s2 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s2; - $s2 = $this->peg_FAILED; - } - } else { - $this->peg_currPos = $s2; - $s2 = $this->peg_FAILED; - } - if ($s2 !== $this->peg_FAILED) { - $s1 = $this->input_substr($s1, $this->peg_currPos - $s1); - } else { - $s1 = $s2; - } - if ($s1 !== $this->peg_FAILED) { - $this->peg_reportedPos = $s0; - $s1 = $this->peg_f8($s1); - } - $s0 = $s1; - $this->peg_silentFails--; - if ($s0 === $this->peg_FAILED) { - $s1 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c17); - } - } - - return $s0; - } - - private function peg_parse__() { - - $s0 = array(); - if (Gutenberg_PEG_peg_char_class_test($this->peg_c23, $this->input_substr($this->peg_currPos, 1))) { - $s1 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s1 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c24); - } - } - if ($s1 !== $this->peg_FAILED) { - while ($s1 !== $this->peg_FAILED) { - $s0[] = $s1; - if (Gutenberg_PEG_peg_char_class_test($this->peg_c23, $this->input_substr($this->peg_currPos, 1))) { - $s1 = $this->input_substr($this->peg_currPos, 1); - $this->peg_currPos++; - } else { - $s1 = $this->peg_FAILED; - if ($this->peg_silentFails === 0) { - $this->peg_fail($this->peg_c24); - } - } - } - } else { - $s0 = $this->peg_FAILED; - } - - return $s0; - } - - public function parse($input) { - $arguments = func_get_args(); - $options = count($arguments) > 1 ? $arguments[1] : array(); - $this->cleanup_state(); - - if (is_array($input)) { - $this->input = $input; - } else { - preg_match_all("/./us", $input, $match); - $this->input = $match[0]; - } - $this->input_length = count($this->input); - - $this->peg_FAILED = new stdClass; - $this->peg_c0 = array("type" => "any", "description" => "any character" ); - $this->peg_c1 = "<!--"; - $this->peg_c2 = array( "type" => "literal", "value" => "<!--", "description" => "\"<!--\"" ); - $this->peg_c3 = "wp:"; - $this->peg_c4 = array( "type" => "literal", "value" => "wp:", "description" => "\"wp:\"" ); - $this->peg_c5 = "/-->"; - $this->peg_c6 = array( "type" => "literal", "value" => "/-->", "description" => "\"/-->\"" ); - $this->peg_c7 = "-->"; - $this->peg_c8 = array( "type" => "literal", "value" => "-->", "description" => "\"-->\"" ); - $this->peg_c9 = "/wp:"; - $this->peg_c10 = array( "type" => "literal", "value" => "/wp:", "description" => "\"/wp:\"" ); - $this->peg_c11 = "/"; - $this->peg_c12 = array( "type" => "literal", "value" => "/", "description" => "\"/\"" ); - $this->peg_c13 = array(array(97,122)); - $this->peg_c14 = array( "type" => "class", "value" => "[a-z]", "description" => "[a-z]" ); - $this->peg_c15 = array(array(97,122), array(48,57), array(95,95), array(45,45)); - $this->peg_c16 = array( "type" => "class", "value" => "[a-z0-9_-]", "description" => "[a-z0-9_-]" ); - $this->peg_c17 = array("type" => "other", "description" => "JSON-encoded attributes embedded in a block's opening comment" ); - $this->peg_c18 = "{"; - $this->peg_c19 = array( "type" => "literal", "value" => "{", "description" => "\"{\"" ); - $this->peg_c20 = "}"; - $this->peg_c21 = array( "type" => "literal", "value" => "}", "description" => "\"}\"" ); - $this->peg_c22 = ""; - $this->peg_c23 = array(array(32,32), array(9,9), array(13,13), array(10,10)); - $this->peg_c24 = array( "type" => "class", "value" => "[ \t\r\n]", "description" => "[ \t\r\n]" ); - - $peg_startRuleFunctions = array( 'Block_List' => array($this, "peg_parseBlock_List") ); - $peg_startRuleFunction = array($this, "peg_parseBlock_List"); - if (isset($options["startRule"])) { - if (!(isset($peg_startRuleFunctions[$options["startRule"]]))) { - throw new Exception("Can't start parsing from rule \"" + $options["startRule"] + "\"."); - } - - $peg_startRuleFunction = $peg_startRuleFunctions[$options["startRule"]]; - } - - /* BEGIN initializer code */ - - // The `maybeJSON` function is not needed in PHP because its return semantics - // are the same as `json_decode` - - if ( ! function_exists( 'peg_empty_attrs' ) ) { - function peg_empty_attrs() { - static $empty_attrs = null; - - if ( null === $empty_attrs ) { - $empty_attrs = json_decode( '{}', true ); - } - - return $empty_attrs; - } - } - - // array arguments are backwards because of PHP - if ( ! function_exists( 'peg_process_inner_content' ) ) { - function peg_process_inner_content( $array ) { - $html = ''; - $blocks = array(); - $content = array(); - - foreach ( $array as $item ) { - if ( is_string( $item ) ) { - $html .= $item; - $content[] = $item; - } else { - $blocks[] = $item; - $content[] = null; - } - } - - return array( $html, $blocks, $content ); - } - } - - if ( ! function_exists( 'peg_join_blocks' ) ) { - function peg_join_blocks( $pre, $tokens, $post ) { - $blocks = array(); - - if ( ! empty( $pre ) ) { - $blocks[] = array( - 'blockName' => null, - 'attrs' => peg_empty_attrs(), - 'innerBlocks' => array(), - 'innerHTML' => $pre, - 'innerContent' => array( $pre ), - ); - } - - foreach ( $tokens as $token ) { - list( $token, $html ) = $token; - - $blocks[] = $token; - - if ( ! empty( $html ) ) { - $blocks[] = array( - 'blockName' => null, - 'attrs' => peg_empty_attrs(), - 'innerBlocks' => array(), - 'innerHTML' => $html, - 'innerContent' => array( $html ), - ); - } - } - - if ( ! empty( $post ) ) { - $blocks[] = array( - 'blockName' => null, - 'attrs' => peg_empty_attrs(), - 'innerBlocks' => array(), - 'innerHTML' => $post, - 'innerContent' => array( $post ), - ); - } - - return $blocks; - } - } - - - /* END initializer code */ - - $peg_result = call_user_func($peg_startRuleFunction); - - if ($peg_result !== $this->peg_FAILED && $this->peg_currPos === $this->input_length) { - $this->cleanup_state(); // Free up memory - return $peg_result; - } else { - if ($peg_result !== $this->peg_FAILED && $this->peg_currPos < $this->input_length) { - $this->peg_fail(array("type" => "end", "description" => "end of input" )); - } - - $exception = $this->peg_buildException(null, $this->peg_maxFailExpected, $this->peg_maxFailPos); - $this->cleanup_state(); // Free up memory - throw $exception; - } - } - -}; \ No newline at end of file diff --git a/packages/block-serialization-spec-parser/.gitignore b/packages/block-serialization-spec-parser/.gitignore new file mode 100644 index 0000000000000..9fd357b5956f0 --- /dev/null +++ b/packages/block-serialization-spec-parser/.gitignore @@ -0,0 +1,3 @@ +# Build Artifacts +parser.js +parser.php diff --git a/packages/block-serialization-spec-parser/.npmignore b/packages/block-serialization-spec-parser/.npmignore new file mode 100644 index 0000000000000..efd9db21d6ec0 --- /dev/null +++ b/packages/block-serialization-spec-parser/.npmignore @@ -0,0 +1,2 @@ +# Intentionally empty, to avoid inheriting from `.gitignore`, since `parser.js` +# and `parser.php` should be included with the npm distributable. diff --git a/packages/block-serialization-spec-parser/CHANGELOG.md b/packages/block-serialization-spec-parser/CHANGELOG.md index c2db52c417908..e4f403ffd6849 100644 --- a/packages/block-serialization-spec-parser/CHANGELOG.md +++ b/packages/block-serialization-spec-parser/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.1.0 (Unreleased) + +- A `parser.php` file generated from the PEGJS grammar is now included. + ## 2.0.2 (2018-12-12) ## 2.0.1 (2018-11-30) diff --git a/bin/create-php-parser.js b/packages/block-serialization-spec-parser/bin/create-php-parser.js similarity index 71% rename from bin/create-php-parser.js rename to packages/block-serialization-spec-parser/bin/create-php-parser.js index 0d661ff0f906b..c9e48a200fa48 100755 --- a/bin/create-php-parser.js +++ b/packages/block-serialization-spec-parser/bin/create-php-parser.js @@ -5,7 +5,7 @@ const phpegjs = require( 'phpegjs' ); const fs = require( 'fs' ); const path = require( 'path' ); -const peg = fs.readFileSync( 'packages/block-serialization-spec-parser/grammar.pegjs', 'utf8' ); +const peg = fs.readFileSync( path.join( __dirname, '..', 'grammar.pegjs' ), 'utf8' ); const parser = pegjs.generate( peg, @@ -20,6 +20,6 @@ const parser = pegjs.generate( ); fs.writeFileSync( - path.join( __dirname, '..', 'lib', 'parser.php' ), + path.join( __dirname, '..', 'parser.php' ), parser ); diff --git a/packages/block-serialization-spec-parser/package.json b/packages/block-serialization-spec-parser/package.json index 16ca515258977..918c269169f09 100644 --- a/packages/block-serialization-spec-parser/package.json +++ b/packages/block-serialization-spec-parser/package.json @@ -22,6 +22,8 @@ "access": "public" }, "scripts": { - "build": "pegjs --format umd -o ./parser.js ./grammar.pegjs" + "build": "concurrently \"npm run build:js\" \"npm run build:php\"", + "build:js": "pegjs --format umd -o ./parser.js ./grammar.pegjs", + "build:php": "node bin/create-php-parser.js" } } diff --git a/packages/block-serialization-spec-parser/parser.js b/packages/block-serialization-spec-parser/parser.js deleted file mode 100644 index be39ccaa3046d..0000000000000 --- a/packages/block-serialization-spec-parser/parser.js +++ /dev/null @@ -1,1791 +0,0 @@ -/* - * Generated by PEG.js 0.10.0. - * - * http://pegjs.org/ - */ -(function(root, factory) { - if (typeof define === "function" && define.amd) { - define([], factory); - } else if (typeof module === "object" && module.exports) { - module.exports = factory(); - } -})(this, function() { - "use strict"; - - function peg$subclass(child, parent) { - function ctor() { this.constructor = child; } - ctor.prototype = parent.prototype; - child.prototype = new ctor(); - } - - function peg$SyntaxError(message, expected, found, location) { - this.message = message; - this.expected = expected; - this.found = found; - this.location = location; - this.name = "SyntaxError"; - - if (typeof Error.captureStackTrace === "function") { - Error.captureStackTrace(this, peg$SyntaxError); - } - } - - peg$subclass(peg$SyntaxError, Error); - - peg$SyntaxError.buildMessage = function(expected, found) { - var DESCRIBE_EXPECTATION_FNS = { - literal: function(expectation) { - return "\"" + literalEscape(expectation.text) + "\""; - }, - - "class": function(expectation) { - var escapedParts = "", - i; - - for (i = 0; i < expectation.parts.length; i++) { - escapedParts += expectation.parts[i] instanceof Array - ? classEscape(expectation.parts[i][0]) + "-" + classEscape(expectation.parts[i][1]) - : classEscape(expectation.parts[i]); - } - - return "[" + (expectation.inverted ? "^" : "") + escapedParts + "]"; - }, - - any: function(expectation) { - return "any character"; - }, - - end: function(expectation) { - return "end of input"; - }, - - other: function(expectation) { - return expectation.description; - } - }; - - function hex(ch) { - return ch.charCodeAt(0).toString(16).toUpperCase(); - } - - function literalEscape(s) { - return s - .replace(/\\/g, '\\\\') - .replace(/"/g, '\\"') - .replace(/\0/g, '\\0') - .replace(/\t/g, '\\t') - .replace(/\n/g, '\\n') - .replace(/\r/g, '\\r') - .replace(/[\x00-\x0F]/g, function(ch) { return '\\x0' + hex(ch); }) - .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return '\\x' + hex(ch); }); - } - - function classEscape(s) { - return s - .replace(/\\/g, '\\\\') - .replace(/\]/g, '\\]') - .replace(/\^/g, '\\^') - .replace(/-/g, '\\-') - .replace(/\0/g, '\\0') - .replace(/\t/g, '\\t') - .replace(/\n/g, '\\n') - .replace(/\r/g, '\\r') - .replace(/[\x00-\x0F]/g, function(ch) { return '\\x0' + hex(ch); }) - .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return '\\x' + hex(ch); }); - } - - function describeExpectation(expectation) { - return DESCRIBE_EXPECTATION_FNS[expectation.type](expectation); - } - - function describeExpected(expected) { - var descriptions = new Array(expected.length), - i, j; - - for (i = 0; i < expected.length; i++) { - descriptions[i] = describeExpectation(expected[i]); - } - - descriptions.sort(); - - if (descriptions.length > 0) { - for (i = 1, j = 1; i < descriptions.length; i++) { - if (descriptions[i - 1] !== descriptions[i]) { - descriptions[j] = descriptions[i]; - j++; - } - } - descriptions.length = j; - } - - switch (descriptions.length) { - case 1: - return descriptions[0]; - - case 2: - return descriptions[0] + " or " + descriptions[1]; - - default: - return descriptions.slice(0, -1).join(", ") - + ", or " - + descriptions[descriptions.length - 1]; - } - } - - function describeFound(found) { - return found ? "\"" + literalEscape(found) + "\"" : "end of input"; - } - - return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found."; - }; - - function peg$parse(input, options) { - options = options !== void 0 ? options : {}; - - var peg$FAILED = {}, - - peg$startRuleFunctions = { Block_List: peg$parseBlock_List }, - peg$startRuleFunction = peg$parseBlock_List, - - peg$c0 = peg$anyExpectation(), - peg$c1 = function(pre, b, html) { /** <?php return array( $b, $html ); ?> **/ return [ b, html ] }, - peg$c2 = function(pre, bs, post) { /** <?php return peg_join_blocks( $pre, $bs, $post ); ?> **/ - return joinBlocks( pre, bs, post ); - }, - peg$c3 = "<!--", - peg$c4 = peg$literalExpectation("<!--", false), - peg$c5 = "wp:", - peg$c6 = peg$literalExpectation("wp:", false), - peg$c7 = function(blockName, a) { - /** <?php return $a; ?> **/ - return a; - }, - peg$c8 = "/-->", - peg$c9 = peg$literalExpectation("/-->", false), - peg$c10 = function(blockName, attrs) { - /** <?php - return array( - 'blockName' => $blockName, - 'attrs' => empty( $attrs ) ? peg_empty_attrs() : $attrs, - 'innerBlocks' => array(), - 'innerHTML' => '', - 'innerContent' => array(), - ); - ?> **/ - - return { - blockName: blockName, - attrs: attrs || {}, - innerBlocks: [], - innerHTML: '', - innerContent: [] - }; - }, - peg$c11 = function(s, children, e) { - /** <?php - list( $innerHTML, $innerBlocks, $innerContent ) = peg_process_inner_content( $children ); - - return array( - 'blockName' => $s['blockName'], - 'attrs' => empty( $s['attrs'] ) ? peg_empty_attrs() : $s['attrs'], - 'innerBlocks' => $innerBlocks, - 'innerHTML' => $innerHTML, - 'innerContent' => $innerContent, - ); - ?> **/ - - var innerParts = processInnerContent( children ); - var innerHTML = innerParts[ 0 ]; - var innerBlocks = innerParts[ 1 ]; - var innerContent = innerParts[ 2 ]; - - return { - blockName: s.blockName, - attrs: s.attrs, - innerBlocks: innerBlocks, - innerHTML: innerHTML, - innerContent: innerContent, - }; - }, - peg$c12 = "-->", - peg$c13 = peg$literalExpectation("-->", false), - peg$c14 = function(blockName, attrs) { - /** <?php - return array( - 'blockName' => $blockName, - 'attrs' => isset( $attrs ) ? $attrs : array(), - ); - ?> **/ - - return { - blockName: blockName, - attrs: attrs || {} - }; - }, - peg$c15 = "/wp:", - peg$c16 = peg$literalExpectation("/wp:", false), - peg$c17 = function(blockName) { - /** <?php - return array( - 'blockName' => $blockName, - ); - ?> **/ - - return { - blockName: blockName - }; - }, - peg$c18 = "/", - peg$c19 = peg$literalExpectation("/", false), - peg$c20 = function(type) { - /** <?php return "core/$type"; ?> **/ - return 'core/' + type; - }, - peg$c21 = /^[a-z]/, - peg$c22 = peg$classExpectation([["a", "z"]], false, false), - peg$c23 = /^[a-z0-9_\-]/, - peg$c24 = peg$classExpectation([["a", "z"], ["0", "9"], "_", "-"], false, false), - peg$c25 = peg$otherExpectation("JSON-encoded attributes embedded in a block's opening comment"), - peg$c26 = "{", - peg$c27 = peg$literalExpectation("{", false), - peg$c28 = "}", - peg$c29 = peg$literalExpectation("}", false), - peg$c30 = "", - peg$c31 = function(attrs) { - /** <?php return json_decode( $attrs, true ); ?> **/ - return maybeJSON( attrs ); - }, - peg$c32 = /^[ \t\r\n]/, - peg$c33 = peg$classExpectation([" ", "\t", "\r", "\n"], false, false), - - peg$currPos = 0, - peg$savedPos = 0, - peg$posDetailsCache = [{ line: 1, column: 1 }], - peg$maxFailPos = 0, - peg$maxFailExpected = [], - peg$silentFails = 0, - - peg$result; - - if ("startRule" in options) { - if (!(options.startRule in peg$startRuleFunctions)) { - throw new Error("Can't start parsing from rule \"" + options.startRule + "\"."); - } - - peg$startRuleFunction = peg$startRuleFunctions[options.startRule]; - } - - function text() { - return input.substring(peg$savedPos, peg$currPos); - } - - function location() { - return peg$computeLocation(peg$savedPos, peg$currPos); - } - - function expected(description, location) { - location = location !== void 0 ? location : peg$computeLocation(peg$savedPos, peg$currPos) - - throw peg$buildStructuredError( - [peg$otherExpectation(description)], - input.substring(peg$savedPos, peg$currPos), - location - ); - } - - function error(message, location) { - location = location !== void 0 ? location : peg$computeLocation(peg$savedPos, peg$currPos) - - throw peg$buildSimpleError(message, location); - } - - function peg$literalExpectation(text, ignoreCase) { - return { type: "literal", text: text, ignoreCase: ignoreCase }; - } - - function peg$classExpectation(parts, inverted, ignoreCase) { - return { type: "class", parts: parts, inverted: inverted, ignoreCase: ignoreCase }; - } - - function peg$anyExpectation() { - return { type: "any" }; - } - - function peg$endExpectation() { - return { type: "end" }; - } - - function peg$otherExpectation(description) { - return { type: "other", description: description }; - } - - function peg$computePosDetails(pos) { - var details = peg$posDetailsCache[pos], p; - - if (details) { - return details; - } else { - p = pos - 1; - while (!peg$posDetailsCache[p]) { - p--; - } - - details = peg$posDetailsCache[p]; - details = { - line: details.line, - column: details.column - }; - - while (p < pos) { - if (input.charCodeAt(p) === 10) { - details.line++; - details.column = 1; - } else { - details.column++; - } - - p++; - } - - peg$posDetailsCache[pos] = details; - return details; - } - } - - function peg$computeLocation(startPos, endPos) { - var startPosDetails = peg$computePosDetails(startPos), - endPosDetails = peg$computePosDetails(endPos); - - return { - start: { - offset: startPos, - line: startPosDetails.line, - column: startPosDetails.column - }, - end: { - offset: endPos, - line: endPosDetails.line, - column: endPosDetails.column - } - }; - } - - function peg$fail(expected) { - if (peg$currPos < peg$maxFailPos) { return; } - - if (peg$currPos > peg$maxFailPos) { - peg$maxFailPos = peg$currPos; - peg$maxFailExpected = []; - } - - peg$maxFailExpected.push(expected); - } - - function peg$buildSimpleError(message, location) { - return new peg$SyntaxError(message, null, null, location); - } - - function peg$buildStructuredError(expected, found, location) { - return new peg$SyntaxError( - peg$SyntaxError.buildMessage(expected, found), - expected, - found, - location - ); - } - - function peg$parseBlock_List() { - var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9; - - s0 = peg$currPos; - s1 = peg$currPos; - s2 = []; - s3 = peg$currPos; - s4 = peg$currPos; - peg$silentFails++; - s5 = peg$parseBlock(); - peg$silentFails--; - if (s5 === peg$FAILED) { - s4 = void 0; - } else { - peg$currPos = s4; - s4 = peg$FAILED; - } - if (s4 !== peg$FAILED) { - if (input.length > peg$currPos) { - s5 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c0); } - } - if (s5 !== peg$FAILED) { - s4 = [s4, s5]; - s3 = s4; - } else { - peg$currPos = s3; - s3 = peg$FAILED; - } - } else { - peg$currPos = s3; - s3 = peg$FAILED; - } - while (s3 !== peg$FAILED) { - s2.push(s3); - s3 = peg$currPos; - s4 = peg$currPos; - peg$silentFails++; - s5 = peg$parseBlock(); - peg$silentFails--; - if (s5 === peg$FAILED) { - s4 = void 0; - } else { - peg$currPos = s4; - s4 = peg$FAILED; - } - if (s4 !== peg$FAILED) { - if (input.length > peg$currPos) { - s5 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c0); } - } - if (s5 !== peg$FAILED) { - s4 = [s4, s5]; - s3 = s4; - } else { - peg$currPos = s3; - s3 = peg$FAILED; - } - } else { - peg$currPos = s3; - s3 = peg$FAILED; - } - } - if (s2 !== peg$FAILED) { - s1 = input.substring(s1, peg$currPos); - } else { - s1 = s2; - } - if (s1 !== peg$FAILED) { - s2 = []; - s3 = peg$currPos; - s4 = peg$parseBlock(); - if (s4 !== peg$FAILED) { - s5 = peg$currPos; - s6 = []; - s7 = peg$currPos; - s8 = peg$currPos; - peg$silentFails++; - s9 = peg$parseBlock(); - peg$silentFails--; - if (s9 === peg$FAILED) { - s8 = void 0; - } else { - peg$currPos = s8; - s8 = peg$FAILED; - } - if (s8 !== peg$FAILED) { - if (input.length > peg$currPos) { - s9 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s9 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c0); } - } - if (s9 !== peg$FAILED) { - s8 = [s8, s9]; - s7 = s8; - } else { - peg$currPos = s7; - s7 = peg$FAILED; - } - } else { - peg$currPos = s7; - s7 = peg$FAILED; - } - while (s7 !== peg$FAILED) { - s6.push(s7); - s7 = peg$currPos; - s8 = peg$currPos; - peg$silentFails++; - s9 = peg$parseBlock(); - peg$silentFails--; - if (s9 === peg$FAILED) { - s8 = void 0; - } else { - peg$currPos = s8; - s8 = peg$FAILED; - } - if (s8 !== peg$FAILED) { - if (input.length > peg$currPos) { - s9 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s9 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c0); } - } - if (s9 !== peg$FAILED) { - s8 = [s8, s9]; - s7 = s8; - } else { - peg$currPos = s7; - s7 = peg$FAILED; - } - } else { - peg$currPos = s7; - s7 = peg$FAILED; - } - } - if (s6 !== peg$FAILED) { - s5 = input.substring(s5, peg$currPos); - } else { - s5 = s6; - } - if (s5 !== peg$FAILED) { - peg$savedPos = s3; - s4 = peg$c1(s1, s4, s5); - s3 = s4; - } else { - peg$currPos = s3; - s3 = peg$FAILED; - } - } else { - peg$currPos = s3; - s3 = peg$FAILED; - } - while (s3 !== peg$FAILED) { - s2.push(s3); - s3 = peg$currPos; - s4 = peg$parseBlock(); - if (s4 !== peg$FAILED) { - s5 = peg$currPos; - s6 = []; - s7 = peg$currPos; - s8 = peg$currPos; - peg$silentFails++; - s9 = peg$parseBlock(); - peg$silentFails--; - if (s9 === peg$FAILED) { - s8 = void 0; - } else { - peg$currPos = s8; - s8 = peg$FAILED; - } - if (s8 !== peg$FAILED) { - if (input.length > peg$currPos) { - s9 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s9 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c0); } - } - if (s9 !== peg$FAILED) { - s8 = [s8, s9]; - s7 = s8; - } else { - peg$currPos = s7; - s7 = peg$FAILED; - } - } else { - peg$currPos = s7; - s7 = peg$FAILED; - } - while (s7 !== peg$FAILED) { - s6.push(s7); - s7 = peg$currPos; - s8 = peg$currPos; - peg$silentFails++; - s9 = peg$parseBlock(); - peg$silentFails--; - if (s9 === peg$FAILED) { - s8 = void 0; - } else { - peg$currPos = s8; - s8 = peg$FAILED; - } - if (s8 !== peg$FAILED) { - if (input.length > peg$currPos) { - s9 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s9 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c0); } - } - if (s9 !== peg$FAILED) { - s8 = [s8, s9]; - s7 = s8; - } else { - peg$currPos = s7; - s7 = peg$FAILED; - } - } else { - peg$currPos = s7; - s7 = peg$FAILED; - } - } - if (s6 !== peg$FAILED) { - s5 = input.substring(s5, peg$currPos); - } else { - s5 = s6; - } - if (s5 !== peg$FAILED) { - peg$savedPos = s3; - s4 = peg$c1(s1, s4, s5); - s3 = s4; - } else { - peg$currPos = s3; - s3 = peg$FAILED; - } - } else { - peg$currPos = s3; - s3 = peg$FAILED; - } - } - if (s2 !== peg$FAILED) { - s3 = peg$currPos; - s4 = []; - if (input.length > peg$currPos) { - s5 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c0); } - } - while (s5 !== peg$FAILED) { - s4.push(s5); - if (input.length > peg$currPos) { - s5 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c0); } - } - } - if (s4 !== peg$FAILED) { - s3 = input.substring(s3, peg$currPos); - } else { - s3 = s4; - } - if (s3 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c2(s1, s2, s3); - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - - return s0; - } - - function peg$parseBlock() { - var s0; - - s0 = peg$parseBlock_Void(); - if (s0 === peg$FAILED) { - s0 = peg$parseBlock_Balanced(); - } - - return s0; - } - - function peg$parseBlock_Void() { - var s0, s1, s2, s3, s4, s5, s6, s7, s8; - - s0 = peg$currPos; - if (input.substr(peg$currPos, 4) === peg$c3) { - s1 = peg$c3; - peg$currPos += 4; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c4); } - } - if (s1 !== peg$FAILED) { - s2 = peg$parse__(); - if (s2 !== peg$FAILED) { - if (input.substr(peg$currPos, 3) === peg$c5) { - s3 = peg$c5; - peg$currPos += 3; - } else { - s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c6); } - } - if (s3 !== peg$FAILED) { - s4 = peg$parseBlock_Name(); - if (s4 !== peg$FAILED) { - s5 = peg$parse__(); - if (s5 !== peg$FAILED) { - s6 = peg$currPos; - s7 = peg$parseBlock_Attributes(); - if (s7 !== peg$FAILED) { - s8 = peg$parse__(); - if (s8 !== peg$FAILED) { - peg$savedPos = s6; - s7 = peg$c7(s4, s7); - s6 = s7; - } else { - peg$currPos = s6; - s6 = peg$FAILED; - } - } else { - peg$currPos = s6; - s6 = peg$FAILED; - } - if (s6 === peg$FAILED) { - s6 = null; - } - if (s6 !== peg$FAILED) { - if (input.substr(peg$currPos, 4) === peg$c8) { - s7 = peg$c8; - peg$currPos += 4; - } else { - s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c9); } - } - if (s7 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c10(s4, s6); - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - - return s0; - } - - function peg$parseBlock_Balanced() { - var s0, s1, s2, s3, s4, s5, s6, s7, s8; - - s0 = peg$currPos; - s1 = peg$parseBlock_Start(); - if (s1 !== peg$FAILED) { - s2 = []; - s3 = peg$parseBlock(); - if (s3 === peg$FAILED) { - s3 = peg$currPos; - s4 = []; - s5 = peg$currPos; - s6 = peg$currPos; - peg$silentFails++; - s7 = peg$parseBlock(); - peg$silentFails--; - if (s7 === peg$FAILED) { - s6 = void 0; - } else { - peg$currPos = s6; - s6 = peg$FAILED; - } - if (s6 !== peg$FAILED) { - s7 = peg$currPos; - peg$silentFails++; - s8 = peg$parseBlock_End(); - peg$silentFails--; - if (s8 === peg$FAILED) { - s7 = void 0; - } else { - peg$currPos = s7; - s7 = peg$FAILED; - } - if (s7 !== peg$FAILED) { - if (input.length > peg$currPos) { - s8 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s8 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c0); } - } - if (s8 !== peg$FAILED) { - s6 = [s6, s7, s8]; - s5 = s6; - } else { - peg$currPos = s5; - s5 = peg$FAILED; - } - } else { - peg$currPos = s5; - s5 = peg$FAILED; - } - } else { - peg$currPos = s5; - s5 = peg$FAILED; - } - if (s5 !== peg$FAILED) { - while (s5 !== peg$FAILED) { - s4.push(s5); - s5 = peg$currPos; - s6 = peg$currPos; - peg$silentFails++; - s7 = peg$parseBlock(); - peg$silentFails--; - if (s7 === peg$FAILED) { - s6 = void 0; - } else { - peg$currPos = s6; - s6 = peg$FAILED; - } - if (s6 !== peg$FAILED) { - s7 = peg$currPos; - peg$silentFails++; - s8 = peg$parseBlock_End(); - peg$silentFails--; - if (s8 === peg$FAILED) { - s7 = void 0; - } else { - peg$currPos = s7; - s7 = peg$FAILED; - } - if (s7 !== peg$FAILED) { - if (input.length > peg$currPos) { - s8 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s8 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c0); } - } - if (s8 !== peg$FAILED) { - s6 = [s6, s7, s8]; - s5 = s6; - } else { - peg$currPos = s5; - s5 = peg$FAILED; - } - } else { - peg$currPos = s5; - s5 = peg$FAILED; - } - } else { - peg$currPos = s5; - s5 = peg$FAILED; - } - } - } else { - s4 = peg$FAILED; - } - if (s4 !== peg$FAILED) { - s3 = input.substring(s3, peg$currPos); - } else { - s3 = s4; - } - } - while (s3 !== peg$FAILED) { - s2.push(s3); - s3 = peg$parseBlock(); - if (s3 === peg$FAILED) { - s3 = peg$currPos; - s4 = []; - s5 = peg$currPos; - s6 = peg$currPos; - peg$silentFails++; - s7 = peg$parseBlock(); - peg$silentFails--; - if (s7 === peg$FAILED) { - s6 = void 0; - } else { - peg$currPos = s6; - s6 = peg$FAILED; - } - if (s6 !== peg$FAILED) { - s7 = peg$currPos; - peg$silentFails++; - s8 = peg$parseBlock_End(); - peg$silentFails--; - if (s8 === peg$FAILED) { - s7 = void 0; - } else { - peg$currPos = s7; - s7 = peg$FAILED; - } - if (s7 !== peg$FAILED) { - if (input.length > peg$currPos) { - s8 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s8 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c0); } - } - if (s8 !== peg$FAILED) { - s6 = [s6, s7, s8]; - s5 = s6; - } else { - peg$currPos = s5; - s5 = peg$FAILED; - } - } else { - peg$currPos = s5; - s5 = peg$FAILED; - } - } else { - peg$currPos = s5; - s5 = peg$FAILED; - } - if (s5 !== peg$FAILED) { - while (s5 !== peg$FAILED) { - s4.push(s5); - s5 = peg$currPos; - s6 = peg$currPos; - peg$silentFails++; - s7 = peg$parseBlock(); - peg$silentFails--; - if (s7 === peg$FAILED) { - s6 = void 0; - } else { - peg$currPos = s6; - s6 = peg$FAILED; - } - if (s6 !== peg$FAILED) { - s7 = peg$currPos; - peg$silentFails++; - s8 = peg$parseBlock_End(); - peg$silentFails--; - if (s8 === peg$FAILED) { - s7 = void 0; - } else { - peg$currPos = s7; - s7 = peg$FAILED; - } - if (s7 !== peg$FAILED) { - if (input.length > peg$currPos) { - s8 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s8 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c0); } - } - if (s8 !== peg$FAILED) { - s6 = [s6, s7, s8]; - s5 = s6; - } else { - peg$currPos = s5; - s5 = peg$FAILED; - } - } else { - peg$currPos = s5; - s5 = peg$FAILED; - } - } else { - peg$currPos = s5; - s5 = peg$FAILED; - } - } - } else { - s4 = peg$FAILED; - } - if (s4 !== peg$FAILED) { - s3 = input.substring(s3, peg$currPos); - } else { - s3 = s4; - } - } - } - if (s2 !== peg$FAILED) { - s3 = peg$parseBlock_End(); - if (s3 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c11(s1, s2, s3); - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - - return s0; - } - - function peg$parseBlock_Start() { - var s0, s1, s2, s3, s4, s5, s6, s7, s8; - - s0 = peg$currPos; - if (input.substr(peg$currPos, 4) === peg$c3) { - s1 = peg$c3; - peg$currPos += 4; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c4); } - } - if (s1 !== peg$FAILED) { - s2 = peg$parse__(); - if (s2 !== peg$FAILED) { - if (input.substr(peg$currPos, 3) === peg$c5) { - s3 = peg$c5; - peg$currPos += 3; - } else { - s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c6); } - } - if (s3 !== peg$FAILED) { - s4 = peg$parseBlock_Name(); - if (s4 !== peg$FAILED) { - s5 = peg$parse__(); - if (s5 !== peg$FAILED) { - s6 = peg$currPos; - s7 = peg$parseBlock_Attributes(); - if (s7 !== peg$FAILED) { - s8 = peg$parse__(); - if (s8 !== peg$FAILED) { - peg$savedPos = s6; - s7 = peg$c7(s4, s7); - s6 = s7; - } else { - peg$currPos = s6; - s6 = peg$FAILED; - } - } else { - peg$currPos = s6; - s6 = peg$FAILED; - } - if (s6 === peg$FAILED) { - s6 = null; - } - if (s6 !== peg$FAILED) { - if (input.substr(peg$currPos, 3) === peg$c12) { - s7 = peg$c12; - peg$currPos += 3; - } else { - s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c13); } - } - if (s7 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c14(s4, s6); - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - - return s0; - } - - function peg$parseBlock_End() { - var s0, s1, s2, s3, s4, s5, s6; - - s0 = peg$currPos; - if (input.substr(peg$currPos, 4) === peg$c3) { - s1 = peg$c3; - peg$currPos += 4; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c4); } - } - if (s1 !== peg$FAILED) { - s2 = peg$parse__(); - if (s2 !== peg$FAILED) { - if (input.substr(peg$currPos, 4) === peg$c15) { - s3 = peg$c15; - peg$currPos += 4; - } else { - s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c16); } - } - if (s3 !== peg$FAILED) { - s4 = peg$parseBlock_Name(); - if (s4 !== peg$FAILED) { - s5 = peg$parse__(); - if (s5 !== peg$FAILED) { - if (input.substr(peg$currPos, 3) === peg$c12) { - s6 = peg$c12; - peg$currPos += 3; - } else { - s6 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c13); } - } - if (s6 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c17(s4); - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - - return s0; - } - - function peg$parseBlock_Name() { - var s0; - - s0 = peg$parseNamespaced_Block_Name(); - if (s0 === peg$FAILED) { - s0 = peg$parseCore_Block_Name(); - } - - return s0; - } - - function peg$parseNamespaced_Block_Name() { - var s0, s1, s2, s3, s4; - - s0 = peg$currPos; - s1 = peg$currPos; - s2 = peg$parseBlock_Name_Part(); - if (s2 !== peg$FAILED) { - if (input.charCodeAt(peg$currPos) === 47) { - s3 = peg$c18; - peg$currPos++; - } else { - s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c19); } - } - if (s3 !== peg$FAILED) { - s4 = peg$parseBlock_Name_Part(); - if (s4 !== peg$FAILED) { - s2 = [s2, s3, s4]; - s1 = s2; - } else { - peg$currPos = s1; - s1 = peg$FAILED; - } - } else { - peg$currPos = s1; - s1 = peg$FAILED; - } - } else { - peg$currPos = s1; - s1 = peg$FAILED; - } - if (s1 !== peg$FAILED) { - s0 = input.substring(s0, peg$currPos); - } else { - s0 = s1; - } - - return s0; - } - - function peg$parseCore_Block_Name() { - var s0, s1, s2; - - s0 = peg$currPos; - s1 = peg$currPos; - s2 = peg$parseBlock_Name_Part(); - if (s2 !== peg$FAILED) { - s1 = input.substring(s1, peg$currPos); - } else { - s1 = s2; - } - if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c20(s1); - } - s0 = s1; - - return s0; - } - - function peg$parseBlock_Name_Part() { - var s0, s1, s2, s3, s4; - - s0 = peg$currPos; - s1 = peg$currPos; - if (peg$c21.test(input.charAt(peg$currPos))) { - s2 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c22); } - } - if (s2 !== peg$FAILED) { - s3 = []; - if (peg$c23.test(input.charAt(peg$currPos))) { - s4 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c24); } - } - while (s4 !== peg$FAILED) { - s3.push(s4); - if (peg$c23.test(input.charAt(peg$currPos))) { - s4 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c24); } - } - } - if (s3 !== peg$FAILED) { - s2 = [s2, s3]; - s1 = s2; - } else { - peg$currPos = s1; - s1 = peg$FAILED; - } - } else { - peg$currPos = s1; - s1 = peg$FAILED; - } - if (s1 !== peg$FAILED) { - s0 = input.substring(s0, peg$currPos); - } else { - s0 = s1; - } - - return s0; - } - - function peg$parseBlock_Attributes() { - var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12; - - peg$silentFails++; - s0 = peg$currPos; - s1 = peg$currPos; - s2 = peg$currPos; - if (input.charCodeAt(peg$currPos) === 123) { - s3 = peg$c26; - peg$currPos++; - } else { - s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c27); } - } - if (s3 !== peg$FAILED) { - s4 = []; - s5 = peg$currPos; - s6 = peg$currPos; - peg$silentFails++; - s7 = peg$currPos; - if (input.charCodeAt(peg$currPos) === 125) { - s8 = peg$c28; - peg$currPos++; - } else { - s8 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c29); } - } - if (s8 !== peg$FAILED) { - s9 = peg$parse__(); - if (s9 !== peg$FAILED) { - s10 = peg$c30; - if (s10 !== peg$FAILED) { - if (input.charCodeAt(peg$currPos) === 47) { - s11 = peg$c18; - peg$currPos++; - } else { - s11 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c19); } - } - if (s11 === peg$FAILED) { - s11 = null; - } - if (s11 !== peg$FAILED) { - if (input.substr(peg$currPos, 3) === peg$c12) { - s12 = peg$c12; - peg$currPos += 3; - } else { - s12 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c13); } - } - if (s12 !== peg$FAILED) { - s8 = [s8, s9, s10, s11, s12]; - s7 = s8; - } else { - peg$currPos = s7; - s7 = peg$FAILED; - } - } else { - peg$currPos = s7; - s7 = peg$FAILED; - } - } else { - peg$currPos = s7; - s7 = peg$FAILED; - } - } else { - peg$currPos = s7; - s7 = peg$FAILED; - } - } else { - peg$currPos = s7; - s7 = peg$FAILED; - } - peg$silentFails--; - if (s7 === peg$FAILED) { - s6 = void 0; - } else { - peg$currPos = s6; - s6 = peg$FAILED; - } - if (s6 !== peg$FAILED) { - if (input.length > peg$currPos) { - s7 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c0); } - } - if (s7 !== peg$FAILED) { - s6 = [s6, s7]; - s5 = s6; - } else { - peg$currPos = s5; - s5 = peg$FAILED; - } - } else { - peg$currPos = s5; - s5 = peg$FAILED; - } - while (s5 !== peg$FAILED) { - s4.push(s5); - s5 = peg$currPos; - s6 = peg$currPos; - peg$silentFails++; - s7 = peg$currPos; - if (input.charCodeAt(peg$currPos) === 125) { - s8 = peg$c28; - peg$currPos++; - } else { - s8 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c29); } - } - if (s8 !== peg$FAILED) { - s9 = peg$parse__(); - if (s9 !== peg$FAILED) { - s10 = peg$c30; - if (s10 !== peg$FAILED) { - if (input.charCodeAt(peg$currPos) === 47) { - s11 = peg$c18; - peg$currPos++; - } else { - s11 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c19); } - } - if (s11 === peg$FAILED) { - s11 = null; - } - if (s11 !== peg$FAILED) { - if (input.substr(peg$currPos, 3) === peg$c12) { - s12 = peg$c12; - peg$currPos += 3; - } else { - s12 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c13); } - } - if (s12 !== peg$FAILED) { - s8 = [s8, s9, s10, s11, s12]; - s7 = s8; - } else { - peg$currPos = s7; - s7 = peg$FAILED; - } - } else { - peg$currPos = s7; - s7 = peg$FAILED; - } - } else { - peg$currPos = s7; - s7 = peg$FAILED; - } - } else { - peg$currPos = s7; - s7 = peg$FAILED; - } - } else { - peg$currPos = s7; - s7 = peg$FAILED; - } - peg$silentFails--; - if (s7 === peg$FAILED) { - s6 = void 0; - } else { - peg$currPos = s6; - s6 = peg$FAILED; - } - if (s6 !== peg$FAILED) { - if (input.length > peg$currPos) { - s7 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c0); } - } - if (s7 !== peg$FAILED) { - s6 = [s6, s7]; - s5 = s6; - } else { - peg$currPos = s5; - s5 = peg$FAILED; - } - } else { - peg$currPos = s5; - s5 = peg$FAILED; - } - } - if (s4 !== peg$FAILED) { - if (input.charCodeAt(peg$currPos) === 125) { - s5 = peg$c28; - peg$currPos++; - } else { - s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c29); } - } - if (s5 !== peg$FAILED) { - s3 = [s3, s4, s5]; - s2 = s3; - } else { - peg$currPos = s2; - s2 = peg$FAILED; - } - } else { - peg$currPos = s2; - s2 = peg$FAILED; - } - } else { - peg$currPos = s2; - s2 = peg$FAILED; - } - if (s2 !== peg$FAILED) { - s1 = input.substring(s1, peg$currPos); - } else { - s1 = s2; - } - if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c31(s1); - } - s0 = s1; - peg$silentFails--; - if (s0 === peg$FAILED) { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c25); } - } - - return s0; - } - - function peg$parse__() { - var s0, s1; - - s0 = []; - if (peg$c32.test(input.charAt(peg$currPos))) { - s1 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c33); } - } - if (s1 !== peg$FAILED) { - while (s1 !== peg$FAILED) { - s0.push(s1); - if (peg$c32.test(input.charAt(peg$currPos))) { - s1 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c33); } - } - } - } else { - s0 = peg$FAILED; - } - - return s0; - } - - - - /* - * - * _____ _ _ - * / ____| | | | | - * | | __ _ _| |_ ___ _ __ | |__ ___ _ __ __ _ - * | | |_ | | | | __/ _ \ '_ \| '_ \ / _ \ '__/ _` | - * | |__| | |_| | || __/ | | | |_) | __/ | | (_| | - * \_____|\__,_|\__\___|_| |_|_.__/ \___|_| \__, | - * __/ | - * GRAMMAR |___/ - * - * - * Welcome to the grammar file for Gutenberg posts! - * - * Please don't be distracted by the functions at the top - * here - they're just helpers for the grammar below. We - * try to keep them as minimal and simple as possible, - * but the parser generator forces us to declare them at - * the beginning of the file. - * - * What follows is the official specification grammar for - * documents created or edited in Gutenberg. It starts at - * the top-level rule `Block_List` - * - * The grammar is defined by a series of _rules_ and ways - * to return matches on those rules. It's a _PEG_, a - * parsing expression grammar, which simply means that for - * each of our rules we have a set of sub-rules to match - * on and the generated parser will try them in order - * until it finds the first match. - * - * This grammar is a _specification_ (with as little actual - * code as we can get away with) which is used by the - * parser generator to generate the actual _parser_ which - * is used by Gutenberg. We generate two parsers: one in - * JavaScript for use the browser and one in PHP for - * WordPress itself. PEG parser generators are available - * in many languages, though different libraries may require - * some translation of this grammar into their syntax. - * - * For more information: - * @see https://pegjs.org - * @see https://en.wikipedia.org/wiki/Parsing_expression_grammar - * - */ - - /** <?php - // The `maybeJSON` function is not needed in PHP because its return semantics - // are the same as `json_decode` - - if ( ! function_exists( 'peg_empty_attrs' ) ) { - function peg_empty_attrs() { - static $empty_attrs = null; - - if ( null === $empty_attrs ) { - $empty_attrs = json_decode( '{}', true ); - } - - return $empty_attrs; - } - } - - // array arguments are backwards because of PHP - if ( ! function_exists( 'peg_process_inner_content' ) ) { - function peg_process_inner_content( $array ) { - $html = ''; - $blocks = array(); - $content = array(); - - foreach ( $array as $item ) { - if ( is_string( $item ) ) { - $html .= $item; - $content[] = $item; - } else { - $blocks[] = $item; - $content[] = null; - } - } - - return array( $html, $blocks, $content ); - } - } - - if ( ! function_exists( 'peg_join_blocks' ) ) { - function peg_join_blocks( $pre, $tokens, $post ) { - $blocks = array(); - - if ( ! empty( $pre ) ) { - $blocks[] = array( - 'blockName' => null, - 'attrs' => peg_empty_attrs(), - 'innerBlocks' => array(), - 'innerHTML' => $pre, - 'innerContent' => array( $pre ), - ); - } - - foreach ( $tokens as $token ) { - list( $token, $html ) = $token; - - $blocks[] = $token; - - if ( ! empty( $html ) ) { - $blocks[] = array( - 'blockName' => null, - 'attrs' => peg_empty_attrs(), - 'innerBlocks' => array(), - 'innerHTML' => $html, - 'innerContent' => array( $html ), - ); - } - } - - if ( ! empty( $post ) ) { - $blocks[] = array( - 'blockName' => null, - 'attrs' => peg_empty_attrs(), - 'innerBlocks' => array(), - 'innerHTML' => $post, - 'innerContent' => array( $post ), - ); - } - - return $blocks; - } - } - - ?> **/ - - function freeform( s ) { - return s.length && { - blockName: null, - attrs: {}, - innerBlocks: [], - innerHTML: s, - innerContent: [ s ], - }; - } - - function joinBlocks( pre, tokens, post ) { - var blocks = [], i, l, html, item, token; - - if ( pre.length ) { - blocks.push( freeform( pre ) ); - } - - for ( i = 0, l = tokens.length; i < l; i++ ) { - item = tokens[ i ]; - token = item[ 0 ]; - html = item[ 1 ]; - - blocks.push( token ); - if ( html.length ) { - blocks.push( freeform( html ) ); - } - } - - if ( post.length ) { - blocks.push( freeform( post ) ); - } - - return blocks; - } - - function maybeJSON( s ) { - try { - return JSON.parse( s ); - } catch (e) { - return null; - } - } - - function processInnerContent( list ) { - var i, l, item; - var html = ''; - var blocks = []; - var content = []; - - // nod to performance over a simpler reduce - // and clone model we could have taken here - for ( i = 0, l = list.length; i < l; i++ ) { - item = list[ i ]; - - if ( 'string' === typeof item ) { - html += item; - content.push( item ); - } else { - blocks.push( item ); - content.push( null ); - } - }; - - return [ html, blocks, content ]; - } - - - - peg$result = peg$startRuleFunction(); - - if (peg$result !== peg$FAILED && peg$currPos === input.length) { - return peg$result; - } else { - if (peg$result !== peg$FAILED && peg$currPos < input.length) { - peg$fail(peg$endExpectation()); - } - - throw peg$buildStructuredError( - peg$maxFailExpected, - peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null, - peg$maxFailPos < input.length - ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1) - : peg$computeLocation(peg$maxFailPos, peg$maxFailPos) - ); - } - } - - return { - SyntaxError: peg$SyntaxError, - parse: peg$parse - }; -}); diff --git a/packages/block-serialization-spec-parser/test/test-parser.php b/packages/block-serialization-spec-parser/test/test-parser.php index 28c523951c343..a55183c44b8e5 100644 --- a/packages/block-serialization-spec-parser/test/test-parser.php +++ b/packages/block-serialization-spec-parser/test/test-parser.php @@ -8,7 +8,7 @@ */ // Include the generated parser. -require_once dirname( __FILE__ ) . '/../../../lib/parser.php'; +require_once dirname( __FILE__ ) . '/../parser.php'; $parser = new Gutenberg_PEG_Parser(); diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 9e0d0f2bd2a4b..85a39fbd82a4a 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -33,7 +33,7 @@ <!-- Exclude generated files --> <exclude-pattern>./languages/gutenberg-translations.php</exclude-pattern> - <exclude-pattern>./lib/parser.php</exclude-pattern> + <exclude-pattern>./packages/block-serialization-spec-parser/parser.php</exclude-pattern> <rule ref="PHPCompatibility.PHP.NewKeywords.t_namespaceFound"> <exclude-pattern>lib/class-wp-rest-block-renderer-controller.php</exclude-pattern> From af916f053c256b0a51ff710e7af4ac8552c4b856 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 25 Jan 2019 11:32:48 -0500 Subject: [PATCH 237/691] Plugin: Remove core-defined block detection functions (#13467) --- lib/register.php | 55 ------------------------------------------------ 1 file changed, 55 deletions(-) diff --git a/lib/register.php b/lib/register.php index 3dc7264a9dfb0..f4b776eb309cd 100644 --- a/lib/register.php +++ b/lib/register.php @@ -310,32 +310,6 @@ function gutenberg_can_edit_post_type( $post_type ) { return apply_filters( 'gutenberg_can_edit_post_type', $can_edit, $post_type ); } -if ( ! function_exists( 'has_blocks' ) ) { - /** - * Determine whether a post or content string has blocks. - * - * This test optimizes for performance rather than strict accuracy, detecting - * the pattern of a block but not validating its structure. For strict accuracy - * you should use the block parser on post content. - * - * @since 3.6.0 - * @see gutenberg_parse_blocks() - * - * @param int|string|WP_Post|null $post Optional. Post content, post ID, or post object. Defaults to global $post. - * @return bool Whether the post has blocks. - */ - function has_blocks( $post = null ) { - if ( ! is_string( $post ) ) { - $wp_post = get_post( $post ); - if ( $wp_post instanceof WP_Post ) { - $post = $wp_post->post_content; - } - } - - return false !== strpos( (string) $post, '<!-- wp:' ); - } -} - /** * Determine whether a post has blocks. This test optimizes for performance * rather than strict accuracy, detecting the pattern of a block but not @@ -374,35 +348,6 @@ function gutenberg_content_has_blocks( $content ) { return has_blocks( $content ); } -if ( ! function_exists( 'has_block' ) ) { - /** - * Determine whether a $post or a string contains a specific block type. - * This test optimizes for performance rather than strict accuracy, detecting - * the block type exists but not validating its structure. - * For strict accuracy, you should use the block parser on post content. - * - * @since 3.6.0 - * - * @param string $block_type Full Block type to look for. - * @param int|string|WP_Post|null $post Optional. Post content, post ID, or post object. Defaults to global $post. - * @return bool Whether the post content contains the specified block. - */ - function has_block( $block_type, $post = null ) { - if ( ! has_blocks( $post ) ) { - return false; - } - - if ( ! is_string( $post ) ) { - $wp_post = get_post( $post ); - if ( $wp_post instanceof WP_Post ) { - $post = $wp_post->post_content; - } - } - - return false !== strpos( $post, '<!-- wp:' . $block_type . ' ' ); - } -} - /** * Returns the current version of the block format that the content string is using. * From ff6b8340fdf87e6219e42cbb24e5aca66ef89d61 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 25 Jan 2019 12:22:50 -0500 Subject: [PATCH 238/691] Plugin: Remove list screens integrations (#13459) --- .../backward-compatibility/deprecations.md | 4 + gutenberg.php | 218 +----------------- 2 files changed, 16 insertions(+), 206 deletions(-) diff --git a/docs/designers-developers/developers/backward-compatibility/deprecations.md b/docs/designers-developers/developers/backward-compatibility/deprecations.md index 4bc5c3a89151b..d422ea4def9bc 100644 --- a/docs/designers-developers/developers/backward-compatibility/deprecations.md +++ b/docs/designers-developers/developers/backward-compatibility/deprecations.md @@ -26,6 +26,10 @@ The Gutenberg project's deprecation policy is intended to support backward compa - The PHP function `gutenberg_bulk_post_updated_messages` has been removed. - The PHP function `gutenberg_kses_allowedtags` has been removed. - The PHP function `gutenberg_add_responsive_body_class` has been removed. +- The PHP function `gutenberg_add_edit_link_filters` has been removed. +- The PHP function `gutenberg_add_edit_link` has been removed. +- The PHP function `gutenberg_block_bulk_actions` has been removed. +- The PHP function `gutenberg_replace_default_add_new_button` has been removed. ## 4.5.0 - `Dropdown.refresh()` has been deprecated as the contained `Popover` is now automatically refreshed. diff --git a/gutenberg.php b/gutenberg.php index 902e76675851b..09d759a64c488 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -263,84 +263,25 @@ function gutenberg_redirect_demo() { * the post/page screens. * * @since 1.5.0 + * @deprecated 5.0.0 */ function gutenberg_add_edit_link_filters() { - // For hierarchical post types. - add_filter( 'page_row_actions', 'gutenberg_add_edit_link', 10, 2 ); - // For non-hierarchical post types. - add_filter( 'post_row_actions', 'gutenberg_add_edit_link', 10, 2 ); + _deprecated_function( __FUNCTION__, '5.0.0' ); } -add_action( 'admin_init', 'gutenberg_add_edit_link_filters' ); /** * Registers an additional link in the post/page screens to edit any post/page in * the Classic editor. * * @since 1.5.0 + * @deprecated 5.0.0 * - * @param array $actions Post actions. - * @param WP_Post $post Edited post. + * @param array $actions Post actions. * - * @return array Updated post actions. + * @return array Updated post actions. */ -function gutenberg_add_edit_link( $actions, $post ) { - // Build the classic edit action. See also: WP_Posts_List_Table::handle_row_actions(). - $title = _draft_or_post_title( $post->ID ); - - if ( 'wp_block' === $post->post_type ) { - unset( $actions['inline hide-if-no-js'] ); - - // Export uses block raw content, which is only returned from the post - // REST endpoint via `context=edit`, requiring edit capability. - $post_type = get_post_type_object( $post->post_type ); - if ( ! current_user_can( $post_type->cap->edit_post, $post->ID ) ) { - return $actions; - } - - $actions['export'] = sprintf( - '<button type="button" class="wp-list-reusable-blocks__export button-link" data-id="%s" aria-label="%s">%s</button>', - $post->ID, - esc_attr( - sprintf( - /* translators: %s: post title */ - __( 'Export &#8220;%s&#8221; as JSON', 'gutenberg' ), - $title - ) - ), - __( 'Export as JSON', 'gutenberg' ) - ); - return $actions; - } - - if ( ! gutenberg_can_edit_post( $post ) ) { - return $actions; - } - - $edit_url = get_edit_post_link( $post->ID, 'raw' ); - $edit_url = add_query_arg( 'classic-editor', '', $edit_url ); - - $edit_action = array( - 'classic' => sprintf( - '<a href="%s" aria-label="%s">%s</a>', - esc_url( $edit_url ), - esc_attr( - sprintf( - /* translators: %s: post title */ - __( 'Edit &#8220;%s&#8221; in the classic editor', 'gutenberg' ), - $title - ) - ), - __( 'Classic Editor', 'gutenberg' ) - ), - ); - - // Insert the Classic Edit action after the Edit action. - $edit_offset = array_search( 'edit', array_keys( $actions ), true ); - $actions = array_merge( - array_slice( $actions, 0, $edit_offset + 1 ), - $edit_action, - array_slice( $actions, $edit_offset + 1 ) - ); +function gutenberg_add_edit_link( $actions ) { + _deprecated_function( __FUNCTION__, '5.0.0' ); return $actions; } @@ -349,162 +290,27 @@ function gutenberg_add_edit_link( $actions, $post ) { * Removes the Edit action from the reusable block list's Bulk Actions dropdown. * * @since 3.8.0 + * @deprecated 5.0.0 * * @param array $actions Bulk actions. * * @return array Updated bulk actions. */ function gutenberg_block_bulk_actions( $actions ) { - unset( $actions['edit'] ); + _deprecated_function( __FUNCTION__, '5.0.0' ); + return $actions; } -add_filter( 'bulk_actions-edit-wp_block', 'gutenberg_block_bulk_actions' ); /** * Prints the JavaScript to replace the default "Add New" button.$_COOKIE * * @since 1.5.0 + * @deprecated 5.0.0 */ function gutenberg_replace_default_add_new_button() { - global $typenow; - - if ( 'wp_block' === $typenow ) { - ?> - <style type="text/css"> - .page-title-action { - display: none; - } - </style> - <?php - } - - if ( ! gutenberg_can_edit_post_type( $typenow ) ) { - return; - } - - ?> - <style type="text/css"> - .split-page-title-action { - display: inline-block; - } - - .split-page-title-action a, - .split-page-title-action a:active, - .split-page-title-action .expander:after { - padding: 6px 10px; - position: relative; - top: -3px; - text-decoration: none; - border: 1px solid #ccc; - border-radius: 2px; - background: #f7f7f7; - text-shadow: none; - font-weight: 600; - font-size: 13px; - line-height: normal; /* IE8-IE11 need this for buttons */ - color: #0073aa; /* some of these controls are button elements and don't inherit from links */ - cursor: pointer; - outline: 0; - } - - .split-page-title-action a:hover, - .split-page-title-action .expander:hover:after { - border-color: #008EC2; - background: #00a0d2; - color: #fff; - } - - .split-page-title-action a:focus, - .split-page-title-action .expander:focus:after { - border-color: #5b9dd9; - box-shadow: 0 0 2px rgba( 30, 140, 190, 0.8 ); - } - - .split-page-title-action .expander:after { - content: "\f140"; - font: 400 20px/.5 dashicons; - speak: none; - top: 1px; - <?php if ( is_rtl() ) : ?> - right: -1px; - <?php else : ?> - left: -1px; - <?php endif; ?> - position: relative; - vertical-align: top; - text-decoration: none !important; - padding: 4px 5px 4px 3px; - } - - .split-page-title-action .dropdown { - display: none; - } - - .split-page-title-action .dropdown.visible { - display: block; - position: absolute; - margin-top: 3px; - z-index: 1; - } - - .split-page-title-action .dropdown.visible a { - display: block; - top: 0; - margin: -1px 0; - <?php if ( is_rtl() ) : ?> - padding-left: 9px; - <?php else : ?> - padding-right: 9px; - <?php endif; ?> - } - - .split-page-title-action .expander { - outline: none; - } - - </style> - <script type="text/javascript"> - document.addEventListener( 'DOMContentLoaded', function() { - var buttons = document.getElementsByClassName( 'page-title-action' ), - button = buttons.item( 0 ); - - if ( ! button ) { - return; - } - - var url = button.href; - var urlHasParams = ( -1 !== url.indexOf( '?' ) ); - var classicUrl = url + ( urlHasParams ? '&' : '?' ) + 'classic-editor'; - - var newbutton = '<span id="split-page-title-action" class="split-page-title-action">'; - newbutton += '<a href="' + url + '">' + button.innerText + '</a>'; - newbutton += '<span class="expander" tabindex="0" role="button" aria-haspopup="true" aria-label="<?php echo esc_js( __( 'Toggle editor selection menu', 'gutenberg' ) ); ?>"></span>'; - newbutton += '<span class="dropdown"><a href="' + url + '">Gutenberg</a>'; - newbutton += '<a href="' + classicUrl + '"><?php echo esc_js( __( 'Classic Editor', 'gutenberg' ) ); ?></a></span></span><span class="page-title-action" style="display:none;"></span>'; - - button.insertAdjacentHTML( 'afterend', newbutton ); - button.parentNode.removeChild( button ); - - var expander = document.getElementById( 'split-page-title-action' ).getElementsByClassName( 'expander' ).item( 0 ); - var dropdown = expander.parentNode.querySelector( '.dropdown' ); - function toggleDropdown() { - dropdown.classList.toggle( 'visible' ); - } - expander.addEventListener( 'click', function( e ) { - e.preventDefault(); - toggleDropdown(); - } ); - expander.addEventListener( 'keydown', function( e ) { - if ( 13 === e.which || 32 === e.which ) { - e.preventDefault(); - toggleDropdown(); - } - } ); - } ); - </script> - <?php + _deprecated_function( __FUNCTION__, '5.0.0' ); } -add_action( 'admin_print_scripts-edit.php', 'gutenberg_replace_default_add_new_button' ); /** * Adds the block-editor-page class to the body tag on the Gutenberg page. From d28b22873f07d4a44b3c44c17861485de8999822 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 25 Jan 2019 12:25:00 -0500 Subject: [PATCH 239/691] API Fetch: Expose nonce on created middleware function (#13451) --- lib/client-assets.php | 23 +++++++- lib/packages-dependencies.php | 1 - package-lock.json | 1 - packages/api-fetch/CHANGELOG.md | 7 ++- packages/api-fetch/README.md | 2 + packages/api-fetch/package.json | 1 - packages/api-fetch/src/middlewares/nonce.js | 53 ++++++------------- .../api-fetch/src/middlewares/test/nonce.js | 1 + 8 files changed, 46 insertions(+), 43 deletions(-) diff --git a/lib/client-assets.php b/lib/client-assets.php index 093b5a46a35a3..3e338be2e7f2d 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -185,10 +185,31 @@ function gutenberg_register_scripts_and_styles() { gutenberg_register_packages_scripts(); // Inline scripts. + global $wp_scripts; + if ( isset( $wp_scripts->registered['wp-api-fetch'] ) ) { + $wp_scripts->registered['wp-api-fetch']->deps[] = 'wp-hooks'; + } wp_add_inline_script( 'wp-api-fetch', sprintf( - 'wp.apiFetch.use( wp.apiFetch.createNonceMiddleware( "%s" ) );', + implode( + "\n", + array( + '( function() {', + ' var nonceMiddleware = wp.apiFetch.createNonceMiddleware( "%s" );', + ' wp.apiFetch.use( nonceMiddleware );', + ' wp.hooks.addAction(', + ' "heartbeat.tick",', + ' "core/api-fetch/create-nonce-middleware",', + ' function( response ) {', + ' if ( response[ "rest_nonce" ] ) {', + ' nonceMiddleware.nonce = response[ "rest_nonce" ];', + ' }', + ' }', + ' )', + '} )()', + ) + ), ( wp_installing() && ! is_multisite() ) ? '' : wp_create_nonce( 'wp_rest' ) ), 'after' diff --git a/lib/packages-dependencies.php b/lib/packages-dependencies.php index ef1b2bc22cdb9..ece2a6ae2bb86 100644 --- a/lib/packages-dependencies.php +++ b/lib/packages-dependencies.php @@ -16,7 +16,6 @@ 'wp-rich-text', ), 'wp-api-fetch' => array( - 'wp-hooks', 'wp-i18n', 'wp-url', ), diff --git a/package-lock.json b/package-lock.json index d862eeb3aa823..ca8a4feba01f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2271,7 +2271,6 @@ "version": "file:packages/api-fetch", "requires": { "@babel/runtime": "^7.0.0", - "@wordpress/hooks": "file:packages/hooks", "@wordpress/i18n": "file:packages/i18n", "@wordpress/url": "file:packages/url" } diff --git a/packages/api-fetch/CHANGELOG.md b/packages/api-fetch/CHANGELOG.md index 2e813a48221b2..1727529b88366 100644 --- a/packages/api-fetch/CHANGELOG.md +++ b/packages/api-fetch/CHANGELOG.md @@ -1,7 +1,12 @@ -## 2.3.0 (Unreleased) +## 3.0.0 (Unreleased) + +### Breaking Changes + +- A created nonce middleware will no longer automatically listen for `heartbeat.tick` actions. Assign to the new `nonce` middleware property instead. ### New Feature +- The function returned by `createNonceMiddleware` includes an assignable `nonce` property corresponding to the active nonce to be used. - Default fetch handler can be overridden with a custom fetch handler ## 2.2.6 (2018-12-12) diff --git a/packages/api-fetch/README.md b/packages/api-fetch/README.md index 26075a7cef6c4..c5b6b190ddbe3 100644 --- a/packages/api-fetch/README.md +++ b/packages/api-fetch/README.md @@ -76,6 +76,8 @@ const nonce = "nonce value"; apiFetch.use( apiFetch.createNonceMiddleware( nonce ) ); ``` +The function returned by `createNonceMiddleware` includes a `nonce` property corresponding to the actively used nonce. You may also assign to this property if you have a fresh nonce value to use. + **Root URL middleware** ```js diff --git a/packages/api-fetch/package.json b/packages/api-fetch/package.json index 33eb220c8f857..7c3bc9c5ed17e 100644 --- a/packages/api-fetch/package.json +++ b/packages/api-fetch/package.json @@ -22,7 +22,6 @@ "react-native": "src/index", "dependencies": { "@babel/runtime": "^7.0.0", - "@wordpress/hooks": "file:../hooks", "@wordpress/i18n": "file:../i18n", "@wordpress/url": "file:../url" }, diff --git a/packages/api-fetch/src/middlewares/nonce.js b/packages/api-fetch/src/middlewares/nonce.js index 706715a55a9e0..e9baf2e785b5d 100644 --- a/packages/api-fetch/src/middlewares/nonce.js +++ b/packages/api-fetch/src/middlewares/nonce.js @@ -1,50 +1,27 @@ -/** - * External dependencies - */ -import { addAction } from '@wordpress/hooks'; +function createNonceMiddleware( nonce ) { + function middleware( options, next ) { + const { headers = {} } = options; -const createNonceMiddleware = ( nonce ) => { - let usedNonce = nonce; - - /** - * This is not ideal but it's fine for now. - * - * Configure heartbeat to refresh the wp-api nonce, keeping the editor - * authorization intact. - */ - addAction( 'heartbeat.tick', 'core/api-fetch/create-nonce-middleware', ( response ) => { - if ( response[ 'rest-nonce' ] ) { - usedNonce = response[ 'rest-nonce' ]; - } - } ); - - return function( options, next ) { - let headers = options.headers || {}; // If an 'X-WP-Nonce' header (or any case-insensitive variation // thereof) was specified, no need to add a nonce header. - let addNonceHeader = true; for ( const headerName in headers ) { - if ( headers.hasOwnProperty( headerName ) ) { - if ( headerName.toLowerCase() === 'x-wp-nonce' ) { - addNonceHeader = false; - break; - } + if ( headerName.toLowerCase() === 'x-wp-nonce' ) { + return next( options ); } } - if ( addNonceHeader ) { - // Do not mutate the original headers object, if any. - headers = { - ...headers, - 'X-WP-Nonce': usedNonce, - }; - } - return next( { ...options, - headers, + headers: { + ...headers, + 'X-WP-Nonce': middleware.nonce, + }, } ); - }; -}; + } + + middleware.nonce = nonce; + + return middleware; +} export default createNonceMiddleware; diff --git a/packages/api-fetch/src/middlewares/test/nonce.js b/packages/api-fetch/src/middlewares/test/nonce.js index 7c0dde16845f0..3ce40bbd55f77 100644 --- a/packages/api-fetch/src/middlewares/test/nonce.js +++ b/packages/api-fetch/src/middlewares/test/nonce.js @@ -31,6 +31,7 @@ describe( 'Nonce middleware', () => { headers: { 'X-WP-Nonce': 'existing nonce' }, }; const callback = ( options ) => { + expect( options ).toBe( requestOptions ); expect( options.headers[ 'X-WP-Nonce' ] ).toBe( 'existing nonce' ); }; From bd6158dc3605ff2dea1e5441dd5a61a0adf649cd Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 25 Jan 2019 12:41:42 -0500 Subject: [PATCH 240/691] Plugin: Deprecate gutenberg_content_block_version (#13469) --- .../developers/backward-compatibility/deprecations.md | 1 + lib/register.php | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/designers-developers/developers/backward-compatibility/deprecations.md b/docs/designers-developers/developers/backward-compatibility/deprecations.md index d422ea4def9bc..5e3b0d3b479d6 100644 --- a/docs/designers-developers/developers/backward-compatibility/deprecations.md +++ b/docs/designers-developers/developers/backward-compatibility/deprecations.md @@ -30,6 +30,7 @@ The Gutenberg project's deprecation policy is intended to support backward compa - The PHP function `gutenberg_add_edit_link` has been removed. - The PHP function `gutenberg_block_bulk_actions` has been removed. - The PHP function `gutenberg_replace_default_add_new_button` has been removed. +- The PHP function `gutenberg_content_block_version` has been removed. Use [`block_version`](https://developer.wordpress.org/reference/functions/block_version/) instead. ## 4.5.0 - `Dropdown.refresh()` has been deprecated as the contained `Popover` is now automatically refreshed. diff --git a/lib/register.php b/lib/register.php index f4b776eb309cd..dd81b63195095 100644 --- a/lib/register.php +++ b/lib/register.php @@ -355,12 +355,15 @@ function gutenberg_content_has_blocks( $content ) { * * @since 2.8.0 * @see gutenberg_content_has_blocks() + * @deprecated 5.0.0 block_version * * @param string $content Content to test. * @return int The block format version. */ function gutenberg_content_block_version( $content ) { - return has_blocks( $content ) ? 1 : 0; + _deprecated_function( __FUNCTION__, '5.0.0', 'block_version' ); + + return block_version( $content ); } /** From 7c26c89b23483b7ade087a263dded8c3590d4102 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 25 Jan 2019 13:12:48 -0500 Subject: [PATCH 241/691] Plugin: Deprecate gutenberg_get_block_categories (#13454) --- .../backward-compatibility/deprecations.md | 1 + lib/client-assets.php | 38 ++----------------- 2 files changed, 5 insertions(+), 34 deletions(-) diff --git a/docs/designers-developers/developers/backward-compatibility/deprecations.md b/docs/designers-developers/developers/backward-compatibility/deprecations.md index 5e3b0d3b479d6..40dc8f23e4c74 100644 --- a/docs/designers-developers/developers/backward-compatibility/deprecations.md +++ b/docs/designers-developers/developers/backward-compatibility/deprecations.md @@ -31,6 +31,7 @@ The Gutenberg project's deprecation policy is intended to support backward compa - The PHP function `gutenberg_block_bulk_actions` has been removed. - The PHP function `gutenberg_replace_default_add_new_button` has been removed. - The PHP function `gutenberg_content_block_version` has been removed. Use [`block_version`](https://developer.wordpress.org/reference/functions/block_version/) instead. +- The PHP function `gutenberg_get_block_categories` has been removed. Use [`get_block_categories`](https://developer.wordpress.org/reference/functions/get_block_categories/) instead. ## 4.5.0 - `Dropdown.refresh()` has been deprecated as the contained `Popover` is now automatically refreshed. diff --git a/lib/client-assets.php b/lib/client-assets.php index 3e338be2e7f2d..0169fecdbd298 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -889,45 +889,15 @@ function gutenberg_get_autosave_newer_than_post_save( $post ) { * Returns all the block categories. * * @since 2.2.0 + * @deprecated 5.0.0 get_block_categories * * @param WP_Post $post Post object. * @return Object[] Block categories. */ function gutenberg_get_block_categories( $post ) { - $default_categories = array( - array( - 'slug' => 'common', - 'title' => __( 'Common Blocks', 'gutenberg' ), - 'icon' => null, - ), - array( - 'slug' => 'formatting', - 'title' => __( 'Formatting', 'gutenberg' ), - 'icon' => null, - ), - array( - 'slug' => 'layout', - 'title' => __( 'Layout Elements', 'gutenberg' ), - 'icon' => null, - ), - array( - 'slug' => 'widgets', - 'title' => __( 'Widgets', 'gutenberg' ), - 'icon' => null, - ), - array( - 'slug' => 'embed', - 'title' => __( 'Embeds', 'gutenberg' ), - 'icon' => null, - ), - array( - 'slug' => 'reusable', - 'title' => __( 'Reusable Blocks', 'gutenberg' ), - 'icon' => null, - ), - ); + _deprecated_function( __FUNCTION__, '5.0.0', 'get_block_categories' ); - return apply_filters( 'block_categories', $default_categories, $post ); + return get_block_categories( $post ); } /** @@ -1092,7 +1062,7 @@ function gutenberg_editor_scripts_and_styles( $hook ) { wp_add_inline_script( 'wp-blocks', - sprintf( 'wp.blocks.setCategories( %s );', wp_json_encode( gutenberg_get_block_categories( $post ) ) ), + sprintf( 'wp.blocks.setCategories( %s );', wp_json_encode( get_block_categories( $post ) ) ), 'after' ); From 310cb4b952d81ff6119cb5b746de34fb12fbf2ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Wrede?= <soerenwrede@gmail.com> Date: Fri, 25 Jan 2019 19:19:57 +0100 Subject: [PATCH 242/691] Set minimum of words for RSS excerpt (#13502) --- packages/block-library/src/rss/edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/rss/edit.js b/packages/block-library/src/rss/edit.js index 8e89146527f60..a08e7fc8fe4d5 100644 --- a/packages/block-library/src/rss/edit.js +++ b/packages/block-library/src/rss/edit.js @@ -140,7 +140,7 @@ class RSSEdit extends Component { label={ __( 'Max number of words in excerpt' ) } value={ excerptLength } onChange={ ( value ) => setAttributes( { excerptLength: value } ) } - min={ 0 } + min={ 10 } max={ 100 } /> } From 896ce3893d6421123e0f222df5c3be887e74eb12 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 25 Jan 2019 13:29:16 -0500 Subject: [PATCH 243/691] Plugin: Deprecate register_tinymce_scripts (#13466) --- .../backward-compatibility/deprecations.md | 1 + lib/client-assets.php | 24 ++++--------------- 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/docs/designers-developers/developers/backward-compatibility/deprecations.md b/docs/designers-developers/developers/backward-compatibility/deprecations.md index 40dc8f23e4c74..4497cda8b2b6d 100644 --- a/docs/designers-developers/developers/backward-compatibility/deprecations.md +++ b/docs/designers-developers/developers/backward-compatibility/deprecations.md @@ -32,6 +32,7 @@ The Gutenberg project's deprecation policy is intended to support backward compa - The PHP function `gutenberg_replace_default_add_new_button` has been removed. - The PHP function `gutenberg_content_block_version` has been removed. Use [`block_version`](https://developer.wordpress.org/reference/functions/block_version/) instead. - The PHP function `gutenberg_get_block_categories` has been removed. Use [`get_block_categories`](https://developer.wordpress.org/reference/functions/get_block_categories/) instead. +- The PHP function `register_tinymce_scripts` has been removed. Use [`wp_register_tinymce_scripts`](https://developer.wordpress.org/reference/functions/wp_register_tinymce_scripts/) instead. ## 4.5.0 - `Dropdown.refresh()` has been deprecated as the contained `Popover` is now automatically refreshed. diff --git a/lib/client-assets.php b/lib/client-assets.php index 0169fecdbd298..640c5b74922b9 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -69,26 +69,14 @@ function gutenberg_get_script_polyfill( $tests ) { if ( ! function_exists( 'register_tinymce_scripts' ) ) { /** * Registers the main TinyMCE scripts. + * + * @deprecated 5.0.0 wp_register_tinymce_scripts */ function register_tinymce_scripts() { - global $tinymce_version, $concatenate_scripts, $compress_scripts; - if ( ! isset( $concatenate_scripts ) ) { - script_concat_settings(); - } - $suffix = SCRIPT_DEBUG ? '' : '.min'; - $compressed = $compress_scripts && $concatenate_scripts && isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) - && false !== stripos( $_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip' ); - // Load tinymce.js when running from /src, otherwise load wp-tinymce.js.gz (in production) or - // tinymce.min.js (when SCRIPT_DEBUG is true). - $mce_suffix = false !== strpos( get_bloginfo( 'version' ), '-src' ) ? '' : '.min'; - if ( $compressed ) { - gutenberg_override_script( 'wp-tinymce', includes_url( 'js/tinymce/' ) . 'wp-tinymce.php', array(), $tinymce_version ); - } else { - gutenberg_override_script( 'wp-tinymce-root', includes_url( 'js/tinymce/' ) . "tinymce{$mce_suffix}.js", array(), $tinymce_version ); - gutenberg_override_script( 'wp-tinymce', includes_url( 'js/tinymce/' ) . "plugins/compat3x/plugin{$suffix}.js", array( 'wp-tinymce-root' ), $tinymce_version ); - } + _deprecated_function( __FUNCTION__, '5.0.0', 'wp_register_tinymce_scripts' ); - gutenberg_override_script( 'wp-tinymce-lists', includes_url( 'js/tinymce/' ) . "plugins/lists/plugin{$suffix}.js", array( 'wp-tinymce' ), $tinymce_version ); + global $wp_scripts; + return wp_register_tinymce_scripts( $wp_scripts ); } } @@ -167,8 +155,6 @@ function gutenberg_register_packages_scripts() { function gutenberg_register_scripts_and_styles() { gutenberg_register_vendor_scripts(); - register_tinymce_scripts(); - wp_add_inline_script( 'wp-polyfill', gutenberg_get_script_polyfill( From 947ff276d83c4c214d28893b234ebca0f7177d35 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 25 Jan 2019 13:56:33 -0500 Subject: [PATCH 244/691] Plugin: Deprecate gutenberg_register_post_types (#13468) --- .../backward-compatibility/deprecations.md | 1 + lib/register.php | 94 +------------------ 2 files changed, 3 insertions(+), 92 deletions(-) diff --git a/docs/designers-developers/developers/backward-compatibility/deprecations.md b/docs/designers-developers/developers/backward-compatibility/deprecations.md index 4497cda8b2b6d..9b19dd3c8ed5c 100644 --- a/docs/designers-developers/developers/backward-compatibility/deprecations.md +++ b/docs/designers-developers/developers/backward-compatibility/deprecations.md @@ -33,6 +33,7 @@ The Gutenberg project's deprecation policy is intended to support backward compa - The PHP function `gutenberg_content_block_version` has been removed. Use [`block_version`](https://developer.wordpress.org/reference/functions/block_version/) instead. - The PHP function `gutenberg_get_block_categories` has been removed. Use [`get_block_categories`](https://developer.wordpress.org/reference/functions/get_block_categories/) instead. - The PHP function `register_tinymce_scripts` has been removed. Use [`wp_register_tinymce_scripts`](https://developer.wordpress.org/reference/functions/wp_register_tinymce_scripts/) instead. +- The PHP function `gutenberg_register_post_types` has been removed. ## 4.5.0 - `Dropdown.refresh()` has been deprecated as the contained `Popover` is now automatically refreshed. diff --git a/lib/register.php b/lib/register.php index dd81b63195095..0d6497ca6274c 100644 --- a/lib/register.php +++ b/lib/register.php @@ -386,101 +386,11 @@ function gutenberg_add_gutenberg_post_state( $post_states, $post ) { * Registers custom post types required by the Gutenberg editor. * * @since 0.10.0 + * @deprecated 5.0.0 */ function gutenberg_register_post_types() { - register_post_type( - 'wp_block', - array( - 'labels' => array( - 'name' => _x( 'Blocks', 'post type general name', 'gutenberg' ), - 'singular_name' => _x( 'Block', 'post type singular name', 'gutenberg' ), - 'menu_name' => _x( 'Blocks', 'admin menu', 'gutenberg' ), - 'name_admin_bar' => _x( 'Block', 'add new on admin bar', 'gutenberg' ), - 'add_new' => _x( 'Add New', 'Block', 'gutenberg' ), - 'add_new_item' => __( 'Add New Block', 'gutenberg' ), - 'new_item' => __( 'New Block', 'gutenberg' ), - 'edit_item' => __( 'Edit Block', 'gutenberg' ), - 'view_item' => __( 'View Block', 'gutenberg' ), - 'all_items' => __( 'All Blocks', 'gutenberg' ), - 'search_items' => __( 'Search Blocks', 'gutenberg' ), - 'not_found' => __( 'No blocks found.', 'gutenberg' ), - 'not_found_in_trash' => __( 'No blocks found in Trash.', 'gutenberg' ), - 'filter_items_list' => __( 'Filter blocks list', 'gutenberg' ), - 'items_list_navigation' => __( 'Blocks list navigation', 'gutenberg' ), - 'items_list' => __( 'Blocks list', 'gutenberg' ), - 'item_published' => __( 'Block published.', 'gutenberg' ), - 'item_published_privately' => __( 'Block published privately.', 'gutenberg' ), - 'item_reverted_to_draft' => __( 'Block reverted to draft.', 'gutenberg' ), - 'item_scheduled' => __( 'Block scheduled.', 'gutenberg' ), - 'item_updated' => __( 'Block updated.', 'gutenberg' ), - ), - 'public' => false, - 'show_ui' => true, - 'show_in_menu' => false, - 'rewrite' => false, - 'show_in_rest' => true, - 'rest_base' => 'blocks', - 'rest_controller_class' => 'WP_REST_Blocks_Controller', - 'capability_type' => 'block', - 'capabilities' => array( - 'read' => 'read_blocks', - 'create_posts' => 'create_blocks', - ), - 'map_meta_cap' => true, - 'supports' => array( - 'title', - 'editor', - ), - ) - ); - - $editor_caps = array( - 'edit_blocks', - 'edit_others_blocks', - 'publish_blocks', - 'read_private_blocks', - 'read_blocks', - 'delete_blocks', - 'delete_private_blocks', - 'delete_published_blocks', - 'delete_others_blocks', - 'edit_private_blocks', - 'edit_published_blocks', - 'create_blocks', - ); - - $caps_map = array( - 'administrator' => $editor_caps, - 'editor' => $editor_caps, - 'author' => array( - 'edit_blocks', - 'publish_blocks', - 'read_blocks', - 'delete_blocks', - 'delete_published_blocks', - 'edit_published_blocks', - 'create_blocks', - ), - 'contributor' => array( - 'read_blocks', - ), - ); - - foreach ( $caps_map as $role_name => $caps ) { - $role = get_role( $role_name ); - - if ( empty( $role ) ) { - continue; - } - - foreach ( $caps as $cap ) { - if ( ! $role->has_cap( $cap ) ) { - $role->add_cap( $cap ); - } - } - } + _deprecated_function( __FUNCTION__, '5.0.0' ); } -add_action( 'init', 'gutenberg_register_post_types' ); /** * Apply the correct labels for Reusable Blocks in the bulk action updated messages. From c8cb3c27a14d1c6ec8de49659557788aed735958 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 25 Jan 2019 14:02:20 -0500 Subject: [PATCH 245/691] Block Switcher: Render disabled button even if multi-selection (#13431) --- packages/editor/CHANGELOG.md | 4 ++ .../src/components/block-switcher/index.js | 23 ++++++--- .../test/__snapshots__/index.js.snap | 25 ++++++++++ .../components/block-switcher/test/index.js | 49 ++++++++++++------- 4 files changed, 75 insertions(+), 26 deletions(-) diff --git a/packages/editor/CHANGELOG.md b/packages/editor/CHANGELOG.md index e4cc87f80faa4..30e57fd4f033f 100644 --- a/packages/editor/CHANGELOG.md +++ b/packages/editor/CHANGELOG.md @@ -4,6 +4,10 @@ - Added `createCustomColorsHOC` for creating a higher order `withCustomColors` component. +### Bug Fixes + +- BlockSwitcher will now consistently render an icon for block multi-selections. + ### Internal - Removed `jQuery` dependency diff --git a/packages/editor/src/components/block-switcher/index.js b/packages/editor/src/components/block-switcher/index.js index adc4bf4efc142..3bd61ff38e210 100644 --- a/packages/editor/src/components/block-switcher/index.js +++ b/packages/editor/src/components/block-switcher/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { castArray, filter, first, mapKeys, orderBy } from 'lodash'; +import { castArray, filter, first, mapKeys, orderBy, uniq, map } from 'lodash'; /** * WordPress dependencies @@ -53,13 +53,20 @@ export class BlockSwitcher extends Component { 'desc' ); - const sourceBlockName = blocks[ 0 ].name; - const blockType = getBlockType( sourceBlockName ); + // When selection consists of blocks of multiple types, display an + // appropriate icon to communicate the non-uniformity. + const isSelectionOfSameType = uniq( map( blocks, 'name' ) ).length === 1; + + let icon; + if ( isSelectionOfSameType ) { + const sourceBlockName = blocks[ 0 ].name; + const blockType = getBlockType( sourceBlockName ); + icon = blockType.icon; + } else { + icon = 'layout'; + } if ( ! hasBlockStyles && ! possibleBlockTransformations.length ) { - if ( blocks.length > 1 ) { - return null; - } return ( <Toolbar> <IconButton @@ -67,7 +74,7 @@ export class BlockSwitcher extends Component { className="editor-block-switcher__no-switcher-icon" label={ __( 'Block icon' ) } > - <BlockIcon icon={ blockType.icon } showColors /> + <BlockIcon icon={ icon } showColors /> </IconButton> </Toolbar> ); @@ -110,7 +117,7 @@ export class BlockSwitcher extends Component { tooltip={ label } onKeyDown={ openOnArrowDown } > - <BlockIcon icon={ blockType.icon } showColors /> + <BlockIcon icon={ icon } showColors /> <SVG className="editor-block-switcher__transform" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><Path d="M6.5 8.9c.6-.6 1.4-.9 2.2-.9h6.9l-1.3 1.3 1.4 1.4L19.4 7l-3.7-3.7-1.4 1.4L15.6 6H8.7c-1.4 0-2.6.5-3.6 1.5l-2.8 2.8 1.4 1.4 2.8-2.8zm13.8 2.4l-2.8 2.8c-.6.6-1.3.9-2.1.9h-7l1.3-1.3-1.4-1.4L4.6 16l3.7 3.7 1.4-1.4L8.4 17h6.9c1.3 0 2.6-.5 3.5-1.5l2.8-2.8-1.3-1.4z" /></SVG> </IconButton> </Toolbar> diff --git a/packages/editor/src/components/block-switcher/test/__snapshots__/index.js.snap b/packages/editor/src/components/block-switcher/test/__snapshots__/index.js.snap index f8a340a0480ad..9e03c4e31fb0d 100644 --- a/packages/editor/src/components/block-switcher/test/__snapshots__/index.js.snap +++ b/packages/editor/src/components/block-switcher/test/__snapshots__/index.js.snap @@ -1,5 +1,30 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`BlockSwitcher should render disabled block switcher with multi block of different types when no transforms 1`] = ` +<Toolbar> + <IconButton + className="editor-block-switcher__no-switcher-icon" + disabled={true} + label="Block icon" + > + <BlockIcon + icon="layout" + showColors={true} + /> + </IconButton> +</Toolbar> +`; + +exports[`BlockSwitcher should render enabled block switcher with multi block when transforms exist 1`] = ` +<Dropdown + className="editor-block-switcher" + contentClassName="editor-block-switcher__popover" + position="bottom right" + renderContent={[Function]} + renderToggle={[Function]} +/> +`; + exports[`BlockSwitcher should render switcher with blocks 1`] = ` <Dropdown className="editor-block-switcher" diff --git a/packages/editor/src/components/block-switcher/test/index.js b/packages/editor/src/components/block-switcher/test/index.js index 26a71eeb4a213..47f784240db2b 100644 --- a/packages/editor/src/components/block-switcher/test/index.js +++ b/packages/editor/src/components/block-switcher/test/index.js @@ -42,7 +42,7 @@ describe( 'BlockSwitcher', () => { level: 3, }, isValid: true, - name: 'core/paragraph', + name: 'core/heading', originalContent: '<h3>I am the greatest!</h3>', clientId: 'c2403fd2-4e63-5ffa-b71c-1e0ea656c5b0', }; @@ -54,11 +54,19 @@ describe( 'BlockSwitcher', () => { edit: () => { }, save: () => {}, transforms: { - to: [ { - type: 'block', - blocks: [ 'core/paragraph' ], - transform: () => {}, - } ], + to: [ + { + type: 'block', + blocks: [ 'core/paragraph' ], + transform: () => {}, + }, + { + type: 'block', + blocks: [ 'core/paragraph' ], + transform: () => {}, + isMultiBlock: true, + }, + ], }, } ); @@ -93,6 +101,7 @@ describe( 'BlockSwitcher', () => { headingBlock1, ]; const inserterItems = [ + { name: 'core/heading', frecency: 1 }, { name: 'core/paragraph', frecency: 1 }, ]; @@ -101,24 +110,28 @@ describe( 'BlockSwitcher', () => { expect( wrapper ).toMatchSnapshot(); } ); - test( 'should not render block switcher with multi block of different types.', () => { - const blocks = [ - headingBlock1, - textBlock, + test( 'should render disabled block switcher with multi block of different types when no transforms', () => { + const blocks = [ headingBlock1, textBlock ]; + const inserterItems = [ + { name: 'core/heading', frecency: 1 }, + { name: 'core/paragraph', frecency: 1 }, ]; - const wrapper = shallow( <BlockSwitcher blocks={ blocks } /> ); - expect( wrapper.html() ).toBeNull(); + const wrapper = shallow( <BlockSwitcher blocks={ blocks } inserterItems={ inserterItems } /> ); + + expect( wrapper ).toMatchSnapshot(); } ); - test( 'should not render a component when the multi selected types of blocks match.', () => { - const blocks = [ - headingBlock1, - headingBlock2, + test( 'should render enabled block switcher with multi block when transforms exist', () => { + const blocks = [ headingBlock1, headingBlock2 ]; + const inserterItems = [ + { name: 'core/heading', frecency: 1 }, + { name: 'core/paragraph', frecency: 1 }, ]; - const wrapper = shallow( <BlockSwitcher blocks={ blocks } /> ); - expect( wrapper.html() ).toBeNull(); + const wrapper = shallow( <BlockSwitcher blocks={ blocks } inserterItems={ inserterItems } /> ); + + expect( wrapper ).toMatchSnapshot(); } ); describe( 'Dropdown', () => { From f7ade90fb6c7a616ae6c58e4d9907f55c440dc6a Mon Sep 17 00:00:00 2001 From: Paul Sealock <psealock@gmail.com> Date: Sat, 26 Jan 2019 08:26:40 +1300 Subject: [PATCH 246/691] Datepicker: Add inValidDay support (#12962) --- packages/components/CHANGELOG.md | 1 + packages/components/src/date-time/README.md | 7 +++++++ packages/components/src/date-time/date.js | 5 ++++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 1ec2b397bcea6..427e5aa90759a 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -12,6 +12,7 @@ ### New Feature - `Dropdown` now has a `focusOnMount` prop which is passed directly to the contained `Popover`. +- `DatePicker` has new prop `isInvalidDate` exposing react-dates' `isOutsideRange`. ## 7.0.5 (2019-01-03) diff --git a/packages/components/src/date-time/README.md b/packages/components/src/date-time/README.md index c70c8199952b3..1366be8312635 100644 --- a/packages/components/src/date-time/README.md +++ b/packages/components/src/date-time/README.md @@ -60,3 +60,10 @@ Whether we use a 12-hour clock. With a 12-hour clock, an AM/PM widget is display - Type: `bool` - Required: No + +### isInvalidDate + +A callback function which receives a Date object representing a day as an argument, and should return a Boolean to signify if the day is valid or not. + +- Type: `Function` +- Required: No diff --git a/packages/components/src/date-time/date.js b/packages/components/src/date-time/date.js index 7da1bf4f41823..92be3a3970a90 100644 --- a/packages/components/src/date-time/date.js +++ b/packages/components/src/date-time/date.js @@ -36,7 +36,7 @@ class DatePicker extends Component { } render() { - const { currentDate } = this.props; + const { currentDate, isInvalidDate } = this.props; const momentDate = currentDate ? moment( currentDate ) : moment(); @@ -56,6 +56,9 @@ class DatePicker extends Component { transitionDuration={ 0 } weekDayFormat="ddd" isRTL={ isRTL() } + isOutsideRange={ ( date ) => { + return isInvalidDate && isInvalidDate( date.toDate() ); + } } /> </div> ); From 43a20692fd446181643273f2c0790a29c0c88d00 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 25 Jan 2019 14:28:53 -0500 Subject: [PATCH 247/691] Plugin: Deprecate `gutenberg` theme support (#13458) --- .../developers/backward-compatibility/deprecations.md | 1 + lib/client-assets.php | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/docs/designers-developers/developers/backward-compatibility/deprecations.md b/docs/designers-developers/developers/backward-compatibility/deprecations.md index 9b19dd3c8ed5c..12b1771484923 100644 --- a/docs/designers-developers/developers/backward-compatibility/deprecations.md +++ b/docs/designers-developers/developers/backward-compatibility/deprecations.md @@ -34,6 +34,7 @@ The Gutenberg project's deprecation policy is intended to support backward compa - The PHP function `gutenberg_get_block_categories` has been removed. Use [`get_block_categories`](https://developer.wordpress.org/reference/functions/get_block_categories/) instead. - The PHP function `register_tinymce_scripts` has been removed. Use [`wp_register_tinymce_scripts`](https://developer.wordpress.org/reference/functions/wp_register_tinymce_scripts/) instead. - The PHP function `gutenberg_register_post_types` has been removed. +- The `gutenberg` theme support option has been removed. Use [`align-wide`](https://wordpress.org/gutenberg/handbook/designers-developers/developers/themes/theme-support/#wide-alignment) instead. ## 4.5.0 - `Dropdown.refresh()` has been deprecated as the contained `Popover` is now automatically refreshed. diff --git a/lib/client-assets.php b/lib/client-assets.php index 640c5b74922b9..f072ee348402a 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -1103,6 +1103,11 @@ function gutenberg_editor_scripts_and_styles( $hook ) { $color_palette = current( (array) get_theme_support( 'editor-color-palette' ) ); $font_sizes = current( (array) get_theme_support( 'editor-font-sizes' ) ); + if ( ! empty( $gutenberg_theme_support ) ) { + wp_enqueue_script( 'wp-deprecated' ); + wp_add_inline_script( 'wp-deprecated', 'wp.deprecated( "`gutenberg` theme support", { plugin: "Gutenberg", version: "5.2", alternative: "`align-wide` theme support" } );' ); + } + /** * Filters the allowed block types for the editor, defaulting to true (all * block types supported). From 17481452f981effd758751c08250f2b819c00042 Mon Sep 17 00:00:00 2001 From: Karol Gorski <naerriel@gmail.com> Date: Fri, 25 Jan 2019 20:42:53 +0100 Subject: [PATCH 248/691] Add watcher on Linux: change fs to node-watch (#13448) * Add watcher on Linux: change fs to node-watch * Add watcher on Linux: Change fs to node-watch (pt.2.) * Add watcher on Linux: Change fs to node-watch (pt.3.) * Framework: Regenerate package-lock.json --- bin/packages/watch.js | 5 +++-- package-lock.json | 6 ++++++ package.json | 1 + 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/bin/packages/watch.js b/bin/packages/watch.js index d12d5410f2b7f..fce8a6beedb97 100644 --- a/bin/packages/watch.js +++ b/bin/packages/watch.js @@ -2,6 +2,7 @@ * External dependencies */ const fs = require( 'fs' ); +const watch = require( 'node-watch' ); const { execSync } = require( 'child_process' ); const path = require( 'path' ); const chalk = require( 'chalk' ); @@ -33,13 +34,13 @@ getPackages().forEach( ( p ) => { const srcDir = path.resolve( p, 'src' ); try { fs.accessSync( srcDir, fs.F_OK ); - fs.watch( path.resolve( p, 'src' ), { recursive: true }, ( event, filename ) => { + watch( path.resolve( p, 'src' ), { recursive: true }, ( event, filename ) => { if ( ! isSourceFile( filename ) ) { return; } const filePath = path.resolve( srcDir, filename ); - if ( ( event === 'change' || event === 'rename' ) && exists( filePath ) ) { + if ( ( event === 'update' ) && exists( filePath ) ) { // eslint-disable-next-line no-console console.log( chalk.green( '->' ), `${ event }: ${ filename }` ); rebuild( filePath ); diff --git a/package-lock.json b/package-lock.json index ca8a4feba01f5..30246ca516f38 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13819,6 +13819,12 @@ } } }, + "node-watch": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/node-watch/-/node-watch-0.6.0.tgz", + "integrity": "sha512-XAgTL05z75ptd7JSVejH1a2Dm1zmXYhuDr9l230Qk6Z7/7GPcnAs/UyJJ4ggsXSvWil8iOzwQLW0zuGUvHpG8g==", + "dev": true + }, "nomnom": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.6.2.tgz", diff --git a/package.json b/package.json index ca66510ec46b6..c59a6c200a189 100644 --- a/package.json +++ b/package.json @@ -97,6 +97,7 @@ "lodash": "4.17.10", "mkdirp": "0.5.1", "node-sass": "4.11.0", + "node-watch": "0.6.0", "pegjs": "0.10.0", "phpegjs": "1.0.0-beta7", "react-dom": "16.6.3", From 59ebd4f6ac7530cd9f51ccd99a87c5b8e941c840 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 25 Jan 2019 16:33:24 -0500 Subject: [PATCH 249/691] Plugin: Deprecate gutenberg_prepare_blocks_for_js (#13457) --- .../backward-compatibility/deprecations.md | 1 + lib/client-assets.php | 24 +++---------- phpunit/class-prepare-for-js-test.php | 34 ------------------- 3 files changed, 6 insertions(+), 53 deletions(-) delete mode 100644 phpunit/class-prepare-for-js-test.php diff --git a/docs/designers-developers/developers/backward-compatibility/deprecations.md b/docs/designers-developers/developers/backward-compatibility/deprecations.md index 12b1771484923..b5df2462e1050 100644 --- a/docs/designers-developers/developers/backward-compatibility/deprecations.md +++ b/docs/designers-developers/developers/backward-compatibility/deprecations.md @@ -35,6 +35,7 @@ The Gutenberg project's deprecation policy is intended to support backward compa - The PHP function `register_tinymce_scripts` has been removed. Use [`wp_register_tinymce_scripts`](https://developer.wordpress.org/reference/functions/wp_register_tinymce_scripts/) instead. - The PHP function `gutenberg_register_post_types` has been removed. - The `gutenberg` theme support option has been removed. Use [`align-wide`](https://wordpress.org/gutenberg/handbook/designers-developers/developers/themes/theme-support/#wide-alignment) instead. +- The PHP function `gutenberg_prepare_blocks_for_js` has been removed. Use [`get_block_editor_server_block_settings`](https://developer.wordpress.org/reference/functions/get_block_editor_server_block_settings/) instead. ## 4.5.0 - `Dropdown.refresh()` has been deprecated as the contained `Popover` is now automatically refreshed. diff --git a/lib/client-assets.php b/lib/client-assets.php index f072ee348402a..d33ebdd12fe93 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -702,28 +702,14 @@ function gutenberg_register_vendor_script( $handle, $src, $deps = array() ) { * array of registered block data keyed by block name. Data includes properties * of a block relevant for client registration. * + * @deprecated 5.0.0 get_block_editor_server_block_settings + * * @return array An associative array of registered block data. */ function gutenberg_prepare_blocks_for_js() { - $block_registry = WP_Block_Type_Registry::get_instance(); - $blocks = array(); - $keys_to_pick = array( 'title', 'description', 'icon', 'category', 'keywords', 'supports', 'attributes' ); - - foreach ( $block_registry->get_all_registered() as $block_name => $block_type ) { - foreach ( $keys_to_pick as $key ) { - if ( ! isset( $block_type->{ $key } ) ) { - continue; - } - - if ( ! isset( $blocks[ $block_name ] ) ) { - $blocks[ $block_name ] = array(); - } - - $blocks[ $block_name ][ $key ] = $block_type->{ $key }; - } - } + _deprecated_function( __FUNCTION__, '5.0.0', 'get_block_editor_server_block_settings' ); - return $blocks; + return get_block_editor_server_block_settings(); } /** @@ -1081,7 +1067,7 @@ function gutenberg_editor_scripts_and_styles( $hook ) { // Preload server-registered block schemas. wp_add_inline_script( 'wp-blocks', - 'wp.blocks.unstable__bootstrapServerSideBlockDefinitions(' . json_encode( gutenberg_prepare_blocks_for_js() ) . ');' + 'wp.blocks.unstable__bootstrapServerSideBlockDefinitions(' . json_encode( get_block_editor_server_block_settings() ) . ');' ); // Get admin url for handling meta boxes. diff --git a/phpunit/class-prepare-for-js-test.php b/phpunit/class-prepare-for-js-test.php deleted file mode 100644 index 38dceb8fec6df..0000000000000 --- a/phpunit/class-prepare-for-js-test.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -/** - * Block types registration Tests - * - * @package Gutenberg - */ - -/** - * Test gutenberg_prepare_blocks_for_js() - */ -class Prepare_For_JS_Test extends WP_UnitTestCase { - - function tearDown() { - parent::tearDown(); - - $registry = WP_Block_Type_Registry::get_instance(); - $registry->unregister( 'core/dummy' ); - } - - function test_gutenberg_prepare_blocks_for_js() { - $name = 'core/dummy'; - $settings = array( - 'icon' => 'text', - 'render_callback' => 'foo', - ); - - register_block_type( $name, $settings ); - - $blocks = gutenberg_prepare_blocks_for_js(); - - $this->assertArrayHasKey( $name, $blocks ); - $this->assertSame( array( 'icon' => 'text' ), $blocks[ $name ] ); - } -} From a4744388b2dd7d55c7a24d8336740539d1c192c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Wrede?= <soerenwrede@gmail.com> Date: Fri, 25 Jan 2019 23:03:49 +0100 Subject: [PATCH 250/691] Amazon Kindle block (#13510) --- packages/block-library/src/embed/core-embeds.js | 15 +++++++++++++++ packages/block-library/src/embed/icons.js | 1 + 2 files changed, 16 insertions(+) diff --git a/packages/block-library/src/embed/core-embeds.js b/packages/block-library/src/embed/core-embeds.js index d14c76cadff2a..9fe6c2b335cd8 100644 --- a/packages/block-library/src/embed/core-embeds.js +++ b/packages/block-library/src/embed/core-embeds.js @@ -16,6 +16,7 @@ import { embedVimeoIcon, embedRedditIcon, embedTumbrIcon, + embedAmazonIcon, } from './icons'; /** @@ -338,4 +339,18 @@ export const others = [ }, patterns: [ /^https?:\/\/wordpress\.tv\/.+/i ], }, + { + name: 'core-embed/amazon-kindle', + settings: { + title: 'Amazon Kindle', + icon: embedAmazonIcon, + keywords: [ __( 'ebook' ) ], + responsive: false, + description: __( 'Embed Amazon Kindle content.' ), + }, + patterns: [ + /^https?:\/\/([a-z0-9-]+\.)?(amazon|amzn)(\.[a-z]{2,4})+\/.+/i, + /^https?:\/\/(www\.)?(a\.co|z\.cn)\/.+/i, + ], + }, ]; diff --git a/packages/block-library/src/embed/icons.js b/packages/block-library/src/embed/icons.js index 3fd80cff5ade9..d7ebe434d8fdb 100644 --- a/packages/block-library/src/embed/icons.js +++ b/packages/block-library/src/embed/icons.js @@ -43,3 +43,4 @@ export const embedTumbrIcon = { foreground: '#35465c', src: <SVG viewBox="0 0 24 24"><Path d="M19 3H5c-1.105 0-2 .895-2 2v14c0 1.105.895 2 2 2h14c1.105 0 2-.895 2-2V5c0-1.105-.895-2-2-2zm-5.57 14.265c-2.445.042-3.37-1.742-3.37-2.998V10.6H8.922V9.15c1.703-.615 2.113-2.15 2.21-3.026.006-.06.053-.084.08-.084h1.645V8.9h2.246v1.7H12.85v3.495c.008.476.182 1.13 1.08 1.107.3-.008.698-.094.907-.194l.54 1.6c-.205.297-1.12.642-1.946.657z" /></SVG>, }; +export const embedAmazonIcon = <SVG viewBox="0 0 24 24"><Path d="M18.42 14.58c-.51-.66-1.05-1.23-1.05-2.5V7.87c0-1.8.15-3.45-1.2-4.68-1.05-1.02-2.79-1.35-4.14-1.35-2.6 0-5.52.96-6.12 4.14-.06.36.18.54.4.57l2.66.3c.24-.03.42-.27.48-.5.24-1.12 1.17-1.63 2.2-1.63.56 0 1.22.21 1.55.7.4.56.33 1.31.33 1.97v.36c-1.59.18-3.66.27-5.16.93a4.63 4.63 0 0 0-2.93 4.44c0 2.82 1.8 4.23 4.1 4.23 1.95 0 3.03-.45 4.53-1.98.51.72.66 1.08 1.59 1.83.18.09.45.09.63-.1v.04l2.1-1.8c.24-.21.2-.48.03-.75zm-5.4-1.2c-.45.75-1.14 1.23-1.92 1.23-1.05 0-1.65-.81-1.65-1.98 0-2.31 2.1-2.73 4.08-2.73v.6c0 1.05.03 1.92-.5 2.88z" /><Path d="M21.69 19.2a17.62 17.62 0 0 1-21.6-1.57c-.23-.2 0-.5.28-.33a23.88 23.88 0 0 0 20.93 1.3c.45-.19.84.3.39.6z" /><Path d="M22.8 17.96c-.36-.45-2.22-.2-3.1-.12-.23.03-.3-.18-.05-.36 1.5-1.05 3.96-.75 4.26-.39.3.36-.1 2.82-1.5 4.02-.21.18-.42.1-.3-.15.3-.8 1.02-2.58.69-3z" /></SVG>; From c89de50708581987af0d432bb4cd1939f3f80273 Mon Sep 17 00:00:00 2001 From: Marty Helmick <info@martyhelmick.com> Date: Fri, 25 Jan 2019 17:31:30 -0500 Subject: [PATCH 251/691] Include :visited links in button color (#12183) I noticed the underscores theme's `:visited` link color was overriding our button color. I also made the outline button's transparent background specific to the background-color so it wouldn't overwrite other background properties. For example a `background-image: linear-gradient()` on hover. --- packages/block-library/src/button/style.scss | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/button/style.scss b/packages/block-library/src/button/style.scss index 2a45e2e574b38..1519c6a11ddce 100644 --- a/packages/block-library/src/button/style.scss +++ b/packages/block-library/src/button/style.scss @@ -32,7 +32,8 @@ $blocks-button__height: 56px; &:hover, &:focus, - &:active { + &:active, + &:visited { color: inherit; } } @@ -45,7 +46,7 @@ $blocks-button__height: 56px; color: $dark-gray-700; .wp-block-button__link { - background: transparent; + background-color: transparent; border: 2px solid currentcolor; } } From edc97d9826840d5f78e5b2c89d758a6e148ca2cc Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell@kjellr.com> Date: Fri, 25 Jan 2019 17:36:02 -0500 Subject: [PATCH 252/691] Replace the fullscreen "exit" icon with a back arrow (#13403) As per the notes in #10813, this PR updates the full screen "Exit" icon with a "Back" icon and label instead. This icon is more common across the web, and will thus be a bit less confusing for users. Note: The original issue suggested using a Material icon, but I've just swapped this out with the equivalent Dashicon for simplicity in implementtion. If someone knows how to swap that out with a SVG, go for it. :thumbsup: --- .../src/components/header/fullscreen-mode-close/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/edit-post/src/components/header/fullscreen-mode-close/index.js b/packages/edit-post/src/components/header/fullscreen-mode-close/index.js index 00aa63134d1b7..2cbde749470f6 100644 --- a/packages/edit-post/src/components/header/fullscreen-mode-close/index.js +++ b/packages/edit-post/src/components/header/fullscreen-mode-close/index.js @@ -19,12 +19,12 @@ function FullscreenModeClose( { isActive, postType } ) { return ( <Toolbar className="edit-post-fullscreen-mode-close__toolbar"> <IconButton - icon="exit" + icon="arrow-left-alt2" href={ addQueryArgs( 'edit.php', { post_type: postType.slug } ) } label={ get( postType, [ 'labels', 'view_items' ], - __( 'View Posts' ) + __( 'Back' ) ) } /> </Toolbar> From 033e6640c1f6ab013af73ed679bfa32a6cfed5a0 Mon Sep 17 00:00:00 2001 From: Brent Swisher <brent@brentswisher.com> Date: Fri, 25 Jan 2019 17:42:43 -0500 Subject: [PATCH 253/691] Update the columns attribute in onSelectImages so that if images are removed via the media modal, the columns can't be higher than the new number of images (#13488) --- packages/block-library/src/gallery/edit.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/block-library/src/gallery/edit.js b/packages/block-library/src/gallery/edit.js index 4415a3dc563b3..5a9b04ce2e944 100644 --- a/packages/block-library/src/gallery/edit.js +++ b/packages/block-library/src/gallery/edit.js @@ -109,8 +109,10 @@ class GalleryEdit extends Component { } onSelectImages( images ) { + const { columns } = this.props.attributes; this.setAttributes( { images: images.map( ( image ) => pickRelevantMediaFiles( image ) ), + columns: columns ? Math.min( images.length, columns ) : columns, } ); } From 654403517d52b8cd053ba41dad6012bd8767d3ba Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 25 Jan 2019 23:53:45 -0500 Subject: [PATCH 254/691] Plugin: Deprecate gutenberg_load_list_reusable_blocks (#13456) --- .../backward-compatibility/deprecations.md | 1 + lib/client-assets.php | 12 +++--------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/docs/designers-developers/developers/backward-compatibility/deprecations.md b/docs/designers-developers/developers/backward-compatibility/deprecations.md index b5df2462e1050..03608d2634ff5 100644 --- a/docs/designers-developers/developers/backward-compatibility/deprecations.md +++ b/docs/designers-developers/developers/backward-compatibility/deprecations.md @@ -36,6 +36,7 @@ The Gutenberg project's deprecation policy is intended to support backward compa - The PHP function `gutenberg_register_post_types` has been removed. - The `gutenberg` theme support option has been removed. Use [`align-wide`](https://wordpress.org/gutenberg/handbook/designers-developers/developers/themes/theme-support/#wide-alignment) instead. - The PHP function `gutenberg_prepare_blocks_for_js` has been removed. Use [`get_block_editor_server_block_settings`](https://developer.wordpress.org/reference/functions/get_block_editor_server_block_settings/) instead. +- The PHP function `gutenberg_load_list_reusable_blocks` has been removed. ## 4.5.0 - `Dropdown.refresh()` has been deprecated as the contained `Popover` is now automatically refreshed. diff --git a/lib/client-assets.php b/lib/client-assets.php index d33ebdd12fe93..15ffc18d86516 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -1325,14 +1325,8 @@ function gutenberg_editor_scripts_and_styles( $hook ) { /** * Enqueue the reusable blocks listing page's script * - * @param string $hook Screen name. + * @deprecated 5.0.0 */ -function gutenberg_load_list_reusable_blocks( $hook ) { - $is_reusable_blocks_list_page = 'edit.php' === $hook && isset( $_GET['post_type'] ) && 'wp_block' === $_GET['post_type']; - if ( $is_reusable_blocks_list_page ) { - gutenberg_load_locale_data(); - wp_enqueue_script( 'wp-list-reusable-blocks' ); - wp_enqueue_style( 'wp-list-reusable-blocks' ); - } +function gutenberg_load_list_reusable_blocks() { + _deprecated_function( __FUNCTION__, '5.0.0' ); } -add_action( 'admin_enqueue_scripts', 'gutenberg_load_list_reusable_blocks' ); From 096c12c999d6beb45d5162a0ed8e66c075d3cbbd Mon Sep 17 00:00:00 2001 From: Dave Whitley <drw158@gmail.com> Date: Sat, 26 Jan 2019 01:22:10 -0600 Subject: [PATCH 255/691] Update for RangeControl documentation (#12564) * Update for RangeControl documentation This adds design documentation to RangeControl and also edits one sentence in the developer documentation to make it more generic. * Typos and small adjustments * Moving types higher in the doc * Updates slider language I removed "range slider" language so that it's not confused with the types of sliders. I also inserted "RangeControl" when referring to the whole component. "Slider" is used when casually referring to the thumb+track portion of the component. --- .../components/src/range-control/README.md | 115 +++++++++++++++--- 1 file changed, 101 insertions(+), 14 deletions(-) diff --git a/packages/components/src/range-control/README.md b/packages/components/src/range-control/README.md index b35557b867a07..28fcae0c82c95 100644 --- a/packages/components/src/range-control/README.md +++ b/packages/components/src/range-control/README.md @@ -1,17 +1,101 @@ # RangeControl -RangeControl component is used to create range slider to input numerical values. +RangeControls are used to make selections from a range of incremental values. +![](https://make.wordpress.org/design/files/2018/12/rangecontrol.png) -## Usage +A RangeControl for volume + +## Table of contents + +1. [Design guidelines](#design-guidelines) +2. [Development guidelines](#development-guidelines) +3. [Related components](#related-components) + +## Design guidelines + +### Anatomy + +![](https://make.wordpress.org/design/files/2018/12/rangecontrol-anatomy.png) + +A RangeControl can contain the following elements: + +1. **Track**: The track shows the range available for user selection. For left-to-right (LTR) languages, the smallest value appears on the far left, and the largest value on the far right. For right-to-left (RTL) languages this orientation is reversed, with the smallest value on the far right and the largest value on the far left. +2. **Thumb**: The thumb slides along the track, displaying the selected value through its position. +3. **Value entry field** (optional): The value entry field displays the currently selected, specific numerical value. +4. **Icon** (optional): An icon can be displayed before or after the slider. +5. **Tick mark** (optional): Tick marks represent predetermined values to which the user can move the slider. + +### Types + +#### Continuous sliders + +Continuous sliders allow users to select a value along a subjective range. They do not display the selected numeric value. Use them when displaying/editing the numeric value is not important, like volume. + +#### Discrete sliders + +Discrete sliders can be adjusted to a specific value by referencing its value entry field. Use them when it’s important to display/edit the numeric value, like text size. + +Possible selections may be organized through the use of tick marks, which a thumb will snap to (or to which an input will round up or down). + +### Behavior + +- **Click and drag**: The slider is controlled by clicking the thumb and dragging it. +- **Click jump**: The slider is controlled by clicking the track. +- **Click and arrow**: The slider is controlled by clicking the thumb, then using arrow keys to move it. +- **Tab and arrow**: The slider is controlled by using the tab key to select the thumb of the desired slider, then using arrow keys to move it. +- **Tick marks** (Optional) Discrete sliders can use evenly spaced tick marks along the slider track, and the thumb will snap to them. Each tick mark should change the setting in increments that are discernible to the user. +- **Value entry field** (Optional): Discrete sliders have value entry fields. After a text entry is made, the slider position automatically updates to reflect the new value. + +### Usage + +RangeControls reflect a range of values along a track, from which users may select a single value. They are ideal for adjusting settings such as volume, opacity, or text size. + +RangeControls can have icons on both ends of the track that reflect a range of values. + +#### Immediate effects + +Changes made with RangeControls are immediate, allowing a user to make adjustments until finding their preference. They shouldn’t be paired with settings that have delays in providing feedback. + +![A RangeControl that requires a save action](https://make.wordpress.org/design/files/2018/12/rangecontrol-save-action.png) + +**Don’t** +Don’t use RangeControls if the effect isn’t immediate. + +#### Current state + +RangeControls reflect the current state of the settings they control. + +#### Values + +![](https://make.wordpress.org/design/files/2018/12/rangecontrol-field.png) + +A RangeControl with an editable numeric value + +**Editable numeric values**: Editable numeric values allow users to set the exact value of a RangeControl. After setting a value, the thumb position is immediately updated to match the new value. + +![A RangeControl with only two values](https://make.wordpress.org/design/files/2018/12/rangecontrol-2-values.png) + +**Don’t** +RangeControls should only be used for choosing selections from a range of values (e.g., don’t use a RangeControl if there are only 2 values). + +![](https://make.wordpress.org/design/files/2018/12/rangecontrol-disabled.png) + +**Don’t** +RangeControls should provide the full range of choices available for the user to select from (e.g., don’t disable only part of a RangeControl). + +## Development guidelines + +### Usage + +Render a RangeControl to make a selection from a range of incremental values. -Render a user interface to select the number of columns between 2 and 10. ```jsx import { RangeControl } from '@wordpress/components'; import { withState } from '@wordpress/compose'; const MyRangeControl = withState( { - columns: 2, + columns: 2, } )( ( { columns, setState } ) => ( <RangeControl label="Columns" @@ -23,65 +107,68 @@ const MyRangeControl = withState( { ) ); ``` -## Props +### Props The set of props accepted by the component will be specified below. Props not included in this set will be applied to the input elements. -### label +#### label If this property is added, a label will be generated using label property as the content. - Type: `String` - Required: No -### help +#### help If this property is added, a help text will be generated using help property as the content. - Type: `String` - Required: No - -### beforeIcon +#### beforeIcon If this property is added, a DashIcon component will be rendered before the slider with the icon equal to beforeIcon - Type: `String` - Required: No -### afterIcon +#### afterIcon If this property is added, a DashIcon component will be rendered after the slider with the icon equal to afterIcon - Type: `String` - Required: No -### allowReset +#### allowReset If this property is true, a button to reset the the slider is rendered. - Type: `Boolean` - Required: No -### initialPosition +#### initialPosition If no value exists this prop contains the slider starting position. - Type: `Number` - Required: No -### value +#### value The current value of the range slider. - Type: `Number` - Required: Yes -### onChange +#### onChange A function that receives the new value. If allowReset is true, when onChange is called without any parameter passed it should reset the value. - Type: `function` - Required: Yes + +## Related components + +- To collect a numerical input in a text field, use the `TextControl` component. From acb42fac47544c350f05a92db8b6fc428141b7c4 Mon Sep 17 00:00:00 2001 From: Danilo Ercoli <ercoli@gmail.com> Date: Sat, 26 Jan 2019 12:14:30 +0100 Subject: [PATCH 256/691] [RMmobile] Add Enter.key detection to the Title block (#13500) * Intercept Enter.key and give the ability to add a new block after * Remove the custom mobile code, and use the default web implementation --- .../editor/src/components/post-title/index.native.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/editor/src/components/post-title/index.native.js b/packages/editor/src/components/post-title/index.native.js index 61148e0f4e7a6..1ce2384db72ea 100644 --- a/packages/editor/src/components/post-title/index.native.js +++ b/packages/editor/src/components/post-title/index.native.js @@ -55,8 +55,9 @@ class PostTitle extends Component { <TextInput blurOnSubmit={ true } textAlignVertical="top" - multiline - numberOfLines={ 0 } + multiline={ false } + onSubmitEditing={ this.props.onEnterPress } + returnKeyType={ 'next' } onChangeText={ this.onChange } onFocus={ this.onSelect } placeholder={ decodedPlaceholder } @@ -69,10 +70,14 @@ class PostTitle extends Component { const applyWithDispatch = withDispatch( ( dispatch ) => { const { + insertDefaultBlock, clearSelectedBlock, } = dispatch( 'core/editor' ); return { + onEnterPress() { + insertDefaultBlock( undefined, undefined, 0 ); + }, clearSelectedBlock, }; } ); From a5e069b7c351ce878b35bbc95f78d36a1b812a7d Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Mon, 28 Jan 2019 08:18:37 -0500 Subject: [PATCH 257/691] Plugin: Deprecate gutenberg_add_gutenberg_post_state (#13471) --- .../backward-compatibility/deprecations.md | 1 + lib/register.php | 14 ++++++-------- phpunit/class-admin-test.php | 15 --------------- 3 files changed, 7 insertions(+), 23 deletions(-) diff --git a/docs/designers-developers/developers/backward-compatibility/deprecations.md b/docs/designers-developers/developers/backward-compatibility/deprecations.md index 03608d2634ff5..699dd190cd885 100644 --- a/docs/designers-developers/developers/backward-compatibility/deprecations.md +++ b/docs/designers-developers/developers/backward-compatibility/deprecations.md @@ -23,6 +23,7 @@ The Gutenberg project's deprecation policy is intended to support backward compa - The PHP function `gutenberg_filter_post_type_labels` has been removed. - The PHP function `gutenberg_preload_api_request` has been removed. Use [`rest_preload_api_request`](https://developer.wordpress.org/reference/functions/rest_preload_api_request/) instead. - The PHP function `gutenberg_remove_wpcom_markdown_support` has been removed. +- The PHP function `gutenberg_add_gutenberg_post_state` has been removed. - The PHP function `gutenberg_bulk_post_updated_messages` has been removed. - The PHP function `gutenberg_kses_allowedtags` has been removed. - The PHP function `gutenberg_add_responsive_body_class` has been removed. diff --git a/lib/register.php b/lib/register.php index 0d6497ca6274c..17694855511f8 100644 --- a/lib/register.php +++ b/lib/register.php @@ -369,18 +369,16 @@ function gutenberg_content_block_version( $content ) { /** * Adds a "Gutenberg" post state for post tables, if the post contains blocks. * - * @param array $post_states An array of post display states. - * @param WP_Post $post The current post object. - * @return array A filtered array of post display states. + * @deprecated 5.0.0 + * + * @param array $post_states An array of post display states. + * @return array A filtered array of post display states. */ -function gutenberg_add_gutenberg_post_state( $post_states, $post ) { - if ( has_blocks( $post ) ) { - $post_states[] = 'Gutenberg'; - } +function gutenberg_add_gutenberg_post_state( $post_states ) { + _deprecated_function( __FUNCTION__, '5.0.0' ); return $post_states; } -add_filter( 'display_post_states', 'gutenberg_add_gutenberg_post_state', 10, 2 ); /** * Registers custom post types required by the Gutenberg editor. diff --git a/phpunit/class-admin-test.php b/phpunit/class-admin-test.php index b13995af52300..f801df79e4f16 100644 --- a/phpunit/class-admin-test.php +++ b/phpunit/class-admin-test.php @@ -141,21 +141,6 @@ function test_gutenberg_content_has_blocks() { $this->assertFalse( gutenberg_content_has_blocks( $content_without_blocks ) ); } - /** - * Tests gutenberg_add_gutenberg_post_state(). - * - * @covers ::gutenberg_add_gutenberg_post_state - */ - function test_add_gutenberg_post_state() { - // With blocks. - $post_states = apply_filters( 'display_post_states', array(), get_post( self::$post_with_blocks ) ); - $this->assertEquals( array( 'Gutenberg' ), $post_states ); - - // Without blocks. - $post_states = apply_filters( 'display_post_states', array(), get_post( self::$post_without_blocks ) ); - $this->assertEquals( array(), $post_states ); - } - /** * Test that the revisions 'return to editor' links are set correctly for Classic & Gutenberg editors. * From cea6957ea893045306af65f27556848837e09034 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Mon, 28 Jan 2019 08:22:31 -0500 Subject: [PATCH 258/691] Testing: Remove PHPUnit tests covering core functions (#13513) --- phpunit/bootstrap.php | 3 - phpunit/class-block-type-registry-test.php | 153 ---------- phpunit/class-block-type-test.php | 275 ------------------ phpunit/class-do-blocks-test.php | 96 ------ phpunit/class-dynamic-blocks-render-test.php | 243 ---------------- phpunit/class-performance-test.php | 38 --- phpunit/class-registration-test.php | 99 ------- phpunit/class-reusable-blocks-render-test.php | 103 ------- phpunit/class-wp-dummy-block-type.php | 16 - phpunit/fixtures/do-blocks-expected.html | 22 -- phpunit/fixtures/do-blocks-original.html | 25 -- phpunit/fixtures/long-content.html | 130 --------- 12 files changed, 1203 deletions(-) delete mode 100644 phpunit/class-block-type-registry-test.php delete mode 100644 phpunit/class-block-type-test.php delete mode 100644 phpunit/class-do-blocks-test.php delete mode 100644 phpunit/class-dynamic-blocks-render-test.php delete mode 100644 phpunit/class-performance-test.php delete mode 100644 phpunit/class-registration-test.php delete mode 100644 phpunit/class-reusable-blocks-render-test.php delete mode 100644 phpunit/class-wp-dummy-block-type.php delete mode 100644 phpunit/fixtures/do-blocks-expected.html delete mode 100644 phpunit/fixtures/do-blocks-original.html delete mode 100644 phpunit/fixtures/long-content.html diff --git a/phpunit/bootstrap.php b/phpunit/bootstrap.php index 6043dd1bbd037..8aab3cac808dd 100644 --- a/phpunit/bootstrap.php +++ b/phpunit/bootstrap.php @@ -33,9 +33,6 @@ */ function _manually_load_plugin() { require dirname( dirname( __FILE__ ) ) . '/lib/load.php'; - - // Require dummy block type class for testing. - require_once dirname( __FILE__ ) . '/class-wp-dummy-block-type.php'; } tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' ); diff --git a/phpunit/class-block-type-registry-test.php b/phpunit/class-block-type-registry-test.php deleted file mode 100644 index 0ba75cd8d07e7..0000000000000 --- a/phpunit/class-block-type-registry-test.php +++ /dev/null @@ -1,153 +0,0 @@ -<?php -/** - * WP_Block_Type_Registry Tests - * - * @package Gutenberg - */ - -/** - * Tests for WP_Block_Type_Registry - */ -class Block_Type_Registry_Test extends WP_UnitTestCase { - - /** - * Dummy block type registry. - * - * @var WP_Block_Type_Registry - */ - private $registry = null; - - function setUp() { - parent::setUp(); - - $this->registry = new WP_Block_Type_Registry(); - } - - function tearDown() { - parent::tearDown(); - - $this->registry = null; - } - - /** - * Should reject numbers - * - * @expectedIncorrectUsage WP_Block_Type_Registry::register - */ - function test_invalid_non_string_names() { - $result = $this->registry->register( 1, array() ); - $this->assertFalse( $result ); - } - - /** - * Should reject blocks without a namespace - * - * @expectedIncorrectUsage WP_Block_Type_Registry::register - */ - function test_invalid_names_without_namespace() { - $result = $this->registry->register( 'paragraph', array() ); - $this->assertFalse( $result ); - } - - /** - * Should reject blocks with invalid characters - * - * @expectedIncorrectUsage WP_Block_Type_Registry::register - */ - function test_invalid_characters() { - $result = $this->registry->register( 'still/_doing_it_wrong', array() ); - $this->assertFalse( $result ); - } - - /** - * Should reject blocks with uppercase characters - * - * @expectedIncorrectUsage WP_Block_Type_Registry::register - */ - function test_uppercase_characters() { - $result = $this->registry->register( 'Core/Paragraph', array() ); - $this->assertFalse( $result ); - } - - /** - * Should accept valid block names - */ - function test_register_block_type() { - $name = 'core/paragraph'; - $settings = array( - 'icon' => 'editor-paragraph', - ); - - $block_type = $this->registry->register( $name, $settings ); - $this->assertEquals( $name, $block_type->name ); - $this->assertEquals( $settings['icon'], $block_type->icon ); - $this->assertEquals( $block_type, $this->registry->get_registered( $name ) ); - } - - /** - * Should fail to re-register the same block - * - * @expectedIncorrectUsage WP_Block_Type_Registry::register - */ - function test_register_block_type_twice() { - $name = 'core/paragraph'; - $settings = array( - 'icon' => 'editor-paragraph', - ); - - $result = $this->registry->register( $name, $settings ); - $this->assertNotFalse( $result ); - $result = $this->registry->register( $name, $settings ); - $this->assertFalse( $result ); - } - - /** - * Should accept a WP_Block_Type instance - */ - function test_register_block_type_instance() { - $block_type = new WP_Dummy_Block_Type( 'core/dummy' ); - - $result = $this->registry->register( $block_type ); - $this->assertSame( $block_type, $result ); - } - - /** - * Unregistering should fail if a block is not registered - * - * @expectedIncorrectUsage WP_Block_Type_Registry::unregister - */ - function test_unregister_not_registered_block() { - $result = $this->registry->unregister( 'core/unregistered' ); - $this->assertFalse( $result ); - } - - /** - * Should unregister existing blocks - */ - function test_unregister_block_type() { - $name = 'core/paragraph'; - $settings = array( - 'icon' => 'editor-paragraph', - ); - - $this->registry->register( $name, $settings ); - $block_type = $this->registry->unregister( $name ); - $this->assertEquals( $name, $block_type->name ); - $this->assertEquals( $settings['icon'], $block_type->icon ); - $this->assertFalse( $this->registry->is_registered( $name ) ); - } - - function test_get_all_registered() { - $names = array( 'core/paragraph', 'core/image', 'core/blockquote' ); - $settings = array( - 'icon' => 'random', - ); - - foreach ( $names as $name ) { - $this->registry->register( $name, $settings ); - } - - $registered = $this->registry->get_all_registered(); - $this->assertEqualSets( $names, array_keys( $registered ) ); - } -} diff --git a/phpunit/class-block-type-test.php b/phpunit/class-block-type-test.php deleted file mode 100644 index 648893082b104..0000000000000 --- a/phpunit/class-block-type-test.php +++ /dev/null @@ -1,275 +0,0 @@ -<?php -/** - * WP_Block_Type Tests - * - * @package Gutenberg - */ - -/** - * Tests for WP_Block_Type - */ -class Block_Type_Test extends WP_UnitTestCase { - function setUp() { - parent::setUp(); - } - - /** - * Editor user ID. - * - * @var int - */ - protected static $editor_user_id; - - /** - * ID for a post containing blocks. - * - * @var int - */ - protected static $post_with_blocks; - - /** - * ID for a post without blocks. - * - * @var int - */ - protected static $post_without_blocks; - - /** - * Set up before class. - */ - public static function wpSetUpBeforeClass() { - self::$editor_user_id = self::factory()->user->create( - array( - 'role' => 'editor', - ) - ); - - self::$post_with_blocks = self::factory()->post->create( - array( - 'post_title' => 'Example', - 'post_content' => "<!-- wp:core/text {\"dropCap\":true} -->\n<p class=\"has-drop-cap\">Tester</p>\n<!-- /wp:core/text -->", - ) - ); - - self::$post_without_blocks = self::factory()->post->create( - array( - 'post_title' => 'Example', - 'post_content' => 'Tester', - ) - ); - } - - function test_set_props() { - $name = 'core/dummy'; - $args = array( - 'render_callback' => array( $this, 'render_dummy_block' ), - 'foo' => 'bar', - ); - - $block_type = new WP_Block_Type( $name, $args ); - - $this->assertSame( $name, $block_type->name ); - $this->assertSame( $args['render_callback'], $block_type->render_callback ); - $this->assertSame( $args['foo'], $block_type->foo ); - } - - function test_render() { - $attributes = array( - 'foo' => 'bar', - 'bar' => 'foo', - ); - - $block_type = new WP_Block_Type( - 'core/dummy', - array( - 'render_callback' => array( $this, 'render_dummy_block' ), - ) - ); - $output = $block_type->render( $attributes ); - $this->assertEquals( $attributes, json_decode( $output, true ) ); - } - - function test_render_with_content() { - $attributes = array( - 'foo' => 'bar', - 'bar' => 'foo', - ); - - $content = 'baz'; - - $expected = array_merge( $attributes, array( '_content' => $content ) ); - - $block_type = new WP_Block_Type( - 'core/dummy', - array( - 'render_callback' => array( $this, 'render_dummy_block_with_content' ), - ) - ); - $output = $block_type->render( $attributes, $content ); - $this->assertEquals( $expected, json_decode( $output, true ) ); - } - - function test_render_for_static_block() { - $block_type = new WP_Block_Type( 'core/dummy', array() ); - $output = $block_type->render(); - - $this->assertEquals( '', $output ); - } - - function test_is_dynamic_for_static_block() { - $block_type = new WP_Block_Type( 'core/dummy', array() ); - - $this->assertFalse( $block_type->is_dynamic() ); - } - - function test_is_dynamic_for_dynamic_block() { - $block_type = new WP_Block_Type( - 'core/dummy', - array( - 'render_callback' => array( $this, 'render_dummy_block' ), - ) - ); - - $this->assertTrue( $block_type->is_dynamic() ); - } - - function test_prepare_attributes() { - $attributes = array( - 'correct' => 'include', - 'wrongType' => 5, - 'wrongTypeDefaulted' => 5, - /* missingDefaulted */ - 'undefined' => 'include', - 'intendedNull' => null, - ); - - $block_type = new WP_Block_Type( - 'core/dummy', - array( - 'attributes' => array( - 'correct' => array( - 'type' => 'string', - ), - 'wrongType' => array( - 'type' => 'string', - ), - 'wrongTypeDefaulted' => array( - 'type' => 'string', - 'default' => 'defaulted', - ), - 'missingDefaulted' => array( - 'type' => 'string', - 'default' => 'define', - ), - 'intendedNull' => array( - 'type' => array( 'string', 'null' ), - 'default' => 'wrong', - ), - ), - ) - ); - - $prepared_attributes = $block_type->prepare_attributes_for_render( $attributes ); - - $this->assertEquals( - array( - 'correct' => 'include', - /* wrongType */ - 'wrongTypeDefaulted' => 'defaulted', - 'missingDefaulted' => 'define', - 'undefined' => 'include', - 'intendedNull' => null, - ), - $prepared_attributes - ); - } - - function test_prepare_attributes_none_defined() { - $attributes = array( 'exists' => 'keep' ); - - $block_type = new WP_Block_Type( 'core/dummy', array() ); - - $prepared_attributes = $block_type->prepare_attributes_for_render( $attributes ); - - $this->assertEquals( $attributes, $prepared_attributes ); - } - - function test_has_block_with_mixed_content() { - $mixed_post_content = 'before' . - '<!-- wp:core/dummy --><!-- /wp:core/dummy -->' . - '<!-- wp:core/dummy_atts {"value":"b1"} --><!-- /wp:core/dummy_atts -->' . - '<!-- wp:core/dummy-child --> - <p>testing the test</p> - <!-- /wp:core/dummy-child -->' . - 'between' . - '<!-- wp:core/self-close-dummy /-->' . - '<!-- wp:custom/dummy {"value":"b2"} /-->' . - 'after'; - - $this->assertTrue( has_block( 'core/dummy', $mixed_post_content ) ); - - $this->assertTrue( has_block( 'core/dummy_atts', $mixed_post_content ) ); - - $this->assertTrue( has_block( 'core/dummy-child', $mixed_post_content ) ); - - $this->assertTrue( has_block( 'core/self-close-dummy', $mixed_post_content ) ); - - $this->assertTrue( has_block( 'custom/dummy', $mixed_post_content ) ); - - // checking for a partial block name should fail. - $this->assertFalse( has_block( 'core/dumm', $mixed_post_content ) ); - - // checking for a wrong namespace should fail. - $this->assertFalse( has_block( 'custom/dummy_atts', $mixed_post_content ) ); - - // checking for namespace only should not work. Or maybe ... ? - $this->assertFalse( has_block( 'core', $mixed_post_content ) ); - } - - function test_has_block_with_invalid_content() { - // some content with invalid HMTL comments and a single valid block. - $invalid_content = 'before' . - '<!- - wp:core/weird-space --><!-- /wp:core/weird-space -->' . - '<!--wp:core/untrimmed-left --><!-- /wp:core/untrimmed -->' . - '<!-- wp:core/dummy --><!-- /wp:core/dummy -->' . - '<!-- wp:core/untrimmed-right--><!-- /wp:core/untrimmed2 -->' . - 'after'; - - $this->assertFalse( has_block( 'core/text', self::$post_without_blocks ) ); - - $this->assertFalse( has_block( 'core/weird-space', $invalid_content ) ); - - $this->assertFalse( has_block( 'core/untrimmed-left', $invalid_content ) ); - - $this->assertFalse( has_block( 'core/untrimmed-right', $invalid_content ) ); - - $this->assertTrue( has_block( 'core/dummy', $invalid_content ) ); - } - - function test_post_has_block() { - // should fail for a non-existent block `custom/dummy`. - $this->assertFalse( has_block( 'custom/dummy', self::$post_with_blocks ) ); - - // this functions should not work without the second param until the $post global is set. - $this->assertFalse( has_block( 'core/text' ) ); - $this->assertFalse( has_block( 'core/dummy' ) ); - - global $post; - $post = get_post( self::$post_with_blocks ); - - // check if the function correctly detects content from the $post global. - $this->assertTrue( has_block( 'core/text' ) ); - // even if it detects a proper $post global it should still be false for a missing block. - $this->assertFalse( has_block( 'core/dummy' ) ); - } - - function render_dummy_block( $attributes ) { - return json_encode( $attributes ); - } - - function render_dummy_block_with_content( $attributes, $content ) { - $attributes['_content'] = $content; - - return json_encode( $attributes ); - } -} diff --git a/phpunit/class-do-blocks-test.php b/phpunit/class-do-blocks-test.php deleted file mode 100644 index 147545e65ca0a..0000000000000 --- a/phpunit/class-do-blocks-test.php +++ /dev/null @@ -1,96 +0,0 @@ -<?php -/** - * `do_blocks` rendering test - * - * @package Gutenberg - */ - -/** - * Test do_blocks - */ -class Do_Blocks_Test extends WP_UnitTestCase { - /** - * Tear down. - */ - function tearDown() { - parent::tearDown(); - - $registry = WP_Block_Type_Registry::get_instance(); - - if ( $registry->is_registered( 'core/dummy' ) ) { - $registry->unregister( 'core/dummy' ); - } - } - - /** - * Test do_blocks removes comment demarcations. - * - * @covers ::do_blocks - */ - function test_do_blocks_removes_comments() { - $original_html = file_get_contents( dirname( __FILE__ ) . '/fixtures/do-blocks-original.html' ); - $expected_html = file_get_contents( dirname( __FILE__ ) . '/fixtures/do-blocks-expected.html' ); - - $actual_html = do_blocks( $original_html ); - - $this->assertEquals( $expected_html, $actual_html ); - } - - /** - * Test that shortcode blocks get the same HTML as shortcodes in Classic content. - */ - function test_the_content() { - add_shortcode( 'someshortcode', array( $this, 'handle_shortcode' ) ); - - $classic_content = "Foo\n\n[someshortcode]\n\nBar\n\n[/someshortcode]\n\nBaz"; - $block_content = "<!-- wp:core/paragraph --><p>Foo</p>\n<!-- /wp:core/paragraph -->\n\n<!-- wp:core/shortcode -->[someshortcode]\n\nBar\n\n[/someshortcode]<!-- /wp:core/shortcode -->\n\n<!-- wp:core/paragraph -->\n<p>Baz</p>\n<!-- /wp:core/paragraph -->"; - - $classic_filtered_content = apply_filters( 'the_content', $classic_content ); - $block_filtered_content = apply_filters( 'the_content', $block_content ); - - // Block rendering add some extra blank lines, but we're not worried about them. - $block_filtered_content = preg_replace( "/\n{2,}/", "\n", $block_filtered_content ); - - $this->assertEquals( $classic_filtered_content, $block_filtered_content ); - } - - function test_can_nest_at_least_so_deep() { - $minimum_depth = 99; - - $content = 'deep inside'; - for ( $i = 0; $i < $minimum_depth; $i++ ) { - $content = '<!-- wp:dummy -->' . $content . '<!-- /wp:dummy -->'; - } - - $this->assertEquals( 'deep inside', do_blocks( $content ) ); - } - - function test_can_nest_at_least_so_deep_with_dynamic_blocks() { - $minimum_depth = 99; - - $content = '0'; - for ( $i = 0; $i < $minimum_depth; $i++ ) { - $content = '<!-- wp:dummy -->' . $content . '<!-- /wp:dummy -->'; - } - - register_block_type( - 'core/dummy', - array( - 'render_callback' => array( - $this, - 'render_dynamic_incrementer', - ), - ) - ); - - $this->assertEquals( $minimum_depth, (int) do_blocks( $content ) ); - } - - function handle_shortcode( $atts, $content ) { - return $content; - } - - function render_dynamic_incrementer( $attrs, $content ) { - return (string) ( 1 + (int) $content ); - } -} diff --git a/phpunit/class-dynamic-blocks-render-test.php b/phpunit/class-dynamic-blocks-render-test.php deleted file mode 100644 index 35799bc9bf73d..0000000000000 --- a/phpunit/class-dynamic-blocks-render-test.php +++ /dev/null @@ -1,243 +0,0 @@ -<?php -/** - * Dynamic blocks rendering Test - * - * @package Gutenberg - */ - -/** - * Test do_blocks, WP_Block_Type::render - */ -class Dynamic_Blocks_Render_Test extends WP_UnitTestCase { - - /** - * Dummy block instance number. - * - * @var int - */ - protected $dummy_block_instance_number = 0; - - /** - * Dummy block rendering function. - * - * @param array $attributes Block attributes. - * - * @return string Block output. - */ - function render_dummy_block( $attributes ) { - $this->dummy_block_instance_number += 1; - return $this->dummy_block_instance_number . ':' . $attributes['value']; - } - - /** - * Dummy block rendering function, returning numeric value. - * - * @return number Block output. - */ - function render_dummy_block_numeric() { - return 10; - } - - function render_serialize_dynamic_block( $attributes, $content ) { - return base64_encode( serialize( array( $attributes, $content ) ) ); - } - - /** - * Dummy block rendering function, creating a new WP_Query instance. - * - * @return string Block output. - */ - function render_dummy_block_wp_query() { - $content = ''; - $recent = new WP_Query( array( - 'numberposts' => 10, - 'orderby' => 'ID', - 'order' => 'DESC', - 'post_type' => 'post', - 'post_status' => 'draft, publish, future, pending, private', - 'suppress_filters' => true, - ) ); - - while ( $recent->have_posts() ) { - $recent->the_post(); - - $content .= get_the_title(); - } - - wp_reset_postdata(); - - return $content; - } - - /** - * Tear down. - */ - function tearDown() { - parent::tearDown(); - - $this->dummy_block_instance_number = 0; - - $registry = WP_Block_Type_Registry::get_instance(); - - if ( $registry->is_registered( 'core/dummy' ) ) { - $registry->unregister( 'core/dummy' ); - } - - if ( $registry->is_registered( 'core/dynamic' ) ) { - $registry->unregister( 'core/dynamic' ); - } - } - - /** - * Test dynamic blocks that lack content, including void blocks. - * - * @covers ::do_blocks - */ - function test_dynamic_block_rendering() { - $settings = array( - 'render_callback' => array( - $this, - 'render_dummy_block', - ), - ); - register_block_type( 'core/dummy', $settings ); - - // The duplicated dynamic blocks below are there to ensure that do_blocks() replaces each one-by-one. - $post_content = - 'before' . - '<!-- wp:core/dummy {"value":"b1"} --><!-- /wp:core/dummy -->' . - '<!-- wp:core/dummy {"value":"b1"} --><!-- /wp:core/dummy -->' . - 'between' . - '<!-- wp:core/dummy {"value":"b2"} /-->' . - '<!-- wp:core/dummy {"value":"b2"} /-->' . - 'after'; - - $updated_post_content = do_blocks( $post_content ); - $this->assertEquals( - $updated_post_content, - 'before' . - '1:b1' . - '2:b1' . - 'between' . - '3:b2' . - '4:b2' . - 'after' - ); - } - - /** - * Tests that do_blocks() maintains the global $post variable when dynamic - * blocks create new WP_Query instances in their callbacks. - * - * @covers ::do_blocks - */ - function test_global_post_persistence() { - global $post; - - register_block_type( - 'core/dummy', - array( - 'render_callback' => array( - $this, - 'render_dummy_block_wp_query', - ), - ) - ); - - $posts = self::factory()->post->create_many( 5 ); - $post = get_post( end( $posts ) ); - - $global_post = $post; - do_blocks( '<!-- wp:core/dummy /-->' ); - - $this->assertEquals( $global_post, $post ); - } - - /** - * Test dynamic blocks return string value from render, even if render - * callback does not. - * - * @covers WP_Block_Type::render - */ - function test_dynamic_block_renders_string() { - $settings = array( - 'render_callback' => array( - $this, - 'render_dummy_block_numeric', - ), - ); - - register_block_type( 'core/dummy', $settings ); - $block_type = new WP_Block_Type( 'core/dummy', $settings ); - - $rendered = $block_type->render(); - - $this->assertSame( '10', $rendered ); - $this->assertInternalType( 'string', $rendered ); - } - - function test_dynamic_block_gets_inner_html() { - register_block_type( - 'core/dynamic', - array( - 'render_callback' => array( - $this, - 'render_serialize_dynamic_block', - ), - ) - ); - - $output = do_blocks( '<!-- wp:dynamic -->inner<!-- /wp:dynamic -->' ); - - list( /* attrs */, $content ) = unserialize( base64_decode( $output ) ); - - $this->assertEquals( 'inner', $content ); - } - - function test_dynamic_block_gets_rendered_inner_blocks() { - register_block_type( - 'core/dummy', - array( - 'render_callback' => array( - $this, - 'render_dummy_block_numeric', - ), - ) - ); - register_block_type( - 'core/dynamic', - array( - 'render_callback' => array( - $this, - 'render_serialize_dynamic_block', - ), - ) - ); - - $output = do_blocks( '<!-- wp:dynamic -->before<!-- wp:dummy /-->after<!-- /wp:dynamic -->' ); - - list( /* attrs */, $content ) = unserialize( base64_decode( $output ) ); - - $this->assertEquals( 'before10after', $content ); - } - - function test_dynamic_block_gets_rendered_inner_dynamic_blocks() { - register_block_type( - 'core/dynamic', - array( - 'render_callback' => array( - $this, - 'render_serialize_dynamic_block', - ), - ) - ); - - $output = do_blocks( '<!-- wp:dynamic -->before<!-- wp:dynamic -->deep inner<!-- /wp:dynamic -->after<!-- /wp:dynamic -->' ); - - list( /* attrs */, $content ) = unserialize( base64_decode( $output ) ); - - $inner = $this->render_serialize_dynamic_block( array(), 'deep inner' ); - - $this->assertEquals( $content, 'before' . $inner . 'after' ); - } -} diff --git a/phpunit/class-performance-test.php b/phpunit/class-performance-test.php deleted file mode 100644 index 20b58e722dda4..0000000000000 --- a/phpunit/class-performance-test.php +++ /dev/null @@ -1,38 +0,0 @@ -<?php -/** - * Server-side performance tests - * - * @package Gutenberg - */ - -// To run these tests, set the RUN_SLOW_TESTS environment variable to a truthy -// value. -if ( getenv( 'RUN_SLOW_TESTS' ) ) { - class Performance_Test extends WP_UnitTestCase { - function test_parse_large_post() { - $html = file_get_contents( - dirname( __FILE__ ) . '/fixtures/long-content.html' - ); - - $start = microtime( true ); - $start_mem = memory_get_usage(); - - $blocks = gutenberg_parse_blocks( $html ); - - $time = microtime( true ) - $start; - $mem = memory_get_usage() - $start_mem; - - if ( getenv( 'SHOW_PERFORMANCE_INFO' ) ) { - error_log( '' ); - error_log( 'Memory used (KB) : ' . round( $mem / 1024 ) ); - error_log( 'Time (ms) : ' . round( $time * 1000 ) ); - } - - $this->assertLessThanOrEqual( - 0.3, // Seconds. - $time, - "Parsing 'phpunit/fixtures/long-content.html' took too long." - ); - } - } -} diff --git a/phpunit/class-registration-test.php b/phpunit/class-registration-test.php deleted file mode 100644 index 2b09c83bc2d48..0000000000000 --- a/phpunit/class-registration-test.php +++ /dev/null @@ -1,99 +0,0 @@ -<?php -/** - * Block types registration Tests - * - * @package Gutenberg - */ - -/** - * Test register_block_type(), unregister_block_type(), get_dynamic_block_names() - */ -class Registration_Test extends WP_UnitTestCase { - - protected static $post_id; - - public static function wpSetUpBeforeClass( $factory ) { - self::$post_id = $factory->post->create( - array( - 'post_content' => file_get_contents( dirname( __FILE__ ) . '/fixtures/do-blocks-original.html' ), - ) - ); - } - - public static function wpTearDownAfterClass() { - // Also deletes revisions. - wp_delete_post( self::$post_id, true ); - } - - function render_stub() {} - - function tearDown() { - parent::tearDown(); - - $registry = WP_Block_Type_Registry::get_instance(); - - foreach ( array( 'dummy', 'dynamic' ) as $block_name ) { - $block_name = 'core/' . $block_name; - - if ( $registry->is_registered( $block_name ) ) { - $registry->unregister( $block_name ); - } - } - } - - function test_register_affects_main_registry() { - $name = 'core/dummy'; - $settings = array( - 'icon' => 'text', - ); - - register_block_type( $name, $settings ); - - $registry = WP_Block_Type_Registry::get_instance(); - $this->assertTrue( $registry->is_registered( $name ) ); - } - - function test_unregister_affects_main_registry() { - $name = 'core/dummy'; - $settings = array( - 'icon' => 'text', - ); - - register_block_type( $name, $settings ); - unregister_block_type( $name ); - - $registry = WP_Block_Type_Registry::get_instance(); - $this->assertFalse( $registry->is_registered( $name ) ); - } - - function test_get_dynamic_block_names() { - register_block_type( 'core/dummy', array() ); - register_block_type( 'core/dynamic', array( 'render_callback' => array( $this, 'render_stub' ) ) ); - - $dynamic_block_names = get_dynamic_block_names(); - - $this->assertContains( 'core/dynamic', $dynamic_block_names ); - $this->assertNotContains( 'core/dummy', $dynamic_block_names ); - } - - function test_has_blocks() { - // Test with passing post ID. - $this->assertTrue( has_blocks( self::$post_id ) ); - - // Test with passing WP_Post object. - $this->assertTrue( has_blocks( get_post( self::$post_id ) ) ); - - // Test with passing content string. - $this->assertTrue( has_blocks( get_post( self::$post_id ) ) ); - - // Test default. - $this->assertFalse( has_blocks() ); - $query = new WP_Query( array( 'post__in' => array( self::$post_id ) ) ); - $query->the_post(); - $this->assertTrue( has_blocks() ); - - // Test string (without blocks). - $content = file_get_contents( dirname( __FILE__ ) . '/fixtures/do-blocks-expected.html' ); - $this->assertFalse( has_blocks( $content ) ); - } -} diff --git a/phpunit/class-reusable-blocks-render-test.php b/phpunit/class-reusable-blocks-render-test.php deleted file mode 100644 index 44b1f439c7652..0000000000000 --- a/phpunit/class-reusable-blocks-render-test.php +++ /dev/null @@ -1,103 +0,0 @@ -<?php -/** - * Reusable block rendering tests. - * - * @package Gutenberg - */ - -/** - * Tests reusable block rendering. - */ -class Reusable_Blocks_Render_Test extends WP_UnitTestCase { - /** - * Fake user ID. - * - * @var int - */ - protected static $user_id; - - /** - * Fake block ID. - * - * @var int - */ - protected static $block_id; - - /** - * Fake post ID. - * - * @var int - */ - protected static $post_id; - - /** - * Create fake data before tests run. - * - * @param WP_UnitTest_Factory $factory Helper that creates fake data. - */ - public static function wpSetUpBeforeClass( $factory ) { - self::$user_id = $factory->user->create( - array( - 'role' => 'editor', - ) - ); - - self::$post_id = $factory->post->create( - array( - 'post_author' => self::$user_id, - 'post_type' => 'post', - 'post_status' => 'publish', - 'post_title' => 'Test Post', - 'post_content' => '<p>Hello world!</p>', - ) - ); - - self::$block_id = $factory->post->create( - array( - 'post_author' => self::$user_id, - 'post_type' => 'wp_block', - 'post_status' => 'publish', - 'post_title' => 'Test Block', - 'post_content' => '<!-- wp:core/paragraph --><p>Hello world!</p><!-- /wp:core/paragraph -->', - ) - ); - } - - /** - * Delete fake data after tests run. - */ - public static function wpTearDownAfterClass() { - wp_delete_post( self::$block_id, true ); - wp_delete_post( self::$post_id, true ); - self::delete_user( self::$user_id ); - } - - /** - * Test rendering of a reusable block. - */ - public function test_render() { - $block_type = WP_Block_Type_Registry::get_instance()->get_registered( 'core/block' ); - $output = $block_type->render( array( 'ref' => self::$block_id ) ); - $this->assertSame( '<p>Hello world!</p>', $output ); - } - - /** - * Test rendering of a reusable block when 'ref' is missing, which should fail by - * rendering an empty string. - */ - public function test_ref_empty() { - $block_type = WP_Block_Type_Registry::get_instance()->get_registered( 'core/block' ); - $output = $block_type->render( array() ); - $this->assertSame( '', $output ); - } - - /** - * Test rendering of a reusable block when 'ref' points to wrong post type, which - * should fail by rendering an empty string. - */ - public function test_ref_wrong_post_type() { - $block_type = WP_Block_Type_Registry::get_instance()->get_registered( 'core/block' ); - $output = $block_type->render( array( 'ref' => self::$post_id ) ); - $this->assertSame( '', $output ); - } -} diff --git a/phpunit/class-wp-dummy-block-type.php b/phpunit/class-wp-dummy-block-type.php deleted file mode 100644 index cdd697872044a..0000000000000 --- a/phpunit/class-wp-dummy-block-type.php +++ /dev/null @@ -1,16 +0,0 @@ -<?php -/** - * WP_Dummy_Block_Type for testing - * - * @package Gutenberg - */ - -/** - * Test class extending WP_Block_Type - */ -class WP_Dummy_Block_Type extends WP_Block_Type { - - public function render( $attributes = array(), $content = '' ) { - return '<div>' . $content . '</div>'; - } -} diff --git a/phpunit/fixtures/do-blocks-expected.html b/phpunit/fixtures/do-blocks-expected.html deleted file mode 100644 index f413182051334..0000000000000 --- a/phpunit/fixtures/do-blocks-expected.html +++ /dev/null @@ -1,22 +0,0 @@ -<p>First Auto Paragraph</p> - -<!--more--> - - -<p>First Gutenberg Paragraph</p> - - -<p>Second Auto Paragraph</p> - - - - -<p>Third Gutenberg Paragraph</p> - - -<p>Third Auto Paragraph</p> - -<p>[someshortcode]</p> -<p>And some content?!</p> -<p>[/someshortcode]</p> - diff --git a/phpunit/fixtures/do-blocks-original.html b/phpunit/fixtures/do-blocks-original.html deleted file mode 100644 index bbd2eb4bb1457..0000000000000 --- a/phpunit/fixtures/do-blocks-original.html +++ /dev/null @@ -1,25 +0,0 @@ -<p>First Auto Paragraph</p> - -<!--more--> - -<!-- wp:core/paragraph --> -<p>First Gutenberg Paragraph</p> -<!-- /wp:core/paragraph --> - -<p>Second Auto Paragraph</p> - -<!-- wp:core/test-self-closing /--> - -<!-- wp:core/paragraph --> -<p>Third Gutenberg Paragraph</p> -<!-- /wp:core/paragraph --> - -<p>Third Auto Paragraph</p> - -<!-- wp:core/shortcode --> -[someshortcode] - -And some content?! - -[/someshortcode] -<!-- /wp:core/shortcode --> diff --git a/phpunit/fixtures/long-content.html b/phpunit/fixtures/long-content.html deleted file mode 100644 index 065b7c402c149..0000000000000 --- a/phpunit/fixtures/long-content.html +++ /dev/null @@ -1,130 +0,0 @@ -<!-- wp:cover-image {"url":"https://cldup.com/Fz-ASbo2s3.jpg","align":"wide"} --> -<section class="wp-block-cover-image has-background-dim" style="background-image:url(https://cldup.com/Fz-ASbo2s3.jpg)"><h2>Of Mountains &amp; Printing Presses</h2></section> -<!-- /wp:cover-image --> -<!-- wp:paragraph --> -<p>The goal of this new editor is to make adding rich content to WordPress simple and enjoyable. This whole post is composed of <em>pieces of content</em>—somewhat similar to LEGO bricks—that you can move around and interact with. Move your cursor around and you&#x27;ll notice the different blocks light up with outlines and arrows. Press the arrows to reposition blocks quickly, without fearing about losing things in the process of copying and pasting.</p> -<!-- /wp:paragraph --> -<!-- wp:paragraph --> -<p>What you are reading now is a <strong>text block</strong>, the most basic block of all. The text block has its own controls to be moved freely around the post...</p> -<!-- /wp:paragraph --> -<!-- wp:paragraph {"align":"right"} --> -<p style="text-align:right">... like this one, which is right aligned.</p> -<!-- /wp:paragraph --> -<!-- wp:paragraph --> -<p>Headings are separate blocks as well, which helps with the outline and organization of your content.</p> -<!-- /wp:paragraph --> -<!-- wp:heading --> -<h2>A Picture is worth a Thousand Words</h2> -<!-- /wp:heading --> -<!-- wp:paragraph --> -<p>Handling images and media with the utmost care is a primary focus of the new editor. Hopefully, you&#x27;ll find aspects of adding captions or going full-width with your pictures much easier and robust than before.</p> -<!-- /wp:paragraph --> -<!-- wp:image {"align":"center"} --> -<div class="wp-block-image"><figure class="aligncenter"><img src="https://cldup.com/cXyG__fTLN.jpg" alt="Beautiful landscape" /><figcaption>Give it a try. Press the &quot;wide&quot; button on the image toolbar.</figcaption></figure></div> -<!-- /wp:image --> -<!-- wp:paragraph --> -<p>Try selecting and removing or editing the caption, now you don&#x27;t have to be careful about selecting the image or other text by mistake and ruining the presentation.</p> -<!-- /wp:paragraph --> -<!-- wp:heading --> -<h2>The <em>Inserter</em> Tool</h2> -<!-- /wp:heading --> -<!-- wp:paragraph --> -<p>Imagine everything that WordPress can do is available to you quickly and in the same place on the interface. No need to figure out HTML tags, classes, or remember complicated shortcode syntax. That&#x27;s the spirit behind the inserter—the <code>(+)</code> button you&#x27;ll see around the editor—which allows you to browse all available content blocks and add them into your post. Plugins and themes are able to register their own, opening up all sort of possibilities for rich editing and publishing.</p> -<!-- /wp:paragraph --> -<!-- wp:paragraph --> -<p>Go give it a try, you may discover things WordPress can already add into your posts that you didn&#x27;t know about. Here&#x27;s a short list of what you can currently find there:</p> -<!-- /wp:paragraph --> -<!-- wp:list --> -<ul> -<li>Text &amp; Headings</li> -<li>Images &amp; Videos</li> -<li>Galleries</li> -<li>Embeds, like YouTube, Tweets, or other WordPress posts.</li> -<li>Layout blocks, like Buttons, Hero Images, Separators, etc.</li> -<li>And <em>Lists</em> like this one of course :)</li> -</ul> -<!-- /wp:list --> -<!-- wp:separator --> -<hr class="wp-block-separator" /> -<!-- /wp:separator --> -<!-- wp:heading --> -<h2>Visual Editing</h2> -<!-- /wp:heading --> -<!-- wp:paragraph --> -<p>A huge benefit of blocks is that you can edit them in place and manipulate your content directly. Instead of having fields for editing things like the source of a quote, or the text of a button, you can directly change the content. Try editing the following quote:</p> -<!-- /wp:paragraph --> -<!-- wp:quote --> -<blockquote class="wp-block-quote blocks-quote-style-1"> -<p>The editor will endeavour to create a new page and post building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery.</p> -<cite>Matt Mullenweg, 2017</cite> -</blockquote> -<!-- /wp:quote --> -<!-- wp:paragraph --> -<p>The information corresponding to the source of the quote is a separate text field, similar to captions under images, so the structure of the quote is protected even if you select, modify, or remove the source. It&#x27;s always easy to add it back.</p> -<!-- /wp:paragraph --> -<!-- wp:paragraph --> -<p>Blocks can be anything you need. For instance, you may want to add a subdued quote as part of the composition of your text, or you may prefer to display a giant stylized one. All of these options are available in the inserter.</p> -<!-- /wp:paragraph --> -<!-- wp:gallery {"columns":2} --> -<div class="wp-block-gallery columns-2 is-cropped"> -<figure class="blocks-gallery-item"><img src="https://cldup.com/n0g6ME5VKC.jpg" alt="" /></figure> -<figure class="blocks-gallery-item"><img src="https://cldup.com/ZjESfxPI3R.jpg" alt="" /></figure> -<figure class="blocks-gallery-item"><img src="https://cldup.com/EKNF8xD2UM.jpg" alt="" /></figure> -</div> -<!-- /wp:gallery --> -<!-- wp:paragraph --> -<p>You can change the amount of columns in your galleries by dragging a slider in the block inspector in the sidebar.</p> -<!-- /wp:paragraph --> -<!-- wp:heading --> -<h2>Media Rich</h2> -<!-- /wp:heading --> -<!-- wp:paragraph --> -<p>If you combine the new <strong>wide</strong> and <strong>full-wide</strong> alignments with galleries, you can create a very media rich layout, very quickly:</p> -<!-- /wp:paragraph --> -<!-- wp:image {"align":"full"} --> -<figure class="wp-block-image alignfull"><img src="https://cldup.com/8lhI-gKnI2.jpg" alt="Accessibility is important don&#x27;t forget image alt attribute" /></figure> -<!-- /wp:image --> -<!-- wp:paragraph --> -<p>Sure, the full-wide image can be pretty big. But sometimes the image is worth it.</p> -<!-- /wp:paragraph --> -<!-- wp:gallery {"align":"wide"} --> -<div class="wp-block-gallery alignwide columns-2 is-cropped"> -<figure class="blocks-gallery-item"><img src="https://cldup.com/_rSwtEeDGD.jpg" alt="" /></figure> -<figure class="blocks-gallery-item"><img src="https://cldup.com/L-cC3qX2DN.jpg" alt="" /></figure> -</div> -<!-- /wp:gallery --> -<!-- wp:paragraph --> -<p>The above is a gallery with just two images. It&#x27;s an easier way to create visually appealing layouts, without having to deal with floats. You can also easily convert the gallery back to individual images again, by using the block switcher.</p> -<!-- /wp:paragraph --> -<!-- wp:paragraph --> -<p>Any block can opt into these alignments. The embed block has them also, and is responsive out of the box:</p> -<!-- /wp:paragraph --> -<!-- wp:embed {"url":"https://vimeo.com/22439234","align":"wide"} --> -<figure class="wp-block-embed alignwide"> -https://vimeo.com/22439234 -</figure> -<!-- /wp:embed --> -<!-- wp:paragraph --> -<p>You can build any block you like, static or dynamic, decorative or plain. Here&#x27;s a pullquote block:</p> -<!-- /wp:paragraph --> -<!-- wp:pullquote --> -<blockquote class="wp-block-pullquote"> -<p>Code is Poetry</p> -<cite>The WordPress community</cite> -</blockquote> -<!-- /wp:pullquote --> -<!-- wp:paragraph {"align":"center"} --> -<p style="text-align:center"><em>If you want to learn more about how to build additional blocks, or if you are interested in helping with the project, head over to the <a href="https://github.com/WordPress/gutenberg">GitHub repository</a>.</em></p> -<!-- /wp:paragraph --> -<!-- wp:button {"align":"center"} --> -<div class="wp-block-button aligncenter"><a href="https://github.com/WordPress/gutenberg"><span>Help build Gutenberg</span></a></div> -<!-- /wp:button --> -<!-- wp:separator --> -<hr class="wp-block-separator" /> -<!-- /wp:separator --> -<!-- wp:paragraph {"align":"center"} --> -<p style="text-align:center">Thanks for testing Gutenberg!</p> -<!-- /wp:paragraph --> -<!-- wp:paragraph {"align":"center"} --> -<p style="text-align:center"><img draggable="false" class="emoji" alt="👋" src="https://s.w.org/images/core/emoji/2.3/svg/1f44b.svg" /></p> -<!-- /wp:paragraph --> From 3a99ddf8a4a3bda7ce793b8a5fcb094824a2a3ae Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Mon, 28 Jan 2019 08:45:11 -0500 Subject: [PATCH 259/691] Plugin: Remove redundant core compatibility from plugin (#13442) --- .../backward-compatibility/deprecations.md | 6 + lib/compat.php | 268 ++---------------- 2 files changed, 33 insertions(+), 241 deletions(-) diff --git a/docs/designers-developers/developers/backward-compatibility/deprecations.md b/docs/designers-developers/developers/backward-compatibility/deprecations.md index 699dd190cd885..b3a007565f0c8 100644 --- a/docs/designers-developers/developers/backward-compatibility/deprecations.md +++ b/docs/designers-developers/developers/backward-compatibility/deprecations.md @@ -38,6 +38,12 @@ The Gutenberg project's deprecation policy is intended to support backward compa - The `gutenberg` theme support option has been removed. Use [`align-wide`](https://wordpress.org/gutenberg/handbook/designers-developers/developers/themes/theme-support/#wide-alignment) instead. - The PHP function `gutenberg_prepare_blocks_for_js` has been removed. Use [`get_block_editor_server_block_settings`](https://developer.wordpress.org/reference/functions/get_block_editor_server_block_settings/) instead. - The PHP function `gutenberg_load_list_reusable_blocks` has been removed. +- The PHP function `_gutenberg_utf8_split` has been removed. Use `_mb_substr` instead. +- The PHP function `gutenberg_disable_editor_settings_wpautop` has been removed. +- The PHP function `gutenberg_add_rest_nonce_to_heartbeat_response_headers` has been removed. +- The PHP function `gutenberg_check_if_classic_needs_warning_about_blocks` has been removed. +- The PHP function `gutenberg_warn_classic_about_blocks` has been removed. +- The PHP function `gutenberg_show_privacy_policy_help_text` has been removed. ## 4.5.0 - `Dropdown.refresh()` has been deprecated as the contained `Popover` is now automatically refreshed. diff --git a/lib/compat.php b/lib/compat.php index 03af8d9302af1..dc090cfe047de 100644 --- a/lib/compat.php +++ b/lib/compat.php @@ -14,277 +14,67 @@ * Splits a UTF-8 string into an array of UTF-8-encoded codepoints. * * @since 0.5.0 + * @deprecated 5.0.0 _mb_substr * - * Based on WordPress' _mb_substr() compat function. - * - * @param string $str The string to split. - * @return array + * @param string $str The string to split. + * @return string Extracted substring. */ function _gutenberg_utf8_split( $str ) { - if ( _wp_can_use_pcre_u() ) { - // Use the regex unicode support to separate the UTF-8 characters into - // an array. - preg_match_all( '/./us', $str, $match ); - return $match[0]; - } - - $regex = '/( - [\x00-\x7F] # single-byte sequences 0xxxxxxx - | [\xC2-\xDF][\x80-\xBF] # double-byte sequences 110xxxxx 10xxxxxx - | \xE0[\xA0-\xBF][\x80-\xBF] # triple-byte sequences 1110xxxx 10xxxxxx * 2 - | [\xE1-\xEC][\x80-\xBF]{2} - | \xED[\x80-\x9F][\x80-\xBF] - | [\xEE-\xEF][\x80-\xBF]{2} - | \xF0[\x90-\xBF][\x80-\xBF]{2} # four-byte sequences 11110xxx 10xxxxxx * 3 - | [\xF1-\xF3][\x80-\xBF]{3} - | \xF4[\x80-\x8F][\x80-\xBF]{2} - )/x'; - - // Start with 1 element instead of 0 since the first thing we do is pop. - $chars = array( '' ); - do { - // We had some string left over from the last round, but we counted it - // in that last round. - array_pop( $chars ); - - // Split by UTF-8 character, limit to 1000 characters (last array - // element will contain the rest of the string). - $pieces = preg_split( - $regex, - $str, - 1000, - PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY - ); - - $chars = array_merge( $chars, $pieces ); + _deprecated_function( __FUNCTION__, '5.0.0', '_mb_substr' ); - // If there's anything left over, repeat the loop. - if ( count( $pieces ) > 1 ) { - $str = array_pop( $pieces ); - } else { - break; - } - } while ( $str ); - - return $chars; + return _mb_substr( $str ); } /** * Disables wpautop behavior in classic editor when post contains blocks, to * prevent removep from invalidating paragraph blocks. * - * @param array $settings Original editor settings. - * @param string $editor_id ID for the editor instance. - * @return array Filtered settings. + * @link https://core.trac.wordpress.org/ticket/45113 + * @link https://core.trac.wordpress.org/changeset/43758 + * @deprecated 5.0.0 + * + * @param array $settings Original editor settings. + * @return array Filtered settings. */ -function gutenberg_disable_editor_settings_wpautop( $settings, $editor_id ) { - $post = get_post(); - if ( 'content' === $editor_id && is_object( $post ) && has_blocks( $post ) ) { - $settings['wpautop'] = false; - } +function gutenberg_disable_editor_settings_wpautop( $settings ) { + _deprecated_function( __FUNCTION__, '5.0.0' ); return $settings; } -add_filter( 'wp_editor_settings', 'gutenberg_disable_editor_settings_wpautop', 10, 2 ); /** * Add rest nonce to the heartbeat response. * - * @param array $response Original heartbeat response. - * @return array New heartbeat response. + * @link https://core.trac.wordpress.org/ticket/45113 + * @link https://core.trac.wordpress.org/changeset/43939 + * @deprecated 5.0.0 + * + * @param array $response Original heartbeat response. + * @return array New heartbeat response. */ function gutenberg_add_rest_nonce_to_heartbeat_response_headers( $response ) { - $response['rest-nonce'] = wp_create_nonce( 'wp_rest' ); + _deprecated_function( __FUNCTION__, '5.0.0' ); + return $response; } -add_filter( 'wp_refresh_nonces', 'gutenberg_add_rest_nonce_to_heartbeat_response_headers' ); /** * Check if we need to load the block warning in the Classic Editor. * - * @since 3.4.0 + * @deprecated 5.0.0 */ function gutenberg_check_if_classic_needs_warning_about_blocks() { - global $pagenow; - - if ( ! in_array( $pagenow, array( 'post.php', 'post-new.php' ), true ) || ! isset( $_REQUEST['classic-editor'] ) ) { - return; - } - - $post = get_post(); - if ( ! $post ) { - return; - } - - if ( ! has_blocks( $post ) ) { - return; - } - - // Enqueue the JS we're going to need in the dialog. - wp_enqueue_script( 'wp-a11y' ); - wp_enqueue_script( 'wp-sanitize' ); - - add_action( 'admin_footer', 'gutenberg_warn_classic_about_blocks' ); + _deprecated_function( __FUNCTION__, '5.0.0' ); } -add_action( 'admin_enqueue_scripts', 'gutenberg_check_if_classic_needs_warning_about_blocks' ); /** * Adds a warning to the Classic Editor when trying to edit a post containing blocks. * * @since 3.4.0 + * @deprecated 5.0.0 */ function gutenberg_warn_classic_about_blocks() { - $post = get_post(); - - $gutenberg_edit_link = get_edit_post_link( $post->ID, 'raw' ); - - $classic_edit_link = $gutenberg_edit_link; - $classic_edit_link = add_query_arg( - array( - 'classic-editor' => '', - 'hide-block-warning' => '', - ), - $classic_edit_link - ); - - $revisions_link = ''; - if ( wp_revisions_enabled( $post ) ) { - $revisions = wp_get_post_revisions( $post ); - - // If there's only one revision, that won't help. - if ( count( $revisions ) > 1 ) { - reset( $revisions ); // Reset pointer for key(). - $revisions_link = get_edit_post_link( key( $revisions ) ); - } - } - ?> - <style type="text/css"> - #blocks-in-post-dialog .notification-dialog { - position: fixed; - top: 50%; - left: 50%; - width: 500px; - box-sizing: border-box; - transform: translate(-50%, -50%); - margin: 0; - padding: 25px; - max-height: 90%; - background: #fff; - box-shadow: 0 3px 6px rgba(0, 0, 0, 0.3); - line-height: 1.5; - z-index: 1000005; - overflow-y: auto; - } - - @media only screen and (max-height: 480px), screen and (max-width: 450px) { - #blocks-in-post-dialog .notification-dialog { - top: 0; - left: 0; - width: 100%; - height: 100%; - transform: none; - max-height: 100%; - } - } - </style> - - <div id="blocks-in-post-dialog" class="notification-dialog-wrap"> - <div class="notification-dialog-background"></div> - <div class="notification-dialog"> - <div class="blocks-in-post-message"> - <p><?php _e( 'This post was previously edited in Gutenberg. You can continue in the Classic Editor, but you may lose data and formatting.', 'gutenberg' ); ?></p> - <?php - if ( $revisions_link ) { - ?> - <p> - <?php - /* translators: link to the post revisions page */ - printf( __( 'You can also <a href="%s">browse previous revisions</a> and restore a version of the post before it was edited in Gutenberg.', 'gutenberg' ), esc_url( $revisions_link ) ); - ?> - </p> - <?php - } else { - ?> - <p><strong><?php _e( 'Because this post does not have revisions, you will not be able to revert any changes you make in the Classic Editor.', 'gutenberg' ); ?></strong></p> - <?php - } - ?> - </div> - <p> - <a class="button button-primary blocks-in-post-gutenberg-button" href="<?php echo esc_url( $gutenberg_edit_link ); ?>"><?php _e( 'Edit in Gutenberg', 'gutenberg' ); ?></a> - <button type="button" class="button blocks-in-post-classic-button"><?php _e( 'Continue to Classic Editor', 'gutenberg' ); ?></button> - </p> - </div> - </div> - - <script type="text/javascript"> - /* <![CDATA[ */ - ( function( $ ) { - var dialog = {}; - - dialog.init = function() { - // The modal - dialog.warning = $( '#blocks-in-post-dialog' ); - // Get the links and buttons within the modal. - dialog.warningTabbables = dialog.warning.find( 'a, button' ); - - // Get the text within the modal. - dialog.rawMessage = dialog.warning.find( '.blocks-in-post-message' ).text(); - - // Hide all the #wpwrap content from assistive technologies. - $( '#wpwrap' ).attr( 'aria-hidden', 'true' ); - - // Detach the warning modal from its position and append it to the body. - $( document.body ) - .addClass( 'modal-open' ) - .append( dialog.warning.detach() ); - - // Reveal the modal and set focus on the Gutenberg button. - dialog.warning - .removeClass( 'hidden' ) - .find( '.blocks-in-post-gutenberg-button' ).focus(); - - // Attach event handlers. - dialog.warningTabbables.on( 'keydown', dialog.constrainTabbing ); - dialog.warning.on( 'click', '.blocks-in-post-classic-button', dialog.dismissWarning ); - - // Make screen readers announce the warning message after a short delay (necessary for some screen readers). - setTimeout( function() { - wp.a11y.speak( wp.sanitize.stripTags( dialog.rawMessage.replace( /\s+/g, ' ' ) ), 'assertive' ); - }, 1000 ); - }; - - dialog.constrainTabbing = function( event ) { - var firstTabbable, lastTabbable; - - if ( 9 !== event.which ) { - return; - } - - firstTabbable = dialog.warningTabbables.first()[0]; - lastTabbable = dialog.warningTabbables.last()[0]; - - if ( lastTabbable === event.target && ! event.shiftKey ) { - firstTabbable.focus(); - event.preventDefault(); - } else if ( firstTabbable === event.target && event.shiftKey ) { - lastTabbable.focus(); - event.preventDefault(); - } - }; - - dialog.dismissWarning = function() { - // Hide modal. - dialog.warning.remove(); - $( '#wpwrap' ).removeAttr( 'aria-hidden' ); - $( 'body' ).removeClass( 'modal-open' ); - }; - - $( document ).ready( dialog.init ); - } )( jQuery ); - /* ]]> */ - </script> - <?php + _deprecated_function( __FUNCTION__, '5.0.0' ); } /** @@ -296,12 +86,8 @@ function gutenberg_warn_classic_about_blocks() { * consume the notice and display it with the Notices API. * * @since 4.5.0 + * @deprecated 5.0.0 */ function gutenberg_show_privacy_policy_help_text() { - if ( is_gutenberg_page() && has_action( 'edit_form_after_title', array( 'WP_Privacy_Policy_Content', 'notice' ) ) ) { - remove_action( 'edit_form_after_title', array( 'WP_Privacy_Policy_Content', 'notice' ) ); - - WP_Privacy_Policy_Content::notice( get_post() ); - } + _deprecated_function( __FUNCTION__, '5.0.0' ); } -add_action( 'admin_notices', 'gutenberg_show_privacy_policy_help_text' ); From 2ca9e14639cd968983b4fc2a78763821d42acab7 Mon Sep 17 00:00:00 2001 From: Marko Savic <savicmarko1985@gmail.com> Date: Mon, 28 Jan 2019 15:18:29 +0100 Subject: [PATCH 260/691] Rnmobile/upload media file (#13128) Upload media file --- .../block-library/src/image/edit.native.js | 165 ++++++++++++++---- packages/components/src/index.native.js | 1 + .../components/src/spinner/index.native.js | 13 ++ .../media-placeholder/index.native.js | 10 +- .../media-placeholder/styles.native.scss | 5 +- 5 files changed, 156 insertions(+), 38 deletions(-) create mode 100644 packages/components/src/spinner/index.native.js diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index 476dea2981b15..eb73ef558542d 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -1,68 +1,167 @@ /** * External dependencies */ +import React from 'react'; import { View, Image, TextInput } from 'react-native'; -import RNReactNativeGutenbergBridge from 'react-native-gutenberg-bridge'; +import { + subscribeMediaUpload, + onMediaLibraryPressed, + onUploadMediaPressed, + onCapturePhotoPressed, + onImageQueryReattach, +} from 'react-native-gutenberg-bridge'; /** * Internal dependencies */ import { MediaPlaceholder, RichText, BlockControls } from '@wordpress/editor'; -import { Toolbar, ToolbarButton } from '@wordpress/components'; -import { Component } from '@wordpress/element'; +import { Toolbar, ToolbarButton, Spinner } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import ImageSize from './image-size'; +import { isURL } from '@wordpress/url'; -class ImageEdit extends Component { - constructor() { - super( ...arguments ); - this.onMediaLibraryPress = this.onMediaLibraryPress.bind( this ); +const MEDIA_ULOAD_STATE_UPLOADING = 1; +const MEDIA_ULOAD_STATE_SUCCEEDED = 2; +const MEDIA_ULOAD_STATE_FAILED = 3; + +export default class ImageEdit extends React.Component { + constructor( props ) { + super( props ); + + this.state = { + progress: 0, + isUploadInProgress: false, + }; + + this.mediaUpload = this.mediaUpload.bind( this ); + this.addMediaUploadListener = this.addMediaUploadListener.bind( this ); + this.removeMediaUploadListener = this.removeMediaUploadListener.bind( this ); + this.finishMediaUploadWithSuccess = this.finishMediaUploadWithSuccess.bind( this ); + this.finishMediaUploadWithFailure = this.finishMediaUploadWithFailure.bind( this ); + } + + componentDidMount() { + const { attributes } = this.props; + + if ( attributes.id && ! isURL( attributes.url ) ) { + this.addMediaUploadListener(); + onImageQueryReattach(); + } + } + + componentWillUnmount() { + this.removeMediaUploadListener(); + } + + mediaUpload( payload ) { + const { attributes } = this.props; + + if ( payload.mediaId !== attributes.id ) { + return; + } + + switch ( payload.state ) { + case MEDIA_ULOAD_STATE_UPLOADING: + this.setState( { progress: payload.progress, isUploadInProgress: true } ); + break; + case MEDIA_ULOAD_STATE_SUCCEEDED: + this.finishMediaUploadWithSuccess( payload ); + break; + case MEDIA_ULOAD_STATE_FAILED: + this.finishMediaUploadWithFailure( payload ); + break; + } } - onUploadPress() { - // This method should present an image picker from - // the device. - //TODO: Implement upload image method. + finishMediaUploadWithSuccess( payload ) { + const { setAttributes } = this.props; + + setAttributes( { url: payload.mediaUrl, id: payload.mediaServerId } ); + this.setState( { isUploadInProgress: false } ); + + this.removeMediaUploadListener(); } - onMediaLibraryPress() { - RNReactNativeGutenbergBridge.onMediaLibraryPress( ( mediaUrl ) => { - if ( mediaUrl ) { - this.props.setAttributes( { url: mediaUrl } ); - } + finishMediaUploadWithFailure( payload ) { + const { setAttributes } = this.props; + + setAttributes( { url: payload.mediaUrl, id: payload.mediaId } ); + this.setState( { isUploadInProgress: false } ); + + this.removeMediaUploadListener(); + } + + addMediaUploadListener() { + this.subscriptionParentMediaUpload = subscribeMediaUpload( ( payload ) => { + this.mediaUpload( payload ); } ); } - toolbarEditButton() { - return ( - <Toolbar> - <ToolbarButton - className="components-toolbar__control" - label={ __( 'Edit image' ) } - icon="edit" - onClick={ this.onMediaLibraryPress } - /> - </Toolbar> - ); + removeMediaUploadListener() { + if ( this.subscriptionParentMediaUpload ) { + this.subscriptionParentMediaUpload.remove(); + } } render() { const { attributes, isSelected, setAttributes } = this.props; const { url, caption, height, width } = attributes; + const onMediaLibraryButtonPressed = () => { + onMediaLibraryPressed( ( mediaId, mediaUrl ) => { + if ( mediaUrl ) { + setAttributes( { id: mediaId, url: mediaUrl } ); + } + } ); + }; + if ( ! url ) { + const onUploadMediaButtonPressed = () => { + onUploadMediaPressed( ( mediaId, mediaUri ) => { + if ( mediaUri ) { + this.addMediaUploadListener( ); + setAttributes( { url: mediaUri, id: mediaId } ); + } + } ); + }; + + const onCapturePhotoButtonPressed = () => { + onCapturePhotoPressed( ( mediaId, mediaUri ) => { + if ( mediaUri ) { + this.addMediaUploadListener( ); + setAttributes( { url: mediaUri, id: mediaId } ); + } + } ); + }; + return ( <MediaPlaceholder - onUploadPress={ this.onUploadPress } - onMediaLibraryPress={ this.onMediaLibraryPress } + onUploadMediaPressed={ onUploadMediaButtonPressed } + onMediaLibraryPressed={ onMediaLibraryButtonPressed } + onCapturePhotoPressed={ onCapturePhotoButtonPressed } /> ); } + const toolbarEditButton = ( + <Toolbar> + <ToolbarButton + label={ __( 'Edit image' ) } + icon="edit" + onClick={ onMediaLibraryButtonPressed } + /> + </Toolbar> + ); + + const showSpinner = this.state.isUploadInProgress; + const opacity = this.state.isUploadInProgress ? 0.3 : 1; + const progress = this.state.progress * 100; + return ( <View style={ { flex: 1 } }> + { showSpinner && <Spinner progress={ progress } /> } <BlockControls> - { this.toolbarEditButton() } + { toolbarEditButton } </BlockControls> <ImageSize src={ url } > { ( sizes ) => { @@ -84,7 +183,7 @@ class ImageEdit extends Component { return ( <View style={ { flex: 1 } } > <Image - style={ { width: finalWidth, height: finalHeight } } + style={ { width: finalWidth, height: finalHeight, opacity } } resizeMethod="scale" source={ { uri: url } } key={ url } @@ -99,7 +198,7 @@ class ImageEdit extends Component { style={ { textAlign: 'center' } } underlineColorAndroid="transparent" value={ caption } - placeholder={ 'Write caption…' } + placeholder={ __( 'Write caption…' ) } onChangeText={ ( newCaption ) => setAttributes( { caption: newCaption } ) } /> </View> @@ -108,5 +207,3 @@ class ImageEdit extends Component { ); } } - -export default ImageEdit; diff --git a/packages/components/src/index.native.js b/packages/components/src/index.native.js index cbaae28e089bd..65dd41e713fa3 100644 --- a/packages/components/src/index.native.js +++ b/packages/components/src/index.native.js @@ -5,6 +5,7 @@ export { default as Toolbar } from './toolbar'; export { default as ToolbarButton } from './toolbar-button'; export { default as withSpokenMessages } from './higher-order/with-spoken-messages'; export { default as IconButton } from './icon-button'; +export { default as Spinner } from './spinner'; export { createSlotFill, Slot, Fill, Provider as SlotFillProvider } from './slot-fill'; // Higher-Order Components diff --git a/packages/components/src/spinner/index.native.js b/packages/components/src/spinner/index.native.js new file mode 100644 index 0000000000000..305019bc8b40d --- /dev/null +++ b/packages/components/src/spinner/index.native.js @@ -0,0 +1,13 @@ +import { View } from 'react-native'; + +export default function Spinner( props ) { + const { progress } = props; + + const width = progress + '%'; + + return ( + <View style={ { flex: 1, height: 5, backgroundColor: '#c8d7e1' } }> + <View style={ { width, height: 5, backgroundColor: '#0087be' } } /> + </View> + ); +} diff --git a/packages/editor/src/components/media-placeholder/index.native.js b/packages/editor/src/components/media-placeholder/index.native.js index 6a64523a43962..0b70472c2407f 100644 --- a/packages/editor/src/components/media-placeholder/index.native.js +++ b/packages/editor/src/components/media-placeholder/index.native.js @@ -5,18 +5,22 @@ import { View, Text, Button } from 'react-native'; import styles from './styles.scss'; +import { __ } from '@wordpress/i18n'; + function MediaPlaceholder( props ) { return ( <View style={ styles.emptyStateContainer }> <Text style={ styles.emptyStateTitle }> - Image + { __( 'Image' ) } </Text> <Text style={ styles.emptyStateDescription }> - Select an image from your library. + { __( 'Upload a new image or select a file from your library.' ) } </Text> <View style={ styles.emptyStateButtonsContainer }> - <Button title="Media Library" onPress={ props.onMediaLibraryPress } /> + <Button title={ __( 'Device Library' ) } onPress={ props.onUploadMediaPressed } /> + <Button title={ __( 'Take photo' ) } onPress={ props.onCapturePhotoPressed } /> </View> + <Button title={ __( 'Media Library' ) } onPress={ props.onMediaLibraryPressed } /> </View> ); } diff --git a/packages/editor/src/components/media-placeholder/styles.native.scss b/packages/editor/src/components/media-placeholder/styles.native.scss index e05681743ee96..0014afe089ac5 100644 --- a/packages/editor/src/components/media-placeholder/styles.native.scss +++ b/packages/editor/src/components/media-placeholder/styles.native.scss @@ -18,6 +18,9 @@ } .emptyStateButtonsContainer { + margin-top: 15; + margin-bottom: 15; flex-direction: row; - align-items: center; + justify-content: space-evenly; + width: 100%; } From cfb269d219e36fbeb917fe0bea6fecf4356a7187 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Mon, 28 Jan 2019 09:23:26 -0500 Subject: [PATCH 261/691] Plugin: Deprecate metabox supports, fall back to core (#13449) * Plugin: Emulate is_block_editor for Gutenberg screen * Plugin: Deprecate metabox supports, fall back to core --- .../backward-compatibility/deprecations.md | 12 + gutenberg.php | 15 +- lib/client-assets.php | 68 +-- lib/meta-box-partial-page.php | 395 ++---------------- lib/register.php | 211 +--------- phpunit/class-meta-box-test.php | 203 --------- 6 files changed, 61 insertions(+), 843 deletions(-) delete mode 100644 phpunit/class-meta-box-test.php diff --git a/docs/designers-developers/developers/backward-compatibility/deprecations.md b/docs/designers-developers/developers/backward-compatibility/deprecations.md index b3a007565f0c8..f2ce4c1ee8d2f 100644 --- a/docs/designers-developers/developers/backward-compatibility/deprecations.md +++ b/docs/designers-developers/developers/backward-compatibility/deprecations.md @@ -44,6 +44,18 @@ The Gutenberg project's deprecation policy is intended to support backward compa - The PHP function `gutenberg_check_if_classic_needs_warning_about_blocks` has been removed. - The PHP function `gutenberg_warn_classic_about_blocks` has been removed. - The PHP function `gutenberg_show_privacy_policy_help_text` has been removed. +- The PHP function `gutenberg_common_scripts_and_styles` has been removed. Use [`wp_common_block_scripts_and_styles`](https://developer.wordpress.org/reference/functions/wp_common_block_scripts_and_styles/) instead. +- The PHP function `gutenberg_enqueue_registered_block_scripts_and_styles` has been removed. Use [`wp_enqueue_registered_block_scripts_and_styles`](https://developer.wordpress.org/reference/functions/wp_enqueue_registered_block_scripts_and_styles/) instead. +- The PHP function `gutenberg_meta_box_save` has been removed. +- The PHP function `gutenberg_meta_box_save_redirect` has been removed. +- The PHP function `gutenberg_filter_meta_boxes` has been removed. +- The PHP function `gutenberg_intercept_meta_box_render` has been removed. +- The PHP function `gutenberg_override_meta_box_callback` has been removed. +- The PHP function `gutenberg_show_meta_box_warning` has been removed. +- The PHP function `the_gutenberg_metaboxes` has been removed. Use [`the_block_editor_meta_boxes`](https://developer.wordpress.org/reference/functions/the_block_editor_meta_boxes/) instead. +- The PHP function `gutenberg_meta_box_post_form_hidden_fields` has been removed. Use [`the_block_editor_meta_box_post_form_hidden_fields`](https://developer.wordpress.org/reference/functions/the_block_editor_meta_box_post_form_hidden_fields/) instead. +- The PHP function `gutenberg_toggle_custom_fields` has been removed. +- The PHP function `gutenberg_collect_meta_box_data` has been removed. Use [`register_and_do_post_meta_boxes`](https://developer.wordpress.org/reference/functions/register_and_do_post_meta_boxes/) instead. ## 4.5.0 - `Dropdown.refresh()` has been deprecated as the contained `Popover` is now automatically refreshed. diff --git a/gutenberg.php b/gutenberg.php index 09d759a64c488..f91df4b918597 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -52,8 +52,8 @@ function the_gutenberg_project() { <div class="block-editor gutenberg"> <h1 class="screen-reader-text"><?php echo esc_html( $post_type_object->labels->edit_item ); ?></h1> <div id="editor" class="block-editor__container gutenberg__editor"></div> - <div id="metaboxes" style="display: none;"> - <?php the_gutenberg_metaboxes(); ?> + <div id="metaboxes" class="hidden"> + <?php the_block_editor_meta_boxes(); ?> </div> </div> <?php @@ -211,6 +211,15 @@ function gutenberg_init( $return, $post ) { return false; } + // Instruct WordPress that this is the block editor. Without this, a call + // to `is_block_editor()` would yield `false` while editing a post with + // Gutenberg. + // + // [TODO]: This is temporary so long as Gutenberg is implemented to use + // `replace_editor`, rather than allow `edit-form-blocks.php` from core to + // take effect, where this would otherwise be assigned. + get_current_screen()->is_block_editor( true ); + add_action( 'admin_enqueue_scripts', 'gutenberg_editor_scripts_and_styles' ); add_filter( 'screen_options_show_screen', '__return_false' ); add_filter( 'admin_body_class', 'gutenberg_add_admin_body_class' ); @@ -237,7 +246,7 @@ function gutenberg_init( $return, $post ) { * includes/meta-boxes is typically loaded from edit-form-advanced.php. */ require_once ABSPATH . 'wp-admin/includes/meta-boxes.php'; - gutenberg_collect_meta_box_data(); + register_and_do_post_meta_boxes( $post ); require_once ABSPATH . 'wp-admin/admin-header.php'; the_gutenberg_project(); diff --git a/lib/client-assets.php b/lib/client-assets.php index 15ffc18d86516..c90cb8a05a8ff 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -721,72 +721,26 @@ function gutenberg_prepare_blocks_for_js() { * are loaded last. * * @since 0.4.0 + * @deprecated 5.0.0 wp_common_block_scripts_and_styles */ function gutenberg_common_scripts_and_styles() { - if ( ! is_gutenberg_page() && is_admin() ) { - return; - } - - // Enqueue basic styles built out of Gutenberg through `npm build`. - wp_enqueue_style( 'wp-block-library' ); - - /* - * Enqueue block styles built through plugins. This lives in a separate - * action for a couple of reasons: (1) we want to load these assets - * (usually stylesheets) in *both* frontend and editor contexts, and (2) - * one day we may need to be smarter about whether assets are included - * based on whether blocks are actually present on the page. - */ + _deprecated_function( __FUNCTION__, '5.0.0', 'wp_common_block_scripts_and_styles' ); - /** - * Fires after enqueuing block assets for both editor and front-end. - * - * Call `add_action` on any hook before 'wp_enqueue_scripts'. - * - * In the function call you supply, simply use `wp_enqueue_script` and - * `wp_enqueue_style` to add your functionality to the Gutenberg editor. - * - * @since 0.4.0 - */ - do_action( 'enqueue_block_assets' ); + wp_common_block_scripts_and_styles(); } -add_action( 'wp_enqueue_scripts', 'gutenberg_common_scripts_and_styles' ); -add_action( 'admin_enqueue_scripts', 'gutenberg_common_scripts_and_styles' ); /** * Enqueues registered block scripts and styles, depending on current rendered * context (only enqueuing editor scripts while in context of the editor). * * @since 2.0.0 + * @deprecated 5.0.0 wp_enqueue_registered_block_scripts_and_styles */ function gutenberg_enqueue_registered_block_scripts_and_styles() { - $is_editor = ( 'enqueue_block_editor_assets' === current_action() ); + _deprecated_function( __FUNCTION__, '5.0.0', 'wp_enqueue_registered_block_scripts_and_styles' ); - $block_registry = WP_Block_Type_Registry::get_instance(); - foreach ( $block_registry->get_all_registered() as $block_name => $block_type ) { - // Front-end styles. - if ( ! empty( $block_type->style ) ) { - wp_enqueue_style( $block_type->style ); - } - - // Front-end script. - if ( ! empty( $block_type->script ) ) { - wp_enqueue_script( $block_type->script ); - } - - // Editor styles. - if ( $is_editor && ! empty( $block_type->editor_style ) ) { - wp_enqueue_style( $block_type->editor_style ); - } - - // Editor script. - if ( $is_editor && ! empty( $block_type->editor_script ) ) { - wp_enqueue_script( $block_type->editor_script ); - } - } + wp_enqueue_registered_block_scripts_and_styles(); } -add_action( 'enqueue_block_assets', 'gutenberg_enqueue_registered_block_scripts_and_styles' ); -add_action( 'enqueue_block_editor_assets', 'gutenberg_enqueue_registered_block_scripts_and_styles' ); /** * Assigns a default editor template with a default block by post format, if @@ -1074,10 +1028,10 @@ function gutenberg_editor_scripts_and_styles( $hook ) { $meta_box_url = admin_url( 'post.php' ); $meta_box_url = add_query_arg( array( - 'post' => $post->ID, - 'action' => 'edit', - 'classic-editor' => true, - 'meta_box' => true, + 'post' => $post->ID, + 'action' => 'edit', + 'meta-box-loader' => true, + '_wpnonce' => wp_create_nonce( 'meta-box-loader' ), ), $meta_box_url ); @@ -1277,7 +1231,7 @@ function gutenberg_editor_scripts_and_styles( $hook ) { $init_script = <<<JS ( function() { - window._wpLoadGutenbergEditor = new Promise( function( resolve ) { + window._wpLoadGutenbergEditor = window._wpLoadBlockEditor = new Promise( function( resolve ) { wp.domReady( function() { resolve( wp.editPost.initializeEditor( 'editor', "%s", %d, %s, %s ) ); } ); diff --git a/lib/meta-box-partial-page.php b/lib/meta-box-partial-page.php index aec680bd8dd24..3e78a00ec506e 100644 --- a/lib/meta-box-partial-page.php +++ b/lib/meta-box-partial-page.php @@ -14,432 +14,95 @@ * The HTML returned by this page is irrelevant, it's being called in AJAX ignoring its output * * @since 1.8.0 + * @deprecated 5.0.0 */ function gutenberg_meta_box_save() { - /** - * Needs classic editor to be active. - * - * @see https://github.com/WordPress/gutenberg/commit/bdf94e65ac0c10b3ce5d8e214f0c9e1081997d9b - */ - if ( ! isset( $_REQUEST['classic-editor'] ) ) { - return; - } - - /** - * The meta_box param as long as it is set on the wp-admin/post.php request - * will trigger this partial page. - * - * Essentially all that happens is we try to load in the scripts from admin_head - * and admin_footer to mimic the assets for a typical post.php. - * - * @in_the_future Hopefully the meta box param can be changed to a location, - * or contenxt, so that we can use this API to render meta boxes that appear, - * in the sidebar vs. regular content, or core meta boxes vs others. For now - * a request like http://local.wordpress.dev/wp-admin/post.php?post=40007&action=edit&meta_box=taco - * works just fine! - */ - if ( ! isset( $_REQUEST['meta_box'] ) || 'post.php' !== $GLOBALS['pagenow'] ) { - return; - } - - // Ths action is not needed since it's an XHR call. - remove_action( 'admin_head', 'wp_admin_canonical_url' ); - the_gutenberg_metaboxes(); + _deprecated_function( __FUNCTION__, '5.0.0' ); } -add_action( 'do_meta_boxes', 'gutenberg_meta_box_save', 1000 ); - /** * Allows the meta box endpoint to correctly redirect to the meta box endpoint * when a post is saved. * * @since 1.5.0 + * @deprecated 5.0.0 * * @param string $location The location of the meta box, 'side', 'normal'. - * @param int $post_id Post ID. * @return string Modified location of the meta box. - * - * @hooked redirect_post_location priority 10 */ -function gutenberg_meta_box_save_redirect( $location, $post_id ) { - if ( isset( $_REQUEST['gutenberg_meta_boxes'] ) ) { - $location = add_query_arg( - array( - 'meta_box' => true, - 'action' => 'edit', - 'classic-editor' => true, - 'post' => $post_id, - ), - admin_url( 'post.php' ) - ); - } +function gutenberg_meta_box_save_redirect( $location ) { + _deprecated_function( __FUNCTION__, '5.0.0' ); return $location; } -add_filter( 'redirect_post_location', 'gutenberg_meta_box_save_redirect', 10, 2 ); - /** * Filter out core meta boxes as well as the post thumbnail. * * @since 1.5.0 + * @deprecated 5.0.0 * * @param array $meta_boxes Meta box data. * @return array Meta box data without core meta boxes. */ function gutenberg_filter_meta_boxes( $meta_boxes ) { - $core_side_meta_boxes = array( - 'submitdiv', - 'formatdiv', - 'pageparentdiv', - 'postimagediv', - ); - - $custom_taxonomies = get_taxonomies( - array( - 'show_ui' => true, - ), - 'objects' - ); - - // Following the same logic as meta box generation in: - // https://github.com/WordPress/wordpress-develop/blob/c896326/src/wp-admin/edit-form-advanced.php#L288-L292. - foreach ( $custom_taxonomies as $custom_taxonomy ) { - $core_side_meta_boxes [] = $custom_taxonomy->hierarchical ? - $custom_taxonomy->name . 'div' : - 'tagsdiv-' . $custom_taxonomy->name; - } - - $core_normal_meta_boxes = array( - 'revisionsdiv', - 'postexcerpt', - 'trackbacksdiv', - 'commentstatusdiv', - 'commentsdiv', - 'slugdiv', - 'authordiv', - ); - - // Whether or not to load the 'postcustom' meta box is stored as a user meta - // field so that we're not always loading its assets. - $enable_custom_fields = (bool) get_user_meta( get_current_user_id(), 'enable_custom_fields', true ); - if ( ! $enable_custom_fields ) { - $core_normal_meta_boxes[] = 'postcustom'; - } - - $taxonomy_callbacks_to_unset = array( - 'post_tags_meta_box', - 'post_categories_meta_box', - ); - - foreach ( $meta_boxes as $page => $contexts ) { - foreach ( $contexts as $context => $priorities ) { - foreach ( $priorities as $priority => $boxes ) { - foreach ( $boxes as $name => $data ) { - if ( 'normal' === $context && in_array( $name, $core_normal_meta_boxes ) ) { - unset( $meta_boxes[ $page ][ $context ][ $priority ][ $name ] ); - } elseif ( 'side' === $context && in_array( $name, $core_side_meta_boxes ) ) { - unset( $meta_boxes[ $page ][ $context ][ $priority ][ $name ] ); - } - // Filter out any taxonomies as Gutenberg already provides JS alternative. - if ( isset( $data['callback'] ) && in_array( $data['callback'], $taxonomy_callbacks_to_unset ) ) { - unset( $meta_boxes[ $page ][ $context ][ $priority ][ $name ] ); - } - // Filter out meta boxes that are just registered for back compat. - if ( isset( $data['args']['__back_compat_meta_box'] ) && $data['args']['__back_compat_meta_box'] ) { - unset( $meta_boxes[ $page ][ $context ][ $priority ][ $name ] ); - } - } - } - } - } + _deprecated_function( __FUNCTION__, '5.0.0' ); return $meta_boxes; } -add_filter( 'filter_gutenberg_meta_boxes', 'gutenberg_filter_meta_boxes' ); - /** * Go through the global metaboxes, and override the render callback, so we can trigger our warning if needed. * * @since 1.8.0 + * @deprecated 5.0.0 */ function gutenberg_intercept_meta_box_render() { - global $wp_meta_boxes; - - foreach ( $wp_meta_boxes as $post_type => $contexts ) { - foreach ( $contexts as $context => $priorities ) { - foreach ( $priorities as $priority => $boxes ) { - foreach ( $boxes as $id => $box ) { - if ( ! is_array( $box ) ) { - continue; - } - if ( ! is_array( $wp_meta_boxes[ $post_type ][ $context ][ $priority ][ $id ]['args'] ) ) { - $wp_meta_boxes[ $post_type ][ $context ][ $priority ][ $id ]['args'] = array(); - } - if ( ! isset( $wp_meta_boxes[ $post_type ][ $context ][ $priority ][ $id ]['args']['__original_callback'] ) ) { - $wp_meta_boxes[ $post_type ][ $context ][ $priority ][ $id ]['args']['__original_callback'] = $box['callback']; - $wp_meta_boxes[ $post_type ][ $context ][ $priority ][ $id ]['callback'] = 'gutenberg_override_meta_box_callback'; - } - } - } - } - } + _deprecated_function( __FUNCTION__, '5.0.0' ); } -add_action( 'submitpost_box', 'gutenberg_intercept_meta_box_render' ); -add_action( 'submitpage_box', 'gutenberg_intercept_meta_box_render' ); -add_action( 'edit_page_form', 'gutenberg_intercept_meta_box_render' ); -add_action( 'edit_form_advanced', 'gutenberg_intercept_meta_box_render' ); /** * Check if this metabox only exists for back compat purposes, show a warning if it doesn't. * * @since 1.8.0 - * - * @param mixed $object The object being operated on, on this screen. - * @param array $box The current meta box definition. + * @deprecated 5.0.0 */ -function gutenberg_override_meta_box_callback( $object, $box ) { - $callback = $box['args']['__original_callback']; - unset( $box['args']['__original_callback'] ); - - $block_compatible = true; - if ( isset( $box['args']['__block_editor_compatible_meta_box'] ) ) { - $block_compatible = (bool) $box['args']['__block_editor_compatible_meta_box']; - unset( $box['args']['__block_editor_compatible_meta_box'] ); - } - - if ( isset( $box['args']['__back_compat_meta_box'] ) ) { - $block_compatible |= (bool) $box['args']['__back_compat_meta_box']; - unset( $box['args']['__back_compat_meta_box'] ); - } - - if ( ! $block_compatible ) { - gutenberg_show_meta_box_warning( $callback ); - } - - call_user_func( $callback, $object, $box ); +function gutenberg_override_meta_box_callback() { + _deprecated_function( __FUNCTION__, '5.0.0' ); } /** * Display a warning in the metabox that the current plugin is causing the fallback to the old editor. * * @since 1.8.0 - * - * @param callable $callback The function that a plugin has defined to render a meta box. + * @deprecated 5.0.0 */ -function gutenberg_show_meta_box_warning( $callback ) { - // Only show the warning when WP_DEBUG is enabled. - if ( ! WP_DEBUG ) { - return; - } - - // Don't show in the Gutenberg meta box UI. - if ( ! isset( $_REQUEST['classic-editor'] ) ) { - return; - } - - try { - if ( is_array( $callback ) ) { - $reflection = new ReflectionMethod( $callback[0], $callback[1] ); - } else { - $reflection = new ReflectionFunction( $callback ); - } - } catch ( ReflectionException $exception ) { - // We could not properly reflect on the callable, so we abort here. - return; - } - - if ( $reflection->isInternal() ) { - return; - } - - $filename = $reflection->getFileName(); - if ( strpos( $filename, WP_PLUGIN_DIR ) !== 0 ) { - return; - } - - $filename = str_replace( WP_PLUGIN_DIR, '', $filename ); - $filename = preg_replace( '|^/([^/]*/).*$|', '\\1', $filename ); - - $plugins = get_plugins(); - foreach ( $plugins as $name => $plugin ) { - if ( strpos( $name, $filename ) === 0 ) { - ?> - <div class="error inline"> - <p> - <?php - /* translators: %s is the name of the plugin that generated this meta box. */ - printf( __( 'Gutenberg incompatible meta box, from the "%s" plugin.', 'gutenberg' ), $plugin['Name'] ); - ?> - </p> - </div> - <?php - } - } +function gutenberg_show_meta_box_warning() { + _deprecated_function( __FUNCTION__, '5.0.0' ); } /** * Renders the WP meta boxes forms. * * @since 1.8.0 + * @deprecated 5.0.0 the_block_editor_meta_boxes */ function the_gutenberg_metaboxes() { - global $post, $current_screen, $wp_meta_boxes; - - // Handle meta box state. - $_original_meta_boxes = $wp_meta_boxes; - - /** - * Fires right before the meta boxes are rendered. - * - * This allows for the filtering of meta box data, that should already be - * present by this point. Do not use as a means of adding meta box data. - * - * By default gutenberg_filter_meta_boxes() is hooked in and can be - * unhooked to restore core meta boxes. - * - * @param array $wp_meta_boxes Global meta box state. - */ - $wp_meta_boxes = apply_filters( 'filter_gutenberg_meta_boxes', $wp_meta_boxes ); - $locations = array( 'side', 'normal', 'advanced' ); - $priorities = array( 'high', 'sorted', 'core', 'default', 'low' ); - // Render meta boxes. - ?> - <form class="metabox-base-form"> - <?php gutenberg_meta_box_post_form_hidden_fields( $post ); ?> - </form> - <form id="toggle-custom-fields-form" method="post" action="<?php echo esc_attr( admin_url( 'admin-post.php' ) ); ?>"> - <?php wp_nonce_field( 'toggle_custom_fields' ); ?> - <input type="hidden" name="action" value="toggle_custom_fields" /> - </form> - <?php foreach ( $locations as $location ) : ?> - <form class="metabox-location-<?php echo esc_attr( $location ); ?>"> - <div id="poststuff" class="sidebar-open"> - <div id="postbox-container-2" class="postbox-container"> - <?php - do_meta_boxes( - $current_screen, - $location, - $post - ); - ?> - </div> - </div> - </form> - <?php endforeach; ?> - <?php - - $meta_boxes_per_location = array(); - foreach ( $locations as $location ) { - $meta_boxes_per_location[ $location ] = array(); - foreach ( $priorities as $priority ) { - if ( isset( $wp_meta_boxes[ $current_screen->id ][ $location ][ $priority ] ) ) { - $meta_boxes = (array) $wp_meta_boxes[ $current_screen->id ][ $location ][ $priority ]; - foreach ( $meta_boxes as $meta_box ) { - if ( false == $meta_box || ! $meta_box['title'] ) { - continue; - } - - $meta_boxes_per_location[ $location ][] = array( - 'id' => $meta_box['id'], - 'title' => $meta_box['title'], - ); - } - } - } - } + _deprecated_function( __FUNCTION__, '5.0.0', 'the_block_editor_meta_boxes' ); - /** - * Sadly we probably can not add this data directly into editor settings. - * - * ACF and other meta boxes need admin_head to fire for meta box registry. - * admin_head fires after admin_enqueue_scripts which is where we create our - * editor instance. If a cleaner solution can be imagined, please change - * this, and try to get this data to load directly into the editor settings. - */ - $script = 'window._wpLoadGutenbergEditor.then( function() { - wp.data.dispatch( \'core/edit-post\' ).setAvailableMetaBoxesPerLocation( ' . wp_json_encode( $meta_boxes_per_location ) . ' ); - } );'; - - wp_add_inline_script( 'wp-edit-post', $script ); - - /** - * When `wp-edit-post` is output in the `<head>`, the inline script needs to be manually printed. Otherwise, - * metaboxes will not display because inline scripts for `wp-edit-post` will not be printed again after this point. - * - * @see https://github.com/WordPress/gutenberg/issues/6963 - */ - if ( wp_script_is( 'wp-edit-post', 'done' ) ) { - printf( "<script type='text/javascript'>\n%s\n</script>\n", trim( $script ) ); - } - - /** - * If the 'postcustom' meta box is enabled, then we need to perform some - * extra initialization on it. - */ - $enable_custom_fields = (bool) get_user_meta( get_current_user_id(), 'enable_custom_fields', true ); - if ( $enable_custom_fields ) { - $script = "( function( $ ) { - if ( $('#postcustom').length ) { - $( '#the-list' ).wpList( { - addBefore: function( s ) { - s.data += '&post_id=$post->ID'; - return s; - }, - addAfter: function() { - $('table#list-table').show(); - } - }); - } - } )( jQuery );"; - - wp_enqueue_script( 'wp-lists' ); - wp_add_inline_script( 'wp-lists', $script ); - } - - // Reset meta box data. - $wp_meta_boxes = $_original_meta_boxes; + the_block_editor_meta_boxes(); } /** * Renders the hidden form required for the meta boxes form. * - * @param WP_Post $post Current post object. - * * @since 1.8.0 + * @deprecated 5.0.0 the_block_editor_meta_box_post_form_hidden_fields */ -function gutenberg_meta_box_post_form_hidden_fields( $post ) { - $form_extra = ''; - if ( 'auto-draft' === $post->post_status ) { - $form_extra .= "<input type='hidden' id='auto_draft' name='auto_draft' value='1' />"; - } - $form_action = 'editpost'; - $nonce_action = 'update-post_' . $post->ID; - $form_extra .= "<input type='hidden' id='post_ID' name='post_ID' value='" . esc_attr( $post->ID ) . "' />"; - $referer = wp_get_referer(); - $current_user = wp_get_current_user(); - $user_id = $current_user->ID; - wp_nonce_field( $nonce_action ); - ?> - <input type="hidden" id="user-id" name="user_ID" value="<?php echo (int) $user_id; ?>" /> - <input type="hidden" id="hiddenaction" name="action" value="<?php echo esc_attr( $form_action ); ?>" /> - <input type="hidden" id="originalaction" name="originalaction" value="<?php echo esc_attr( $form_action ); ?>" /> - <input type="hidden" id="post_type" name="post_type" value="<?php echo esc_attr( $post->post_type ); ?>" /> - <input type="hidden" id="original_post_status" name="original_post_status" value="<?php echo esc_attr( $post->post_status ); ?>" /> - <input type="hidden" id="referredby" name="referredby" value="<?php echo $referer ? esc_url( $referer ) : ''; ?>" /> - <!-- These fields are not part of the standard post form. Used to redirect back to this page on save. --> - <input type="hidden" name="gutenberg_meta_boxes" value="gutenberg_meta_boxes" /> +function gutenberg_meta_box_post_form_hidden_fields() { + _deprecated_function( __FUNCTION__, '5.0.0', 'the_block_editor_meta_box_post_form_hidden_fields' ); - <?php - if ( 'draft' !== get_post_status( $post ) ) { - wp_original_referer_field( true, 'previous' ); - } - echo $form_extra; - wp_nonce_field( 'meta-box-order', 'meta-box-order-nonce', false ); - wp_nonce_field( 'closedpostboxes', 'closedpostboxesnonce', false ); - // Permalink title nonce. - wp_nonce_field( 'samplepermalink', 'samplepermalinknonce', false ); + the_block_editor_meta_box_post_form_hidden_fields(); } /** @@ -448,18 +111,8 @@ function gutenberg_meta_box_post_form_hidden_fields( $post ) { * user to completely enable or disable the 'postcustom' meta box. * * @since 5.2.0 + * @deprecated 5.0.0 */ function gutenberg_toggle_custom_fields() { - check_admin_referer( 'toggle_custom_fields' ); - - $current_user_id = get_current_user_id(); - if ( $current_user_id ) { - $enable_custom_fields = (bool) get_user_meta( $current_user_id, 'enable_custom_fields', true ); - update_user_meta( $current_user_id, 'enable_custom_fields', ! $enable_custom_fields ); - } - - wp_safe_redirect( wp_get_referer() ); - exit; + _deprecated_function( __FUNCTION__, '5.0.0' ); } - -add_action( 'admin_post_toggle_custom_fields', 'gutenberg_toggle_custom_fields' ); diff --git a/lib/register.php b/lib/register.php index 17694855511f8..ebdc43c7f2e5c 100644 --- a/lib/register.php +++ b/lib/register.php @@ -15,217 +15,10 @@ * Redirects to classic editor if a meta box is incompatible. * * @since 1.5.0 + * @deprecated 5.0.0 register_and_do_post_meta_boxes */ function gutenberg_collect_meta_box_data() { - global $current_screen, $wp_meta_boxes, $post, $typenow; - - $screen = $current_screen; - - // If we are working with an already predetermined post. - if ( isset( $_REQUEST['post'] ) ) { - $post = get_post( absint( $_REQUEST['post'] ) ); - $typenow = $post->post_type; - - if ( ! gutenberg_can_edit_post( $post->ID ) ) { - return; - } - } else { - // Eventually add handling for creating new posts of different types in Gutenberg. - } - $post_type = $post->post_type; - $post_type_object = get_post_type_object( $post_type ); - - if ( ! gutenberg_can_edit_post_type( $post_type ) ) { - return; - } - - // Disable hidden metaboxes because there's no UI to toggle visibility. - add_filter( 'hidden_meta_boxes', '__return_empty_array' ); - - $thumbnail_support = current_theme_supports( 'post-thumbnails', $post_type ) && post_type_supports( $post_type, 'thumbnail' ); - if ( ! $thumbnail_support && 'attachment' === $post_type && $post->post_mime_type ) { - if ( wp_attachment_is( 'audio', $post ) ) { - $thumbnail_support = post_type_supports( 'attachment:audio', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:audio' ); - } elseif ( wp_attachment_is( 'video', $post ) ) { - $thumbnail_support = post_type_supports( 'attachment:video', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:video' ); - } - } - - /* - * WIP: Collect and send information needed to render meta boxes. - * From wp-admin/edit-form-advanced.php - * Relevant code there: - * do_action( 'do_meta_boxes', $post_type, {'normal','advanced','side'}, $post ); - * do_meta_boxes( $post_type, 'side', $post ); - * do_meta_boxes( null, 'normal', $post ); - * do_meta_boxes( null, 'advanced', $post ); - */ - $publish_callback_args = null; - if ( post_type_supports( $post_type, 'revisions' ) && 'auto-draft' !== $post->post_status ) { - $revisions = wp_get_post_revisions( $post->ID ); - - // We should aim to show the revisions meta box only when there are revisions. - if ( count( $revisions ) > 1 ) { - reset( $revisions ); // Reset pointer for key(). - $publish_callback_args = array( - 'revisions_count' => count( $revisions ), - 'revision_id' => key( $revisions ), - ); - add_meta_box( 'revisionsdiv', __( 'Revisions', 'gutenberg' ), 'post_revisions_meta_box', $screen, 'normal', 'core' ); - } - } - - if ( 'attachment' == $post_type ) { - wp_enqueue_script( 'image-edit' ); - wp_enqueue_style( 'imgareaselect' ); - add_meta_box( 'submitdiv', __( 'Save', 'gutenberg' ), 'attachment_submit_meta_box', $screen, 'side', 'core' ); - add_action( 'edit_form_after_title', 'edit_form_image_editor' ); - - if ( wp_attachment_is( 'audio', $post ) ) { - add_meta_box( 'attachment-id3', __( 'Metadata', 'gutenberg' ), 'attachment_id3_data_meta_box', $screen, 'normal', 'core' ); - } - } else { - add_meta_box( 'submitdiv', __( 'Publish', 'gutenberg' ), 'post_submit_meta_box', $screen, 'side', 'core', $publish_callback_args ); - } - - if ( current_theme_supports( 'post-formats' ) && post_type_supports( $post_type, 'post-formats' ) ) { - add_meta_box( 'formatdiv', _x( 'Format', 'post format', 'gutenberg' ), 'post_format_meta_box', $screen, 'side', 'core' ); - } - - // All taxonomies. - foreach ( get_object_taxonomies( $post ) as $tax_name ) { - $taxonomy = get_taxonomy( $tax_name ); - if ( ! $taxonomy->show_ui || false === $taxonomy->meta_box_cb ) { - continue; - } - - $label = $taxonomy->labels->name; - - if ( ! is_taxonomy_hierarchical( $tax_name ) ) { - $tax_meta_box_id = 'tagsdiv-' . $tax_name; - } else { - $tax_meta_box_id = $tax_name . 'div'; - } - - add_meta_box( $tax_meta_box_id, $label, $taxonomy->meta_box_cb, $screen, 'side', 'core', array( 'taxonomy' => $tax_name ) ); - } - - if ( post_type_supports( $post_type, 'page-attributes' ) || count( get_page_templates( $post ) ) > 0 ) { - add_meta_box( 'pageparentdiv', $post_type_object->labels->attributes, 'page_attributes_meta_box', $screen, 'side', 'core' ); - } - - if ( $thumbnail_support && current_user_can( 'upload_files' ) ) { - add_meta_box( 'postimagediv', esc_html( $post_type_object->labels->featured_image ), 'post_thumbnail_meta_box', $screen, 'side', 'low' ); - } - - if ( post_type_supports( $post_type, 'excerpt' ) ) { - add_meta_box( 'postexcerpt', __( 'Excerpt', 'gutenberg' ), 'post_excerpt_meta_box', $screen, 'normal', 'core' ); - } - - if ( post_type_supports( $post_type, 'trackbacks' ) ) { - add_meta_box( 'trackbacksdiv', __( 'Send Trackbacks', 'gutenberg' ), 'post_trackback_meta_box', $screen, 'normal', 'core' ); - } - - if ( post_type_supports( $post_type, 'custom-fields' ) ) { - add_meta_box( 'postcustom', __( 'Custom Fields', 'gutenberg' ), 'post_custom_meta_box', $screen, 'normal', 'core' ); - } - - /** - * Fires in the middle of built-in meta box registration. - * - * @since 2.1.0 - * @deprecated 3.7.0 Use 'add_meta_boxes' instead. - * - * @param WP_Post $post Post object. - */ - do_action( 'dbx_post_advanced', $post ); - - // Allow the Discussion meta box to show up if the post type supports comments, - // or if comments or pings are open. - if ( comments_open( $post ) || pings_open( $post ) || post_type_supports( $post_type, 'comments' ) ) { - add_meta_box( 'commentstatusdiv', __( 'Discussion', 'gutenberg' ), 'post_comment_status_meta_box', $screen, 'normal', 'core' ); - } - - $stati = get_post_stati( array( 'public' => true ) ); - if ( empty( $stati ) ) { - $stati = array( 'publish' ); - } - $stati[] = 'private'; - - if ( in_array( get_post_status( $post ), $stati ) ) { - // If the post type support comments, or the post has comments, allow the - // Comments meta box. - if ( comments_open( $post ) || pings_open( $post ) || $post->comment_count > 0 || post_type_supports( $post_type, 'comments' ) ) { - add_meta_box( 'commentsdiv', __( 'Comments', 'gutenberg' ), 'post_comment_meta_box', $screen, 'normal', 'core' ); - } - } - - if ( ! ( 'pending' == get_post_status( $post ) && ! current_user_can( $post_type_object->cap->publish_posts ) ) ) { - add_meta_box( 'slugdiv', __( 'Slug', 'gutenberg' ), 'post_slug_meta_box', $screen, 'normal', 'core' ); - } - - if ( post_type_supports( $post_type, 'author' ) && current_user_can( $post_type_object->cap->edit_others_posts ) ) { - add_meta_box( 'authordiv', __( 'Author', 'gutenberg' ), 'post_author_meta_box', $screen, 'normal', 'core' ); - } - - // Run the hooks for adding meta boxes for a specific post type. - do_action( 'add_meta_boxes', $post_type, $post ); - do_action( "add_meta_boxes_{$post_type}", $post ); - - // Set up meta box locations. - $locations = array( 'normal', 'advanced', 'side' ); - - // Foreach location run the hooks meta boxes are potentially registered on. - foreach ( $locations as $location ) { - do_action( - 'do_meta_boxes', - $screen, - $location, - $post - ); - } - do_action( 'edit_form_advanced', $post ); - - // Copy meta box state. - $_meta_boxes_copy = $wp_meta_boxes; - - /** - * Documented in lib/meta-box-partial-page.php - * - * @param array $wp_meta_boxes Global meta box state. - */ - $_meta_boxes_copy = apply_filters( 'filter_gutenberg_meta_boxes', $_meta_boxes_copy ); - - // Redirect to classic editor if a meta box is incompatible. - foreach ( $locations as $location ) { - if ( ! isset( $_meta_boxes_copy[ $post->post_type ][ $location ] ) ) { - continue; - } - // Check if we have a meta box that has declared itself incompatible with the block editor. - foreach ( $_meta_boxes_copy[ $post->post_type ][ $location ] as $boxes ) { - foreach ( $boxes as $box ) { - /* - * If __block_editor_compatible_meta_box is declared as a false-y value, - * the meta box is not compatible with the block editor. - */ - if ( is_array( $box['args'] ) - && isset( $box['args']['__block_editor_compatible_meta_box'] ) - && ! $box['args']['__block_editor_compatible_meta_box'] ) { - $incompatible_meta_box = true; - ?> - <script type="text/javascript"> - var joiner = '?'; - if ( window.location.search ) { - joiner = '&'; - } - window.location.href += joiner + 'classic-editor'; - </script> - <?php - exit; - } - } - } - } + _deprecated_function( __FUNCTION__, '5.0.0', 'register_and_do_post_meta_boxes' ); } /** diff --git a/phpunit/class-meta-box-test.php b/phpunit/class-meta-box-test.php deleted file mode 100644 index 805737a4a54d7..0000000000000 --- a/phpunit/class-meta-box-test.php +++ /dev/null @@ -1,203 +0,0 @@ -<?php -/** - * Test for meta box integration. - * - * @package Gutenberg - */ - -/** - * Tests meta box integration. - * - * Most of the PHP portion of the meta box integration is not testeable due to - * WordPress's architecture. These tests cover the portions that are testable. - */ -class Meta_Box_Test extends WP_UnitTestCase { - - public function setUp() { - parent::setUp(); - - $this->meta_boxes = array( - 'post' => array( - 'normal' => array( - 'core' => array( - 'revisionsdiv' => array( - 'id' => 'revisionsdiv', - 'title' => 'Revisions', - 'callback' => 'post_revisions_meta_box', - 'args' => null, - ), - 'postexcerpt' => array( - 'id' => 'postexcerpt', - 'title' => 'Excerpt', - 'callback' => 'post_excerpt_meta_box', - 'args' => null, - ), - 'trackbacksdiv' => array( - 'id' => 'trackbacksdiv', - 'title' => 'Send Trackbacks', - 'callback' => 'post_trackback_meta_box', - 'args' => null, - ), - 'postcustom' => array( - 'id' => 'postcustom', - 'title' => 'Custom Fields', - 'callback' => 'post_custom_meta_box', - 'args' => null, - ), - 'commentstatusdiv' => array( - 'id' => 'commentstatusdiv', - 'title' => 'Discussion', - 'callback' => 'post_comment_status_meta_box', - 'args' => null, - ), - 'commentsdiv' => array( - 'id' => 'commentsdiv', - 'title' => 'Comments', - 'callback' => 'post_comment_meta_box', - 'args' => null, - ), - 'slugdiv' => array( - 'id' => 'slugdiv', - 'title' => 'Slug', - 'callback' => 'post_slug_meta_box', - 'args' => null, - ), - 'authordiv' => array( - 'id' => 'authordiv', - 'title' => 'Author', - 'callback' => 'post_author_meta_box', - 'args' => null, - ), - ), - 'low' => array(), - 'high' => array(), - ), - 'side' => array( - 'core' => array( - 'submitdiv' => array( - 'id' => 'submitdiv', - 'title' => 'Submit', - 'callback' => 'post_submit_meta_box', - 'args' => null, - ), - 'formatdiv' => array( - 'id' => 'formatdiv', - 'title' => 'Format', - 'callback' => 'post_format_meta_box', - 'args' => null, - ), - 'categorydiv' => array( - 'id' => 'categorydiv', - 'title' => 'Categories', - 'callback' => 'post_categories_meta_box', - 'args' => null, - ), - 'tagsdiv-post_tag' => array( - 'id' => 'tagsdiv-post_tag', - 'title' => 'Tags', - 'callback' => 'post_tags_meta_box', - 'args' => null, - ), - 'postimagediv' => array( - 'id' => 'postimagediv', - 'title' => 'Featured Image', - 'callback' => 'post_image_meta_box', - 'args' => null, - ), - ), - 'low' => array(), - ), - ), - ); - } - - /** - * Test filtering of meta box data. - */ - public function test_gutenberg_filter_meta_boxes() { - $meta_boxes = $this->meta_boxes; - // Add in a meta box. - $meta_boxes['post']['normal']['high']['some-meta-box'] = array( 'meta-box-stuff' ); - - $expected_meta_boxes = $this->meta_boxes; - // We expect to remove only core meta boxes. - $expected_meta_boxes['post']['normal']['core'] = array(); - $expected_meta_boxes['post']['side']['core'] = array(); - $expected_meta_boxes['post']['normal']['high']['some-meta-box'] = array( 'meta-box-stuff' ); - - $actual = gutenberg_filter_meta_boxes( $meta_boxes ); - $expected = $expected_meta_boxes; - - $this->assertEquals( $expected, $actual ); - } - - /** - * Test filtering back compat meta boxes - */ - public function test_gutenberg_filter_back_compat_meta_boxes() { - $meta_boxes = $this->meta_boxes; - - // Add in a back compat meta box. - $meta_boxes['post']['normal']['high']['some-meta-box'] = array( - 'id' => 'some-meta-box', - 'title' => 'Some Meta Box', - 'callback' => 'some_meta_box', - 'args' => array( - '__back_compat_meta_box' => true, - ), - ); - - // Add in a normal meta box. - $meta_boxes['post']['normal']['high']['some-other-meta-box'] = array( 'other-meta-box-stuff' ); - - $expected_meta_boxes = $this->meta_boxes; - // We expect to remove only core meta boxes. - $expected_meta_boxes['post']['normal']['core'] = array(); - $expected_meta_boxes['post']['side']['core'] = array(); - $expected_meta_boxes['post']['normal']['high']['some-other-meta-box'] = array( 'other-meta-box-stuff' ); - - $actual = gutenberg_filter_meta_boxes( $meta_boxes ); - $expected = $expected_meta_boxes; - - $this->assertEquals( $expected, $actual ); - } - - /** - * Test filtering of meta box data with taxonomy meta boxes. - * - * By default Gutenberg will provide a much enhanced JavaScript alternative - * to the meta boxes using the standard category and tags meta box callbacks. - */ - public function test_gutenberg_filter_meta_boxes_for_taxonomies() { - $meta_boxes = $this->meta_boxes; - // Add in a meta box. - $meta_boxes['post']['normal']['high']['my-cool-tax'] = array( 'callback' => 'post_tags_meta_box' ); - $meta_boxes['post']['normal']['high']['my-cool-hierarchical-tax'] = array( 'callback' => 'post_categories_meta_box' ); - - $expected_meta_boxes = $this->meta_boxes; - // We expect to remove only core meta boxes. - $expected_meta_boxes['post']['normal']['core'] = array(); - $expected_meta_boxes['post']['side']['core'] = array(); - // We expect the high location to be empty even though we have registered meta boxes. - $expected_meta_boxes['post']['normal']['high'] = array(); - - $actual = gutenberg_filter_meta_boxes( $meta_boxes ); - $expected = $expected_meta_boxes; - - $this->assertEquals( $expected, $actual ); - } - - /** - * Test that a removed meta box remains empty after gutenberg_intercept_meta_box_render() fires. - */ - public function test_gutenberg_intercept_meta_box_render_skips_empty_boxes() { - global $wp_meta_boxes; - - add_meta_box( 'test-intercept-box', 'Test Intercept box', '__return_empty_string', 'post', 'side', 'default' ); - remove_meta_box( 'test-intercept-box', 'post', 'side' ); - - gutenberg_intercept_meta_box_render(); - - $this->assertFalse( $wp_meta_boxes['post']['side']['default']['test-intercept-box'] ); - } -} From 2bd701d399462ebe9e47ccc9109bb10891b050c6 Mon Sep 17 00:00:00 2001 From: Dave Smith <getdavemail@gmail.com> Date: Mon, 28 Jan 2019 14:42:55 +0000 Subject: [PATCH 262/691] =?UTF-8?q?Fixes=20tense=20on=20verb=20=E2=80=9Cas?= =?UTF-8?q?sign=E2=80=9D=20to=20correct=20tense=20(#13541)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously the verb “assign” was using the past tense. Corrected to use the right tense given the context. --- .../developers/block-api/block-templates.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/designers-developers/developers/block-api/block-templates.md b/docs/designers-developers/developers/block-api/block-templates.md index a26d822d0100e..bbc6efe968ebb 100644 --- a/docs/designers-developers/developers/block-api/block-templates.md +++ b/docs/designers-developers/developers/block-api/block-templates.md @@ -88,7 +88,7 @@ add_action( 'init', 'my_add_template_to_posts' ); ## Nested Templates -Container blocks like the columns blocks also support templates. This is achieved by assigned a nested template to the block. +Container blocks like the columns blocks also support templates. This is achieved by assigning a nested template to the block. ```php $template = array( From 32547ddcb3114962667ab3ec263aae3c939c2c0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20Van=C2=A0Durpe?= <iseulde@automattic.com> Date: Mon, 28 Jan 2019 17:24:19 +0100 Subject: [PATCH 263/691] RichText: List: Fix indent/outdent (#12667) * RichText: List: use value to indent/outdent * Add outdent * Support multi outdent * Hold one reference per format * Keep list formats when indenting to new index * Remove unneeded parameter * Rename * Add logic to change list type * Add tests * Add e2e tests * Add some basic docs. Clean up. * Remove duplicate wp-tinymce dependency * Clean up * Add more docs, fix getSelectedListNode * Put duplicate text values in constant --- lib/packages-dependencies.php | 1 - .../blocks/__snapshots__/list.test.js.snap | 42 ++++++ packages/e2e-tests/specs/blocks/list.test.js | 59 ++++++++ .../editor/src/components/rich-text/index.js | 17 +-- .../src/components/rich-text/list-edit.js | 122 +++++++++++----- .../src/components/rich-text/tinymce.js | 5 - packages/rich-text/src/change-list-type.js | 62 ++++++++ packages/rich-text/src/get-line-index.js | 26 ++++ .../rich-text/src/get-parent-line-index.js | 37 +++++ packages/rich-text/src/indent-list-items.js | 91 ++++++++++++ packages/rich-text/src/index.js | 3 + packages/rich-text/src/normalise-formats.js | 41 +++--- packages/rich-text/src/outdent-list-items.js | 50 +++++++ .../rich-text/src/test/change-list-type.js | 74 ++++++++++ .../rich-text/src/test/indent-list-items.js | 133 ++++++++++++++++++ .../rich-text/src/test/normalise-formats.js | 5 +- .../rich-text/src/test/outdent-list-items.js | 96 +++++++++++++ 17 files changed, 785 insertions(+), 79 deletions(-) create mode 100644 packages/rich-text/src/change-list-type.js create mode 100644 packages/rich-text/src/get-line-index.js create mode 100644 packages/rich-text/src/get-parent-line-index.js create mode 100644 packages/rich-text/src/indent-list-items.js create mode 100644 packages/rich-text/src/outdent-list-items.js create mode 100644 packages/rich-text/src/test/change-list-type.js create mode 100644 packages/rich-text/src/test/indent-list-items.js create mode 100644 packages/rich-text/src/test/outdent-list-items.js diff --git a/lib/packages-dependencies.php b/lib/packages-dependencies.php index ece2a6ae2bb86..60a125997f3bc 100644 --- a/lib/packages-dependencies.php +++ b/lib/packages-dependencies.php @@ -133,7 +133,6 @@ ), 'wp-editor' => array( 'lodash', - 'wp-tinymce-lists', 'wp-a11y', 'wp-api-fetch', 'wp-blob', diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap b/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap index 19a5186156809..a4cb6cdb75be5 100644 --- a/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap +++ b/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap @@ -86,6 +86,18 @@ exports[`List should be immeadiately saved on indentation 1`] = ` <!-- /wp:list -->" `; +exports[`List should change the base list type 1`] = ` +"<!-- wp:list {\\"ordered\\":true} --> +<ol><li></li></ol> +<!-- /wp:list -->" +`; + +exports[`List should change the indented list type 1`] = ` +"<!-- wp:list --> +<ul><li>a<ol><li>1</li></ol></li></ul> +<!-- /wp:list -->" +`; + exports[`List should create paragraph on split at end and merge back with content 1`] = ` "<!-- wp:list --> <ul><li>one</li></ul> @@ -102,6 +114,36 @@ exports[`List should create paragraph on split at end and merge back with conten <!-- /wp:list -->" `; +exports[`List should indent and outdent level 1 1`] = ` +"<!-- wp:list --> +<ul><li>a<ul><li>1</li></ul></li></ul> +<!-- /wp:list -->" +`; + +exports[`List should indent and outdent level 1 2`] = ` +"<!-- wp:list --> +<ul><li>a</li><li>1</li></ul> +<!-- /wp:list -->" +`; + +exports[`List should indent and outdent level 2 1`] = ` +"<!-- wp:list --> +<ul><li>a<ul><li>1<ul><li>i</li></ul></li></ul></li></ul> +<!-- /wp:list -->" +`; + +exports[`List should indent and outdent level 2 2`] = ` +"<!-- wp:list --> +<ul><li>a<ul><li>1</li><li>i</li></ul></li></ul> +<!-- /wp:list -->" +`; + +exports[`List should indent and outdent level 2 3`] = ` +"<!-- wp:list --> +<ul><li>a<ul><li>1</li></ul></li><li>i</li></ul> +<!-- /wp:list -->" +`; + exports[`List should split indented list item 1`] = ` "<!-- wp:list --> <ul><li>one<ul><li>two</li><li>three</li></ul></li></ul> diff --git a/packages/e2e-tests/specs/blocks/list.test.js b/packages/e2e-tests/specs/blocks/list.test.js index 1401698c43a11..022c0b88b1336 100644 --- a/packages/e2e-tests/specs/blocks/list.test.js +++ b/packages/e2e-tests/specs/blocks/list.test.js @@ -203,4 +203,63 @@ describe( 'List', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + + it( 'should change the base list type', async () => { + await insertBlock( 'List' ); + await page.click( 'button[aria-label="Convert to ordered list"]' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'should change the indented list type', async () => { + await insertBlock( 'List' ); + await page.keyboard.type( 'a' ); + await page.keyboard.press( 'Enter' ); + await pressKeyWithModifier( 'primary', 'm' ); + await page.keyboard.type( '1' ); + + // Pointer device is needed. Shift+Tab won't focus the toolbar. + // To do: fix so Shift+Tab works. + await page.mouse.move( 200, 300, { steps: 10 } ); + await page.mouse.move( 250, 350, { steps: 10 } ); + + await page.click( 'button[aria-label="Convert to ordered list"]' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'should indent and outdent level 1', async () => { + await insertBlock( 'List' ); + await page.keyboard.type( 'a' ); + await page.keyboard.press( 'Enter' ); + await pressKeyWithModifier( 'primary', 'm' ); + await page.keyboard.type( '1' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + + await pressKeyWithModifier( 'primaryShift', 'm' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'should indent and outdent level 2', async () => { + await insertBlock( 'List' ); + await page.keyboard.type( 'a' ); + await page.keyboard.press( 'Enter' ); + await pressKeyWithModifier( 'primary', 'm' ); + await page.keyboard.type( '1' ); + await page.keyboard.press( 'Enter' ); + await pressKeyWithModifier( 'primary', 'm' ); + await page.keyboard.type( 'i' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + + await pressKeyWithModifier( 'primaryShift', 'm' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + + await pressKeyWithModifier( 'primaryShift', 'm' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); } ); diff --git a/packages/editor/src/components/rich-text/index.js b/packages/editor/src/components/rich-text/index.js index 72d86b8441556..21ae5a91b5cac 100644 --- a/packages/editor/src/components/rich-text/index.js +++ b/packages/editor/src/components/rich-text/index.js @@ -91,7 +91,6 @@ export class RichText extends Component { this.onSplit = this.props.unstableOnSplit; } - this.onSetup = this.onSetup.bind( this ); this.onFocus = this.onFocus.bind( this ); this.onBlur = this.onBlur.bind( this ); this.onChange = this.onChange.bind( this ); @@ -137,15 +136,6 @@ export class RichText extends Component { this.editableRef = node; } - /** - * Sets a reference to the TinyMCE editor instance. - * - * @param {Editor} editor The editor instance as passed by TinyMCE. - */ - onSetup( editor ) { - this.editor = editor; - } - setFocusedElement() { if ( this.props.setFocusedElement ) { this.props.setFocusedElement( this.props.instanceId ); @@ -837,12 +827,12 @@ export class RichText extends Component { <div className={ classes } onFocus={ this.setFocusedElement } > - { isSelected && this.editor && this.multilineTag === 'li' && ( + { isSelected && this.multilineTag === 'li' && ( <ListEdit - editor={ this.editor } onTagNameChange={ onTagNameChange } tagName={ Tagname } - onSyncDOM={ () => this.onChange( this.createRecord() ) } + value={ record } + onChange={ this.onChange } /> ) } { isSelected && ! inlineToolbar && ( @@ -865,7 +855,6 @@ export class RichText extends Component { <Fragment> <TinyMCE tagName={ Tagname } - onSetup={ this.onSetup } style={ style } record={ record } valueToEditableHTML={ this.valueToEditableHTML } diff --git a/packages/editor/src/components/rich-text/list-edit.js b/packages/editor/src/components/rich-text/list-edit.js index cb52fe688d7ef..81de868f62719 100644 --- a/packages/editor/src/components/rich-text/list-edit.js +++ b/packages/editor/src/components/rich-text/list-edit.js @@ -5,6 +5,11 @@ import { Toolbar } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { Fragment } from '@wordpress/element'; +import { + indentListItems, + outdentListItems, + changeListType, +} from '@wordpress/rich-text'; /** * Internal dependencies @@ -13,60 +18,105 @@ import { Fragment } from '@wordpress/element'; import { RichTextShortcut } from './shortcut'; import BlockFormatControls from '../block-format-controls'; -function isListRootSelected( editor ) { - return ( - ! editor.selection || - editor.selection.getNode().closest( 'ol,ul' ) === editor.getBody() - ); -} +const { TEXT_NODE, ELEMENT_NODE } = window.Node; -function isActiveListType( editor, tagName, rootTagName ) { - if ( document.activeElement !== editor.getBody() ) { - return tagName === rootTagName; +/** + * Gets the selected list node, which is the closest list node to the start of + * the selection. + * + * @return {?Element} The selected list node, or undefined if none is selected. + */ +function getSelectedListNode() { + const selection = window.getSelection(); + + if ( selection.rangeCount === 0 ) { + return; } - const listItem = editor.selection.getNode(); - const list = listItem.closest( 'ol,ul' ); + let { startContainer } = selection.getRangeAt( 0 ); + + if ( startContainer.nodeType === TEXT_NODE ) { + startContainer = startContainer.parentNode; + } - if ( ! list ) { + if ( startContainer.nodeType !== ELEMENT_NODE ) { return; } - return list.nodeName.toLowerCase() === tagName; + const rootNode = startContainer.closest( '*[contenteditable]' ); + + if ( ! rootNode || ! rootNode.contains( startContainer ) ) { + return; + } + + return startContainer.closest( 'ol,ul' ); } -export const ListEdit = ( { editor, onTagNameChange, tagName, onSyncDOM } ) => ( +/** + * Whether or not the root list is selected. + * + * @return {boolean} True if the root list or nothing is selected, false if an + * inner list is selected. + */ +function isListRootSelected() { + const listNode = getSelectedListNode(); + + // Consider the root list selected if nothing is selected. + return ! listNode || listNode.contentEditable === 'true'; +} + +/** + * Wether or not the selected list has the given tag name. + * + * @param {string} tagName The tag name the list should have. + * @param {string} rootTagName The current root tag name, to compare with in + * case nothing is selected. + * + * @return {boolean} [description] + */ +function isActiveListType( tagName, rootTagName ) { + const listNode = getSelectedListNode(); + + if ( ! listNode ) { + return tagName === rootTagName; + } + + return listNode.nodeName.toLowerCase() === tagName; +} + +export const ListEdit = ( { + onTagNameChange, + tagName, + value, + onChange, +} ) => ( <Fragment> <RichTextShortcut type="primary" character="[" onUse={ () => { - editor.execCommand( 'Outdent' ); - onSyncDOM(); + onChange( outdentListItems( value ) ); } } /> <RichTextShortcut type="primary" character="]" onUse={ () => { - editor.execCommand( 'Indent' ); - onSyncDOM(); + onChange( indentListItems( value, { type: tagName } ) ); } } /> <RichTextShortcut type="primary" character="m" onUse={ () => { - editor.execCommand( 'Indent' ); - onSyncDOM(); + onChange( indentListItems( value, { type: tagName } ) ); } } /> <RichTextShortcut type="primaryShift" character="m" onUse={ () => { - editor.execCommand( 'Outdent' ); - onSyncDOM(); + onChange( outdentListItems( value ) ); } } /> <BlockFormatControls> @@ -75,43 +125,39 @@ export const ListEdit = ( { editor, onTagNameChange, tagName, onSyncDOM } ) => ( onTagNameChange && { icon: 'editor-ul', title: __( 'Convert to unordered list' ), - isActive: isActiveListType( editor, 'ul', tagName ), + isActive: isActiveListType( 'ul', tagName ), onClick() { - if ( isListRootSelected( editor ) ) { + onChange( changeListType( value, { type: 'ul' } ) ); + + if ( isListRootSelected() ) { onTagNameChange( 'ul' ); - } else { - editor.execCommand( 'InsertUnorderedList' ); - onSyncDOM(); } }, }, onTagNameChange && { icon: 'editor-ol', title: __( 'Convert to ordered list' ), - isActive: isActiveListType( editor, 'ol', tagName ), + isActive: isActiveListType( 'ol', tagName ), onClick() { - if ( isListRootSelected( editor ) ) { + onChange( changeListType( value, { type: 'ol' } ) ); + + if ( isListRootSelected() ) { onTagNameChange( 'ol' ); - } else { - editor.execCommand( 'InsertOrderedList' ); - onSyncDOM(); } }, }, { icon: 'editor-outdent', title: __( 'Outdent list item' ), - onClick() { - editor.execCommand( 'Outdent' ); - onSyncDOM(); + onClick: () => { + onChange( outdentListItems( value ) ); }, }, { icon: 'editor-indent', title: __( 'Indent list item' ), - onClick() { - editor.execCommand( 'Indent' ); - onSyncDOM(); + onClick: () => { + onChange( indentListItems( value, { type: tagName } ) ); }, }, ].filter( Boolean ) } diff --git a/packages/editor/src/components/rich-text/tinymce.js b/packages/editor/src/components/rich-text/tinymce.js index a29117bdce24d..529fa15c1a54c 100644 --- a/packages/editor/src/components/rich-text/tinymce.js +++ b/packages/editor/src/components/rich-text/tinymce.js @@ -200,16 +200,11 @@ export default class TinyMCE extends Component { lists_indent_on_tab: false, }; - if ( multilineTag === 'li' ) { - settings.plugins.push( 'lists' ); - } - tinymce.init( { ...settings, target: this.editorNode, setup: ( editor ) => { this.editor = editor; - this.props.onSetup( editor ); // TinyMCE resets the element content on initialization, even // when it's already identical to what exists currently. This diff --git a/packages/rich-text/src/change-list-type.js b/packages/rich-text/src/change-list-type.js new file mode 100644 index 0000000000000..2b92dda0ab92c --- /dev/null +++ b/packages/rich-text/src/change-list-type.js @@ -0,0 +1,62 @@ +/** + * Internal dependencies + */ + +import { LINE_SEPARATOR } from './special-characters'; +import { normaliseFormats } from './normalise-formats'; +import { getLineIndex } from './get-line-index'; +import { getParentLineIndex } from './get-parent-line-index'; + +/** + * Changes the list type of the selected indented list, if any. Looks at the + * currently selected list item and takes the parent list, then changes the list + * type of this list. When multiple lines are selected, the parent lists are + * takes and changed. + * + * @param {Object} value Value to change. + * @param {Object} newFormat The new list format object. Choose between + * `{ type: 'ol' }` and `{ type: 'ul' }`. + * + * @return {Object} The changed value. + */ +export function changeListType( value, newFormat ) { + const { text, formats, start, end } = value; + const startLineFormats = formats[ getLineIndex( value, start ) ] || []; + const endLineFormats = formats[ getLineIndex( value, end ) ] || []; + const startIndex = getParentLineIndex( value, start ); + const newFormats = formats.slice( 0 ); + const startCount = startLineFormats.length - 1; + const endCount = endLineFormats.length - 1; + + let changed; + + for ( let index = startIndex + 1 || 0; index < text.length; index++ ) { + if ( text[ index ] !== LINE_SEPARATOR ) { + continue; + } + + if ( ( newFormats[ index ] || [] ).length <= startCount ) { + break; + } + + if ( ! newFormats[ index ] ) { + continue; + } + + changed = true; + newFormats[ index ] = newFormats[ index ].map( ( format, i ) => { + return i < startCount || i > endCount ? format : newFormat; + } ); + } + + if ( ! changed ) { + return value; + } + + return normaliseFormats( { + text, + formats: newFormats, + start, + end, + } ); +} diff --git a/packages/rich-text/src/get-line-index.js b/packages/rich-text/src/get-line-index.js new file mode 100644 index 0000000000000..ff44efb98fceb --- /dev/null +++ b/packages/rich-text/src/get-line-index.js @@ -0,0 +1,26 @@ +/** + * Internal dependencies + */ + +import { LINE_SEPARATOR } from './special-characters'; + +/** + * Gets the currently selected line index, or the first line index if the + * selection spans over multiple items. + * + * @param {Object} value Value to get the line index from. + * @param {boolean} startIndex Optional index that should be contained by the + * line. Defaults to the selection start of the + * value. + * + * @return {?boolean} The line index. Undefined if not found. + */ +export function getLineIndex( { start, text }, startIndex = start ) { + let index = startIndex; + + while ( index-- ) { + if ( text[ index ] === LINE_SEPARATOR ) { + return index; + } + } +} diff --git a/packages/rich-text/src/get-parent-line-index.js b/packages/rich-text/src/get-parent-line-index.js new file mode 100644 index 0000000000000..332764dc45bd3 --- /dev/null +++ b/packages/rich-text/src/get-parent-line-index.js @@ -0,0 +1,37 @@ +/** + * Internal dependencies + */ + +import { LINE_SEPARATOR } from './special-characters'; + +/** + * Gets the index of the first parent list. To get the parent list formats, we + * go through every list item until we find one with exactly one format type + * less. + * + * @param {Object} value Value to search. + * @param {number} startIndex Index to start search at. + * + * @return {Array} The parent list line index. + */ +export function getParentLineIndex( { text, formats }, startIndex ) { + let index = startIndex; + let startFormats; + + while ( index-- ) { + if ( text[ index ] !== LINE_SEPARATOR ) { + continue; + } + + const formatsAtIndex = formats[ index ] || []; + + if ( ! startFormats ) { + startFormats = formatsAtIndex; + continue; + } + + if ( formatsAtIndex.length === startFormats.length - 1 ) { + return index; + } + } +} diff --git a/packages/rich-text/src/indent-list-items.js b/packages/rich-text/src/indent-list-items.js new file mode 100644 index 0000000000000..39226de4722dc --- /dev/null +++ b/packages/rich-text/src/indent-list-items.js @@ -0,0 +1,91 @@ +/** + * Internal dependencies + */ + +import { LINE_SEPARATOR } from './special-characters'; +import { normaliseFormats } from './normalise-formats'; +import { getLineIndex } from './get-line-index'; + +/** + * Indents any selected list items if possible. + * + * @param {Object} value Value to change. + * @param {Object} rootFormat + * + * @return {Object} The changed value. + */ +export function indentListItems( value, rootFormat ) { + const lineIndex = getLineIndex( value ); + + // There is only one line, so the line cannot be indented. + if ( lineIndex === undefined ) { + return value; + } + + const { text, formats, start, end } = value; + const formatsAtLineIndex = formats[ lineIndex ] || []; + const targetFormats = formats[ getLineIndex( value, lineIndex ) ] || []; + + // The the indentation of the current line is greater than previous line, + // then the line cannot be furter indented. + if ( formatsAtLineIndex.length > targetFormats.length ) { + return value; + } + + const newFormats = formats.slice(); + + for ( let index = lineIndex; index < end; index++ ) { + if ( text[ index ] !== LINE_SEPARATOR ) { + continue; + } + + // If the indentation of the previous line is the same as the current + // line, then duplicate the type and append all current types. E.g. + // + // 1. one + // 2. two <= Selected + // * three <= Selected + // + // should become: + // + // 1. one + // 1. two <= Selected + // * three <= Selected + // + // ^ Inserted list + // + // Otherwise take the target formats and append traling lists. E.g. + // + // 1. one + // * target + // 2. two <= Selected + // * three <= Selected + // + // should become: + // + // 1. one + // * target + // * two <= Selected + // * three <= Selected + // + if ( targetFormats.length === formatsAtLineIndex.length ) { + const lastformat = targetFormats[ targetFormats.length - 1 ] || rootFormat; + + newFormats[ index ] = targetFormats.concat( + [ lastformat ], + ( newFormats[ index ] || [] ).slice( targetFormats.length ) + ); + } else { + newFormats[ index ] = targetFormats.concat( + ( newFormats[ index ] || [] ).slice( targetFormats.length - 1 ) + ); + } + } + + return normaliseFormats( { + text, + formats: newFormats, + start, + end, + } ); +} diff --git a/packages/rich-text/src/index.js b/packages/rich-text/src/index.js index 61e6475dc778a..86f7cd1b29ae1 100644 --- a/packages/rich-text/src/index.js +++ b/packages/rich-text/src/index.js @@ -25,3 +25,6 @@ export { toHTMLString } from './to-html-string'; export { toggleFormat } from './toggle-format'; export { LINE_SEPARATOR } from './special-characters'; export { unregisterFormatType } from './unregister-format-type'; +export { indentListItems } from './indent-list-items'; +export { outdentListItems } from './outdent-list-items'; +export { changeListType } from './change-list-type'; diff --git a/packages/rich-text/src/normalise-formats.js b/packages/rich-text/src/normalise-formats.js index 349540df9dc25..533df66933886 100644 --- a/packages/rich-text/src/normalise-formats.js +++ b/packages/rich-text/src/normalise-formats.js @@ -1,3 +1,9 @@ +/** + * External dependencies + */ + +import { find } from 'lodash'; + /** * Internal dependencies */ @@ -12,25 +18,22 @@ import { isFormatEqual } from './is-format-equal'; * @return {Object} New value with normalised formats. */ export function normaliseFormats( { formats, text, start, end } ) { - const newFormats = formats.slice( 0 ); - - newFormats.forEach( ( formatsAtIndex, index ) => { - const lastFormatsAtIndex = newFormats[ index - 1 ]; - - if ( lastFormatsAtIndex ) { - const newFormatsAtIndex = formatsAtIndex.slice( 0 ); - - newFormatsAtIndex.forEach( ( format, formatIndex ) => { - const lastFormat = lastFormatsAtIndex[ formatIndex ]; - - if ( isFormatEqual( format, lastFormat ) ) { - newFormatsAtIndex[ formatIndex ] = lastFormat; - } - } ); - - newFormats[ index ] = newFormatsAtIndex; - } - } ); + const refs = []; + const newFormats = formats.map( ( formatsAtIndex ) => + formatsAtIndex.map( ( format ) => { + const equalRef = find( refs, ( ref ) => + isFormatEqual( ref, format ) + ); + + if ( equalRef ) { + return equalRef; + } + + refs.push( format ); + + return format; + } ) + ); return { formats: newFormats, text, start, end }; } diff --git a/packages/rich-text/src/outdent-list-items.js b/packages/rich-text/src/outdent-list-items.js new file mode 100644 index 0000000000000..78036aae165e1 --- /dev/null +++ b/packages/rich-text/src/outdent-list-items.js @@ -0,0 +1,50 @@ +/** + * Internal dependencies + */ + +import { LINE_SEPARATOR } from './special-characters'; +import { normaliseFormats } from './normalise-formats'; +import { getLineIndex } from './get-line-index'; +import { getParentLineIndex } from './get-parent-line-index'; + +/** + * Outdents any selected list items if possible. + * + * @param {Object} value Value to change. + * + * @return {Object} The changed value. + */ +export function outdentListItems( value ) { + const { text, formats, start, end } = value; + const lineIndex = getLineIndex( value ); + const lineFormats = formats[ lineIndex ]; + + if ( lineFormats === undefined ) { + return value; + } + + const newFormats = formats.slice( 0 ); + const parentFormats = formats[ getParentLineIndex( value, lineIndex ) ] || []; + + for ( let index = lineIndex; index < end; index++ ) { + if ( text[ index ] !== LINE_SEPARATOR ) { + continue; + } + + // Omit the indentation level where the selection starts. + newFormats[ index ] = parentFormats.concat( + newFormats[ index ].slice( parentFormats.length + 1 ) + ); + + if ( newFormats[ index ].length === 0 ) { + delete newFormats[ lineIndex ]; + } + } + + return normaliseFormats( { + text, + formats: newFormats, + start, + end, + } ); +} diff --git a/packages/rich-text/src/test/change-list-type.js b/packages/rich-text/src/test/change-list-type.js new file mode 100644 index 0000000000000..3817c66ba7977 --- /dev/null +++ b/packages/rich-text/src/test/change-list-type.js @@ -0,0 +1,74 @@ +/** + * External dependencies + */ +import deepFreeze from 'deep-freeze'; + +/** + * Internal dependencies + */ + +import { changeListType } from '../change-list-type'; +import { getSparseArrayLength } from './helpers'; +import { LINE_SEPARATOR } from '../special-characters'; + +describe( 'changeListType', () => { + const ul = { type: 'ul' }; + const ol = { type: 'ol' }; + + it( 'should only change list type if list item is indented', () => { + const record = { + formats: [ , ], + text: '1', + start: 1, + end: 1, + }; + const result = changeListType( deepFreeze( record ), ul ); + + expect( result ).toEqual( record ); + expect( result ).toBe( record ); + expect( getSparseArrayLength( result.formats ) ).toBe( 0 ); + } ); + + it( 'should change list type', () => { + const record = { + formats: [ , [ ul ] ], + text: `1${ LINE_SEPARATOR }`, + start: 2, + end: 2, + }; + const expected = { + formats: [ , [ ol ] ], + text: `1${ LINE_SEPARATOR }`, + start: 2, + end: 2, + }; + const result = changeListType( deepFreeze( record ), ol ); + + expect( result ).toEqual( expected ); + expect( result ).not.toBe( record ); + expect( getSparseArrayLength( result.formats ) ).toBe( 1 ); + } ); + + it( 'should outdent with multiple lines selected', () => { + // As we're testing list formats, the text should remain the same. + const text = `a${ LINE_SEPARATOR }1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }i${ LINE_SEPARATOR }3${ LINE_SEPARATOR }4${ LINE_SEPARATOR }b`; + + const record = { + formats: [ , [ ul ], , [ ul ], , [ ul, ul ], , [ ul ], , [ ul ], , , , [ ul ], , ], + text, + start: 4, + end: 9, + }; + const expected = { + formats: [ , [ ol ], , [ ol ], , [ ol, ul ], , [ ol ], , [ ol ], , , , [ ul ], , ], + text, + start: 4, + end: 9, + }; + const result = changeListType( deepFreeze( record ), ol ); + + expect( result ).toEqual( expected ); + expect( result ).not.toBe( record ); + expect( getSparseArrayLength( result.formats ) ).toBe( 6 ); + } ); +} ); diff --git a/packages/rich-text/src/test/indent-list-items.js b/packages/rich-text/src/test/indent-list-items.js new file mode 100644 index 0000000000000..c55d5063d21a8 --- /dev/null +++ b/packages/rich-text/src/test/indent-list-items.js @@ -0,0 +1,133 @@ +/** + * External dependencies + */ +import deepFreeze from 'deep-freeze'; + +/** + * Internal dependencies + */ + +import { indentListItems } from '../indent-list-items'; +import { getSparseArrayLength } from './helpers'; +import { LINE_SEPARATOR } from '../special-characters'; + +describe( 'indentListItems', () => { + const ul = { type: 'ul' }; + const ol = { type: 'ol' }; + + it( 'should not indent only item', () => { + const record = { + formats: [ , ], + text: '1', + start: 1, + end: 1, + }; + const result = indentListItems( deepFreeze( record ), ul ); + + expect( result ).toEqual( record ); + expect( result ).toBe( record ); + expect( getSparseArrayLength( result.formats ) ).toBe( 0 ); + } ); + + it( 'should indent', () => { + // As we're testing list formats, the text should remain the same. + const text = `1${ LINE_SEPARATOR }`; + const record = { + formats: [ , , ], + text, + start: 2, + end: 2, + }; + const expected = { + formats: [ , [ ul ] ], + text, + start: 2, + end: 2, + }; + const result = indentListItems( deepFreeze( record ), ul ); + + expect( result ).toEqual( expected ); + expect( result ).not.toBe( record ); + expect( getSparseArrayLength( result.formats ) ).toBe( 1 ); + } ); + + it( 'should not indent without target list', () => { + const record = { + formats: [ , [ ul ] ], + text: `1${ LINE_SEPARATOR }`, + start: 2, + end: 2, + }; + const result = indentListItems( deepFreeze( record ), ul ); + + expect( result ).toEqual( record ); + expect( result ).toBe( record ); + expect( getSparseArrayLength( result.formats ) ).toBe( 1 ); + } ); + + it( 'should indent and merge with previous list', () => { + // As we're testing list formats, the text should remain the same. + const text = `1${ LINE_SEPARATOR }${ LINE_SEPARATOR }`; + const record = { + formats: [ , [ ol ], , ], + text, + start: 3, + end: 3, + }; + const expected = { + formats: [ , [ ol ], [ ol ] ], + text, + start: 3, + end: 3, + }; + const result = indentListItems( deepFreeze( record ), ul ); + + expect( result ).toEqual( expected ); + expect( result ).not.toBe( record ); + expect( getSparseArrayLength( result.formats ) ).toBe( 2 ); + } ); + + it( 'should indent already indented item', () => { + // As we're testing list formats, the text should remain the same. + const text = `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3`; + const record = { + formats: [ , [ ul ], , [ ul ], , ], + text, + start: 5, + end: 5, + }; + const expected = { + formats: [ , [ ul ], , [ ul, ul ], , ], + text, + start: 5, + end: 5, + }; + const result = indentListItems( deepFreeze( record ), ul ); + + expect( result ).toEqual( expected ); + expect( result ).not.toBe( record ); + expect( getSparseArrayLength( result.formats ) ).toBe( 2 ); + } ); + + it( 'should indent with multiple lines selected', () => { + // As we're testing list formats, the text should remain the same. + const text = `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3`; + const record = { + formats: [ , , , [ ul ], , ], + text, + start: 2, + end: 5, + }; + const expected = { + formats: [ , [ ul ], , [ ul, ul ], , ], + text, + start: 2, + end: 5, + }; + const result = indentListItems( deepFreeze( record ), ul ); + + expect( result ).toEqual( expected ); + expect( result ).not.toBe( record ); + expect( getSparseArrayLength( result.formats ) ).toBe( 2 ); + } ); +} ); diff --git a/packages/rich-text/src/test/normalise-formats.js b/packages/rich-text/src/test/normalise-formats.js index 8984bcb17771b..25525625f18b5 100644 --- a/packages/rich-text/src/test/normalise-formats.js +++ b/packages/rich-text/src/test/normalise-formats.js @@ -16,16 +16,17 @@ describe( 'normaliseFormats', () => { it( 'should normalise formats', () => { const record = { - formats: [ , [ em ], [ { ...em }, { ...strong } ], [ em, strong ] ], + formats: [ , [ em ], [ { ...em }, { ...strong } ], [ em, strong ], , [ { ...em } ] ], text: 'one two three', }; const result = normaliseFormats( deepFreeze( record ) ); expect( result ).toEqual( record ); expect( result ).not.toBe( record ); - expect( getSparseArrayLength( result.formats ) ).toBe( 3 ); + expect( getSparseArrayLength( result.formats ) ).toBe( 4 ); expect( result.formats[ 1 ][ 0 ] ).toBe( result.formats[ 2 ][ 0 ] ); expect( result.formats[ 1 ][ 0 ] ).toBe( result.formats[ 3 ][ 0 ] ); + expect( result.formats[ 1 ][ 0 ] ).toBe( result.formats[ 5 ][ 0 ] ); expect( result.formats[ 2 ][ 1 ] ).toBe( result.formats[ 3 ][ 1 ] ); } ); } ); diff --git a/packages/rich-text/src/test/outdent-list-items.js b/packages/rich-text/src/test/outdent-list-items.js new file mode 100644 index 0000000000000..d321a4c02ffe8 --- /dev/null +++ b/packages/rich-text/src/test/outdent-list-items.js @@ -0,0 +1,96 @@ +/** + * External dependencies + */ +import deepFreeze from 'deep-freeze'; + +/** + * Internal dependencies + */ + +import { outdentListItems } from '../outdent-list-items'; +import { getSparseArrayLength } from './helpers'; +import { LINE_SEPARATOR } from '../special-characters'; + +describe( 'outdentListItems', () => { + const ul = { type: 'ul' }; + + it( 'should not outdent only item', () => { + const record = { + formats: [ , ], + text: '1', + start: 1, + end: 1, + }; + const result = outdentListItems( deepFreeze( record ), ul ); + + expect( result ).toEqual( record ); + expect( result ).toBe( record ); + expect( getSparseArrayLength( result.formats ) ).toBe( 0 ); + } ); + + it( 'should indent', () => { + // As we're testing list formats, the text should remain the same. + const text = `1${ LINE_SEPARATOR }`; + const record = { + formats: [ , [ ul ] ], + text, + start: 2, + end: 2, + }; + const expected = { + formats: [ , , ], + text, + start: 2, + end: 2, + }; + const result = outdentListItems( deepFreeze( record ), ul ); + + expect( result ).toEqual( expected ); + expect( result ).not.toBe( record ); + expect( getSparseArrayLength( result.formats ) ).toBe( 0 ); + } ); + + it( 'should outdent two levels deep', () => { + // As we're testing list formats, the text should remain the same. + const text = `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3`; + const record = { + formats: [ , [ ul ], , [ ul, ul ], , ], + text, + start: 5, + end: 5, + }; + const expected = { + formats: [ , [ ul ], , [ ul ], , ], + text, + start: 5, + end: 5, + }; + const result = outdentListItems( deepFreeze( record ), ul ); + + expect( result ).toEqual( expected ); + expect( result ).not.toBe( record ); + expect( getSparseArrayLength( result.formats ) ).toBe( 2 ); + } ); + + it( 'should outdent with multiple lines selected', () => { + // As we're testing list formats, the text should remain the same. + const text = `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3`; + const record = { + formats: [ , [ ul ], , [ ul, ul ], , ], + text, + start: 2, + end: 5, + }; + const expected = { + formats: [ , , , [ ul ], , ], + text, + start: 2, + end: 5, + }; + const result = outdentListItems( deepFreeze( record ), ul ); + + expect( result ).toEqual( expected ); + expect( result ).not.toBe( record ); + expect( getSparseArrayLength( result.formats ) ).toBe( 1 ); + } ); +} ); From 891074a17f3abfdcc37189dcc113f162066865ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Wrede?= <soerenwrede@gmail.com> Date: Mon, 28 Jan 2019 17:55:45 +0100 Subject: [PATCH 264/691] Fix typo (#13524) --- assets/stylesheets/_mixins.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/stylesheets/_mixins.scss b/assets/stylesheets/_mixins.scss index 090854559f257..54f812844eb04 100644 --- a/assets/stylesheets/_mixins.scss +++ b/assets/stylesheets/_mixins.scss @@ -278,7 +278,7 @@ } } - /* In small screens with resposive menu expanded there is small white space. */ + /* In small screens with responsive menu expanded there is small white space. */ @media (max-width: #{ ($break-small) }) { .auto-fold .wp-responsive-open #{$selector} { margin-left: -18px; From b598fb3db99332794c44b05e5d6e2b88ed36643d Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez <diegoreymendez@users.noreply.github.com> Date: Mon, 28 Jan 2019 14:07:28 -0300 Subject: [PATCH 265/691] Adds undo/redo support for the title. (#13514) --- packages/editor/src/components/post-title/index.native.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/editor/src/components/post-title/index.native.js b/packages/editor/src/components/post-title/index.native.js index 1ce2384db72ea..a06a1c94b4cec 100644 --- a/packages/editor/src/components/post-title/index.native.js +++ b/packages/editor/src/components/post-title/index.native.js @@ -72,12 +72,16 @@ const applyWithDispatch = withDispatch( ( dispatch ) => { const { insertDefaultBlock, clearSelectedBlock, + undo, + redo, } = dispatch( 'core/editor' ); return { onEnterPress() { insertDefaultBlock( undefined, undefined, 0 ); }, + onUndo: undo, + onRedo: redo, clearSelectedBlock, }; } ); From 014896535a8a9a2d9a3f6635c71774b1860e9599 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Mon, 28 Jan 2019 13:52:01 -0500 Subject: [PATCH 266/691] Plugin: Remove TinyMCE-specific vendor script handling (#13538) * Plugin: Remove TinyMCE-specific vendor script handling * Plugin: Remove TinyMCE-specific vendor script tests --- lib/client-assets.php | 37 +++++++------------ phpunit/class-vendor-script-filename-test.php | 20 ---------- 2 files changed, 13 insertions(+), 44 deletions(-) diff --git a/lib/client-assets.php b/lib/client-assets.php index c90cb8a05a8ff..4fff23ed4bdf5 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -599,32 +599,21 @@ function gutenberg_register_vendor_scripts() { * @since 0.1.0 */ function gutenberg_vendor_script_filename( $handle, $src ) { - $match_tinymce_plugin = preg_match( - '@tinymce.*/plugins/([^/]+)/plugin(\.min)?\.js$@', - $src, - $tinymce_plugin_pieces + $filename = basename( $src ); + $match = preg_match( + '/^' + . '(?P<ignore>.*?)' + . '(?P<suffix>\.min)?' + . '(?P<extension>\.js)' + . '(?P<extra>.*)' + . '$/', + $filename, + $filename_pieces ); - if ( $match_tinymce_plugin ) { - $prefix = 'tinymce-plugin-' . $tinymce_plugin_pieces[1]; - $suffix = isset( $tinymce_plugin_pieces[2] ) ? $tinymce_plugin_pieces[2] : ''; - } else { - $filename = basename( $src ); - $match = preg_match( - '/^' - . '(?P<ignore>.*?)' - . '(?P<suffix>\.min)?' - . '(?P<extension>\.js)' - . '(?P<extra>.*)' - . '$/', - $filename, - $filename_pieces - ); - - $prefix = $handle; - $suffix = $match ? $filename_pieces['suffix'] : ''; - } - $hash = substr( md5( $src ), 0, 8 ); + $prefix = $handle; + $suffix = $match ? $filename_pieces['suffix'] : ''; + $hash = substr( md5( $src ), 0, 8 ); return "${prefix}${suffix}.${hash}.js"; } diff --git a/phpunit/class-vendor-script-filename-test.php b/phpunit/class-vendor-script-filename-test.php index f053df2011159..0ee7a7b78b83d 100644 --- a/phpunit/class-vendor-script-filename-test.php +++ b/phpunit/class-vendor-script-filename-test.php @@ -19,16 +19,6 @@ function vendor_script_filename_cases() { 'https://unpkg.com/react-dom@16.6.3/umd/react-dom.development.js', 'react-dom-handle.HASH.js', ), - array( - 'tinymce-handle', - 'https://fiddle.azurewebsites.net/tinymce/nightly/tinymce.js', - 'tinymce-handle.HASH.js', - ), - array( - 'tinymce-plugin-handle', - 'https://fiddle.azurewebsites.net/tinymce/nightly/plugins/lists/plugin.js', - 'tinymce-plugin-lists.HASH.js', - ), // Production mode scripts. array( 'react-handle', @@ -40,16 +30,6 @@ function vendor_script_filename_cases() { 'https://unpkg.com/react-dom@16.6.3/umd/react-dom.production.min.js', 'react-dom-handle.min.HASH.js', ), - array( - 'tinymce-handle', - 'https://fiddle.azurewebsites.net/tinymce/nightly/tinymce.min.js', - 'tinymce-handle.min.HASH.js', - ), - array( - 'tinymce-plugin-handle', - 'https://fiddle.azurewebsites.net/tinymce/nightly/plugins/lists/plugin.min.js', - 'tinymce-plugin-lists.min.HASH.js', - ), // Other cases. array( 'something-handle', From 98b1350b471cb9f3764181ad9a85f262b527df68 Mon Sep 17 00:00:00 2001 From: Karol Gorski <naerriel@gmail.com> Date: Mon, 28 Jan 2019 20:13:28 +0100 Subject: [PATCH 267/691] Update isShallowEqual documentation and unit tests by deep comparison (#13526) * Update isShallowEqual documentation and unit tests by deep comparison * Update packages/is-shallow-equal/README.md Co-Authored-By: Naerriel <naerriel@gmail.com> --- packages/is-shallow-equal/README.md | 18 ++++++++++++++++++ packages/is-shallow-equal/test/index.js | 8 ++++++++ 2 files changed, 26 insertions(+) diff --git a/packages/is-shallow-equal/README.md b/packages/is-shallow-equal/README.md index ac68c2997ac73..3c53ed3261e44 100644 --- a/packages/is-shallow-equal/README.md +++ b/packages/is-shallow-equal/README.md @@ -29,6 +29,24 @@ import { isShallowEqualArrays } from '@wordpress/is-shallow-equal'; import { isShallowEqualObjects } from '@wordpress/is-shallow-equal'; ``` +Shallow comparison differs from deep comparison by the fact that it compares members from each as being strictly equal to the other, meaning that arrays and objects will be compared by their _references_, not by their values (see also [_Object Equality in JavaScript_.](http://adripofjavascript.com/blog/drips/object-equality-in-javascript.html)) In situations where nested objects must be compared by value, consider using [Lodash's `isEqual`](https://lodash.com/docs/4.17.11#isEqual) instead. + +```js +import isShallowEqual from '@wordpress/is-shallow-equal'; +import { isEqual } from 'lodash'; // deep comparison + +let object = { a: 1 }; + +isShallowEqual( [ { a: 1 } ], [ { a: 1 } ] ); +// ⇒ false + +isEqual( [ { a: 1 } ], [ { a: 1 } ] ); +// ⇒ true + +isShallowEqual( [ object ], [ object ] ); +// ⇒ true +``` + ## Rationale Shallow equality utilities are already a dime a dozen. Since these operations are often at the core of critical hot code paths, the WordPress contributors had specific requirements that were found to only be partially satisfied by existing solutions. diff --git a/packages/is-shallow-equal/test/index.js b/packages/is-shallow-equal/test/index.js index ee918194cdad1..49f2526dbdf27 100644 --- a/packages/is-shallow-equal/test/index.js +++ b/packages/is-shallow-equal/test/index.js @@ -84,6 +84,14 @@ describe( 'isShallowEqual', () => { expect( isShallowEqual( b, a ) ).toBe( true ); } ); + it( 'returns false on object deep-but-referentially-unequal values', () => { + const a = { foo: {} }; + const b = { foo: {} }; + + expect( isShallowEqual( a, b ) ).toBe( false ); + expect( isShallowEqual( b, a ) ).toBe( false ); + } ); + it( 'returns false if a array has more keys than b', () => { const a = [ 1, 2 ]; const b = [ 1 ]; From b944ed777b54c8fd93cc894040218550e0fd3c7f Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Mon, 28 Jan 2019 11:55:26 -0800 Subject: [PATCH 268/691] Expand block template examples (#13494) * Expand block template examples Adds examples for PHP and JS for using templates. Fixes #8251 * Consolidate examples and cleanup Update function names to be consistent across example. --- .../developers/block-api/block-templates.md | 68 +++++++++++++------ 1 file changed, 46 insertions(+), 22 deletions(-) diff --git a/docs/designers-developers/developers/block-api/block-templates.md b/docs/designers-developers/developers/block-api/block-templates.md index bbc6efe968ebb..caa96ff20b5bd 100644 --- a/docs/designers-developers/developers/block-api/block-templates.md +++ b/docs/designers-developers/developers/block-api/block-templates.md @@ -17,18 +17,50 @@ Planned additions: Templates can be declared in JS or in PHP as an array of blockTypes (block name and optional attributes). +The first example in PHP creates a template for posts that includes an image block to start, you can add as many or as few blocks to your template as needed. + +PHP example: + +```php +<?php +function myplugin_register_template() { + $post_type_object = get_post_type_object( 'post' ); + $post_type_object->template = array( + array( 'core/image' ), + ); +} +add_action( 'init', 'myplugin_register_template' ); +``` + +The following example in JavaScript creates a new block using [InnerBlocks](/packages/editor/src/components/inner-blocks) and templates, when inserted creates a set of blocks based off the template. + ```js -const template = [ - [ 'block/name', {} ], // [ blockName, attributes ] +const el = wp.element.createElement; +const { registerBlockType } = wp.blocks; +const { InnerBlocks } = wp.editor; + +const BLOCKS_TEMPLATE = [ + [ 'core/image', {} ], + [ 'core/paragraph', { placeholder: 'Image Details' } ], ]; -``` -```php -'template' => array( - array( 'block/name' ), -), +registerBlockType( 'myplugin/template', { + title: 'My Template Block', + category: 'widgets', + edit: ( props ) => { + return el( InnerBlocks, { + template: BLOCKS_TEMPLATE, + templateLock: false + }); + }, + save: ( props ) => { + return el( InnerBlocks.Content, {} ); + }, +}); ``` +See the [Meta Block Tutorial](/docs/designers-developers/developers/tutorials/metabox/meta-block-5-finishing.md) for a full example of a template in use. + ## Custom Post types A custom post type can register its own template during registration: @@ -61,20 +93,7 @@ add_action( 'init', 'myplugin_register_book_post_type' ); Sometimes the intention might be to lock the template on the UI so that the blocks presented cannot be manipulated. This is achieved with a `template_lock` property. ```php -'template_lock' => 'all', // or 'insert' to allow moving -``` - -*Options:* - -- `all` — prevents all operations. It is not possible to insert new blocks, move existing blocks, or delete blocks. -- `insert` — prevents inserting or removing blocks, but allows moving existing blocks. - -## Existing Post Types - -It is also possible to assign a template to an existing post type like "posts" and "pages": - -```php -function my_add_template_to_posts() { +function myplugin_register_template() { $post_type_object = get_post_type_object( 'post' ); $post_type_object->template = array( array( 'core/paragraph', array( @@ -83,9 +102,14 @@ function my_add_template_to_posts() { ); $post_type_object->template_lock = 'all'; } -add_action( 'init', 'my_add_template_to_posts' ); +add_action( 'init', 'myplugin_register_template' ); ``` +*Options:* + +- `all` — prevents all operations. It is not possible to insert new blocks, move existing blocks, or delete blocks. +- `insert` — prevents inserting or removing blocks, but allows moving existing blocks. + ## Nested Templates Container blocks like the columns blocks also support templates. This is achieved by assigning a nested template to the block. From 77f617603bb369cd16e8ed76ef636b2b82afaaac Mon Sep 17 00:00:00 2001 From: Jefferson Rabb <j.max.rabb@gmail.com> Date: Tue, 29 Jan 2019 02:32:36 -0500 Subject: [PATCH 269/691] Focal Point Picker for the Cover Block (#10925) --- docs/manifest.json | 6 + packages/block-library/src/cover/index.js | 21 ++ .../src/focal-point-picker/README.md | 68 +++++ .../src/focal-point-picker/index.js | 251 ++++++++++++++++++ .../src/focal-point-picker/style.scss | 75 ++++++ packages/components/src/index.js | 1 + packages/components/src/style.scss | 1 + 7 files changed, 423 insertions(+) create mode 100644 packages/components/src/focal-point-picker/README.md create mode 100644 packages/components/src/focal-point-picker/index.js create mode 100644 packages/components/src/focal-point-picker/style.scss diff --git a/docs/manifest.json b/docs/manifest.json index e7d2a88e11cc5..e82a59211c392 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -899,6 +899,12 @@ "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/external-link/README.md", "parent": "components" }, + { + "title": "FocalPointPicker", + "slug": "focal-point-picker", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/focal-point-picker/README.md", + "parent": "components" + }, { "title": "FocusableIframe", "slug": "focusable-iframe", diff --git a/packages/block-library/src/cover/index.js b/packages/block-library/src/cover/index.js index b1ceb293c336f..1df4323902ecb 100644 --- a/packages/block-library/src/cover/index.js +++ b/packages/block-library/src/cover/index.js @@ -7,6 +7,7 @@ import classnames from 'classnames'; * WordPress dependencies */ import { + FocalPointPicker, IconButton, PanelBody, RangeControl, @@ -67,6 +68,9 @@ const blockAttributes = { type: 'string', default: 'image', }, + focalPoint: { + type: 'object', + }, }; export const name = 'core/cover'; @@ -175,6 +179,7 @@ export const settings = { backgroundType, contentAlign, dimRatio, + focalPoint, hasParallax, id, title, @@ -224,6 +229,10 @@ export const settings = { backgroundColor: overlayColor.color, }; + if ( focalPoint ) { + style.backgroundPosition = `${ focalPoint.x * 100 }% ${ focalPoint.y * 100 }%`; + } + const controls = ( <Fragment> <BlockControls> @@ -265,6 +274,14 @@ export const settings = { onChange={ toggleParallax } /> ) } + { IMAGE_BACKGROUND_TYPE === backgroundType && ! hasParallax && ( + <FocalPointPicker + label={ __( 'Focal Point Picker' ) } + url={ url } + value={ focalPoint } + onChange={ ( value ) => setAttributes( { focalPoint: value } ) } + /> + ) } <PanelColorSettings title={ __( 'Overlay' ) } initialOpen={ true } @@ -370,6 +387,7 @@ export const settings = { contentAlign, customOverlayColor, dimRatio, + focalPoint, hasParallax, overlayColor, title, @@ -382,6 +400,9 @@ export const settings = { if ( ! overlayColorClass ) { style.backgroundColor = customOverlayColor; } + if ( focalPoint && ! hasParallax ) { + style.backgroundPosition = `${ focalPoint.x * 100 }% ${ focalPoint.y * 100 }%`; + } const classes = classnames( dimRatioToClass( dimRatio ), diff --git a/packages/components/src/focal-point-picker/README.md b/packages/components/src/focal-point-picker/README.md new file mode 100644 index 0000000000000..7afb26f278152 --- /dev/null +++ b/packages/components/src/focal-point-picker/README.md @@ -0,0 +1,68 @@ +# Focal Point Picker + +Focal Point Picker is a component which creates a UI for identifying the most important visual point of an image. This component addresses a specific problem: with large background images it is common to see undesirable crops, especially when viewing on smaller viewports such as mobile phones. This component allows the selection of the point with the most important visual information and returns it as a pair of numbers between 0 and 1. This value can be easily converted into the CSS `background-position` attribute, and will ensure that the focal point is never cropped out, regardless of viewport. + +Example focal point picker value: `{ x: 0.5, y: 0.1 }` +Corresponding CSS: `background-position: 50% 10%;` + +## Usage + +```jsx +import { FocalPointPicker } from '@wordpress/components'; + +const MyFocalPointPicker = withState( { + focalPoint: { + x: 0.5, + y: 0.5 + }, +} )( ( { focalPoint, setState } ) => { + const url = '/path/to/image'; + const dimensions = { + width: 400, + height: 100 + }; + return ( + <FocalPointPicker + url={ url } + dimensions={ dimensions } + value={ focalPoint } + onChange={ ( focalPoint ) => setState( { focalPoint } ) } + /> + ) +} ); + +/* Example function to render the CSS styles based on Focal Point Picker value */ +const renderImageContainerWithFocalPoint = ( url, focalPoint ) => { + const style = { + backgroundImage: `url(${ url })` , + backgroundPosition: `${ focalPoint.x * 100 }% ${ focalPoint.y * 100 }%` + } + return <div style={ style } />; +}; +``` + +## Props + +### `url` + +- Type: `Text` +- Required: Yes +- Description: URL of the image to be displayed + +### `dimensions` + +- Type: `Object` +- Required: Yes +- Description: An object describing the height and width of the image. Requires two paramaters: `height`, `width`. + +### `value` + +- Type: `Object` +- Required: Yes +- Description: The focal point. Should be an object containing `x` and `y` params. + +### `onChange` + +- Type: `Function` +- Required: Yes +- Description: Callback which is called when the focal point changes. diff --git a/packages/components/src/focal-point-picker/index.js b/packages/components/src/focal-point-picker/index.js new file mode 100644 index 0000000000000..315c28f7303b7 --- /dev/null +++ b/packages/components/src/focal-point-picker/index.js @@ -0,0 +1,251 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Component, createRef } from '@wordpress/element'; +import { withInstanceId, compose } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import BaseControl from '../base-control'; +import withFocusOutside from '../higher-order/with-focus-outside'; +import { Path, SVG } from '../primitives'; + +const TEXTCONTROL_MIN = 0; +const TEXTCONTROL_MAX = 100; + +export class FocalPointPicker extends Component { + constructor() { + super( ...arguments ); + this.onMouseMove = this.onMouseMove.bind( this ); + this.state = { + isDragging: false, + bounds: {}, + percentages: {}, + }; + this.containerRef = createRef(); + this.imageRef = createRef(); + this.horizontalPositionChanged = this.horizontalPositionChanged.bind( this ); + this.verticalPositionChanged = this.verticalPositionChanged.bind( this ); + this.onLoad = this.onLoad.bind( this ); + } + componentDidMount() { + this.setState( { + percentages: this.props.value, + } ); + } + componentDidUpdate( prevProps ) { + if ( prevProps.url !== this.props.url ) { + this.setState( { + isDragging: false, + } ); + } + } + calculateBounds() { + const bounds = { + top: 0, + left: 0, + bottom: 0, + right: 0, + width: 0, + height: 0, + }; + if ( ! this.imageRef.current ) { + return bounds; + } + const dimensions = { + width: this.imageRef.current.clientWidth, + height: this.imageRef.current.clientHeight, + }; + const pickerDimensions = this.pickerDimensions(); + const widthRatio = pickerDimensions.width / dimensions.width; + const heightRatio = pickerDimensions.height / dimensions.height; + if ( heightRatio >= widthRatio ) { + bounds.width = bounds.right = pickerDimensions.width; + bounds.height = dimensions.height * widthRatio; + bounds.top = ( pickerDimensions.height - bounds.height ) / 2; + bounds.bottom = bounds.top + bounds.height; + } else { + bounds.height = bounds.bottom = pickerDimensions.height; + bounds.width = dimensions.width * heightRatio; + bounds.left = ( pickerDimensions.width - bounds.width ) / 2; + bounds.right = bounds.left + bounds.width; + } + return bounds; + } + onLoad() { + this.setState( { + bounds: this.calculateBounds(), + } ); + } + onMouseMove( event ) { + const { isDragging, bounds } = this.state; + const { onChange } = this.props; + + if ( isDragging ) { + const pickerDimensions = this.pickerDimensions(); + const cursorPosition = { + left: event.pageX - pickerDimensions.left, + top: event.pageY - pickerDimensions.top, + }; + const left = Math.max( + bounds.left, + Math.min( + cursorPosition.left, bounds.right + ) + ); + const top = Math.max( + bounds.top, + Math.min( + cursorPosition.top, bounds.bottom + ) + ); + const percentages = { + x: ( left - bounds.left ) / ( pickerDimensions.width - ( bounds.left * 2 ) ), + y: ( top - bounds.top ) / ( pickerDimensions.height - ( bounds.top * 2 ) ), + }; + this.setState( { percentages }, function() { + onChange( { + x: this.state.percentages.x, + y: this.state.percentages.y, + } ); + } ); + } + } + fractionToPercentage( fraction ) { + return Math.round( fraction * 100 ); + } + horizontalPositionChanged( event ) { + this.positionChangeFromTextControl( 'x', event.target.value ); + } + verticalPositionChanged( event ) { + this.positionChangeFromTextControl( 'y', event.target.value ); + } + positionChangeFromTextControl( axis, value ) { + const { onChange } = this.props; + const { percentages } = this.state; + const cleanValue = Math.max( Math.min( parseInt( value ), 100 ), 0 ); + percentages[ axis ] = cleanValue ? cleanValue / 100 : 0; + this.setState( { percentages }, function() { + onChange( { + x: this.state.percentages.x, + y: this.state.percentages.y, + } ); + } ); + } + pickerDimensions() { + if ( this.containerRef.current ) { + return { + width: this.containerRef.current.clientWidth, + height: this.containerRef.current.clientHeight, + top: this.containerRef.current.getBoundingClientRect().top + document.body.scrollTop, + left: this.containerRef.current.getBoundingClientRect().left, + }; + } + return { + width: 0, + height: 0, + left: 0, + top: 0, + }; + } + handleFocusOutside() { + this.setState( { + isDragging: false, + } ); + } + render() { + const { instanceId, url, value, label, help, className } = this.props; + const { bounds, isDragging, percentages } = this.state; + const pickerDimensions = this.pickerDimensions(); + const iconCoordinates = { + left: ( value.x * ( pickerDimensions.width - ( bounds.left * 2 ) ) ) + bounds.left, + top: ( value.y * ( pickerDimensions.height - ( bounds.top * 2 ) ) ) + bounds.top, + }; + const iconContainerStyle = { + left: `${ iconCoordinates.left }px`, + top: `${ iconCoordinates.top }px`, + }; + const iconContainerClasses = classnames( + 'components-focal-point-picker__icon_container', + isDragging ? 'is-dragging' : null + ); + const id = `inspector-focal-point-picker-control-${ instanceId }`; + const horizontalPositionId = `inspector-focal-point-picker-control-horizontal-position-${ instanceId }`; + const verticalPositionId = `inspector-focal-point-picker-control-horizontal-position-${ instanceId }`; + return ( + <BaseControl label={ label } id={ id } help={ help } className={ className }> + <div className="components-focal-point-picker-wrapper"> + <div + className="components-focal-point-picker" + onMouseDown={ () => this.setState( { isDragging: true } ) } + onDragStart={ () => this.setState( { isDragging: true } ) } + onMouseUp={ () => this.setState( { isDragging: false } ) } + onDrop={ () => this.setState( { isDragging: false } ) } + onMouseMove={ this.onMouseMove } + ref={ this.containerRef } + role="button" + tabIndex="-1" + > + <img + alt="Dimensions helper" + onLoad={ this.onLoad } + ref={ this.imageRef } + src={ url } + draggable="false" + /> + <div className={ iconContainerClasses } style={ iconContainerStyle }> + <SVG className="components-focal-point-picker__icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30"> + <Path className="components-focal-point-picker__icon-outline" d="M15 1C7.3 1 1 7.3 1 15s6.3 14 14 14 14-6.3 14-14S22.7 1 15 1zm0 22c-4.4 0-8-3.6-8-8s3.6-8 8-8 8 3.6 8 8-3.6 8-8 8z" /> + <Path className="components-focal-point-picker__icon-fill" d="M15 3C8.4 3 3 8.4 3 15s5.4 12 12 12 12-5.4 12-12S21.6 3 15 3zm0 22C9.5 25 5 20.5 5 15S9.5 5 15 5s10 4.5 10 10-4.5 10-10 10z" /> + </SVG> + </div> + </div> + </div> + <div className="components-focal-point-picker_position-display-container"> + <BaseControl label={ __( 'Horizontal Pos.' ) }> + <input + className="components-text-control__input" + id={ horizontalPositionId } + max={ TEXTCONTROL_MAX } + min={ TEXTCONTROL_MIN } + onChange={ this.horizontalPositionChanged } + type="number" + value={ this.fractionToPercentage( percentages.x ) } + /> + <span>%</span> + </BaseControl> + <BaseControl label={ __( 'Vertical Pos.' ) }> + <input + className="components-text-control__input" + id={ verticalPositionId } + max={ TEXTCONTROL_MAX } + min={ TEXTCONTROL_MIN } + onChange={ this.verticalPositionChanged } + type="number" + value={ this.fractionToPercentage( percentages.y ) } + /> + <span>%</span> + </BaseControl> + </div> + </BaseControl> + ); + } +} + +FocalPointPicker.defaultProps = { + url: null, + value: { + x: 0.5, + y: 0.5, + }, + onChange: () => {}, +}; + +export default compose( [ withInstanceId, withFocusOutside ] )( FocalPointPicker ); diff --git a/packages/components/src/focal-point-picker/style.scss b/packages/components/src/focal-point-picker/style.scss new file mode 100644 index 0000000000000..f93f2e57ad688 --- /dev/null +++ b/packages/components/src/focal-point-picker/style.scss @@ -0,0 +1,75 @@ +.components-focal-point-picker-wrapper { + background-color: transparent; + border: 1px solid $light-gray-500; + height: 200px; + width: 100%; + padding: 14px; +} + +.components-focal-point-picker { + align-items: center; + cursor: pointer; + display: flex; + height: 100%; + justify-content: center; + position: relative; + width: 100%; + + img { + height: auto; + max-height: 100%; + max-width: 100%; + width: auto; + user-select: none; + } +} + +.components-focal-point-picker__icon_container { + background-color: transparent; + cursor: grab; + height: 30px; + opacity: 0.8; + position: absolute; + will-change: transform; + width: 30px; + z-index: 10000; + + &.is-dragging { + cursor: grabbing; + } +} + +.components-focal-point-picker__icon { + display: block; + height: 100%; + left: -15px; + position: absolute; + top: -15px; + width: 100%; + + .components-focal-point-picker__icon-outline { + fill: $white; + } + + .components-focal-point-picker__icon-fill { + fill: theme(primary); + } +} + +.components-focal-point-picker_position-display-container { + margin: 1em 0; + display: flex; + + .components-base-control__field { + margin: 0 1em 0 0; + } + + input[type="number"].components-text-control__input { // Needs specificity to override padding. + max-width: 4em; + padding: 6px 4px; + } + + span { + margin: 0 0 0 0.2em; + } +} diff --git a/packages/components/src/index.js b/packages/components/src/index.js index 415cd5a1be5dc..f892867c7a400 100644 --- a/packages/components/src/index.js +++ b/packages/components/src/index.js @@ -19,6 +19,7 @@ export { default as DropZoneProvider } from './drop-zone/provider'; export { default as Dropdown } from './dropdown'; export { default as DropdownMenu } from './dropdown-menu'; export { default as ExternalLink } from './external-link'; +export { default as FocalPointPicker } from './focal-point-picker'; export { default as FocusableIframe } from './focusable-iframe'; export { default as FontSizePicker } from './font-size-picker'; export { default as FormFileUpload } from './form-file-upload'; diff --git a/packages/components/src/style.scss b/packages/components/src/style.scss index c3e8b38e7a7ef..046c7b6264efd 100644 --- a/packages/components/src/style.scss +++ b/packages/components/src/style.scss @@ -13,6 +13,7 @@ @import "./drop-zone/style.scss"; @import "./dropdown-menu/style.scss"; @import "./external-link/style.scss"; +@import "./focal-point-picker/style.scss"; @import "./font-size-picker/style.scss"; @import "./form-file-upload/style.scss"; @import "./form-toggle/style.scss"; From 4a1fc6fa6b405b6c262500335cc1daffaba60173 Mon Sep 17 00:00:00 2001 From: John <johng75@gmail.com> Date: Tue, 29 Jan 2019 07:36:28 +0000 Subject: [PATCH 270/691] Block switch: ensure hover is reset when style changed (#12317) When a block style is selected we should reset the hover state so that any component using this will be adjusted accordingly --- packages/editor/src/components/block-styles/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/editor/src/components/block-styles/index.js b/packages/editor/src/components/block-styles/index.js index 6892df08c6d06..ad108daa18643 100644 --- a/packages/editor/src/components/block-styles/index.js +++ b/packages/editor/src/components/block-styles/index.js @@ -79,6 +79,7 @@ function BlockStyles( { function updateClassName( style ) { const updatedClassName = replaceActiveStyle( className, activeStyle, style ); onChangeClassName( updatedClassName ); + onHoverClassName( null ); onSwitch(); } From cd0af05939345641fb0b99335f87321797d4e86d Mon Sep 17 00:00:00 2001 From: Hendrik Luehrsen <Luehrsen@users.noreply.github.com> Date: Tue, 29 Jan 2019 08:37:27 +0100 Subject: [PATCH 271/691] Add innerBlocks as an argument to the transform function (#11979) Co-Authored-By: Hendrik Luehrsen <Luehrsen@users.noreply.github.com> --- .../block-api/block-registration.md | 33 ++++++ packages/blocks/CHANGELOG.md | 6 ++ packages/blocks/src/api/factory.js | 7 +- packages/blocks/src/api/test/factory.js | 100 ++++++++++++++++++ .../plugins/inner-blocks-templates/index.js | 71 ++++++++++--- 5 files changed, 202 insertions(+), 15 deletions(-) diff --git a/docs/designers-developers/developers/block-api/block-registration.md b/docs/designers-developers/developers/block-api/block-registration.md index 90d0752ff170e..e36628c7249e0 100644 --- a/docs/designers-developers/developers/block-api/block-registration.md +++ b/docs/designers-developers/developers/block-api/block-registration.md @@ -311,6 +311,39 @@ transforms: { ``` {% end %} +A block with innerBlocks can also be transformed from and to another block with innerBlocks. + +{% codetabs %} +{% ES5 %} +```js +transforms: { + to: [ + { + type: 'block', + blocks: [ 'some/block-with-innerblocks' ], + transform: function( attributes, innerBlocks ) { + return createBlock( 'some/other-block-with-innerblocks', attributes, innerBlocks ); + }, + }, + ], +}, +``` +{% ESNext %} +```js +transforms: { + to: [ + { + type: 'block', + blocks: [ 'some/block-with-innerblocks' ], + transform: ( attributes, innerBlocks ) => { + return createBlock( 'some/other-block-with-innerblocks', attributes, innerBlocks); + }, + }, + ], +}, +``` +{% end %} + An optional `isMatch` function can be specified on a transform object. This provides an opportunity to perform additional checks on whether a transform should be possible. Returning `false` from this function will prevent the transform from being displayed as an option to the user. {% codetabs %} diff --git a/packages/blocks/CHANGELOG.md b/packages/blocks/CHANGELOG.md index 1f40a53235f4c..4d607751d8a31 100644 --- a/packages/blocks/CHANGELOG.md +++ b/packages/blocks/CHANGELOG.md @@ -1,3 +1,9 @@ +## 6.1.0 (Unreleased) + +### New Feature + +- Blocks' `transforms` will receive `innerBlocks` as the second argument (or an array of each block's respective `innerBlocks` for a multi-transform). + ## 6.0.5 (2019-01-03) ## 6.0.4 (2018-12-12) diff --git a/packages/blocks/src/api/factory.js b/packages/blocks/src/api/factory.js index 9068a8b3d8cd3..c7bd01f2c699c 100644 --- a/packages/blocks/src/api/factory.js +++ b/packages/blocks/src/api/factory.js @@ -346,9 +346,12 @@ export function switchToBlockType( blocks, name ) { let transformationResults; if ( transformation.isMultiBlock ) { - transformationResults = transformation.transform( blocksArray.map( ( currentBlock ) => currentBlock.attributes ) ); + transformationResults = transformation.transform( + blocksArray.map( ( currentBlock ) => currentBlock.attributes ), + blocksArray.map( ( currentBlock ) => currentBlock.innerBlocks ) + ); } else { - transformationResults = transformation.transform( firstBlock.attributes ); + transformationResults = transformation.transform( firstBlock.attributes, firstBlock.innerBlocks ); } // Ensure that the transformation function returned an object or an array diff --git a/packages/blocks/src/api/test/factory.js b/packages/blocks/src/api/test/factory.js index eaf222b8b5c86..11249b08d36ce 100644 --- a/packages/blocks/src/api/test/factory.js +++ b/packages/blocks/src/api/test/factory.js @@ -1122,6 +1122,106 @@ describe( 'block factory', () => { value: 'smoked ribs', } ); } ); + + it( 'should pass through inner blocks to transform', () => { + registerBlockType( 'core/updated-columns-block', { + attributes: { + value: { + type: 'string', + }, + }, + transforms: { + from: [ { + type: 'block', + blocks: [ 'core/columns-block' ], + transform( attributes, innerBlocks ) { + return createBlock( + 'core/updated-columns-block', + attributes, + innerBlocks.map( ( innerBlock ) => { + return cloneBlock( innerBlock, { + value: 'after', + } ); + } ), + ); + }, + } ], + }, + save: noop, + category: 'common', + title: 'updated columns block', + } ); + registerBlockType( 'core/columns-block', defaultBlockSettings ); + registerBlockType( 'core/column-block', defaultBlockSettings ); + + const block = createBlock( + 'core/columns-block', + {}, + [ createBlock( 'core/column-block', { value: 'before' } ) ] + ); + + const transformedBlocks = switchToBlockType( block, 'core/updated-columns-block' ); + + expect( transformedBlocks ).toHaveLength( 1 ); + expect( transformedBlocks[ 0 ].innerBlocks ).toHaveLength( 1 ); + expect( transformedBlocks[ 0 ].innerBlocks[ 0 ].attributes.value ).toBe( 'after' ); + } ); + + it( 'should pass through inner blocks to transform (multi)', () => { + registerBlockType( 'core/updated-columns-block', { + attributes: { + value: { + type: 'string', + }, + }, + transforms: { + from: [ { + type: 'block', + blocks: [ 'core/columns-block' ], + isMultiBlock: true, + transform( blocksAttributes, blocksInnerBlocks ) { + return blocksAttributes.map( ( attributes, i ) => { + return createBlock( + 'core/updated-columns-block', + attributes, + blocksInnerBlocks[ i ].map( ( innerBlock ) => { + return cloneBlock( innerBlock, { + value: 'after' + i, + } ); + } ), + ); + } ); + }, + } ], + }, + save: noop, + category: 'common', + title: 'updated columns block', + } ); + registerBlockType( 'core/columns-block', defaultBlockSettings ); + registerBlockType( 'core/column-block', defaultBlockSettings ); + + const blocks = [ + createBlock( + 'core/columns-block', + {}, + [ createBlock( 'core/column-block', { value: 'before' } ) ] + ), + createBlock( + 'core/columns-block', + {}, + [ createBlock( 'core/column-block', { value: 'before' } ) ] + ), + ]; + + const transformedBlocks = switchToBlockType( blocks, 'core/updated-columns-block' ); + + expect( transformedBlocks ).toHaveLength( 2 ); + expect( transformedBlocks[ 0 ].innerBlocks ).toHaveLength( 1 ); + expect( transformedBlocks[ 0 ].innerBlocks[ 0 ].attributes.value ).toBe( 'after0' ); + expect( transformedBlocks[ 1 ].innerBlocks ).toHaveLength( 1 ); + expect( transformedBlocks[ 1 ].innerBlocks[ 0 ].attributes.value ).toBe( 'after1' ); + } ); } ); describe( 'getBlockTransforms', () => { diff --git a/packages/e2e-tests/plugins/inner-blocks-templates/index.js b/packages/e2e-tests/plugins/inner-blocks-templates/index.js index 0922fe106cecf..6c0b62c51fea4 100644 --- a/packages/e2e-tests/plugins/inner-blocks-templates/index.js +++ b/packages/e2e-tests/plugins/inner-blocks-templates/index.js @@ -1,5 +1,6 @@ ( function() { var registerBlockType = wp.blocks.registerBlockType; + var createBlock = wp.blocks.createBlock; var el = wp.element.createElement; var InnerBlocks = wp.editor.InnerBlocks; var __ = wp.i18n.__; @@ -28,10 +29,10 @@ edit: function( props ) { return el( - InnerBlocks, - { - template: TEMPLATE, - } + InnerBlocks, + { + template: TEMPLATE, + } ); }, @@ -45,11 +46,11 @@ edit: function( props ) { return el( - InnerBlocks, - { - template: TEMPLATE, - templateLock: 'all', - } + InnerBlocks, + { + template: TEMPLATE, + templateLock: 'all', + } ); }, @@ -63,13 +64,57 @@ edit: function( props ) { return el( - InnerBlocks, - { - template: TEMPLATE_PARAGRAPH_PLACEHOLDER, - } + InnerBlocks, + { + template: TEMPLATE_PARAGRAPH_PLACEHOLDER, + } ); }, save, } ); + + registerBlockType( 'test/test-inner-blocks-transformer-target', { + title: 'Test Inner Blocks transformer target', + icon: 'cart', + category: 'common', + + transforms: { + from: [ + { + type: 'block', + blocks: [ + 'test/i-dont-exist', + 'test/test-inner-blocks-no-locking', + 'test/test-inner-blocks-locking-all', + 'test/test-inner-blocks-paragraph-placeholder' + ], + transform: function( attributes, innerBlocks ) { + return createBlock( 'test/test-inner-blocks-transformer-target', attributes, innerBlocks ); + }, + }, + ], + to: [ + { + type: 'block', + blocks: [ 'test/i-dont-exist' ], + transform: function( attributes, innerBlocks ) { + return createBlock( 'test/test-inner-blocks-transformer-target', attributes, innerBlocks ); + }, + } + ] + }, + + edit: function( props ) { + return el( + InnerBlocks, + { + template: TEMPLATE, + } + ); + }, + + save, + } ); + } )(); From d1c78bf149bd4bca3af968e3d3b7162cd670359a Mon Sep 17 00:00:00 2001 From: Pascal Birchler <pascal.birchler@gmail.com> Date: Tue, 29 Jan 2019 02:38:42 -0500 Subject: [PATCH 272/691] Add default block style if missing (#12519) * Add default block style if missing * Add 'block style' context --- .../editor/src/components/block-styles/index.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/editor/src/components/block-styles/index.js b/packages/editor/src/components/block-styles/index.js index ad108daa18643..c44f8f686e191 100644 --- a/packages/editor/src/components/block-styles/index.js +++ b/packages/editor/src/components/block-styles/index.js @@ -11,6 +11,8 @@ import { compose } from '@wordpress/compose'; import { withSelect, withDispatch } from '@wordpress/data'; import TokenList from '@wordpress/token-list'; import { ENTER, SPACE } from '@wordpress/keycodes'; +import { _x } from '@wordpress/i18n'; +import { getBlockType } from '@wordpress/blocks'; /** * Internal dependencies @@ -68,6 +70,7 @@ function BlockStyles( { onChangeClassName, name, attributes, + type, onSwitch = noop, onHoverClassName = noop, } ) { @@ -75,6 +78,17 @@ function BlockStyles( { return null; } + if ( ! type.styles && ! find( styles, 'isDefault' ) ) { + styles = [ + { + name: 'default', + label: _x( 'Default', 'block style' ), + isDefault: true, + }, + ...styles, + ]; + } + const activeStyle = getActiveStyle( styles, className ); function updateClassName( style ) { const updatedClassName = replaceActiveStyle( className, activeStyle, style ); @@ -132,12 +146,14 @@ export default compose( [ const { getBlock } = select( 'core/editor' ); const { getBlockStyles } = select( 'core/blocks' ); const block = getBlock( clientId ); + const blockType = getBlockType( block.name ); return { name: block.name, attributes: block.attributes, className: block.attributes.className || '', styles: getBlockStyles( block.name ), + type: blockType, }; } ), withDispatch( ( dispatch, { clientId } ) => { From bf124b7f10b864694e6c1ffecb0f9fac161472b7 Mon Sep 17 00:00:00 2001 From: Sheri Bigelow <sheri@designsimply.com> Date: Tue, 29 Jan 2019 00:41:50 -0700 Subject: [PATCH 273/691] Update Question template for new issues (#13351) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update Question template for new issues Now that Gutenberg has shipped in WordPress 5.0, we should consider updating the Question template for new issues to gently help people move their Gutenberg help requests to the main support forum as a first stop. This is because the ideal workflow is to ask for help and complete any troubleshooting needed with support first, which can then be moved to GitHub as a bug report once sufficient details are uncovered. This will help reduce duplicate requests, increase the quality of bug reports, and help developers working on the project focus on feature work and bug fixes for verified bug reports. * Update Question template to add StackExchange Add https://wordpress.stackexchange.com/ as a fallback suggested resource for where to get help with technical help requests. Note: this was suggested in a Core Editor Weekly Chat on 2019-01-23 at https://wordpress.slack.com/archives/C02QB2JS7/p1548252431142200. * Update Question template to ask to search first I met with the WordPress Core Support Team to discuss changes to the Question template and we decided to: * Add a line to ask people to search before posting! * Move the handbook note up. * Update the title to "Help Request". * Update the description to "Please post help requests or ‘how to’ questions in support channels first". Link to the discussion for reference: https://wordpress.slack.com/archives/C02RQC6RW/p1548349915905600 * Update Question template to move the handbook link to the end Move the handbook note to the bottom, to be friendlier. * Update Question template with editorial feedback Mending the tone after an editorial review! 😍 --- .github/ISSUE_TEMPLATE/Custom.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/Custom.md b/.github/ISSUE_TEMPLATE/Custom.md index 89783e5bbea90..196cdeb63305f 100644 --- a/.github/ISSUE_TEMPLATE/Custom.md +++ b/.github/ISSUE_TEMPLATE/Custom.md @@ -1,13 +1,17 @@ --- -name: Question -about: Questions or 'how to' about Gutenberg +name: Help Request +about: Please post help requests or ‘how to’ questions in support channels first --- -If you have a question you have a few places that you can ask this: +Search first! Your issue may have already been reported. -- Support Forums: https://wordpress.org/support/plugin/gutenberg -- Handbook: https://wordpress.org/gutenberg/handbook -- https://chat.wordpress.org #core-editor +For general help requests, please post in the support forum at https://wordpress.org/support/forum/how-to-and-troubleshooting/. -If you are unable to ask in those places you can ask here, however you will get faster responses through those recommended places. +Technical help requests have their own section of the support forum at https://wordpress.org/support/forum/wp-advanced/. + +You may also ask for technical support at https://wordpress.stackexchange.com/. + +Please make sure you have checked the Handbook at https://wordpress.org/gutenberg/handbook before asking your question. + +Thank you! From 9e523ceb3221d6eb89a9aa69e3f5db956a483f9d Mon Sep 17 00:00:00 2001 From: Ajit Bohra <ajit@lubus.in> Date: Tue, 29 Jan 2019 14:32:16 +0530 Subject: [PATCH 274/691] Docs: update links (#13531) * Docs: update links * Update link Co-Authored-By: ajitbohra <ajit@lubus.in> * Update link Co-Authored-By: ajitbohra <ajit@lubus.in> * Update link Co-Authored-By: ajitbohra <ajit@lubus.in> * Update link Co-Authored-By: ajitbohra <ajit@lubus.in> * Update link Co-Authored-By: ajitbohra <ajit@lubus.in> * Update & fix dead links * Update README.md * Update writing-your-first-block-type.md * Update scripts.md --- docs/contributors/reference.md | 10 +-- docs/contributors/scripts.md | 64 +++++++++---------- .../backward-compatibility/deprecations.md | 6 +- .../developers/block-api/README.md | 4 +- .../developers/block-api/block-attributes.md | 2 +- .../developers/block-api/block-deprecation.md | 8 +-- .../developers/block-api/block-edit-save.md | 8 +-- .../block-api/block-registration.md | 12 ++-- .../developers/data/README.md | 16 ++--- .../developers/filters/README.md | 2 +- .../developers/filters/block-filters.md | 2 +- .../developers/themes/theme-support.md | 2 +- .../block-tutorial/creating-dynamic-blocks.md | 2 +- .../generate-blocks-with-wp-cli.md | 16 ++--- ...roducing-attributes-and-editable-fields.md | 2 +- .../writing-your-first-block-type.md | 4 +- .../javascript/extending-the-block-editor.md | 4 +- .../developers/tutorials/javascript/readme.md | 12 ++-- .../tutorials/metabox/meta-block-3-add.md | 2 +- .../sidebar-tutorial/plugin-sidebar-0.md | 14 ++-- .../plugin-sidebar-1-up-and-running.md | 2 +- .../plugin-sidebar-2-styles-and-controls.md | 2 +- .../plugin-sidebar-3-register-meta.md | 2 +- .../plugin-sidebar-4-initialize-input.md | 4 +- .../plugin-sidebar-5-update-meta.md | 2 +- docs/designers-developers/faq.md | 8 +-- docs/designers-developers/key-concepts.md | 2 +- docs/readme.md | 4 +- docs/tool/generator.js | 2 +- packages/components/CONTRIBUTING.md | 2 +- 30 files changed, 111 insertions(+), 111 deletions(-) diff --git a/docs/contributors/reference.md b/docs/contributors/reference.md index 551c6f82ab8e7..95c0b603323e5 100644 --- a/docs/contributors/reference.md +++ b/docs/contributors/reference.md @@ -1,9 +1,9 @@ # Reference -- [Glossary](../../docs/designers-developers/glossary.md) -- [Coding Guidelines](../../docs/contributors/coding-guidelines.md) -- [Testing Overview](../../docs/contributors/testing-overview.md) -- [Frequently Asked Questions](../../docs/designers-developers/faq.md) +- [Glossary](/docs/designers-developers/glossary.md) +- [Coding Guidelines](/docs/contributors/coding-guidelines.md) +- [Testing Overview](/docs/contributors/testing-overview.md) +- [Frequently Asked Questions](/docs/designers-developers/faq.md) ## Logo <img width="200" src="https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/final-g-wapuu-black.svg?sanitize=true" alt="Gutenberg Logo" /> @@ -14,4 +14,4 @@ Released under GPL license, made by [Cristel Rossignol](https://twitter.com/cris ## Mockups -Mockup Sketch files are available in <a href="https://wordpress.org/gutenberg/handbook/designers-developers/designers/design-resources/">the Design section</a>. +Mockup Sketch files are available in [the Design section](/docs/designers-developers/designers/design-resources.md)</a>. diff --git a/docs/contributors/scripts.md b/docs/contributors/scripts.md index dd7e1d295d528..5d5c1f672a4b2 100644 --- a/docs/contributors/scripts.md +++ b/docs/contributors/scripts.md @@ -8,38 +8,38 @@ The editor includes a number of packages to enable various pieces of functionali | Script Name | Handle | Description | |-------------|--------|-------------| -| [Blob](https://wordpress.org/gutenberg/handbook/packages/packages-blob/) | wp-blob | Blob utilities | -| [Block Library](https://wordpress.org/gutenberg/handbook/packages/packages-block-library/) | wp-block-library | Block library for the editor | -| [Blocks](https://wordpress.org/gutenberg/handbook/packages/packages-blocks/) | wp-blocks | Block creations | -| [Block Serialization Default Parser](https://wordpress.org/gutenberg/handbook/packages/packages-block-serialization-default-parser/) | wp-block-serialization-default-parser | Default block serialization parser implementations for WordPress documents | -| [Block Serialization Spec Parser](https://wordpress.org/gutenberg/handbook/packages/packages-block-serialization-spec-parser/) | wp-block-serialization-spec-parser | Grammar file (grammar.pegjs) for WordPress posts | -| [Components](https://wordpress.org/gutenberg/handbook/packages/packages-components/) | wp-components | Generic components to be used for creating common UI elements | -| [Compose](https://wordpress.org/gutenberg/handbook/packages/packages-compose/) | wp-compose | Collection of handy Higher Order Components (HOCs) | -| [Core Data](https://wordpress.org/gutenberg/handbook/packages/packages-core-data/) | wp-core-data | Simplify access to and manipulation of core WordPress entities | -| [Data](https://wordpress.org/gutenberg/handbook/packages/packages-data/) | wp-data | Data module serves as a hub to manage application state for both plugins and WordPress itself | -| [Date](https://wordpress.org/gutenberg/handbook/packages/packages-date/) | wp-date | Date module for WordPress | -| [Deprecated](https://wordpress.org/gutenberg/handbook/packages/packages-deprecated/) | wp-deprecated | Utility to log a message to notify developers about a deprecated feature | -| [Dom](https://wordpress.org/gutenberg/handbook/packages/packages-dom/) | wp-dom | DOM utilities module for WordPress | -| [Dom Ready](https://wordpress.org/gutenberg/handbook/packages/packages-dom-ready/) | wp-dom-ready | Execute callback after the DOM is loaded | -| [Editor](https://wordpress.org/gutenberg/handbook/packages/packages-editor/) | wp-editor | Building blocks for WordPress editors | -| [Edit Post](https://wordpress.org/gutenberg/handbook/packages/packages-edit-post/) | wp-edit-post | Edit Post Module for WordPress | -| [Element](https://wordpress.org/gutenberg/handbook/packages/packages-element/) | wp-element |Element is, quite simply, an abstraction layer atop [React](https://reactjs.org/) | -| [Escape Html](https://wordpress.org/gutenberg/handbook/packages/packages-escape-html/) | wp-escape-html | Escape HTML utils | -| [Hooks](https://wordpress.org/gutenberg/handbook/packages/packages-hooks/) | wp-hooks | A lightweight and efficient EventManager for JavaScript | -| [Html Entities](https://wordpress.org/gutenberg/handbook/packages/packages-html-entities/) | wp-html-entities | HTML entity utilities for WordPress | -| [I18N](https://wordpress.org/gutenberg/handbook/packages/packages-i18n/) | wp-i18n | Internationalization utilities for client-side localization | -| [Is Shallow Equal](https://wordpress.org/gutenberg/handbook/packages/packages-is-shallow-equal/) | wp-is-shallow-equal | A function for performing a shallow comparison between two objects or arrays | -| [Keycodes](https://wordpress.org/gutenberg/handbook/packages/packages-keycodes/) | wp-keycodes | Keycodes utilities for WordPress, used to check the key pressed in events like `onKeyDown` | -| [List Reusable Bocks](https://wordpress.org/gutenberg/handbook/packages/packages-list-reusable-blocks/) | wp-list-reusable-blocks | Package used to add import/export links to the listing page of the reusable blocks | -| [NUX](https://wordpress.org/gutenberg/handbook/packages/packages-nux/) | wp-nux | Components, and wp.data methods useful for onboarding a new user to the WordPress admin interface | -| [Plugins](https://wordpress.org/gutenberg/handbook/packages/packages-plugins/) | wp-plugins | Plugins module for WordPress | -| [Redux Routine](https://wordpress.org/gutenberg/handbook/packages/packages-redux-routine/) | wp-redux-routine | Redux middleware for generator coroutines | -| [Rich Text](https://wordpress.org/gutenberg/handbook/packages/packages-rich-text/) | wp-rich-text | Helper functions to convert HTML or a DOM tree into a rich text value and back | -| [Shortcode](https://wordpress.org/gutenberg/handbook/packages/packages-shortcode/) | wp-shortcode | Shortcode module for WordPress | -| [Token List](https://wordpress.org/gutenberg/handbook/packages/packages-token-list/) | wp-token-list | Constructable, plain JavaScript [DOMTokenList](https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList) implementation, supporting non-browser runtimes | -| [URL](https://wordpress.org/gutenberg/handbook/packages/packages-url/) | wp-url | A collection of utilities to manipulate URLs | -| [Viewport](https://wordpress.org/gutenberg/handbook/packages/packages-viewport/) | wp-viewport | Module for responding to changes in the browser viewport size | -| [Wordcount](https://wordpress.org/gutenberg/handbook/packages/packages-wordcount/) | wp-wordcount | WordPress word count utility | +| [Blob](/packages/blob/README.md) | wp-blob | Blob utilities | +| [Block Library](/packages/block-library/README.md) | wp-block-library | Block library for the editor | +| [Blocks](/packages/blocks/README.md) | wp-blocks | Block creations | +| [Block Serialization Default Parser](/packages/block-serialization-default-parser/README.md) | wp-block-serialization-default-parser | Default block serialization parser implementations for WordPress documents | +| [Block Serialization Spec Parser](/packages/block-serialization-spec-parser/README.md) | wp-block-serialization-spec-parser | Grammar file (grammar.pegjs) for WordPress posts | +| [Components](/packages/components/README.md) | wp-components | Generic components to be used for creating common UI elements | +| [Compose](/packages/compose/README.md) | wp-compose | Collection of handy Higher Order Components (HOCs) | +| [Core Data](/packages/core-data/README.md) | wp-core-data | Simplify access to and manipulation of core WordPress entities | +| [Data](/packages/data/README.md) | wp-data | Data module serves as a hub to manage application state for both plugins and WordPress itself | +| [Date](/packages/date/README.md) | wp-date | Date module for WordPress | +| [Deprecated](/packages/deprecated/README.md) | wp-deprecated | Utility to log a message to notify developers about a deprecated feature | +| [Dom](/packages/dom/README.md) | wp-dom | DOM utilities module for WordPress | +| [Dom Ready](/packages/dom-ready/README.md) | wp-dom-ready | Execute callback after the DOM is loaded | +| [Editor](/packages/editor/README.md) | wp-editor | Building blocks for WordPress editors | +| [Edit Post](/packages/edit-post/README.md) | wp-edit-post | Edit Post Module for WordPress | +| [Element](/packages/element/README.md) | wp-element |Element is, quite simply, an abstraction layer atop [React](https://reactjs.org/) | +| [Escape Html](/packages/escape-html/README.md) | wp-escape-html | Escape HTML utils | +| [Hooks](/packages/hooks/README.md) | wp-hooks | A lightweight and efficient EventManager for JavaScript | +| [Html Entities](/packages/html-entities/README.md) | wp-html-entities | HTML entity utilities for WordPress | +| [I18N](/packages/i18n/README.md) | wp-i18n | Internationalization utilities for client-side localization | +| [Is Shallow Equal](/packages/is-shallow-equal/README.md) | wp-is-shallow-equal | A function for performing a shallow comparison between two objects or arrays | +| [Keycodes](/packages/keycodes/README.md) | wp-keycodes | Keycodes utilities for WordPress, used to check the key pressed in events like `onKeyDown` | +| [List Reusable Bocks](/packages/list-reusable-blocks/README.md) | wp-list-reusable-blocks | Package used to add import/export links to the listing page of the reusable blocks | +| [NUX](/packages/nux/README.md) | wp-nux | Components, and wp.data methods useful for onboarding a new user to the WordPress admin interface | +| [Plugins](/packages/plugins/README.md) | wp-plugins | Plugins module for WordPress | +| [Redux Routine](/packages/redux-routine/README.md) | wp-redux-routine | Redux middleware for generator coroutines | +| [Rich Text](/packages/rich-text/README.md) | wp-rich-text | Helper functions to convert HTML or a DOM tree into a rich text value and back | +| [Shortcode](/packages/shortcode/README.md) | wp-shortcode | Shortcode module for WordPress | +| [Token List](/packages/token-list/README.md) | wp-token-list | Constructable, plain JavaScript [DOMTokenList](https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList) implementation, supporting non-browser runtimes | +| [URL](/packages/url/README.md) | wp-url | A collection of utilities to manipulate URLs | +| [Viewport](/packages/viewport/README.md) | wp-viewport | Module for responding to changes in the browser viewport size | +| [Wordcount](/packages/wordcount/README.md) | wp-wordcount | WordPress word count utility | ## Vendor Scripts diff --git a/docs/designers-developers/developers/backward-compatibility/deprecations.md b/docs/designers-developers/developers/backward-compatibility/deprecations.md index f2ce4c1ee8d2f..e4a99cff29513 100644 --- a/docs/designers-developers/developers/backward-compatibility/deprecations.md +++ b/docs/designers-developers/developers/backward-compatibility/deprecations.md @@ -214,11 +214,11 @@ The Gutenberg project's deprecation policy is intended to support backward compa ## 3.0.0 - `wp.blocks.registerCoreBlocks` function removed. Please use `wp.coreBlocks.registerCoreBlocks` instead. - - Raw TinyMCE event handlers for `RichText` have been deprecated. Please use [documented props](https://wordpress.org/gutenberg/handbook/block-api/rich-text-api/), ancestor event handler, or onSetup access to the internal editor instance event hub instead. + - Raw TinyMCE event handlers for `RichText` have been deprecated. Please use [documented props](/packages/editor/src/components/rich-text/README.md), ancestor event handler, or onSetup access to the internal editor instance event hub instead. ## 2.8.0 - - `Original autocompleter interface in wp.components.Autocomplete` updated. Please use `latest autocompleter interface` instead. See: https://github.com/WordPress/gutenberg/blob/master/components/autocomplete/README.md. + - `Original autocompleter interface in wp.components.Autocomplete` updated. Please use `latest autocompleter interface` instead. See [autocomplete](/packages/components/src/autocomplete/README.md) for more info. - `getInserterItems`: the `allowedBlockTypes` argument is now mandatory. - `getFrecentInserterItems`: the `allowedBlockTypes` argument is now mandatory. @@ -240,6 +240,6 @@ The Gutenberg project's deprecation policy is intended to support backward compa - `wp.blocks.BlockDescription` component removed. Please use the `description` block property instead. - `wp.blocks.InspectorControls.*` components removed. Please use `wp.components.*` components instead. - - `wp.blocks.source.*` matchers removed. Please use the declarative attributes instead. See: https://wordpress.org/gutenberg/handbook/block-api/attributes/. + - `wp.blocks.source.*` matchers removed. Please use the declarative attributes instead. See [block attributes](/docs/designers-developers/developers/block-api/block-attributes.md) for more info. - `wp.data.select( 'selector', ...args )` removed. Please use `wp.data.select( reducerKey' ).*` instead. - `wp.blocks.MediaUploadButton` component removed. Please use `wp.blocks.MediaUpload` component instead. diff --git a/docs/designers-developers/developers/block-api/README.md b/docs/designers-developers/developers/block-api/README.md index af153d59ba38a..56d159bda6d13 100644 --- a/docs/designers-developers/developers/block-api/README.md +++ b/docs/designers-developers/developers/block-api/README.md @@ -4,8 +4,8 @@ Blocks are the fundamental element of the editor. They are the primary way in wh ## Registering a block -All blocks must be registered before they can be used in the editor. You can learn about block registration, and the available options, in the [block registration](../../../../docs/designers-developers/developers/block-api/block-registration.md) documentation. +All blocks must be registered before they can be used in the editor. You can learn about block registration, and the available options, in the [block registration](/docs/designers-developers/developers/block-api/block-registration.md) documentation. ## Block `edit` and `save` -The `edit` and `save` functions define the editor interface with which a user would interact, and the markup to be serialized back when a post is saved. They are the heart of how a block operates, so they are [covered separately](../../../../docs/designers-developers/developers/block-api/block-edit-save.md). +The `edit` and `save` functions define the editor interface with which a user would interact, and the markup to be serialized back when a post is saved. They are the heart of how a block operates, so they are [covered separately](/docs/designers-developers/developers/block-api/block-edit-save.md). diff --git a/docs/designers-developers/developers/block-api/block-attributes.md b/docs/designers-developers/developers/block-api/block-attributes.md index 48a9b36ef83b5..fd61866f5286c 100644 --- a/docs/designers-developers/developers/block-api/block-attributes.md +++ b/docs/designers-developers/developers/block-api/block-attributes.md @@ -4,7 +4,7 @@ Attribute sources are used to define the strategy by which block attribute values are extracted from saved post content. They provide a mechanism to map from the saved markup to a JavaScript representation of a block. -If no attribute source is specified, the attribute will be saved to (and read from) the block's [comment delimiter](../../../../docs/designers-developers/key-concepts.md#delimiters-and-parsing-expression-grammar). +If no attribute source is specified, the attribute will be saved to (and read from) the block's [comment delimiter](/docs/designers-developers/key-concepts.md#delimiters-and-parsing-expression-grammar). Each source accepts an optional selector as the first argument. If a selector is specified, the source behavior will be run against the corresponding element(s) contained within the block. Otherwise it will be run against the block's root node. diff --git a/docs/designers-developers/developers/block-api/block-deprecation.md b/docs/designers-developers/developers/block-api/block-deprecation.md index 3fd500c164541..7e64bfb89a999 100644 --- a/docs/designers-developers/developers/block-api/block-deprecation.md +++ b/docs/designers-developers/developers/block-api/block-deprecation.md @@ -9,9 +9,9 @@ A block can have several deprecated versions. A deprecation will be tried if a p Deprecations are defined on a block type as its `deprecated` property, an array of deprecation objects where each object takes the form: -- `attributes` (Object): The [attributes definition](../../../../docs/designers-developers/developers/block-api/block-attributes.md) of the deprecated form of the block. -- `support` (Object): The [supports definition](../../../../docs/designers-developers/developers/block-api/block-registration.md) of the deprecated form of the block. -- `save` (Function): The [save implementation](../../../../docs/designers-developers/developers/block-api/block-edit-save.md) of the deprecated form of the block. +- `attributes` (Object): The [attributes definition](/docs/designers-developers/developers/block-api/block-attributes.md) of the deprecated form of the block. +- `support` (Object): The [supports definition](/docs/designers-developers/developers/block-api/block-registration.md) of the deprecated form of the block. +- `save` (Function): The [save implementation](/docs/designers-developers/developers/block-api/block-edit-save.md) of the deprecated form of the block. - `migrate` (Function, Optional): A function which, given the attributes and inner blocks of the parsed block, is expected to return either the attributes compatible with the deprecated block, or a tuple array of `[ attributes, innerBlocks ]`. - `isEligible` (Function, Optional): A function which, given the attributes and inner blocks of the parsed block, returns true if the deprecation can handle the block migration. This is particularly useful in cases where a block is technically valid even once deprecated, and requires updates to its attributes or inner blocks. @@ -275,4 +275,4 @@ registerBlockType( 'gutenberg/block-with-deprecated-version', { In the example above we updated the block to use an inner paragraph block with a title instead of a title attribute. -*Above are example cases of block deprecation. For more, real-world examples, check for deprecations in the [core block library](https://github.com/WordPress/gutenberg/tree/master/packages/block-library/src). Core blocks have been updated across releases and contain simple and complex deprecations.* +*Above are example cases of block deprecation. For more, real-world examples, check for deprecations in the [core block library](/packages/block-library/src/README.md). Core blocks have been updated across releases and contain simple and complex deprecations.* diff --git a/docs/designers-developers/developers/block-api/block-edit-save.md b/docs/designers-developers/developers/block-api/block-edit-save.md index f72baac90858d..1bfaf08f10a22 100644 --- a/docs/designers-developers/developers/block-api/block-edit-save.md +++ b/docs/designers-developers/developers/block-api/block-edit-save.md @@ -117,11 +117,11 @@ save() { ``` {% end %} -For most blocks, the return value of `save` should be an [instance of WordPress Element](https://github.com/WordPress/gutenberg/blob/master/packages/element/README.md) representing how the block is to appear on the front of the site. +For most blocks, the return value of `save` should be an [instance of WordPress Element](/packages/element/README.md) representing how the block is to appear on the front of the site. _Note:_ While it is possible to return a string value from `save`, it _will be escaped_. If the string includes HTML markup, the markup will be shown on the front of the site verbatim, not as the equivalent HTML node content. If you must return raw HTML from `save`, use `wp.element.RawHTML`. As the name implies, this is prone to [cross-site scripting](https://en.wikipedia.org/wiki/Cross-site_scripting) and therefore is discouraged in favor of a WordPress Element hierarchy whenever possible. -For [dynamic blocks](../../../../docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md), the return value of `save` could either represent a cached copy of the block's content to be shown only in case the plugin implementing the block is ever disabled. Alternatively, return a `null` (empty) value to save no markup in post content for the dynamic block, instead deferring this to always be calculated when the block is shown on the front of the site. +For [dynamic blocks](/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md), the return value of `save` could either represent a cached copy of the block's content to be shown only in case the plugin implementing the block is ever disabled. Alternatively, return a `null` (empty) value to save no markup in post content for the dynamic block, instead deferring this to always be calculated when the block is shown on the front of the site. ### attributes @@ -171,10 +171,10 @@ The two most common sources of block invalidations are: Before starting to debug, be sure to familiarize yourself with the validation step described above documenting the process for detecting whether a block is invalid. A block is invalid if its regenerated markup does not match what is saved in post content, so often this can be caused by the attributes of a block being parsed incorrectly from the saved content. -If you're using [attribute sources](../../../../docs/designers-developers/developers/block-api/block-attributes.md), be sure that attributes sourced from markup are saved exactly as you expect, and in the correct type (usually a `'string'` or `'number'`). +If you're using [attribute sources](/docs/designers-developers/developers/block-api/block-attributes.md), be sure that attributes sourced from markup are saved exactly as you expect, and in the correct type (usually a `'string'` or `'number'`). When a block is detected as invalid, a warning will be logged into your browser's developer tools console. The warning will include specific details about the exact point at which a difference in markup occurred. Be sure to look closely at any differences in the expected and actual markups to see where problems are occurring. **I've changed my block's `save` behavior and old content now includes invalid blocks. How can I fix this?** -Refer to the guide on [Deprecated Blocks](../../../../docs/designers-developers/developers/block-api/block-deprecations.md) to learn more about how to accommodate legacy content in intentional markup changes. +Refer to the guide on [Deprecated Blocks](/docs/designers-developers/developers/block-api/block-deprecations.md) to learn more about how to accommodate legacy content in intentional markup changes. diff --git a/docs/designers-developers/developers/block-api/block-registration.md b/docs/designers-developers/developers/block-api/block-registration.md index e36628c7249e0..4bc32fb9f4e0f 100644 --- a/docs/designers-developers/developers/block-api/block-registration.md +++ b/docs/designers-developers/developers/block-api/block-registration.md @@ -66,7 +66,7 @@ The core provided categories are: category: 'widgets', ``` -Plugins and Themes can also register [custom block categories](../docs/extensibility/extending-blocks/#managing-block-categories). +Plugins and Themes can also register [custom block categories](/docs/designers-developers/developers/filters/block-filters.md#managing-block-categories). #### Icon (optional) @@ -82,7 +82,7 @@ icon: 'book-alt', icon: <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="none" d="M0 0h24v24H0V0z" /><path d="M19 13H5v-2h14v2z" /></svg>, ``` -**Note:** Custom SVG icons are automatically wrapped in the [`wp.components.SVG` component](https://github.com/WordPress/gutenberg/tree/master/packages/components/src/primitives/svg/) to add accessibility attributes (`aria-hidden`, `role`, and `focusable`). +**Note:** Custom SVG icons are automatically wrapped in the [`wp.components.SVG` component](/packages/components/src/primitives/svg/) to add accessibility attributes (`aria-hidden`, `role`, and `focusable`). An object can also be passed as icon, in this case, icon, as specified above, should be included in the src property. Besides src the object can contain background and foreground colors, this colors will appear with the icon @@ -138,7 +138,7 @@ styles: [ ], ``` -Plugins and Themes can also register [custom block style](../docs/extensibility/extending-blocks/#block-style-variations) for existing blocks. +Plugins and Themes can also register [custom block style](/docs/designers-developers/developers/filters/block-filters.md#block-style-variations) for existing blocks. #### Attributes (optional) @@ -166,7 +166,7 @@ attributes: { }, ``` -* **See: [Attributes](../docs/block-api/attributes.md).** +* **See: [Attributes](/docs/designers-developers/developers/block-api/block-attributes.md).** #### Transforms (optional) @@ -486,7 +486,7 @@ transforms: { * **Type:** `Array` -Blocks are able to be inserted into blocks that use [`InnerBlocks`](https://github.com/WordPress/gutenberg/blob/master/packages/editor/src/components/inner-blocks/README.md) as nested content. Sometimes it is useful to restrict a block so that it is only available as a nested block. For example, you might want to allow an 'Add to Cart' block to only be available within a 'Product' block. +Blocks are able to be inserted into blocks that use [`InnerBlocks`](/packages/editor/src/components/inner-blocks/README.md) as nested content. Sometimes it is useful to restrict a block so that it is only available as a nested block. For example, you might want to allow an 'Add to Cart' block to only be available within a 'Product' block. Setting `parent` lets a block require that it is only available when nested within the specified blocks. @@ -525,7 +525,7 @@ attributes: { } ``` -- `alignWide` (default `true`): This property allows to enable [wide alignment](../docs/extensibility/theme-support.md#wide-alignment) for your theme. To disable this behavior for a single block, set this flag to `false`. +- `alignWide` (default `true`): This property allows to enable [wide alignment](/docs/designers-developers/developers/themes/theme-support.md#wide-alignment) for your theme. To disable this behavior for a single block, set this flag to `false`. ```js // Remove the support for wide alignment. diff --git a/docs/designers-developers/developers/data/README.md b/docs/designers-developers/developers/data/README.md index 5233753c1445e..7ba7f9264e8bc 100644 --- a/docs/designers-developers/developers/data/README.md +++ b/docs/designers-developers/developers/data/README.md @@ -1,10 +1,10 @@ # Data Module Reference - - [**core**: WordPress Core Data](../../docs/designers-developers/developers/data/data-core.md) - - [**core/annotations**: Annotations](../../docs/designers-developers/developers/data/data-core-annotations.md) - - [**core/blocks**: Block Types Data](../../docs/designers-developers/developers/data/data-core-blocks.md) - - [**core/editor**: The Editor’s Data](../../docs/designers-developers/developers/data/data-core-editor.md) - - [**core/edit-post**: The Editor’s UI Data](../../docs/designers-developers/developers/data/data-core-edit-post.md) - - [**core/notices**: Notices Data](../../docs/designers-developers/developers/data/data-core-notices.md) - - [**core/nux**: The NUX (New User Experience) Data](../../docs/designers-developers/developers/data/data-core-nux.md) - - [**core/viewport**: The Viewport Data](../../docs/designers-developers/developers/data/data-core-viewport.md) \ No newline at end of file + - [**core**: WordPress Core Data](/docs/designers-developers/developers/data/data-core.md) + - [**core/annotations**: Annotations](/docs/designers-developers/developers/data/data-core-annotations.md) + - [**core/blocks**: Block Types Data](/docs/designers-developers/developers/data/data-core-blocks.md) + - [**core/editor**: The Editor’s Data](/docs/designers-developers/developers/data/data-core-editor.md) + - [**core/edit-post**: The Editor’s UI Data](/docs/designers-developers/developers/data/data-core-edit-post.md) + - [**core/notices**: Notices Data](/docs/designers-developers/developers/data/data-core-notices.md) + - [**core/nux**: The NUX (New User Experience) Data](/docs/designers-developers/developers/data/data-core-nux.md) + - [**core/viewport**: The Viewport Data](/docs/designers-developers/developers/data/data-core-viewport.md) \ No newline at end of file diff --git a/docs/designers-developers/developers/filters/README.md b/docs/designers-developers/developers/filters/README.md index f6da621ac74be..c110d2ed3a554 100644 --- a/docs/designers-developers/developers/filters/README.md +++ b/docs/designers-developers/developers/filters/README.md @@ -4,4 +4,4 @@ There are two types of hooks: [Actions](https://developer.wordpress.org/plugins/hooks/actions/) and [Filters](https://developer.wordpress.org/plugins/hooks/filters/). In addition to PHP actions and filters, WordPress also provides a mechanism for registering and executing hooks in JavaScript. This functionality is also available on npm as the [@wordpress/hooks](https://www.npmjs.com/package/@wordpress/hooks) package, for general purpose use. -You can also learn more about both APIs: [PHP](https://codex.wordpress.org/Plugin_API/) and [JavaScript](https://github.com/WordPress/packages/tree/master/packages/hooks). +You can also learn more about both APIs: [PHP](https://codex.wordpress.org/Plugin_API/) and [JavaScript](/packages/tree/master/packages/hooks). diff --git a/docs/designers-developers/developers/filters/block-filters.md b/docs/designers-developers/developers/filters/block-filters.md index cc4f71bb95540..120abb63fb385 100644 --- a/docs/designers-developers/developers/filters/block-filters.md +++ b/docs/designers-developers/developers/filters/block-filters.md @@ -113,7 +113,7 @@ wp.hooks.addFilter( ); ``` -_Note:_ This filter must always be run on every page load, and not in your browser's developer tools console. Otherwise, a [block validation](../../../../docs/designers-developers/developers/block-api/block-edit-save.md#validation) error will occur the next time the post is edited. This is due to the fact that block validation occurs by verifying that the saved output matches what is stored in the post's content during editor initialization. So, if this filter does not exist when the editor loads, the block will be marked as invalid. +_Note:_ This filter must always be run on every page load, and not in your browser's developer tools console. Otherwise, a [block validation](/docs/designers-developers/developers/block-api/block-edit-save.md#validation) error will occur the next time the post is edited. This is due to the fact that block validation occurs by verifying that the saved output matches what is stored in the post's content during editor initialization. So, if this filter does not exist when the editor loads, the block will be marked as invalid. #### `blocks.getBlockDefaultClassName` diff --git a/docs/designers-developers/developers/themes/theme-support.md b/docs/designers-developers/developers/themes/theme-support.md index 07ab19f68df7d..765483eadb798 100644 --- a/docs/designers-developers/developers/themes/theme-support.md +++ b/docs/designers-developers/developers/themes/theme-support.md @@ -266,7 +266,7 @@ To change the main column width of the editor, add the following CSS to `style-e You can use those editor widths to match those in your theme. You can use any CSS width unit, including `%` or `px`. -Further reading: [Applying Styles with Stylesheets](https://wordpress.org/gutenberg/handbook/blocks/applying-styles-with-stylesheets/). +Further reading: [Applying Styles with Stylesheets](/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md). ## Default block styles diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md b/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md index e905d0b1a9a94..aa785ff85e31f 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md @@ -124,7 +124,7 @@ There are a few things to notice: ## Live rendering in Gutenberg editor -Gutenberg 2.8 added the [`<ServerSideRender>`](https://github.com/WordPress/gutenberg/tree/master/packages/components/src/server-side-render) block which enables rendering to take place on the server using PHP rather than in JavaScript. +Gutenberg 2.8 added the [`<ServerSideRender>`](/packages/components/src/server-side-render) block which enables rendering to take place on the server using PHP rather than in JavaScript. *Server-side render is meant as a fallback; client-side rendering in JavaScript is always preferred (client rendering is faster and allows better editor manipulation).* diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/generate-blocks-with-wp-cli.md b/docs/designers-developers/developers/tutorials/block-tutorial/generate-blocks-with-wp-cli.md index 81c3fa0373a82..b8fc6862dc69d 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/generate-blocks-with-wp-cli.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/generate-blocks-with-wp-cli.md @@ -5,7 +5,7 @@ It turns out that writing the simplest possible block which contains only static - [zgordon/gutenberg-course](https://github.com/zgordon/gutenberg-course) - a repository for Zac Gordon's Gutenberg Development Course - [ahmadawais/create-guten-block](https://github.com/ahmadawais/create-guten-block) - A zero-configuration developer toolkit for building WordPress Gutenberg block plugins -It might be also a good idea to browse the folder with [all core blocks](https://github.com/WordPress/gutenberg/tree/master/packages/block-library/src) to see how they are implemented. +It might be also a good idea to browse the folder with [all core blocks](/packages/block-library/src) to see how they are implemented. ## WP-CLI @@ -62,7 +62,7 @@ This will generate 4 files inside the `movies` plugin directory. All files conta * Registers all block assets so that they can be enqueued through Gutenberg in * the corresponding context. * - * @see https://wordpress.org/gutenberg/handbook/blocks/writing-your-first-block-type/#enqueuing-block-scripts + * @see https://wordpress.org/gutenberg/handbook/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type/ */ function movie_block_init() { $dir = dirname( __FILE__ ); @@ -109,23 +109,23 @@ add_action( 'init', 'movie_block_init' ); ( function( wp ) { /** * Registers a new block provided a unique name and an object defining its behavior. - * @see https://github.com/WordPress/gutenberg/tree/master/blocks#api + * @see https://wordpress.org/gutenberg/handbook/designers-developers/developers/block-api/#registering-a-block */ var registerBlockType = wp.blocks.registerBlockType; /** * Returns a new element of given type. Element is an abstraction layer atop React. - * @see https://github.com/WordPress/gutenberg/tree/master/packages/element#element + * @see https://wordpress.org/gutenberg/handbook/designers-developers/developers/packages/packages-element/ */ var el = wp.element.createElement; /** * Retrieves the translation of text. - * @see https://github.com/WordPress/gutenberg/tree/master/i18n#api + * @see https://wordpress.org/gutenberg/handbook/designers-developers/developers/packages/packages-i18n/ */ var __ = wp.i18n.__; /** * Every block starts by registering a new block type definition. - * @see https://wordpress.org/gutenberg/handbook/block-api/ + * @see https://wordpress.org/gutenberg/handbook/designers-developers/developers/block-api/#registering-a-block */ registerBlockType( 'movies/movie', { /** @@ -151,7 +151,7 @@ add_action( 'init', 'movie_block_init' ); /** * The edit function describes the structure of your block in the context of the editor. * This represents what the editor will render when the block is used. - * @see https://wordpress.org/gutenberg/handbook/block-edit-save/#edit + * @see https://wordpress.org/gutenberg/handbook/designers-developers/developers/block-api/block-edit-save/#edit * * @param {Object} [props] Properties passed from the editor. * @return {Element} Element to render. @@ -167,7 +167,7 @@ add_action( 'init', 'movie_block_init' ); /** * The save function defines the way in which the different attributes should be combined * into the final markup, which is then serialized by Gutenberg into `post_content`. - * @see https://wordpress.org/gutenberg/handbook/block-edit-save/#save + * @see https://wordpress.org/gutenberg/handbook/designers-developers/developers/block-api/block-edit-save/#save * * @return {Element} Element to render. */ diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md b/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md index cd2b40e7d561e..7fa730c73abce 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md @@ -110,7 +110,7 @@ registerBlockType( 'gutenberg-boilerplate-esnext/hello-world-step-03', { ``` {% end %} -When registering a new block type, the `attributes` property describes the shape of the attributes object you'd like to receive in the `edit` and `save` functions. Each value is a [source function](../../../../../docs/designers-developers/developers/block-api/block-attributes.md) to find the desired value from the markup of the block. +When registering a new block type, the `attributes` property describes the shape of the attributes object you'd like to receive in the `edit` and `save` functions. Each value is a [source function](/docs/designers-developers/developers/block-api/block-attributes.md) to find the desired value from the markup of the block. In the code snippet above, when loading the editor, we will extract the `content` value as the HTML of the paragraph element in the saved post's markup. diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md b/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md index 4a7c8449763bc..ea9a18bbf2368 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md @@ -28,7 +28,7 @@ add_action( 'init', 'gutenberg_boilerplate_block' ); Note the two script dependencies: - __`wp-blocks`__ includes block type registration and related functions -- __`wp-element`__ includes the [WordPress Element abstraction](https://github.com/WordPress/gutenberg/tree/master/packages/element) for describing the structure of your blocks +- __`wp-element`__ includes the [WordPress Element abstraction](/packages/element/README.md) for describing the structure of your blocks If you were to use a component from the `wp-editor` package, for example the RichText component, you would also need to add `wp-editor` to the dependency list. @@ -82,7 +82,7 @@ registerBlockType( 'gutenberg-boilerplate-esnext/hello-world-step-01', { ``` {% end %} -Once a block is registered, you should immediately see that it becomes available as an option in the editor inserter dialog, using values from `title`, `icon`, and `category` to organize its display. You can choose an icon from any included in the built-in [Dashicons icon set](https://developer.wordpress.org/resource/dashicons/), or provide a [custom svg element](https://wordpress.org/gutenberg/handbook/designers-developers/developers/block-api/block-registration/#icon-optional). +Once a block is registered, you should immediately see that it becomes available as an option in the editor inserter dialog, using values from `title`, `icon`, and `category` to organize its display. You can choose an icon from any included in the built-in [Dashicons icon set](https://developer.wordpress.org/resource/dashicons/), or provide a [custom svg element](/docs/designers-developers/developers/block-api/block-registration.md#icon-optional). A block name must be prefixed with a namespace specific to your plugin. This helps prevent conflicts when more than one plugin registers a block with the same name. diff --git a/docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md b/docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md index 67d703cb37310..8d175ef3e6d9b 100644 --- a/docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md +++ b/docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md @@ -1,6 +1,6 @@ # Extending the Block Editor -Let's look at using the [Block Style Variation example](../../../../../docs/designers-developers/developers/filters/block-filters.md#block-style-variations) to extend the editor. This example allows you to add your own custom CSS class name to any core block type. +Let's look at using the [Block Style Variation example](/docs/designers-developers/developers/filters/block-filters.md#block-style-variations) to extend the editor. This example allows you to add your own custom CSS class name to any core block type. Replace the existing `console.log()` code in your `myguten.js` file with: @@ -30,7 +30,7 @@ add_action( 'enqueue_block_editor_assets', 'myguten_enqueue' ); The last argument in the `wp_enqueue_script()` function is an array of dependencies. WordPress makes packages available under the `wp` namespace. In the example, you use `wp.blocks` to access the items that the blocks package exports (in this case the `registerBlockStyle()` function). -See [Packages](../../../../../docs/designers-developers/developers/packages.md) for list of available packages and what objects they export. +See [Packages](/docs/designers-developers/developers/packages.md) for list of available packages and what objects they export. After you have updated both JavaScript and PHP files, go to the Block Editor and create a new post. diff --git a/docs/designers-developers/developers/tutorials/javascript/readme.md b/docs/designers-developers/developers/tutorials/javascript/readme.md index 11fecdff4d269..55628c1cd398e 100644 --- a/docs/designers-developers/developers/tutorials/javascript/readme.md +++ b/docs/designers-developers/developers/tutorials/javascript/readme.md @@ -11,9 +11,9 @@ The Block Editor introduced in WordPress 5.0 is written entirely in JavaScript, ### Table of Contents -1. [Plugins Background](../../../../../docs/designers-developers/developers/tutorials/javascript/plugins-background.md) -2. [Loading JavaScript](../../../../../docs/designers-developers/developers/tutorials/javascript/loading-javascript.md) -3. [Extending the Block Editor](../../../../../docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md) -4. [Troubleshooting](../../../../../docs/designers-developers/developers/tutorials/javascript/troubleshooting.md) -5. [JavaScript Versions and Building](../../../../../docs/designers-developers/developers/tutorials/javascript/versions-and-building.md) -6. [Scope your code](../../../../../docs/designers-developers/developers/tutorials/javascript/scope-your-code.md) \ No newline at end of file +1. [Plugins Background](/docs/designers-developers/developers/tutorials/javascript/plugins-background.md) +2. [Loading JavaScript](/docs/designers-developers/developers/tutorials/javascript/loading-javascript.md) +3. [Extending the Block Editor](/docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md) +4. [Troubleshooting](/docs/designers-developers/developers/tutorials/javascript/troubleshooting.md) +5. [JavaScript Versions and Building](/docs/designers-developers/developers/tutorials/javascript/versions-and-building.md) +6. [Scope your code](/docs/designers-developers/developers/tutorials/javascript/scope-your-code.md) diff --git a/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md b/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md index 3bfc68e09262b..0ad9966b91a12 100644 --- a/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md +++ b/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md @@ -2,7 +2,7 @@ With the meta field registered in the previous step, next you will create a new block used to display the field value to the user. See the [Block Tutorial](/docs/designers-developers/developers/tutorials/block-tutorial/readme.md) for a deeper understanding of creating custom blocks. -For this block, you will use the TextControl component, which is similar to an HTML input text field. For additional components, check out the [components](https://github.com/WordPress/gutenberg/tree/master/packages/components/src) and [editor](https://github.com/WordPress/gutenberg/tree/master/packages/editor/src/components) packages repositories. +For this block, you will use the TextControl component, which is similar to an HTML input text field. For additional components, check out the [components](/packages/components/src) and [editor](/packages/editor/src/components) packages repositories. Attributes are the information displayed in blocks. As shown in the block tutorial, the source of attributes come from the text or HTML a user writes in the editor. For your meta block, the attribute will come from the post meta field. diff --git a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md index 9e7efca41b793..3ec8bcba59e93 100644 --- a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md +++ b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md @@ -1,12 +1,12 @@ # Creating a sidebar for your plugin -This tutorial starts with you having an existing plugin setup and ready to add PHP and JavaScript code. Please, refer to [Getting started with JavaScript](../../../../../docs/designers-developers/developers/tutorials/javascript/) tutorial for an introduction to WordPress plugins and how to use JavaScript to extend the block editor. +This tutorial starts with you having an existing plugin setup and ready to add PHP and JavaScript code. Please, refer to [Getting started with JavaScript](/docs/designers-developers/developers/tutorials/javascript/) tutorial for an introduction to WordPress plugins and how to use JavaScript to extend the block editor. In the next sections, you're going to create a custom sidebar for a plugin that contains a text control so the user can update a value that is stored in the `post_meta` table. -1. [Get a sidebar up and running](../../../../../docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-1-up-and-running.md) -2. [Tweak the sidebar style and add controls](../../../../../docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-2-styles-and-controls.md) -3. [Register a new meta field](../../../../../docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-3-register-meta.md) -4. [Initialize the input control with the meta field value](../../../../../docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-4-initialize-input.md) -5. [Update the meta field value when input's content changes](../../../../../docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-5-update-meta.md) -6. [Finishing touches](../../../../../docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md) +1. [Get a sidebar up and running](/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-1-up-and-running.md) +2. [Tweak the sidebar style and add controls](/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-2-styles-and-controls.md) +3. [Register a new meta field](/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-3-register-meta.md) +4. [Initialize the input control with the meta field value](/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-4-initialize-input.md) +5. [Update the meta field value when input's content changes](/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-5-update-meta.md) +6. [Finishing touches](/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md) diff --git a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-1-up-and-running.md b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-1-up-and-running.md index 54fc46066f272..6b9c335f335a5 100644 --- a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-1-up-and-running.md +++ b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-1-up-and-running.md @@ -1,6 +1,6 @@ # Get a sidebar up and running -The first step in the journey is to tell the editor that there is a new plugin that will have its own sidebar. You can do so by using the [registerPlugin](../../../../../docs/designers-developers/developers/packages/packages-plugins/), [PluginSidebar](../../../../../docs/designers-developers/developers/packages/packages-edit-post/#pluginsidebar), and [createElement](../../../../../docs/designers-developers/developers/packages/packages-element/) utilities provided by WordPress, to be found in the `@wordpress/plugins`, `@wordpress/edit-post`, and `@wordpress/element` [packages](../../../../../docs/designers-developers/developers/packages/), respectively. +The first step in the journey is to tell the editor that there is a new plugin that will have its own sidebar. You can do so by using the [registerPlugin](/packages/plugins/REAMDE.md), [PluginSidebar](/packages/edit-post/README.md#pluginsidebar), and [createElement](/packages/element/README.md) utilities provided by WordPress, to be found in the `@wordpress/plugins`, `@wordpress/edit-post`, and `@wordpress/element` [packages](/docs/designers-developers/developers/packages.md), respectively. Add the following code to a JavaScript file called `plugin-sidebar.js` and save it within your plugin's directory: diff --git a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-2-styles-and-controls.md b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-2-styles-and-controls.md index 27e05ba9b2216..d919d11c657ae 100644 --- a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-2-styles-and-controls.md +++ b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-2-styles-and-controls.md @@ -2,7 +2,7 @@ After the sidebar is up and running, the next step is to fill it up with the necessary components and basic styling. -To visualize and edit the meta field value you'll use an input component. The `@wordpress/components` package contains many components available for you to reuse, and, specifically, the [TextControl](../../../../../docs/designers-developers/developers/components/text-control/) is aimed at creating an input field: +To visualize and edit the meta field value you'll use an input component. The `@wordpress/components` package contains many components available for you to reuse, and, specifically, the [TextControl](/packages/components/src/text-control/README.md) is aimed at creating an input field: ```js ( function( wp ) { diff --git a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-3-register-meta.md b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-3-register-meta.md index a4d150401e519..9434ee462fc8b 100644 --- a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-3-register-meta.md +++ b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-3-register-meta.md @@ -12,7 +12,7 @@ register_meta( 'post', 'sidebar_plugin_meta_block_field', array( ) ); ``` -To make sure the field has been loaded, query the block editor [internal data structures](../../../../../docs/designers-developers/developers/data/), also known as _stores_. Open your browser's console, and execute this piece of code: +To make sure the field has been loaded, query the block editor [internal data structures](/docs/designers-developers/developers/data/), also known as _stores_. Open your browser's console, and execute this piece of code: ```js wp.data.select( 'core/editor' ).getCurrentPost().meta; diff --git a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-4-initialize-input.md b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-4-initialize-input.md index 38e09a2529548..d826d9df5547c 100644 --- a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-4-initialize-input.md +++ b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-4-initialize-input.md @@ -39,7 +39,7 @@ Now that the field is available in the editor store, it can be surfaced to the U Now you can focus solely on the `MetaBlockField` component. The goal is to initialize it with the value of `sidebar_plugin_meta_block_field`, but also to keep it updated when that value changes. -WordPress has [some utilities to work with data](../../../../../docs/designers-developers/developers/packages/packages-data/) from the stores. The first you're going to use is [withSelect](../../../../../docs/designers-developers/developers/packages/packages-data/#withselect-mapselecttoprops-function-function), whose signature is: +WordPress has [some utilities to work with data](/packages/data/README.md) from the stores. The first you're going to use is [withSelect](/packages/data/README.md#withselect-mapselecttoprops-function-function), whose signature is: ```js withSelect( @@ -105,7 +105,7 @@ This is how the code changes from the previous section: * The `MetaBlockField` function has now a `props` argument as input. It contains the data object returned by the `mapSelectToProps` function, which it uses to initialize its value property. * The component rendered within the `div` element was also updated, the plugin now uses `MetaBlockFieldWithData`. This will be updated every time the original data changes. -* [getEditedPostAttribute](../../../../../docs/designers-developers/developers/data/data-core-editor/#geteditedpostattribute) is used to retrieve data instead of [getCurrentPost](../../../../../docs/designers-developers/developers/data/data-core-editor/#getcurrentpost) because it returns the most recent values of the post, including user editions that haven't been yet saved. +* [getEditedPostAttribute](/docs/designers-developers/developers/data/data-core-editor.md#geteditedpostattribute) is used to retrieve data instead of [getCurrentPost](/docs/designers-developers/developers/data/data-core-editor.md#getcurrentpost) because it returns the most recent values of the post, including user editions that haven't been yet saved. Update the code and open the sidebar. The input's content is no longer `Initial value` but a void string. Users can't type values yet, but let's check that the component is updated if the value in the store changes. Open the browser's console, execute diff --git a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-5-update-meta.md b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-5-update-meta.md index 0cb56a29b7e85..0ccab7e115b18 100644 --- a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-5-update-meta.md +++ b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-5-update-meta.md @@ -1,6 +1,6 @@ # Update the meta field when the input's content changes -The last step in the journey is to update the meta field when the input content changes. To do that, you'll use another utility from the `@wordpress/data` package, [withDispatch](../../../../../docs/designers-developers/developers/packages/packages-data/#withdispatch-mapdispatchtoprops-function-function). +The last step in the journey is to update the meta field when the input content changes. To do that, you'll use another utility from the `@wordpress/data` package, [withDispatch](/packages/data/README.md#withdispatch-mapdispatchtoprops-function-function). `withDispatch` works similarly to `withSelect`. It takes two functions, the first returns an object with data, and the second takes that data object as input and returns a new UI component. Let's see how to use it: diff --git a/docs/designers-developers/faq.md b/docs/designers-developers/faq.md index fb2d16dfe68fc..1f54d5d78e7ea 100644 --- a/docs/designers-developers/faq.md +++ b/docs/designers-developers/faq.md @@ -251,7 +251,7 @@ Our [list of supported browsers can be found in the Make WordPress handbook](htt ## How do I make my own block? -The API for creating blocks is a crucial aspect of the project. We are working on improved documentation and tutorials. Check out the [Creating Block Types](../../docs/designers-developers/developers/tutorials/block-tutorial/readme.md) section to get started. +The API for creating blocks is a crucial aspect of the project. We are working on improved documentation and tutorials. Check out the [Creating Block Types](/docs/designers-developers/developers/tutorials/block-tutorial/readme.md) section to get started. ## Does Gutenberg involve editing posts/pages in the front-end? @@ -295,7 +295,7 @@ Blocks will be able to provide base structural CSS styles, and themes can add st Other features, like the new _wide_ and _full-wide_ alignment options, will simply be CSS classes applied to blocks that offer this alignment. We are looking at how a theme can opt in to this feature, for example using `add_theme_support`. -*See:* [Theme Support](../../docs/designers-developers/developers/themes/theme-support.md) +*See:* [Theme Support](/docs/designers-developers/developers/themes/theme-support.md) ## How will editor styles work? @@ -308,7 +308,7 @@ function gutenbergtheme_editor_styles() { add_action( 'enqueue_block_editor_assets', 'gutenbergtheme_editor_styles' ); ``` -*See:* [Editor Styles](../../docs/designers-developers/developers/themes/theme-support.md#editor-styles) +*See:* [Editor Styles](/docs/designers-developers/developers/themes/theme-support.md#editor-styles) ## Should I be concerned that Gutenberg will make my plugin obsolete? @@ -353,7 +353,7 @@ Our approach—as outlined in [the technical overview introduction](https://make This also [gives us the flexibility](https://github.com/WordPress/gutenberg/issues/1516) to store those blocks that are inherently separate from the content stream (reusable pieces like widgets or small post type elements) elsewhere, and just keep token references for their placement. -We suggest you look at the [Gutenberg key concepts](../../docs/designers-developers/key-concepts.md) to learn more about how this aspect of the project works. +We suggest you look at the [Gutenberg key concepts](/docs/designers-developers/key-concepts.md) to learn more about how this aspect of the project works. ## How can I parse the post content back out into blocks in PHP or JS? In JS: diff --git a/docs/designers-developers/key-concepts.md b/docs/designers-developers/key-concepts.md index ff9007185fe0e..3252337ba2b33 100644 --- a/docs/designers-developers/key-concepts.md +++ b/docs/designers-developers/key-concepts.md @@ -123,7 +123,7 @@ After running this through the parser we're left with a simple object we can man This has dramatic implications for how simple and performant we can make our parser. These explicit boundaries also protect damage in a single block from bleeding into other blocks or tarnishing the entire document. It also allows the system to identify unrecognized blocks before rendering them. -_N.B.:_ The defining aspect of blocks are their semantics and the isolation mechanism they provide; in other words, their identity. On the other hand, where their data is stored is a more liberal aspect. Blocks support more than just static local data (via JSON literals inside the HTML comment or within the block's HTML), and more mechanisms (_e.g._, global blocks or otherwise resorting to storage in complementary `WP_Post` objects) are expected. See [attributes](../../docs/designers-developers/developers/block-api/block-attributes.md) for details. +_N.B.:_ The defining aspect of blocks are their semantics and the isolation mechanism they provide; in other words, their identity. On the other hand, where their data is stored is a more liberal aspect. Blocks support more than just static local data (via JSON literals inside the HTML comment or within the block's HTML), and more mechanisms (_e.g._, global blocks or otherwise resorting to storage in complementary `WP_Post` objects) are expected. See [attributes](/docs/designers-developers/developers/block-api/block-attributes.md) for details. ## The Anatomy of a Serialized Block diff --git a/docs/readme.md b/docs/readme.md index 5164003a654a8..eb699e6a2ed08 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -6,7 +6,7 @@ The Gutenberg project provides three sources of documentation: Learn how to build blocks and extend the editor, best practices for designing block interfaces, and how to create themes that make the most of the new features Gutenberg provides. -[Visit the Designer & Developer Handbook](../docs/designers-developers/readme.md) +[Visit the Designer & Developer Handbook](/docs/designers-developers/readme.md) ## User Handbook @@ -17,4 +17,4 @@ Discover the new features Gutenberg offers, learn how your site will be affected Help make Gutenberg better by contributing ideas, code, testing, and more. -[Visit the Contributor Handbook](../docs/contributors/readme.md) +[Visit the Contributor Handbook](/docs/contributors/readme.md) diff --git a/docs/tool/generator.js b/docs/tool/generator.js index a632fb40b5268..f5ee6f584e78f 100644 --- a/docs/tool/generator.js +++ b/docs/tool/generator.js @@ -17,7 +17,7 @@ function generateTableOfContent( parsedNamespaces ) { '# Data Module Reference', '', Object.values( parsedNamespaces ).map( ( parsedNamespace ) => { - return ` - [**${ parsedNamespace.name }**: ${ parsedNamespace.title }](../../docs/designers-developers/developers/data/data-${ kebabCase( parsedNamespace.name ) }.md)`; + return ` - [**${ parsedNamespace.name }**: ${ parsedNamespace.title }](/docs/designers-developers/developers/data/data-${ kebabCase( parsedNamespace.name ) }.md)`; } ).join( '\n' ), ].join( '\n' ); } diff --git a/packages/components/CONTRIBUTING.md b/packages/components/CONTRIBUTING.md index cebec43084b1e..18ef8d72b238a 100644 --- a/packages/components/CONTRIBUTING.md +++ b/packages/components/CONTRIBUTING.md @@ -2,7 +2,7 @@ Thank you for taking the time to contribute. -The following is a set of guidelines for contributing to the `@wordpress/components` package to be considered in addition to the general ones described in our [Contributing Policy](../../CONTRIBUTING.md). +The following is a set of guidelines for contributing to the `@wordpress/components` package to be considered in addition to the general ones described in our [Contributing Policy](/CONTRIBUTING.md). ## Examples From a00c7f4b028c556e38d174706b4e2b3fb35e96bc Mon Sep 17 00:00:00 2001 From: Ajit Bohra <ajit@lubus.in> Date: Tue, 29 Jan 2019 14:40:24 +0530 Subject: [PATCH 275/691] Docs: clarify isDefault usage for registerBlockStyles() (#11478) * Docs: Remove isDefault from registerBlockStyle * Docs: clarify isDefault usage for registerBlockStyles() * Clarify isDefault Co-Authored-By: ajitbohra <ajit@lubus.in> --- docs/designers-developers/developers/filters/block-filters.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/designers-developers/developers/filters/block-filters.md b/docs/designers-developers/developers/filters/block-filters.md index 120abb63fb385..99ba4cc0a0d49 100644 --- a/docs/designers-developers/developers/filters/block-filters.md +++ b/docs/designers-developers/developers/filters/block-filters.md @@ -17,7 +17,7 @@ wp.blocks.registerBlockStyle( 'core/quote', { The example above registers a block style variation named `fancy-quote` to the `core/quote` block. When the user selects this block style variation from the styles selector, an `is-style-fancy-quote` className will be added to the block's wrapper. -By adding `isDefault: true`, you can make registered style variation to be active by default when a block is inserted. +By adding `isDefault: true` you can mark the registered style variation as the one that is recognized as active when no custom class name is provided. It also means that there will be no custom class name added to the HTML output for the style that is marked as default. To remove a block style variation use `wp.blocks.unregisterBlockStyle()`. From 190ce7f8cd0076305bec4641ff555072c0f438c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Tue, 29 Jan 2019 10:14:54 +0100 Subject: [PATCH 276/691] Fix wp-settings permissions (#13539) --- bin/install-wordpress.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/install-wordpress.sh b/bin/install-wordpress.sh index 40eec0810bd90..a5ba87ec4f4d5 100755 --- a/bin/install-wordpress.sh +++ b/bin/install-wordpress.sh @@ -65,6 +65,7 @@ fi # Make sure the uploads and upgrade folders exist and we have permissions to add files. echo -e $(status_message "Ensuring that files can be uploaded...") docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm $CONTAINER chmod 767 /var/www/html/wp-content/plugins +docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm $CONTAINER chmod 767 /var/www/html/wp-settings.php docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm $CONTAINER mkdir -p /var/www/html/wp-content/uploads docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm $CONTAINER chmod -v 767 /var/www/html/wp-content/uploads docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm $CONTAINER mkdir -p /var/www/html/wp-content/upgrade From 5bdc8c612b783a877a84a80521d0cc9280b4f6a4 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Tue, 29 Jan 2019 10:06:28 +0000 Subject: [PATCH 277/691] Fix: Add button styles to notice actions without url. Allow custom classes on notice actions. (#13116) --- packages/components/src/notice/README.md | 2 +- packages/components/src/notice/index.js | 40 ++++++++++++++----- packages/components/src/notice/style.scss | 3 ++ .../notice/test/__snapshots__/index.js.snap | 1 + 4 files changed, 34 insertions(+), 12 deletions(-) diff --git a/packages/components/src/notice/README.md b/packages/components/src/notice/README.md index c32bf02a3de10..9b0b62937b7f8 100644 --- a/packages/components/src/notice/README.md +++ b/packages/components/src/notice/README.md @@ -31,4 +31,4 @@ The following props are used to control the display of the component. * `status`: (string) can be `warning` (yellow), `success` (green), `error` (red). * `onRemove`: function called when dismissing the notice * `isDismissible`: (boolean) defaults to true, whether the notice should be dismissible or not -* `actions`: (array) an array of action objects. Each member object should contain a `label` and either a `url` link string or `onClick` callback function. +* `actions`: (array) an array of action objects. Each member object should contain a `label` and either a `url` link string or `onClick` callback function. A `className` property can be used to add custom classes to the button styles. By default, some classes are used (e.g: is-link or is-default) the default classes can be removed by setting property `noDefaultClasses` to `false`. diff --git a/packages/components/src/notice/index.js b/packages/components/src/notice/index.js index 90b2d908deee9..99d5f9968f111 100644 --- a/packages/components/src/notice/index.js +++ b/packages/components/src/notice/index.js @@ -36,17 +36,35 @@ function Notice( { <div className={ classes }> <div className="components-notice__content"> { children } - { actions.map( ( { label, url, onClick }, index ) => ( - <Button - key={ index } - href={ url } - isLink={ !! url } - onClick={ onClick } - className="components-notice__action" - > - { label } - </Button> - ) ) } + { actions.map( + ( + { + className: buttonCustomClasses, + label, + noDefaultClasses = false, + onClick, + url, + }, + index + ) => { + return ( + <Button + key={ index } + href={ url } + isDefault={ ! noDefaultClasses && ! url } + isLink={ ! noDefaultClasses && !! url } + onClick={ url ? undefined : onClick } + className={ classnames( + 'components-notice__action', + buttonCustomClasses + ) } + > + { label } + </Button> + ); + } + + ) } </div> { isDismissible && ( <IconButton diff --git a/packages/components/src/notice/style.scss b/packages/components/src/notice/style.scss index 31b364c7e21a6..8003d33384a3c 100644 --- a/packages/components/src/notice/style.scss +++ b/packages/components/src/notice/style.scss @@ -34,6 +34,9 @@ &.is-link { margin-left: 4px; } + &.is-default { + vertical-align: initial; + } } .components-notice__dismiss { diff --git a/packages/components/src/notice/test/__snapshots__/index.js.snap b/packages/components/src/notice/test/__snapshots__/index.js.snap index 7be3c43c0a1b5..28b35679ceec6 100644 --- a/packages/components/src/notice/test/__snapshots__/index.js.snap +++ b/packages/components/src/notice/test/__snapshots__/index.js.snap @@ -11,6 +11,7 @@ exports[`Notice should match snapshot 1`] = ` <ForwardRef(Button) className="components-notice__action" href="https://example.com" + isDefault={false} isLink={true} > View From a13d933513a4a7c31ded5cc16c78f1204d7969d7 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Tue, 29 Jan 2019 10:26:39 +0000 Subject: [PATCH 278/691] Fix: Hide empty categories in categories block. (#13549) This commit uses hide_empty option during categories request to make sure categories that have no posts are not displayed in the categories block edit view. On the frontend, they are not rendered so the edit view should replicate that. Some categories may still show zero as a count. That happens because these categories have descendant categories with posts associated to them. --- packages/block-library/src/categories/edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/categories/edit.js b/packages/block-library/src/categories/edit.js index 5341ad6ff5168..ad1dea3ec55bf 100644 --- a/packages/block-library/src/categories/edit.js +++ b/packages/block-library/src/categories/edit.js @@ -201,7 +201,7 @@ export default compose( withSelect( ( select ) => { const { getEntityRecords } = select( 'core' ); const { isResolving } = select( 'core/data' ); - const query = { per_page: -1 }; + const query = { per_page: -1, hide_empty: true }; return { categories: getEntityRecords( 'taxonomy', 'category', query ), From dc68e4bd7818e423d9abc68224a94235bc3c8d7e Mon Sep 17 00:00:00 2001 From: Sheri Bigelow <sheri@designsimply.com> Date: Tue, 29 Jan 2019 03:34:14 -0700 Subject: [PATCH 279/691] Quick refresh of the Repository Management doc (#13495) * Quick refresh of the Repository Management doc This PR seeks to update the Repository Management documentation to reflect recent practices including the following: - Reorganize the labels section. - General updates to the triage section. - Add a note about how to handle help requests. - Update common examples of labels, milestones, and projects. - Change 'backlog' to 'list'. * Quick refresh of the Repository Management doc Updating based on feedback received: - Be less specific about the version numbers in example milestones. - Add more guidelines about the Needs More Info label. - Mention "Good First Issue" and "Good First Review" labels. * Update repository-management.md --- docs/contributors/repository-management.md | 72 +++++++++++----------- 1 file changed, 35 insertions(+), 37 deletions(-) diff --git a/docs/contributors/repository-management.md b/docs/contributors/repository-management.md index d4e8df0ebf6aa..08b47e1777f2a 100644 --- a/docs/contributors/repository-management.md +++ b/docs/contributors/repository-management.md @@ -1,6 +1,6 @@ # Repository Management -The goal is for this to be a living document explaining how we collaboratively manage the Gutenberg repository. If you’d like to suggest a change, please open an issue for discussion or submit a pull request to the document. +This is a living document explaining how we collaboratively manage the Gutenberg repository. If you’d like to suggest a change, please open an issue for discussion or submit a pull request to the document. This document covers: @@ -17,14 +17,24 @@ This document covers: ## Issues -A healthy issue backlog is one where issues are relevant and actionable. *Relevant* in the sense that they relate to the project’s current priorities. *Actionable* in the sense that it’s clear what action(s) need to be taken to resolve the issue. +A healthy issue list is one where issues are relevant and actionable. *Relevant* in the sense that they relate to the project’s current priorities. *Actionable* in the sense that it’s clear what action(s) need to be taken to resolve the issue. -Any issues that are irrelevant or not actionable should be closed, because they get in the way of making progress on the project. Imagine the issue backlog as a desk: the more clutter you have on it, the more difficult it is to use the space to get work done. +Any issues that are irrelevant or not actionable should be closed, because they get in the way of making progress on the project. Imagine the issue list as a desk: the more clutter you have on it, the more difficult it is to use the space to get work done. ### Labels -To better organize the issue backlog, all issues should have [one or more labels](https://github.com/WordPress/gutenberg/labels). Here are some you might commonly see: +All issues should have [one or more labels](https://github.com/WordPress/gutenberg/labels). +Workflow labels start with “Needs” and may be applied as needed. Ideally, each workflow label will have a group that follows it, such as the Accessibility Team for `Needs Accessibility Feedback`, the Testing Team for `Needs Testing`, etc. + +[Priority High](https://github.com/WordPress/gutenberg/labels/Priority%20High) and [Priority OMGWTFBBQ](https://github.com/WordPress/gutenberg/labels/Priority%20OMGWTFBBQ) issues should have an assignee and/or be in an active milestone. + +Help requests or 'how to' questions should be posted in a relevant support forum as a first step. If something might be a bug but it's not clear, the Support Team or a forum volunteer can help troubleshoot the case to help get all the right information needed for an effective bug report. + +Here are some labels you might commonly see: + +- [Good First Issue](https://github.com/WordPress/gutenberg/labels/Good%20First%20Issue) - Issues identified as good for new contributors to work on. Comment to note that you intend to work on the issue and reference the issue number in the pull request you submit. +- [Good First Review](https://github.com/WordPress/gutenberg/labels/Good%20First%20Review) - Pull requests identified as good for new contributors who are interested in doing code reviews. - [Needs Accessibility Feedback](https://github.com/WordPress/gutenberg/labels/Accessibility) - Changes that impact accessibility and need corresponding review (e.g. markup changes). - [Needs Design Feedback](https://github.com/WordPress/gutenberg/labels/Needs%20Design%20Feedback) - Changes that modify the design or user experience in some way and need sign-off. - [[Type] Bug](https://github.com/WordPress/gutenberg/labels/%5BType%5D%20Bug) - An existing feature is broken in some way. @@ -32,53 +42,41 @@ To better organize the issue backlog, all issues should have [one or more labels - [[Type] Plugin / Extension Conflict](https://github.com/WordPress/gutenberg/labels/%5BType%5D%20Plugin%20%2F%20Extension%20Conflict) - Documentation of a conflict between Gutenberg and a plugin or extension. The plugin author should be informed and provided documentation on how to address. - [[Status] Needs More Info](https://github.com/WordPress/gutenberg/labels/%5BStatus%5D%20Needs%20More%20Info) - The issue needs more information in order to be actionable and relevant. Typically this requires follow-up from the original reporter. -Workflow labels may be applied as needed and start with “Needs”. Ideally, each workflow label will have a group that follows it, such as the Accessibility Team for `Needs Accessibility Feedback`, the Testing Team for `Needs Testing`, etc. - -`Priority High` and `Priority OMGWTFBBQ` issues should have an assignee and/or be in an active milestone. - [Check out the label directory](https://github.com/WordPress/gutenberg/labels) for a listing of all labels. ### Milestones -We put issues into [milestones](https://github.com/wordpress/gutenberg/milestones) to better categorize them. Here are some you might see: - -- The next 2 releases we have milestones for (e.g. 2.2, 2.3). -- [Feature Complete](https://github.com/WordPress/gutenberg/milestone/8): This includes big features and is what will be managing the vision of Gutenberg. All of this would be done before even merge proposal is thought about. Examples here include nesting, drag and drop and extensibility API. -- [Merge Proposal: Editor](https://github.com/WordPress/gutenberg/milestone/22): All issues related to merge proposal for the editor. -- [Merge Proposal: Rest API](https://github.com/WordPress/gutenberg/milestone/39): All issues related to merge proposal for the Rest API -- [Merge Proposal: Accessibility](https://github.com/WordPress/gutenberg/milestone/43): All accessibility issues related to merge proposal. -- [Merge Proposal: Media](https://github.com/WordPress/gutenberg/milestone/42): All issues related to merge proposal for the media component. -- [Merge Proposal: Documentation](https://github.com/WordPress/gutenberg/milestone/50): All issues related to documentation for the merge proposal. -- [Merge Proposal: i18n](https://github.com/WordPress/gutenberg/milestone/49): All translation issues for the merge proposal. -- [Merge Proposal: Customization](https://github.com/WordPress/gutenberg/milestone/44): All Customization issues for the merge proposal. -- [Merge Proposal: Plugin](https://github.com/WordPress/gutenberg/milestone/48): All plugin and extensibility issues for the merge proposal. -- [Merge Proposal: Back Compat](https://github.com/WordPress/gutenberg/milestone/47): All back compatibility issues for the merge proposal. -- [Merge Proposal: Themes](https://github.com/WordPress/gutenberg/milestone/48): All theme issues for the merge proposal. -- [Merge Proposal: Core](https://github.com/WordPress/gutenberg/milestone/45): All core issues for the merge proposal that don't fit other merge proposal milestones. -- [Bonus Features](https://github.com/WordPress/gutenberg/milestone/32): Again likely not part of triage and includes nice to haves for the project, if time before merge. A few examples include collaborative editing and footnotes. +We put issues into [milestones](https://github.com/wordpress/gutenberg/milestones) to better categorize them. Issues are added to milestones starting with `WordPress` and pull requests are added to milestones ending in `(Gutenberg)`. + +Here are some milestones you might see: + +- [WordPress X.Y](https://github.com/WordPress/gutenberg/milestone/70): Tasks that should be done for future WordPress releases. +- [X.Y (Gutenberg)](https://github.com/WordPress/gutenberg/milestone/85): PRs targeted for the Gutenberg Plugin X.Y release. - [Future](https://github.com/WordPress/gutenberg/milestone/35): this is something that is confirmed by everyone as a good thing but doesn’t fall into other criteria. ### Triaging Issues -To keep the issue backlog healthy, it needs to be triaged regularly. *Triage* is the practice of reviewing existing issues to make sure they’re relevant, actionable, and have all the information they need. +To keep the issue list healthy, it needs to be triaged regularly. *Triage* is the practice of reviewing existing issues to make sure they’re relevant, actionable, and have all the information they need. -Anyone can help triage the backlog, although you’ll need contributor permission on the Gutenberg repository to modify an issue’s labels or edit its title. +Anyone can help triage, although you’ll need contributor permission on the Gutenberg repository to modify an issue’s labels or edit its title. Here are a couple places you can start: -- [All Gutenberg issues without an assigned label](https://github.com/wordpress/gutenberg/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-asc+no%3Alabel) -- [The least recently updated Gutenberg issues](https://github.com/WordPress/gutenberg/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-asc) +- [All Gutenberg issues without an assigned label](https://github.com/wordpress/gutenberg/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-asc+no%3Alabel). +- [The least recently updated Gutenberg issues](https://github.com/WordPress/gutenberg/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-asc). -When reviewing the issue backlog, here are some steps you can perform: +When reviewing issues, here are some steps you can perform: -- If it’s a bug report, test to confirm the report. If there is not enough information to confirm the report, add the `[Status] Needs More Info` label. -- If the issue is missing labels, add some to better categorize it. -- If the issue is duplicate of another already in the backlog, close the issue by commenting with “Duplicate of #<original-id>”. Add any relevant new details to the existing issue. +- First search for duplicates. If the issue is duplicate, close it by commenting with “Duplicate of #<original-id>” and add any relevant new details to the existing issue. +- If the issue is missing labels, add some to better categorize it (requires proper permissions). +- If the title doesn’t communicate the issue, edit it for clarity (requires proper permissions). +- If it’s a bug report, test to confirm the report or add the `Needs Testing` label. If there is not enough information to confirm the report, add the `[Status] Needs More Info` label and ask for the details needed. +- Remove the `[Status] Needs More Info` if the author of the issue has responded with enough details. +- Close the issue with a note if it has a `[Status] Needs More Info` label but the author didn't respond in 2+ weeks. - If there was conversation on the issue but no actionable steps identified, follow up with the participants to see what’s actionable. -- If the title doesn’t communicate the issue, edit it for clarity. - If you feel comfortable triaging the issue further, then you can also: - Check that the bug report is valid by debugging it to see if you can track down the technical specifics. - - Check if the issue is missing some detail and see if you can fill in those details. For instance, if a bug report is missing visual detail, it’s helpful to reproduce the issue locally and upload a GIF. + - Check if the issue is missing some detail and see if you can fill in those details. For instance, if a bug report is missing visual detail, it’s helpful to reproduce the issue locally and upload a screenshot or GIF. ## Pull Requests @@ -158,6 +156,6 @@ We use [GitHub projects](https://github.com/WordPress/gutenberg/projects) to kee Some key projects include: -* [Customization](https://github.com/WordPress/gutenberg/projects/13) - Blocks and tasks needed for customization in Gutenberg. -* [Extensibility](https://github.com/WordPress/gutenberg/projects/14) - Comprises the entirety of extensibility APIs. See [Native Gutenberg Extensibility Overview](https://github.com/WordPress/gutenberg/issues/3330) for more details. -* [Third-Party Compatibility](https://github.com/WordPress/gutenberg/projects/15) - Issue that impact Gutenberg's adoption in the real world. +* [Phase 2](https://github.com/WordPress/gutenberg/projects/13) - Development tasks needed for Phase 2 of Gutenberg. +* [Phase 2 design](https://github.com/WordPress/gutenberg/projects/21) - Tasks for design in Phase 2. Note: specific projects may have their own boards. +* [Ideas](https://github.com/WordPress/gutenberg/projects/8) - Project containing tickets that, while closed for the time being, can be revisited in the future. From b35774d56866a6d54db04a396f13494c06c3069d Mon Sep 17 00:00:00 2001 From: Kuba Birecki <ice9js@users.noreply.github.com> Date: Tue, 29 Jan 2019 11:43:09 +0100 Subject: [PATCH 280/691] Replace Polldaddy embed block with Crowdsignal (#12854) * Replace Polldaddy embed block with Crowdsignal * Remove Polldaddy from the inserter * Update core-embeds.js * Update core-embeds.js * Update core-embeds.js --- .../block-library/src/embed/core-embeds.js | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/embed/core-embeds.js b/packages/block-library/src/embed/core-embeds.js index 9fe6c2b335cd8..7edb114dae9ba 100644 --- a/packages/block-library/src/embed/core-embeds.js +++ b/packages/block-library/src/embed/core-embeds.js @@ -145,6 +145,25 @@ export const others = [ }, patterns: [ /^https?:\/\/(www\.)?collegehumor\.com\/.+/i ], }, + { + name: 'core-embed/crowdsignal', + settings: { + title: 'Crowdsignal', + icon: embedContentIcon, + keywords: [ 'polldaddy' ], + transform: [ { + type: 'block', + blocks: [ 'core-embed/polldaddy' ], + transform: ( content ) => { + return createBlock( 'core-embed/crowdsignal', { + content, + } ); + }, + } ], + description: __( 'Embed Crowdsignal (formerly Polldaddy) content.' ), + }, + patterns: [ /^https?:\/\/((.+\.)?polldaddy\.com|poll\.fm|.+\.survey\.fm)\/.+/i ], + }, { name: 'core-embed/dailymotion', settings: { @@ -210,13 +229,17 @@ export const others = [ patterns: [ /^https?:\/\/(www\.)?mixcloud\.com\/.+/i ], }, { + // Deprecated in favour of the core-embed/crowdsignal block name: 'core-embed/polldaddy', settings: { title: 'Polldaddy', icon: embedContentIcon, description: __( 'Embed Polldaddy content.' ), + supports: { + inserter: false, + }, }, - patterns: [ /^https?:\/\/(www\.)?polldaddy\.com\/.+/i ], + patterns: [], }, { name: 'core-embed/reddit', From cd63614338d6b7646aa3bd4be76bac0bd876c542 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Tue, 29 Jan 2019 12:00:09 +0100 Subject: [PATCH 281/691] Clarify multi-selection focus behavior (#11247) * Clarify multi-selection focus behavior * Update block.js --- packages/editor/src/components/block-list/block.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/editor/src/components/block-list/block.js b/packages/editor/src/components/block-list/block.js index 87d9d11795767..82e7154b76995 100644 --- a/packages/editor/src/components/block-list/block.js +++ b/packages/editor/src/components/block-list/block.js @@ -99,8 +99,9 @@ export class BlockListBlock extends Component { this.focusTabbable( true ); } - // When triggering a multi-selection, - // move the focus to the wrapper of the first selected block. + // When triggering a multi-selection, move the focus to the wrapper of the first selected block. + // This ensures that it is not possible to continue editing the initially selected block + // when a multi-selection is triggered. if ( this.props.isFirstMultiSelected && ! prevProps.isFirstMultiSelected ) { this.wrapperNode.focus(); } @@ -301,6 +302,8 @@ export class BlockListBlock extends Component { deleteOrInsertAfterWrapper( event ) { const { keyCode, target } = event; + // These block shortcuts should only trigger if the wrapper of the block is selected + // And when it's not a multi-selection to avoid conflicting with RichText/Inputs and multiselection. if ( ! this.props.isSelected || target !== this.wrapperNode || From 6d500c2085513f490f87b66c9bff01b91fd07b75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Tue, 29 Jan 2019 12:12:03 +0100 Subject: [PATCH 282/691] Scripts: Remove npm run build from test-e2e default run (#13420) --- .travis.yml | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 55fd93ba4a1ef..8a464d766d973 100644 --- a/.travis.yml +++ b/.travis.yml @@ -68,6 +68,7 @@ jobs: - ./bin/setup-local-env.sh script: - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests + - npm run build - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 2 == 0' < ~/.jest-e2e-tests ) - name: E2E tests (Admin with plugins) (2/2) @@ -76,6 +77,7 @@ jobs: - ./bin/setup-local-env.sh script: - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests + - npm run build - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 2 == 1' < ~/.jest-e2e-tests ) - name: E2E tests (Author without plugins) (1/2) @@ -84,6 +86,7 @@ jobs: - ./bin/setup-local-env.sh script: - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests + - npm run build - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 2 == 0' < ~/.jest-e2e-tests ) - name: E2E tests (Author without plugins) (2/2) @@ -92,4 +95,5 @@ jobs: - ./bin/setup-local-env.sh script: - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests + - npm run build - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 2 == 1' < ~/.jest-e2e-tests ) diff --git a/package.json b/package.json index c59a6c200a189..136d1db76679f 100644 --- a/package.json +++ b/package.json @@ -177,7 +177,7 @@ "publish:dev": "npm run build:packages && lerna publish --npm-tag next", "publish:prod": "npm run build:packages && lerna publish", "test": "npm run lint && npm run test-unit", - "pretest-e2e": "concurrently \"./bin/reset-e2e-tests.sh\" \"npm run build\"", + "pretest-e2e": "./bin/reset-e2e-tests.sh", "test-e2e": "wp-scripts test-e2e --config packages/e2e-tests/jest.config.js", "test-e2e:watch": "npm run test-e2e -- --watch", "test-php": "npm run lint-php && npm run test-unit-php", From ccbf07e3dcd9990284a9b8014537917772ec5c98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20Van=C2=A0Durpe?= <iseulde@automattic.com> Date: Tue, 29 Jan 2019 13:00:06 +0100 Subject: [PATCH 283/691] RichText: List: Fix outdent with children (#13559) * Fix outdent * Add unit test * Add e2e test * Add unit tests for getLastChildIndex --- .../blocks/__snapshots__/list.test.js.snap | 12 +++++ packages/e2e-tests/specs/blocks/list.test.js | 18 +++++++ .../rich-text/src/get-last-child-index.js | 40 +++++++++++++++ packages/rich-text/src/outdent-list-items.js | 21 +++++--- .../src/test/get-last-child-index.js | 50 +++++++++++++++++++ .../rich-text/src/test/outdent-list-items.js | 30 +++++++++-- 6 files changed, 160 insertions(+), 11 deletions(-) create mode 100644 packages/rich-text/src/get-last-child-index.js create mode 100644 packages/rich-text/src/test/get-last-child-index.js diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap b/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap index a4cb6cdb75be5..71118e8d65118 100644 --- a/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap +++ b/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap @@ -144,6 +144,18 @@ exports[`List should indent and outdent level 2 3`] = ` <!-- /wp:list -->" `; +exports[`List should outdent with children 1`] = ` +"<!-- wp:list --> +<ul><li>a<ul><li>b<ul><li>c</li></ul></li></ul></li></ul> +<!-- /wp:list -->" +`; + +exports[`List should outdent with children 2`] = ` +"<!-- wp:list --> +<ul><li>a</li><li>b<ul><li>c</li></ul></li></ul> +<!-- /wp:list -->" +`; + exports[`List should split indented list item 1`] = ` "<!-- wp:list --> <ul><li>one<ul><li>two</li><li>three</li></ul></li></ul> diff --git a/packages/e2e-tests/specs/blocks/list.test.js b/packages/e2e-tests/specs/blocks/list.test.js index 022c0b88b1336..98b7f74bd2536 100644 --- a/packages/e2e-tests/specs/blocks/list.test.js +++ b/packages/e2e-tests/specs/blocks/list.test.js @@ -262,4 +262,22 @@ describe( 'List', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + + it( 'should outdent with children', async () => { + await insertBlock( 'List' ); + await page.keyboard.type( 'a' ); + await page.keyboard.press( 'Enter' ); + await pressKeyWithModifier( 'primary', 'm' ); + await page.keyboard.type( 'b' ); + await page.keyboard.press( 'Enter' ); + await pressKeyWithModifier( 'primary', 'm' ); + await page.keyboard.type( 'c' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + + await page.keyboard.press( 'ArrowUp' ); + await pressKeyWithModifier( 'primaryShift', 'm' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); } ); diff --git a/packages/rich-text/src/get-last-child-index.js b/packages/rich-text/src/get-last-child-index.js new file mode 100644 index 0000000000000..976051b10c0a3 --- /dev/null +++ b/packages/rich-text/src/get-last-child-index.js @@ -0,0 +1,40 @@ +/** + * Internal dependencies + */ + +import { LINE_SEPARATOR } from './special-characters'; + +/** + * Gets the line index of the last child in the list. + * + * @param {Object} value Value to search. + * @param {number} lineIndex Line index of a list item in the list. + * + * @return {Array} The index of the last child. + */ +export function getLastChildIndex( { text, formats }, lineIndex ) { + const lineFormats = formats[ lineIndex ] || []; + // Use the given line index in case there are no next children. + let childIndex = lineIndex; + + // `lineIndex` could be `undefined` if it's the first line. + for ( let index = lineIndex || 0; index < text.length; index++ ) { + // We're only interested in line indices. + if ( text[ index ] !== LINE_SEPARATOR ) { + continue; + } + + const formatsAtIndex = formats[ index ] || []; + + // If the amout of formats is equal or more, store it, then return the + // last one if the amount of formats is less. + if ( formatsAtIndex.length >= lineFormats.length ) { + childIndex = index; + } else { + return childIndex; + } + } + + // If the end of the text is reached, return the last child index. + return childIndex; +} diff --git a/packages/rich-text/src/outdent-list-items.js b/packages/rich-text/src/outdent-list-items.js index 78036aae165e1..b26587f3a0bd1 100644 --- a/packages/rich-text/src/outdent-list-items.js +++ b/packages/rich-text/src/outdent-list-items.js @@ -6,6 +6,7 @@ import { LINE_SEPARATOR } from './special-characters'; import { normaliseFormats } from './normalise-formats'; import { getLineIndex } from './get-line-index'; import { getParentLineIndex } from './get-parent-line-index'; +import { getLastChildIndex } from './get-last-child-index'; /** * Outdents any selected list items if possible. @@ -16,17 +17,23 @@ import { getParentLineIndex } from './get-parent-line-index'; */ export function outdentListItems( value ) { const { text, formats, start, end } = value; - const lineIndex = getLineIndex( value ); - const lineFormats = formats[ lineIndex ]; + const startingLineIndex = getLineIndex( value, start ); - if ( lineFormats === undefined ) { + // Return early if the starting line index cannot be further outdented. + if ( formats[ startingLineIndex ] === undefined ) { return value; } const newFormats = formats.slice( 0 ); - const parentFormats = formats[ getParentLineIndex( value, lineIndex ) ] || []; - - for ( let index = lineIndex; index < end; index++ ) { + const parentFormats = formats[ getParentLineIndex( value, startingLineIndex ) ] || []; + const endingLineIndex = getLineIndex( value, end ); + const lastChildIndex = getLastChildIndex( value, endingLineIndex ); + + // Outdent all list items from the starting line index until the last child + // index of the ending list. All children of the ending list need to be + // outdented, otherwise they'll be orphaned. + for ( let index = startingLineIndex; index <= lastChildIndex; index++ ) { + // Skip indices that are not line separators. if ( text[ index ] !== LINE_SEPARATOR ) { continue; } @@ -37,7 +44,7 @@ export function outdentListItems( value ) { ); if ( newFormats[ index ].length === 0 ) { - delete newFormats[ lineIndex ]; + delete newFormats[ index ]; } } diff --git a/packages/rich-text/src/test/get-last-child-index.js b/packages/rich-text/src/test/get-last-child-index.js new file mode 100644 index 0000000000000..55c881d356555 --- /dev/null +++ b/packages/rich-text/src/test/get-last-child-index.js @@ -0,0 +1,50 @@ +/** + * External dependencies + */ +import deepFreeze from 'deep-freeze'; + +/** + * Internal dependencies + */ + +import { getLastChildIndex } from '../get-last-child-index'; +import { LINE_SEPARATOR } from '../special-characters'; + +describe( 'outdentListItems', () => { + const ul = { type: 'ul' }; + + it( 'should return undefined if there is only one line', () => { + expect( getLastChildIndex( deepFreeze( { + formats: [ , ], + text: '1', + } ), undefined ) ).toBe( undefined ); + } ); + + it( 'should return the last line if no line is indented', () => { + expect( getLastChildIndex( deepFreeze( { + formats: [ , ], + text: `1${ LINE_SEPARATOR }`, + } ), undefined ) ).toBe( 1 ); + } ); + + it( 'should return the last child index', () => { + expect( getLastChildIndex( deepFreeze( { + formats: [ , [ ul ], , [ ul ], , ], + text: `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3`, + } ), undefined ) ).toBe( 3 ); + } ); + + it( 'should return the last child index by sibling', () => { + expect( getLastChildIndex( deepFreeze( { + formats: [ , [ ul ], , [ ul ], , ], + text: `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3`, + } ), 1 ) ).toBe( 3 ); + } ); + + it( 'should return the last child index (with further lower indented items)', () => { + expect( getLastChildIndex( deepFreeze( { + formats: [ , [ ul ], , , , ], + text: `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3`, + } ), 1 ) ).toBe( 1 ); + } ); +} ); diff --git a/packages/rich-text/src/test/outdent-list-items.js b/packages/rich-text/src/test/outdent-list-items.js index d321a4c02ffe8..afe1c09d03a31 100644 --- a/packages/rich-text/src/test/outdent-list-items.js +++ b/packages/rich-text/src/test/outdent-list-items.js @@ -21,7 +21,7 @@ describe( 'outdentListItems', () => { start: 1, end: 1, }; - const result = outdentListItems( deepFreeze( record ), ul ); + const result = outdentListItems( deepFreeze( record ) ); expect( result ).toEqual( record ); expect( result ).toBe( record ); @@ -43,7 +43,7 @@ describe( 'outdentListItems', () => { start: 2, end: 2, }; - const result = outdentListItems( deepFreeze( record ), ul ); + const result = outdentListItems( deepFreeze( record ) ); expect( result ).toEqual( expected ); expect( result ).not.toBe( record ); @@ -65,7 +65,7 @@ describe( 'outdentListItems', () => { start: 5, end: 5, }; - const result = outdentListItems( deepFreeze( record ), ul ); + const result = outdentListItems( deepFreeze( record ) ); expect( result ).toEqual( expected ); expect( result ).not.toBe( record ); @@ -87,10 +87,32 @@ describe( 'outdentListItems', () => { start: 2, end: 5, }; - const result = outdentListItems( deepFreeze( record ), ul ); + const result = outdentListItems( deepFreeze( record ) ); expect( result ).toEqual( expected ); expect( result ).not.toBe( record ); expect( getSparseArrayLength( result.formats ) ).toBe( 1 ); } ); + + it( 'should outdent list item with children', () => { + // As we're testing list formats, the text should remain the same. + const text = `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3${ LINE_SEPARATOR }4`; + const record = { + formats: [ , [ ul ], , [ ul, ul ], , [ ul, ul ], , ], + text, + start: 2, + end: 2, + }; + const expected = { + formats: [ , , , [ ul ], , [ ul ], , ], + text, + start: 2, + end: 2, + }; + const result = outdentListItems( deepFreeze( record ) ); + + expect( result ).toEqual( expected ); + expect( result ).not.toBe( record ); + expect( getSparseArrayLength( result.formats ) ).toBe( 2 ); + } ); } ); From bf1c9417d8c92dc37c796aab6be7c0714c47e2e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20Van=C2=A0Durpe?= <iseulde@automattic.com> Date: Tue, 29 Jan 2019 17:07:53 +0100 Subject: [PATCH 284/691] RichText: List: Fix getParentIndex (#13562) * RichText: List: Fix getParentIndex * Fill out test name * Add unit tests for getParentLineIndex * Guard against negative lineIndex --- packages/rich-text/src/change-list-type.js | 5 ++- .../rich-text/src/get-parent-line-index.js | 18 +++----- .../src/test/get-parent-line-index.js | 43 +++++++++++++++++++ .../rich-text/src/test/outdent-list-items.js | 22 ++++++++++ 4 files changed, 75 insertions(+), 13 deletions(-) create mode 100644 packages/rich-text/src/test/get-parent-line-index.js diff --git a/packages/rich-text/src/change-list-type.js b/packages/rich-text/src/change-list-type.js index 2b92dda0ab92c..1dfc040657363 100644 --- a/packages/rich-text/src/change-list-type.js +++ b/packages/rich-text/src/change-list-type.js @@ -21,9 +21,10 @@ import { getParentLineIndex } from './get-parent-line-index'; */ export function changeListType( value, newFormat ) { const { text, formats, start, end } = value; - const startLineFormats = formats[ getLineIndex( value, start ) ] || []; + const startingLineIndex = getLineIndex( value, start ); + const startLineFormats = formats[ startingLineIndex ] || []; const endLineFormats = formats[ getLineIndex( value, end ) ] || []; - const startIndex = getParentLineIndex( value, start ); + const startIndex = getParentLineIndex( value, startingLineIndex ); const newFormats = formats.slice( 0 ); const startCount = startLineFormats.length - 1; const endCount = endLineFormats.length - 1; diff --git a/packages/rich-text/src/get-parent-line-index.js b/packages/rich-text/src/get-parent-line-index.js index 332764dc45bd3..bd3f72de96519 100644 --- a/packages/rich-text/src/get-parent-line-index.js +++ b/packages/rich-text/src/get-parent-line-index.js @@ -9,27 +9,23 @@ import { LINE_SEPARATOR } from './special-characters'; * go through every list item until we find one with exactly one format type * less. * - * @param {Object} value Value to search. - * @param {number} startIndex Index to start search at. + * @param {Object} value Value to search. + * @param {number} lineIndex Line index of a child list item. * * @return {Array} The parent list line index. */ -export function getParentLineIndex( { text, formats }, startIndex ) { - let index = startIndex; - let startFormats; +export function getParentLineIndex( { text, formats }, lineIndex ) { + const startFormats = formats[ lineIndex ] || []; - while ( index-- ) { + let index = lineIndex; + + while ( index-- >= 0 ) { if ( text[ index ] !== LINE_SEPARATOR ) { continue; } const formatsAtIndex = formats[ index ] || []; - if ( ! startFormats ) { - startFormats = formatsAtIndex; - continue; - } - if ( formatsAtIndex.length === startFormats.length - 1 ) { return index; } diff --git a/packages/rich-text/src/test/get-parent-line-index.js b/packages/rich-text/src/test/get-parent-line-index.js new file mode 100644 index 0000000000000..4e6a75ffd0a6e --- /dev/null +++ b/packages/rich-text/src/test/get-parent-line-index.js @@ -0,0 +1,43 @@ +/** + * External dependencies + */ +import deepFreeze from 'deep-freeze'; + +/** + * Internal dependencies + */ + +import { getParentLineIndex } from '../get-parent-line-index'; +import { LINE_SEPARATOR } from '../special-characters'; + +describe( 'getParentLineIndex', () => { + const ul = { type: 'ul' }; + + it( 'should return undefined if there is only one line', () => { + expect( getParentLineIndex( deepFreeze( { + formats: [ , ], + text: '1', + } ), undefined ) ).toBe( undefined ); + } ); + + it( 'should return undefined if the list is part of the first root list child', () => { + expect( getParentLineIndex( deepFreeze( { + formats: [ , ], + text: `1${ LINE_SEPARATOR }2`, + } ), 2 ) ).toBe( undefined ); + } ); + + it( 'should return the line index of the parent list (1)', () => { + expect( getParentLineIndex( deepFreeze( { + formats: [ , , , [ ul ], , ], + text: `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3`, + } ), 3 ) ).toBe( 1 ); + } ); + + it( 'should return the line index of the parent list (2)', () => { + expect( getParentLineIndex( deepFreeze( { + formats: [ , [ ul ], , [ ul, ul ], , [ ul ], , ], + text: `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3${ LINE_SEPARATOR }4`, + } ), 5 ) ).toBe( undefined ); + } ); +} ); diff --git a/packages/rich-text/src/test/outdent-list-items.js b/packages/rich-text/src/test/outdent-list-items.js index afe1c09d03a31..c5b75d5d39188 100644 --- a/packages/rich-text/src/test/outdent-list-items.js +++ b/packages/rich-text/src/test/outdent-list-items.js @@ -115,4 +115,26 @@ describe( 'outdentListItems', () => { expect( result ).not.toBe( record ); expect( getSparseArrayLength( result.formats ) ).toBe( 2 ); } ); + + it( 'should outdent list based on parent list', () => { + // As we're testing list formats, the text should remain the same. + const text = `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3${ LINE_SEPARATOR }4`; + const record = { + formats: [ , [ ul ], , [ ul, ul ], , [ ul ], , ], + text, + start: 6, + end: 6, + }; + const expected = { + formats: [ , [ ul ], , [ ul, ul ], , , , ], + text, + start: 6, + end: 6, + }; + const result = outdentListItems( deepFreeze( record ) ); + + expect( result ).toEqual( expected ); + expect( result ).not.toBe( record ); + expect( getSparseArrayLength( result.formats ) ).toBe( 2 ); + } ); } ); From f84cecae5fdd1488753f5748b6546724e2c40b25 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Tue, 29 Jan 2019 11:36:32 -0500 Subject: [PATCH 285/691] Plugin: Populate demo content by default content filters (#13553) * Plugin: Populate demo content by default content filters * Plugin: Avoid checking post status on default content Assumed to be called only in the process of generating a new post for edit (the same assumption checked by testing 'auto-draft' status) * Plugin: Remove unreachable initial_edits code --- gutenberg.php | 13 --------- lib/client-assets.php | 14 +--------- lib/demo.php | 64 +++++++++++++++++++++++++++++++++++++++++++ lib/load.php | 2 +- 4 files changed, 66 insertions(+), 27 deletions(-) create mode 100644 lib/demo.php diff --git a/gutenberg.php b/gutenberg.php index f91df4b918597..502469ba2f421 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -254,19 +254,6 @@ function gutenberg_init( $return, $post ) { return true; } -/** - * Redirects the demo page to edit a new post. - */ -function gutenberg_redirect_demo() { - global $pagenow; - - if ( 'admin.php' === $pagenow && isset( $_GET['page'] ) && 'gutenberg' === $_GET['page'] ) { - wp_safe_redirect( admin_url( 'post-new.php?gutenberg-demo' ) ); - exit; - } -} -add_action( 'admin_init', 'gutenberg_redirect_demo' ); - /** * Adds the filters to register additional links for the Gutenberg editor in * the post/page screens. diff --git a/lib/client-assets.php b/lib/client-assets.php index 4fff23ed4bdf5..634c5352e6e16 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -865,8 +865,6 @@ function gutenberg_get_available_image_sizes() { * @param string $hook Screen name. */ function gutenberg_editor_scripts_and_styles( $hook ) { - $is_demo = isset( $_GET['gutenberg-demo'] ); - global $wp_scripts, $wp_meta_boxes; // Add "wp-hooks" as dependency of "heartbeat". @@ -983,17 +981,7 @@ function gutenberg_editor_scripts_and_styles( $hook ) { // Assign initial edits, if applicable. These are not initially assigned // to the persisted post, but should be included in its save payload. - if ( $is_new_post && $is_demo ) { - // Prepopulate with some test content in demo. - ob_start(); - include gutenberg_dir_path() . 'post-content.php'; - $demo_content = ob_get_clean(); - - $initial_edits = array( - 'title' => __( 'Welcome to the Gutenberg Editor', 'gutenberg' ), - 'content' => $demo_content, - ); - } elseif ( $is_new_post ) { + if ( $is_new_post ) { // Override "(Auto Draft)" new post default title with empty string, // or filtered value. $initial_edits = array( diff --git a/lib/demo.php b/lib/demo.php new file mode 100644 index 0000000000000..11272050bf4c2 --- /dev/null +++ b/lib/demo.php @@ -0,0 +1,64 @@ +<?php +/** + * Supports for populating the Gutenberg demo content new post. + * + * @package gutenberg + */ + +if ( ! defined( 'ABSPATH' ) ) { + die( 'Silence is golden.' ); +} + +/** + * Redirects the demo page to edit a new post. + */ +function gutenberg_redirect_demo() { + global $pagenow; + + if ( 'admin.php' === $pagenow && isset( $_GET['page'] ) && 'gutenberg' === $_GET['page'] ) { + wp_safe_redirect( admin_url( 'post-new.php?gutenberg-demo' ) ); + exit; + } +} +add_action( 'admin_init', 'gutenberg_redirect_demo' ); + +/** + * Assigns the default content for the Gutenberg demo post. + * + * @param string $content Default post content. + * + * @return string Demo content if creating a new Gutenberg demo post, or the + * default content otherwise. + */ +function gutenberg_default_demo_content( $content ) { + $is_demo = isset( $_GET['gutenberg-demo'] ); + + if ( $is_demo ) { + // Prepopulate with some test content in demo. + ob_start(); + include gutenberg_dir_path() . 'post-content.php'; + return ob_get_clean(); + } + + return $content; +} +add_filter( 'default_content', 'gutenberg_default_demo_content' ); + +/** + * Assigns the default title for the Gutenberg demo post. + * + * @param string $title Default post title. + * + * @return string Demo title if creating a new Gutenberg demo post, or the + * default title otherwise. + */ +function gutenberg_default_demo_title( $title ) { + $is_demo = isset( $_GET['gutenberg-demo'] ); + + if ( $is_demo ) { + return __( 'Welcome to the Gutenberg Editor', 'gutenberg' ); + } + + return $title; +} +add_filter( 'default_title', 'gutenberg_default_demo_title' ); diff --git a/lib/load.php b/lib/load.php index ada0850ce4163..2f8644ccf4636 100644 --- a/lib/load.php +++ b/lib/load.php @@ -22,7 +22,7 @@ require dirname( __FILE__ ) . '/plugin-compat.php'; require dirname( __FILE__ ) . '/i18n.php'; require dirname( __FILE__ ) . '/register.php'; - +require dirname( __FILE__ ) . '/demo.php'; // Register server-side code for individual blocks. if ( ! function_exists( 'render_block_core_archives' ) ) { From 5f2c25d8da2846dec918aee5a6c2296faccd0d14 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Tue, 29 Jan 2019 11:36:51 -0500 Subject: [PATCH 286/691] Plugin: Avoid setting generic "Edit Post" title on load (#13552) --- gutenberg.php | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 502469ba2f421..13f0e148b57de 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -201,8 +201,6 @@ function gutenberg_pre_init() { * @return bool Whether Gutenberg was initialized. */ function gutenberg_init( $return, $post ) { - global $title, $post_type; - if ( true === $return && current_filter() === 'replace_editor' ) { return $return; } @@ -224,17 +222,6 @@ function gutenberg_init( $return, $post ) { add_filter( 'screen_options_show_screen', '__return_false' ); add_filter( 'admin_body_class', 'gutenberg_add_admin_body_class' ); - $post_type_object = get_post_type_object( $post_type ); - - /* - * Always force <title> to 'Edit Post' (or equivalent) - * because it needs to be in a generic state for both - * post-new.php and post.php?post=<id>. - */ - if ( ! empty( $post_type_object ) ) { - $title = $post_type_object->labels->edit_item; - } - /* * Remove the emoji script as it is incompatible with both React and any * contenteditable fields. From 4165bbd439be60069d59e7935fb57b4523bf2112 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Tue, 29 Jan 2019 11:37:38 -0500 Subject: [PATCH 287/691] Plugin: Deprecate window._wpLoadGutenbergEditor (#13547) --- .../backward-compatibility/deprecations.md | 1 + lib/client-assets.php | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/docs/designers-developers/developers/backward-compatibility/deprecations.md b/docs/designers-developers/developers/backward-compatibility/deprecations.md index e4a99cff29513..81cd5352a2cfe 100644 --- a/docs/designers-developers/developers/backward-compatibility/deprecations.md +++ b/docs/designers-developers/developers/backward-compatibility/deprecations.md @@ -56,6 +56,7 @@ The Gutenberg project's deprecation policy is intended to support backward compa - The PHP function `gutenberg_meta_box_post_form_hidden_fields` has been removed. Use [`the_block_editor_meta_box_post_form_hidden_fields`](https://developer.wordpress.org/reference/functions/the_block_editor_meta_box_post_form_hidden_fields/) instead. - The PHP function `gutenberg_toggle_custom_fields` has been removed. - The PHP function `gutenberg_collect_meta_box_data` has been removed. Use [`register_and_do_post_meta_boxes`](https://developer.wordpress.org/reference/functions/register_and_do_post_meta_boxes/) instead. +- `window._wpLoadGutenbergEditor` has been removed. Use `window._wpLoadBlockEditor` instead. Note: This is a private API, not intended for public use. It may be removed in the future. ## 4.5.0 - `Dropdown.refresh()` has been deprecated as the contained `Popover` is now automatically refreshed. diff --git a/lib/client-assets.php b/lib/client-assets.php index 634c5352e6e16..3d5045e3fe67c 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -1208,11 +1208,27 @@ function gutenberg_editor_scripts_and_styles( $hook ) { $init_script = <<<JS ( function() { - window._wpLoadGutenbergEditor = window._wpLoadBlockEditor = new Promise( function( resolve ) { + window._wpLoadBlockEditor = new Promise( function( resolve ) { wp.domReady( function() { resolve( wp.editPost.initializeEditor( 'editor', "%s", %d, %s, %s ) ); } ); } ); + + Object.defineProperty( window, '_wpLoadGutenbergEditor', { + get: function() { + // TODO: Hello future maintainer. In removing this deprecation, + // ensure also to check whether `wp-editor`'s dependencies in + // `package-dependencies.php` still require `wp-deprecated`. + wp.deprecated( '`window._wpLoadGutenbergEditor`', { + plugin: 'Gutenberg', + version: '5.2', + alternative: '`window._wpLoadBlockEditor`', + hint: 'This is a private API, not intended for public use. It may be removed in the future.' + } ); + + return window._wpLoadBlockEditor; + } + } ); } )(); JS; From d9cb84e5bdc4d4e46884edb83ed8418ab8bc0b3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20Van=C2=A0Durpe?= <iseulde@automattic.com> Date: Tue, 29 Jan 2019 18:04:21 +0100 Subject: [PATCH 288/691] RichText: List: fix indentation (#13563) * fix list indentation * Add more tests * Guard against negative lineIndex * Fix outdent error --- packages/rich-text/src/indent-list-items.js | 79 ++++++++++--------- packages/rich-text/src/outdent-list-items.js | 5 +- .../rich-text/src/test/indent-list-items.js | 44 +++++++++++ .../rich-text/src/test/outdent-list-items.js | 22 ++++++ 4 files changed, 113 insertions(+), 37 deletions(-) diff --git a/packages/rich-text/src/indent-list-items.js b/packages/rich-text/src/indent-list-items.js index 39226de4722dc..516b3c85f96ea 100644 --- a/packages/rich-text/src/indent-list-items.js +++ b/packages/rich-text/src/indent-list-items.js @@ -6,6 +6,36 @@ import { LINE_SEPARATOR } from './special-characters'; import { normaliseFormats } from './normalise-formats'; import { getLineIndex } from './get-line-index'; +/** + * Gets the line index of the first previous list item with higher indentation. + * + * @param {Object} value Value to search. + * @param {number} lineIndex Line index of the list item to compare with. + * + * @return {boolean} The line index. + */ +function getTargetLevelLineIndex( { text, formats }, lineIndex ) { + const startFormats = formats[ lineIndex ] || []; + + let index = lineIndex; + + while ( index-- >= 0 ) { + if ( text[ index ] !== LINE_SEPARATOR ) { + continue; + } + + const formatsAtIndex = formats[ index ] || []; + + // Return the first line index that is one level higher. If the level is + // lower or equal, there is no result. + if ( formatsAtIndex.length === startFormats.length + 1 ) { + return index; + } else if ( formatsAtIndex.length <= startFormats.length ) { + return; + } + } +} + /** * Indents any selected list items if possible. * @@ -23,62 +53,39 @@ export function indentListItems( value, rootFormat ) { } const { text, formats, start, end } = value; + const previousLineIndex = getLineIndex( value, lineIndex ); const formatsAtLineIndex = formats[ lineIndex ] || []; - const targetFormats = formats[ getLineIndex( value, lineIndex ) ] || []; + const formatsAtPreviousLineIndex = formats[ previousLineIndex ] || []; // The the indentation of the current line is greater than previous line, // then the line cannot be furter indented. - if ( formatsAtLineIndex.length > targetFormats.length ) { + if ( formatsAtLineIndex.length > formatsAtPreviousLineIndex.length ) { return value; } const newFormats = formats.slice(); + const targetLevelLineIndex = getTargetLevelLineIndex( value, lineIndex ); for ( let index = lineIndex; index < end; index++ ) { if ( text[ index ] !== LINE_SEPARATOR ) { continue; } - // If the indentation of the previous line is the same as the current - // line, then duplicate the type and append all current types. E.g. - // - // 1. one - // 2. two <= Selected - // * three <= Selected - // - // should become: - // - // 1. one - // 1. two <= Selected - // * three <= Selected - // - // ^ Inserted list - // - // Otherwise take the target formats and append traling lists. E.g. - // - // 1. one - // * target - // 2. two <= Selected - // * three <= Selected - // - // should become: - // - // 1. one - // * target - // * two <= Selected - // * three <= Selected - // - if ( targetFormats.length === formatsAtLineIndex.length ) { + // Get the previous list, and if there's a child list, take over the + // formats. If not, duplicate the last level and create a new level. + if ( targetLevelLineIndex ) { + const targetFormats = formats[ targetLevelLineIndex ] || []; + newFormats[ index ] = targetFormats.concat( + ( newFormats[ index ] || [] ).slice( targetFormats.length - 1 ) + ); + } else { + const targetFormats = formats[ previousLineIndex ] || []; const lastformat = targetFormats[ targetFormats.length - 1 ] || rootFormat; newFormats[ index ] = targetFormats.concat( [ lastformat ], ( newFormats[ index ] || [] ).slice( targetFormats.length ) ); - } else { - newFormats[ index ] = targetFormats.concat( - ( newFormats[ index ] || [] ).slice( targetFormats.length - 1 ) - ); } } diff --git a/packages/rich-text/src/outdent-list-items.js b/packages/rich-text/src/outdent-list-items.js index b26587f3a0bd1..3a493caa9b03a 100644 --- a/packages/rich-text/src/outdent-list-items.js +++ b/packages/rich-text/src/outdent-list-items.js @@ -38,9 +38,12 @@ export function outdentListItems( value ) { continue; } + // In the case of level 0, the formats at the index are undefined. + const currentFormats = newFormats[ index ] || []; + // Omit the indentation level where the selection starts. newFormats[ index ] = parentFormats.concat( - newFormats[ index ].slice( parentFormats.length + 1 ) + currentFormats.slice( parentFormats.length + 1 ) ); if ( newFormats[ index ].length === 0 ) { diff --git a/packages/rich-text/src/test/indent-list-items.js b/packages/rich-text/src/test/indent-list-items.js index c55d5063d21a8..e7f631e5fa8f9 100644 --- a/packages/rich-text/src/test/indent-list-items.js +++ b/packages/rich-text/src/test/indent-list-items.js @@ -130,4 +130,48 @@ describe( 'indentListItems', () => { expect( result ).not.toBe( record ); expect( getSparseArrayLength( result.formats ) ).toBe( 2 ); } ); + + it( 'should indent one level at a time', () => { + // As we're testing list formats, the text should remain the same. + const text = `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3${ LINE_SEPARATOR }4`; + const record = { + formats: [ , [ ul ], , [ ul, ul ], , , , ], + text, + start: 6, + end: 6, + }; + + const result1 = indentListItems( deepFreeze( record ), ul ); + + expect( result1 ).not.toBe( record ); + expect( getSparseArrayLength( result1.formats ) ).toBe( 3 ); + expect( result1 ).toEqual( { + formats: [ , [ ul ], , [ ul, ul ], , [ ul ], , ], + text, + start: 6, + end: 6, + } ); + + const result2 = indentListItems( deepFreeze( result1 ), ul ); + + expect( result2 ).not.toBe( result1 ); + expect( getSparseArrayLength( result2.formats ) ).toBe( 3 ); + expect( result2 ).toEqual( { + formats: [ , [ ul ], , [ ul, ul ], , [ ul, ul ], , ], + text, + start: 6, + end: 6, + } ); + + const result3 = indentListItems( deepFreeze( result2 ), ul ); + + expect( result3 ).not.toBe( result2 ); + expect( getSparseArrayLength( result3.formats ) ).toBe( 3 ); + expect( result3 ).toEqual( { + formats: [ , [ ul ], , [ ul, ul ], , [ ul, ul, ul ], , ], + text, + start: 6, + end: 6, + } ); + } ); } ); diff --git a/packages/rich-text/src/test/outdent-list-items.js b/packages/rich-text/src/test/outdent-list-items.js index c5b75d5d39188..c2bf8b30e4766 100644 --- a/packages/rich-text/src/test/outdent-list-items.js +++ b/packages/rich-text/src/test/outdent-list-items.js @@ -137,4 +137,26 @@ describe( 'outdentListItems', () => { expect( result ).not.toBe( record ); expect( getSparseArrayLength( result.formats ) ).toBe( 2 ); } ); + + it( 'should outdent when a selected item is at level 0', () => { + // As we're testing list formats, the text should remain the same. + const text = `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3`; + const record = { + formats: [ , [ ul ], , , , ], + text, + start: 2, + end: 5, + }; + const expected = { + formats: [ , , , , , ], + text, + start: 2, + end: 5, + }; + const result = outdentListItems( deepFreeze( record ) ); + + expect( result ).toEqual( expected ); + expect( result ).not.toBe( record ); + expect( getSparseArrayLength( result.formats ) ).toBe( 0 ); + } ); } ); From a6f7f9d4dcd80f0a1c8e9b5ae4336afcb4004832 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Tue, 29 Jan 2019 12:04:59 -0500 Subject: [PATCH 289/691] Block API: Parse entity only when valid character reference (#13512) * Block API: Rename IdentityEntityParser as DecodeEntityParser * Block API: Parse entity only when valid character reference --- packages/blocks/CHANGELOG.md | 4 ++ packages/blocks/src/api/test/validation.js | 49 +++++++++++-- packages/blocks/src/api/validation.js | 81 ++++++++++++++++++++-- 3 files changed, 123 insertions(+), 11 deletions(-) diff --git a/packages/blocks/CHANGELOG.md b/packages/blocks/CHANGELOG.md index 4d607751d8a31..e4a1b5977bcc0 100644 --- a/packages/blocks/CHANGELOG.md +++ b/packages/blocks/CHANGELOG.md @@ -4,6 +4,10 @@ - Blocks' `transforms` will receive `innerBlocks` as the second argument (or an array of each block's respective `innerBlocks` for a multi-transform). +### Bug Fixes + +- Block validation will now correctly validate character references, resolving some issues where a standalone ampersand `&` followed later in markup by a character reference (e.g. `&amp;`) could wrongly mark a block as being invalid. ([#13512](https://github.com/WordPress/gutenberg/pull/13512)) + ## 6.0.5 (2019-01-03) ## 6.0.4 (2018-12-12) diff --git a/packages/blocks/src/api/test/validation.js b/packages/blocks/src/api/test/validation.js index babcad7081ae0..fcfb61bff6ae2 100644 --- a/packages/blocks/src/api/test/validation.js +++ b/packages/blocks/src/api/test/validation.js @@ -2,7 +2,8 @@ * Internal dependencies */ import { - IdentityEntityParser, + isValidCharacterReference, + DecodeEntityParser, getTextPiecesSplitOnWhitespace, getTextWithCollapsedWhitespace, getMeaningfulAttributePairs, @@ -41,13 +42,39 @@ describe( 'validation', () => { } ); } ); - describe( 'IdentityEntityParser', () => { + describe( 'isValidCharacterReference', () => { + it( 'returns true for a named character reference', () => { + const result = isValidCharacterReference( 'blk12' ); + + expect( result ).toBe( true ); + } ); + + it( 'returns true for a decimal character reference', () => { + const result = isValidCharacterReference( '#33' ); + + expect( result ).toBe( true ); + } ); + + it( 'returns true for a hexadecimal character reference', () => { + const result = isValidCharacterReference( '#xC6' ); + + expect( result ).toBe( true ); + } ); + + it( 'returns false for an invalid character reference', () => { + const result = isValidCharacterReference( ' Test</h2><h2>Test &amp' ); + + expect( result ).toBe( false ); + } ); + } ); + + describe( 'DecodeEntityParser', () => { it( 'can be constructed', () => { - expect( new IdentityEntityParser() instanceof IdentityEntityParser ).toBe( true ); + expect( new DecodeEntityParser() instanceof DecodeEntityParser ).toBe( true ); } ); it( 'returns parse as decoded value', () => { - expect( new IdentityEntityParser().parse( 'quot' ) ).toBe( '"' ); + expect( new DecodeEntityParser().parse( 'quot' ) ).toBe( '"' ); } ); } ); @@ -397,6 +424,20 @@ describe( 'validation', () => { expect( isEquivalent ).toBe( true ); } ); + it( 'should account for character reference validity', () => { + // Regression: Previously the validator would wrongly evaluate the + // segment of text ` Test</h2><h2>Test &amp` as a character + // reference, as it's between an opening `&` and terminating `;`. + // + // See: https://github.com/WordPress/gutenberg/issues/12448 + const isEquivalent = isEquivalentHTML( + '<h2>Test &amp; Test</h2><h2>Test &amp; Test</h2>', + '<h2>Test & Test</h2><h2>Test &amp; Test</h2>' + ); + + expect( isEquivalent ).toBe( true ); + } ); + it( 'should return false when more tokens in first', () => { const isEquivalent = isEquivalentHTML( '<div>Hello</div>', diff --git a/packages/blocks/src/api/validation.js b/packages/blocks/src/api/validation.js index 4ccf580b8ffdd..452c8f832a43f 100644 --- a/packages/blocks/src/api/validation.js +++ b/packages/blocks/src/api/validation.js @@ -154,24 +154,91 @@ const TEXT_NORMALIZATIONS = [ ]; /** - * Subsitute EntityParser class for `simple-html-tokenizer` which bypasses - * entity substitution in favor of validator's internal normalization. + * Regular expression matching a named character reference. In lieu of bundling + * a full set of references, the pattern covers the minimal necessary to test + * positively against the full set. + * + * "The ampersand must be followed by one of the names given in the named + * character references section, using the same case." + * + * Tested aginst "12.5 Named character references": + * + * ``` + * const references = [ ...document.querySelectorAll( + * '#named-character-references-table tr[id^=entity-] td:first-child' + * ) ].map( ( code ) => code.textContent ) + * references.every( ( reference ) => /^[\da-z]+$/i.test( reference ) ) + * ``` + * + * @link https://html.spec.whatwg.org/multipage/syntax.html#character-references + * @link https://html.spec.whatwg.org/multipage/named-characters.html#named-character-references + * + * @type {RegExp} + */ +const REGEXP_NAMED_CHARACTER_REFERENCE = /^[\da-z]+$/i; + +/** + * Regular expression matching a decimal character reference. + * + * "The ampersand must be followed by a U+0023 NUMBER SIGN character (#), + * followed by one or more ASCII digits, representing a base-ten integer" + * + * @link https://html.spec.whatwg.org/multipage/syntax.html#character-references + * + * @type {RegExp} + */ +const REGEXP_DECIMAL_CHARACTER_REFERENCE = /^#\d+$/; + +/** + * Regular expression matching a hexadecimal character reference. + * + * "The ampersand must be followed by a U+0023 NUMBER SIGN character (#), which + * must be followed by either a U+0078 LATIN SMALL LETTER X character (x) or a + * U+0058 LATIN CAPITAL LETTER X character (X), which must then be followed by + * one or more ASCII hex digits, representing a hexadecimal integer" + * + * @link https://html.spec.whatwg.org/multipage/syntax.html#character-references + * + * @type {RegExp} + */ +const REGEXP_HEXADECIMAL_CHARACTER_REFERENCE = /^#x[\da-f]+$/i; + +/** + * Returns true if the given string is a valid character reference segment, or + * false otherwise. The text should be stripped of `&` and `;` demarcations. + * + * @param {string} text Text to test. + * + * @return {boolean} Whether text is valid character reference. + */ +export function isValidCharacterReference( text ) { + return ( + REGEXP_NAMED_CHARACTER_REFERENCE.test( text ) || + REGEXP_DECIMAL_CHARACTER_REFERENCE.test( text ) || + REGEXP_HEXADECIMAL_CHARACTER_REFERENCE.test( text ) + ); +} + +/** + * Subsitute EntityParser class for `simple-html-tokenizer` which uses the + * implementation of `decodeEntities` from `html-entities`, in order to avoid + * bundling a massive named character reference. * * @see https://github.com/tildeio/simple-html-tokenizer/tree/master/src/entity-parser.ts */ -export class IdentityEntityParser { +export class DecodeEntityParser { /** * Returns a substitute string for an entity string sequence between `&` * and `;`, or undefined if no substitution should occur. * - * In this implementation, undefined is always returned. - * * @param {string} entity Entity fragment discovered in HTML. * * @return {?string} Entity substitute value. */ parse( entity ) { - return decodeEntities( '&' + entity + ';' ); + if ( isValidCharacterReference( entity ) ) { + return decodeEntities( '&' + entity + ';' ); + } } } @@ -451,7 +518,7 @@ export function getNextNonWhitespaceToken( tokens ) { */ function getHTMLTokens( html ) { try { - return new Tokenizer( new IdentityEntityParser() ).tokenize( html ); + return new Tokenizer( new DecodeEntityParser() ).tokenize( html ); } catch ( e ) { log.warning( 'Malformed HTML detected: %s', html ); } From 797df63a80a32d1bfaf5d6b2ed6162e760318c1b Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Tue, 29 Jan 2019 12:05:14 -0500 Subject: [PATCH 290/691] Plugin: Deprecate gutenberg_get_script_polyfill (#13536) --- .../backward-compatibility/deprecations.md | 1 + lib/client-assets.php | 28 +++------- phpunit/class-polyfill-test.php | 54 ------------------- 3 files changed, 8 insertions(+), 75 deletions(-) delete mode 100644 phpunit/class-polyfill-test.php diff --git a/docs/designers-developers/developers/backward-compatibility/deprecations.md b/docs/designers-developers/developers/backward-compatibility/deprecations.md index 81cd5352a2cfe..6ff124b27a34f 100644 --- a/docs/designers-developers/developers/backward-compatibility/deprecations.md +++ b/docs/designers-developers/developers/backward-compatibility/deprecations.md @@ -57,6 +57,7 @@ The Gutenberg project's deprecation policy is intended to support backward compa - The PHP function `gutenberg_toggle_custom_fields` has been removed. - The PHP function `gutenberg_collect_meta_box_data` has been removed. Use [`register_and_do_post_meta_boxes`](https://developer.wordpress.org/reference/functions/register_and_do_post_meta_boxes/) instead. - `window._wpLoadGutenbergEditor` has been removed. Use `window._wpLoadBlockEditor` instead. Note: This is a private API, not intended for public use. It may be removed in the future. +- The PHP function `gutenberg_get_script_polyfill` has been removed. Use [`wp_get_script_polyfill`](https://developer.wordpress.org/reference/functions/wp_get_script_polyfill/) instead. ## 4.5.0 - `Dropdown.refresh()` has been deprecated as the contained `Popover` is now automatically refreshed. diff --git a/lib/client-assets.php b/lib/client-assets.php index 3d5045e3fe67c..469310edbe118 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -43,27 +43,10 @@ function gutenberg_url( $path ) { * @return string Conditional polyfill inline script. */ function gutenberg_get_script_polyfill( $tests ) { - global $wp_scripts; - - $polyfill = ''; - foreach ( $tests as $test => $handle ) { - if ( ! array_key_exists( $handle, $wp_scripts->registered ) ) { - continue; - } - - $polyfill .= ( - // Test presence of feature... - '( ' . $test . ' ) || ' . - // ...appending polyfill on any failures. Cautious viewers may balk - // at the `document.write`. Its caveat of synchronous mid-stream - // blocking write is exactly the behavior we need though. - 'document.write( \'<script src="' . - esc_url( $wp_scripts->registered[ $handle ]->src ) . - '"></scr\' + \'ipt>\' );' - ); - } + _deprecated_function( __FUNCTION__, '5.0.0', 'wp_get_script_polyfill' ); - return $polyfill; + global $wp_scripts; + return wp_get_script_polyfill( $wp_scripts, $tests ); } if ( ! function_exists( 'register_tinymce_scripts' ) ) { @@ -153,11 +136,14 @@ function gutenberg_register_packages_scripts() { * @since 0.1.0 */ function gutenberg_register_scripts_and_styles() { + global $wp_scripts; + gutenberg_register_vendor_scripts(); wp_add_inline_script( 'wp-polyfill', - gutenberg_get_script_polyfill( + wp_get_script_polyfill( + $wp_scripts, array( '\'fetch\' in window' => 'wp-polyfill-fetch', 'document.contains' => 'wp-polyfill-node-contains', diff --git a/phpunit/class-polyfill-test.php b/phpunit/class-polyfill-test.php deleted file mode 100644 index c64878bd13fb2..0000000000000 --- a/phpunit/class-polyfill-test.php +++ /dev/null @@ -1,54 +0,0 @@ -<?php -/** - * Test gutenberg_get_script_polyfill() - * - * @package Gutenberg - */ - -class Polyfill_Test extends WP_UnitTestCase { - var $old_wp_scripts; - - function setUp() { - parent::setUp(); - $this->old_wp_scripts = isset( $GLOBALS['wp_scripts'] ) ? $GLOBALS['wp_scripts'] : null; - remove_action( 'wp_default_scripts', 'wp_default_scripts' ); - - $GLOBALS['wp_scripts'] = new WP_Scripts(); - - $GLOBALS['wp_scripts']->default_version = get_bloginfo( 'version' ); - - gutenberg_register_scripts_and_styles(); - } - - public function tearDown() { - $GLOBALS['wp_scripts'] = $this->old_wp_scripts; - add_action( 'wp_default_scripts', 'wp_default_scripts' ); - parent::tearDown(); - } - - function test_gutenberg_get_script_polyfill_ignores_missing_handle() { - $polyfill = gutenberg_get_script_polyfill( - array( - '\'Promise\' in window' => 'promise', - ) - ); - - $this->assertEquals( '', $polyfill ); - } - - function test_gutenberg_get_script_polyfill_returns_inline_script() { - wp_register_script( 'promise', 'https://unpkg.com/promise-polyfill/promise.js' ); - - $polyfill = gutenberg_get_script_polyfill( - array( - '\'Promise\' in window' => 'promise', - ) - ); - - $this->assertEquals( - '( \'Promise\' in window ) || document.write( \'<script src="https://unpkg.com/promise-polyfill/promise.js"></scr\' + \'ipt>\' );', - $polyfill - ); - } - -} From e301fc4825df84385ca9b425bcacbe24cbd57a26 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Mon, 28 Jan 2019 17:57:20 +0100 Subject: [PATCH 291/691] chore(release): publish - @wordpress/annotations@1.0.7 - @wordpress/block-library@2.2.14 - @wordpress/components@7.0.7 - @wordpress/edit-post@3.1.9 - @wordpress/editor@9.0.9 - @wordpress/format-library@1.2.12 - @wordpress/list-reusable-blocks@1.1.20 - @wordpress/nux@3.0.8 - @wordpress/rich-text@3.0.6 --- packages/annotations/package.json | 2 +- packages/block-library/package.json | 2 +- packages/components/package.json | 2 +- packages/edit-post/package.json | 2 +- packages/editor/package.json | 2 +- packages/format-library/package.json | 2 +- packages/list-reusable-blocks/package.json | 2 +- packages/nux/package.json | 2 +- packages/rich-text/package.json | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/annotations/package.json b/packages/annotations/package.json index 6a89aa196b265..34314a94e3f59 100644 --- a/packages/annotations/package.json +++ b/packages/annotations/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/annotations", - "version": "1.0.6", + "version": "1.0.7", "description": "Annotate content in the Gutenberg editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-library/package.json b/packages/block-library/package.json index 03123f6c4aadd..ad0d614dd2cd2 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-library", - "version": "2.2.13", + "version": "2.2.14", "description": "Block library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/components/package.json b/packages/components/package.json index 2b97622f3da9c..6d435f2b5ae8c 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/components", - "version": "7.0.6", + "version": "7.0.7", "description": "UI components for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json index fc44436ec4792..a062b6a223ffa 100644 --- a/packages/edit-post/package.json +++ b/packages/edit-post/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-post", - "version": "3.1.8", + "version": "3.1.9", "description": "Edit Post module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/editor/package.json b/packages/editor/package.json index 79684c95217c3..6b76323a549a4 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/editor", - "version": "9.0.8", + "version": "9.0.9", "description": "Building blocks for WordPress editors.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/format-library/package.json b/packages/format-library/package.json index a18456013c62c..4394c4eca03f2 100644 --- a/packages/format-library/package.json +++ b/packages/format-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/format-library", - "version": "1.2.11", + "version": "1.2.12", "description": "Format library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/list-reusable-blocks/package.json b/packages/list-reusable-blocks/package.json index 9a51dc4dd0957..36236e8fd53ac 100644 --- a/packages/list-reusable-blocks/package.json +++ b/packages/list-reusable-blocks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/list-reusable-blocks", - "version": "1.1.19", + "version": "1.1.20", "description": "Adding Export/Import support to the reusable blocks listing.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/nux/package.json b/packages/nux/package.json index b0015ff5c201f..83268570ffa55 100644 --- a/packages/nux/package.json +++ b/packages/nux/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/nux", - "version": "3.0.7", + "version": "3.0.8", "description": "NUX (New User eXperience) module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/rich-text/package.json b/packages/rich-text/package.json index 7c475baec9a52..251217e2e6f49 100644 --- a/packages/rich-text/package.json +++ b/packages/rich-text/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/rich-text", - "version": "3.0.5", + "version": "3.0.6", "description": "Rich text value and manipulation API.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", From 4a717ae81c1c0637be338ed5cc8b6ce65038e74a Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Tue, 29 Jan 2019 18:16:14 +0100 Subject: [PATCH 292/691] chore(release): publish - @wordpress/annotations@1.0.8 - @wordpress/block-library@2.2.15 - @wordpress/components@7.0.8 - @wordpress/edit-post@3.1.10 - @wordpress/editor@9.0.10 - @wordpress/format-library@1.2.13 - @wordpress/list-reusable-blocks@1.1.21 - @wordpress/nux@3.0.9 - @wordpress/rich-text@3.0.7 --- packages/annotations/package.json | 2 +- packages/block-library/package.json | 2 +- packages/components/package.json | 2 +- packages/edit-post/package.json | 2 +- packages/editor/package.json | 2 +- packages/format-library/package.json | 2 +- packages/list-reusable-blocks/package.json | 2 +- packages/nux/package.json | 2 +- packages/rich-text/package.json | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/annotations/package.json b/packages/annotations/package.json index 34314a94e3f59..a259d8a4d55ac 100644 --- a/packages/annotations/package.json +++ b/packages/annotations/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/annotations", - "version": "1.0.7", + "version": "1.0.8", "description": "Annotate content in the Gutenberg editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-library/package.json b/packages/block-library/package.json index ad0d614dd2cd2..855f2257ca3f1 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-library", - "version": "2.2.14", + "version": "2.2.15", "description": "Block library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/components/package.json b/packages/components/package.json index 6d435f2b5ae8c..7cfe766f40617 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/components", - "version": "7.0.7", + "version": "7.0.8", "description": "UI components for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json index a062b6a223ffa..d6ec1e5c769a4 100644 --- a/packages/edit-post/package.json +++ b/packages/edit-post/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-post", - "version": "3.1.9", + "version": "3.1.10", "description": "Edit Post module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/editor/package.json b/packages/editor/package.json index 6b76323a549a4..f27cff81ed68d 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/editor", - "version": "9.0.9", + "version": "9.0.10", "description": "Building blocks for WordPress editors.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/format-library/package.json b/packages/format-library/package.json index 4394c4eca03f2..cb77cba8519cd 100644 --- a/packages/format-library/package.json +++ b/packages/format-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/format-library", - "version": "1.2.12", + "version": "1.2.13", "description": "Format library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/list-reusable-blocks/package.json b/packages/list-reusable-blocks/package.json index 36236e8fd53ac..06ac020007686 100644 --- a/packages/list-reusable-blocks/package.json +++ b/packages/list-reusable-blocks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/list-reusable-blocks", - "version": "1.1.20", + "version": "1.1.21", "description": "Adding Export/Import support to the reusable blocks listing.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/nux/package.json b/packages/nux/package.json index 83268570ffa55..aadfc11b17e13 100644 --- a/packages/nux/package.json +++ b/packages/nux/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/nux", - "version": "3.0.8", + "version": "3.0.9", "description": "NUX (New User eXperience) module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/rich-text/package.json b/packages/rich-text/package.json index 251217e2e6f49..541cccc79cc56 100644 --- a/packages/rich-text/package.json +++ b/packages/rich-text/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/rich-text", - "version": "3.0.6", + "version": "3.0.7", "description": "Rich text value and manipulation API.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", From b1e26ae9b99c1badeffc52adcdc7c371efd178c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Est=C3=AAv=C3=A3o?= <sergioestevao@gmail.com> Date: Tue, 29 Jan 2019 21:39:44 +0000 Subject: [PATCH 293/691] Rnmobile/media methods refactor (#13554) * Refactor name of media upload methods. * Change method name for media upload sync. * Refactor media request method names on bridge to make clear intent. --- .../block-library/src/image/edit.native.js | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index eb73ef558542d..6c5fad9b7b05e 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -5,10 +5,10 @@ import React from 'react'; import { View, Image, TextInput } from 'react-native'; import { subscribeMediaUpload, - onMediaLibraryPressed, - onUploadMediaPressed, - onCapturePhotoPressed, - onImageQueryReattach, + requestMediaPickFromMediaLibrary, + requestMediaPickFromDeviceLibrary, + requestMediaPickFromDeviceCamera, + mediaUploadSync, } from 'react-native-gutenberg-bridge'; /** @@ -45,7 +45,7 @@ export default class ImageEdit extends React.Component { if ( attributes.id && ! isURL( attributes.url ) ) { this.addMediaUploadListener(); - onImageQueryReattach(); + mediaUploadSync(); } } @@ -108,7 +108,7 @@ export default class ImageEdit extends React.Component { const { url, caption, height, width } = attributes; const onMediaLibraryButtonPressed = () => { - onMediaLibraryPressed( ( mediaId, mediaUrl ) => { + requestMediaPickFromMediaLibrary( ( mediaId, mediaUrl ) => { if ( mediaUrl ) { setAttributes( { id: mediaId, url: mediaUrl } ); } @@ -116,8 +116,8 @@ export default class ImageEdit extends React.Component { }; if ( ! url ) { - const onUploadMediaButtonPressed = () => { - onUploadMediaPressed( ( mediaId, mediaUri ) => { + const onMediaUploadButtonPressed = () => { + requestMediaPickFromDeviceLibrary( ( mediaId, mediaUri ) => { if ( mediaUri ) { this.addMediaUploadListener( ); setAttributes( { url: mediaUri, id: mediaId } ); @@ -125,8 +125,8 @@ export default class ImageEdit extends React.Component { } ); }; - const onCapturePhotoButtonPressed = () => { - onCapturePhotoPressed( ( mediaId, mediaUri ) => { + const onMediaCaptureButtonPressed = () => { + requestMediaPickFromDeviceCamera( ( mediaId, mediaUri ) => { if ( mediaUri ) { this.addMediaUploadListener( ); setAttributes( { url: mediaUri, id: mediaId } ); @@ -136,9 +136,9 @@ export default class ImageEdit extends React.Component { return ( <MediaPlaceholder - onUploadMediaPressed={ onUploadMediaButtonPressed } + onUploadMediaPressed={ onMediaUploadButtonPressed } onMediaLibraryPressed={ onMediaLibraryButtonPressed } - onCapturePhotoPressed={ onCapturePhotoButtonPressed } + onCapturePhotoPressed={ onMediaCaptureButtonPressed } /> ); } From e5e6d6cdadd4d7c2dbc5c3b156387bd0d31cbd28 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Tue, 29 Jan 2019 23:49:46 -0800 Subject: [PATCH 294/691] Docs: Add accessbility specific page (#13169) * Docs: Add accessbility specific page Adds an additional page to the developer handbook on accessibility in particular the landmark regions. Fixes #3217 * Add navigationRegions link --- .../developers/accessibility.md | 17 +++++++++++++++++ docs/manifest.json | 6 ++++++ docs/toc.json | 1 + 3 files changed, 24 insertions(+) create mode 100644 docs/designers-developers/developers/accessibility.md diff --git a/docs/designers-developers/developers/accessibility.md b/docs/designers-developers/developers/accessibility.md new file mode 100644 index 0000000000000..ab6c0d0066b1d --- /dev/null +++ b/docs/designers-developers/developers/accessibility.md @@ -0,0 +1,17 @@ +# Accessibility + +Accessibility documentation for developers working on the Gutenberg Project. + +For more information on accessibility and WordPress see the [Make WordPress Accessibility Handbook](https://make.wordpress.org/accessibility/handbook/) and the [Accessibility Team section](https://make.wordpress.org/accessibility/). + +## Landmark Regions + +It is a best practice to include ALL content on the page in landmarks, so that screen reader users who rely on them to navigate from section to section do not lose track of content. + +For setting up navigation between different regions, see the [navigateRegions package](/packages/components/src/higher-order/navigate-regions/README.md) for additional documentation. + +Read more regarding landmark design from W3C: + +- [General Principles of Landmark Design](https://www.w3.org/TR/wai-aria-practices-1.1/#general-principles-of-landmark-design) +- [ARIA Landmarks Example](https://www.w3.org/TR/wai-aria-practices/examples/landmarks/) +- [HTML5 elements that by default define ARIA landmarks](https://www.w3.org/TR/wai-aria-practices/examples/landmarks/HTML5.html) diff --git a/docs/manifest.json b/docs/manifest.json index e82a59211c392..35964c417ab5e 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -101,6 +101,12 @@ "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/internationalization.md", "parent": "developers" }, + { + "title": "Accessibility", + "slug": "accessibility", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/accessibility.md", + "parent": "developers" + }, { "title": "Data Module Reference", "slug": "data", diff --git a/docs/toc.json b/docs/toc.json index cdb3fa096507b..4e06b65d552ce 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -18,6 +18,7 @@ {"docs/designers-developers/developers/filters/autocomplete-filters.md": []} ]}, {"docs/designers-developers/developers/internationalization.md": []}, + {"docs/designers-developers/developers/accessibility.md": []}, {"docs/designers-developers/developers/data/README.md": "{{data}}"}, {"docs/designers-developers/developers/packages.md": "{{packages}}"}, {"packages/components/README.md": "{{components}}"}, From d3f9fcf48b7248c9bce22d0bacb46eeb9ad526b7 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Wed, 30 Jan 2019 00:11:30 -0800 Subject: [PATCH 295/691] Update util.js (#13582) Fix typo --- packages/block-library/src/embed/util.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/embed/util.js b/packages/block-library/src/embed/util.js index 5352fce1fb58e..5161d13523c6b 100644 --- a/packages/block-library/src/embed/util.js +++ b/packages/block-library/src/embed/util.js @@ -51,7 +51,7 @@ export const isFromWordPress = ( html ) => { export const getPhotoHtml = ( photo ) => { // 100% width for the preview so it fits nicely into the document, some "thumbnails" are - // acually the full size photo. + // actually the full size photo. const photoPreview = <p><img src={ photo.thumbnail_url } alt={ photo.title } width="100%" /></p>; return renderToString( photoPreview ); }; From 5105c197279597d1b302822e2d8712c45ad6b3f3 Mon Sep 17 00:00:00 2001 From: Timothy Jacobs <timothy@ironbounddesigns.com> Date: Wed, 30 Jan 2019 05:07:11 -0500 Subject: [PATCH 296/691] Components: Set type=button for TabPanel button elements. (#11944) * Components: Set type=button for TabPanel button elements. The default button type is "submit" which submits the enclosing form. Setting the type property to "button" prevents this behavior. * Use a Button component for Tab buttons * Remove empty line --- packages/components/src/tab-panel/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/components/src/tab-panel/index.js b/packages/components/src/tab-panel/index.js index 2e94bd2b05e14..5558272f2ffc4 100644 --- a/packages/components/src/tab-panel/index.js +++ b/packages/components/src/tab-panel/index.js @@ -13,9 +13,10 @@ import { withInstanceId } from '@wordpress/compose'; * Internal dependencies */ import { NavigableMenu } from '../navigable-container'; +import Button from '../button'; const TabButton = ( { tabId, onClick, children, selected, ...rest } ) => ( - <button role="tab" + <Button role="tab" tabIndex={ selected ? null : -1 } aria-selected={ selected } id={ tabId } @@ -23,7 +24,7 @@ const TabButton = ( { tabId, onClick, children, selected, ...rest } ) => ( { ...rest } > { children } - </button> + </Button> ); class TabPanel extends Component { From b5fbda755ce99fdb5e16430d0fb3fcf1f12a298e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Wrede?= <soerenwrede@gmail.com> Date: Wed, 30 Jan 2019 11:44:35 +0100 Subject: [PATCH 297/691] Add changelog for RSS block (#13588) * Add changelog for RSS block * Update packages/block-library/CHANGELOG.md Co-Authored-By: Soean <soerenwrede@gmail.com> --- packages/block-library/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/block-library/CHANGELOG.md b/packages/block-library/CHANGELOG.md index 398b2af5887d9..12ab36c1f2104 100644 --- a/packages/block-library/CHANGELOG.md +++ b/packages/block-library/CHANGELOG.md @@ -3,6 +3,7 @@ ### New Feature - Add background color controls for the table block. +- Add new `RSS` block ([#7966](https://github.com/WordPress/gutenberg/pull/7966)). ## 2.2.12 (2019-01-03) From b09c855d0dfb47d326a8878fdedbafe4d370dee3 Mon Sep 17 00:00:00 2001 From: Nikolay Ninarski <ninio@shtrak.eu> Date: Wed, 30 Jan 2019 14:21:46 +0200 Subject: [PATCH 298/691] Removed unnecessary className attribute. Fixes #11664 (#11831) ## Description Added a check for empty string to the `onChange` handler of the "Additional CSS Class field". If the string is empty the `setAttributes` is called with `{ className: undefined }` This will resolve #11664 ## How has this been tested? Created a new post and added a heading block. In the Advanced menu, added a class and switched to the Code Editor Tested on local WP running on Windows with Chrome 70.0.3538.102 ## Types of changes Bug fix (non-breaking change which fixes an issue) ## Checklist: - [x] My code is tested. - [x] My code follows the WordPress code style. <!-- Check code: `npm run lint`, Guidelines: https://make.wordpress.org/core/handbook/best-practices/coding-standards/javascript/ --> - [x] My code follows the accessibility standards. <!-- Guidelines: https://make.wordpress.org/core/handbook/best-practices/coding-standards/accessibility-coding-standards/ --> - [x] My code has proper inline documentation. <!-- Guidelines: https://make.wordpress.org/core/handbook/best-practices/inline-documentation-standards/javascript/ --> --- packages/editor/src/hooks/custom-class-name.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor/src/hooks/custom-class-name.js b/packages/editor/src/hooks/custom-class-name.js index 9779dfd1a2bb5..3d4af47227670 100644 --- a/packages/editor/src/hooks/custom-class-name.js +++ b/packages/editor/src/hooks/custom-class-name.js @@ -65,7 +65,7 @@ export const withInspectorControl = createHigherOrderComponent( ( BlockEdit ) => value={ props.attributes.className || '' } onChange={ ( nextValue ) => { props.setAttributes( { - className: nextValue, + className: nextValue !== '' ? nextValue : undefined, } ); } } /> From 62e1522ef5431fcc7f515588b203672d396b7029 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Wed, 30 Jan 2019 08:41:14 -0500 Subject: [PATCH 299/691] REST API: Remove oEmbed proxy HTML filtering (#13575) * Plugin: Remove oEmbed proxy HTML filtering * Plugin: Use updated Trac ticket link for oEmbed filter reference * Plugin: Use updated Trac ticket link for oEmbed filter reference --- lib/rest-api.php | 61 ++++++++++++++++-------------------------------- 1 file changed, 20 insertions(+), 41 deletions(-) diff --git a/lib/rest-api.php b/lib/rest-api.php index 616493a371219..d12d7df12dfcc 100644 --- a/lib/rest-api.php +++ b/lib/rest-api.php @@ -21,12 +21,9 @@ function gutenberg_register_rest_routes() { } /** - * Make sure oEmbed REST Requests apply the WP Embed security mechanism for WordPress embeds. + * Handle a failing oEmbed proxy request to try embedding as a shortcode. * - * @see https://core.trac.wordpress.org/ticket/32522 - * - * TODO: This is a temporary solution. Next step would be to edit the WP_oEmbed_Controller, - * once merged into Core. + * @see https://core.trac.wordpress.org/ticket/45447 * * @since 2.3.0 * @@ -36,50 +33,32 @@ function gutenberg_register_rest_routes() { * @return WP_HTTP_Response|object|WP_Error The REST Request response. */ function gutenberg_filter_oembed_result( $response, $handler, $request ) { - if ( 'GET' !== $request->get_method() ) { + if ( ! is_wp_error( $response ) || 'oembed_invalid_url' !== $response->get_error_code() || + '/oembed/1.0/proxy' !== $request->get_route() ) { return $response; } - if ( is_wp_error( $response ) && 'oembed_invalid_url' !== $response->get_error_code() ) { + // Try using a classic embed instead. + global $wp_embed; + $html = $wp_embed->shortcode( array(), $_GET['url'] ); + if ( ! $html ) { return $response; } - // External embeds. - if ( '/oembed/1.0/proxy' === $request->get_route() ) { - if ( is_wp_error( $response ) ) { - // It's possibly a local post, so lets try and retrieve it that way. - $post_id = url_to_postid( $_GET['url'] ); - $data = get_oembed_response_data( $post_id, apply_filters( 'oembed_default_width', 600 ) ); - - if ( $data ) { - // It's a local post! - $response = (object) $data; - } else { - // Try using a classic embed, instead. - global $wp_embed; - $html = $wp_embed->shortcode( array(), $_GET['url'] ); - if ( $html ) { - global $wp_scripts; - // Check if any scripts were enqueued by the shortcode, and - // include them in the response. - $enqueued_scripts = array(); - foreach ( $wp_scripts->queue as $script ) { - $enqueued_scripts[] = $wp_scripts->registered[ $script ]->src; - } - return array( - 'provider_name' => __( 'Embed Handler', 'gutenberg' ), - 'html' => $html, - 'scripts' => $enqueued_scripts, - ); - } - } - } - - // Make sure the HTML is run through the oembed sanitisation routines. - $response->html = wp_oembed_get( $_GET['url'], $_GET ); + global $wp_scripts; + + // Check if any scripts were enqueued by the shortcode, and include them in + // the response. + $enqueued_scripts = array(); + foreach ( $wp_scripts->queue as $script ) { + $enqueued_scripts[] = $wp_scripts->registered[ $script ]->src; } - return $response; + return array( + 'provider_name' => __( 'Embed Handler', 'gutenberg' ), + 'html' => $html, + 'scripts' => $enqueued_scripts, + ); } add_filter( 'rest_request_after_callbacks', 'gutenberg_filter_oembed_result', 10, 3 ); From 0fa385f13dc306616bf3b24cd1adcb4644df584f Mon Sep 17 00:00:00 2001 From: Sara Cope <sara.cope@gsa.gov> Date: Wed, 30 Jan 2019 09:54:24 -0500 Subject: [PATCH 300/691] Typo fix (#13595) Update the first sentence on line 289 from: "Reducer tests are also be a great fit for snapshots." to: "Reducer tests are also a great fit for snapshots." --- docs/contributors/testing-overview.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contributors/testing-overview.md b/docs/contributors/testing-overview.md index 495109b5f0712..b2a2602491f9d 100644 --- a/docs/contributors/testing-overview.md +++ b/docs/contributors/testing-overview.md @@ -286,7 +286,7 @@ describe( 'SolarSystem', () => { } ); ``` -Reducer tests are also be a great fit for snapshots. They are often large, complex data structures that shouldn't change unexpectedly, exactly what snapshots excel at! +Reducer tests are also a great fit for snapshots. They are often large, complex data structures that shouldn't change unexpectedly, exactly what snapshots excel at! #### Working with snapshots From 5a8d276eb40ba0defeeff8abf9fee259887dbc44 Mon Sep 17 00:00:00 2001 From: Igor Zinovyev <zinigor@gmail.com> Date: Wed, 30 Jan 2019 18:12:41 +0300 Subject: [PATCH 301/691] Fixes plural messages POT generation. (#13577) * Fixes plural messages POT generation. * Moved the parseInt statement to the assignment. * Added the CHANGELOG.md entry. --- packages/babel-plugin-makepot/CHANGELOG.md | 6 ++++++ packages/babel-plugin-makepot/src/index.js | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/babel-plugin-makepot/CHANGELOG.md b/packages/babel-plugin-makepot/CHANGELOG.md index c70831cbada60..8079f5197a2fd 100644 --- a/packages/babel-plugin-makepot/CHANGELOG.md +++ b/packages/babel-plugin-makepot/CHANGELOG.md @@ -1,3 +1,9 @@ +## v2.1.1 (Unreleased) + +### Bug Fix + +- Fixed Babel plugin for POT file generation to properly handle plural numbers specified in the passed header. ([#13577](https://github.com/WordPress/gutenberg/pull/13577)) + ## 2.1.0 (2018-09-05) ### New Feature diff --git a/packages/babel-plugin-makepot/src/index.js b/packages/babel-plugin-makepot/src/index.js index 1f9524f524ac5..cf66b8bbc80f3 100644 --- a/packages/babel-plugin-makepot/src/index.js +++ b/packages/babel-plugin-makepot/src/index.js @@ -246,7 +246,7 @@ module.exports = function() { // Attempt to exract nplurals from header const pluralsMatch = ( baseData.headers[ 'plural-forms' ] || '' ).match( /nplurals\s*=\s*(\d+);/ ); if ( pluralsMatch ) { - nplurals = pluralsMatch[ 1 ]; + nplurals = parseInt( pluralsMatch[ 1 ], 10 ); } } From f2c5db6c8202a7cab09384c1d2b691882d977ae0 Mon Sep 17 00:00:00 2001 From: Dixita Dusara <dixitadusara@gmail.com> Date: Wed, 30 Jan 2019 22:16:48 +0530 Subject: [PATCH 302/691] Fixed wording for the color picker saturation (#13479) Related: #13444 This pull request seeks to fix wording for the color picker saturation. Let me know if any changes require. Thank you, --- packages/components/src/color-picker/saturation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/color-picker/saturation.js b/packages/components/src/color-picker/saturation.js index 8e86655025e92..90c6f8661bb90 100644 --- a/packages/components/src/color-picker/saturation.js +++ b/packages/components/src/color-picker/saturation.js @@ -175,7 +175,7 @@ export class Saturation extends Component { className="screen-reader-text" id={ `color-picker-saturation-${ instanceId }` }> { __( - 'Use your arrow keys to change the base color. Move up to lighten the color, down to darken, left to increase saturation, and right to decrease saturation.' + 'Use your arrow keys to change the base color. Move up to lighten the color, down to darken, left to decrease saturation, and right to increase saturation.' ) } </div> </div> From a85767a12f456e99c91ccb1f6a0d01f0ea32624b Mon Sep 17 00:00:00 2001 From: etoledom <etoledom@icloud.com> Date: Wed, 30 Jan 2019 21:35:37 +0100 Subject: [PATCH 303/691] Image settings button (#13597) * rnmobile: Implement image settings button using InspectorControls.Slot pattern. * rnmobile: Add missing semicolon --- packages/block-library/src/image/edit.native.js | 17 ++++++++++++++++- packages/editor/src/components/index.native.js | 1 + 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index 6c5fad9b7b05e..7413978f2e012 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -14,7 +14,7 @@ import { /** * Internal dependencies */ -import { MediaPlaceholder, RichText, BlockControls } from '@wordpress/editor'; +import { MediaPlaceholder, RichText, BlockControls, InspectorControls } from '@wordpress/editor'; import { Toolbar, ToolbarButton, Spinner } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import ImageSize from './image-size'; @@ -143,6 +143,10 @@ export default class ImageEdit extends React.Component { ); } + const onImageSettingsButtonPressed = () => { + + }; + const toolbarEditButton = ( <Toolbar> <ToolbarButton @@ -153,6 +157,14 @@ export default class ImageEdit extends React.Component { </Toolbar> ); + const inlineToolbarButtons = ( + <ToolbarButton + label={ __( 'Image Settings' ) } + icon="admin-generic" + onClick={ onImageSettingsButtonPressed } + /> + ); + const showSpinner = this.state.isUploadInProgress; const opacity = this.state.isUploadInProgress ? 0.3 : 1; const progress = this.state.progress * 100; @@ -163,6 +175,9 @@ export default class ImageEdit extends React.Component { <BlockControls> { toolbarEditButton } </BlockControls> + <InspectorControls> + { inlineToolbarButtons } + </InspectorControls> <ImageSize src={ url } > { ( sizes ) => { const { diff --git a/packages/editor/src/components/index.native.js b/packages/editor/src/components/index.native.js index d226b4fe46480..07bd4c9993e6d 100644 --- a/packages/editor/src/components/index.native.js +++ b/packages/editor/src/components/index.native.js @@ -10,3 +10,4 @@ export { default as DefaultBlockAppender } from './default-block-appender'; export { default as PostTitle } from './post-title'; export { default as EditorHistoryRedo } from './editor-history/redo'; export { default as EditorHistoryUndo } from './editor-history/undo'; +export { default as InspectorControls } from './inspector-controls'; From b9e9aedda252e91bcecf3a34a19cf8af79bbdef2 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Wed, 30 Jan 2019 15:38:10 -0500 Subject: [PATCH 304/691] eslint-plugin: Add rule `no-unused-vars-before-return` (#12828) * scripts: Bump ESLint dependency to 5.10.x * eslint-plugin: Add rule `no-unused-vars-before-return` * eslint-plugin: Rephrase return-unused message for declarator * Update error message in the unit test to reflect code changes * Scripts: Include Migration Guide link for ESLint major bump CHANGELOG note --- package-lock.json | 384 ++++++++++-------- packages/eslint-plugin/CHANGELOG.md | 4 + packages/eslint-plugin/README.md | 12 +- packages/eslint-plugin/configs/custom.js | 4 + .../rules/no-unused-vars-before-return.md | 31 ++ packages/eslint-plugin/index.js | 1 + .../__tests__/no-unused-vars-before-return.js | 45 ++ packages/eslint-plugin/rules/index.js | 1 + .../rules/no-unused-vars-before-return.js | 56 +++ packages/scripts/CHANGELOG.md | 6 +- packages/scripts/package.json | 2 +- 11 files changed, 364 insertions(+), 182 deletions(-) create mode 100644 packages/eslint-plugin/docs/rules/no-unused-vars-before-return.md create mode 100644 packages/eslint-plugin/rules/__tests__/no-unused-vars-before-return.js create mode 100644 packages/eslint-plugin/rules/index.js create mode 100644 packages/eslint-plugin/rules/no-unused-vars-before-return.js diff --git a/package-lock.json b/package-lock.json index 30246ca516f38..83491b6775a23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2782,7 +2782,7 @@ "chalk": "^2.4.1", "check-node-version": "^3.1.1", "cross-spawn": "^5.1.0", - "eslint": "^4.19.1", + "eslint": "^5.12.1", "jest": "^23.6.0", "jest-puppeteer": "3.2.1", "npm-package-json-lint": "^3.3.1", @@ -4331,23 +4331,6 @@ "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", "dev": true }, - "caller-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", - "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", - "dev": true, - "requires": { - "callsites": "^0.2.0" - }, - "dependencies": { - "callsites": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", - "dev": true - } - } - }, "callsites": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", @@ -4475,9 +4458,9 @@ "dev": true }, "chardet": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", - "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, "check-node-version": { @@ -6280,21 +6263,6 @@ "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", "dev": true }, - "del": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", - "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", - "dev": true, - "requires": { - "globby": "^5.0.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "rimraf": "^2.2.8" - } - }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -6807,76 +6775,146 @@ } }, "eslint": { - "version": "4.19.1", - "resolved": "http://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", - "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==", + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.12.1.tgz", + "integrity": "sha512-54NV+JkTpTu0d8+UYSA8mMKAG4XAsaOrozA9rCW7tgneg1mevcL7wIotPC+fZ0SkWwdhNqoXoxnQCTBp7UvTsg==", "dev": true, "requires": { - "ajv": "^5.3.0", - "babel-code-frame": "^6.22.0", + "@babel/code-frame": "^7.0.0", + "ajv": "^6.5.3", "chalk": "^2.1.0", - "concat-stream": "^1.6.0", - "cross-spawn": "^5.1.0", - "debug": "^3.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", "doctrine": "^2.1.0", - "eslint-scope": "^3.7.1", + "eslint-scope": "^4.0.0", + "eslint-utils": "^1.3.1", "eslint-visitor-keys": "^1.0.0", - "espree": "^3.5.4", - "esquery": "^1.0.0", + "espree": "^5.0.0", + "esquery": "^1.0.1", "esutils": "^2.0.2", "file-entry-cache": "^2.0.0", "functional-red-black-tree": "^1.0.1", "glob": "^7.1.2", - "globals": "^11.0.1", - "ignore": "^3.3.3", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", - "inquirer": "^3.0.6", - "is-resolvable": "^1.0.0", - "js-yaml": "^3.9.1", + "inquirer": "^6.1.0", + "js-yaml": "^3.12.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.3.0", - "lodash": "^4.17.4", - "minimatch": "^3.0.2", + "lodash": "^4.17.5", + "minimatch": "^3.0.4", "mkdirp": "^0.5.1", "natural-compare": "^1.4.0", "optionator": "^0.8.2", "path-is-inside": "^1.0.2", "pluralize": "^7.0.0", "progress": "^2.0.0", - "regexpp": "^1.0.1", - "require-uncached": "^1.0.3", - "semver": "^5.3.0", + "regexpp": "^2.0.1", + "semver": "^5.5.1", "strip-ansi": "^4.0.0", - "strip-json-comments": "~2.0.1", - "table": "4.0.2", - "text-table": "~0.2.0" + "strip-json-comments": "^2.0.1", + "table": "^5.0.2", + "text-table": "^0.2.0" }, "dependencies": { - "ajv-keywords": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", - "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", + "acorn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.5.tgz", + "integrity": "sha512-i33Zgp3XWtmZBMNvCr4azvOFeWVw1Rk6p3hfi3LUDvIFraOMywb1kAtrbi+med14m4Xfpqm3zRZMT+c0FNE7kg==", "dev": true }, - "regexpp": { - "version": "1.1.0", - "resolved": "http://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", - "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==", + "acorn-jsx": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz", + "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==", "dev": true }, - "table": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", - "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", + "ajv": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.7.0.tgz", + "integrity": "sha512-RZXPviBTtfmtka9n9sy1N5M5b82CbxWIR6HIis4s3WQTXDJamc/0gpCWNGz6EWdWp4DOfjzJfhz/AS9zVPjjWg==", "dev": true, "requires": { - "ajv": "^5.2.3", - "ajv-keywords": "^2.1.0", - "chalk": "^2.1.0", - "lodash": "^4.17.4", - "slice-ansi": "1.0.0", - "string-width": "^2.1.1" + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" } + }, + "eslint-scope": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", + "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "espree": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.0.tgz", + "integrity": "sha512-1MpUfwsdS9MMoN7ZXqAr9e9UKdVHDcvrJpyx7mm1WuQlx/ygErEQBzgi5Nh5qBHIoYweprhtMkTCb9GhcAIcsA==", + "dev": true, + "requires": { + "acorn": "^6.0.2", + "acorn-jsx": "^5.0.0", + "eslint-visitor-keys": "^1.0.0" + } + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true } } }, @@ -6962,6 +7000,12 @@ "estraverse": "^4.1.1" } }, + "eslint-utils": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", + "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", + "dev": true + }, "eslint-visitor-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", @@ -7414,14 +7458,25 @@ } }, "external-editor": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", - "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", + "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", "dev": true, "requires": { - "chardet": "^0.4.0", - "iconv-lite": "^0.4.17", + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", "tmp": "^0.0.33" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } } }, "extglob": { @@ -7879,14 +7934,14 @@ } }, "flat-cache": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", - "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", + "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", "dev": true, "requires": { "circular-json": "^0.3.1", - "del": "^2.0.2", "graceful-fs": "^4.1.2", + "rimraf": "~2.6.2", "write": "^0.2.1" } }, @@ -9088,20 +9143,6 @@ "integrity": "sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==", "dev": true }, - "globby": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", - "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, "globjoin": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", @@ -9537,6 +9578,24 @@ "minimatch": "^3.0.4" } }, + "import-fresh": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.0.0.tgz", + "integrity": "sha512-pOnA9tfM3Uwics+SaBLCNyZZZbK+4PTu0OPZtLlMIrv17EdBoC15S9Kn8ckJ9TZTyKb3ywNE5y1yeDxxGA7nTQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + } + } + }, "import-lazy": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-3.1.0.tgz", @@ -9621,25 +9680,41 @@ } }, "inquirer": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", - "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.1.tgz", + "integrity": "sha512-088kl3DRT2dLU5riVMKKr1DlImd6X7smDhpXUCkJDCKvTEJeRiXh0G132HG9u5a+6Ylw9plFRY7RuTnwohYSpg==", "dev": true, "requires": { "ansi-escapes": "^3.0.0", "chalk": "^2.0.0", "cli-cursor": "^2.1.0", "cli-width": "^2.0.0", - "external-editor": "^2.0.4", + "external-editor": "^3.0.0", "figures": "^2.0.0", - "lodash": "^4.3.0", + "lodash": "^4.17.10", "mute-stream": "0.0.7", "run-async": "^2.2.0", - "rx-lite": "^4.0.8", - "rx-lite-aggregates": "^4.0.8", + "rxjs": "^6.1.0", "string-width": "^2.1.0", - "strip-ansi": "^4.0.0", + "strip-ansi": "^5.0.0", "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", + "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", + "dev": true + }, + "strip-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.0.0.tgz", + "integrity": "sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow==", + "dev": true, + "requires": { + "ansi-regex": "^4.0.0" + } + } } }, "interpret": { @@ -9942,32 +10017,6 @@ "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", "dev": true }, - "is-path-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", - "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", - "dev": true - }, - "is-path-in-cwd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", - "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", - "dev": true, - "requires": { - "is-path-inside": "^1.0.0" - }, - "dependencies": { - "is-path-inside": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", - "dev": true, - "requires": { - "path-is-inside": "^1.0.1" - } - } - } - }, "is-path-inside": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.0.0.tgz", @@ -14564,6 +14613,23 @@ "readable-stream": "^2.1.5" } }, + "parent-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.0.tgz", + "integrity": "sha512-8Mf5juOMmiE4FcmzYc4IaiS9L3+9paz2KOiXzkRviCP6aDmN49Hz6EMWz0lGNp9pX80GvvAuLADtyGfW/Em3TA==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + }, + "dependencies": { + "callsites": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.0.0.tgz", + "integrity": "sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw==", + "dev": true + } + } + }, "parse-asn1": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", @@ -17356,6 +17422,12 @@ "safe-regex": "^1.1.0" } }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, "regexpu-core": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.2.0.tgz", @@ -17556,24 +17628,6 @@ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" }, - "require-uncached": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", - "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", - "dev": true, - "requires": { - "caller-path": "^0.1.0", - "resolve-from": "^1.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", - "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", - "dev": true - } - } - }, "requireindex": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", @@ -17771,21 +17825,6 @@ "integrity": "sha1-FPlQpCF9fjXapxu8vljv9o6ksrc=", "dev": true }, - "rx-lite": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", - "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", - "dev": true - }, - "rx-lite-aggregates": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", - "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", - "dev": true, - "requires": { - "rx-lite": "*" - } - }, "rxjs": { "version": "6.3.3", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", @@ -18280,15 +18319,6 @@ "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", "dev": true }, - "slice-ansi": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", - "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0" - } - }, "slide": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 0ab0b2624c58d..775abcac66305 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -4,6 +4,10 @@ - The `esnext` and `recommended` rulesets now enforce [`object-shorthand`](https://eslint.org/docs/rules/object-shorthand) +### New Features + +- New Rule: [`@wordpress/no-unused-vars-before-return`](https://github.com/WordPress/gutenberg/blob/master/packages/eslint-plugin/docs/rules/no-unused-vars-before-return.md) + ## 1.0.0 (2018-12-12) ### New Features diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 3463e97d84024..1f001411b7319 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -10,7 +10,7 @@ Install the module npm install @wordpress/eslint-plugin --save-dev ``` -### Usage +## Usage To opt-in to the default configuration, extend your own project's `.eslintrc` file: @@ -24,7 +24,7 @@ Refer to the [ESLint documentation on Shareable Configs](http://eslint.org/docs/ The `recommended` preset will include rules governing an ES2015+ environment, and includes rules from the [`eslint-plugin-jsx-a11y`](https://github.com/evcohen/eslint-plugin-jsx-a11y) and [`eslint-plugin-react`](https://github.com/yannickcr/eslint-plugin-react) projects. -#### Rulesets +### Rulesets Alternatively, you can opt-in to only the more granular rulesets offered by the plugin. These include: @@ -45,7 +45,13 @@ These rules can be used additively, so you could extend both `esnext` and `custo The granular rulesets will not define any environment globals. As such, if they are required for your project, you will need to define them yourself. -#### Legacy +### Rules + +Rule|Description +---|--- +[no-unused-vars-before-return](docs/rules/no-unused-vars-before-return.md)|Disallow assigning variable values if unused before a return + +### Legacy If you are using WordPress' `.jshintrc` JSHint configuration and you would like to take the first step to migrate to an ESLint equivalent it is also possible to define your own project's `.eslintrc` file as: diff --git a/packages/eslint-plugin/configs/custom.js b/packages/eslint-plugin/configs/custom.js index 0318c85c324c9..5691c3c5d0c2b 100644 --- a/packages/eslint-plugin/configs/custom.js +++ b/packages/eslint-plugin/configs/custom.js @@ -1,5 +1,9 @@ module.exports = { + plugins: [ + '@wordpress', + ], rules: { + '@wordpress/no-unused-vars-before-return': 'error', 'no-restricted-syntax': [ 'error', { diff --git a/packages/eslint-plugin/docs/rules/no-unused-vars-before-return.md b/packages/eslint-plugin/docs/rules/no-unused-vars-before-return.md new file mode 100644 index 0000000000000..7dba90d405973 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-unused-vars-before-return.md @@ -0,0 +1,31 @@ +# Avoid assigning variable values if unused before a return (no-unused-vars-before-return) + +To avoid wastefully computing the result of a function call when assigning a variable value, the variable should be declared as late as possible. In practice, if a function includes an early return path, any variable declared prior to the `return` must be referenced at least once. Otherwise, in the condition which satisfies that return path, the declared variable is effectively considered dead code. This can have a performance impact when the logic performed in assigning the value is non-trivial. + +## Rule details + +The following patterns are considered warnings: + +```js +function example( number ) { + const foo = doSomeCostlyOperation(); + if ( number > 10 ) { + return number + 1; + } + + return number + foo; +} +``` + +The following patterns are not considered warnings: + +```js +function example( number ) { + if ( number > 10 ) { + return number + 1; + } + + const foo = doSomeCostlyOperation(); + return number + foo; +} +``` diff --git a/packages/eslint-plugin/index.js b/packages/eslint-plugin/index.js index 0933aba1cc826..fcba80b48203a 100644 --- a/packages/eslint-plugin/index.js +++ b/packages/eslint-plugin/index.js @@ -1,3 +1,4 @@ module.exports = { configs: require( './configs' ), + rules: require( './rules' ), }; diff --git a/packages/eslint-plugin/rules/__tests__/no-unused-vars-before-return.js b/packages/eslint-plugin/rules/__tests__/no-unused-vars-before-return.js new file mode 100644 index 0000000000000..394b88728954d --- /dev/null +++ b/packages/eslint-plugin/rules/__tests__/no-unused-vars-before-return.js @@ -0,0 +1,45 @@ +/** + * External dependencies + */ +import { RuleTester } from 'eslint'; + +/** + * Internal dependencies + */ +import rule from '../no-unused-vars-before-return'; + +const ruleTester = new RuleTester( { + parserOptions: { + ecmaVersion: 6, + }, +} ); + +ruleTester.run( 'no-unused-vars-before-return', rule, { + valid: [ + { + code: ` +function example( number ) { + if ( number > 10 ) { + return number + 1; + } + + const foo = doSomeCostlyOperation(); + return number + foo; +}`, + }, + ], + invalid: [ + { + code: ` +function example( number ) { + const foo = doSomeCostlyOperation(); + if ( number > 10 ) { + return number + 1; + } + + return number + foo; +}`, + errors: [ { message: 'Variables should not be assigned until just prior its first reference. An early return statement may leave this variable unused.' } ], + }, + ], +} ); diff --git a/packages/eslint-plugin/rules/index.js b/packages/eslint-plugin/rules/index.js new file mode 100644 index 0000000000000..035c09a8fa767 --- /dev/null +++ b/packages/eslint-plugin/rules/index.js @@ -0,0 +1 @@ +module.exports = require( 'requireindex' )( __dirname ); diff --git a/packages/eslint-plugin/rules/no-unused-vars-before-return.js b/packages/eslint-plugin/rules/no-unused-vars-before-return.js new file mode 100644 index 0000000000000..012b7172bdbb0 --- /dev/null +++ b/packages/eslint-plugin/rules/no-unused-vars-before-return.js @@ -0,0 +1,56 @@ +module.exports = { + meta: { + type: 'problem', + schema: [], + }, + create( context ) { + return { + ReturnStatement( node ) { + let functionScope = context.getScope(); + while ( functionScope.type !== 'function' && functionScope.upper ) { + functionScope = functionScope.upper; + } + + if ( ! functionScope ) { + return; + } + + for ( const variable of functionScope.variables ) { + const declaratorCandidate = variable.defs.find( ( def ) => { + return ( + def.node.type === 'VariableDeclarator' && + // Allow declarations which are not initialized. + def.node.init && + // Target function calls as "expensive". + def.node.init.type === 'CallExpression' && + // Allow unused if part of an object destructuring. + def.node.id.type !== 'ObjectPattern' && + // Only target assignments preceding `return`. + def.node.end < node.end + ); + } ); + + if ( ! declaratorCandidate ) { + continue; + } + + // The first entry in `references` is the declaration + // itself, which can be ignored. + const isUsedBeforeReturn = variable.references.slice( 1 ).some( ( reference ) => { + return reference.identifier.end < node.end; + } ); + + if ( isUsedBeforeReturn ) { + continue; + } + + context.report( + declaratorCandidate.node, + 'Variables should not be assigned until just prior its first reference. ' + + 'An early return statement may leave this variable unused.' + ); + } + }, + }; + }, +}; diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index 9e297b5b652d2..fe24a86900ee0 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -1,4 +1,8 @@ -## 2.6.0 (Unreleased) +## 3.0.0 (Unreleased) + +### Breaking Changes + +- The bundled `eslint` dependency has been updated from requiring `^4.19.1` to requiring `^5.12.1` (see [Migration Guide](https://eslint.org/docs/user-guide/migrating-to-5.0.0)). ### New Features diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 86d4211b874d5..8aaf6e8ee7ac6 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -38,7 +38,7 @@ "chalk": "^2.4.1", "check-node-version": "^3.1.1", "cross-spawn": "^5.1.0", - "eslint": "^4.19.1", + "eslint": "^5.12.1", "jest": "^23.6.0", "jest-puppeteer": "3.2.1", "npm-package-json-lint": "^3.3.1", From 3fd221845bfb6b3c553368b8b3cfbcc29452932f Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Wed, 30 Jan 2019 17:10:48 -0500 Subject: [PATCH 305/691] Plugin: Remove `user_can_richedit` filtering (#13608) --- lib/client-assets.php | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/lib/client-assets.php b/lib/client-assets.php index 469310edbe118..96986825e9bc2 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -893,13 +893,6 @@ function gutenberg_editor_scripts_and_styles( $hook ) { 'after' ); - // Ignore Classic Editor's `rich_editing` user option, aka "Disable visual - // editor". Forcing this to be true guarantees that TinyMCE and its plugins - // are available in Gutenberg. Fixes - // https://github.com/WordPress/gutenberg/issues/5667. - $user_can_richedit = user_can_richedit(); - add_filter( 'user_can_richedit', '__return_true' ); - wp_enqueue_script( 'wp-edit-post' ); wp_enqueue_script( 'wp-format-library' ); wp_enqueue_style( 'wp-format-library' ); @@ -1134,7 +1127,7 @@ function gutenberg_editor_scripts_and_styles( $hook ) { 'allowedMimeTypes' => get_allowed_mime_types(), 'styles' => $styles, 'imageSizes' => gutenberg_get_available_image_sizes(), - 'richEditingEnabled' => $user_can_richedit, + 'richEditingEnabled' => user_can_richedit(), // Ideally, we'd remove this and rely on a REST API endpoint. 'postLock' => $lock_details, From 6e4e9aa3f92ec6e568084630c295f020da9bb13e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Thu, 31 Jan 2019 11:24:11 +0100 Subject: [PATCH 306/691] Docs: Add clarification about git workflow (#13534) * Docs: Add clarification about git workflow * Update git-workflow.md * Update git-workflow.md * Reword paragraph on merging vs. rebasing * Add references * Add fork section * Clarify link to section "Perform a rebase" * Apply suggestions from code review Co-Authored-By: gziolo <grzegorz@gziolo.pl> --- docs/contributors/git-workflow.md | 44 ++++++++++++++++++++++ docs/contributors/repository-management.md | 2 +- docs/manifest.json | 18 ++++++--- docs/toc.json | 3 +- 4 files changed, 59 insertions(+), 8 deletions(-) create mode 100644 docs/contributors/git-workflow.md diff --git a/docs/contributors/git-workflow.md b/docs/contributors/git-workflow.md new file mode 100644 index 0000000000000..f159b46126e61 --- /dev/null +++ b/docs/contributors/git-workflow.md @@ -0,0 +1,44 @@ +# Git Workflow + +## Keeping Your Branch Up To Date + +When many different people are working on a project simultaneously, pull requests can go stale quickly. A "stale" pull request is one that is no longer up to date with the main line of development, and it needs to be updated before it can be merged into the project. + +There are two ways to do this: merging and rebasing. In Gutenberg, the recommendation is to rebase. Rebasing means rewriting your changes as if they're happening on top of the main line of development. This ensures the commit history is always clean and linear. Rebasing can be performed as many times as needed while you're working on a pull request. **Do share your work early on** by opening a pull request and keeping your history rebase as you progress. + +The main line of development is known as the `master` branch. If you have a pull-request branch that cannot be merged into `master` due to a conflict (this can happen for long-running pull requests), then in the course of rebasing you'll have to manually resolve any conflicts in your local copy. Learn more in [section _Perform a rebase_](https://github.com/edx/edx-platform/wiki/How-to-Rebase-a-Pull-Request#perform-a-rebase) of _How to Rebase a Pull Request_. + +Once you have resolved any conflicts locally you can update the pull request with `git push --force-with-lease`. Using the `--force-with-lease` parameter is important to guarantee that you don't accidentally overwrite someone else's work. + +To sum it up, you need to fetch any new changes in the repository, rebase your branch on top of `master`, and push the result back to the repository. These are the corresponding commands: + +```sh +git fetch +git rebase master +git push --force-with-lease your-branch-name +``` + +## Keeping Your Fork Up To Date + +Working on pull request starts with forking the Gutenberg repository, your separate working copy. Which can easily go out of sync as new pull requests are merged into the main repository. Here your working repository is a `fork` and the main Gutenberg repository is `upstream`. When working on new pull request you should always update your fork before you do `git checkout -b my-new-branch` to work on a feature or fix. + +To sync your fork you need to fetch the upstream changes and merge them into your fork. These are the corresponding commands: + +``` sh +git fetch upstream +git checkout master +git merge upstream/master +``` + +This will update you local copy to update your fork on github push your changes + +``` +git push +``` + +The above commands will update your `master` branch from _upstream_. To update any other branch replace `master` with the respective branch name. + + +## References +- https://git-scm.com/book/en/v2 +- https://help.github.com/categories/collaborating-with-issues-and-pull-requests/ diff --git a/docs/contributors/repository-management.md b/docs/contributors/repository-management.md index 08b47e1777f2a..7eaa1cb28661e 100644 --- a/docs/contributors/repository-management.md +++ b/docs/contributors/repository-management.md @@ -83,7 +83,6 @@ When reviewing issues, here are some steps you can perform: Gutenberg follows a feature branch pull request workflow for all code and documentation changes. At a high-level, the process looks like this: - 1. Check out a new feature branch locally. 2. Make your changes, testing thoroughly. 3. Commit your changes when you’re happy with them, and push the branch. @@ -127,6 +126,7 @@ A pull request can generally be merged once it is: - Vetted against all potential edge cases. - Changelog entries were properly added. - Reviewed by someone other than the original author. +- [Rebased](/docs/contributors/git-workflow.md#keeping-your-branch-up-to-date) onto the latest version of the master branch. The final pull request merge decision is made by the **@wordpress/gutenberg-core** team. diff --git a/docs/manifest.json b/docs/manifest.json index 35964c417ab5e..6e6dbd2e40251 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -413,18 +413,24 @@ "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/coding-guidelines.md", "parent": "develop" }, - { - "title": "Block Grammar", - "slug": "grammar", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/grammar.md", - "parent": "develop" - }, { "title": "Testing Overview", "slug": "testing-overview", "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/testing-overview.md", "parent": "develop" }, + { + "title": "Git Workflow", + "slug": "git-workflow", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/git-workflow.md", + "parent": "develop" + }, + { + "title": "Block Grammar", + "slug": "grammar", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/grammar.md", + "parent": "develop" + }, { "title": "Scripts", "slug": "scripts", diff --git a/docs/toc.json b/docs/toc.json index 4e06b65d552ce..14bed66d0a75e 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -82,8 +82,9 @@ ]}, {"docs/contributors/develop.md": [ {"docs/contributors/coding-guidelines.md": []}, - {"docs/contributors/grammar.md": []}, {"docs/contributors/testing-overview.md": []}, + {"docs/contributors/git-workflow.md": []}, + {"docs/contributors/grammar.md": []}, {"docs/contributors/scripts.md": []}, {"docs/contributors/release.md": []} ]}, From a8dcfb242b3765e0e0a3d615b3cc6b204fd92b32 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Thu, 31 Jan 2019 12:29:05 +0000 Subject: [PATCH 307/691] Fix: Categories Block: hierarchical Dropdown (#13567) Previously higher than second levels categories here rendered multiple times on the categories block, when "Show Hierarchy" and "Display as Dropdown" options here true. This commit fixes that problem. --- packages/block-library/src/categories/edit.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/categories/edit.js b/packages/block-library/src/categories/edit.js index ad1dea3ec55bf..aee240e7384e6 100644 --- a/packages/block-library/src/categories/edit.js +++ b/packages/block-library/src/categories/edit.js @@ -106,7 +106,8 @@ class CategoriesEdit extends Component { } renderCategoryDropdown() { - const { showHierarchy, instanceId } = this.props; + const { instanceId } = this.props; + const { showHierarchy } = this.props.attributes; const parentId = showHierarchy ? 0 : null; const categories = this.getCategories( parentId ); const selectId = `blocks-category-select-${ instanceId }`; From 7fd6316997acca945899e8bd2fe2e8a5b4e6427b Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Thu, 31 Jan 2019 12:31:53 +0000 Subject: [PATCH 308/691] Add: className prop support to server side render. (#13568) --- .../src/server-side-render/README.md | 30 +++++++++++++++++++ .../src/server-side-render/index.js | 26 +++++++++++++--- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/packages/components/src/server-side-render/README.md b/packages/components/src/server-side-render/README.md index 55b0be7630dab..df9d75dec8fa4 100644 --- a/packages/components/src/server-side-render/README.md +++ b/packages/components/src/server-side-render/README.md @@ -8,6 +8,36 @@ ServerSideRender should be regarded as a fallback or legacy mechanism, it is not New blocks should be built in conjunction with any necessary REST API endpoints, so that JavaScript can be used for rendering client-side in the `edit` function. This gives the best user experience, instead of relying on using the PHP `render_callback`. The logic necessary for rendering should be included in the endpoint, so that both the client-side JavaScript and server-side PHP logic should require a minimal amount of differences. +## Props + +### attributes + +An object containing the attributes of the block to be server-side rendered. +E.g: `{ displayAsDropdown: true }`, `{ showHierarchy: true }`, etc... +- Type: `Object` +- Required: No + +### block + +The identifier of the block to be server-side rendered. +Examples: "core/archives", "core/latest-comments", "core/rss", etc... +- Type: `String` +- Required: Yes + +### className + +A class added to the DOM element that wraps the server side rendered block. +Examples: "my-custom-server-side-rendered". +- Type: `String` +- Required: No + +### urlQueryArgs + +Query arguments to apply to the request URL. +E.g: `{ post_id: 12 }`. +- Type: `Object` +- Required: No + ## Usage Render core/archives preview. diff --git a/packages/components/src/server-side-render/index.js b/packages/components/src/server-side-render/index.js index ed11eac1c518b..71d649dfbf644 100644 --- a/packages/components/src/server-side-render/index.js +++ b/packages/components/src/server-side-render/index.js @@ -85,24 +85,42 @@ export class ServerSideRender extends Component { render() { const response = this.state.response; + const { className } = this.props; if ( ! response ) { return ( - <Placeholder><Spinner /></Placeholder> + <Placeholder + className={ className } + > + <Spinner /> + </Placeholder> ); } else if ( response.error ) { // translators: %s: error message describing the problem const errorMessage = sprintf( __( 'Error loading block: %s' ), response.errorMsg ); return ( - <Placeholder>{ errorMessage }</Placeholder> + <Placeholder + className={ className } + > + { errorMessage } + </Placeholder> ); } else if ( ! response.length ) { return ( - <Placeholder>{ __( 'No results found.' ) }</Placeholder> + <Placeholder + className={ className } + > + { __( 'No results found.' ) } + </Placeholder> ); } return ( - <RawHTML key="html">{ response }</RawHTML> + <RawHTML + key="html" + className={ className } + > + { response } + </RawHTML> ); } } From bebda205b2039b39457aa3d6f20e275789ba0b57 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Thu, 31 Jan 2019 16:17:13 +0000 Subject: [PATCH 309/691] Make clickOnMoreMenuItem not dependent on aria labels (#13166) The clickOnMoreMenuItem end 2 end test util was dependent on aria labels being present on each menu item. This aria labels are redundant and just duplicate the inner text menu item, they will be removed as soon as https://github.com/WordPress/gutenberg/pull/12955 is merged. This commit updates an end 2 end test util that relied on this aria labels being present. This way we will make sure tests pass on https://github.com/WordPress/gutenberg/pull/12955 as soon as this PR is merged. ## How has this been tested? I verified the end 2 end tests pass. I reverted all snapshot updates in https://github.com/WordPress/gutenberg/pull/12955 and merge that PR with this and I checked the end 2 end tests continue to pass. --- .../src/click-on-more-menu-item.js | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/e2e-test-utils/src/click-on-more-menu-item.js b/packages/e2e-test-utils/src/click-on-more-menu-item.js index a71cf13f1e12c..0465441ec6496 100644 --- a/packages/e2e-test-utils/src/click-on-more-menu-item.js +++ b/packages/e2e-test-utils/src/click-on-more-menu-item.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import { first } from 'lodash'; + /** * Clicks on More Menu item, searches for the button with the text provided and clicks it. * @@ -7,7 +12,18 @@ export async function clickOnMoreMenuItem( buttonLabel ) { await expect( page ).toClick( '.edit-post-more-menu [aria-label="Show more tools & options"]' ); - await page.click( - `.edit-post-more-menu__content button[aria-label="${ buttonLabel }"]` - ); + const moreMenuContainerSelector = + '//*[contains(concat(" ", @class, " "), " edit-post-more-menu__content ")]'; + let elementToClick = first( await page.$x( + `${ moreMenuContainerSelector }//button[contains(text(), "${ buttonLabel }")]` + ) ); + // If button is not found, the label should be on the info wrapper. + if ( ! elementToClick ) { + elementToClick = first( await page.$x( + moreMenuContainerSelector + + '//button' + + `/*[contains(concat(" ", @class, " "), " components-menu-item__info-wrapper ")][contains(text(), "${ buttonLabel }")]` + ) ); + } + await elementToClick.click(); } From 22aba0d13e92892086b9e4bca1c31725e059bcd8 Mon Sep 17 00:00:00 2001 From: Marko Savic <savicmarko1985@gmail.com> Date: Thu, 31 Jan 2019 17:48:19 +0100 Subject: [PATCH 310/691] Rnmobile/upload media failed state (#13615) *Implement upload media failed state --- .../block-library/src/image/edit.native.js | 155 +++++++++++------- .../src/image/styles.native.scss | 17 ++ 2 files changed, 111 insertions(+), 61 deletions(-) create mode 100644 packages/block-library/src/image/styles.native.scss diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index 7413978f2e012..fe3fa7c54d2e1 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -2,27 +2,31 @@ * External dependencies */ import React from 'react'; -import { View, Image, TextInput } from 'react-native'; +import { View, ImageBackground, TextInput, Text, TouchableWithoutFeedback } from 'react-native'; import { subscribeMediaUpload, requestMediaPickFromMediaLibrary, requestMediaPickFromDeviceLibrary, requestMediaPickFromDeviceCamera, mediaUploadSync, + requestImageFailedRetryDialog, + requestImageUploadCancelDialog, } from 'react-native-gutenberg-bridge'; /** * Internal dependencies */ import { MediaPlaceholder, RichText, BlockControls, InspectorControls } from '@wordpress/editor'; -import { Toolbar, ToolbarButton, Spinner } from '@wordpress/components'; +import { Toolbar, ToolbarButton, Spinner, Dashicon } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import ImageSize from './image-size'; import { isURL } from '@wordpress/url'; +import styles from './styles.scss'; -const MEDIA_ULOAD_STATE_UPLOADING = 1; -const MEDIA_ULOAD_STATE_SUCCEEDED = 2; -const MEDIA_ULOAD_STATE_FAILED = 3; +const MEDIA_UPLOAD_STATE_UPLOADING = 1; +const MEDIA_UPLOAD_STATE_SUCCEEDED = 2; +const MEDIA_UPLOAD_STATE_FAILED = 3; +const MEDIA_UPLOAD_STATE_RESET = 4; export default class ImageEdit extends React.Component { constructor( props ) { @@ -31,6 +35,7 @@ export default class ImageEdit extends React.Component { this.state = { progress: 0, isUploadInProgress: false, + isUploadFailed: false, }; this.mediaUpload = this.mediaUpload.bind( this ); @@ -38,6 +43,7 @@ export default class ImageEdit extends React.Component { this.removeMediaUploadListener = this.removeMediaUploadListener.bind( this ); this.finishMediaUploadWithSuccess = this.finishMediaUploadWithSuccess.bind( this ); this.finishMediaUploadWithFailure = this.finishMediaUploadWithFailure.bind( this ); + this.onImagePressed = this.onImagePressed.bind( this ); } componentDidMount() { @@ -53,6 +59,16 @@ export default class ImageEdit extends React.Component { this.removeMediaUploadListener(); } + onImagePressed() { + const { attributes } = this.props; + + if ( this.state.isUploadInProgress ) { + requestImageUploadCancelDialog( attributes.id ); + } else if ( attributes.id && ! isURL( attributes.url ) ) { + requestImageFailedRetryDialog( attributes.id ); + } + } + mediaUpload( payload ) { const { attributes } = this.props; @@ -61,15 +77,18 @@ export default class ImageEdit extends React.Component { } switch ( payload.state ) { - case MEDIA_ULOAD_STATE_UPLOADING: - this.setState( { progress: payload.progress, isUploadInProgress: true } ); + case MEDIA_UPLOAD_STATE_UPLOADING: + this.setState( { progress: payload.progress, isUploadInProgress: true, isUploadFailed: false } ); break; - case MEDIA_ULOAD_STATE_SUCCEEDED: + case MEDIA_UPLOAD_STATE_SUCCEEDED: this.finishMediaUploadWithSuccess( payload ); break; - case MEDIA_ULOAD_STATE_FAILED: + case MEDIA_UPLOAD_STATE_FAILED: this.finishMediaUploadWithFailure( payload ); break; + case MEDIA_UPLOAD_STATE_RESET: + this.mediaUploadStateReset( payload ); + break; } } @@ -85,10 +104,15 @@ export default class ImageEdit extends React.Component { finishMediaUploadWithFailure( payload ) { const { setAttributes } = this.props; - setAttributes( { url: payload.mediaUrl, id: payload.mediaId } ); - this.setState( { isUploadInProgress: false } ); + setAttributes( { id: payload.mediaId } ); + this.setState( { isUploadInProgress: false, isUploadFailed: true } ); + } - this.removeMediaUploadListener(); + mediaUploadStateReset( payload ) { + const { setAttributes } = this.props; + + setAttributes( { id: payload.mediaId, url: null } ); + this.setState( { isUploadInProgress: false, isUploadFailed: false } ); } addMediaUploadListener() { @@ -170,55 +194,64 @@ export default class ImageEdit extends React.Component { const progress = this.state.progress * 100; return ( - <View style={ { flex: 1 } }> - { showSpinner && <Spinner progress={ progress } /> } - <BlockControls> - { toolbarEditButton } - </BlockControls> - <InspectorControls> - { inlineToolbarButtons } - </InspectorControls> - <ImageSize src={ url } > - { ( sizes ) => { - const { - imageWidthWithinContainer, - imageHeightWithinContainer, - } = sizes; - - let finalHeight = imageHeightWithinContainer; - if ( height > 0 && height < imageHeightWithinContainer ) { - finalHeight = height; - } - - let finalWidth = imageWidthWithinContainer; - if ( width > 0 && width < imageWidthWithinContainer ) { - finalWidth = width; - } - - return ( - <View style={ { flex: 1 } } > - <Image - style={ { width: finalWidth, height: finalHeight, opacity } } - resizeMethod="scale" - source={ { uri: url } } - key={ url } - /> - </View> - ); - } } - </ImageSize> - { ( ! RichText.isEmpty( caption ) > 0 || isSelected ) && ( - <View style={ { padding: 12, flex: 1 } }> - <TextInput - style={ { textAlign: 'center' } } - underlineColorAndroid="transparent" - value={ caption } - placeholder={ __( 'Write caption…' ) } - onChangeText={ ( newCaption ) => setAttributes( { caption: newCaption } ) } - /> - </View> - ) } - </View> + <TouchableWithoutFeedback onPress={ this.onImagePressed } disabled={ ! isSelected }> + <View style={ { flex: 1 } }> + { showSpinner && <Spinner progress={ progress } /> } + <BlockControls> + { toolbarEditButton } + </BlockControls> + <InspectorControls> + { inlineToolbarButtons } + </InspectorControls> + <ImageSize src={ url } > + { ( sizes ) => { + const { + imageWidthWithinContainer, + imageHeightWithinContainer, + } = sizes; + + let finalHeight = imageHeightWithinContainer; + if ( height > 0 && height < imageHeightWithinContainer ) { + finalHeight = height; + } + + let finalWidth = imageWidthWithinContainer; + if ( width > 0 && width < imageWidthWithinContainer ) { + finalWidth = width; + } + + return ( + <View style={ { flex: 1 } } > + <ImageBackground + style={ { width: finalWidth, height: finalHeight, opacity } } + resizeMethod="scale" + source={ { uri: url } } + key={ url } + > + { this.state.isUploadFailed && + <View style={ styles.imageContainer } > + <Dashicon icon={ 'image-rotate' } ariaPressed={ 'dashicon-active' } /> + <Text style={ styles.uploadFailedText }>{ __( 'Failed to insert media.\nPlease tap for options.' ) }</Text> + </View> + } + </ImageBackground> + </View> + ); + } } + </ImageSize> + { ( ! RichText.isEmpty( caption ) > 0 || isSelected ) && ( + <View style={ { padding: 12, flex: 1 } }> + <TextInput + style={ { textAlign: 'center' } } + underlineColorAndroid="transparent" + value={ caption } + placeholder={ __( 'Write caption…' ) } + onChangeText={ ( newCaption ) => setAttributes( { caption: newCaption } ) } + /> + </View> + ) } + </View> + </TouchableWithoutFeedback> ); } } diff --git a/packages/block-library/src/image/styles.native.scss b/packages/block-library/src/image/styles.native.scss new file mode 100644 index 0000000000000..833b8126bfc1a --- /dev/null +++ b/packages/block-library/src/image/styles.native.scss @@ -0,0 +1,17 @@ +.imageContainer { + flex: 1; + justify-content: center; + align-items: center; +} + +.uploadFailedText { + color: #fff; + font-size: 14; + margin-top: 5; +} + +.uploadFailedContainer { + position: absolute; + flex-direction: column; + align-items: center; +} From 62f61631bef4b97be519773eb1b949a847dc493c Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Thu, 31 Jan 2019 13:53:07 -0500 Subject: [PATCH 311/691] Plugin: Deprecate gutenberg_add_admin_body_class (#13572) * Plugin: Deprecate gutenberg_add_admin_body_class * Plugin: Reference block-editor-page class for admin class removal --- .../backward-compatibility/deprecations.md | 1 + gutenberg.php | 12 ++++-------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/docs/designers-developers/developers/backward-compatibility/deprecations.md b/docs/designers-developers/developers/backward-compatibility/deprecations.md index 6ff124b27a34f..62752b2c54d6f 100644 --- a/docs/designers-developers/developers/backward-compatibility/deprecations.md +++ b/docs/designers-developers/developers/backward-compatibility/deprecations.md @@ -58,6 +58,7 @@ The Gutenberg project's deprecation policy is intended to support backward compa - The PHP function `gutenberg_collect_meta_box_data` has been removed. Use [`register_and_do_post_meta_boxes`](https://developer.wordpress.org/reference/functions/register_and_do_post_meta_boxes/) instead. - `window._wpLoadGutenbergEditor` has been removed. Use `window._wpLoadBlockEditor` instead. Note: This is a private API, not intended for public use. It may be removed in the future. - The PHP function `gutenberg_get_script_polyfill` has been removed. Use [`wp_get_script_polyfill`](https://developer.wordpress.org/reference/functions/wp_get_script_polyfill/) instead. +- The PHP function `gutenberg_add_admin_body_class` has been removed. Use the `.block-editor-page` class selector in your stylesheets if you need to scope styles to the block editor screen. ## 4.5.0 - `Dropdown.refresh()` has been deprecated as the contained `Popover` is now automatically refreshed. diff --git a/gutenberg.php b/gutenberg.php index 13f0e148b57de..4e0cf4bbc32cd 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -220,7 +220,6 @@ function gutenberg_init( $return, $post ) { add_action( 'admin_enqueue_scripts', 'gutenberg_editor_scripts_and_styles' ); add_filter( 'screen_options_show_screen', '__return_false' ); - add_filter( 'admin_body_class', 'gutenberg_add_admin_body_class' ); /* * Remove the emoji script as it is incompatible with both React and any @@ -299,18 +298,15 @@ function gutenberg_replace_default_add_new_button() { * Adds the block-editor-page class to the body tag on the Gutenberg page. * * @since 1.5.0 + * @deprecated 5.0.0 * * @param string $classes Space separated string of classes being added to the body tag. * @return string The $classes string, with block-editor-page appended. */ function gutenberg_add_admin_body_class( $classes ) { - // gutenberg-editor-page is left for backward compatibility. - if ( current_theme_supports( 'editor-styles' ) && current_theme_supports( 'dark-editor-style' ) ) { - return "$classes block-editor-page gutenberg-editor-page is-fullscreen-mode wp-embed-responsive is-dark-theme"; - } else { - // Default to is-fullscreen-mode to avoid jumps in the UI. - return "$classes block-editor-page gutenberg-editor-page is-fullscreen-mode wp-embed-responsive"; - } + _deprecated_function( __FUNCTION__, '5.0.0' ); + + return $classes; } /** From 21c4aacd7afbdf1354bc480a1318ab73386fd9a1 Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.p.richards@gmail.com> Date: Fri, 1 Feb 2019 14:13:41 +0800 Subject: [PATCH 312/691] Fix the editor save keyboard shortcut not working in code editor view (#13159) * Fix the editor save keyboard shortcut not working in code editor view * Refactor based on code review feedback - Introduce new TextEditorGlobalShortcuts component that implements the save shortcut - Rename EditorGlobalShortcuts to VisualEditorGlobalShortcuts - Add a deprecated version of EditorGlobalShortcuts - Update CHANGELOG --- packages/edit-post/CHANGELOG.md | 3 ++ .../src/components/text-editor/index.js | 7 ++- .../src/components/visual-editor/index.js | 4 +- packages/editor/CHANGELOG.md | 7 ++- .../components/block-settings-menu/index.js | 2 +- .../save-shortcut.js | 52 +++++++++++++++++++ .../text-editor-shortcuts.js | 10 ++++ .../visual-editor-shortcuts.js} | 48 +++++++---------- packages/editor/src/components/index.js | 6 ++- 9 files changed, 103 insertions(+), 36 deletions(-) create mode 100644 packages/editor/src/components/global-keyboard-shortcuts/save-shortcut.js create mode 100644 packages/editor/src/components/global-keyboard-shortcuts/text-editor-shortcuts.js rename packages/editor/src/components/{editor-global-keyboard-shortcuts/index.js => global-keyboard-shortcuts/visual-editor-shortcuts.js} (85%) diff --git a/packages/edit-post/CHANGELOG.md b/packages/edit-post/CHANGELOG.md index ee41b7e7fb9f1..db8c68da54229 100644 --- a/packages/edit-post/CHANGELOG.md +++ b/packages/edit-post/CHANGELOG.md @@ -4,6 +4,9 @@ * Expose the `className` property to style the `PluginSidebar` component. +### Bug Fixes + - Fix 'save' keyboard shortcut not functioning in the Code Editor. + ## 3.1.7 (2019-01-03) ## 3.1.6 (2018-12-18) diff --git a/packages/edit-post/src/components/text-editor/index.js b/packages/edit-post/src/components/text-editor/index.js index 019a19b2e518c..882e97dd7e83c 100644 --- a/packages/edit-post/src/components/text-editor/index.js +++ b/packages/edit-post/src/components/text-editor/index.js @@ -1,7 +1,11 @@ /** * WordPress dependencies */ -import { PostTextEditor, PostTitle } from '@wordpress/editor'; +import { + PostTextEditor, + PostTitle, + TextEditorGlobalKeyboardShortcuts, +} from '@wordpress/editor'; import { IconButton } from '@wordpress/components'; import { withDispatch, withSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; @@ -21,6 +25,7 @@ function TextEditor( { onExit, isRichEditingEnabled } ) { > { __( 'Exit Code Editor' ) } </IconButton> + <TextEditorGlobalKeyboardShortcuts /> </div> ) } <div className="edit-post-text-editor__body"> diff --git a/packages/edit-post/src/components/visual-editor/index.js b/packages/edit-post/src/components/visual-editor/index.js index 03dbcdb3f047b..a8cdc7e06f69d 100644 --- a/packages/edit-post/src/components/visual-editor/index.js +++ b/packages/edit-post/src/components/visual-editor/index.js @@ -7,7 +7,7 @@ import { PostTitle, WritingFlow, ObserveTyping, - EditorGlobalKeyboardShortcuts, + VisualEditorGlobalKeyboardShortcuts, BlockSelectionClearer, MultiSelectScrollIntoView, _BlockSettingsMenuFirstItem, @@ -23,7 +23,7 @@ import PluginBlockSettingsMenuGroup from '../block-settings-menu/plugin-block-se function VisualEditor() { return ( <BlockSelectionClearer className="edit-post-visual-editor editor-styles-wrapper"> - <EditorGlobalKeyboardShortcuts /> + <VisualEditorGlobalKeyboardShortcuts /> <CopyHandler /> <MultiSelectScrollIntoView /> <WritingFlow> diff --git a/packages/editor/CHANGELOG.md b/packages/editor/CHANGELOG.md index 30e57fd4f033f..c5365bd4b44df 100644 --- a/packages/editor/CHANGELOG.md +++ b/packages/editor/CHANGELOG.md @@ -1,8 +1,13 @@ ## 9.1.0 (Unreleased) -### New Feature +### New Features - Added `createCustomColorsHOC` for creating a higher order `withCustomColors` component. +- Added a new `TextEditorGlobalKeyboardShortcuts` component. + +### Deprecations + +- `EditorGlobalKeyboardShortcuts` has been deprecated in favor of `VisualEditorGlobalKeyboardShortcuts`. ### Bug Fixes diff --git a/packages/editor/src/components/block-settings-menu/index.js b/packages/editor/src/components/block-settings-menu/index.js index ec4bbd60db53f..8436276c8f99b 100644 --- a/packages/editor/src/components/block-settings-menu/index.js +++ b/packages/editor/src/components/block-settings-menu/index.js @@ -15,7 +15,7 @@ import { withDispatch } from '@wordpress/data'; /** * Internal dependencies */ -import { shortcuts } from '../editor-global-keyboard-shortcuts'; +import { shortcuts } from '../global-keyboard-shortcuts/visual-editor-shortcuts'; import BlockActions from '../block-actions'; import BlockModeToggle from './block-mode-toggle'; import ReusableBlockConvertButton from './reusable-block-convert-button'; diff --git a/packages/editor/src/components/global-keyboard-shortcuts/save-shortcut.js b/packages/editor/src/components/global-keyboard-shortcuts/save-shortcut.js new file mode 100644 index 0000000000000..c2a117ec6816f --- /dev/null +++ b/packages/editor/src/components/global-keyboard-shortcuts/save-shortcut.js @@ -0,0 +1,52 @@ +/** + * WordPress dependencies + */ +import { KeyboardShortcuts } from '@wordpress/components'; +import { rawShortcut } from '@wordpress/keycodes'; +import { compose } from '@wordpress/compose'; +import { withSelect, withDispatch } from '@wordpress/data'; + +export function SaveShortcut( { onSave } ) { + return ( + <KeyboardShortcuts + bindGlobal + shortcuts={ { + [ rawShortcut.primary( 's' ) ]: ( event ) => { + event.preventDefault(); + onSave(); + }, + } } + /> + ); +} + +export default compose( [ + withSelect( ( select ) => { + const { isEditedPostDirty } = select( 'core/editor' ); + + return { + isDirty: isEditedPostDirty(), + }; + } ), + withDispatch( ( dispatch, ownProps, { select } ) => { + const { + savePost, + } = dispatch( 'core/editor' ); + + return { + onSave() { + // TODO: This should be handled in the `savePost` effect in + // considering `isSaveable`. See note on `isEditedPostSaveable` + // selector about dirtiness and meta-boxes. + // + // See: `isEditedPostSaveable` + const { isEditedPostDirty } = select( 'core/editor' ); + if ( ! isEditedPostDirty() ) { + return; + } + + savePost(); + }, + }; + } ), +] )( SaveShortcut ); diff --git a/packages/editor/src/components/global-keyboard-shortcuts/text-editor-shortcuts.js b/packages/editor/src/components/global-keyboard-shortcuts/text-editor-shortcuts.js new file mode 100644 index 0000000000000..2d2d0510fd316 --- /dev/null +++ b/packages/editor/src/components/global-keyboard-shortcuts/text-editor-shortcuts.js @@ -0,0 +1,10 @@ +/** + * Internal dependencies + */ +import SaveShortcut from './save-shortcut'; + +export default function TextEditorGlobalKeyboardShortcuts() { + return ( + <SaveShortcut /> + ); +} diff --git a/packages/editor/src/components/editor-global-keyboard-shortcuts/index.js b/packages/editor/src/components/global-keyboard-shortcuts/visual-editor-shortcuts.js similarity index 85% rename from packages/editor/src/components/editor-global-keyboard-shortcuts/index.js rename to packages/editor/src/components/global-keyboard-shortcuts/visual-editor-shortcuts.js index ed2a4039755b1..9242734d0e9e4 100644 --- a/packages/editor/src/components/editor-global-keyboard-shortcuts/index.js +++ b/packages/editor/src/components/global-keyboard-shortcuts/visual-editor-shortcuts.js @@ -11,11 +11,13 @@ import { KeyboardShortcuts } from '@wordpress/components'; import { withSelect, withDispatch } from '@wordpress/data'; import { rawShortcut, displayShortcut } from '@wordpress/keycodes'; import { compose } from '@wordpress/compose'; +import deprecated from '@wordpress/deprecated'; /** * Internal dependencies */ import BlockActions from '../block-actions'; +import SaveShortcut from './save-shortcut'; const preventDefault = ( event ) => { event.preventDefault(); @@ -41,13 +43,12 @@ export const shortcuts = { }, }; -class EditorGlobalKeyboardShortcuts extends Component { +class VisualEditorGlobalKeyboardShortcuts extends Component { constructor() { super( ...arguments ); this.selectAll = this.selectAll.bind( this ); this.undoOrRedo = this.undoOrRedo.bind( this ); - this.save = this.save.bind( this ); this.deleteSelectedBlocks = this.deleteSelectedBlocks.bind( this ); this.clearMultiSelection = this.clearMultiSelection.bind( this ); } @@ -70,11 +71,6 @@ class EditorGlobalKeyboardShortcuts extends Component { event.preventDefault(); } - save( event ) { - event.preventDefault(); - this.props.onSave(); - } - deleteSelectedBlocks( event ) { const { selectedBlockClientIds, hasMultiSelection, onRemove, isLocked } = this.props; if ( hasMultiSelection ) { @@ -110,12 +106,7 @@ class EditorGlobalKeyboardShortcuts extends Component { escape: this.clearMultiSelection, } } /> - <KeyboardShortcuts - bindGlobal - shortcuts={ { - [ rawShortcut.primary( 's' ) ]: this.save, - } } - /> + <SaveShortcut /> { selectedBlockClientIds.length > 0 && ( <BlockActions clientIds={ selectedBlockClientIds }> { ( { onDuplicate, onRemove, onInsertAfter, onInsertBefore } ) => ( @@ -146,7 +137,7 @@ class EditorGlobalKeyboardShortcuts extends Component { } } -export default compose( [ +const EnhancedVisualEditorGlobalKeyboardShortcuts = compose( [ withSelect( ( select ) => { const { getBlockOrder, @@ -169,30 +160,16 @@ export default compose( [ selectedBlockClientIds, }; } ), - withDispatch( ( dispatch, ownProps, { select } ) => { + withDispatch( ( dispatch ) => { const { clearSelectedBlock, multiSelect, redo, undo, removeBlocks, - savePost, } = dispatch( 'core/editor' ); return { - onSave() { - // TODO: This should be handled in the `savePost` effect in - // considering `isSaveable`. See note on `isEditedPostSaveable` - // selector about dirtiness and meta-boxes. - // - // See: `isEditedPostSaveable` - const { isEditedPostDirty } = select( 'core/editor' ); - if ( ! isEditedPostDirty() ) { - return; - } - - savePost(); - }, clearSelectedBlock, onMultiSelect: multiSelect, onRedo: redo, @@ -200,4 +177,15 @@ export default compose( [ onRemove: removeBlocks, }; } ), -] )( EditorGlobalKeyboardShortcuts ); +] )( VisualEditorGlobalKeyboardShortcuts ); + +export default EnhancedVisualEditorGlobalKeyboardShortcuts; + +export function EditorGlobalKeyboardShortcuts() { + deprecated( 'EditorGlobalKeyboardShortcuts', { + alternative: 'VisualEditorGlobalKeyboardShortcuts', + plugin: 'Gutenberg', + } ); + + return <EnhancedVisualEditorGlobalKeyboardShortcuts />; +} diff --git a/packages/editor/src/components/index.js b/packages/editor/src/components/index.js index ebd0a59331198..b02da43dbfe57 100644 --- a/packages/editor/src/components/index.js +++ b/packages/editor/src/components/index.js @@ -36,7 +36,11 @@ export { default as URLPopover } from './url-popover'; export { default as AutosaveMonitor } from './autosave-monitor'; export { default as DocumentOutline } from './document-outline'; export { default as DocumentOutlineCheck } from './document-outline/check'; -export { default as EditorGlobalKeyboardShortcuts } from './editor-global-keyboard-shortcuts'; +export { + default as VisualEditorGlobalKeyboardShortcuts, + EditorGlobalKeyboardShortcuts, +} from './global-keyboard-shortcuts/visual-editor-shortcuts'; +export { default as TextEditorGlobalKeyboardShortcuts } from './global-keyboard-shortcuts/text-editor-shortcuts'; export { default as EditorHistoryRedo } from './editor-history/redo'; export { default as EditorHistoryUndo } from './editor-history/undo'; export { default as EditorNotices } from './editor-notices'; From 2009bdd34d4fee4592b8c460615aca420bebdd3b Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell@kjellr.com> Date: Fri, 1 Feb 2019 03:14:21 -0500 Subject: [PATCH 313/691] Increase bottom padding on gallery image caption (#13623) Resolves #8018 by increasing the bottom padding of gallery image captions by 4px. Also increases the coverage of our gradient background by 10% to help with text contrast. --- packages/block-library/src/gallery/style.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/gallery/style.scss b/packages/block-library/src/gallery/style.scss index a62465bb4f225..2557f727c5c0e 100644 --- a/packages/block-library/src/gallery/style.scss +++ b/packages/block-library/src/gallery/style.scss @@ -48,11 +48,11 @@ width: 100%; max-height: 100%; overflow: auto; - padding: 40px 10px 5px; + padding: 40px 10px 9px; color: $white; text-align: center; font-size: $default-font-size; - background: linear-gradient(0deg, rgba($color: $black, $alpha: 0.7) 0, rgba($color: $black, $alpha: 0.3) 60%, transparent); + background: linear-gradient(0deg, rgba($color: $black, $alpha: 0.7) 0, rgba($color: $black, $alpha: 0.3) 70%, transparent); img { display: inline; From dfc970d8a1299993bc47e4a75b5a18779b2f2072 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20Van=C2=A0Durpe?= <iseulde@automattic.com> Date: Fri, 1 Feb 2019 09:58:15 +0100 Subject: [PATCH 314/691] Fix Google Docs table paste (#13543) --- .../raw-handling/phrasing-content-reducer.js | 21 ++----------------- .../test/phrasing-content-reducer.js | 4 ---- .../blocks/src/api/raw-handling/test/utils.js | 6 ++++++ packages/blocks/src/api/raw-handling/utils.js | 16 ++++++++++++-- test/integration/blocks-raw-handling.spec.js | 1 + .../fixtures/google-docs-table-in.html | 1 + .../fixtures/google-docs-table-out.html | 3 +++ 7 files changed, 27 insertions(+), 25 deletions(-) create mode 100644 test/integration/fixtures/google-docs-table-in.html create mode 100644 test/integration/fixtures/google-docs-table-out.html diff --git a/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js b/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js index 3bc6fa11fb239..5237c28495ee7 100644 --- a/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js +++ b/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js @@ -1,18 +1,9 @@ /** * WordPress dependencies */ -import { wrap, unwrap, replaceTag } from '@wordpress/dom'; +import { wrap, replaceTag } from '@wordpress/dom'; -/** - * Internal dependencies - */ -import { isPhrasingContent } from './phrasing-content'; - -function isBlockContent( node, schema = {} ) { - return schema.hasOwnProperty( node.nodeName.toLowerCase() ); -} - -export default function( node, doc, schema ) { +export default function( node, doc ) { if ( node.nodeName === 'SPAN' ) { const { fontWeight, @@ -50,12 +41,4 @@ export default function( node, doc, schema ) { node.removeAttribute( 'rel' ); } } - - if ( - isPhrasingContent( node ) && - node.hasChildNodes() && - Array.from( node.childNodes ).some( ( child ) => isBlockContent( child, schema ) ) - ) { - unwrap( node ); - } } diff --git a/packages/blocks/src/api/raw-handling/test/phrasing-content-reducer.js b/packages/blocks/src/api/raw-handling/test/phrasing-content-reducer.js index 254344e50b11d..14a9d26984956 100644 --- a/packages/blocks/src/api/raw-handling/test/phrasing-content-reducer.js +++ b/packages/blocks/src/api/raw-handling/test/phrasing-content-reducer.js @@ -21,10 +21,6 @@ describe( 'phrasingContentReducer', () => { expect( deepFilterHTML( '<span style="font-style:italic;font-weight:bold">test</span>', [ phrasingContentReducer ], {} ) ).toEqual( '<strong><em><span style="font-style:italic;font-weight:bold">test</span></em></strong>' ); } ); - it( 'should remove invalid phrasing content', () => { - expect( deepFilterHTML( '<strong><p>test</p></strong>', [ phrasingContentReducer ], { p: {} } ) ).toEqual( '<p>test</p>' ); - } ); - it( 'should normalise the rel attribute', () => { const input = '<a href="https://wordpress.org" target="_blank">WordPress</a>'; const output = '<a href="https://wordpress.org" target="_blank" rel="noreferrer noopener">WordPress</a>'; diff --git a/packages/blocks/src/api/raw-handling/test/utils.js b/packages/blocks/src/api/raw-handling/test/utils.js index 66b0e8ffd0a5e..efaea872497ae 100644 --- a/packages/blocks/src/api/raw-handling/test/utils.js +++ b/packages/blocks/src/api/raw-handling/test/utils.js @@ -186,6 +186,12 @@ describe( 'removeInvalidHTML', () => { const output = '<p>test</p>test'; expect( removeInvalidHTML( input, schema ) ).toBe( output ); } ); + + it( 'should remove invalid phrasing content', () => { + const input = '<strong><p>test</p></strong>'; + const output = '<p>test</p>'; + expect( removeInvalidHTML( input, schema ) ).toEqual( output ); + } ); } ); describe( 'getBlockContentSchema', () => { diff --git a/packages/blocks/src/api/raw-handling/utils.js b/packages/blocks/src/api/raw-handling/utils.js index 82f0903824a76..d917b681f9456 100644 --- a/packages/blocks/src/api/raw-handling/utils.js +++ b/packages/blocks/src/api/raw-handling/utils.js @@ -239,9 +239,21 @@ function cleanNodeList( nodeList, doc, schema, inline ) { if ( require.length && ! node.querySelector( require.join( ',' ) ) ) { cleanNodeList( node.childNodes, doc, schema, inline ); unwrap( node ); - } + // If the node is at the top, phrasing content, and + // contains children that are block content, unwrap + // the node because it is invalid. + } else if ( + node.parentNode.nodeName === 'BODY' && + isPhrasingContent( node ) + ) { + cleanNodeList( node.childNodes, doc, schema, inline ); - cleanNodeList( node.childNodes, doc, children, inline ); + if ( Array.from( node.childNodes ).some( ( child ) => ! isPhrasingContent( child ) ) ) { + unwrap( node ); + } + } else { + cleanNodeList( node.childNodes, doc, children, inline ); + } // Remove children if the node is not supposed to have any. } else { while ( node.firstChild ) { diff --git a/test/integration/blocks-raw-handling.spec.js b/test/integration/blocks-raw-handling.spec.js index 364658b1ebf9f..4e0b6c4d60b70 100644 --- a/test/integration/blocks-raw-handling.spec.js +++ b/test/integration/blocks-raw-handling.spec.js @@ -207,6 +207,7 @@ describe( 'Blocks raw handling', () => { 'classic', 'apple', 'google-docs', + 'google-docs-table', 'ms-word', 'ms-word-styled', 'ms-word-online', diff --git a/test/integration/fixtures/google-docs-table-in.html b/test/integration/fixtures/google-docs-table-in.html new file mode 100644 index 0000000000000..8a6b117fa6ed5 --- /dev/null +++ b/test/integration/fixtures/google-docs-table-in.html @@ -0,0 +1 @@ + <meta charset='utf-8'><meta charset="utf-8"><b style="font-weight:normal;" id="docs-internal-guid-7102d5c2-7fff-c8d1-1082-5abceee52545"><br /><div dir="ltr" style="margin-left:0pt;"><table style="border:none;border-collapse:collapse;width:451.27559055118115pt"><colgroup><col width="*" /><col width="*" /><col width="*" /></colgroup><tr style="height:3.75pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">One</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Two</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Three</span></p></td></tr><tr style="height:0pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">1</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">2</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">3</span></p></td></tr><tr style="height:0pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">I</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">II</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">III</span></p></td></tr></table></div></b> diff --git a/test/integration/fixtures/google-docs-table-out.html b/test/integration/fixtures/google-docs-table-out.html new file mode 100644 index 0000000000000..697c2d41ea5cd --- /dev/null +++ b/test/integration/fixtures/google-docs-table-out.html @@ -0,0 +1,3 @@ +<!-- wp:table --> +<table class="wp-block-table"><tbody><tr><td>One</td><td>Two</td><td>Three</td></tr><tr><td>1</td><td>2</td><td>3</td></tr><tr><td>I</td><td>II</td><td>III</td></tr></tbody></table> +<!-- /wp:table --> From ba673eb0b6f38dc611211640eec94c89c7cc6866 Mon Sep 17 00:00:00 2001 From: "Nahid F. Mohit" <nfmohit49@gmail.com> Date: Fri, 1 Feb 2019 01:04:58 -0800 Subject: [PATCH 315/691] Introduce left and right float alignment options to latest posts block (#8814) --- packages/block-library/src/latest-posts/edit.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/block-library/src/latest-posts/edit.js b/packages/block-library/src/latest-posts/edit.js index 650d257603e18..f6bc6953f2955 100644 --- a/packages/block-library/src/latest-posts/edit.js +++ b/packages/block-library/src/latest-posts/edit.js @@ -161,7 +161,6 @@ class LatestPostsEdit extends Component { onChange={ ( nextAlign ) => { setAttributes( { align: nextAlign } ); } } - controls={ [ 'center', 'wide', 'full' ] } /> <Toolbar controls={ layoutControls } /> </BlockControls> From 9fbab73ca4b37fd1aeabe7ce46cf25a563e6b422 Mon Sep 17 00:00:00 2001 From: Marty Helmick <info@martyhelmick.com> Date: Fri, 1 Feb 2019 04:46:57 -0500 Subject: [PATCH 316/691] Remove unintentional right-margin on last odd-item. (#12199) * Remove unintentional right-margin on last odd-item. Looks like it wasn't intended to for the last column to have a `right-margin`, but the `:not(:last-child)` used here would only "not add" the margin. It didn't remove it if the column was odd numbered. This simplifies the logic and adds right and left margin to all columns but removes it from first-left and last-right. I also replaced the `flex: 1;` property with `flex-grow: 1;`. `flex: 1` is = to `flex-grow: 1; flex-shrink: 1; flex-basis: 0;` In this case `flex-shrink` keeps it's default value of `1` and `flex-basis` overwritten and is set to `100%` a couple lines after. So `flex-grow` is all we need. * update editor to match style.css column margin changes * account for beginning and end margins with breakpoints * update editor.css to match style.css * account for editor block padding and the medium breakpoint * remove whitespace * account for negative margins added by the editor * correct flex-basis calculation for column width --- .../block-library/src/columns/editor.scss | 18 ++++-------- packages/block-library/src/columns/style.scss | 28 ++++++++----------- 2 files changed, 18 insertions(+), 28 deletions(-) diff --git a/packages/block-library/src/columns/editor.scss b/packages/block-library/src/columns/editor.scss index ebd2cbf5fdb9d..cadafcd2ef360 100644 --- a/packages/block-library/src/columns/editor.scss +++ b/packages/block-library/src/columns/editor.scss @@ -35,7 +35,7 @@ // Responsiveness: Allow wrapping on mobile. flex-wrap: wrap; - @include break-small() { + @include break-medium() { flex-wrap: nowrap; } @@ -89,7 +89,7 @@ // Beyond mobile, allow 2 columns. @include break-small() { - flex-basis: 50%; + flex-basis: calc(50% - (#{$grid-size-large} + #{$block-padding * 2})); flex-grow: 0; } @@ -97,21 +97,15 @@ // This has to match the same padding applied in style.scss. // Only apply this beyond the mobile breakpoint, as there's only a single column on mobile. @include break-small() { - &:nth-child(odd) { - margin-right: $grid-size-large * 2; - } &:nth-child(even) { - margin-left: $grid-size-large * 2; + margin-left: calc(#{$grid-size-large * 2} + #{$block-padding}); } } - @include break-small() { + // When columns are in a single row, add space before all except the first. + @include break-medium() { &:not(:first-child) { - margin-left: $grid-size-large * 2; - } - - &:not(:last-child) { - margin-right: $grid-size-large * 2; + margin-left: calc(#{$grid-size-large * 2} + #{$block-padding}); } } } diff --git a/packages/block-library/src/columns/style.scss b/packages/block-library/src/columns/style.scss index 90c61ea5acb0f..00322b0afe799 100644 --- a/packages/block-library/src/columns/style.scss +++ b/packages/block-library/src/columns/style.scss @@ -10,18 +10,12 @@ } .wp-block-column { - flex: 1; + flex-grow: 1; margin-bottom: 1em; // Responsiveness: Show at most one columns on mobile. flex-basis: 100%; - // Beyond mobile, allow 2 columns. - @include break-small() { - flex-basis: 50%; - flex-grow: 0; - } - // Prevent the columns from growing wider than their distributed sizes. min-width: 0; @@ -29,22 +23,24 @@ word-break: break-word; // For back-compat. overflow-wrap: break-word; // New standard. - // Add space between columns. Themes can customize this if they wish to work differently. - // Only apply this beyond the mobile breakpoint, as there's only a single column on mobile. @include break-small() { - &:nth-child(odd) { - margin-right: $grid-size-large * 2; - } + + // Beyond mobile, allow 2 columns. + flex-basis: calc(50% - #{$grid-size-large}); + flex-grow: 0; + + // Add space between the 2 columns. Themes can customize this if they wish to work differently. + // Only apply this beyond the mobile breakpoint, as there's only a single column on mobile. &:nth-child(even) { margin-left: $grid-size-large * 2; } + } + + @include break-medium() { + // When columns are in a single row, add space before all except the first. &:not(:first-child) { margin-left: $grid-size-large * 2; } - - &:not(:last-child) { - margin-right: $grid-size-large * 2; - } } } From c20a644d5247a3adc24635b9145174be20bb1faf Mon Sep 17 00:00:00 2001 From: etoledom <etoledom@icloud.com> Date: Fri, 1 Feb 2019 10:48:35 +0100 Subject: [PATCH 317/691] Mobile bottom sheet component (#13612) * rnmobile: Implement image settings button using InspectorControls.Slot pattern. * rnmobile: Add missing semicolon * rnmobile: Adding bottom-sheet component to mobile * rnmobile: Styling bottom-sheet header * rnmobile: Bottom-sheet clean up * rnmobile: Fix lint issues on bottom-sheet related code. * rnmobile: Fix small lint issues * rnmobile: Move inline toolbar button definition out of constant. * rnmobile: Remove extra white-spaces * revert package-lock.json changes * rnmobile: Fix merge issue * rnmobile: exporting component BottomSheet.Button to be used as bottom-sheet header buttons * rnmobile: Adding BottomSheet.Cell component as an extraction for BottomSheet users. * Fix lint issues * Reverting changes to package-lock.json * Fix merge issues --- .../block-library/src/image/edit.native.js | 35 ++++++-- .../editor/src/components/index.native.js | 1 + .../mobile/bottom-sheet/button.native.js | 32 +++++++ .../mobile/bottom-sheet/cell.native.js | 24 +++++ .../mobile/bottom-sheet/index.native.js | 90 +++++++++++++++++++ .../mobile/bottom-sheet/styles.scss | 82 +++++++++++++++++ 6 files changed, 256 insertions(+), 8 deletions(-) create mode 100644 packages/editor/src/components/mobile/bottom-sheet/button.native.js create mode 100644 packages/editor/src/components/mobile/bottom-sheet/cell.native.js create mode 100644 packages/editor/src/components/mobile/bottom-sheet/index.native.js create mode 100644 packages/editor/src/components/mobile/bottom-sheet/styles.scss diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index fe3fa7c54d2e1..8ab944d274fe5 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -16,7 +16,7 @@ import { /** * Internal dependencies */ -import { MediaPlaceholder, RichText, BlockControls, InspectorControls } from '@wordpress/editor'; +import { MediaPlaceholder, RichText, BlockControls, InspectorControls, BottomSheet } from '@wordpress/editor'; import { Toolbar, ToolbarButton, Spinner, Dashicon } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import ImageSize from './image-size'; @@ -33,6 +33,7 @@ export default class ImageEdit extends React.Component { super( props ); this.state = { + showSettings: false, progress: 0, isUploadInProgress: false, isUploadFailed: false, @@ -168,7 +169,11 @@ export default class ImageEdit extends React.Component { } const onImageSettingsButtonPressed = () => { + this.setState( { showSettings: true } ); + }; + const onImageSettingsClose = () => { + this.setState( { showSettings: false } ); }; const toolbarEditButton = ( @@ -181,12 +186,21 @@ export default class ImageEdit extends React.Component { </Toolbar> ); - const inlineToolbarButtons = ( - <ToolbarButton - label={ __( 'Image Settings' ) } - icon="admin-generic" - onClick={ onImageSettingsButtonPressed } - /> + const getInspectorControls = () => ( + <BottomSheet + isVisible={ this.state.showSettings } + title={ __( 'Image Settings' ) } + onClose={ onImageSettingsClose } + rightButton={ + <BottomSheet.Button + text={ __( 'Done' ) } + color={ '#0087be' } + onPress={ onImageSettingsClose } + /> + } + > + <BottomSheet.Cell label={ __( 'Alt Text' ) } value={ __( 'None' ) } onPress={ () => {} } /> + </BottomSheet> ); const showSpinner = this.state.isUploadInProgress; @@ -201,7 +215,11 @@ export default class ImageEdit extends React.Component { { toolbarEditButton } </BlockControls> <InspectorControls> - { inlineToolbarButtons } + <ToolbarButton + label={ __( 'Image Settings' ) } + icon="admin-generic" + onClick={ onImageSettingsButtonPressed } + /> </InspectorControls> <ImageSize src={ url } > { ( sizes ) => { @@ -222,6 +240,7 @@ export default class ImageEdit extends React.Component { return ( <View style={ { flex: 1 } } > + { getInspectorControls() } <ImageBackground style={ { width: finalWidth, height: finalHeight, opacity } } resizeMethod="scale" diff --git a/packages/editor/src/components/index.native.js b/packages/editor/src/components/index.native.js index 07bd4c9993e6d..ebb3fb5b3e94e 100644 --- a/packages/editor/src/components/index.native.js +++ b/packages/editor/src/components/index.native.js @@ -11,3 +11,4 @@ export { default as PostTitle } from './post-title'; export { default as EditorHistoryRedo } from './editor-history/redo'; export { default as EditorHistoryUndo } from './editor-history/undo'; export { default as InspectorControls } from './inspector-controls'; +export { default as BottomSheet } from './mobile/bottom-sheet'; diff --git a/packages/editor/src/components/mobile/bottom-sheet/button.native.js b/packages/editor/src/components/mobile/bottom-sheet/button.native.js new file mode 100644 index 0000000000000..439e056b93187 --- /dev/null +++ b/packages/editor/src/components/mobile/bottom-sheet/button.native.js @@ -0,0 +1,32 @@ +/** +* External dependencies +*/ +import { TouchableOpacity, View, Text } from 'react-native'; + +/** + * Internal dependencies + */ +import styles from './styles.scss'; + +export default function Button( props ) { + const { + onPress, + disabled, + text, + color, + } = props; + + return ( + <TouchableOpacity + accessible={ true } + onPress={ onPress } + disabled={ disabled } + > + <View style={ { flexDirection: 'row', justifyContent: 'center' } }> + <Text style={ { ...styles.buttonText, color } }> + { text } + </Text> + </View> + </TouchableOpacity> + ); +} diff --git a/packages/editor/src/components/mobile/bottom-sheet/cell.native.js b/packages/editor/src/components/mobile/bottom-sheet/cell.native.js new file mode 100644 index 0000000000000..7ad3a2db4b693 --- /dev/null +++ b/packages/editor/src/components/mobile/bottom-sheet/cell.native.js @@ -0,0 +1,24 @@ +/** +* External dependencies +*/ +import { TouchableOpacity, Text } from 'react-native'; + +/** + * Internal dependencies + */ +import styles from './styles.scss'; + +export default function Cell( props ) { + const { + onPress, + label, + value, + } = props; + + return ( + <TouchableOpacity style={ styles.cellContainer } onPress={ onPress }> + <Text style={ styles.cellLabel }>{ label }</Text> + <Text style={ styles.cellValue }>{ value }</Text> + </TouchableOpacity> + ); +} diff --git a/packages/editor/src/components/mobile/bottom-sheet/index.native.js b/packages/editor/src/components/mobile/bottom-sheet/index.native.js new file mode 100644 index 0000000000000..781eb01337e4c --- /dev/null +++ b/packages/editor/src/components/mobile/bottom-sheet/index.native.js @@ -0,0 +1,90 @@ +/** + * External dependencies + */ +import { Text, View } from 'react-native'; +import Modal from 'react-native-modal'; +import SafeArea from 'react-native-safe-area'; + +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import styles from './styles.scss'; +import Button from './button'; +import Cell from './cell'; + +class BottomSheet extends Component { + constructor() { + super( ...arguments ); + this.onSafeAreaInsetsUpdate = this.onSafeAreaInsetsUpdate.bind( this ); + this.state = { + safeAreaBottomInset: 0, + }; + + SafeArea.getSafeAreaInsetsForRootView().then( this.onSafeAreaInsetsUpdate ); + } + + componentDidMount() { + SafeArea.addEventListener( 'safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsUpdate ); + } + + componentWillUnmount() { + SafeArea.removeEventListener( 'safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsUpdate ); + } + + onSafeAreaInsetsUpdate( result ) { + const { safeAreaInsets } = result; + if ( this.state.safeAreaBottomInset !== safeAreaInsets.bottom ) { + this.setState( { safeAreaBottomInset: safeAreaInsets.bottom } ); + } + } + + render() { + const { isVisible, leftButton, rightButton } = this.props; + + return ( + <Modal + isVisible={ isVisible } + style={ styles.bottomModal } + animationInTiming={ 500 } + animationOutTiming={ 500 } + backdropTransitionInTiming={ 500 } + backdropTransitionOutTiming={ 500 } + onBackdropPress={ this.props.onClose } + onSwipe={ this.props.onClose } + swipeDirection="down" + > + <View style={ { ...styles.content, borderColor: 'rgba(0, 0, 0, 0.1)' } }> + <View style={ styles.dragIndicator } /> + <View style={ styles.head }> + <View style={ { flex: 1 } }> + { leftButton } + </View> + <View style={ styles.titleContainer }> + <Text style={ styles.title }> + { this.props.title } + </Text> + </View> + <View style={ { flex: 1 } }> + { rightButton } + </View> + </View> + + <View style={ styles.separator } /> + { this.props.children } + <View style={ { flexGrow: 1 } }></View> + <View style={ { height: this.state.safeAreaBottomInset } } /> + </View> + </Modal> + ); + } +} + +BottomSheet.Button = Button; +BottomSheet.Cell = Cell; + +export default BottomSheet; diff --git a/packages/editor/src/components/mobile/bottom-sheet/styles.scss b/packages/editor/src/components/mobile/bottom-sheet/styles.scss new file mode 100644 index 0000000000000..1062ebceaa8e3 --- /dev/null +++ b/packages/editor/src/components/mobile/bottom-sheet/styles.scss @@ -0,0 +1,82 @@ +//Bottom Sheet + +.bottomModal { + justify-content: flex-end; + margin: 0; +} + +.dragIndicator { + background-color: $light-gray-400; + height: 4px; + width: 10%; + top: -12px; + margin: auto; + border-radius: 2px; +} + +.separator { + background-color: $light-gray-400; + height: 1px; + width: 100%; + margin: auto; +} + +.content { + background-color: $white; + padding: 18px 10px 5px 10px; + justify-content: center; + border-top-right-radius: 8px; + border-top-left-radius: 8px; +} + +.head { + flex-direction: row; + width: 100%; + margin-bottom: 5px; + justify-content: space-between; + align-items: center; + align-content: center; +} + +.title { + color: $dark-gray-600; + font-size: 18px; + font-weight: 600; + text-align: center; +} + +.titleContainer { + justify-content: center; + flex: 2; + align-content: center; +} + +// Button + +.buttonText { + font-size: 18px; + padding: 5px; +} + +// Cell + +//Bottom Sheet + +.cellContainer { + flex-direction: row; + min-height: 50; + justify-content: space-between; + padding-left: 12; + padding-right: 12; + align-items: center; +} + +.cellLabel { + font-size: 18px; + color: #000; +} + +.cellValue { + font-size: 18px; + color: $dark-gray-400; +} From 532ed5fcc9e96400be28cf74ccc405ce4e6a39dd Mon Sep 17 00:00:00 2001 From: Joen Asmussen <joen@automattic.com> Date: Fri, 1 Feb 2019 11:17:30 +0100 Subject: [PATCH 318/691] Try alternate list item jump fix. (#12941) * Try alternate list item jump fix. This PR is an alternative to #12590, and also fixes #12526. Props @Naerriel for initial work and inspiration. The different approach taken here is to embrace that we are applying a specific margin to our list items and overrides bleed from wp-admin. In doing so it moves these margins to the editor styles stylesheet, which is a more appropriate place for it. * Move to "initial". --- packages/editor/src/editor-styles.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/editor/src/editor-styles.scss b/packages/editor/src/editor-styles.scss index e9d58922c13ca..6715bd4d0c987 100644 --- a/packages/editor/src/editor-styles.scss +++ b/packages/editor/src/editor-styles.scss @@ -14,6 +14,11 @@ ul, ol { margin: 0; padding: 0; + + li { + // This overrides a bottom margin globally applied to list items in wp-admin. + margin-bottom: initial; + } } ul { From c3365008640234865a6643ae1837b940875079fa Mon Sep 17 00:00:00 2001 From: etoledom <etoledom@icloud.com> Date: Fri, 1 Feb 2019 13:44:35 +0100 Subject: [PATCH 319/691] Mobile: BottomSheet design tweaks (#13633) * rnmobile: Implement image settings button using InspectorControls.Slot pattern. * rnmobile: Add missing semicolon * rnmobile: Adding bottom-sheet component to mobile * rnmobile: Styling bottom-sheet header * rnmobile: Bottom-sheet clean up * rnmobile: Fix lint issues on bottom-sheet related code. * rnmobile: Fix small lint issues * rnmobile: Move inline toolbar button definition out of constant. * rnmobile: Remove extra white-spaces * revert package-lock.json changes * rnmobile: Fix merge issue * rnmobile: exporting component BottomSheet.Button to be used as bottom-sheet header buttons * rnmobile: Adding BottomSheet.Cell component as an extraction for BottomSheet users. * Fix lint issues * Reverting changes to package-lock.json * Fix merge issues * Add prop to Hide header on Bottom-Sheet * BottomSheet cell label is centered when value prop is not set * Adding icons option to BottomSheet.Cell * Added option to override Label and Value text styles on BottomSheet.Cell * Clean up BottomSheet.Cell styles * Image native: Separate BottomSheet.Cell props in multiple lines * Fix lint issues * Added WordPress dependencies to BottomSheet.Cell --- .../block-library/src/image/edit.native.js | 15 ++++++-- .../mobile/bottom-sheet/cell.native.js | 34 ++++++++++++++++--- .../mobile/bottom-sheet/index.native.js | 32 +++++++++-------- .../mobile/bottom-sheet/styles.scss | 17 +++++++++- 4 files changed, 77 insertions(+), 21 deletions(-) diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index 8ab944d274fe5..985c333e4be40 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -189,8 +189,8 @@ export default class ImageEdit extends React.Component { const getInspectorControls = () => ( <BottomSheet isVisible={ this.state.showSettings } - title={ __( 'Image Settings' ) } onClose={ onImageSettingsClose } + hideHeader rightButton={ <BottomSheet.Button text={ __( 'Done' ) } @@ -199,7 +199,18 @@ export default class ImageEdit extends React.Component { /> } > - <BottomSheet.Cell label={ __( 'Alt Text' ) } value={ __( 'None' ) } onPress={ () => {} } /> + <BottomSheet.Cell + icon={ 'editor-textcolor' } + label={ __( 'Alt Text' ) } + value={ __( 'None' ) } + onPress={ () => {} } + /> + <BottomSheet.Cell + label={ __( 'Reset to original' ) } + labelStyle={ { color: 'red' } } + drawSeparator={ false } + onPress={ () => {} } + /> </BottomSheet> ); diff --git a/packages/editor/src/components/mobile/bottom-sheet/cell.native.js b/packages/editor/src/components/mobile/bottom-sheet/cell.native.js index 7ad3a2db4b693..0ab9a87e61027 100644 --- a/packages/editor/src/components/mobile/bottom-sheet/cell.native.js +++ b/packages/editor/src/components/mobile/bottom-sheet/cell.native.js @@ -1,7 +1,12 @@ /** * External dependencies */ -import { TouchableOpacity, Text } from 'react-native'; +import { TouchableOpacity, Text, View } from 'react-native'; + +/** + * WordPress dependencies + */ +import { Dashicon } from '@wordpress/components'; /** * Internal dependencies @@ -13,12 +18,33 @@ export default function Cell( props ) { onPress, label, value, + drawSeparator = true, + icon, + labelStyle = {}, + valueStyle = {}, } = props; + const defaultLabelStyle = value ? styles.cellLabel : styles.cellLabelCentered; + return ( - <TouchableOpacity style={ styles.cellContainer } onPress={ onPress }> - <Text style={ styles.cellLabel }>{ label }</Text> - <Text style={ styles.cellValue }>{ value }</Text> + <TouchableOpacity onPress={ onPress }> + <View style={ styles.cellContainer }> + <View style={ styles.cellRowContainer }> + { icon && ( + <View style={ styles.cellRowContainer }> + <Dashicon icon={ icon } size={ 30 } /> + <View style={ { width: 12 } } /> + </View> + ) } + <Text style={ { ...defaultLabelStyle, ...labelStyle } }>{ label }</Text> + </View> + { value && ( + <Text style={ { ...styles.cellValue, ...valueStyle } }>{ value }</Text> + ) } + </View> + { drawSeparator && ( + <View style={ styles.separator } /> + ) } </TouchableOpacity> ); } diff --git a/packages/editor/src/components/mobile/bottom-sheet/index.native.js b/packages/editor/src/components/mobile/bottom-sheet/index.native.js index 781eb01337e4c..6926a33d6cd04 100644 --- a/packages/editor/src/components/mobile/bottom-sheet/index.native.js +++ b/packages/editor/src/components/mobile/bottom-sheet/index.native.js @@ -44,7 +44,7 @@ class BottomSheet extends Component { } render() { - const { isVisible, leftButton, rightButton } = this.props; + const { title = '', isVisible, leftButton, rightButton, hideHeader } = this.props; return ( <Modal @@ -60,21 +60,25 @@ class BottomSheet extends Component { > <View style={ { ...styles.content, borderColor: 'rgba(0, 0, 0, 0.1)' } }> <View style={ styles.dragIndicator } /> - <View style={ styles.head }> - <View style={ { flex: 1 } }> - { leftButton } + { hideHeader || ( + <View> + <View style={ styles.head }> + <View style={ { flex: 1 } }> + { leftButton } + </View> + <View style={ styles.titleContainer }> + <Text style={ styles.title }> + { title } + </Text> + </View> + <View style={ { flex: 1 } }> + { rightButton } + </View> + </View> + <View style={ styles.separator } /> </View> - <View style={ styles.titleContainer }> - <Text style={ styles.title }> - { this.props.title } - </Text> - </View> - <View style={ { flex: 1 } }> - { rightButton } - </View> - </View> + ) } - <View style={ styles.separator } /> { this.props.children } <View style={ { flexGrow: 1 } }></View> <View style={ { height: this.state.safeAreaBottomInset } } /> diff --git a/packages/editor/src/components/mobile/bottom-sheet/styles.scss b/packages/editor/src/components/mobile/bottom-sheet/styles.scss index 1062ebceaa8e3..120c2fcdef860 100644 --- a/packages/editor/src/components/mobile/bottom-sheet/styles.scss +++ b/packages/editor/src/components/mobile/bottom-sheet/styles.scss @@ -18,7 +18,6 @@ background-color: $light-gray-400; height: 1px; width: 100%; - margin: auto; } .content { @@ -71,11 +70,27 @@ align-items: center; } +.cellRowContainer { + flex-direction: row; + align-items: center; +} + +.cellIcon { + padding-right: 30; +} + .cellLabel { font-size: 18px; color: #000; } +.cellLabelCentered { + font-size: 18px; + color: #000; + flex: 1; + text-align: center; +} + .cellValue { font-size: 18px; color: $dark-gray-400; From 5134fcf03825881f730c77dcd5c69beec76f0ceb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Fri, 1 Feb 2019 14:00:32 +0100 Subject: [PATCH 320/691] Add Hide teaser help text in More block. (#13630) * Add Hide teaser help text in More block. * Update snapshort for the unit test covering More block --- packages/block-library/src/more/edit.js | 13 ++++++++++--- packages/block-library/src/more/index.js | 2 +- .../src/more/test/__snapshots__/edit.js.snap | 6 ++++-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/packages/block-library/src/more/edit.js b/packages/block-library/src/more/edit.js index 1c207d5b16c98..54347bbdbc6fd 100644 --- a/packages/block-library/src/more/edit.js +++ b/packages/block-library/src/more/edit.js @@ -40,11 +40,17 @@ export default class MoreEdit extends Component { } } + getHideExcerptHelp( checked ) { + return checked ? + __( 'The excerpt is hidden.' ) : + __( 'The excerpt is visible.' ); + } + render() { const { customText, noTeaser } = this.props.attributes; const { setAttributes } = this.props; - const toggleNoTeaser = () => setAttributes( { noTeaser: ! noTeaser } ); + const toggleHideExcerpt = () => setAttributes( { noTeaser: ! noTeaser } ); const { defaultText } = this.state; const value = customText !== undefined ? customText : defaultText; const inputLength = value.length + 1; @@ -54,9 +60,10 @@ export default class MoreEdit extends Component { <InspectorControls> <PanelBody> <ToggleControl - label={ __( 'Hide the teaser before the "More" tag' ) } + label={ __( 'Hide the excerpt on the full content page' ) } checked={ !! noTeaser } - onChange={ toggleNoTeaser } + onChange={ toggleHideExcerpt } + help={ this.getHideExcerptHelp } /> </PanelBody> </InspectorControls> diff --git a/packages/block-library/src/more/index.js b/packages/block-library/src/more/index.js index f51b9ef971e45..58878cbe160e4 100644 --- a/packages/block-library/src/more/index.js +++ b/packages/block-library/src/more/index.js @@ -25,7 +25,7 @@ export const name = 'core/more'; export const settings = { title: _x( 'More', 'block name' ), - description: __( 'Mark the excerpt of this content. Content before this block will be shown in the excerpt on your archives page.' ), + description: __( 'Content before this block will be shown in the excerpt on your archives page.' ), icon: <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><G><Path d="M2 9v2h19V9H2zm0 6h5v-2H2v2zm7 0h5v-2H9v2zm7 0h5v-2h-5v2z" /></G></SVG>, diff --git a/packages/block-library/src/more/test/__snapshots__/edit.js.snap b/packages/block-library/src/more/test/__snapshots__/edit.js.snap index 7c03b45fa5df2..ace475450948e 100644 --- a/packages/block-library/src/more/test/__snapshots__/edit.js.snap +++ b/packages/block-library/src/more/test/__snapshots__/edit.js.snap @@ -6,7 +6,8 @@ exports[`core/more/edit should match snapshot when noTeaser is false 1`] = ` <ForwardRef(PanelBody)> <WithInstanceId(ToggleControl) checked={false} - label="Hide the teaser before the \\"More\\" tag" + help={[Function]} + label="Hide the excerpt on the full content page" onChange={[Function]} /> </ForwardRef(PanelBody)> @@ -31,7 +32,8 @@ exports[`core/more/edit should match snapshot when noTeaser is true 1`] = ` <ForwardRef(PanelBody)> <WithInstanceId(ToggleControl) checked={true} - label="Hide the teaser before the \\"More\\" tag" + help={[Function]} + label="Hide the excerpt on the full content page" onChange={[Function]} /> </ForwardRef(PanelBody)> From 2b7085a02381e01314398303375da1486f63786a Mon Sep 17 00:00:00 2001 From: Tim Wright <timwright12@gmail.com> Date: Fri, 1 Feb 2019 12:48:30 -0500 Subject: [PATCH 321/691] Fix/issue 12501 menu item aria label Removed aria-label on the menuItem component because it was redundant with the button text itself. Also update the tests and snapshots to match the new output. --- packages/components/src/menu-item/index.js | 5 ----- .../src/menu-item/test/__snapshots__/index.js.snap | 4 ---- packages/components/src/menu-item/test/index.js | 1 - .../plugin-more-menu-item/test/__snapshots__/index.js.snap | 1 - 4 files changed, 11 deletions(-) diff --git a/packages/components/src/menu-item/index.js b/packages/components/src/menu-item/index.js index fa8dd6a85d24f..860430b4ecb67 100644 --- a/packages/components/src/menu-item/index.js +++ b/packages/components/src/menu-item/index.js @@ -24,7 +24,6 @@ import IconButton from '../icon-button'; */ export function MenuItem( { children, - label = children, info, className, icon, @@ -38,9 +37,6 @@ export function MenuItem( { 'has-icon': icon, } ); - // Avoid using label if it is passed as non-string children. - label = isString( label ) ? label : undefined; - if ( info ) { const infoId = 'edit-post-feature-toggle__info-' + instanceId; @@ -77,7 +73,6 @@ export function MenuItem( { return createElement( tagName, { - 'aria-label': label, // Make sure aria-checked matches spec https://www.w3.org/TR/wai-aria-1.1/#aria-checked 'aria-checked': ( role === 'menuitemcheckbox' || role === 'menuitemradio' ) ? isSelected : undefined, role, diff --git a/packages/components/src/menu-item/test/__snapshots__/index.js.snap b/packages/components/src/menu-item/test/__snapshots__/index.js.snap index 74eea47847f4a..9ec9378f631a1 100644 --- a/packages/components/src/menu-item/test/__snapshots__/index.js.snap +++ b/packages/components/src/menu-item/test/__snapshots__/index.js.snap @@ -3,7 +3,6 @@ exports[`MenuItem should match snapshot when all props provided 1`] = ` <IconButton aria-checked={true} - aria-label="My item" className="components-menu-item__button my-class has-icon" icon="wordpress" onClick={[Function]} @@ -20,7 +19,6 @@ exports[`MenuItem should match snapshot when all props provided 1`] = ` exports[`MenuItem should match snapshot when info is provided 1`] = ` <Button aria-describedby="edit-post-feature-toggle__info-1" - aria-label="My item" className="components-menu-item__button" role="menuitem" > @@ -43,7 +41,6 @@ exports[`MenuItem should match snapshot when info is provided 1`] = ` exports[`MenuItem should match snapshot when isSelected and role are optionally provided 1`] = ` <IconButton - aria-label="My item" className="components-menu-item__button my-class has-icon" icon="wordpress" onClick={[Function]} @@ -59,7 +56,6 @@ exports[`MenuItem should match snapshot when isSelected and role are optionally exports[`MenuItem should match snapshot when only label provided 1`] = ` <Button - aria-label="My item" className="components-menu-item__button" role="menuitem" > diff --git a/packages/components/src/menu-item/test/index.js b/packages/components/src/menu-item/test/index.js index f79f2da61be78..c5508e55629ac 100644 --- a/packages/components/src/menu-item/test/index.js +++ b/packages/components/src/menu-item/test/index.js @@ -61,7 +61,6 @@ describe( 'MenuItem', () => { </MenuItem> ); - expect( wrapper.prop( 'aria-label' ) ).not.toBeUndefined(); expect( wrapper ).toMatchSnapshot(); } ); diff --git a/packages/edit-post/src/components/header/plugin-more-menu-item/test/__snapshots__/index.js.snap b/packages/edit-post/src/components/header/plugin-more-menu-item/test/__snapshots__/index.js.snap index 692b032d744f0..6e395b2e7ebae 100644 --- a/packages/edit-post/src/components/header/plugin-more-menu-item/test/__snapshots__/index.js.snap +++ b/packages/edit-post/src/components/header/plugin-more-menu-item/test/__snapshots__/index.js.snap @@ -17,7 +17,6 @@ exports[`PluginMoreMenuItem renders menu item as button properly 1`] = ` role="menu" > <button - aria-label="My plugin button menu item" className="components-button components-icon-button components-menu-item__button has-icon has-text" onClick={[Function]} role="menuitem" From 3a1ddabe904f681102ddf9d673b16c3a73f693cb Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Fri, 1 Feb 2019 22:43:24 +0000 Subject: [PATCH 322/691] Fix: Font size picker max width on mobile (#13264) ## Description The font size picker used `justify-content: space-between;` to render the items on the screen. On small screens the sidebar may be 750px wide and I think the display results on this screen were suboptimal. This PR adds a max with to make sure the font size picker looks the same on mobile and desktop. ## How has this been tested? I checked that the font size picker renders as shown in the screenshots ## Screenshots <!-- if applicable --> Before: ![img_6639](https://user-images.githubusercontent.com/11271197/50912783-db2f4180-142a-11e9-9bac-79801f29b3b7.PNG) <img width="778" alt="screenshot 2019-01-09 at 16 23 46" src="https://user-images.githubusercontent.com/11271197/50912840-fc902d80-142a-11e9-8ad6-58285050d80d.png"> After: ![img_6638](https://user-images.githubusercontent.com/11271197/50912858-06b22c00-142b-11e9-94d7-76f9b8483799.PNG) <img width="775" alt="screenshot 2019-01-09 at 16 16 09" src="https://user-images.githubusercontent.com/11271197/50912879-129dee00-142b-11e9-8a72-bd4b4d996a7a.png"> --- packages/components/src/font-size-picker/style.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/components/src/font-size-picker/style.scss b/packages/components/src/font-size-picker/style.scss index d4fa0b8fd98f8..e8bb60c971b0b 100644 --- a/packages/components/src/font-size-picker/style.scss +++ b/packages/components/src/font-size-picker/style.scss @@ -1,4 +1,5 @@ .components-font-size-picker__buttons { + max-width: $sidebar-width - ( 2 * $panel-padding ); display: flex; justify-content: space-between; align-items: center; @@ -7,6 +8,7 @@ .components-range-control__number { height: 24px; line-height: 22px; + margin-left: 0; // Show the reset button as disabled until a value is entered. &[value=""] + .components-button { From ee2d5c7a0d9462ee8162e851d298ea0349787327 Mon Sep 17 00:00:00 2001 From: Ramon <Rahmon@users.noreply.github.com> Date: Mon, 4 Feb 2019 04:40:42 -0200 Subject: [PATCH 323/691] Remove "we" from messages (#13644) * remove "we" from embed preview error message * remove "we" from embed placeholder * Change from "embed" to "embedded". --- packages/block-library/src/embed/embed-placeholder.js | 2 +- packages/block-library/src/embed/embed-preview.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/embed/embed-placeholder.js b/packages/block-library/src/embed/embed-placeholder.js index 5545c6a3ddd3c..9b2176d5ccf3c 100644 --- a/packages/block-library/src/embed/embed-placeholder.js +++ b/packages/block-library/src/embed/embed-placeholder.js @@ -24,7 +24,7 @@ const EmbedPlaceholder = ( props ) => { </Button> { cannotEmbed && <p className="components-placeholder__error"> - { __( 'Sorry, we could not embed that content.' ) }<br /> + { __( 'Sorry, this content could not be embedded.' ) }<br /> <Button isLarge onClick={ tryAgain }>{ _x( 'Try again', 'button label' ) }</Button> <Button isLarge onClick={ fallback }>{ _x( 'Convert to link', 'button label' ) }</Button> </p> } diff --git a/packages/block-library/src/embed/embed-preview.js b/packages/block-library/src/embed/embed-preview.js index fc82b84a4d511..efc11b7a5809b 100644 --- a/packages/block-library/src/embed/embed-preview.js +++ b/packages/block-library/src/embed/embed-preview.js @@ -55,7 +55,7 @@ const EmbedPreview = ( props ) => { { ( cannotPreview ) ? ( <Placeholder icon={ <BlockIcon icon={ icon } showColors /> } label={ label }> <p className="components-placeholder__error"><a href={ url }>{ url }</a></p> - <p className="components-placeholder__error">{ __( 'Sorry, we cannot preview this embedded content in the editor.' ) }</p> + <p className="components-placeholder__error">{ __( 'Sorry, this embedded content cannot be previewed in the editor.' ) }</p> </Placeholder> ) : embedWrapper } { ( ! RichText.isEmpty( caption ) || isSelected ) && ( From dc2c1f72926528626833a526227a6d36c05487df Mon Sep 17 00:00:00 2001 From: Benjamin Ritner <ben@kadencethemes.com> Date: Mon, 4 Feb 2019 04:21:16 -0700 Subject: [PATCH 324/691] 12647 fix css color picker (#12747) * Update css to fix padding with colorPicker * Focus on to numbers * Requested changes --- packages/components/src/color-picker/style.scss | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/components/src/color-picker/style.scss b/packages/components/src/color-picker/style.scss index 336c684dfeca9..368c6f8a70321 100644 --- a/packages/components/src/color-picker/style.scss +++ b/packages/components/src/color-picker/style.scss @@ -35,7 +35,7 @@ position: relative; } .components-color-picker__body { - padding: 16px 16px 12px; + padding: $grid-size-large 0 #{ $grid-size-small * 3 }; } .components-color-picker__controls { display: flex; @@ -199,6 +199,10 @@ fieldset { flex: 1; } + + .components-color-picker__inputs-fields .components-text-control__input[type="number"] { + padding: 2px; + } } .components-color-picker__inputs-fields { display: flex; From 0f82867602bccc993f6006824d4a882c709a50bf Mon Sep 17 00:00:00 2001 From: "Nahid F. Mohit" <nfmohit49@gmail.com> Date: Mon, 4 Feb 2019 03:21:47 -0800 Subject: [PATCH 325/691] Set default values of the width and height input fields according to the actual image dimensions (#7687) --- packages/block-library/src/image/edit.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index 2a3a2b9530a36..c3e136cf6b705 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -474,8 +474,7 @@ class ImageEdit extends Component { type="number" className="block-library-image__dimensions__width" label={ __( 'Width' ) } - value={ width !== undefined ? width : '' } - placeholder={ imageWidth } + value={ width !== undefined ? width : imageWidth } min={ 1 } onChange={ this.updateWidth } /> @@ -483,8 +482,7 @@ class ImageEdit extends Component { type="number" className="block-library-image__dimensions__height" label={ __( 'Height' ) } - value={ height !== undefined ? height : '' } - placeholder={ imageHeight } + value={ height !== undefined ? height : imageHeight } min={ 1 } onChange={ this.updateHeight } /> From a0dcbfae1fb6496209161dc8cb93502a701540b7 Mon Sep 17 00:00:00 2001 From: "Nahid F. Mohit" <nfmohit49@gmail.com> Date: Mon, 4 Feb 2019 03:24:48 -0800 Subject: [PATCH 326/691] Refreshed PR (#9469) --- packages/block-library/src/categories/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/block-library/src/categories/index.js b/packages/block-library/src/categories/index.js index 90e396b433b70..4c65b50840059 100644 --- a/packages/block-library/src/categories/index.js +++ b/packages/block-library/src/categories/index.js @@ -37,6 +37,7 @@ export const settings = { supports: { align: true, + alignWide: false, html: false, }, From 317b21910ec6e8664fdb3d2511cad2238cabcafb Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Mon, 4 Feb 2019 11:26:59 +0000 Subject: [PATCH 327/691] Update lodash to 4.17.10 (#13651) --- lib/client-assets.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/client-assets.php b/lib/client-assets.php index 96986825e9bc2..8f8ca34ccb7cd 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -548,7 +548,7 @@ function gutenberg_register_vendor_scripts() { ); gutenberg_register_vendor_script( 'lodash', - 'https://unpkg.com/lodash@4.17.5/lodash' . $suffix . '.js' + 'https://unpkg.com/lodash@4.17.10/lodash' . $suffix . '.js' ); wp_add_inline_script( 'lodash', 'window.lodash = _.noConflict();' ); gutenberg_register_vendor_script( From 6485630b5e236e7f0b905fca40eb6c9bab96ab1b Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Mon, 4 Feb 2019 11:39:47 +0000 Subject: [PATCH 328/691] Bump plugin version to 5.0.0-rc.1 (#13652) --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 4e0cf4bbc32cd..175a20001ceb4 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,7 +3,7 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the new block editor in core. - * Version: 4.9.0 + * Version: 5.0.0-rc.1 * Author: Gutenberg Team * * @package gutenberg diff --git a/package-lock.json b/package-lock.json index 83491b6775a23..d5851a5b6a26e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "4.9.0", + "version": "5.0.0-rc.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 136d1db76679f..5f25a85f26663 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "4.9.0", + "version": "5.0.0-rc.1", "private": true, "description": "A new WordPress editor experience", "repository": "git+https://github.com/WordPress/gutenberg.git", From a3f016a4fa5b6cfc257ca732272e7108e53a2a35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Mon, 4 Feb 2019 13:25:40 +0100 Subject: [PATCH 329/691] Using addQueryArgs to generate Manage All Reusable Blocks link (#13653) * feat: Use addQueryArgs to generate Manage All Reusable Blocks link * feat: Use addQueryArgs to generate Manage All Reusable Blocks link (forgotten file) * feat: Use addQueryArgs to generate Manage All Reusable Blocks link * fix: Import missing addQueryArgs method * chore: Update package-lock after run npm@6.7 install --- packages/edit-post/src/plugins/index.js | 3 ++- packages/editor/src/components/inserter/menu.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/edit-post/src/plugins/index.js b/packages/edit-post/src/plugins/index.js index 34db5f51dd1a1..05a079db4738f 100644 --- a/packages/edit-post/src/plugins/index.js +++ b/packages/edit-post/src/plugins/index.js @@ -5,6 +5,7 @@ import { MenuItem } from '@wordpress/components'; import { Fragment } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { registerPlugin } from '@wordpress/plugins'; +import { addQueryArgs } from '@wordpress/url'; /** * Internal dependencies @@ -22,7 +23,7 @@ registerPlugin( 'edit-post', { <Fragment> <MenuItem role="menuitem" - href="edit.php?post_type=wp_block" + href={ addQueryArgs( 'edit.php', { post_type: 'wp_block' } ) } > { __( 'Manage All Reusable Blocks' ) } </MenuItem> diff --git a/packages/editor/src/components/inserter/menu.js b/packages/editor/src/components/inserter/menu.js index 0f5ebb437f9e1..7fb3cb2ffdca6 100644 --- a/packages/editor/src/components/inserter/menu.js +++ b/packages/editor/src/components/inserter/menu.js @@ -32,6 +32,7 @@ import { import { withDispatch, withSelect } from '@wordpress/data'; import { withInstanceId, compose, withSafeTimeout } from '@wordpress/compose'; import { LEFT, RIGHT, UP, DOWN, BACKSPACE, ENTER } from '@wordpress/keycodes'; +import { addQueryArgs } from '@wordpress/url'; /** * Internal dependencies @@ -323,7 +324,7 @@ export class InserterMenu extends Component { <BlockTypesList items={ reusableItems } onSelect={ onSelect } onHover={ this.onHover } /> <a className="editor-inserter__manage-reusable-blocks" - href="edit.php?post_type=wp_block" + href={ addQueryArgs( 'edit.php', { post_type: 'wp_block' } ) } > { __( 'Manage All Reusable Blocks' ) } </a> From 888925407909697c1be1460311a393bf3e9bbe4e Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Mon, 4 Feb 2019 13:04:01 +0000 Subject: [PATCH 330/691] Fix: Allow years lower than 1970 in DateTime component. (#13602) ## Description Fixes: https://github.com/WordPress/gutenberg/issues/13579 This PR changes the DateTime component to allow years between 0 and 1970 that were previously unallowed. The classic editor allows this set of years so we are updating the component to be equivalent. Years lower than 0 and greater than 9999 are not allowed because JS error happens inside moment functions if these years are used. The classic editor and the quick edit form also don't allow this set of years. ## How has this been tested? I checked I'm able to use years between 0 and 1970 on the publish date field with success --- packages/components/CHANGELOG.md | 1 + packages/components/src/date-time/time.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 427e5aa90759a..11879149e231d 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -8,6 +8,7 @@ ### Bug Fixes - Resolves a conflict where two instance of Slot would produce an inconsistent or duplicated rendering output. +- Allow years between 0 and 1970 in DateTime component. ### New Feature diff --git a/packages/components/src/date-time/time.js b/packages/components/src/date-time/time.js index 8cb80cdee2167..3d0092df6155f 100644 --- a/packages/components/src/date-time/time.js +++ b/packages/components/src/date-time/time.js @@ -145,7 +145,7 @@ class TimePicker extends Component { const { onChange } = this.props; const { year, date } = this.state; const value = parseInt( year, 10 ); - if ( ! isInteger( value ) || value < 1970 || value > 9999 ) { + if ( ! isInteger( value ) || value < 0 || value > 9999 ) { this.syncState( this.props ); return; } From a1b1eec2f66b80d9a14e1fdd837f8a7625735dee Mon Sep 17 00:00:00 2001 From: etoledom <etoledom@icloud.com> Date: Mon, 4 Feb 2019 16:16:22 +0100 Subject: [PATCH 331/691] Alt image setting (#13631) * rnmobile: Implement image settings button using InspectorControls.Slot pattern. * rnmobile: Add missing semicolon * rnmobile: Adding bottom-sheet component to mobile * rnmobile: Styling bottom-sheet header * rnmobile: Bottom-sheet clean up * rnmobile: Fix lint issues on bottom-sheet related code. * rnmobile: Fix small lint issues * rnmobile: Move inline toolbar button definition out of constant. * rnmobile: Remove extra white-spaces * revert package-lock.json changes * rnmobile: Fix merge issue * rnmobile: Imported BaseControl and TextinputControl to be used on Alt Image Settings * rnmobile: exporting component BottomSheet.Button to be used as bottom-sheet header buttons * rnmobile: Adding BottomSheet.Cell component as an extraction for BottomSheet users. * Fix lint issues * Reverting changes to package-lock.json * Fix merge issues * Remove Done button from Image settings bottom sheet * Make bottom-sheet avoid being behind keyboard * Fix lint issues * Making BottomSheet.Cell value editable as textinput. * Remove unnecesary onPress prop from Alt cell. * Fix lint issues --- .../block-library/src/image/edit.native.js | 19 +++++----- .../src/base-control/index.native.js | 17 +++++++++ packages/components/src/index.native.js | 2 ++ .../src/textarea-control/index.native.js | 26 ++++++++++++++ .../mobile/bottom-sheet/cell.native.js | 35 +++++++++++++++---- .../mobile/bottom-sheet/index.native.js | 11 +++--- .../mobile/bottom-sheet/styles.scss | 6 ++-- 7 files changed, 94 insertions(+), 22 deletions(-) create mode 100644 packages/components/src/base-control/index.native.js create mode 100644 packages/components/src/textarea-control/index.native.js diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index 985c333e4be40..daad9b0df5dde 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -44,6 +44,7 @@ export default class ImageEdit extends React.Component { this.removeMediaUploadListener = this.removeMediaUploadListener.bind( this ); this.finishMediaUploadWithSuccess = this.finishMediaUploadWithSuccess.bind( this ); this.finishMediaUploadWithFailure = this.finishMediaUploadWithFailure.bind( this ); + this.updateAlt = this.updateAlt.bind( this ); this.onImagePressed = this.onImagePressed.bind( this ); } @@ -128,9 +129,13 @@ export default class ImageEdit extends React.Component { } } + updateAlt( newAlt ) { + this.props.setAttributes( { alt: newAlt } ); + } + render() { const { attributes, isSelected, setAttributes } = this.props; - const { url, caption, height, width } = attributes; + const { url, caption, height, width, alt } = attributes; const onMediaLibraryButtonPressed = () => { requestMediaPickFromMediaLibrary( ( mediaId, mediaUrl ) => { @@ -191,19 +196,13 @@ export default class ImageEdit extends React.Component { isVisible={ this.state.showSettings } onClose={ onImageSettingsClose } hideHeader - rightButton={ - <BottomSheet.Button - text={ __( 'Done' ) } - color={ '#0087be' } - onPress={ onImageSettingsClose } - /> - } > <BottomSheet.Cell icon={ 'editor-textcolor' } label={ __( 'Alt Text' ) } - value={ __( 'None' ) } - onPress={ () => {} } + value={ alt || '' } + valuePlaceholder={ __( 'None' ) } + onChangeValue={ this.updateAlt } /> <BottomSheet.Cell label={ __( 'Reset to original' ) } diff --git a/packages/components/src/base-control/index.native.js b/packages/components/src/base-control/index.native.js new file mode 100644 index 0000000000000..ddf4067054d83 --- /dev/null +++ b/packages/components/src/base-control/index.native.js @@ -0,0 +1,17 @@ +/** + * External dependencies + */ +import { Text, View } from 'react-native'; + +export default function BaseControl( { label, help, children } ) { + return ( + <View + accessible={ true } + accessibilityLabel={ label } + > + { label && <Text>{ label }</Text> } + { children } + { help && <Text>{ help }</Text> } + </View> + ); +} diff --git a/packages/components/src/index.native.js b/packages/components/src/index.native.js index 65dd41e713fa3..b66663af05285 100644 --- a/packages/components/src/index.native.js +++ b/packages/components/src/index.native.js @@ -7,6 +7,8 @@ export { default as withSpokenMessages } from './higher-order/with-spoken-messag export { default as IconButton } from './icon-button'; export { default as Spinner } from './spinner'; export { createSlotFill, Slot, Fill, Provider as SlotFillProvider } from './slot-fill'; +export { default as BaseControl } from './base-control'; +export { default as TextareaControl } from './textarea-control'; // Higher-Order Components export { default as withFilters } from './higher-order/with-filters'; diff --git a/packages/components/src/textarea-control/index.native.js b/packages/components/src/textarea-control/index.native.js new file mode 100644 index 0000000000000..059eea86728d7 --- /dev/null +++ b/packages/components/src/textarea-control/index.native.js @@ -0,0 +1,26 @@ +/** + * External dependencies + */ +import { TextInput } from 'react-native'; + +/** + * Internal dependencies + */ +import BaseControl from '../base-control'; + +function TextareaControl( { label, value, help, onChange, rows = 4 } ) { + return ( + <BaseControl label={ label } help={ help } > + <TextInput + style={ { height: 80, borderColor: 'gray', borderWidth: 1 } } + value={ value } + onChangeText={ onChange } + numberOfLines={ rows } + multiline={ rows > 1 } + textAlignVertical="top" + /> + </BaseControl> + ); +} + +export default TextareaControl; diff --git a/packages/editor/src/components/mobile/bottom-sheet/cell.native.js b/packages/editor/src/components/mobile/bottom-sheet/cell.native.js index 0ab9a87e61027..6dc8ff4b26ef2 100644 --- a/packages/editor/src/components/mobile/bottom-sheet/cell.native.js +++ b/packages/editor/src/components/mobile/bottom-sheet/cell.native.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { TouchableOpacity, Text, View } from 'react-native'; +import { TouchableOpacity, Text, View, TextInput } from 'react-native'; /** * WordPress dependencies @@ -18,16 +18,29 @@ export default function Cell( props ) { onPress, label, value, + valuePlaceholder = '', drawSeparator = true, icon, labelStyle = {}, valueStyle = {}, + onChangeValue, } = props; - const defaultLabelStyle = value ? styles.cellLabel : styles.cellLabelCentered; + const showValue = value !== undefined; + const isValueEditable = onChangeValue !== undefined; + const defaultLabelStyle = showValue ? styles.cellLabel : styles.cellLabelCentered; + let valueTextInput; + + const onCellPress = () => { + if ( isValueEditable ) { + valueTextInput.focus(); + } else { + onPress(); + } + }; return ( - <TouchableOpacity onPress={ onPress }> + <TouchableOpacity onPress={ onCellPress }> <View style={ styles.cellContainer }> <View style={ styles.cellRowContainer }> { icon && ( @@ -36,10 +49,20 @@ export default function Cell( props ) { <View style={ { width: 12 } } /> </View> ) } - <Text style={ { ...defaultLabelStyle, ...labelStyle } }>{ label }</Text> + <Text numberOfLines={ 1 } style={ { ...defaultLabelStyle, ...labelStyle } }> + { label } + </Text> </View> - { value && ( - <Text style={ { ...styles.cellValue, ...valueStyle } }>{ value }</Text> + { showValue && ( + <TextInput + ref={ ( c ) => valueTextInput = c } + numberOfLines={ 1 } + style={ { ...styles.cellValue, ...valueStyle } } + value={ value } + placeholder={ valuePlaceholder } + onChangeText={ onChangeValue } + editable={ isValueEditable } + /> ) } </View> { drawSeparator && ( diff --git a/packages/editor/src/components/mobile/bottom-sheet/index.native.js b/packages/editor/src/components/mobile/bottom-sheet/index.native.js index 6926a33d6cd04..e89bc50d44e28 100644 --- a/packages/editor/src/components/mobile/bottom-sheet/index.native.js +++ b/packages/editor/src/components/mobile/bottom-sheet/index.native.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { Text, View } from 'react-native'; +import { Text, View, KeyboardAvoidingView, Platform } from 'react-native'; import Modal from 'react-native-modal'; import SafeArea from 'react-native-safe-area'; @@ -58,7 +58,10 @@ class BottomSheet extends Component { onSwipe={ this.props.onClose } swipeDirection="down" > - <View style={ { ...styles.content, borderColor: 'rgba(0, 0, 0, 0.1)' } }> + <KeyboardAvoidingView + behavior={ Platform.OS === 'ios' && 'padding' } + style={ { ...styles.content, borderColor: 'rgba(0, 0, 0, 0.1)' } } + > <View style={ styles.dragIndicator } /> { hideHeader || ( <View> @@ -78,12 +81,12 @@ class BottomSheet extends Component { <View style={ styles.separator } /> </View> ) } - { this.props.children } <View style={ { flexGrow: 1 } }></View> <View style={ { height: this.state.safeAreaBottomInset } } /> - </View> + </KeyboardAvoidingView> </Modal> + ); } } diff --git a/packages/editor/src/components/mobile/bottom-sheet/styles.scss b/packages/editor/src/components/mobile/bottom-sheet/styles.scss index 120c2fcdef860..f02aa77fe4498 100644 --- a/packages/editor/src/components/mobile/bottom-sheet/styles.scss +++ b/packages/editor/src/components/mobile/bottom-sheet/styles.scss @@ -65,8 +65,8 @@ flex-direction: row; min-height: 50; justify-content: space-between; - padding-left: 12; - padding-right: 12; + margin-left: 12; + margin-right: 12; align-items: center; } @@ -82,6 +82,7 @@ .cellLabel { font-size: 18px; color: #000; + margin-right: 12px; } .cellLabelCentered { @@ -94,4 +95,5 @@ .cellValue { font-size: 18px; color: $dark-gray-400; + text-align: right; } From 609a42c7ec8f6c1e3ff7f51186bfd61d0864458e Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Mon, 4 Feb 2019 09:41:45 -0800 Subject: [PATCH 332/691] Update edit-save documentation (#13578) * Update edit examples with ES5 code * Add extra explanation and links to attributes and block tutorial * Add two examples for attributes, edit, save * Update docs/designers-developers/developers/block-api/block-edit-save.md Co-Authored-By: mkaz <marcus@mkaz.com> * Remove spaces. * Update docs/designers-developers/developers/block-api/block-edit-save.md Co-Authored-By: mkaz <marcus@mkaz.com> * Update docs/designers-developers/developers/block-api/block-edit-save.md Co-Authored-By: mkaz <marcus@mkaz.com> * Update docs/designers-developers/developers/block-api/block-edit-save.md Co-Authored-By: mkaz <marcus@mkaz.com> * Make function defns consistent across ES5/ESNext * Add clone example for ES5 * Update docs/designers-developers/developers/block-api/block-edit-save.md Co-Authored-By: mkaz <marcus@mkaz.com> * Simplify ES5 example, props @nosolosw --- .../developers/block-api/block-edit-save.md | 273 ++++++++++++++++-- 1 file changed, 251 insertions(+), 22 deletions(-) diff --git a/docs/designers-developers/developers/block-api/block-edit-save.md b/docs/designers-developers/developers/block-api/block-edit-save.md index 1bfaf08f10a22..79a3206326031 100644 --- a/docs/designers-developers/developers/block-api/block-edit-save.md +++ b/docs/designers-developers/developers/block-api/block-edit-save.md @@ -6,25 +6,52 @@ When registering a block, the `edit` and `save` functions provide the interface The `edit` function describes the structure of your block in the context of the editor. This represents what the editor will render when the block is used. +{% codetabs %} +{% ES5 %} ```js -// Defining the edit interface -edit() { - return <hr />; +// A static div +edit: function() { + return wp.element.createElement( + 'div', + null, + 'Your block.' + ); +} +``` +{% ESNext %} +```jsx +edit: () => { + return <div>Your block.</div>; } ``` +{% end %} The function receives the following properties through an object argument: ### attributes -This property surfaces all the available attributes and their corresponding values, as described by the `attributes` property when the block type was registered. In this case, assuming we had defined an attribute of `content` during block registration, we would receive and use that value in our edit function: +This property surfaces all the available attributes and their corresponding values, as described by the `attributes` property when the block type was registered. See [attributes documentation](/docs/designers-developers/developers/block-api/block-attributes.md) for how to specify attribute sources. +In this case, assuming we had defined an attribute of `content` during block registration, we would receive and use that value in our edit function: + +{% codetabs %} +{% ES5 %} ```js -// Defining the edit interface -edit( { attributes } ) { +edit: function( props ) { + return wp.element.createElement( + 'div', + null, + props.attributes.content + ); +} +``` +{% ESNext %} +```js +edit: ( { attributes } ) => { return <div>{ attributes.content }</div>; } ``` +{% end %} The value of `attributes.content` will be displayed inside the `div` when inserting the block in the editor. @@ -32,23 +59,53 @@ The value of `attributes.content` will be displayed inside the `div` when insert This property returns the class name for the wrapper element. This is automatically added in the `save` method, but not on `edit`, as the root element may not correspond to what is _visually_ the main element of the block. You can request it to add it to the correct element in your function. +{% codetabs %} +{% ES5 %} +```js +edit: function( props ) { + return wp.element.createElement( + 'div', + { className: props.className }, + props.attributes.content + ); +} +``` +{% ESNext %} ```js -// Defining the edit interface -edit( { attributes, className } ) { +edit: ( { attributes, className } ) => { return <div className={ className }>{ attributes.content }</div>; } ``` +{% end %} ### isSelected The isSelected property is an object that communicates whether the block is currently selected. +{% codetabs %} +{% ES5 %} ```js -// Defining the edit interface -edit( { attributes, className, isSelected } ) { +edit: function( props ) { + return wp.element.createElement( + 'div', + { className: props.className }, + [ + 'Your block.', + props.isSelected ? wp.element.createElement( + 'span', + null, + 'Shows only when the block is selected.' + ) + ] + ); +} +``` +{% ESNext %} +```jsx +edit: ( { attributes, className, isSelected } ) => { return ( <div className={ className }> - { attributes.content } + Your block. { isSelected && <span>Shows only when the block is selected.</span> } @@ -56,14 +113,39 @@ edit( { attributes, className, isSelected } ) { ); } ``` +{% end %} ### setAttributes This function allows the block to update individual attributes based on user interactions. +{% codetabs %} +{% ES5 %} ```js -// Defining the edit interface -edit( { attributes, setAttributes, className, isSelected } ) { +edit: function( props ) { + // Simplify access to attributes + let content = props.attributes.content; + let mySetting = props.attributes.mySetting; + + // Toggle a setting when the user clicks the button + let toggleSetting = () => props.setAttributes( { mySetting: ! mySetting } ); + return wp.element.createElement( + 'div', + { className: props.className }, + [ + content, + props.isSelected ? wp.element.createElement( + 'button', + { onClick: toggleSetting }, + 'Toggle setting' + ) : null + ] + ); +}, +``` +{% ESNext %} +```jsx +edit: ( { attributes, setAttributes, className, isSelected } ) => { // Simplify access to attributes const { content, mySetting } = attributes; @@ -79,22 +161,41 @@ edit( { attributes, setAttributes, className, isSelected } ) { ); } ``` +{% end %} When using attributes that are objects or arrays it's a good idea to copy or clone the attribute prior to updating it: +{% codetabs %} +{% ES5 %} +```js +// Good - cloning the old list +var newList = attributes.list.slice(); + +var addListItem = function( newListItem ) { + setAttributes( { list: newList.concat( [ newListItem ] ) } ); +}; + +// Bad - the list from the existing attribute is modified directly to add the new list item: +var list = attributes.list; +var addListItem = function( newListItem ) { + list.push( newListItem ); + setAttributes( { list: list } ); +}; +``` +{% ESNext %} ```js -// Good - here a new array is created from the old list attribute and a new list item: +// Good - a new array is created from the old list attribute and a new list item: const { list } = attributes; const addListItem = ( newListItem ) => setAttributes( { list: [ ...list, newListItem ] } ); -// Bad - here the list from the existing attribute is modified directly to add the new list item: +// Bad - the list from the existing attribute is modified directly to add the new list item: const { list } = attributes; const addListItem = ( newListItem ) => { list.push( newListItem ); setAttributes( { list } ); }; - ``` +{% end %} Why do this? In JavaScript, arrays and objects are passed by reference, so this practice ensures changes won't affect other code that might hold references to the same data. Furthermore, Gutenberg follows the philosophy of the Redux library that [state should be immutable](https://redux.js.org/faq/immutable-data#what-are-the-benefits-of-immutability)—data should not be changed directly, but instead a new version of the data created containing the changes. @@ -105,14 +206,18 @@ The `save` function defines the way in which the different attributes should be {% codetabs %} {% ES5 %} ```js -save() { - return wp.element.createElement( 'hr' ); +save: function() { + return wp.element.createElement( + 'div', + null, + 'Your block.' + ); } ``` {% ESNext %} ```jsx -save() { - return <hr />; +save: () => { + return <div> Your block. </div>; } ``` {% end %} @@ -130,7 +235,7 @@ As with `edit`, the `save` function also receives an object argument including a {% codetabs %} {% ES5 %} ```js -save( props ) { +save: function( props ) { return wp.element.createElement( 'div', null, @@ -140,12 +245,136 @@ save( props ) { ``` {% ESNext %} ```jsx -save( { attributes } ) { +save: ( { attributes } ) => { return <div>{ attributes.content }</div>; } ``` {% end %} + +When saving your block, you want to save the attributes in the same format specified by the attribute source definition. If no attribute source is specified, the attribute will be saved to the block's comment delimiter. See the [Block Attributes documentation](/docs/designers-developers/developers/block-api/block-attributes.md) for more details. + +## Examples + +Here are a couple examples of using attributes, edit, and save all together. For a full working example, see the [Introducing Attributes and Editable Fields](/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md) section of the Block Tutorial. + +### Saving Attributes to Child Elements + +{% codetabs %} +{% ES5 %} +```js +attributes: { + content: { + type: 'string', + source: 'html', + selector: 'p' + } +}, + +edit: function( props ) { + var updateFieldValue = function( val ) { + props.setAttributes( { content: val } ); + } + return wp.element.createElement( + wp.components.TextControl, + { + label: 'My Text Field', + value: props.attributes.content, + onChange: updateFieldValue, + + } + ); +}, + +save: function( props ) { + return el( 'p', {}, props.attributes.content ); +}, +``` +{% ESNext %} +```jsx +attributes: { + content: { + type: 'string', + source: 'html', + selector: 'p' + } +}, + +edit: ( { attributes, setAttributes } ) => { + const updateFieldValue = ( val ) => { + setAttributes( { content: val } ); + } + return <TextControl + label='My Text Field' + value={ attributes.content } + onChange={ updateFieldValue } + />; +}, + +save: ( { attributes } ) => { + return <p> { attributes.content } </p>; +}, +``` +{% end %} + +### Saving Attributes via Serialization + +Ideally, the attributes saved should be included in the markup. However, there are times when this is not practical, so if no attribute source is specified the attribute is serialized and saved to the block's comment delimiter. + +This example could be for a dynamic block, such as the [Latest Posts block](https://github.com/WordPress/gutenberg/blob/master/packages/block-library/src/latest-posts/index.js), which renders the markup server-side. The save function is still required, however in this case it simply returns null since the block is not saving content from the editor. + +{% codetabs %} +{% ES5 %} +```js +attributes: { + postsToShow: { + type: 'number', + } +}, + +edit: function( props ) { + return wp.element.createElement( + wp.components.TextControl, + { + label: 'Number Posts to Show', + value: props.attributes.postsToShow, + onChange: function( val ) { + props.setAttributes( { postsToShow: parseInt( val ) } ); + }, + } + ); +}, + +save: function() { + return null; +} +``` +{% ESNext %} +```jsx +attributes: { + postsToShow: { + type: 'number', + } +}, + +edit: ( { attributes, setAttributes } ) => { + return <TextControl + label='Number Posts to Show' + value={ attributes.postsToShow } + onChange={ ( val ) => { + setAttributes( { postsToShow: parseInt( val ) } ); + }}, + } + ); +}, + +save: () => { + return null; +} +``` +{% end %} + + ## Validation When the editor loads, all blocks within post content are validated to determine their accuracy in order to protect against content loss. This is closely related to the saving implementation of a block, as a user may unintentionally remove or modify their content if the editor is unable to restore a block correctly. During editor initialization, the saved markup for each block is regenerated using the attributes that were parsed from the post's content. If the newly-generated markup does not match what was already stored in post content, the block is marked as invalid. This is because we assume that unless the user makes edits, the markup should remain identical to the saved content. From fd2468d6c486220f7f1fa5bfa2366c510b46af27 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell@kjellr.com> Date: Mon, 4 Feb 2019 13:38:38 -0500 Subject: [PATCH 333/691] Add a mobile minimum size for form fields (#13639) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On Safari for iOS, the browser automatically zooms into any form field that contains text with a font size less than `16px`. When this occurs, users must manually zoom back out after dismissing the keyboard. This happens frequently in our interface, since many of our text fields use `13px` text. It breaks up the flow, and makes editing feel _very_ non-mobile-optimized. We already have a [`$mobile-text-min-font-size` variable](https://github.com/WordPress/gutenberg/blob/286317c8e61b1a2922243875fe1f98c868d7859b/assets/stylesheets/_variables.scss#L15), and use this to avoid this behavior in the block inserter: https://github.com/WordPress/gutenberg/blob/ce864a6f9aff4eea9b4bd3994b2cf4bae30105cb/packages/editor/src/components/inserter/style.scss#L75-L79 This PR expands that fix out to as many different form fields as I could find: - General form fields, defined in `/edit-post/` (this covers most fields by itself) - Preformatted, Code, and HTML blocks - Form Token Field (used in the tags panel of the document sidebar) - URL Input field ⚠️ _This worked fine in my testing, but since it effects a lot of fields, it could use a lot of testing!_ In addition, the Code Editor extended off-screen on small screens. This PR adds a `max-width` to clean that up. ## Screenshots Before: 🎥 [**Screencast of the issue**](https://cloudup.com/cf1VqxNV5LN) ![ios-zoom-old](https://user-images.githubusercontent.com/1202812/52131756-93f03700-260b-11e9-83de-d03fb5a84a09.gif) <img width="376" alt="screen shot 2019-02-01 at 10 16 56 am" src="https://user-images.githubusercontent.com/1202812/52131610-2e03af80-260b-11e9-83d1-15c66590f62a.png"> After: 🎥 [**Screencast with this fix**](https://cloudup.com/cRQNYsDRsmx) ![ios-zoom](https://user-images.githubusercontent.com/1202812/52131874-f0535680-260b-11e9-9ad8-6ede81e21b60.gif) <img width="375" alt="screen shot 2019-02-01 at 10 02 16 am" src="https://user-images.githubusercontent.com/1202812/52131615-322fcd00-260b-11e9-88af-5407e32388ee.png"> --- cc @iamthomasbishop for some thoughts/testing too. --- packages/block-library/src/code/editor.scss | 7 ++++++- packages/block-library/src/html/editor.scss | 7 ++++++- packages/block-library/src/preformatted/theme.scss | 7 ++++++- packages/components/src/form-token-field/style.scss | 1 - packages/edit-post/src/components/text-editor/style.scss | 1 + packages/edit-post/src/style.scss | 7 ++++++- packages/editor/src/components/post-text-editor/style.scss | 7 ++++++- packages/editor/src/components/url-input/style.scss | 6 ++++++ 8 files changed, 37 insertions(+), 6 deletions(-) diff --git a/packages/block-library/src/code/editor.scss b/packages/block-library/src/code/editor.scss index 1f53d244794e4..ec54fd99ad2bc 100644 --- a/packages/block-library/src/code/editor.scss +++ b/packages/block-library/src/code/editor.scss @@ -1,8 +1,13 @@ .wp-block-code .editor-plain-text { font-family: $editor-html-font; - font-size: $text-editor-font-size; color: $dark-gray-800; + /* Fonts smaller than 16px causes mobile safari to zoom. */ + font-size: $mobile-text-min-font-size; + @include break-small { + font-size: $default-font-size; + } + &:focus { box-shadow: none; } diff --git a/packages/block-library/src/html/editor.scss b/packages/block-library/src/html/editor.scss index 44daf03dd370a..97d4b156aea79 100644 --- a/packages/block-library/src/html/editor.scss +++ b/packages/block-library/src/html/editor.scss @@ -1,11 +1,16 @@ .wp-block-html .editor-plain-text { font-family: $editor-html-font; - font-size: $text-editor-font-size; color: $dark-gray-800; padding: 0.8em 1em; border: 1px solid $light-gray-500; border-radius: 4px; + /* Fonts smaller than 16px causes mobile safari to zoom. */ + font-size: $mobile-text-min-font-size; + @include break-small { + font-size: $default-font-size; + } + &:focus { box-shadow: none; } diff --git a/packages/block-library/src/preformatted/theme.scss b/packages/block-library/src/preformatted/theme.scss index 920ecf7de4238..e0a221912e620 100644 --- a/packages/block-library/src/preformatted/theme.scss +++ b/packages/block-library/src/preformatted/theme.scss @@ -1,7 +1,12 @@ .wp-block-preformatted { pre { font-family: $editor-html-font; - font-size: $text-editor-font-size; color: $dark-gray-800; + + /* Fonts smaller than 16px causes mobile safari to zoom. */ + font-size: $mobile-text-min-font-size; + @include break-small { + font-size: $text-editor-font-size; + } } } diff --git a/packages/components/src/form-token-field/style.scss b/packages/components/src/form-token-field/style.scss index 1080070a4ddaf..99fbc607c819a 100644 --- a/packages/components/src/form-token-field/style.scss +++ b/packages/components/src/form-token-field/style.scss @@ -31,7 +31,6 @@ min-height: 24px; background: inherit; border: 0; - font-size: $default-font-size; color: $dark-gray-800; box-shadow: none; diff --git a/packages/edit-post/src/components/text-editor/style.scss b/packages/edit-post/src/components/text-editor/style.scss index 1a5980bc5e022..e925344dc9e3a 100644 --- a/packages/edit-post/src/components/text-editor/style.scss +++ b/packages/edit-post/src/components/text-editor/style.scss @@ -16,6 +16,7 @@ .edit-post-text-editor { width: 100%; + max-width: calc(100% - #{$grid-size-large * 2}); margin-left: $grid-size-large; margin-right: $grid-size-large; diff --git a/packages/edit-post/src/style.scss b/packages/edit-post/src/style.scss index 90fac100e15ee..83f2a77745458 100644 --- a/packages/edit-post/src/style.scss +++ b/packages/edit-post/src/style.scss @@ -174,10 +174,15 @@ body.block-editor-page { select, textarea { font-family: $default-font; - font-size: $default-font-size; padding: 6px 8px; @include input-style__neutral(); + /* Fonts smaller than 16px causes mobile safari to zoom. */ + font-size: $mobile-text-min-font-size; + @include break-small { + font-size: $default-font-size; + } + &:focus { @include input-style__focus(); } diff --git a/packages/editor/src/components/post-text-editor/style.scss b/packages/editor/src/components/post-text-editor/style.scss index 4f6e9ac44ceb7..fd1c77195cc2f 100644 --- a/packages/editor/src/components/post-text-editor/style.scss +++ b/packages/editor/src/components/post-text-editor/style.scss @@ -7,9 +7,14 @@ resize: none; overflow: hidden; font-family: $editor-html-font; - font-size: $text-editor-font-size; line-height: 150%; + /* Fonts smaller than 16px causes mobile safari to zoom. */ + font-size: $mobile-text-min-font-size; + @include break-small { + font-size: $text-editor-font-size; + } + &:hover, &:focus { border: $border-width solid $light-gray-500; diff --git a/packages/editor/src/components/url-input/style.scss b/packages/editor/src/components/url-input/style.scss index 64429e6d6c4e4..2c1662a4c2ed5 100644 --- a/packages/editor/src/components/url-input/style.scss +++ b/packages/editor/src/components/url-input/style.scss @@ -20,6 +20,12 @@ $input-size: 300px; margin-left: 0; margin-right: 0; + /* Fonts smaller than 16px causes mobile safari to zoom. */ + font-size: $mobile-text-min-font-size; + @include break-small { + font-size: $default-font-size; + } + &::-ms-clear { display: none; } From 101853adbccce92e962068465f7c7c6194048c0b Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Mon, 4 Feb 2019 14:03:58 -0500 Subject: [PATCH 334/691] Workflow: Add repository CODEOWNERS file (#13604) * Workflow: Add repository CODEOWNERS file * Fix duplicate names * Project: Add @swissspidy as i18n package code owner * Remove duplicated /packages/rich-text * Plugin: List individual code owners instead of core group * Adding @coderkevin See related discussion https://github.com/WordPress/gutenberg/pull/11460#issuecomment-460310684 * Plugin: Add mobile (native.js) maintainers as code owners --- .github/CODEOWNERS | 79 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000000..4234e2adab7e5 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,79 @@ +# Data +/packages/api-fetch @youknowriad @gziolo @aduth @nerrad +/packages/core-data @youknowriad @gziolo @aduth @nerrad +/packages/data @youknowriad @gziolo @aduth @nerrad @coderkevin +/packages/redux-routine @youknowriad @gziolo @aduth @nerrad + +# Blocks +/packages/block-library @youknowriad @gziolo @Soean @ajitbohra @jorgefilipecosta + +# Editor +/packages/annotations @youknowriad @gziolo @aduth +/packages/autop @youknowriad @gziolo @aduth +/packages/block-serialization-spec-parser @youknowriad @gziolo @aduth +/packages/block-serialization-default-parser @youknowriad @gziolo @aduth +/packages/blocks @youknowriad @gziolo @aduth +/packages/edit-post @youknowriad @gziolo +/packages/editor @youknowriad @gziolo @nosolosw +/packages/list-reusable-blocks @youknowriad @gziolo @aduth +/packages/shortcode @youknowriad @gziolo @aduth + +# Tooling +/bin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra +/packages/babel-plugin-import-jsx-pragma @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra +/packages/babel-plugin-makepot @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra +/packages/babel-preset-default @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra +/packages/browserslist-config @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra +/packages/custom-templated-path-webpack-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra +/packages/e2e-test-utils @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra +/packages/e2e-tests @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra +/packages/eslint-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra +/packages/jest-console @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra +/packages/jest-preset-default @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra +/packages/jest-puppeteer-axe @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra +/packages/library-export-default-webpack-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra +/packages/npm-package-json-lint-config @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra +/packages/postcss-themes @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra +/packages/scripts @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra + +# UI Components +/packages/components @youknowriad @gziolo @aduth @chrisvanpatten @ajitbohra @jaymanpandya @nosolosw @jorgefilipecosta +/packages/compose @youknowriad @gziolo @aduth @chrisvanpatten @ajitbohra @jaymanpandya @jorgefilipecosta +/packages/element @youknowriad @gziolo @aduth @chrisvanpatten @ajitbohra @jaymanpandya @jorgefilipecosta +/packages/notices @youknowriad @gziolo @aduth @chrisvanpatten @ajitbohra @jaymanpandya @jorgefilipecosta +/packages/nux @youknowriad @gziolo @aduth @chrisvanpatten @ajitbohra @jaymanpandya @jorgefilipecosta +/packages/viewport @youknowriad @gziolo @aduth @chrisvanpatten @ajitbohra @jaymanpandya @jorgefilipecosta + +# Utilities +/packages/a11y @youknowriad @gziolo @aduth +/packages/blob @youknowriad @gziolo @aduth +/packages/date @youknowriad @gziolo @aduth +/packages/deprecated @youknowriad @gziolo @aduth +/packages/dom @youknowriad @gziolo @aduth +/packages/dom-ready @youknowriad @gziolo @aduth +/packages/escape-html @youknowriad @gziolo @aduth +/packages/html-entities @youknowriad @gziolo @aduth +/packages/i18n @youknowriad @gziolo @aduth @swissspidy +/packages/is-shallow-equal @youknowriad @gziolo @aduth +/packages/keycodes @youknowriad @gziolo @aduth +/packages/priority-queue @youknowriad @gziolo @aduth +/packages/token-list @youknowriad @gziolo @aduth +/packages/url @youknowriad @gziolo @aduth +/packages/wordcount @youknowriad @gziolo @aduth + +# Extensibility +/packages/hooks @youknowriad @gziolo @aduth +/packages/plugins @youknowriad @gziolo @aduth + +# Rich Text +/packages/format-library @youknowriad @gziolo @aduth @iseulde @jorgefilipecosta +/packages/rich-text @youknowriad @gziolo @aduth @iseulde @jorgefilipecosta + +# PHP +/lib @youknowriad @gziolo @aduth + +# Documentation +/docs @youknowriad @gziolo @chrisvanpatten @mkaz @ajitbohra @nosolosw + +# Native +*.native.js @daniloercoli @diegoreymendez @etoledom @hypest @koke @marecar3 @mzorz @pinarol @SergioEstevao @tug From 22efd75722d657ebfbaa35fa41e65cf46f19b05f Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Mon, 4 Feb 2019 14:13:23 -0500 Subject: [PATCH 335/691] Plugin: Remove jQuery heartbeat-to-hooks proxying (#13576) --- lib/client-assets.php | 37 +------------------------------------ 1 file changed, 1 insertion(+), 36 deletions(-) diff --git a/lib/client-assets.php b/lib/client-assets.php index 8f8ca34ccb7cd..1178d245ed875 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -851,48 +851,13 @@ function gutenberg_get_available_image_sizes() { * @param string $hook Screen name. */ function gutenberg_editor_scripts_and_styles( $hook ) { - global $wp_scripts, $wp_meta_boxes; - - // Add "wp-hooks" as dependency of "heartbeat". - $heartbeat_script = $wp_scripts->query( 'heartbeat', 'registered' ); - if ( $heartbeat_script && ! in_array( 'wp-hooks', $heartbeat_script->deps ) ) { - $heartbeat_script->deps[] = 'wp-hooks'; - } + global $wp_meta_boxes; // Enqueue heartbeat separately as an "optional" dependency of the editor. // Heartbeat is used for automatic nonce refreshing, but some hosts choose // to disable it outright. wp_enqueue_script( 'heartbeat' ); - // Transforms heartbeat jQuery events into equivalent hook actions. This - // avoids a dependency on jQuery for listening to the event. - $heartbeat_hooks = <<<JS -( function() { - jQuery( document ).on( [ - 'heartbeat-send', - 'heartbeat-tick', - 'heartbeat-error', - 'heartbeat-connection-lost', - 'heartbeat-connection-restored', - 'heartbeat-nonces-expired', - ].join( ' ' ), function( event ) { - var actionName = event.type.replace( /-/g, '.' ), - args; - - // Omit the event argument in applying arguments to the hook callback. - // The remaining arguments are passed to the hook. - args = Array.prototype.slice.call( arguments, 1 ); - - wp.hooks.doAction.apply( null, [ actionName ].concat( args ) ); - } ); -} )(); -JS; - wp_add_inline_script( - 'heartbeat', - $heartbeat_hooks, - 'after' - ); - wp_enqueue_script( 'wp-edit-post' ); wp_enqueue_script( 'wp-format-library' ); wp_enqueue_style( 'wp-format-library' ); From 970b1458bb1dbbffbdbef4e7c95ac774463b5c27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Tue, 5 Feb 2019 14:48:25 +0100 Subject: [PATCH 336/691] Add more reviewers to CODEOWNERS.md file (#13667) * Update CODEOWNERS * Remove gziolo from a few folders --- .github/CODEOWNERS | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4234e2adab7e5..75bddbaf64195 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,22 +1,22 @@ # Data -/packages/api-fetch @youknowriad @gziolo @aduth @nerrad +/packages/api-fetch @youknowriad @aduth @nerrad @mmtr /packages/core-data @youknowriad @gziolo @aduth @nerrad -/packages/data @youknowriad @gziolo @aduth @nerrad @coderkevin -/packages/redux-routine @youknowriad @gziolo @aduth @nerrad +/packages/data @youknowriad @aduth @nerrad @coderkevin +/packages/redux-routine @youknowriad @aduth @nerrad # Blocks /packages/block-library @youknowriad @gziolo @Soean @ajitbohra @jorgefilipecosta # Editor /packages/annotations @youknowriad @gziolo @aduth -/packages/autop @youknowriad @gziolo @aduth +/packages/autop @youknowriad @aduth /packages/block-serialization-spec-parser @youknowriad @gziolo @aduth /packages/block-serialization-default-parser @youknowriad @gziolo @aduth /packages/blocks @youknowriad @gziolo @aduth /packages/edit-post @youknowriad @gziolo /packages/editor @youknowriad @gziolo @nosolosw -/packages/list-reusable-blocks @youknowriad @gziolo @aduth -/packages/shortcode @youknowriad @gziolo @aduth +/packages/list-reusable-blocks @youknowriad @aduth +/packages/shortcode @youknowriad @aduth # Tooling /bin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @@ -53,7 +53,7 @@ /packages/dom-ready @youknowriad @gziolo @aduth /packages/escape-html @youknowriad @gziolo @aduth /packages/html-entities @youknowriad @gziolo @aduth -/packages/i18n @youknowriad @gziolo @aduth @swissspidy +/packages/i18n @youknowriad @aduth @swissspidy /packages/is-shallow-equal @youknowriad @gziolo @aduth /packages/keycodes @youknowriad @gziolo @aduth /packages/priority-queue @youknowriad @gziolo @aduth From a25a2b304fa6806b397052dcf97ce9e2112d5da6 Mon Sep 17 00:00:00 2001 From: Anton Timmermans <email@atimmer.com> Date: Tue, 5 Feb 2019 15:25:52 +0100 Subject: [PATCH 337/691] Add myself as a code owner to the annotations (#13672) --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 75bddbaf64195..c9a533d91a81b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -8,7 +8,7 @@ /packages/block-library @youknowriad @gziolo @Soean @ajitbohra @jorgefilipecosta # Editor -/packages/annotations @youknowriad @gziolo @aduth +/packages/annotations @youknowriad @gziolo @aduth @atimmer /packages/autop @youknowriad @aduth /packages/block-serialization-spec-parser @youknowriad @gziolo @aduth /packages/block-serialization-default-parser @youknowriad @gziolo @aduth From 1d829ddf7a1d5567fa6522fb9da1838a7c50e639 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Tue, 5 Feb 2019 14:52:04 +0000 Subject: [PATCH 338/691] Fix: Linting problem in modal example code (#13671) --- packages/components/src/modal/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/src/modal/README.md b/packages/components/src/modal/README.md index 13667139f8fec..7ca3faff38131 100644 --- a/packages/components/src/modal/README.md +++ b/packages/components/src/modal/README.md @@ -17,7 +17,7 @@ const MyModal = withState( { } )( ( { isOpen, setState } ) => ( <div> <Button isDefault onClick={ () => setState( { isOpen: true } ) }>Open Modal</Button> - { isOpen ? + { isOpen && ( <Modal title="This is my modal" onRequestClose={ () => setState( { isOpen: false } ) }> @@ -25,7 +25,7 @@ const MyModal = withState( { My custom close button </Button> </Modal> - : null } + ) } </div> ) ); ``` From 307b23706d930b8b53c2b8dd88ebd4b58066a854 Mon Sep 17 00:00:00 2001 From: Vadim Nicolai <nicolai.vadim@gmail.com> Date: Tue, 5 Feb 2019 16:56:01 +0200 Subject: [PATCH 339/691] Disable navigation block for text mode. (#12185) * Disable navigation block for text mode. * Add additional rule for disabling navigation blocks. * Disable click handler for required rules. * Adjusted click handler for for text mode. * Disabled keyboard shortcut in text mode. * Deleted obsolete onClick check. --- .../editor/src/components/block-navigation/dropdown.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/editor/src/components/block-navigation/dropdown.js b/packages/editor/src/components/block-navigation/dropdown.js index c6b8e976db5cc..bfcb5f3f0e2d3 100644 --- a/packages/editor/src/components/block-navigation/dropdown.js +++ b/packages/editor/src/components/block-navigation/dropdown.js @@ -18,17 +18,18 @@ const MenuIcon = ( </SVG> ); -function BlockNavigationDropdown( { hasBlocks } ) { +function BlockNavigationDropdown( { hasBlocks, isTextModeEnabled } ) { return ( <Dropdown renderToggle={ ( { isOpen, onToggle } ) => ( <Fragment> - <KeyboardShortcuts + { hasBlocks && ! isTextModeEnabled && <KeyboardShortcuts bindGlobal shortcuts={ { [ rawShortcut.access( 'o' ) ]: onToggle, } } /> + } <IconButton icon={ MenuIcon } aria-expanded={ isOpen } @@ -36,7 +37,7 @@ function BlockNavigationDropdown( { hasBlocks } ) { label={ __( 'Block Navigation' ) } className="editor-block-navigation" shortcut={ displayShortcut.access( 'o' ) } - aria-disabled={ ! hasBlocks } + disabled={ ! hasBlocks || isTextModeEnabled } /> </Fragment> ) } @@ -50,5 +51,6 @@ function BlockNavigationDropdown( { hasBlocks } ) { export default withSelect( ( select ) => { return { hasBlocks: !! select( 'core/editor' ).getBlockCount(), + isTextModeEnabled: select( 'core/edit-post' ).getEditorMode() === 'text', }; } )( BlockNavigationDropdown ); From 1653adf8e22a06cf15f7c475ef79744456011cbb Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Tue, 5 Feb 2019 15:22:28 +0000 Subject: [PATCH 340/691] Make the modal title styling consistent (#13669) --- packages/components/src/modal/style.scss | 4 ++-- .../src/components/keyboard-shortcut-help-modal/index.js | 8 +------- .../components/keyboard-shortcut-help-modal/style.scss | 4 ---- .../test/__snapshots__/index.js.snap | 8 +------- packages/edit-post/src/components/options-modal/index.js | 2 +- .../edit-post/src/components/options-modal/style.scss | 5 ----- .../options-modal/test/__snapshots__/index.js.snap | 8 +------- 7 files changed, 6 insertions(+), 33 deletions(-) diff --git a/packages/components/src/modal/style.scss b/packages/components/src/modal/style.scss index 99a76a85598c7..2058155f1f132 100644 --- a/packages/components/src/modal/style.scss +++ b/packages/components/src/modal/style.scss @@ -81,8 +81,8 @@ } .components-modal__header-heading { - font-size: 1em; - font-weight: 400; + font-size: 1rem; + font-weight: 600; } h1 { diff --git a/packages/edit-post/src/components/keyboard-shortcut-help-modal/index.js b/packages/edit-post/src/components/keyboard-shortcut-help-modal/index.js index 0eae08393a7cb..810519ccd3a49 100644 --- a/packages/edit-post/src/components/keyboard-shortcut-help-modal/index.js +++ b/packages/edit-post/src/components/keyboard-shortcut-help-modal/index.js @@ -69,12 +69,6 @@ const ShortcutSection = ( { title, shortcuts } ) => ( ); export function KeyboardShortcutHelpModal( { isModalActive, toggleModal } ) { - const title = ( - <span className="edit-post-keyboard-shortcut-help__title"> - { __( 'Keyboard Shortcuts' ) } - </span> - ); - return ( <Fragment> <KeyboardShortcuts @@ -86,7 +80,7 @@ export function KeyboardShortcutHelpModal( { isModalActive, toggleModal } ) { { isModalActive && ( <Modal className="edit-post-keyboard-shortcut-help" - title={ title } + title={ __( 'Keyboard Shortcuts' ) } closeLabel={ __( 'Close' ) } onRequestClose={ toggleModal } > diff --git a/packages/edit-post/src/components/keyboard-shortcut-help-modal/style.scss b/packages/edit-post/src/components/keyboard-shortcut-help-modal/style.scss index 9bb49eee5c189..da56dc8704582 100644 --- a/packages/edit-post/src/components/keyboard-shortcut-help-modal/style.scss +++ b/packages/edit-post/src/components/keyboard-shortcut-help-modal/style.scss @@ -1,8 +1,4 @@ .edit-post-keyboard-shortcut-help { - &__title { - font-size: 1rem; - font-weight: 600; - } &__section { margin: 0 0 2rem 0; diff --git a/packages/edit-post/src/components/keyboard-shortcut-help-modal/test/__snapshots__/index.js.snap b/packages/edit-post/src/components/keyboard-shortcut-help-modal/test/__snapshots__/index.js.snap index 65d9ef3a52aaf..fe522a985d3b9 100644 --- a/packages/edit-post/src/components/keyboard-shortcut-help-modal/test/__snapshots__/index.js.snap +++ b/packages/edit-post/src/components/keyboard-shortcut-help-modal/test/__snapshots__/index.js.snap @@ -14,13 +14,7 @@ exports[`KeyboardShortcutHelpModal should match snapshot when the modal is activ className="edit-post-keyboard-shortcut-help" closeLabel="Close" onRequestClose={[Function]} - title={ - <span - className="edit-post-keyboard-shortcut-help__title" - > - Keyboard Shortcuts - </span> - } + title="Keyboard Shortcuts" > <ShortcutSection key="0" diff --git a/packages/edit-post/src/components/options-modal/index.js b/packages/edit-post/src/components/options-modal/index.js index 1d6528815b59c..76cf40a74cd92 100644 --- a/packages/edit-post/src/components/options-modal/index.js +++ b/packages/edit-post/src/components/options-modal/index.js @@ -33,7 +33,7 @@ export function OptionsModal( { isModalActive, isViewable, closeModal } ) { return ( <Modal className="edit-post-options-modal" - title={ <span className="edit-post-options-modal__title">{ __( 'Options' ) }</span> } + title={ __( 'Options' ) } closeLabel={ __( 'Close' ) } onRequestClose={ closeModal } > diff --git a/packages/edit-post/src/components/options-modal/style.scss b/packages/edit-post/src/components/options-modal/style.scss index 28916c0be35e5..62b827d104ecd 100644 --- a/packages/edit-post/src/components/options-modal/style.scss +++ b/packages/edit-post/src/components/options-modal/style.scss @@ -1,9 +1,4 @@ .edit-post-options-modal { - &__title { - font-size: 1rem; - font-weight: 600; - } - &__section { margin: 0 0 2rem 0; } diff --git a/packages/edit-post/src/components/options-modal/test/__snapshots__/index.js.snap b/packages/edit-post/src/components/options-modal/test/__snapshots__/index.js.snap index 94d989e492e4a..a81252b32b95f 100644 --- a/packages/edit-post/src/components/options-modal/test/__snapshots__/index.js.snap +++ b/packages/edit-post/src/components/options-modal/test/__snapshots__/index.js.snap @@ -4,13 +4,7 @@ exports[`OptionsModal should match snapshot when the modal is active 1`] = ` <WithInstanceId(Modal) className="edit-post-options-modal" closeLabel="Close" - title={ - <span - className="edit-post-options-modal__title" - > - Options - </span> - } + title="Options" > <Section title="General" From d36862e95ffd4ee3fadf60c6ce18c9064ea0f985 Mon Sep 17 00:00:00 2001 From: Daniel Bachhuber <daniel@bachhuber.co> Date: Tue, 5 Feb 2019 08:13:02 -0800 Subject: [PATCH 341/691] Mention how 'core/notices' data store is accessed (#13593) This tiny amount of extra context is helpful for those who aren't fully oriented with the entire architecture. --- packages/notices/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/notices/README.md b/packages/notices/README.md index eeeba5f63d6ae..497f10042e255 100644 --- a/packages/notices/README.md +++ b/packages/notices/README.md @@ -16,7 +16,7 @@ _This package assumes that your code will run in an **ES2015+** environment. If ## Usage -When imported, the notices module registers a data store on the `core/notices` namespace. +When imported, the notices module registers a data store on the `core/notices` namespace. In WordPress, this is accessed from `wp.data.dispatch( 'core/notices' )`. For more information about consuming from a data store, refer to [the `@wordpress/data` documentation on _Data Access and Manipulation_](/packages/data/README.md#data-access-and-manipulation). From a1d79a9a8d9ec62152958c2e6351b5c69c748398 Mon Sep 17 00:00:00 2001 From: Danilo Ercoli <ercoli@gmail.com> Date: Tue, 5 Feb 2019 18:08:01 +0100 Subject: [PATCH 342/691] [RNMobile] Use RichText component on Title block (#13548) * Use RichText component in Title block for mobile. This is required to properly intercpet Enter.key on all platforms/keyboards. We decided to move to RichText since all of the work for Enter.key intercept was laready done there per Para and Heading blocks. * Fix lint * Set font family, weight, and size via RN props for Title, Heading, and Para blocks. * Fix lint * Adds font* props to PlainText for mobile * Make `serif` default family for RichText on mobile * Set the correct font family for Image caption on mobile * Set the correct font family for Nextpage block on mobile * Set the correct font family for Code block on mobile * set the correct font family for Image caption on mobile * Remove extra fontFamily props. * Remaps some font names so that they work in iOS. (#13628) * Remaps some font names so that they work in iOS. * Improved the logic for picking the default font in some components. * Modifies the logic that sets the default font for the code component. * Changes the font family in a css file. * Adds an import to have a default font for native. * Standardizes the default font for the code component. * Simplifies the default font for the code block. * Simplifies the default font for the code block. * Configures the default font for plain-text from a css file. * Fixes the styling for the rich text components and restores a line that was removed by mistake. * Fixes a linting problem. * Fixes some linting issues. * Fixes a linting issue. * Make sure PlainText takes in consideration the fontFamily passed via props before falling back to default styles --- .../block-library/src/code/edit.native.js | 1 + .../block-library/src/code/theme.android.scss | 4 -- .../{theme.ios.scss => theme.native.scss} | 5 ++- .../block-library/src/image/edit.native.js | 1 + .../src/nextpage/editor.native.scss | 3 ++ .../src/components/plain-text/index.native.js | 4 ++ .../components/plain-text/style.native.scss | 3 ++ .../src/components/post-title/index.native.js | 43 ++++++++++--------- .../src/components/rich-text/index.native.js | 5 +++ .../components/rich-text/style.native.scss | 5 +++ 10 files changed, 49 insertions(+), 25 deletions(-) delete mode 100644 packages/block-library/src/code/theme.android.scss rename packages/block-library/src/code/{theme.ios.scss => theme.native.scss} (56%) create mode 100644 packages/editor/src/components/rich-text/style.native.scss diff --git a/packages/block-library/src/code/edit.native.js b/packages/block-library/src/code/edit.native.js index d4ee2c8071c69..c072c01df5004 100644 --- a/packages/block-library/src/code/edit.native.js +++ b/packages/block-library/src/code/edit.native.js @@ -36,6 +36,7 @@ export default function CodeEdit( props ) { isSelected={ props.isSelected } onFocus={ onFocus } onBlur={ onBlur } + fontFamily={ ( styles.blockCode.fontFamily ) } /> </View> ); diff --git a/packages/block-library/src/code/theme.android.scss b/packages/block-library/src/code/theme.android.scss deleted file mode 100644 index 18d9c4cdcf2dc..0000000000000 --- a/packages/block-library/src/code/theme.android.scss +++ /dev/null @@ -1,4 +0,0 @@ -.blockCode { - font-family: monospace; -} - diff --git a/packages/block-library/src/code/theme.ios.scss b/packages/block-library/src/code/theme.native.scss similarity index 56% rename from packages/block-library/src/code/theme.ios.scss rename to packages/block-library/src/code/theme.native.scss index c553ba8e275f7..850db82eb0f44 100644 --- a/packages/block-library/src/code/theme.ios.scss +++ b/packages/block-library/src/code/theme.native.scss @@ -1,5 +1,8 @@ /* stylelint-disable font-family-no-missing-generic-family-keyword */ + +@import "variables.scss"; + .blockCode { - font-family: courier; + font-family: $default-monospace-font; } diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index daad9b0df5dde..776a033bc441a 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -272,6 +272,7 @@ export default class ImageEdit extends React.Component { <View style={ { padding: 12, flex: 1 } }> <TextInput style={ { textAlign: 'center' } } + fontFamily={ 'serif' } underlineColorAndroid="transparent" value={ caption } placeholder={ __( 'Write caption…' ) } diff --git a/packages/block-library/src/nextpage/editor.native.scss b/packages/block-library/src/nextpage/editor.native.scss index 0e75d4a7595c2..618ba896961be 100644 --- a/packages/block-library/src/nextpage/editor.native.scss +++ b/packages/block-library/src/nextpage/editor.native.scss @@ -1,5 +1,7 @@ // @format +@import "variables.scss"; + .block-library-nextpage__container { align-items: center; padding: 4px 4px 4px 4px; @@ -12,4 +14,5 @@ .block-library-nextpage__text { text-decoration-style: solid; + font-family: $default-regular-font; } diff --git a/packages/editor/src/components/plain-text/index.native.js b/packages/editor/src/components/plain-text/index.native.js index 35ffd2782a133..b5c3591b22a6c 100644 --- a/packages/editor/src/components/plain-text/index.native.js +++ b/packages/editor/src/components/plain-text/index.native.js @@ -47,6 +47,10 @@ export default class PlainText extends Component { } } onFocus={ this.props.onFocus } // always assign onFocus as a props onBlur={ this.props.onBlur } // always assign onBlur as a props + fontFamily={ this.props.fontFamily || ( styles[ 'editor-plain-text' ].fontFamily ) } + fontSize={ this.props.fontSize } + fontWeight={ this.props.fontWeight } + fontStyle={ this.props.fontStyle } /> ); } diff --git a/packages/editor/src/components/plain-text/style.native.scss b/packages/editor/src/components/plain-text/style.native.scss index 5e0face119c73..b4d0f9effbb4d 100644 --- a/packages/editor/src/components/plain-text/style.native.scss +++ b/packages/editor/src/components/plain-text/style.native.scss @@ -1,4 +1,7 @@ +@import "variables.scss"; + .editor-plain-text { + font-family: $default-regular-font; box-shadow: none; border-width: 0; diff --git a/packages/editor/src/components/post-title/index.native.js b/packages/editor/src/components/post-title/index.native.js index a06a1c94b4cec..c793cb4d0a113 100644 --- a/packages/editor/src/components/post-title/index.native.js +++ b/packages/editor/src/components/post-title/index.native.js @@ -1,27 +1,25 @@ -/** - * External dependencies - */ -import { TextInput } from 'react-native'; - /** * WordPress dependencies */ import { Component } from '@wordpress/element'; +import { RichText } from '@wordpress/editor'; import { decodeEntities } from '@wordpress/html-entities'; import { withDispatch } from '@wordpress/data'; import { withFocusOutside } from '@wordpress/components'; import { withInstanceId, compose } from '@wordpress/compose'; +const minHeight = 53; + class PostTitle extends Component { constructor() { super( ...arguments ); - this.onChange = this.onChange.bind( this ); this.onSelect = this.onSelect.bind( this ); this.onUnselect = this.onUnselect.bind( this ); this.state = { isSelected: false, + aztecHeight: 0, }; } @@ -38,10 +36,6 @@ class PostTitle extends Component { this.setState( { isSelected: false } ); } - onChange( title ) { - this.props.onUpdate( title ); - } - render() { const { placeholder, @@ -52,18 +46,27 @@ class PostTitle extends Component { const decodedPlaceholder = decodeEntities( placeholder ); return ( - <TextInput - blurOnSubmit={ true } - textAlignVertical="top" - multiline={ false } - onSubmitEditing={ this.props.onEnterPress } - returnKeyType={ 'next' } - onChangeText={ this.onChange } + <RichText + tagName={ 'p' } onFocus={ this.onSelect } + onBlur={ this.props.onBlur } // always assign onBlur as a props + multiline={ false } + style={ [ style, { + minHeight: Math.max( minHeight, this.state.aztecHeight ), + } ] } + fontSize={ 24 } + fontWeight={ 'bold' } + onChange={ ( event ) => { + this.props.onUpdate( event.content ); + } } + onContentSizeChange={ ( event ) => { + this.setState( { aztecHeight: event.aztecHeight } ); + } } placeholder={ decodedPlaceholder } - style={ style } - value={ title }> - </TextInput> + value={ title } + onSplit={ this.props.onEnterPress } + > + </RichText> ); } } diff --git a/packages/editor/src/components/rich-text/index.native.js b/packages/editor/src/components/rich-text/index.native.js index 692a51f751810..67d2a86b38957 100644 --- a/packages/editor/src/components/rich-text/index.native.js +++ b/packages/editor/src/components/rich-text/index.native.js @@ -28,6 +28,7 @@ import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ +import styles from './style.scss'; const FORMATTING_CONTROLS = [ { @@ -385,6 +386,10 @@ export class RichText extends Component { color={ 'black' } maxImagesWidth={ 200 } style={ style } + fontFamily={ this.props.fontFamily || styles[ 'editor-rich-text' ].fontFamily } + fontSize={ this.props.fontSize } + fontWeight={ this.props.fontWeight } + fontStyle={ this.props.fontStyle } /> </View> ); diff --git a/packages/editor/src/components/rich-text/style.native.scss b/packages/editor/src/components/rich-text/style.native.scss new file mode 100644 index 0000000000000..15c1b834a87ef --- /dev/null +++ b/packages/editor/src/components/rich-text/style.native.scss @@ -0,0 +1,5 @@ +@import "variables.scss"; + +.editor-rich-text { + font-family: $default-regular-font; +} From a8af1a97598587305d758df3c40359f019fc080c Mon Sep 17 00:00:00 2001 From: etoledom <etoledom@icloud.com> Date: Tue, 5 Feb 2019 19:28:56 +0100 Subject: [PATCH 343/691] Mobile: Link image setting (#13654) * rnmobile: Implement image settings button using InspectorControls.Slot pattern. * rnmobile: Add missing semicolon * rnmobile: Adding bottom-sheet component to mobile * rnmobile: Styling bottom-sheet header * rnmobile: Bottom-sheet clean up * rnmobile: Fix lint issues on bottom-sheet related code. * rnmobile: Fix small lint issues * rnmobile: Move inline toolbar button definition out of constant. * rnmobile: Remove extra white-spaces * revert package-lock.json changes * rnmobile: Fix merge issue * rnmobile: Imported BaseControl and TextinputControl to be used on Alt Image Settings * rnmobile: exporting component BottomSheet.Button to be used as bottom-sheet header buttons * rnmobile: Adding BottomSheet.Cell component as an extraction for BottomSheet users. * Fix lint issues * Reverting changes to package-lock.json * Fix merge issues * Remove Done button from Image settings bottom sheet * Make bottom-sheet avoid being behind keyboard * Fix lint issues * Making BottomSheet.Cell value editable as textinput. * Remove unnecesary onPress prop from Alt cell. * Image Settings: Added Link To setting to bottom-sheet * BottomSheet desing details * Fix bottom-sheet cell value growing larger than container * Bottom-sheet: Fix bottom safe-area inset with keyboard showing * Fix lint issues * Adding textinput props to bottom-sheet cell value. * Removing autocorrect from link-setting text input. * Fix lint issues * Fixing label texts on Image Settings bottom-sheet * Mobile ImageEdit color using global definition. --- .../block-library/src/image/edit.native.js | 25 ++++++++++++-- .../src/image/styles.native.scss | 4 +++ .../mobile/bottom-sheet/cell.native.js | 12 ++++--- .../mobile/bottom-sheet/index.native.js | 1 + .../mobile/bottom-sheet/styles.scss | 34 ++++++++++--------- 5 files changed, 53 insertions(+), 23 deletions(-) diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index 776a033bc441a..914332a76279a 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -28,6 +28,8 @@ const MEDIA_UPLOAD_STATE_SUCCEEDED = 2; const MEDIA_UPLOAD_STATE_FAILED = 3; const MEDIA_UPLOAD_STATE_RESET = 4; +const LINK_DESTINATION_CUSTOM = 'custom'; + export default class ImageEdit extends React.Component { constructor( props ) { super( props ); @@ -45,6 +47,7 @@ export default class ImageEdit extends React.Component { this.finishMediaUploadWithSuccess = this.finishMediaUploadWithSuccess.bind( this ); this.finishMediaUploadWithFailure = this.finishMediaUploadWithFailure.bind( this ); this.updateAlt = this.updateAlt.bind( this ); + this.onSetLinkDestination = this.onSetLinkDestination.bind( this ); this.onImagePressed = this.onImagePressed.bind( this ); } @@ -133,9 +136,16 @@ export default class ImageEdit extends React.Component { this.props.setAttributes( { alt: newAlt } ); } + onSetLinkDestination( href ) { + this.props.setAttributes( { + linkDestination: LINK_DESTINATION_CUSTOM, + href, + } ); + } + render() { const { attributes, isSelected, setAttributes } = this.props; - const { url, caption, height, width, alt } = attributes; + const { url, caption, height, width, alt, href } = attributes; const onMediaLibraryButtonPressed = () => { requestMediaPickFromMediaLibrary( ( mediaId, mediaUrl ) => { @@ -197,6 +207,15 @@ export default class ImageEdit extends React.Component { onClose={ onImageSettingsClose } hideHeader > + <BottomSheet.Cell + icon={ 'admin-links' } + label={ __( 'Link To' ) } + value={ href || '' } + valuePlaceholder={ __( 'Add URL' ) } + onChangeValue={ this.onSetLinkDestination } + autoCapitalize="none" + autoCorrect={ false } + /> <BottomSheet.Cell icon={ 'editor-textcolor' } label={ __( 'Alt Text' ) } @@ -205,8 +224,8 @@ export default class ImageEdit extends React.Component { onChangeValue={ this.updateAlt } /> <BottomSheet.Cell - label={ __( 'Reset to original' ) } - labelStyle={ { color: 'red' } } + label={ __( 'Reset to Original' ) } + labelStyle={ styles.resetSettingsButton } drawSeparator={ false } onPress={ () => {} } /> diff --git a/packages/block-library/src/image/styles.native.scss b/packages/block-library/src/image/styles.native.scss index 833b8126bfc1a..95dacc3cd4c50 100644 --- a/packages/block-library/src/image/styles.native.scss +++ b/packages/block-library/src/image/styles.native.scss @@ -15,3 +15,7 @@ flex-direction: column; align-items: center; } + +.resetSettingsButton { + color: $alert-red; +} diff --git a/packages/editor/src/components/mobile/bottom-sheet/cell.native.js b/packages/editor/src/components/mobile/bottom-sheet/cell.native.js index 6dc8ff4b26ef2..1a24d12952256 100644 --- a/packages/editor/src/components/mobile/bottom-sheet/cell.native.js +++ b/packages/editor/src/components/mobile/bottom-sheet/cell.native.js @@ -24,28 +24,30 @@ export default function Cell( props ) { labelStyle = {}, valueStyle = {}, onChangeValue, + ...valueProps } = props; const showValue = value !== undefined; const isValueEditable = onChangeValue !== undefined; const defaultLabelStyle = showValue ? styles.cellLabel : styles.cellLabelCentered; + const separatorStyle = showValue ? styles.cellSeparator : styles.separator; let valueTextInput; const onCellPress = () => { if ( isValueEditable ) { valueTextInput.focus(); - } else { + } else if ( onPress !== undefined ) { onPress(); } }; return ( - <TouchableOpacity onPress={ onCellPress }> + <TouchableOpacity onPress={ onCellPress } > <View style={ styles.cellContainer }> <View style={ styles.cellRowContainer }> { icon && ( <View style={ styles.cellRowContainer }> - <Dashicon icon={ icon } size={ 30 } /> + <Dashicon icon={ icon } size={ 24 } /> <View style={ { width: 12 } } /> </View> ) } @@ -60,13 +62,15 @@ export default function Cell( props ) { style={ { ...styles.cellValue, ...valueStyle } } value={ value } placeholder={ valuePlaceholder } + placeholderTextColor={ '#87a6bc' } onChangeText={ onChangeValue } editable={ isValueEditable } + { ...valueProps } /> ) } </View> { drawSeparator && ( - <View style={ styles.separator } /> + <View style={ separatorStyle } /> ) } </TouchableOpacity> ); diff --git a/packages/editor/src/components/mobile/bottom-sheet/index.native.js b/packages/editor/src/components/mobile/bottom-sheet/index.native.js index e89bc50d44e28..d4754a3909dfa 100644 --- a/packages/editor/src/components/mobile/bottom-sheet/index.native.js +++ b/packages/editor/src/components/mobile/bottom-sheet/index.native.js @@ -61,6 +61,7 @@ class BottomSheet extends Component { <KeyboardAvoidingView behavior={ Platform.OS === 'ios' && 'padding' } style={ { ...styles.content, borderColor: 'rgba(0, 0, 0, 0.1)' } } + keyboardVerticalOffset={ -this.state.safeAreaBottomInset } > <View style={ styles.dragIndicator } /> { hideHeader || ( diff --git a/packages/editor/src/components/mobile/bottom-sheet/styles.scss b/packages/editor/src/components/mobile/bottom-sheet/styles.scss index f02aa77fe4498..139c79d88de56 100644 --- a/packages/editor/src/components/mobile/bottom-sheet/styles.scss +++ b/packages/editor/src/components/mobile/bottom-sheet/styles.scss @@ -9,7 +9,6 @@ background-color: $light-gray-400; height: 4px; width: 10%; - top: -12px; margin: auto; border-radius: 2px; } @@ -18,12 +17,12 @@ background-color: $light-gray-400; height: 1px; width: 100%; + margin-bottom: 14px; } .content { + padding: 6px 16px 0 16px; background-color: $white; - padding: 18px 10px 5px 10px; - justify-content: center; border-top-right-radius: 8px; border-top-left-radius: 8px; } @@ -59,41 +58,44 @@ // Cell -//Bottom Sheet - .cellContainer { flex-direction: row; - min-height: 50; - justify-content: space-between; - margin-left: 12; - margin-right: 12; + min-height: 48; align-items: center; } +.cellSeparator { + background-color: $light-gray-400; + height: 1px; + width: 100%; + margin-left: 36px; +} + .cellRowContainer { flex-direction: row; align-items: center; } .cellIcon { - padding-right: 30; + padding-right: 0; } .cellLabel { - font-size: 18px; - color: #000; + font-size: 17px; + color: #2e4453; margin-right: 12px; } .cellLabelCentered { - font-size: 18px; - color: #000; + font-size: 17px; + color: #2e4453; flex: 1; text-align: center; } .cellValue { - font-size: 18px; - color: $dark-gray-400; + font-size: 17px; + color: #2e4453; text-align: right; + flex: 1; } From 2c305e5d9e5461f6f168a14b7fb106793cd0c63a Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Tue, 5 Feb 2019 13:32:25 -0500 Subject: [PATCH 344/691] Project: Avoid additional native-file code owner notification (#13675) * Project: Assign native code ownership to GitHub empty (ghost) user * Project: Add *.native.scss code ownership exception --- .github/CODEOWNERS | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c9a533d91a81b..e57180a7fe2c5 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -75,5 +75,6 @@ # Documentation /docs @youknowriad @gziolo @chrisvanpatten @mkaz @ajitbohra @nosolosw -# Native -*.native.js @daniloercoli @diegoreymendez @etoledom @hypest @koke @marecar3 @mzorz @pinarol @SergioEstevao @tug +# Native (Unowned) +*.native.js @ghost +*.native.scss @ghost From 0a5b11fc0aafa65085fdd568a41d3488e49cdd1e Mon Sep 17 00:00:00 2001 From: Adam Silverstein <adam@10up.com> Date: Tue, 5 Feb 2019 12:46:07 -0700 Subject: [PATCH 345/691] Update CODEOWNERS --- .github/CODEOWNERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e57180a7fe2c5..e29e3266964f2 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -62,8 +62,8 @@ /packages/wordcount @youknowriad @gziolo @aduth # Extensibility -/packages/hooks @youknowriad @gziolo @aduth -/packages/plugins @youknowriad @gziolo @aduth +/packages/hooks @youknowriad @gziolo @aduth @adamsilverstein +/packages/plugins @youknowriad @gziolo @aduth @adamsilverstein # Rich Text /packages/format-library @youknowriad @gziolo @aduth @iseulde @jorgefilipecosta From 4c57b59e670cedd21149a2b703815288e5369a0b Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.p.richards@gmail.com> Date: Wed, 6 Feb 2019 16:39:32 +0800 Subject: [PATCH 346/691] Add talldan and noisysocks to codeowners file (#13685) * Add talldan to codeowners file * Add noisysocks to codeowners file * Add talldan to a few more places --- .github/CODEOWNERS | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e29e3266964f2..e644c9034890b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,21 +1,21 @@ # Data /packages/api-fetch @youknowriad @aduth @nerrad @mmtr -/packages/core-data @youknowriad @gziolo @aduth @nerrad +/packages/core-data @youknowriad @gziolo @aduth @nerrad @noisysocks /packages/data @youknowriad @aduth @nerrad @coderkevin /packages/redux-routine @youknowriad @aduth @nerrad # Blocks -/packages/block-library @youknowriad @gziolo @Soean @ajitbohra @jorgefilipecosta +/packages/block-library @youknowriad @gziolo @Soean @ajitbohra @jorgefilipecosta @talldan @noisysocks # Editor /packages/annotations @youknowriad @gziolo @aduth @atimmer /packages/autop @youknowriad @aduth /packages/block-serialization-spec-parser @youknowriad @gziolo @aduth /packages/block-serialization-default-parser @youknowriad @gziolo @aduth -/packages/blocks @youknowriad @gziolo @aduth -/packages/edit-post @youknowriad @gziolo -/packages/editor @youknowriad @gziolo @nosolosw -/packages/list-reusable-blocks @youknowriad @aduth +/packages/blocks @youknowriad @gziolo @aduth @noisysocks +/packages/edit-post @youknowriad @gziolo @talldan @noisysocks +/packages/editor @youknowriad @gziolo @nosolosw @talldan @noisysocks +/packages/list-reusable-blocks @youknowriad @aduth @noisysocks /packages/shortcode @youknowriad @aduth # Tooling @@ -26,7 +26,7 @@ /packages/browserslist-config @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra /packages/custom-templated-path-webpack-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra /packages/e2e-test-utils @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra -/packages/e2e-tests @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra +/packages/e2e-tests @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @talldan /packages/eslint-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra /packages/jest-console @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra /packages/jest-preset-default @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @@ -37,12 +37,12 @@ /packages/scripts @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra # UI Components -/packages/components @youknowriad @gziolo @aduth @chrisvanpatten @ajitbohra @jaymanpandya @nosolosw @jorgefilipecosta -/packages/compose @youknowriad @gziolo @aduth @chrisvanpatten @ajitbohra @jaymanpandya @jorgefilipecosta -/packages/element @youknowriad @gziolo @aduth @chrisvanpatten @ajitbohra @jaymanpandya @jorgefilipecosta -/packages/notices @youknowriad @gziolo @aduth @chrisvanpatten @ajitbohra @jaymanpandya @jorgefilipecosta -/packages/nux @youknowriad @gziolo @aduth @chrisvanpatten @ajitbohra @jaymanpandya @jorgefilipecosta -/packages/viewport @youknowriad @gziolo @aduth @chrisvanpatten @ajitbohra @jaymanpandya @jorgefilipecosta +/packages/components @youknowriad @gziolo @aduth @chrisvanpatten @ajitbohra @jaymanpandya @nosolosw @jorgefilipecosta @talldan @noisysocks +/packages/compose @youknowriad @gziolo @aduth @chrisvanpatten @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks +/packages/element @youknowriad @gziolo @aduth @chrisvanpatten @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks +/packages/notices @youknowriad @gziolo @aduth @chrisvanpatten @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks +/packages/nux @youknowriad @gziolo @aduth @chrisvanpatten @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks +/packages/viewport @youknowriad @gziolo @aduth @chrisvanpatten @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks # Utilities /packages/a11y @youknowriad @gziolo @aduth @@ -55,10 +55,10 @@ /packages/html-entities @youknowriad @gziolo @aduth /packages/i18n @youknowriad @aduth @swissspidy /packages/is-shallow-equal @youknowriad @gziolo @aduth -/packages/keycodes @youknowriad @gziolo @aduth +/packages/keycodes @youknowriad @gziolo @aduth @talldan /packages/priority-queue @youknowriad @gziolo @aduth /packages/token-list @youknowriad @gziolo @aduth -/packages/url @youknowriad @gziolo @aduth +/packages/url @youknowriad @gziolo @aduth @talldan /packages/wordcount @youknowriad @gziolo @aduth # Extensibility From 6e69c49afdd1920e0733955f4f981726ecb910cc Mon Sep 17 00:00:00 2001 From: Joen Asmussen <joen@automattic.com> Date: Wed, 6 Feb 2019 10:00:54 +0100 Subject: [PATCH 347/691] Try: Restore block mover for floats. (#12758) * Try: Restore block mover for floats. This PR hopes to fix #12736, #11424, #10300. Currently we hide the block mover for any floated block. We do this because if you have a left floated image followed by a paragraph, the block mover for the image would overlap exactly with the block mover for the subsequent paragraph block. This would cause moving a float to be fiddly and virtually impossible as the hover states for the two conflicted. This PR is an experiment to mitigate, or possibly fix that. It's a try branch because it is not necessarily a solid fix. What this PR does: - It makes it so the block mover cannot be invoked when only hovering a float, but when the float is selected they are permanently visible. - It changes some z-indexes so floats always have a higher z-index than not a float. - It changes the footprint of the side-UI container to "cover up" the underlying paragraph block, so it won't invoke unlesss you move the mouse below the footprint. This is best explained in some GIFs. The reason this is a "Try" branch is that when you move the mouse over the adjacent paragraph, the block mover is show as overlapping the selected floats block mover. But due to the rearranging of z-indexes, at least this is only a visual issue. A click on the floats block mover will still work as intended. Why can't the float have on-hover block movers like every other block, you ask? Picture again the test-case of a left floated image followed by a paragraph of text. In this case, hovering over the image would show a block mover that would visually appear to be that of the paragraph block. By showing it when the float is selected, the context is clear. We could experiment with not showing the hover block mover for float-adjacent blocks when hovered, but this isn't feasible because you might have a float, and then a block that clears this float, in which case the rule would break down. Lay your thoughts on me. * Address feedback, and improve dragging edgecase. This addresses comment feedback by @ZebulanStanphill, thanks, and it also hides the block mover from the ghost when you are dragging. * Add borders around floats. --- assets/stylesheets/_z-index.scss | 8 +- .../editor/src/components/block-list/block.js | 30 ++++---- .../src/components/block-list/style.scss | 74 +++++++++++++------ .../src/components/block-mover/style.scss | 2 + 4 files changed, 75 insertions(+), 39 deletions(-) diff --git a/assets/stylesheets/_z-index.scss b/assets/stylesheets/_z-index.scss index b21bff104a6ca..b8f00482e84af 100644 --- a/assets/stylesheets/_z-index.scss +++ b/assets/stylesheets/_z-index.scss @@ -5,7 +5,6 @@ $z-layers: ( ".editor-block-list__block-edit::before": 0, ".editor-block-switcher__arrow": 1, - ".editor-block-list__block {core/image aligned left or right}": 20, ".editor-block-list__block {core/image aligned wide or fullwide}": 20, ".block-library-classic__toolbar": 10, ".editor-block-list__layout .reusable-block-indicator": 1, @@ -48,9 +47,12 @@ $z-layers: ( ".components-drop-zone": 100, ".components-drop-zone__content": 110, - // Block controls, particularly in nested contexts, floats aside block and + // The block mover, particularly in nested contexts, // should overlap most block content. - ".editor-block-list__block.is-{selected,hovered} .editor-block-{settings-menu,mover}": 80, + ".editor-block-list__block.is-{selected,hovered} .editor-block-mover": 80, + + // The block mover for floats should overlap the controls of adjacent blocks. + ".editor-block-list__block {core/image aligned left or right}": 81, // Small screen inner blocks overlay must be displayed above drop zone, // settings menu, and movers. diff --git a/packages/editor/src/components/block-list/block.js b/packages/editor/src/components/block-list/block.js index 82e7154b76995..fc79575258011 100644 --- a/packages/editor/src/components/block-list/block.js +++ b/packages/editor/src/components/block-list/block.js @@ -519,25 +519,25 @@ export class BlockListBlock extends Component { clientId={ clientId } rootClientId={ rootClientId } /> - { shouldRenderMovers && ( - <BlockMover - clientIds={ clientId } - blockElementId={ blockElementId } - isFirst={ isFirst } - isLast={ isLast } - isHidden={ ! ( isHovered || isSelected ) || hoverArea !== 'left' } - isDraggable={ - isDraggable !== false && - ( ! isPartOfMultiSelection && isMovable ) - } - onDragStart={ this.onDragStart } - onDragEnd={ this.onDragEnd } - /> - ) } { isFirstMultiSelected && ( <BlockMultiControls rootClientId={ rootClientId } /> ) } <div className="editor-block-list__block-edit"> + { shouldRenderMovers && ( + <BlockMover + clientIds={ clientId } + blockElementId={ blockElementId } + isFirst={ isFirst } + isLast={ isLast } + isHidden={ ! ( isHovered || isSelected ) || hoverArea !== 'left' } + isDraggable={ + isDraggable !== false && + ( ! isPartOfMultiSelection && isMovable ) + } + onDragStart={ this.onDragStart } + onDragEnd={ this.onDragEnd } + /> + ) } { shouldShowBreadcrumb && ( <BlockBreadcrumb clientId={ clientId } diff --git a/packages/editor/src/components/block-list/style.scss b/packages/editor/src/components/block-list/style.scss index b89d2ac389494..a024bb2198d06 100644 --- a/packages/editor/src/components/block-list/style.scss +++ b/packages/editor/src/components/block-list/style.scss @@ -1,8 +1,8 @@ .editor-block-list__layout .components-draggable__clone { - // Hide the Block UI when dragging the block - // This ensures the page scroll properly (no sticky elements) + // Hide the Block UI when dragging the block. + // This ensures the page scroll properly (no sticky elements). .editor-block-contextual-toolbar { - // I think important is fine here to avoid over complexing the selector + // It's probably okay to use !important here to avoid over-complicating the selector. display: none !important; } } @@ -127,7 +127,7 @@ } } - // Selected style + // Selected style. &.is-selected > .editor-block-list__block-edit::before { // Use opacity to work in various editor styles. outline: $border-width solid $dark-opacity-light-500; @@ -137,12 +137,12 @@ } } - // Hover style + // Hover style. &.is-hovered > .editor-block-list__block-edit::before { outline: $border-width solid theme(outlines); } - // Spotlight mode + // Spotlight mode. &.is-focus-mode:not(.is-multi-selected) { opacity: 0.5; transition: opacity 0.1s linear; @@ -168,7 +168,7 @@ background-color: $blue-medium-highlight; } - // selection style for multiple blocks + // Selection style for multiple blocks. &.is-multi-selected *::selection { background-color: transparent; } @@ -179,7 +179,7 @@ // Use opacity to work in various editor styles. mix-blend-mode: multiply; - // Collapse extra vertical padding on selection + // Collapse extra vertical padding on selection. top: -$block-padding; bottom: -$block-padding; @@ -293,12 +293,6 @@ margin-bottom: $border-width; } - // Hide all additional UI on floats. - .editor-block-mover, - .editor-block-list__block-mobile-toolbar { - display: none; - } - // Position toolbar better on mobile. .editor-block-contextual-toolbar { width: auto; @@ -506,7 +500,8 @@ .editor-block-list__block { // Left and right block settings and mover. - > .editor-block-mover { + &.is-multi-selected > .editor-block-mover, + > .editor-block-list__block-edit > .editor-block-mover { position: absolute; width: $block-side-ui-width + $block-side-ui-clearance; @@ -516,7 +511,8 @@ } // Position depending on whether selected or not. - > .editor-block-mover { + &.is-multi-selected > .editor-block-mover, + > .editor-block-list__block-edit > .editor-block-mover { top: -$block-padding - $border-width; } @@ -526,24 +522,60 @@ &.is-selected, &.is-hovered { .editor-block-mover { - z-index: z-index(".editor-block-list__block.is-{selected,hovered} .editor-block-{settings-menu,mover}"); + z-index: z-index(".editor-block-list__block.is-{selected,hovered} .editor-block-mover"); } } } // Left side UI. - > .editor-block-mover { + &.is-multi-selected > .editor-block-mover, + > .editor-block-list__block-edit > .editor-block-mover { padding-right: $block-side-ui-clearance; - // Position for top level blocks - left: -$block-side-ui-width - $block-side-ui-clearance; + // Position for top level blocks. + left: -$block-side-ui-width - $block-side-ui-clearance - $block-padding - $border-width; - // Mobile + // Hide on mobile, as mobile has a separate solution. display: none; @include break-small() { display: block; } } + + &.is-multi-selected > .editor-block-mover { + left: -$block-side-ui-width - $block-side-ui-clearance; + } + + // For floats, show block mover when block is selected, and never on hover. + &[data-align="left"], + &[data-align="right"] { + // Show always when the block is selected. + &.is-selected > .editor-block-list__block-edit > .editor-block-mover { + // Don't show on mobile, allow the special mobile toolbar to work there. + display: none; + @include break-small() { + display: block; + opacity: 1; + animation: none; + + // Make wider and taller to make "safe" hover area bigger. + // The intent is to make it less likely that you hover float-adjacent + // blocks that visually appear below the block. + width: $block-side-ui-width + $block-side-ui-clearance + $block-padding + $border-width; + height: auto; + padding-bottom: $block-padding; + + // Unset the negative top margin, or it might overlap the block toolbar. + margin-top: 0; + } + } + + // Don't show on hover, or on the "ghost" when dragging. + &.is-hovered > .editor-block-list__block-edit > .editor-block-mover, + &.is-dragging > .editor-block-list__block-edit > .editor-block-mover { + display: none; + } + } } diff --git a/packages/editor/src/components/block-mover/style.scss b/packages/editor/src/components/block-mover/style.scss index 8c6a0b6e4136c..12ebccbe0035e 100644 --- a/packages/editor/src/components/block-mover/style.scss +++ b/packages/editor/src/components/block-mover/style.scss @@ -87,6 +87,8 @@ .editor-block-mover__control-drag-handle:not(:disabled):not([aria-disabled="true"]):not(.is-default), .editor-block-mover__control { @include break-small() { + .editor-block-list__layout [data-align="right"] &, + .editor-block-list__layout [data-align="left"] &, .editor-block-list__layout .editor-block-list__layout & { background: $white; box-shadow: inset 0 0 0 1px $light-gray-500; From f0bb097d0a3a14dec7b04ba174a94df806809447 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Wed, 6 Feb 2019 10:17:11 +0100 Subject: [PATCH 348/691] Introduce the concept of registry selectors (#13662) --- packages/core-data/src/selectors.js | 21 ++++----------------- packages/data/src/factory.js | 12 ++++++++++++ packages/data/src/index.js | 1 + packages/data/src/namespace-store.js | 12 ++++++++---- packages/data/src/test/registry.js | 22 ++++++++++++++++++++++ 5 files changed, 47 insertions(+), 21 deletions(-) create mode 100644 packages/data/src/factory.js diff --git a/packages/core-data/src/selectors.js b/packages/core-data/src/selectors.js index b9ed8b575e086..36130c3439df6 100644 --- a/packages/core-data/src/selectors.js +++ b/packages/core-data/src/selectors.js @@ -7,7 +7,7 @@ import { map, find, get, filter, compact, defaultTo } from 'lodash'; /** * WordPress dependencies */ -import { select } from '@wordpress/data'; +import { createRegistrySelector } from '@wordpress/data'; import deprecated from '@wordpress/deprecated'; /** @@ -16,19 +16,6 @@ import deprecated from '@wordpress/deprecated'; import { REDUCER_KEY } from './name'; import { getQueriedItems } from './queried-data'; -/** - * Returns true if resolution is in progress for the core selector of the given - * name and arguments. - * - * @param {string} selectorName Core data selector name. - * @param {...*} args Arguments passed to selector. - * - * @return {boolean} Whether resolution is in progress. - */ -function isResolving( selectorName, ...args ) { - return select( 'core/data' ).isResolving( REDUCER_KEY, selectorName, args ); -} - /** * Returns true if a request is in progress for embed preview data, or false * otherwise. @@ -38,9 +25,9 @@ function isResolving( selectorName, ...args ) { * * @return {boolean} Whether a request is in progress for an embed preview. */ -export function isRequestingEmbedPreview( state, url ) { - return isResolving( 'getEmbedPreview', url ); -} +export const isRequestingEmbedPreview = createRegistrySelector( ( registry ) => ( state, url ) => { + return registry.select( 'core/data' ).isResolving( REDUCER_KEY, 'getEmbedPreview', [ url ] ); +} ); /** * Returns all available authors. diff --git a/packages/data/src/factory.js b/packages/data/src/factory.js new file mode 100644 index 0000000000000..866ce64052839 --- /dev/null +++ b/packages/data/src/factory.js @@ -0,0 +1,12 @@ +/** + * Mark a function as a registry selector. + * + * @param {function} registrySelector Function receiving a registry object and returning a state selector. + * + * @return {function} marked registry selector. + */ +export function createRegistrySelector( registrySelector ) { + registrySelector.isRegistrySelector = true; + + return registrySelector; +} diff --git a/packages/data/src/index.js b/packages/data/src/index.js index 6574f002d47e4..a5d1a51214654 100644 --- a/packages/data/src/index.js +++ b/packages/data/src/index.js @@ -15,6 +15,7 @@ export { default as RegistryProvider, RegistryConsumer } from './components/regi export { default as __experimentalAsyncModeProvider } from './components/async-mode-provider'; export { createRegistry } from './registry'; export { plugins }; +export { createRegistrySelector } from './factory'; /** * The combineReducers helper function turns an object whose values are different diff --git a/packages/data/src/namespace-store.js b/packages/data/src/namespace-store.js index dc0c0075fd620..2e5f2b3022a3f 100644 --- a/packages/data/src/namespace-store.js +++ b/packages/data/src/namespace-store.js @@ -19,7 +19,7 @@ import createResolversCacheMiddleware from './resolvers-cache-middleware'; * * @param {string} key Identifying string used for namespace and redex dev tools. * @param {Object} options Contains reducer, actions, selectors, and resolvers. - * @param {Object} registry Temporary registry reference, required for namespace updates. + * @param {Object} registry Registry reference. * * @return {Object} Store Object. */ @@ -32,7 +32,7 @@ export default function createNamespace( key, options, registry ) { actions = mapActions( options.actions, store ); } if ( options.selectors ) { - selectors = mapSelectors( options.selectors, store ); + selectors = mapSelectors( options.selectors, store, registry ); } if ( options.resolvers ) { const fulfillment = getCoreDataFulfillment( registry, key ); @@ -100,10 +100,14 @@ function createReduxStore( reducer, key, registry ) { * public facing API. Selectors will get passed the * state as first argument. * @param {Object} store The redux store to which the selectors should be mapped. + * @param {Object} registry Registry reference. + * * @return {Object} Selectors mapped to the redux store provided. */ -function mapSelectors( selectors, store ) { - const createStateSelector = ( selector ) => function runSelector() { +function mapSelectors( selectors, store, registry ) { + const createStateSelector = ( registeredSelector ) => function runSelector() { + const selector = registeredSelector.isRegistrySelector ? registeredSelector( registry ) : registeredSelector; + // This function is an optimized implementation of: // // selector( store.getState(), ...arguments ) diff --git a/packages/data/src/test/registry.js b/packages/data/src/test/registry.js index 70dc39fae0a07..e85495240e1f3 100644 --- a/packages/data/src/test/registry.js +++ b/packages/data/src/test/registry.js @@ -7,6 +7,7 @@ import { castArray, mapValues } from 'lodash'; * Internal dependencies */ import { createRegistry } from '../registry'; +import { createRegistrySelector } from '../factory'; describe( 'createRegistry', () => { let registry; @@ -441,6 +442,27 @@ describe( 'createRegistry', () => { expect( registry.select( 'reducer1' ).selector2() ).toEqual( 'result2' ); expect( selector2 ).toBeCalledWith( store.getState() ); } ); + + it( 'should run the registry selectors properly', () => { + const selector1 = () => 'result1'; + const selector2 = createRegistrySelector( ( reg ) => () => + reg.select( 'reducer1' ).selector1() + ); + registry.registerStore( 'reducer1', { + reducer: () => 'state1', + selectors: { + selector1, + }, + } ); + registry.registerStore( 'reducer2', { + reducer: () => 'state1', + selectors: { + selector2, + }, + } ); + + expect( registry.select( 'reducer2' ).selector2() ).toEqual( 'result1' ); + } ); } ); describe( 'subscribe', () => { From 7c33661f611ac87b52238e537868d75eaed9e67e Mon Sep 17 00:00:00 2001 From: JR Tashjian <jrtashjian@gmail.com> Date: Wed, 6 Feb 2019 04:17:49 -0500 Subject: [PATCH 349/691] FormToggle: component styles for external use (#12385) * FormToggle wrapper class needs inline-block styling. Necessary for proper positioning of elements within .components-form-toggle. * Elements within FormToggle component need to have border-box box-sizing. This styling occurs within the block-editor on the post edit screen but does not exist outside of it. Moving this box-sizing value to the component resolves #8871. --- packages/components/src/form-toggle/style.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/components/src/form-toggle/style.scss b/packages/components/src/form-toggle/style.scss index 0def01e03beaa..51280da0ac8da 100644 --- a/packages/components/src/form-toggle/style.scss +++ b/packages/components/src/form-toggle/style.scss @@ -4,12 +4,14 @@ $toggle-border-width: 2px; .components-form-toggle { position: relative; + display: inline-block; // On/Off icon indicators. .components-form-toggle__on, .components-form-toggle__off { position: absolute; top: $toggle-border-width * 3; + box-sizing: border-box; } .components-form-toggle__off { @@ -26,6 +28,7 @@ $toggle-border-width: 2px; .components-form-toggle__track { content: ""; display: inline-block; + box-sizing: border-box; vertical-align: top; background-color: $white; border: $toggle-border-width solid $dark-gray-300; @@ -38,6 +41,7 @@ $toggle-border-width: 2px; .components-form-toggle__thumb { display: block; position: absolute; + box-sizing: border-box; top: $toggle-border-width * 2; left: $toggle-border-width * 2; width: $toggle-height - ($toggle-border-width * 4); From d82a3bcde825bb23991c2790aad10844169d3f3c Mon Sep 17 00:00:00 2001 From: Glen Young <glen@geekpulp.co.nz> Date: Wed, 6 Feb 2019 23:01:26 +1300 Subject: [PATCH 350/691] Update sidebar spacing (#13181) * Update sidebar spacing * Update packages/components/src/base-control/style.scss Co-Authored-By: geekpulp <glen@geekpulp.co.nz> * updated use of $grid-size Increased the size of some spacing to allow more room between elements. * Commit fixes --- packages/components/src/base-control/style.scss | 5 ++++- .../components/sidebar/settings-sidebar/style.scss | 11 ++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/components/src/base-control/style.scss b/packages/components/src/base-control/style.scss index fe065ce93fac5..e3af118add5e1 100644 --- a/packages/components/src/base-control/style.scss +++ b/packages/components/src/base-control/style.scss @@ -18,6 +18,9 @@ .components-base-control__help { margin-top: -$grid-size; font-style: italic; - margin-bottom: 0; } } + +.components-base-control + .components-base-control { + margin-bottom: $grid-size-large; +} diff --git a/packages/edit-post/src/components/sidebar/settings-sidebar/style.scss b/packages/edit-post/src/components/sidebar/settings-sidebar/style.scss index 0640c4312e03c..83749d10a4d61 100644 --- a/packages/edit-post/src/components/sidebar/settings-sidebar/style.scss +++ b/packages/edit-post/src/components/sidebar/settings-sidebar/style.scss @@ -1,12 +1,13 @@ .edit-post-settings-sidebar__panel-block .components-panel__body { border: none; border-top: $border-width solid $light-gray-500; - margin: 0 -16px; + margin: 0 #{ -$grid-size-large }; .components-base-control { - margin: 0 0 1.5em 0; + margin-bottom: #{ $grid-size * 3 }; + &:last-child { - margin-bottom: 0.5em; + margin-bottom: $grid-size; } } @@ -15,10 +16,10 @@ } &:first-child { - margin-top: 16px; + margin-top: $grid-size-large; } &:last-child { - margin-bottom: -16px; + margin-bottom: -$grid-size-large; } } From 669d0c59a4489e83790492cc88292d87116822c8 Mon Sep 17 00:00:00 2001 From: Danilo Ercoli <ercoli@gmail.com> Date: Wed, 6 Feb 2019 11:14:56 +0100 Subject: [PATCH 351/691] [RNMobile] Do not use hard coded `fontFamily` in Image block (#13677) * Use RichText component in Title block for mobile. This is required to properly intercpet Enter.key on all platforms/keyboards. We decided to move to RichText since all of the work for Enter.key intercept was laready done there per Para and Heading blocks. * Fix lint * Set font family, weight, and size via RN props for Title, Heading, and Para blocks. * Fix lint * Adds font* props to PlainText for mobile * Make `serif` default family for RichText on mobile * Set the correct font family for Image caption on mobile * Set the correct font family for Nextpage block on mobile * Set the correct font family for Code block on mobile * set the correct font family for Image caption on mobile * Remove extra fontFamily props. * Remaps some font names so that they work in iOS. (#13628) * Remaps some font names so that they work in iOS. * Improved the logic for picking the default font in some components. * Modifies the logic that sets the default font for the code component. * Changes the font family in a css file. * Adds an import to have a default font for native. * Standardizes the default font for the code component. * Simplifies the default font for the code block. * Simplifies the default font for the code block. * Configures the default font for plain-text from a css file. * Fixes the styling for the rich text components and restores a line that was removed by mistake. * Fixes a linting problem. * Fixes some linting issues. * Fixes a linting issue. * Make sure PlainText takes in consideration the fontFamily passed via props before falling back to default styles * Do not use hard coded `fontFamily` value, instead use CSS style. * Add missing new line at end of style --- packages/block-library/src/image/edit.native.js | 2 +- packages/block-library/src/image/styles.native.scss | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index 914332a76279a..66b35ea5ef726 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -291,7 +291,7 @@ export default class ImageEdit extends React.Component { <View style={ { padding: 12, flex: 1 } }> <TextInput style={ { textAlign: 'center' } } - fontFamily={ 'serif' } + fontFamily={ this.props.fontFamily || ( styles[ 'caption-text' ].fontFamily ) } underlineColorAndroid="transparent" value={ caption } placeholder={ __( 'Write caption…' ) } diff --git a/packages/block-library/src/image/styles.native.scss b/packages/block-library/src/image/styles.native.scss index 95dacc3cd4c50..cc3566080f666 100644 --- a/packages/block-library/src/image/styles.native.scss +++ b/packages/block-library/src/image/styles.native.scss @@ -1,3 +1,7 @@ +// @format + +@import "variables.scss"; + .imageContainer { flex: 1; justify-content: center; @@ -16,6 +20,10 @@ align-items: center; } +.caption-text { + font-family: $default-regular-font; +} + .resetSettingsButton { color: $alert-red; } From 71186b2531304dce691bde8e6e4b69ff8409df45 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Wed, 6 Feb 2019 11:31:14 +0000 Subject: [PATCH 352/691] Bump plugin version to 5.0.0 (#13692) --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 175a20001ceb4..ed2ad711cd42f 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,7 +3,7 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the new block editor in core. - * Version: 5.0.0-rc.1 + * Version: 5.0.0 * Author: Gutenberg Team * * @package gutenberg diff --git a/package-lock.json b/package-lock.json index d5851a5b6a26e..9182e63d2d2e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.0.0-rc.1", + "version": "5.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 5f25a85f26663..1b53a0ce186dd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.0.0-rc.1", + "version": "5.0.0", "private": true, "description": "A new WordPress editor experience", "repository": "git+https://github.com/WordPress/gutenberg.git", From a615ce6862545360ce6a463a5a7a5a17f71711f4 Mon Sep 17 00:00:00 2001 From: Zebulan Stanphill <zebulanstanphill@gmail.com> Date: Wed, 6 Feb 2019 09:01:46 -0600 Subject: [PATCH 353/691] Use block icons in media placeholders (#11788) * Use block icons in media placeholders * Add braces around BlockIcon components when used as attribute values. Co-Authored-By: ZebulanStanphill <zebulanstanphill@gmail.com> * Vertically center content of placeholder label. * Change Media & Text block placeholder icon --- packages/block-library/src/audio/edit.js | 16 +++++--- packages/block-library/src/audio/icon.js | 6 +++ packages/block-library/src/audio/index.js | 10 ++--- .../audio/test/__snapshots__/index.js.snap | 33 +++++++++------ packages/block-library/src/cover/icon.js | 6 +++ packages/block-library/src/cover/index.js | 24 ++++++----- .../cover/test/__snapshots__/index.js.snap | 33 +++++++++------ packages/block-library/src/file/edit.js | 20 +++++---- packages/block-library/src/file/icon.js | 6 +++ packages/block-library/src/file/index.js | 4 +- packages/block-library/src/gallery/edit.js | 12 +++--- packages/block-library/src/gallery/icon.js | 6 +++ packages/block-library/src/gallery/index.js | 4 +- .../gallery/test/__snapshots__/index.js.snap | 41 +++++++++++++------ packages/block-library/src/image/edit.js | 24 ++++++----- packages/block-library/src/image/icon.js | 6 +++ packages/block-library/src/image/index.js | 13 +++--- packages/block-library/src/media-text/icon.js | 6 +++ .../block-library/src/media-text/index.js | 10 ++--- .../src/media-text/media-container-icon.js | 6 +++ .../src/media-text/media-container.js | 12 ++++-- packages/block-library/src/video/edit.js | 12 +++--- packages/block-library/src/video/icon.js | 6 +++ packages/block-library/src/video/index.js | 10 ++--- .../video/test/__snapshots__/index.js.snap | 33 +++++++++------ .../components/src/placeholder/style.scss | 1 + 26 files changed, 235 insertions(+), 125 deletions(-) create mode 100644 packages/block-library/src/audio/icon.js create mode 100644 packages/block-library/src/cover/icon.js create mode 100644 packages/block-library/src/file/icon.js create mode 100644 packages/block-library/src/gallery/icon.js create mode 100644 packages/block-library/src/image/icon.js create mode 100644 packages/block-library/src/media-text/icon.js create mode 100644 packages/block-library/src/media-text/media-container-icon.js create mode 100644 packages/block-library/src/video/icon.js diff --git a/packages/block-library/src/audio/edit.js b/packages/block-library/src/audio/edit.js index ee2966208d015..9e85cea5de22d 100644 --- a/packages/block-library/src/audio/edit.js +++ b/packages/block-library/src/audio/edit.js @@ -1,25 +1,31 @@ /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; +import { getBlobByURL, isBlobURL } from '@wordpress/blob'; import { Disabled, IconButton, PanelBody, SelectControl, - Toolbar, ToggleControl, + Toolbar, withNotices, } from '@wordpress/components'; -import { Component, Fragment } from '@wordpress/element'; import { BlockControls, + BlockIcon, InspectorControls, MediaPlaceholder, RichText, mediaUpload, } from '@wordpress/editor'; -import { getBlobByURL, isBlobURL } from '@wordpress/blob'; +import { Component, Fragment } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import icon from './icon'; /** * Internal dependencies @@ -115,7 +121,7 @@ class AudioEdit extends Component { if ( editing ) { return ( <MediaPlaceholder - icon="media-audio" + icon={ <BlockIcon icon={ icon } /> } className={ className } onSelect={ onSelectAudio } onSelectURL={ this.onSelectURL } diff --git a/packages/block-library/src/audio/icon.js b/packages/block-library/src/audio/icon.js new file mode 100644 index 0000000000000..4b871506bf478 --- /dev/null +++ b/packages/block-library/src/audio/icon.js @@ -0,0 +1,6 @@ +/** + * WordPress dependencies + */ +import { Path, SVG } from '@wordpress/components'; + +export default <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path d="M0,0h24v24H0V0z" fill="none" /><Path d="m12 3l0.01 10.55c-0.59-0.34-1.27-0.55-2-0.55-2.22 0-4.01 1.79-4.01 4s1.79 4 4.01 4 3.99-1.79 3.99-4v-10h4v-4h-6zm-1.99 16c-1.1 0-2-0.9-2-2s0.9-2 2-2 2 0.9 2 2-0.9 2-2 2z" /></SVG>; diff --git a/packages/block-library/src/audio/index.js b/packages/block-library/src/audio/index.js index 51a307102397d..1e3d95da29b45 100644 --- a/packages/block-library/src/audio/index.js +++ b/packages/block-library/src/audio/index.js @@ -1,16 +1,16 @@ /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; +import { createBlobURL } from '@wordpress/blob'; +import { createBlock } from '@wordpress/blocks'; import { RichText } from '@wordpress/editor'; -import { SVG, Path } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ import edit from './edit'; -import { createBlock } from '@wordpress/blocks'; -import { createBlobURL } from '@wordpress/blob'; +import icon from './icon'; export const name = 'core/audio'; @@ -19,7 +19,7 @@ export const settings = { description: __( 'Embed a simple audio player.' ), - icon: <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path d="M0,0h24v24H0V0z" fill="none" /><Path d="m12 3l0.01 10.55c-0.59-0.34-1.27-0.55-2-0.55-2.22 0-4.01 1.79-4.01 4s1.79 4 4.01 4 3.99-1.79 3.99-4v-10h4v-4h-6zm-1.99 16c-1.1 0-2-0.9-2-2s0.9-2 2-2 2 0.9 2 2-0.9 2-2 2z" /></SVG>, + icon, category: 'common', diff --git a/packages/block-library/src/audio/test/__snapshots__/index.js.snap b/packages/block-library/src/audio/test/__snapshots__/index.js.snap index 76405487e3a2a..6ebf5b5e4f09c 100644 --- a/packages/block-library/src/audio/test/__snapshots__/index.js.snap +++ b/packages/block-library/src/audio/test/__snapshots__/index.js.snap @@ -7,20 +7,27 @@ exports[`core/audio block edit matches snapshot 1`] = ` <div class="components-placeholder__label" > - <svg - aria-hidden="true" - class="dashicon dashicons-media-audio" - focusable="false" - height="20" - role="img" - viewBox="0 0 20 20" - width="20" - xmlns="http://www.w3.org/2000/svg" + <span + class="editor-block-icon" > - <path - d="M12 2l4 4v12H4V2h8zm0 4h3l-3-3v3zm1 7.26V8.09c0-.11-.04-.21-.12-.29-.07-.08-.16-.11-.27-.1 0 0-3.97.71-4.25.78C8.07 8.54 8 8.8 8 9v3.37c-.2-.09-.42-.07-.6-.07-.38 0-.7.13-.96.39-.26.27-.4.58-.4.96 0 .37.14.69.4.95.26.27.58.4.96.4.34 0 .7-.04.96-.26.26-.23.64-.65.64-1.12V10.3l3-.6V12c-.67-.2-1.17.04-1.44.31-.26.26-.39.58-.39.95 0 .38.13.69.39.96.27.26.71.39 1.08.39.38 0 .7-.13.96-.39.26-.27.4-.58.4-.96z" - /> - </svg> + <svg + aria-hidden="true" + focusable="false" + height="24" + role="img" + viewBox="0 0 24 24" + width="24" + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M0,0h24v24H0V0z" + fill="none" + /> + <path + d="m12 3l0.01 10.55c-0.59-0.34-1.27-0.55-2-0.55-2.22 0-4.01 1.79-4.01 4s1.79 4 4.01 4 3.99-1.79 3.99-4v-10h4v-4h-6zm-1.99 16c-1.1 0-2-0.9-2-2s0.9-2 2-2 2 0.9 2 2-0.9 2-2 2z" + /> + </svg> + </span> Audio </div> <div diff --git a/packages/block-library/src/cover/icon.js b/packages/block-library/src/cover/icon.js new file mode 100644 index 0000000000000..abd932271d6eb --- /dev/null +++ b/packages/block-library/src/cover/icon.js @@ -0,0 +1,6 @@ +/** + * WordPress dependencies + */ +import { Path, SVG } from '@wordpress/components'; + +export default <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><Path d="M4 4h7V2H4c-1.1 0-2 .9-2 2v7h2V4zm6 9l-4 5h12l-3-4-2.03 2.71L10 13zm7-4.5c0-.83-.67-1.5-1.5-1.5S14 7.67 14 8.5s.67 1.5 1.5 1.5S17 9.33 17 8.5zM20 2h-7v2h7v7h2V4c0-1.1-.9-2-2-2zm0 18h-7v2h7c1.1 0 2-.9 2-2v-7h-2v7zM4 13H2v7c0 1.1.9 2 2 2h7v-2H4v-7z" /><Path d="M0 0h24v24H0z" fill="none" /></SVG>; diff --git a/packages/block-library/src/cover/index.js b/packages/block-library/src/cover/index.js index 1df4323902ecb..3ad3e1c8546e7 100644 --- a/packages/block-library/src/cover/index.js +++ b/packages/block-library/src/cover/index.js @@ -6,6 +6,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ +import { createBlock } from '@wordpress/blocks'; import { FocalPointPicker, IconButton, @@ -14,25 +15,28 @@ import { ToggleControl, Toolbar, withNotices, - SVG, - Path, } from '@wordpress/components'; -import { Fragment } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; -import { createBlock } from '@wordpress/blocks'; import { compose } from '@wordpress/compose'; import { + AlignmentToolbar, BlockControls, + BlockIcon, InspectorControls, MediaPlaceholder, MediaUpload, MediaUploadCheck, - AlignmentToolbar, PanelColorSettings, RichText, - withColors, getColorClassName, + withColors, } from '@wordpress/editor'; +import { Fragment } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import icon from './icon'; const blockAttributes = { title: { @@ -84,7 +88,7 @@ export const settings = { description: __( 'Add an image or video with a text overlay — great for headers.' ), - icon: <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><Path d="M4 4h7V2H4c-1.1 0-2 .9-2 2v7h2V4zm6 9l-4 5h12l-3-4-2.03 2.71L10 13zm7-4.5c0-.83-.67-1.5-1.5-1.5S14 7.67 14 8.5s.67 1.5 1.5 1.5S17 9.33 17 8.5zM20 2h-7v2h7v7h2V4c0-1.1-.9-2-2-2zm0 18h-7v2h7c1.1 0 2-.9 2-2v-7h-2v7zM4 13H2v7c0 1.1.9 2 2 2h7v-2H4v-7z" /><Path d="M0 0h24v24H0z" fill="none" /></SVG>, + icon, category: 'common', @@ -308,7 +312,7 @@ export const settings = { if ( ! url ) { const hasTitle = ! RichText.isEmpty( title ); - const icon = hasTitle ? undefined : 'format-image'; + const placeholderIcon = hasTitle ? undefined : <BlockIcon icon={ icon } />; const label = hasTitle ? ( <RichText tagName="h2" @@ -322,7 +326,7 @@ export const settings = { <Fragment> { controls } <MediaPlaceholder - icon={ icon } + icon={ placeholderIcon } className={ className } labels={ { title: label, diff --git a/packages/block-library/src/cover/test/__snapshots__/index.js.snap b/packages/block-library/src/cover/test/__snapshots__/index.js.snap index e1c2749b03b89..44e8409be5d7b 100644 --- a/packages/block-library/src/cover/test/__snapshots__/index.js.snap +++ b/packages/block-library/src/cover/test/__snapshots__/index.js.snap @@ -7,20 +7,27 @@ exports[`core/cover block edit matches snapshot 1`] = ` <div class="components-placeholder__label" > - <svg - aria-hidden="true" - class="dashicon dashicons-format-image" - focusable="false" - height="20" - role="img" - viewBox="0 0 20 20" - width="20" - xmlns="http://www.w3.org/2000/svg" + <span + class="editor-block-icon" > - <path - d="M2.25 1h15.5c.69 0 1.25.56 1.25 1.25v15.5c0 .69-.56 1.25-1.25 1.25H2.25C1.56 19 1 18.44 1 17.75V2.25C1 1.56 1.56 1 2.25 1zM17 17V3H3v14h14zM10 6c0-1.1-.9-2-2-2s-2 .9-2 2 .9 2 2 2 2-.9 2-2zm3 5s0-6 3-6v10c0 .55-.45 1-1 1H5c-.55 0-1-.45-1-1V8c2 0 3 4 3 4s1-3 3-3 3 2 3 2z" - /> - </svg> + <svg + aria-hidden="true" + focusable="false" + height="24" + role="img" + viewBox="0 0 24 24" + width="24" + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M4 4h7V2H4c-1.1 0-2 .9-2 2v7h2V4zm6 9l-4 5h12l-3-4-2.03 2.71L10 13zm7-4.5c0-.83-.67-1.5-1.5-1.5S14 7.67 14 8.5s.67 1.5 1.5 1.5S17 9.33 17 8.5zM20 2h-7v2h7v7h2V4c0-1.1-.9-2-2-2zm0 18h-7v2h7c1.1 0 2-.9 2-2v-7h-2v7zM4 13H2v7c0 1.1.9 2 2 2h7v-2H4v-7z" + /> + <path + d="M0 0h24v24H0z" + fill="none" + /> + </svg> + </span> Cover </div> <div diff --git a/packages/block-library/src/file/edit.js b/packages/block-library/src/file/edit.js index 002bd43cf3617..9dca21987bc8c 100644 --- a/packages/block-library/src/file/edit.js +++ b/packages/block-library/src/file/edit.js @@ -6,29 +6,35 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; -import { getBlobByURL, revokeBlobURL, isBlobURL } from '@wordpress/blob'; +import { + getBlobByURL, + isBlobURL, + revokeBlobURL, +} from '@wordpress/blob'; import { ClipboardButton, IconButton, Toolbar, withNotices, } from '@wordpress/components'; +import { compose } from '@wordpress/compose'; import { withSelect } from '@wordpress/data'; -import { Component, Fragment } from '@wordpress/element'; import { + BlockControls, + BlockIcon, MediaUpload, - MediaPlaceholder, MediaUploadCheck, - BlockControls, + MediaPlaceholder, RichText, mediaUpload, } from '@wordpress/editor'; -import { compose } from '@wordpress/compose'; +import { Component, Fragment } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ +import icon from './icon'; import FileBlockInspector from './inspector'; class FileEdit extends Component { @@ -136,7 +142,7 @@ class FileEdit extends Component { if ( ! href || hasError ) { return ( <MediaPlaceholder - icon="media-default" + icon={ <BlockIcon icon={ icon } /> } labels={ { title: __( 'File' ), instructions: __( 'Drag a file, upload a new one or select a file from your library.' ), diff --git a/packages/block-library/src/file/icon.js b/packages/block-library/src/file/icon.js new file mode 100644 index 0000000000000..a4ddc5b93f910 --- /dev/null +++ b/packages/block-library/src/file/icon.js @@ -0,0 +1,6 @@ +/** + * WordPress dependencies + */ +import { Path, SVG } from '@wordpress/components'; + +export default <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><Path d="M9 6l2 2h9v10H4V6h5m1-2H4L2 6v12l2 2h16l2-2V8l-2-2h-8l-2-2z" /></SVG>; diff --git a/packages/block-library/src/file/index.js b/packages/block-library/src/file/index.js index 43471765187c8..1c7087039a5cc 100644 --- a/packages/block-library/src/file/index.js +++ b/packages/block-library/src/file/index.js @@ -11,12 +11,12 @@ import { createBlobURL } from '@wordpress/blob'; import { createBlock } from '@wordpress/blocks'; import { select } from '@wordpress/data'; import { RichText } from '@wordpress/editor'; -import { SVG, Path } from '@wordpress/components'; /** * Internal dependencies */ import edit from './edit'; +import icon from './icon'; export const name = 'core/file'; @@ -25,7 +25,7 @@ export const settings = { description: __( 'Add a link to a downloadable file.' ), - icon: <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><Path d="M9 6l2 2h9v10H4V6h5m1-2H4L2 6v12l2 2h16l2-2V8l-2-2h-8l-2-2z" /></SVG>, + icon, category: 'common', diff --git a/packages/block-library/src/gallery/edit.js b/packages/block-library/src/gallery/edit.js index 5a9b04ce2e944..1199b881f93b3 100644 --- a/packages/block-library/src/gallery/edit.js +++ b/packages/block-library/src/gallery/edit.js @@ -7,12 +7,10 @@ import { filter, pick, map, get } from 'lodash'; /** * WordPress dependencies */ -import { Component, Fragment } from '@wordpress/element'; -import { __, sprintf } from '@wordpress/i18n'; import { - IconButton, DropZone, FormFileUpload, + IconButton, PanelBody, RangeControl, SelectControl, @@ -22,16 +20,20 @@ import { } from '@wordpress/components'; import { BlockControls, - MediaUpload, + BlockIcon, MediaPlaceholder, + MediaUpload, InspectorControls, mediaUpload, } from '@wordpress/editor'; +import { Component, Fragment } from '@wordpress/element'; +import { __, sprintf } from '@wordpress/i18n'; /** * Internal dependencies */ import GalleryImage from './gallery-image'; +import icon from './icon'; const MAX_COLUMNS = 8; const linkOptions = [ @@ -220,7 +222,7 @@ class GalleryEdit extends Component { <Fragment> { controls } <MediaPlaceholder - icon="format-gallery" + icon={ <BlockIcon icon={ icon } /> } className={ className } labels={ { title: __( 'Gallery' ), diff --git a/packages/block-library/src/gallery/icon.js b/packages/block-library/src/gallery/icon.js new file mode 100644 index 0000000000000..54dce1ede1e53 --- /dev/null +++ b/packages/block-library/src/gallery/icon.js @@ -0,0 +1,6 @@ +/** + * WordPress dependencies + */ +import { G, Path, SVG } from '@wordpress/components'; + +export default <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><G><Path d="M20 4v12H8V4h12m0-2H8L6 4v12l2 2h12l2-2V4l-2-2z" /><Path d="M12 12l1 2 3-3 3 4H9z" /><Path d="M2 6v14l2 2h14v-2H4V6H2z" /></G></SVG>; diff --git a/packages/block-library/src/gallery/index.js b/packages/block-library/src/gallery/index.js index b43b9ae3a6034..9e616efa3684f 100644 --- a/packages/block-library/src/gallery/index.js +++ b/packages/block-library/src/gallery/index.js @@ -10,12 +10,12 @@ import { __ } from '@wordpress/i18n'; import { createBlock } from '@wordpress/blocks'; import { RichText, mediaUpload } from '@wordpress/editor'; import { createBlobURL } from '@wordpress/blob'; -import { G, Path, SVG } from '@wordpress/components'; /** * Internal dependencies */ import { default as edit, defaultColumnsNumber, pickRelevantMediaFiles } from './edit'; +import icon from './icon'; const blockAttributes = { images: { @@ -84,7 +84,7 @@ const parseShortcodeIds = ( ids ) => { export const settings = { title: __( 'Gallery' ), description: __( 'Display multiple images in a rich gallery.' ), - icon: <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><G><Path d="M20 4v12H8V4h12m0-2H8L6 4v12l2 2h12l2-2V4l-2-2z" /><Path d="M12 12l1 2 3-3 3 4H9z" /><Path d="M2 6v14l2 2h14v-2H4V6H2z" /></G></SVG>, + icon, category: 'common', keywords: [ __( 'images' ), __( 'photos' ) ], attributes: blockAttributes, diff --git a/packages/block-library/src/gallery/test/__snapshots__/index.js.snap b/packages/block-library/src/gallery/test/__snapshots__/index.js.snap index 6f00bba1cb56e..48829d35124c6 100644 --- a/packages/block-library/src/gallery/test/__snapshots__/index.js.snap +++ b/packages/block-library/src/gallery/test/__snapshots__/index.js.snap @@ -7,20 +7,35 @@ exports[`core/gallery block edit matches snapshot 1`] = ` <div class="components-placeholder__label" > - <svg - aria-hidden="true" - class="dashicon dashicons-format-gallery" - focusable="false" - height="20" - role="img" - viewBox="0 0 20 20" - width="20" - xmlns="http://www.w3.org/2000/svg" + <span + class="editor-block-icon" > - <path - d="M16 4h1.96c.57 0 1.04.47 1.04 1.04v12.92c0 .57-.47 1.04-1.04 1.04H5.04C4.47 19 4 18.53 4 17.96V16H2.04C1.47 16 1 15.53 1 14.96V2.04C1 1.47 1.47 1 2.04 1h12.92c.57 0 1.04.47 1.04 1.04V4zM3 14h11V3H3v11zm5-8.5C8 4.67 7.33 4 6.5 4S5 4.67 5 5.5 5.67 7 6.5 7 8 6.33 8 5.5zm2 4.5s1-5 3-5v8H4V7c2 0 2 3 2 3s.33-2 2-2 2 2 2 2zm7 7V6h-1v8.96c0 .57-.47 1.04-1.04 1.04H6v1h11z" - /> - </svg> + <svg + aria-hidden="true" + focusable="false" + height="24" + role="img" + viewBox="0 0 24 24" + width="24" + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M0 0h24v24H0V0z" + fill="none" + /> + <g> + <path + d="M20 4v12H8V4h12m0-2H8L6 4v12l2 2h12l2-2V4l-2-2z" + /> + <path + d="M12 12l1 2 3-3 3 4H9z" + /> + <path + d="M2 6v14l2 2h14v-2H4V6H2z" + /> + </g> + </svg> + </span> Gallery </div> <div diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index c3e136cf6b705..44534956f5b5a 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -3,21 +3,18 @@ */ import classnames from 'classnames'; import { + compact, get, isEmpty, map, last, pick, - compact, } from 'lodash'; /** * WordPress dependencies */ -import { getPath } from '@wordpress/url'; -import { __, sprintf } from '@wordpress/i18n'; -import { Component, Fragment } from '@wordpress/element'; -import { getBlobByURL, revokeBlobURL, isBlobURL } from '@wordpress/blob'; +import { getBlobByURL, isBlobURL, revokeBlobURL } from '@wordpress/blob'; import { Button, ButtonGroup, @@ -26,30 +23,35 @@ import { ResizableBox, SelectControl, Spinner, - TextControl, TextareaControl, + TextControl, + ToggleControl, Toolbar, withNotices, - ToggleControl, } from '@wordpress/components'; +import { compose } from '@wordpress/compose'; import { withSelect } from '@wordpress/data'; import { - RichText, + BlockAlignmentToolbar, BlockControls, + BlockIcon, InspectorControls, MediaPlaceholder, MediaUpload, MediaUploadCheck, - BlockAlignmentToolbar, + RichText, mediaUpload, } from '@wordpress/editor'; +import { Component, Fragment } from '@wordpress/element'; +import { __, sprintf } from '@wordpress/i18n'; +import { getPath } from '@wordpress/url'; import { withViewportMatch } from '@wordpress/viewport'; -import { compose } from '@wordpress/compose'; /** * Internal dependencies */ import { createUpgradedEmbedBlock } from '../embed/util'; +import icon from './icon'; import ImageSize from './image-size'; /** @@ -423,7 +425,7 @@ class ImageEdit extends Component { <Fragment> { controls } <MediaPlaceholder - icon="format-image" + icon={ <BlockIcon icon={ icon } /> } className={ className } onSelect={ this.onSelectImage } onSelectURL={ this.onSelectURL } diff --git a/packages/block-library/src/image/icon.js b/packages/block-library/src/image/icon.js new file mode 100644 index 0000000000000..b029bab8fbe98 --- /dev/null +++ b/packages/block-library/src/image/icon.js @@ -0,0 +1,6 @@ +/** + * WordPress dependencies + */ +import { Path, SVG } from '@wordpress/components'; + +export default <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path d="M0,0h24v24H0V0z" fill="none" /><Path d="m19 5v14h-14v-14h14m0-2h-14c-1.1 0-2 0.9-2 2v14c0 1.1 0.9 2 2 2h14c1.1 0 2-0.9 2-2v-14c0-1.1-0.9-2-2-2z" /><Path d="m14.14 11.86l-3 3.87-2.14-2.59-3 3.86h12l-3.86-5.14z" /></SVG>; diff --git a/packages/block-library/src/image/index.js b/packages/block-library/src/image/index.js index 65bd1df8f1dc2..9c8d519425dfd 100644 --- a/packages/block-library/src/image/index.js +++ b/packages/block-library/src/image/index.js @@ -6,24 +6,21 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { Fragment } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; +import { createBlobURL } from '@wordpress/blob'; import { createBlock, getBlockAttributes, getPhrasingContentSchema, } from '@wordpress/blocks'; import { RichText } from '@wordpress/editor'; -import { createBlobURL } from '@wordpress/blob'; -import { - Path, - SVG, -} from '@wordpress/components'; +import { Fragment } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ import edit from './edit'; +import icon from './icon'; export const name = 'core/image'; @@ -131,7 +128,7 @@ export const settings = { description: __( 'Insert an image to make a visual statement.' ), - icon: <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path d="M0,0h24v24H0V0z" fill="none" /><Path d="m19 5v14h-14v-14h14m0-2h-14c-1.1 0-2 0.9-2 2v14c0 1.1 0.9 2 2 2h14c1.1 0 2-0.9 2-2v-14c0-1.1-0.9-2-2-2z" /><Path d="m14.14 11.86l-3 3.87-2.14-2.59-3 3.86h12l-3.86-5.14z" /></SVG>, + icon, category: 'common', diff --git a/packages/block-library/src/media-text/icon.js b/packages/block-library/src/media-text/icon.js new file mode 100644 index 0000000000000..cfc1f405b34cc --- /dev/null +++ b/packages/block-library/src/media-text/icon.js @@ -0,0 +1,6 @@ +/** + * WordPress dependencies + */ +import { Path, SVG } from '@wordpress/components'; + +export default <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><Path d="M13 17h8v-2h-8v2zM3 19h8V5H3v14zM13 9h8V7h-8v2zm0 4h8v-2h-8v2z" /></SVG>; diff --git a/packages/block-library/src/media-text/index.js b/packages/block-library/src/media-text/index.js index 6cb54f5d3c830..01182b306a642 100644 --- a/packages/block-library/src/media-text/index.js +++ b/packages/block-library/src/media-text/index.js @@ -1,24 +1,24 @@ /** * External dependencies */ -import { noop } from 'lodash'; import classnames from 'classnames'; +import { noop } from 'lodash'; /** * WordPress dependencies */ -import { Path, SVG } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; +import { createBlock } from '@wordpress/blocks'; import { InnerBlocks, getColorClassName, } from '@wordpress/editor'; -import { createBlock } from '@wordpress/blocks'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ import edit from './edit'; +import icon from './icon'; const DEFAULT_MEDIA_WIDTH = 50; @@ -73,7 +73,7 @@ export const settings = { description: __( 'Set media and words side-by-side for a richer layout.' ), - icon: <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><Path d="M13 17h8v-2h-8v2zM3 19h8V5H3v14zM13 9h8V7h-8v2zm0 4h8v-2h-8v2z" /></SVG>, + icon, category: 'layout', diff --git a/packages/block-library/src/media-text/media-container-icon.js b/packages/block-library/src/media-text/media-container-icon.js new file mode 100644 index 0000000000000..6e6de30b7a61d --- /dev/null +++ b/packages/block-library/src/media-text/media-container-icon.js @@ -0,0 +1,6 @@ +/** + * WordPress dependencies + */ +import { Path, SVG } from '@wordpress/components'; + +export default <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><Path d="M18 2l2 4h-2l-2-4h-3l2 4h-2l-2-4h-1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V2zm2 12H10V4.4L11.8 8H20z" /><Path d="M14 20H4V10h3V8H4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-3h-2z" /><Path d="M5 19h8l-1.59-2H9.24l-.84 1.1L7 16.3 5 19z" /></SVG>; diff --git a/packages/block-library/src/media-text/media-container.js b/packages/block-library/src/media-text/media-container.js index 863179d748b90..6f0ce32685f63 100644 --- a/packages/block-library/src/media-text/media-container.js +++ b/packages/block-library/src/media-text/media-container.js @@ -1,14 +1,20 @@ /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; -import { Component, Fragment } from '@wordpress/element'; import { IconButton, ResizableBox, Toolbar } from '@wordpress/components'; import { BlockControls, + BlockIcon, MediaPlaceholder, MediaUpload, } from '@wordpress/editor'; +import { Component, Fragment } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import icon from './media-container-icon'; /** * Constants @@ -67,7 +73,7 @@ class MediaContainer extends Component { const { onSelectMedia, className } = this.props; return ( <MediaPlaceholder - icon="format-image" + icon={ <BlockIcon icon={ icon } /> } labels={ { title: __( 'Media area' ), } } diff --git a/packages/block-library/src/video/edit.js b/packages/block-library/src/video/edit.js index 118bd11760668..e22f35d0bf41e 100644 --- a/packages/block-library/src/video/edit.js +++ b/packages/block-library/src/video/edit.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; +import { getBlobByURL, isBlobURL } from '@wordpress/blob'; import { BaseControl, Button, @@ -9,13 +9,13 @@ import { IconButton, PanelBody, SelectControl, - Toolbar, ToggleControl, + Toolbar, withNotices, } from '@wordpress/components'; -import { Component, Fragment, createRef } from '@wordpress/element'; import { BlockControls, + BlockIcon, InspectorControls, MediaPlaceholder, MediaUpload, @@ -23,12 +23,14 @@ import { RichText, mediaUpload, } from '@wordpress/editor'; -import { getBlobByURL, isBlobURL } from '@wordpress/blob'; +import { Component, Fragment, createRef } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ import { createUpgradedEmbedBlock } from '../embed/util'; +import icon from './icon'; const ALLOWED_MEDIA_TYPES = [ 'video' ]; const VIDEO_POSTER_ALLOWED_MEDIA_TYPES = [ 'image' ]; @@ -150,7 +152,7 @@ class VideoEdit extends Component { if ( editing ) { return ( <MediaPlaceholder - icon="media-video" + icon={ <BlockIcon icon={ icon } /> } className={ className } onSelect={ onSelectVideo } onSelectURL={ this.onSelectURL } diff --git a/packages/block-library/src/video/icon.js b/packages/block-library/src/video/icon.js new file mode 100644 index 0000000000000..a029952561e9d --- /dev/null +++ b/packages/block-library/src/video/icon.js @@ -0,0 +1,6 @@ +/** + * WordPress dependencies + */ +import { Path, SVG } from '@wordpress/components'; + +export default <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><Path d="M4 6l2 4h14v8H4V6m18-2h-4l2 4h-3l-2-4h-2l2 4h-3l-2-4H8l2 4H7L5 4H4L2 6v12l2 2h16l2-2V4z" /></SVG>; diff --git a/packages/block-library/src/video/index.js b/packages/block-library/src/video/index.js index ac20d15dcaa33..bba5819300228 100644 --- a/packages/block-library/src/video/index.js +++ b/packages/block-library/src/video/index.js @@ -1,16 +1,16 @@ /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; -import { RichText } from '@wordpress/editor'; -import { createBlock } from '@wordpress/blocks'; import { createBlobURL } from '@wordpress/blob'; -import { SVG, Path } from '@wordpress/components'; +import { createBlock } from '@wordpress/blocks'; +import { RichText } from '@wordpress/editor'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ import edit from './edit'; +import icon from './icon'; export const name = 'core/video'; @@ -19,7 +19,7 @@ export const settings = { description: __( 'Embed a video from your media library or upload a new one.' ), - icon: <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><Path d="M4 6l2 4h14v8H4V6m18-2h-4l2 4h-3l-2-4h-2l2 4h-3l-2-4H8l2 4H7L5 4H4L2 6v12l2 2h16l2-2V4z" /></SVG>, + icon, keywords: [ __( 'movie' ) ], diff --git a/packages/block-library/src/video/test/__snapshots__/index.js.snap b/packages/block-library/src/video/test/__snapshots__/index.js.snap index e69b765f795ae..174b01d7878ed 100644 --- a/packages/block-library/src/video/test/__snapshots__/index.js.snap +++ b/packages/block-library/src/video/test/__snapshots__/index.js.snap @@ -7,20 +7,27 @@ exports[`core/video block edit matches snapshot 1`] = ` <div class="components-placeholder__label" > - <svg - aria-hidden="true" - class="dashicon dashicons-media-video" - focusable="false" - height="20" - role="img" - viewBox="0 0 20 20" - width="20" - xmlns="http://www.w3.org/2000/svg" + <span + class="editor-block-icon" > - <path - d="M12 2l4 4v12H4V2h8zm0 4h3l-3-3v3zm-1 8v-3c0-.27-.1-.51-.29-.71-.2-.19-.44-.29-.71-.29H7c-.27 0-.51.1-.71.29-.19.2-.29.44-.29.71v3c0 .27.1.51.29.71.2.19.44.29.71.29h3c.27 0 .51-.1.71-.29.19-.2.29-.44.29-.71zm3 1v-5l-2 2v1z" - /> - </svg> + <svg + aria-hidden="true" + focusable="false" + height="24" + role="img" + viewBox="0 0 24 24" + width="24" + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M0 0h24v24H0V0z" + fill="none" + /> + <path + d="M4 6l2 4h14v8H4V6m18-2h-4l2 4h-3l-2-4h-2l2 4h-3l-2-4H8l2 4H7L5 4H4L2 6v12l2 2h16l2-2V4z" + /> + </svg> + </span> Video </div> <div diff --git a/packages/components/src/placeholder/style.scss b/packages/components/src/placeholder/style.scss index 1d2f079a1dd37..14dbef2a0f4eb 100644 --- a/packages/components/src/placeholder/style.scss +++ b/packages/components/src/placeholder/style.scss @@ -21,6 +21,7 @@ .components-placeholder__label { display: flex; + align-items: center; justify-content: center; font-weight: 600; margin-bottom: 1em; From 31b6b175af43862dbdcfb9a05ca4887f3ad60116 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Wed, 6 Feb 2019 15:25:51 -0500 Subject: [PATCH 354/691] Plugin: Remove lingering references to integrated classic editor (#13544) * Docs: Update meta box document to reflect core behavior * Plugin: Remove lingering references to integrated Classic Editor --- .../backward-compatibility/deprecations.md | 6 +++ .../backward-compatibility/meta-box.md | 14 +++---- gutenberg.php | 24 +++--------- lib/client-assets.php | 2 +- lib/register.php | 32 +++++----------- phpunit/class-admin-test.php | 37 ------------------- 6 files changed, 27 insertions(+), 88 deletions(-) diff --git a/docs/designers-developers/developers/backward-compatibility/deprecations.md b/docs/designers-developers/developers/backward-compatibility/deprecations.md index 62752b2c54d6f..a5db81f7514f5 100644 --- a/docs/designers-developers/developers/backward-compatibility/deprecations.md +++ b/docs/designers-developers/developers/backward-compatibility/deprecations.md @@ -2,6 +2,12 @@ The Gutenberg project's deprecation policy is intended to support backward compatibility for releases, when possible. The current deprecations are listed below and are grouped by _the version at which they will be removed completely_. If your plugin depends on these behaviors, you must update to the recommended alternative before the noted version. +## 5.3.0 + +- The PHP function `gutenberg_redirect_to_classic_editor_when_saving_posts` has been removed. +- The PHP function `gutenberg_revisions_link_to_editor` has been removed. +- The PHP function `gutenberg_remember_classic_editor_when_saving_posts` has been removed. + ## 5.2.0 - The PHP function `gutenberg_parse_blocks` has been removed. Use [`parse_blocks`](https://developer.wordpress.org/reference/functions/parse_blocks/) instead. diff --git a/docs/designers-developers/developers/backward-compatibility/meta-box.md b/docs/designers-developers/developers/backward-compatibility/meta-box.md index e6268e6924ea9..18feca91ceed2 100644 --- a/docs/designers-developers/developers/backward-compatibility/meta-box.md +++ b/docs/designers-developers/developers/backward-compatibility/meta-box.md @@ -38,11 +38,11 @@ When Gutenberg is used, this meta box will no longer be displayed in the meta bo On each Gutenberg page load, we register an action that collects the meta box data to determine if an area is empty. The original global state is reset upon collection of meta box data. -See `lib/register.php gutenberg_trick_plugins_into_registering_meta_boxes()` +See [`register_and_do_post_meta_boxes`](https://developer.wordpress.org/reference/functions/register_and_do_post_meta_boxes/). -`gutenberg_collect_meta_box_data()` is hooked in later on `admin_head`. It will run through the functions and hooks that `post.php` runs to register meta boxes; namely `add_meta_boxes`, `add_meta_boxes_{$post->post_type}`, and `do_meta_boxes`. +It will run through the functions and hooks that `post.php` runs to register meta boxes; namely `add_meta_boxes`, `add_meta_boxes_{$post->post_type}`, and `do_meta_boxes`. -A copy of the global `$wp_meta_boxes` is made then filtered through `apply_filters( 'filter_gutenberg_meta_boxes', $_meta_boxes_copy );`, which will strip out any core meta boxes, standard custom taxonomy meta boxes, and any meta boxes that have declared themselves as only existing for backward compatibility purposes. +Meta boxes are filtered to strip out any core meta boxes, standard custom taxonomy meta boxes, and any meta boxes that have declared themselves as only existing for backward compatibility purposes. Then each location for this particular type of meta box is checked for whether it is active. If it is not empty a value of true is stored, if it is empty a value of false is stored. This meta box location data is then dispatched by the editor Redux store in `INITIALIZE_META_BOX_STATE`. @@ -63,13 +63,9 @@ When the post is updated, only meta box areas that are active will be submitted. When the meta box area is saving, we display an updating overlay, to prevent users from changing the form values while a save is in progress. -After the new block editor is made into the default editor, it will be necessary to provide the classic-editor flag to access the meta box partial page. +An example save url would look like: -`gutenberg_meta_box_save()` saves meta box changes. A `meta_box` request parameter should be present and should match one of `'advanced'`, `'normal'`, or `'side'`. This value will determine which meta box area is served. - -So an example url would look like: - -`mysite.com/wp-admin/post.php?post=1&action=edit&meta_box=$location&classic-editor` +`mysite.com/wp-admin/post.php?post=1&action=edit&meta-box-loader=1` This url is automatically passed into React via a `_wpMetaBoxUrl` global variable. diff --git a/gutenberg.php b/gutenberg.php index ed2ad711cd42f..11e173a033767 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -31,21 +31,11 @@ function the_gutenberg_project() { <noscript> <div class="error" style="position:absolute;top:32px;z-index:40"><p> <?php - // Using Gutenberg as Plugin. - if ( is_plugin_active( 'gutenberg/gutenberg.php' ) ) { - $current_url = esc_url( add_query_arg( 'classic-editor', true, $_SERVER['REQUEST_URI'] ) ); - printf( - // Translators: link is to current page specify classic editor. - __( 'The Block Editor requires JavaScript. You can use the <a href="%s">Classic Editor</a>.', 'gutenberg' ), - $current_url - ); - } else { // Using Gutenberg in Core. - printf( - /* translators: %s: https://wordpress.org/plugins/classic-editor/ */ - __( 'The Block Editor requires JavaScript. Please try the <a href="%s">Classic Editor plugin</a>.', 'gutenberg' ), - __( 'https://wordpress.org/plugins/classic-editor/', 'gutenberg' ) - ); - } + printf( + /* translators: %s: https://wordpress.org/plugins/classic-editor/ */ + __( 'The Block Editor requires JavaScript. Please try the <a href="%s">Classic Editor plugin</a>.', 'gutenberg' ), + __( 'https://wordpress.org/plugins/classic-editor/', 'gutenberg' ) + ); ?> </p></div> </noscript> @@ -128,10 +118,6 @@ function is_gutenberg_page() { return false; } - if ( isset( $_GET['classic-editor'] ) ) { - return false; - } - if ( ! gutenberg_can_edit_post( $post ) ) { return false; } diff --git a/lib/client-assets.php b/lib/client-assets.php index 1178d245ed875..97afc6084a41c 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -1110,7 +1110,7 @@ function gutenberg_editor_scripts_and_styles( $hook ) { $post_autosave = gutenberg_get_autosave_newer_than_post_save( $post ); if ( $post_autosave ) { $editor_settings['autosave'] = array( - 'editLink' => add_query_arg( 'gutenberg', true, get_edit_post_link( $post_autosave->ID ) ), + 'editLink' => get_edit_post_link( $post_autosave->ID ), ); } diff --git a/lib/register.php b/lib/register.php index ebdc43c7f2e5c..f8f7410d31253 100644 --- a/lib/register.php +++ b/lib/register.php @@ -203,67 +203,55 @@ function gutenberg_bulk_post_updated_messages( $messages ) { * Injects a hidden input in the edit form to propagate the information that classic editor is selected. * * @since 1.5.2 + * @deprecated 5.0.0 */ function gutenberg_remember_classic_editor_when_saving_posts() { - ?> - <input type="hidden" name="classic-editor" /> - <?php + _deprecated_function( __FUNCTION__, '5.0.0' ); } -add_action( 'edit_form_top', 'gutenberg_remember_classic_editor_when_saving_posts' ); /** * Appends a query argument to the redirect url to make sure it gets redirected to the classic editor. * * @since 1.5.2 + * @deprecated 5.0.0 * * @param string $url Redirect url. * @return string Redirect url. */ function gutenberg_redirect_to_classic_editor_when_saving_posts( $url ) { - if ( isset( $_REQUEST['classic-editor'] ) ) { - $url = add_query_arg( 'classic-editor', '', $url ); - } + _deprecated_function( __FUNCTION__, '5.0.0' ); + return $url; } -add_filter( 'redirect_post_location', 'gutenberg_redirect_to_classic_editor_when_saving_posts', 10, 1 ); /** * Appends a query argument to the edit url to make sure it is redirected to * the editor from which the user navigated. * * @since 1.5.2 + * @deprecated 5.0.0 * * @param string $url Edit url. * @return string Edit url. */ function gutenberg_revisions_link_to_editor( $url ) { - global $pagenow; - if ( 'revision.php' !== $pagenow || isset( $_REQUEST['gutenberg'] ) ) { - return $url; - } + _deprecated_function( __FUNCTION__, '5.0.0' ); - return add_query_arg( 'classic-editor', '', $url ); + return $url; } -add_filter( 'get_edit_post_link', 'gutenberg_revisions_link_to_editor' ); /** * Modifies revisions data to preserve Gutenberg argument used in determining * where to redirect user returning to editor. * * @since 1.9.0 + * @deprecated 5.0.0 * * @param array $revisions_data The bootstrapped data for the revisions screen. * @return array Modified bootstrapped data for the revisions screen. */ function gutenberg_revisions_restore( $revisions_data ) { - if ( isset( $_REQUEST['gutenberg'] ) ) { - $revisions_data['restoreUrl'] = add_query_arg( - 'gutenberg', - $_REQUEST['gutenberg'], - $revisions_data['restoreUrl'] - ); - } + _deprecated_function( __FUNCTION__, '5.0.0' ); return $revisions_data; } -add_filter( 'wp_prepare_revision_for_js', 'gutenberg_revisions_restore' ); diff --git a/phpunit/class-admin-test.php b/phpunit/class-admin-test.php index f801df79e4f16..d7342213b853e 100644 --- a/phpunit/class-admin-test.php +++ b/phpunit/class-admin-test.php @@ -140,41 +140,4 @@ function test_gutenberg_content_has_blocks() { $this->assertTrue( gutenberg_content_has_blocks( $content_with_blocks ) ); $this->assertFalse( gutenberg_content_has_blocks( $content_without_blocks ) ); } - - /** - * Test that the revisions 'return to editor' links are set correctly for Classic & Gutenberg editors. - * - * @covers ::gutenberg_revisions_link_to_editor - */ - function test_gutenberg_revisions_link_to_editor() { - global $pagenow; - - // Set up $pagenow so the filter will work. - $pagenow = 'revision.php'; - - // Test the filter when Gutenberg is the active editor. - $_REQUEST['gutenberg'] = '1'; - $link = apply_filters( 'get_edit_post_link', 'http://test.com' ); - $this->assertEquals( 'http://test.com', $link ); - - // Test the filter when Gutenberg is not the active editor. - unset( $_REQUEST['gutenberg'] ); - $link = apply_filters( 'get_edit_post_link', 'http://test.com' ); - $this->assertEquals( 'http://test.com?classic-editor', $link ); - } - - /** - * Test that the revisions 'restore this revision' button links correctly for Classic & Gutenberg editors. - */ - function test_gutenberg_revisions_restore() { - // Test the filter when Gutenberg is the active editor. - $_REQUEST['gutenberg'] = '1'; - $link = apply_filters( 'wp_prepare_revision_for_js', array( 'restoreUrl' => 'http://test.com' ) ); - $this->assertEquals( array( 'restoreUrl' => 'http://test.com?gutenberg=1' ), $link ); - - // Test the filter when Gutenberg is not the active editor. - unset( $_REQUEST['gutenberg'] ); - $link = apply_filters( 'wp_prepare_revision_for_js', array( 'restoreUrl' => 'http://test.com' ) ); - $this->assertEquals( array( 'restoreUrl' => 'http://test.com' ), $link ); - } } From 8181261809f928175514dc00d39c888bc5274d8f Mon Sep 17 00:00:00 2001 From: Robert Anderson <robert@noisysocks.com> Date: Thu, 7 Feb 2019 11:38:06 +1100 Subject: [PATCH 355/691] Add search block (#13583) * Adding search widget block Co-authored-by: Rachel Cherry <bamadesigner@gmail.com> * Search block: Move away from legacy get_search_form() markup Changes the Search block to render its own search form markup, instead of the same markup that get_search_form() renders. This lets us fully match the design spec. * s/Placeholder text/Placeholder Text/ Co-Authored-By: noisysocks <robert@noisysocks.com> * Remove superfluous 'your' Co-Authored-By: noisysocks <robert@noisysocks.com> * Search: Add button and inline placeholder editing * Update CHANGELOG * Search block: Support custom CSS classes * Search block: Add aria-label to placeholder <input> * Search block: Style label as bold in theme.scss * Search block: Add aria-label to all inputs * Search block: Use ellipsis at end of input placeholders * Search block: Display placeholder in 'placeholder' attribute, not 'value' * Revert "Search block: Display placeholder in 'placeholder' attribute, not 'value'" This reverts commit 154cddcacb1b577b2c40cee5c901cb47e6b5e472. * Search block: Remove placeholder attribute when there is a placeholder value * Search block: Fix placeholder attribute receiving false instead of undefined --- lib/load.php | 3 + packages/block-library/CHANGELOG.md | 1 + packages/block-library/src/editor.scss | 1 + packages/block-library/src/index.js | 2 + packages/block-library/src/search/edit.js | 43 ++++++++++ packages/block-library/src/search/editor.scss | 26 ++++++ packages/block-library/src/search/index.js | 29 +++++++ packages/block-library/src/search/index.php | 82 +++++++++++++++++++ packages/block-library/src/search/style.scss | 16 ++++ packages/block-library/src/search/theme.scss | 5 ++ packages/block-library/src/style.scss | 1 + packages/block-library/src/theme.scss | 1 + .../full-content/fixtures/core__search.html | 1 + .../full-content/fixtures/core__search.json | 10 +++ .../fixtures/core__search.parsed.json | 18 ++++ .../fixtures/core__search.serialized.html | 1 + .../fixtures/core__search__custom-text.html | 1 + .../fixtures/core__search__custom-text.json | 10 +++ .../core__search__custom-text.parsed.json | 22 +++++ .../core__search__custom-text.serialized.html | 1 + 20 files changed, 274 insertions(+) create mode 100644 packages/block-library/src/search/edit.js create mode 100644 packages/block-library/src/search/editor.scss create mode 100644 packages/block-library/src/search/index.js create mode 100644 packages/block-library/src/search/index.php create mode 100644 packages/block-library/src/search/style.scss create mode 100644 packages/block-library/src/search/theme.scss create mode 100644 test/integration/full-content/fixtures/core__search.html create mode 100644 test/integration/full-content/fixtures/core__search.json create mode 100644 test/integration/full-content/fixtures/core__search.parsed.json create mode 100644 test/integration/full-content/fixtures/core__search.serialized.html create mode 100644 test/integration/full-content/fixtures/core__search__custom-text.html create mode 100644 test/integration/full-content/fixtures/core__search__custom-text.json create mode 100644 test/integration/full-content/fixtures/core__search__custom-text.parsed.json create mode 100644 test/integration/full-content/fixtures/core__search__custom-text.serialized.html diff --git a/lib/load.php b/lib/load.php index 2f8644ccf4636..b3c81971a353f 100644 --- a/lib/load.php +++ b/lib/load.php @@ -49,3 +49,6 @@ if ( ! function_exists( 'render_block_core_shortcode' ) ) { require dirname( __FILE__ ) . '/../packages/block-library/src/shortcode/index.php'; } +if ( ! function_exists( 'render_block_core_search' ) ) { + require dirname( __FILE__ ) . '/../packages/block-library/src/search/index.php'; +} diff --git a/packages/block-library/CHANGELOG.md b/packages/block-library/CHANGELOG.md index 12ab36c1f2104..427d55ba22412 100644 --- a/packages/block-library/CHANGELOG.md +++ b/packages/block-library/CHANGELOG.md @@ -4,6 +4,7 @@ - Add background color controls for the table block. - Add new `RSS` block ([#7966](https://github.com/WordPress/gutenberg/pull/7966)). +- Add new `Search` block ([#13583](https://github.com/WordPress/gutenberg/pull/13583)). ## 2.2.12 (2019-01-03) diff --git a/packages/block-library/src/editor.scss b/packages/block-library/src/editor.scss index 9fb94f40bdef7..b5778d5b472ff 100644 --- a/packages/block-library/src/editor.scss +++ b/packages/block-library/src/editor.scss @@ -23,6 +23,7 @@ @import "./pullquote/editor.scss"; @import "./quote/editor.scss"; @import "./rss/editor.scss"; +@import "./search/editor.scss"; @import "./shortcode/editor.scss"; @import "./spacer/editor.scss"; @import "./subhead/editor.scss"; diff --git a/packages/block-library/src/index.js b/packages/block-library/src/index.js index 5dd3fee7b2a29..aac4e56fc7a8b 100644 --- a/packages/block-library/src/index.js +++ b/packages/block-library/src/index.js @@ -39,6 +39,7 @@ import * as preformatted from './preformatted'; import * as pullquote from './pullquote'; import * as reusableBlock from './block'; import * as rss from './rss'; +import * as search from './search'; import * as separator from './separator'; import * as shortcode from './shortcode'; import * as spacer from './spacer'; @@ -87,6 +88,7 @@ export const registerCoreBlocks = () => { preformatted, pullquote, rss, + search, separator, reusableBlock, spacer, diff --git a/packages/block-library/src/search/edit.js b/packages/block-library/src/search/edit.js new file mode 100644 index 0000000000000..f5a5acd20c498 --- /dev/null +++ b/packages/block-library/src/search/edit.js @@ -0,0 +1,43 @@ +/** + * WordPress dependencies + */ +import { RichText } from '@wordpress/editor'; +import { __ } from '@wordpress/i18n'; + +export default function SearchEdit( { className, attributes, setAttributes } ) { + const { label, placeholder, buttonText } = attributes; + + return ( + <div className={ className }> + <RichText + wrapperClassName="wp-block-search__label" + aria-label={ __( 'Label text' ) } + placeholder={ __( 'Add label…' ) } + keepPlaceholderOnFocus + formattingControls={ [] } + value={ label } + onChange={ ( html ) => setAttributes( { label: html } ) } + /> + <input + className="wp-block-search__input" + aria-label={ __( 'Optional placeholder text' ) } + // We hide the placeholder field's placeholder when there is a value. This + // stops screen readers from reading the placeholder field's placeholder + // which is confusing. + placeholder={ placeholder ? undefined : __( 'Optional placeholder…' ) } + value={ placeholder } + onChange={ ( event ) => setAttributes( { placeholder: event.target.value } ) } + /> + <RichText + wrapperClassName="wp-block-search__button" + className="wp-block-search__button-rich-text" + aria-label={ __( 'Button text' ) } + placeholder={ __( 'Add button text…' ) } + keepPlaceholderOnFocus + formattingControls={ [] } + value={ buttonText } + onChange={ ( html ) => setAttributes( { buttonText: html } ) } + /> + </div> + ); +} diff --git a/packages/block-library/src/search/editor.scss b/packages/block-library/src/search/editor.scss new file mode 100644 index 0000000000000..cb5a489f180fb --- /dev/null +++ b/packages/block-library/src/search/editor.scss @@ -0,0 +1,26 @@ +.wp-block-search { + .wp-block-search__input { + border-radius: $radius-round-rectangle; + border: 1px solid $dark-gray-150; + color: $dark-opacity-300; + font-family: $default-font; + font-size: $default-font-size; + + &:focus { + outline: none; + } + } + + .wp-block-search__button { + background: #f7f7f7; + border-radius: $radius-round-rectangle; + border: 1px solid #ccc; + box-shadow: inset 0 -1px 0 #ccc; + font-family: $default-font; + font-size: $default-font-size; + + .wp-block-search__button-rich-text { + padding: 6px 10px; + } + } +} diff --git a/packages/block-library/src/search/index.js b/packages/block-library/src/search/index.js new file mode 100644 index 0000000000000..0fcfa75722ff4 --- /dev/null +++ b/packages/block-library/src/search/index.js @@ -0,0 +1,29 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import edit from './edit'; + +export const name = 'core/search'; + +export const settings = { + title: __( 'Search' ), + + description: __( 'Help visitors find your content.' ), + + icon: 'search', + + category: 'widgets', + + keywords: [ __( 'find' ) ], + + edit, + + save() { + return null; + }, +}; diff --git a/packages/block-library/src/search/index.php b/packages/block-library/src/search/index.php new file mode 100644 index 0000000000000..ac7da463e92f2 --- /dev/null +++ b/packages/block-library/src/search/index.php @@ -0,0 +1,82 @@ +<?php +/** + * Server-side rendering of the `core/search` block. + * + * @package WordPress + */ + +/** + * Dynamically renders the `core/search` block. + * + * @param array $attributes The block attributes. + * + * @return string The search block markup. + */ +function render_block_core_search( $attributes ) { + static $instance_id = 0; + + $input_id = 'wp-block-search__input-' . ++$instance_id; + + if ( ! empty( $attributes['label'] ) ) { + $label_markup = sprintf( + '<label for="%s" class="wp-block-search__label">%s</label>', + $input_id, + $attributes['label'] + ); + } + + $input_markup = sprintf( + '<input type="search" id="%s" class="wp-block-search__input" name="s" value="%s" placeholder="%s" />', + $input_id, + esc_attr( get_search_query() ), + esc_attr( $attributes['placeholder'] ) + ); + + if ( ! empty( $attributes['buttonText'] ) ) { + $button_markup = sprintf( + '<button type="submit" class="wp-block-search__button">%s</button>', + $attributes['buttonText'] + ); + } + + $class = 'wp-block-search'; + if ( isset( $attributes['className'] ) ) { + $class .= ' ' . $attributes['className']; + } + + return sprintf( + '<form class="%s" role="search" method="get" action="%s">%s</form>', + $class, + esc_url( home_url( '/' ) ), + $label_markup . $input_markup . $button_markup + ); +} + +/** + * Registers the `core/search` block on the server. + */ +function register_block_core_search() { + register_block_type( + 'core/search', + array( + 'attributes' => array( + 'label' => array( + 'type' => 'string', + 'default' => __( 'Search' ), + ), + 'placeholder' => array( + 'type' => 'string', + 'default' => '', + ), + 'buttonText' => array( + 'type' => 'string', + 'default' => __( 'Search' ), + ), + ), + + 'render_callback' => 'render_block_core_search', + ) + ); +} + +add_action( 'init', 'register_block_core_search' ); diff --git a/packages/block-library/src/search/style.scss b/packages/block-library/src/search/style.scss new file mode 100644 index 0000000000000..80a8a20b5fd37 --- /dev/null +++ b/packages/block-library/src/search/style.scss @@ -0,0 +1,16 @@ +.wp-block-search { + display: flex; + flex-wrap: wrap; + + .wp-block-search__label { + width: 100%; + } + + .wp-block-search__input { + flex-grow: 1; + } + + .wp-block-search__button { + margin-left: 10px; + } +} diff --git a/packages/block-library/src/search/theme.scss b/packages/block-library/src/search/theme.scss new file mode 100644 index 0000000000000..f95849183b5f6 --- /dev/null +++ b/packages/block-library/src/search/theme.scss @@ -0,0 +1,5 @@ +.wp-block-search { + .wp-block-search__label { + font-weight: bold; + } +} diff --git a/packages/block-library/src/style.scss b/packages/block-library/src/style.scss index 09d3a2125e378..35168d85ab237 100644 --- a/packages/block-library/src/style.scss +++ b/packages/block-library/src/style.scss @@ -16,6 +16,7 @@ @import "./pullquote/style.scss"; @import "./quote/style.scss"; @import "./rss/style.scss"; +@import "./search/style.scss"; @import "./separator/style.scss"; @import "./subhead/style.scss"; @import "./table/style.scss"; diff --git a/packages/block-library/src/theme.scss b/packages/block-library/src/theme.scss index 3dcef9d811ecb..7a4dcce562f8a 100644 --- a/packages/block-library/src/theme.scss +++ b/packages/block-library/src/theme.scss @@ -2,5 +2,6 @@ @import "./preformatted/theme.scss"; @import "./pullquote/theme.scss"; @import "./quote/theme.scss"; +@import "./search/theme.scss"; @import "./separator/theme.scss"; @import "./table/theme.scss"; diff --git a/test/integration/full-content/fixtures/core__search.html b/test/integration/full-content/fixtures/core__search.html new file mode 100644 index 0000000000000..f62e8a853c2ea --- /dev/null +++ b/test/integration/full-content/fixtures/core__search.html @@ -0,0 +1 @@ +<!-- wp:search /--> diff --git a/test/integration/full-content/fixtures/core__search.json b/test/integration/full-content/fixtures/core__search.json new file mode 100644 index 0000000000000..e1397fdc9a37a --- /dev/null +++ b/test/integration/full-content/fixtures/core__search.json @@ -0,0 +1,10 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/search", + "isValid": true, + "attributes": {}, + "innerBlocks": [], + "originalContent": "" + } +] diff --git a/test/integration/full-content/fixtures/core__search.parsed.json b/test/integration/full-content/fixtures/core__search.parsed.json new file mode 100644 index 0000000000000..0dfea6eb47b8f --- /dev/null +++ b/test/integration/full-content/fixtures/core__search.parsed.json @@ -0,0 +1,18 @@ +[ + { + "blockName": "core/search", + "attrs": {}, + "innerBlocks": [], + "innerHTML": "", + "innerContent": [] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/test/integration/full-content/fixtures/core__search.serialized.html b/test/integration/full-content/fixtures/core__search.serialized.html new file mode 100644 index 0000000000000..f62e8a853c2ea --- /dev/null +++ b/test/integration/full-content/fixtures/core__search.serialized.html @@ -0,0 +1 @@ +<!-- wp:search /--> diff --git a/test/integration/full-content/fixtures/core__search__custom-text.html b/test/integration/full-content/fixtures/core__search__custom-text.html new file mode 100644 index 0000000000000..bb6e6a56c9a33 --- /dev/null +++ b/test/integration/full-content/fixtures/core__search__custom-text.html @@ -0,0 +1 @@ +<!-- wp:search {"label":"Custom label","placeholder":"Custom placeholder","buttonText":"Custom button text"} /--> diff --git a/test/integration/full-content/fixtures/core__search__custom-text.json b/test/integration/full-content/fixtures/core__search__custom-text.json new file mode 100644 index 0000000000000..e1397fdc9a37a --- /dev/null +++ b/test/integration/full-content/fixtures/core__search__custom-text.json @@ -0,0 +1,10 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/search", + "isValid": true, + "attributes": {}, + "innerBlocks": [], + "originalContent": "" + } +] diff --git a/test/integration/full-content/fixtures/core__search__custom-text.parsed.json b/test/integration/full-content/fixtures/core__search__custom-text.parsed.json new file mode 100644 index 0000000000000..fd128b35f93c8 --- /dev/null +++ b/test/integration/full-content/fixtures/core__search__custom-text.parsed.json @@ -0,0 +1,22 @@ +[ + { + "blockName": "core/search", + "attrs": { + "buttonText": "Custom button text", + "label": "Custom label", + "placeholder": "Custom placeholder" + }, + "innerBlocks": [], + "innerHTML": "", + "innerContent": [] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/test/integration/full-content/fixtures/core__search__custom-text.serialized.html b/test/integration/full-content/fixtures/core__search__custom-text.serialized.html new file mode 100644 index 0000000000000..f62e8a853c2ea --- /dev/null +++ b/test/integration/full-content/fixtures/core__search__custom-text.serialized.html @@ -0,0 +1 @@ +<!-- wp:search /--> From c763c03cc07812739e067bafd2f348ff63a173c5 Mon Sep 17 00:00:00 2001 From: Daniel Bachhuber <daniel@bachhuber.co> Date: Wed, 6 Feb 2019 17:24:32 -0800 Subject: [PATCH 356/691] Add a developer tutorial for displaying notices (#13703) * Add a developer tutorial for displaying notices * Update `manifest.json` with new document * Fix link to available actions and selectors Co-Authored-By: danielbachhuber <daniel@bachhuber.co> * Avoid closures per feedback * Use 'core' to distinguish between WordPress, themes, and plugins * Link to the "How to JavaScript" tutorial for further detail --- .../developers/tutorials/notices/README.md | 80 ++++++++++++++++++ .../tutorials/notices/block-editor-notice.png | Bin 0 -> 97083 bytes .../notices/classic-editor-notice.png | Bin 0 -> 98147 bytes .../developers/tutorials/readme.md | 2 + docs/manifest.json | 6 ++ docs/toc.json | 1 + 6 files changed, 89 insertions(+) create mode 100644 docs/designers-developers/developers/tutorials/notices/README.md create mode 100644 docs/designers-developers/developers/tutorials/notices/block-editor-notice.png create mode 100644 docs/designers-developers/developers/tutorials/notices/classic-editor-notice.png diff --git a/docs/designers-developers/developers/tutorials/notices/README.md b/docs/designers-developers/developers/tutorials/notices/README.md new file mode 100644 index 0000000000000..63296f41dfb4d --- /dev/null +++ b/docs/designers-developers/developers/tutorials/notices/README.md @@ -0,0 +1,80 @@ +# Displaying Notices from Your Plugin or Theme + +Notices are informational UI displayed near the top of admin pages. WordPress core, themes, and plugins all use notices to indicate the result of an action, or to draw the user's attention to necessary information. + +In the Classic Editor, notices hooked onto the `admin_notices` action can render whatever HTML they'd like. In the Block Editor, notices are restricted to a more formal API. + +## Notices in the Classic Editor + +In the Classic Editor, here's an example of the "Post draft updated" notice: + +![Post draft updated in the Classic Editor](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/notices/classic-editor-notice.png) + +Producing an equivalent "Post draft updated" notice would require code like this: + +```php +/** + * Hook into the 'admin_notices' action to render + * a generic HTML notice. + */ +function myguten_admin_notice() { + $screen = get_current_screen(); + // Only render this notice in the post editor. + if ( ! $screen || 'post' !== $screen->base ) { + return; + } + // Render the notice's HTML. + // Each notice should be wrapped in a <div> + // with a 'notice' class. + echo '<div class="notice notice-success is-dismissible"><p>'; + echo sprintf( __( 'Post draft updated. <a href="%s" target="_blank">Preview post</a>' ), get_preview_post_link() ); + echo '</p></div>'; +}; +add_action( 'admin_notices', 'myguten_admin_notice' ); +``` + +Importantly, the `admin_notices` hook allows a developer to render whatever HTML they'd like. One advantage is that the developer has a great amount of flexibility. The key disadvantage is that arbitrary HTML makes future iterations on notices more difficult, if not possible, because the iterations need to accommodate for arbitrary HTML. This is why the Block Editor has a formal API. + +## Notices in the Block Editor + +In the Block Editor, here's an example of the "Post published" notice: + +![Post published in the Block Editor](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/notices/block-editor-notice.png) + +Producing an equivalent "Post published" notice would require code like this: + +```js +( function( wp ) { + wp.data.dispatch('core/notices').createNotice( + 'success', // Can be one of: success, info, warning, error. + 'Post published.', // Text string to display. + { + isDismissible: true, // Whether the user can dismiss the notice. + // Any actions the user can perform. + actions: [ + { + url: '#', + label: 'View post' + } + ] + } + ); +} )( window.wp ); +``` + +You'll want to use this _Notices Data API_ when producing a notice from within the JavaScript application lifecycle. + +To better understand the specific code example above: + +* `wp` is WordPress global window variable. +* `wp.data` is an object provided by the Block Editor for accessing the Block Editor data store. +* `wp.data.dispatch('core/notices')` accesses functionality registered to the Block Editor data store by the Notices package. +* `createNotice()` is a function offered by the Notices package to register a new notice. The Block Editor reads from the notice data store in order to know which notices to display. + +Check out the [_Loading JavaScript_](/docs/designers-developers/developers/tutorials/javascript/loading-javascript.md) tutorial for a primer on how to load your custom JavaScript into the Block Editor. + +## Learn More + +The Block Editor offers a complete API for generating notices. The official documentation is a great place to review what's possible. + +For a full list of the available actions and selectors, refer to the [Notices Data Handbook](/docs/designers-developers/developers/data/data-core-notices.md) page. diff --git a/docs/designers-developers/developers/tutorials/notices/block-editor-notice.png b/docs/designers-developers/developers/tutorials/notices/block-editor-notice.png new file mode 100644 index 0000000000000000000000000000000000000000..89bf3e48c0c596e71a89a8c9ec8c76208a199329 GIT binary patch literal 97083 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 JAaB`5{68MG!Oj2x literal 0 HcmV?d00001 diff --git a/docs/designers-developers/developers/tutorials/notices/classic-editor-notice.png b/docs/designers-developers/developers/tutorials/notices/classic-editor-notice.png new file mode 100644 index 0000000000000000000000000000000000000000..d96f4aa79a2e45b5ece6a29ac22d73dfd24db0ce GIT binary patch literal 98147 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 Q4e&?p?1eK~r!4RMA5k>&c>n+a literal 0 HcmV?d00001 diff --git a/docs/designers-developers/developers/tutorials/readme.md b/docs/designers-developers/developers/tutorials/readme.md index f63254cd3fbb5..dd063e13e5d64 100644 --- a/docs/designers-developers/developers/tutorials/readme.md +++ b/docs/designers-developers/developers/tutorials/readme.md @@ -6,6 +6,8 @@ * See the [Meta Boxes Tutorial](/docs/designers-developers/developers/tutorials/metabox/readme.md) for new ways of extending the editor storing and using post meta data. +* Check out the [Notices Tutorial](/docs/designers-developers/developers/tutorials/notices/README.md) to learn how to display informational UI at the top of the editor. + * The [Sidebar Tutorial](/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md) will walk you through the steps of creating a sidebar to update data from the `post_meta` table. * Learn how to add customized buttons to the toolbar with the [Format API tutorial](/docs/designers-developers/developers/tutorials/format-api/). diff --git a/docs/manifest.json b/docs/manifest.json index 6e6dbd2e40251..974392def6be0 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -281,6 +281,12 @@ "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/metabox/meta-block-5-finishing.md", "parent": "metabox" }, + { + "title": "Displaying Notices from Your Plugin or Theme", + "slug": "notices", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/notices/README.md", + "parent": "tutorials" + }, { "title": "Creating a sidebar for your plugin", "slug": "plugin-sidebar-0", diff --git a/docs/toc.json b/docs/toc.json index 14bed66d0a75e..10baee1552306 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -53,6 +53,7 @@ {"docs/designers-developers/developers/tutorials/metabox/meta-block-4-use-data.md": []}, {"docs/designers-developers/developers/tutorials/metabox/meta-block-5-finishing.md": []} ]}, + {"docs/designers-developers/developers/tutorials/notices/README.md": []}, {"docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md": [ {"docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-1-up-and-running.md": []}, {"docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-2-styles-and-controls.md": []}, From 09b722c63f851e851cac87a76c082a21079c2725 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Wrede?= <soerenwrede@gmail.com> Date: Thu, 7 Feb 2019 08:45:35 +0100 Subject: [PATCH 357/691] Fix typo (#13711) --- docs/contributors/testing-overview.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contributors/testing-overview.md b/docs/contributors/testing-overview.md index b2a2602491f9d..7d0d01921a6a5 100644 --- a/docs/contributors/testing-overview.md +++ b/docs/contributors/testing-overview.md @@ -220,7 +220,7 @@ However, if the change was intentional, follow these steps to update the snapsho npm run test-unit -- --updateSnapshot --testPathPattern path/to/tests ``` 1. Review the diff and ensure the changes are expected and intentional. -1. Commit. +2. Commit. #### What are snapshots? From c7a8c1247ce9e6b6f4d8364c2370873578a23b3a Mon Sep 17 00:00:00 2001 From: Sergey <grey.rsi@gmail.com> Date: Thu, 7 Feb 2019 10:14:54 +0200 Subject: [PATCH 358/691] =?UTF-8?q?render=5Fblock=5Fcore=5Flatest=5Fposts(?= =?UTF-8?q?)=20use=20get=5Fposts()=20instead=20of=20wp=5Fget=5Frecen?= =?UTF-8?q?=E2=80=A6=20(#11984)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * render_block_core_latest_posts() use get_posts() instead of wp_get_recent_posts() * small fix * small fix --- .../block-library/src/latest-posts/index.php | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/packages/block-library/src/latest-posts/index.php b/packages/block-library/src/latest-posts/index.php index 18a43e51828e5..0b0d858bbfe5e 100644 --- a/packages/block-library/src/latest-posts/index.php +++ b/packages/block-library/src/latest-posts/index.php @@ -14,38 +14,37 @@ */ function render_block_core_latest_posts( $attributes ) { $args = array( - 'numberposts' => $attributes['postsToShow'], - 'post_status' => 'publish', - 'order' => $attributes['order'], - 'orderby' => $attributes['orderBy'], + 'posts_per_page' => $attributes['postsToShow'], + 'post_status' => 'publish', + 'order' => $attributes['order'], + 'orderby' => $attributes['orderBy'], + 'suppress_filters' => false, ); if ( isset( $attributes['categories'] ) ) { $args['category'] = $attributes['categories']; } - $recent_posts = wp_get_recent_posts( $args ); + $recent_posts = get_posts( $args ); $list_items_markup = ''; foreach ( $recent_posts as $post ) { - $post_id = $post['ID']; - - $title = get_the_title( $post_id ); + $title = get_the_title( $post ); if ( ! $title ) { $title = __( '(Untitled)' ); } $list_items_markup .= sprintf( '<li><a href="%1$s">%2$s</a>', - esc_url( get_permalink( $post_id ) ), + esc_url( get_permalink( $post ) ), esc_html( $title ) ); if ( isset( $attributes['displayPostDate'] ) && $attributes['displayPostDate'] ) { $list_items_markup .= sprintf( '<time datetime="%1$s" class="wp-block-latest-posts__post-date">%2$s</time>', - esc_attr( get_the_date( 'c', $post_id ) ), - esc_html( get_the_date( '', $post_id ) ) + esc_attr( get_the_date( 'c', $post ) ), + esc_html( get_the_date( '', $post ) ) ); } From 3e12a7a8f45ba155a2547fc029c55a2f69516333 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Thu, 7 Feb 2019 09:22:39 +0100 Subject: [PATCH 359/691] chore(release): publish (#13717) - @wordpress/block-library@2.2.16 - @wordpress/block-serialization-default-parser@2.0.5 - @wordpress/blocks@6.0.7 - @wordpress/edit-post@3.1.11 - @wordpress/editor@9.0.11 - @wordpress/format-library@1.2.14 --- packages/block-library/package.json | 2 +- packages/block-serialization-default-parser/package.json | 2 +- packages/blocks/package.json | 2 +- packages/edit-post/package.json | 2 +- packages/editor/package.json | 2 +- packages/format-library/package.json | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/block-library/package.json b/packages/block-library/package.json index 855f2257ca3f1..d5287d21ff4a7 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-library", - "version": "2.2.15", + "version": "2.2.16", "description": "Block library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-serialization-default-parser/package.json b/packages/block-serialization-default-parser/package.json index 777bad8d74fe8..5ff1c754c2b3f 100644 --- a/packages/block-serialization-default-parser/package.json +++ b/packages/block-serialization-default-parser/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-serialization-default-parser", - "version": "2.0.4", + "version": "2.0.5", "description": "Block serialization specification parser for WordPress posts.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/blocks/package.json b/packages/blocks/package.json index f2d0534fe8ae1..6ba8838680ead 100644 --- a/packages/blocks/package.json +++ b/packages/blocks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/blocks", - "version": "6.0.6", + "version": "6.0.7", "description": "Block API for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json index d6ec1e5c769a4..875736be903a9 100644 --- a/packages/edit-post/package.json +++ b/packages/edit-post/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-post", - "version": "3.1.10", + "version": "3.1.11", "description": "Edit Post module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/editor/package.json b/packages/editor/package.json index f27cff81ed68d..64c21004d5462 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/editor", - "version": "9.0.10", + "version": "9.0.11", "description": "Building blocks for WordPress editors.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/format-library/package.json b/packages/format-library/package.json index cb77cba8519cd..1b11c71283258 100644 --- a/packages/format-library/package.json +++ b/packages/format-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/format-library", - "version": "1.2.13", + "version": "1.2.14", "description": "Format library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", From e8e26cc095a294cd8a2716a034f24f7bacb18739 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell@kjellr.com> Date: Thu, 7 Feb 2019 05:17:13 -0500 Subject: [PATCH 360/691] Fix rounded corners in block icons (#13659) * Restore rounded corners to the Gallery icon. * Restore rounded corners to the File block icon. * Restore rounded corners to the Video icon. * Restore rounded corners to the Table block icon. * Restore rounded corners to the button icon. * Restore rounded corners to the Archives block icon. * Restore rounded corners to the Latest Comments block icon. * Restore rounded corners to the audio embed block icon. * Use the correct button icon. I originally swapped this out with the 7x5 version instead of tnhe 16x9 version we'd been using previously. * Restore rounded corners to the columns block icon. * Update tests --- packages/block-library/src/archives/index.js | 2 +- packages/block-library/src/button/index.js | 2 +- packages/block-library/src/columns/index.js | 2 +- packages/block-library/src/embed/icons.js | 2 +- packages/block-library/src/file/icon.js | 2 +- packages/block-library/src/gallery/icon.js | 2 +- .../src/gallery/test/__snapshots__/index.js.snap | 8 +------- packages/block-library/src/latest-comments/index.js | 2 +- packages/block-library/src/table/index.js | 2 +- packages/block-library/src/video/icon.js | 2 +- .../src/video/test/__snapshots__/index.js.snap | 2 +- 11 files changed, 11 insertions(+), 17 deletions(-) diff --git a/packages/block-library/src/archives/index.js b/packages/block-library/src/archives/index.js index 8dfca64a8823c..b523701fcfae4 100644 --- a/packages/block-library/src/archives/index.js +++ b/packages/block-library/src/archives/index.js @@ -16,7 +16,7 @@ export const settings = { description: __( 'Display a monthly archive of your posts.' ), - icon: <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><G><Path d="M7 11h2v2H7v-2zm14-5v14l-2 2H5l-2-2V6l2-2h1V2h2v2h8V2h2v2h1l2 2zM5 8h14V6H5v2zm14 12V10H5v10h14zm-4-7h2v-2h-2v2zm-4 0h2v-2h-2v2z" /></G></SVG>, + icon: <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><G><Path d="M7 11h2v2H7v-2zm14-5v14c0 1.1-.9 2-2 2H5c-1.11 0-2-.9-2-2l.01-14c0-1.1.88-2 1.99-2h1V2h2v2h8V2h2v2h1c1.1 0 2 .9 2 2zM5 8h14V6H5v2zm14 12V10H5v10h14zm-4-7h2v-2h-2v2zm-4 0h2v-2h-2v2z" /></G></SVG>, category: 'widgets', diff --git a/packages/block-library/src/button/index.js b/packages/block-library/src/button/index.js index 5dddccfa760ca..3b8b028e9ad10 100644 --- a/packages/block-library/src/button/index.js +++ b/packages/block-library/src/button/index.js @@ -66,7 +66,7 @@ export const settings = { description: __( 'Prompt visitors to take action with a custom button.' ), - icon: <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><G><Path d="M19 6H5L3 8v8l2 2h14l2-2V8l-2-2zm0 10H5V8h14v8z" /></G></SVG>, + icon: <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><G><Path d="M19 6H5c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 10H5V8h14v8z" /></G></SVG>, category: 'layout', diff --git a/packages/block-library/src/columns/index.js b/packages/block-library/src/columns/index.js index 2c0e55b2d1035..374650b9ab13b 100644 --- a/packages/block-library/src/columns/index.js +++ b/packages/block-library/src/columns/index.js @@ -70,7 +70,7 @@ export const name = 'core/columns'; export const settings = { title: __( 'Columns' ), - icon: <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><G><Path d="M21 4H3L2 5v14l1 1h18l1-1V5l-1-1zM8 18H4V6h4v12zm6 0h-4V6h4v12zm6 0h-4V6h4v12z" /></G></SVG>, + icon: <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><G><Path d="M4,4H20a2,2,0,0,1,2,2V18a2,2,0,0,1-2,2H4a2,2,0,0,1-2-2V6A2,2,0,0,1,4,4ZM4 6V18H8V6Zm6 0V18h4V6Zm6 0V18h4V6Z" /></G></SVG>, category: 'layout', diff --git a/packages/block-library/src/embed/icons.js b/packages/block-library/src/embed/icons.js index d7ebe434d8fdb..21f042479080b 100644 --- a/packages/block-library/src/embed/icons.js +++ b/packages/block-library/src/embed/icons.js @@ -9,7 +9,7 @@ import { } from '@wordpress/components'; export const embedContentIcon = <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path d="M0,0h24v24H0V0z" fill="none" /><Path d="M19,4H5C3.89,4,3,4.9,3,6v12c0,1.1,0.89,2,2,2h14c1.1,0,2-0.9,2-2V6C21,4.9,20.11,4,19,4z M19,18H5V8h14V18z" /></SVG>; -export const embedAudioIcon = <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><Path d="M21 3H3L1 5v14l2 2h18l2-2V5l-2-2zm0 16H3V5h18v14zM8 15a3 3 0 0 1 4-3V6h5v2h-3v7a3 3 0 0 1-6 0z" /></SVG>; +export const embedAudioIcon = <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><Path d="M21 3H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3V5h18v14zM8 15c0-1.66 1.34-3 3-3 .35 0 .69.07 1 .18V6h5v2h-3v7.03c-.02 1.64-1.35 2.97-3 2.97-1.66 0-3-1.34-3-3z" /></SVG>; export const embedPhotoIcon = <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path d="M0,0h24v24H0V0z" fill="none" /><Path d="M21,4H3C1.9,4,1,4.9,1,6v12c0,1.1,0.9,2,2,2h18c1.1,0,2-0.9,2-2V6C23,4.9,22.1,4,21,4z M21,18H3V6h18V18z" /><Polygon points="14.5 11 11 15.51 8.5 12.5 5 17 19 17" /></SVG>; export const embedVideoIcon = <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path d="M0,0h24v24H0V0z" fill="none" /><Path d="m10 8v8l5-4-5-4zm9-5h-14c-1.1 0-2 0.9-2 2v14c0 1.1 0.9 2 2 2h14c1.1 0 2-0.9 2-2v-14c0-1.1-0.9-2-2-2zm0 16h-14v-14h14v14z" /></SVG>; export const embedTwitterIcon = { diff --git a/packages/block-library/src/file/icon.js b/packages/block-library/src/file/icon.js index a4ddc5b93f910..2ee9e4867b270 100644 --- a/packages/block-library/src/file/icon.js +++ b/packages/block-library/src/file/icon.js @@ -3,4 +3,4 @@ */ import { Path, SVG } from '@wordpress/components'; -export default <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><Path d="M9 6l2 2h9v10H4V6h5m1-2H4L2 6v12l2 2h16l2-2V8l-2-2h-8l-2-2z" /></SVG>; +export default <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><Path d="M9.17 6l2 2H20v10H4V6h5.17M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z" /></SVG>; diff --git a/packages/block-library/src/gallery/icon.js b/packages/block-library/src/gallery/icon.js index 54dce1ede1e53..134b3087540de 100644 --- a/packages/block-library/src/gallery/icon.js +++ b/packages/block-library/src/gallery/icon.js @@ -3,4 +3,4 @@ */ import { G, Path, SVG } from '@wordpress/components'; -export default <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><G><Path d="M20 4v12H8V4h12m0-2H8L6 4v12l2 2h12l2-2V4l-2-2z" /><Path d="M12 12l1 2 3-3 3 4H9z" /><Path d="M2 6v14l2 2h14v-2H4V6H2z" /></G></SVG>; +export default <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><G><Path d="M20 4v12H8V4h12m0-2H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-8.5 9.67l1.69 2.26 2.48-3.1L19 15H9zM2 6v14c0 1.1.9 2 2 2h14v-2H4V6H2z" /></G></SVG>; diff --git a/packages/block-library/src/gallery/test/__snapshots__/index.js.snap b/packages/block-library/src/gallery/test/__snapshots__/index.js.snap index 48829d35124c6..9fd3dff017b9d 100644 --- a/packages/block-library/src/gallery/test/__snapshots__/index.js.snap +++ b/packages/block-library/src/gallery/test/__snapshots__/index.js.snap @@ -25,13 +25,7 @@ exports[`core/gallery block edit matches snapshot 1`] = ` /> <g> <path - d="M20 4v12H8V4h12m0-2H8L6 4v12l2 2h12l2-2V4l-2-2z" - /> - <path - d="M12 12l1 2 3-3 3 4H9z" - /> - <path - d="M2 6v14l2 2h14v-2H4V6H2z" + d="M20 4v12H8V4h12m0-2H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-8.5 9.67l1.69 2.26 2.48-3.1L19 15H9zM2 6v14c0 1.1.9 2 2 2h14v-2H4V6H2z" /> </g> </svg> diff --git a/packages/block-library/src/latest-comments/index.js b/packages/block-library/src/latest-comments/index.js index d7420744d0011..2fc124997196b 100644 --- a/packages/block-library/src/latest-comments/index.js +++ b/packages/block-library/src/latest-comments/index.js @@ -16,7 +16,7 @@ export const settings = { description: __( 'Display a list of your most recent comments.' ), - icon: <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><G><Path d="M22 4l-2-2H4L2 4v12l2 2h14l4 4V4zm-2 0v13l-1-1H4V4h16z" /><Path d="M6 12h12v2H6zM6 9h12v2H6zM6 6h12v2H6z" /></G></SVG>, + icon: <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><G><Path d="M21.99 4c0-1.1-.89-2-1.99-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h14l4 4-.01-18zM20 4v13.17L18.83 16H4V4h16zM6 12h12v2H6zm0-3h12v2H6zm0-3h12v2H6z" /></G></SVG>, category: 'widgets', diff --git a/packages/block-library/src/table/index.js b/packages/block-library/src/table/index.js index c6221d688c392..af53386b40f9a 100644 --- a/packages/block-library/src/table/index.js +++ b/packages/block-library/src/table/index.js @@ -78,7 +78,7 @@ export const name = 'core/table'; export const settings = { title: __( 'Table' ), description: __( 'Insert a table — perfect for sharing charts and data.' ), - icon: <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><G><Path d="M20 3H5L3 5v14l2 2h15l2-2V5l-2-2zm0 2v3H5V5h15zm-5 14h-5v-9h5v9zM5 10h3v9H5v-9zm12 9v-9h3v9h-3z" /></G></SVG>, + icon: <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><G><Path d="M20 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h15c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 2v3H5V5h15zm-5 14h-5v-9h5v9zM5 10h3v9H5v-9zm12 9v-9h3v9h-3z" /></G></SVG>, category: 'formatting', attributes: { diff --git a/packages/block-library/src/video/icon.js b/packages/block-library/src/video/icon.js index a029952561e9d..90dd0d64d58ff 100644 --- a/packages/block-library/src/video/icon.js +++ b/packages/block-library/src/video/icon.js @@ -3,4 +3,4 @@ */ import { Path, SVG } from '@wordpress/components'; -export default <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><Path d="M4 6l2 4h14v8H4V6m18-2h-4l2 4h-3l-2-4h-2l2 4h-3l-2-4H8l2 4H7L5 4H4L2 6v12l2 2h16l2-2V4z" /></SVG>; +export default <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><Path d="M4 6.47L5.76 10H20v8H4V6.47M22 4h-4l2 4h-3l-2-4h-2l2 4h-3l-2-4H8l2 4H7L5 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V4z" /></SVG>; diff --git a/packages/block-library/src/video/test/__snapshots__/index.js.snap b/packages/block-library/src/video/test/__snapshots__/index.js.snap index 174b01d7878ed..d728e5e6b6ce0 100644 --- a/packages/block-library/src/video/test/__snapshots__/index.js.snap +++ b/packages/block-library/src/video/test/__snapshots__/index.js.snap @@ -24,7 +24,7 @@ exports[`core/video block edit matches snapshot 1`] = ` fill="none" /> <path - d="M4 6l2 4h14v8H4V6m18-2h-4l2 4h-3l-2-4h-2l2 4h-3l-2-4H8l2 4H7L5 4H4L2 6v12l2 2h16l2-2V4z" + d="M4 6.47L5.76 10H20v8H4V6.47M22 4h-4l2 4h-3l-2-4h-2l2 4h-3l-2-4H8l2 4H7L5 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V4z" /> </svg> </span> From 7980d19ca0cc559bdb831ec174108adb0b01bc66 Mon Sep 17 00:00:00 2001 From: Joen Asmussen <joen@automattic.com> Date: Thu, 7 Feb 2019 11:40:33 +0100 Subject: [PATCH 361/691] Add a few contributors (#13724) This PR recognizes the valuable contributions to the columns block by @drdogbot7 and @m-e-h. --- CONTRIBUTORS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 0cde50e287487..e80c404136ed9 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -126,3 +126,5 @@ This list is manually curated to include valuable contributions by volunteers th | @miya0001 | @miyauchi | | @naogify | @naoki0h | | @gutendev | @gutendev | +| @drdogbot7 | @drdogbot7 | +| @m-e-h | @m-e-h | From 9e1d20ed49a8722abb0ba002692f171c6c628e3e Mon Sep 17 00:00:00 2001 From: Danilo Ercoli <ercoli@gmail.com> Date: Thu, 7 Feb 2019 12:52:21 +0100 Subject: [PATCH 362/691] [RNMobile] Make placeholder text working on RichText (#13699) * Make placeholder text working again on RichText * Make sure the native side is always refreshed when the RichText component is cleared * Remove placeholder check --- packages/editor/src/components/rich-text/index.native.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/editor/src/components/rich-text/index.native.js b/packages/editor/src/components/rich-text/index.native.js index 67d2a86b38957..8a7ce63178eaf 100644 --- a/packages/editor/src/components/rich-text/index.native.js +++ b/packages/editor/src/components/rich-text/index.native.js @@ -360,7 +360,12 @@ export class RichText extends Component { } ) ); // Save back to HTML from React tree - const html = '<' + tagName + '>' + value + '</' + tagName + '>'; + let html = '<' + tagName + '>' + value + '</' + tagName + '>'; + // We need to check if the value is undefined or empty, and then assign it properly otherwise the placeholder is not visible + if ( value === undefined || value === '' ) { + html = ''; + this.lastEventCount = undefined; // force a refresh on the native side + } return ( <View> @@ -373,6 +378,7 @@ export class RichText extends Component { } } text={ { text: html, eventCount: this.lastEventCount } } + placeholder={ this.props.placeholder } onChange={ this.onChange } onFocus={ this.props.onFocus } onBlur={ this.props.onBlur } From 334b22d42e3f5bc2a028e138a5ff849316377aba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Thu, 7 Feb 2019 12:56:16 +0100 Subject: [PATCH 363/691] Update codeowners for mobile (#13730) --- .github/CODEOWNERS | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e644c9034890b..ea182fad9c07a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -77,4 +77,8 @@ # Native (Unowned) *.native.js @ghost +*.android.js @ghost +*.ios.js @ghost *.native.scss @ghost +*.android.scss @ghost +*.ios.scss @ghost From 14ded010237aa6da7b615418008da3c5129e573a Mon Sep 17 00:00:00 2001 From: Nicola Heald <nicola@notnowlewis.com> Date: Thu, 7 Feb 2019 11:59:31 +0000 Subject: [PATCH 364/691] Add notnownikki as code owner for block-library and docs (#13731) --- .github/CODEOWNERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ea182fad9c07a..f5e5fef4f78cf 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -5,7 +5,7 @@ /packages/redux-routine @youknowriad @aduth @nerrad # Blocks -/packages/block-library @youknowriad @gziolo @Soean @ajitbohra @jorgefilipecosta @talldan @noisysocks +/packages/block-library @youknowriad @gziolo @Soean @ajitbohra @jorgefilipecosta @talldan @noisysocks @notnownikki # Editor /packages/annotations @youknowriad @gziolo @aduth @atimmer @@ -73,7 +73,7 @@ /lib @youknowriad @gziolo @aduth # Documentation -/docs @youknowriad @gziolo @chrisvanpatten @mkaz @ajitbohra @nosolosw +/docs @youknowriad @gziolo @chrisvanpatten @mkaz @ajitbohra @nosolosw @notnownikki # Native (Unowned) *.native.js @ghost From 775722c8ad2e161fb7357815634adefccf3df3a7 Mon Sep 17 00:00:00 2001 From: Naoki Ohashi <n.globe.us@gmail.com> Date: Thu, 7 Feb 2019 22:07:38 +0900 Subject: [PATCH 365/691] Editor: Fix second welcome tip instructs the user to open a control that's already open. (#12911) * fix the text of second welcome tip instruction * Fix Typo * Update index.js --- packages/edit-post/src/components/header/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/edit-post/src/components/header/index.js b/packages/edit-post/src/components/header/index.js index 3f3ed97063cd8..7b59784d5d83e 100644 --- a/packages/edit-post/src/components/header/index.js +++ b/packages/edit-post/src/components/header/index.js @@ -69,7 +69,7 @@ function Header( { shortcut={ shortcuts.toggleSidebar } /> <DotTip tipId="core/editor.settings"> - { __( 'You’ll find more settings for your page and blocks in the sidebar. Click “Settings” to open it.' ) } + { __( 'You’ll find more settings for your page and blocks in the sidebar. Click the cog icon to toggle the sidebar open and closed.' ) } </DotTip> </div> <PinnedPlugins.Slot /> From e7fa36768a02ab884add66b17fd1ffa7e44f63ab Mon Sep 17 00:00:00 2001 From: torres126 <43215253+torres126@users.noreply.github.com> Date: Thu, 7 Feb 2019 13:26:29 +0000 Subject: [PATCH 366/691] Update Wording of Sticky Checkbox (#13120) * Update Wording * Update Copy --- packages/editor/src/components/post-sticky/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor/src/components/post-sticky/index.js b/packages/editor/src/components/post-sticky/index.js index ac259e568d446..9cc787d19a8b1 100644 --- a/packages/editor/src/components/post-sticky/index.js +++ b/packages/editor/src/components/post-sticky/index.js @@ -15,7 +15,7 @@ export function PostSticky( { onUpdateSticky, postSticky = false } ) { return ( <PostStickyCheck> <CheckboxControl - label={ __( 'Stick to the Front Page' ) } + label={ __( 'Stick to the top of the blog' ) } checked={ postSticky } onChange={ () => onUpdateSticky( ! postSticky ) } /> From dc7013ed9e7a4f9534d3acd2e36d2ec8a8be3bab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20Van=C2=A0Durpe?= <iseulde@automattic.com> Date: Thu, 7 Feb 2019 14:32:18 +0100 Subject: [PATCH 367/691] CODEOWNERS: Add RichText component to Rich Text section (#13734) --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f5e5fef4f78cf..df5de975fac4f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -68,6 +68,7 @@ # Rich Text /packages/format-library @youknowriad @gziolo @aduth @iseulde @jorgefilipecosta /packages/rich-text @youknowriad @gziolo @aduth @iseulde @jorgefilipecosta +/packages/editor/src/components/rich-text @youknowriad @gziolo @aduth @iseulde @jorgefilipecosta # PHP /lib @youknowriad @gziolo @aduth From e0be6d508cf4a32ecccb34b9956bc3679c037720 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez <diegoreymendez@users.noreply.github.com> Date: Thu, 7 Feb 2019 12:31:56 -0300 Subject: [PATCH 368/691] Adds placeholder coloring support for mobile's rich text component. (#13738) * Adds placeholder support for mobile's rich text component. * Removes an unnecessary line of code. --- packages/editor/src/components/rich-text/index.native.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/editor/src/components/rich-text/index.native.js b/packages/editor/src/components/rich-text/index.native.js index 8a7ce63178eaf..c52f31cbc96ed 100644 --- a/packages/editor/src/components/rich-text/index.native.js +++ b/packages/editor/src/components/rich-text/index.native.js @@ -379,6 +379,7 @@ export class RichText extends Component { } text={ { text: html, eventCount: this.lastEventCount } } placeholder={ this.props.placeholder } + placeholderTextColor={ this.props.placeholderTextColor || 'lightgrey' } onChange={ this.onChange } onFocus={ this.props.onFocus } onBlur={ this.props.onBlur } From dafd70038664c54619ec285a3ef66d2c7d1ca754 Mon Sep 17 00:00:00 2001 From: Daniel Bachhuber <daniel@bachhuber.co> Date: Thu, 7 Feb 2019 07:37:53 -0800 Subject: [PATCH 369/691] Switch a bunch of documentation titles to title case (#13714) * Switch a bunch of documentation titles to title case * Fix one lingering instance * Update `manifest.json` with new titles * Fix merge conflict discrepancy * npm should always be lowercase Co-Authored-By: danielbachhuber <daniel@bachhuber.co> * Correct spelling for JavaScript Co-Authored-By: danielbachhuber <daniel@bachhuber.co> * Update `manifest.json` again --- .../developers/internationalization.md | 2 +- .../developers/packages.md | 4 ++-- .../tutorials/format-api/1-register-format.md | 2 +- .../tutorials/format-api/2-toolbar-button.md | 2 +- .../tutorials/format-api/3-apply-format.md | 2 +- .../developers/tutorials/format-api/README.md | 2 +- .../tutorials/javascript/scope-your-code.md | 8 +++---- .../tutorials/javascript/troubleshooting.md | 2 +- .../javascript/versions-and-building.md | 2 +- .../sidebar-tutorial/plugin-sidebar-0.md | 2 +- .../plugin-sidebar-1-up-and-running.md | 2 +- .../plugin-sidebar-3-register-meta.md | 2 +- .../plugin-sidebar-4-initialize-input.md | 2 +- .../plugin-sidebar-5-update-meta.md | 2 +- .../plugin-sidebar-6-finishing-touches.md | 2 +- docs/manifest.json | 22 +++++++++---------- 16 files changed, 30 insertions(+), 30 deletions(-) diff --git a/docs/designers-developers/developers/internationalization.md b/docs/designers-developers/developers/internationalization.md index 5dcd88f011261..e8e02de2192e9 100644 --- a/docs/designers-developers/developers/internationalization.md +++ b/docs/designers-developers/developers/internationalization.md @@ -26,7 +26,7 @@ The new script package is registered with WordPress as `wp-i18n` and should be d Depending on your developer workflow, you might want to use WP-CLI's `wp i18n make-pot` command or a build tool for Babel called `@wordpress/babel-plugin-makepot` to create the necessary translation file. The latter approach integrates with Babel to extract the I18N methods. -### Common methods in wp.i18n (may look similar) +### Common Methods in wp.i18n (May Look Similar) - `setLocaleData( data: Object, domain: string )`: Creates a new I18N instance providing translation data for a domain. - `__( 'Hello World', 'my-text-domain' )`: Translate a certain string. diff --git a/docs/designers-developers/developers/packages.md b/docs/designers-developers/developers/packages.md index 66235b7fa17b8..e2d5a97a48211 100644 --- a/docs/designers-developers/developers/packages.md +++ b/docs/designers-developers/developers/packages.md @@ -2,7 +2,7 @@ WordPress exposes a list of JavaScript packages and tools for WordPress development. -## Using the packages via WordPress global +## Using the Packages via WordPress Global JavaScript packages are available as a registered script in WordPress and can be accessed using the `wp` global variable. @@ -22,7 +22,7 @@ const { PlainText } = wp.editor; ``` -## Using the packages via npm +## Using the Packages via npm All the packages are also available on [npm](https://www.npmjs.com/org/wordpress) if you want to bundle them in your code. diff --git a/docs/designers-developers/developers/tutorials/format-api/1-register-format.md b/docs/designers-developers/developers/tutorials/format-api/1-register-format.md index 80d091d214ba6..667a63206295e 100644 --- a/docs/designers-developers/developers/tutorials/format-api/1-register-format.md +++ b/docs/designers-developers/developers/tutorials/format-api/1-register-format.md @@ -1,4 +1,4 @@ -# Register a new format +# Register a New Format The first thing you're going to do in this tutorial is to register the new format that the plugin intends to apply. WordPress has the [`registerFormatType`](/packages/rich-text/README.md#registerFormatType) function to do so. diff --git a/docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md b/docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md index 19ae587159c76..8aea061c5ef16 100644 --- a/docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md +++ b/docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md @@ -1,4 +1,4 @@ -# Add a button to the toolbar +# Add a Button to the Toolbar Now that the format is avaible, the next step is to surface it to the UI. You can make use of the [`RichTextToolbarButton`](/packages/editor/src/components/rich-text/README.md#RichTextToolbarButton) component to extend the format toolbar. diff --git a/docs/designers-developers/developers/tutorials/format-api/3-apply-format.md b/docs/designers-developers/developers/tutorials/format-api/3-apply-format.md index 0fbcd031db5f2..278f74143882c 100644 --- a/docs/designers-developers/developers/tutorials/format-api/3-apply-format.md +++ b/docs/designers-developers/developers/tutorials/format-api/3-apply-format.md @@ -1,4 +1,4 @@ -# Apply the format when the button is clicked +# Apply the Format When the Button Is Clicked So far, your custom button doesn't modify the text selected, it only renders a message in the console. Let's change that. diff --git a/docs/designers-developers/developers/tutorials/format-api/README.md b/docs/designers-developers/developers/tutorials/format-api/README.md index 2f41423a3f92c..3ecb76c07e059 100644 --- a/docs/designers-developers/developers/tutorials/format-api/README.md +++ b/docs/designers-developers/developers/tutorials/format-api/README.md @@ -6,7 +6,7 @@ In WordPress lingo, a _format_ is a [HTML tag with text-level semantics](https:/ If you are unfamiliar with how to work with WordPress plugins and JavaScript, you may want to check the [JavaScript Tutorial](/docs/designers-developers/developers/tutorials/javascript/readme.md) first. -## Table of contents +## Table of Contents 1. [Register a new format](/docs/designers-developers/developers/tutorials/format-api/1-register-format.md) 2. [Add a button to the toolbar](/docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md) diff --git a/docs/designers-developers/developers/tutorials/javascript/scope-your-code.md b/docs/designers-developers/developers/tutorials/javascript/scope-your-code.md index 58ddcbcaf0553..5d78b928ae603 100644 --- a/docs/designers-developers/developers/tutorials/javascript/scope-your-code.md +++ b/docs/designers-developers/developers/tutorials/javascript/scope-your-code.md @@ -1,4 +1,4 @@ -# Scope your code +# Scope Your Code Historically, JavaScript files loaded in a web page share the same scope. This means that a global variable declared in one file will be seen by the code in other files. @@ -26,7 +26,7 @@ When loaded on the same page, `first.js` and `second.js` will output the plugin This behavior can be problematic, and is the reason we need to scope the code. By scoping the code—ensuring each file is isolated from each other—we can prevent values unexpectedly changing. -## Scoping code within a function +## Scoping Code Within a Function In JavaScript, you can scope your code by writing it within a function. Functions have "local scope", or a scope that is specific only to that function. Aditionally, in JavaScript you can write anonymous functions, functions without a name, which will also prevent your function name from being overridden in the global scope. @@ -58,7 +58,7 @@ function() { With this trick, the different files won't override each other's variables. Unfortunately, they also won't work as expected, because these functions are being called by no one. We've only _defined_ the functions; we haven't _executed_ them yet. -## Automatically execute anonymous functions +## Automatically Execute Anonymous Functions It turns out there are a few ways to execute anonymous functions in JavaScript, but the most popular is this: @@ -106,7 +106,7 @@ On the other hand, `third.js` doesn't declare a `pluginName` variable, but needs } )( window.pluginName ) ``` -## Future changes +## Future Changes At the beginning we mentioned that: diff --git a/docs/designers-developers/developers/tutorials/javascript/troubleshooting.md b/docs/designers-developers/developers/tutorials/javascript/troubleshooting.md index 056fa803cbd61..c5b6d9895c91b 100644 --- a/docs/designers-developers/developers/tutorials/javascript/troubleshooting.md +++ b/docs/designers-developers/developers/tutorials/javascript/troubleshooting.md @@ -16,7 +16,7 @@ If you are not seeing your changes, check that your JavaScript file is being enq If you do not see the file being loaded, doublecheck the enqueue function is correct. You can also check your server logs to see if there is an error messages. -## Confirm all dependencies are loaded +## Confirm All Dependencies Are Loaded The console log will show an error if a dependency your JavaScript code uses has not been declared and loaded in the browser. In the example, if `myguten.js` script is enqueued without declaring the `wp-blocks` dependency, the console log will show: diff --git a/docs/designers-developers/developers/tutorials/javascript/versions-and-building.md b/docs/designers-developers/developers/tutorials/javascript/versions-and-building.md index c7de41efd4d0a..fb52d247d7861 100644 --- a/docs/designers-developers/developers/tutorials/javascript/versions-and-building.md +++ b/docs/designers-developers/developers/tutorials/javascript/versions-and-building.md @@ -1,4 +1,4 @@ -# JavaScript versions and build step +# JavaScript Versions and Build Step The Gutenberg Handbook shows JavaScript examples in two syntaxes: ES5 and ESNext. These are version names for the JavaScript language standard definitions. You may also see elsewhere the names ES6, or ECMAScript 2015 mentioned. See the [ECMAScript](https://en.wikipedia.org/wiki/ECMAScript) Wikipedia article for all the details. diff --git a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md index 3ec8bcba59e93..f5f270cc9c533 100644 --- a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md +++ b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md @@ -1,4 +1,4 @@ -# Creating a sidebar for your plugin +# Creating a Sidebar for Your Plugin This tutorial starts with you having an existing plugin setup and ready to add PHP and JavaScript code. Please, refer to [Getting started with JavaScript](/docs/designers-developers/developers/tutorials/javascript/) tutorial for an introduction to WordPress plugins and how to use JavaScript to extend the block editor. diff --git a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-1-up-and-running.md b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-1-up-and-running.md index 6b9c335f335a5..73355c151a6fb 100644 --- a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-1-up-and-running.md +++ b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-1-up-and-running.md @@ -1,4 +1,4 @@ -# Get a sidebar up and running +# Get a Sidebar up and Running The first step in the journey is to tell the editor that there is a new plugin that will have its own sidebar. You can do so by using the [registerPlugin](/packages/plugins/REAMDE.md), [PluginSidebar](/packages/edit-post/README.md#pluginsidebar), and [createElement](/packages/element/README.md) utilities provided by WordPress, to be found in the `@wordpress/plugins`, `@wordpress/edit-post`, and `@wordpress/element` [packages](/docs/designers-developers/developers/packages.md), respectively. diff --git a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-3-register-meta.md b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-3-register-meta.md index 9434ee462fc8b..da1717c8817f2 100644 --- a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-3-register-meta.md +++ b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-3-register-meta.md @@ -1,4 +1,4 @@ -# Register the meta field +# Register the Meta Field To work with fields in the `post_meta` table, WordPress has a function called [register_meta](https://developer.wordpress.org/reference/functions/register_meta/). You're going to use it to register a new field called `sidebar_plugin_meta_block_field`, which will be a single string. Note that this field needs to be available through the [REST API](https://developer.wordpress.org/rest-api/) because that's how the block editor access data. diff --git a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-4-initialize-input.md b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-4-initialize-input.md index d826d9df5547c..bc602e39b2798 100644 --- a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-4-initialize-input.md +++ b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-4-initialize-input.md @@ -1,4 +1,4 @@ -# Initialize the input control +# Initialize the Input Control Now that the field is available in the editor store, it can be surfaced to the UI. The first step will be to extract the input control to a separate function so you can expand its functionality while the code stays clear. diff --git a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-5-update-meta.md b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-5-update-meta.md index 0ccab7e115b18..ac5a9884cae9f 100644 --- a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-5-update-meta.md +++ b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-5-update-meta.md @@ -1,4 +1,4 @@ -# Update the meta field when the input's content changes +# Update the Meta Field When the Input's Content Changes The last step in the journey is to update the meta field when the input content changes. To do that, you'll use another utility from the `@wordpress/data` package, [withDispatch](/packages/data/README.md#withdispatch-mapdispatchtoprops-function-function). diff --git a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md index e15b452d59762..b399c2e59189d 100644 --- a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md +++ b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md @@ -1,4 +1,4 @@ -# Finishing touches +# Finishing Touches Your JavaScript code now works as expected, here are a few ways to simplify and make it easier to change in the future. diff --git a/docs/manifest.json b/docs/manifest.json index 974392def6be0..31ad58370f291 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -234,13 +234,13 @@ "parent": "javascript" }, { - "title": "JavaScript versions and build step", + "title": "JavaScript Versions and Build Step", "slug": "versions-and-building", "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/versions-and-building.md", "parent": "javascript" }, { - "title": "Scope your code", + "title": "Scope Your Code", "slug": "scope-your-code", "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/scope-your-code.md", "parent": "javascript" @@ -288,13 +288,13 @@ "parent": "tutorials" }, { - "title": "Creating a sidebar for your plugin", + "title": "Creating a Sidebar for Your Plugin", "slug": "plugin-sidebar-0", "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md", "parent": "tutorials" }, { - "title": "Get a sidebar up and running", + "title": "Get a Sidebar up and Running", "slug": "plugin-sidebar-1-up-and-running", "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-1-up-and-running.md", "parent": "plugin-sidebar-0" @@ -306,25 +306,25 @@ "parent": "plugin-sidebar-0" }, { - "title": "Register the meta field", + "title": "Register the Meta Field", "slug": "plugin-sidebar-3-register-meta", "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-3-register-meta.md", "parent": "plugin-sidebar-0" }, { - "title": "Initialize the input control", + "title": "Initialize the Input Control", "slug": "plugin-sidebar-4-initialize-input", "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-4-initialize-input.md", "parent": "plugin-sidebar-0" }, { - "title": "Update the meta field when the input's content changes", + "title": "Update the Meta Field When the Input's Content Changes", "slug": "plugin-sidebar-5-update-meta", "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-5-update-meta.md", "parent": "plugin-sidebar-0" }, { - "title": "Finishing touches", + "title": "Finishing Touches", "slug": "plugin-sidebar-6-finishing-touches", "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md", "parent": "plugin-sidebar-0" @@ -336,19 +336,19 @@ "parent": "tutorials" }, { - "title": "Register a new format", + "title": "Register a New Format", "slug": "1-register-format", "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/format-api/1-register-format.md", "parent": "format-api" }, { - "title": "Add a button to the toolbar", + "title": "Add a Button to the Toolbar", "slug": "2-toolbar-button", "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md", "parent": "format-api" }, { - "title": "Apply the format when the button is clicked", + "title": "Apply the Format When the Button Is Clicked", "slug": "3-apply-format", "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/format-api/3-apply-format.md", "parent": "format-api" From 3f0ef0b09d2c33bdcdc3539381e9fd436f313810 Mon Sep 17 00:00:00 2001 From: Joen Asmussen <joen@automattic.com> Date: Thu, 7 Feb 2019 17:27:46 +0100 Subject: [PATCH 370/691] Add an Animate component and menu appear animation for popovers & start work on handbook (#13617) --- .../designers/animation.md | 33 +++++++ docs/manifest.json | 12 +++ docs/toc.json | 3 +- packages/components/CHANGELOG.md | 4 + packages/components/src/animate/README.md | 39 +++++++++ packages/components/src/animate/index.js | 25 ++++++ packages/components/src/animate/style.scss | 28 ++++++ packages/components/src/index.js | 1 + packages/components/src/popover/index.js | 85 ++++++++++++------- .../popover/test/__snapshots__/index.js.snap | 4 +- packages/components/src/style.scss | 1 + packages/components/src/tooltip/index.js | 1 + .../src/click-on-more-menu-item.js | 6 ++ packages/e2e-test-utils/src/index.js | 1 + .../e2e-test-utils/src/search-for-block.js | 6 ++ .../src/switch-editor-mode-to.js | 6 ++ .../e2e-test-utils/src/transform-block-to.js | 7 ++ .../e2e-test-utils/src/wait-for-animation.js | 10 +++ .../e2e-tests/specs/block-deletion.test.js | 2 + .../specs/block-hierarchy-navigation.test.js | 5 +- packages/e2e-tests/specs/editor-modes.test.js | 11 ++- .../e2e-tests/specs/font-size-picker.test.js | 3 +- .../e2e-tests/specs/invalid-block.test.js | 2 + packages/e2e-tests/specs/links.test.js | 20 +++++ packages/e2e-tests/specs/nux.test.js | 2 + .../specs/plugins/annotations.test.js | 2 + .../specs/plugins/container-blocks.test.js | 2 + .../e2e-tests/specs/reusable-blocks.test.js | 7 ++ 28 files changed, 293 insertions(+), 35 deletions(-) create mode 100644 docs/designers-developers/designers/animation.md create mode 100644 packages/components/src/animate/README.md create mode 100644 packages/components/src/animate/index.js create mode 100644 packages/components/src/animate/style.scss create mode 100644 packages/e2e-test-utils/src/wait-for-animation.js diff --git a/docs/designers-developers/designers/animation.md b/docs/designers-developers/designers/animation.md new file mode 100644 index 0000000000000..66664639d3a23 --- /dev/null +++ b/docs/designers-developers/designers/animation.md @@ -0,0 +1,33 @@ +# Animation + +Animation can help reinforce a sense of hierarchy and spatial orientation. This document goes into principles you should follow when you add animation. + +## Principles + +### Point of Origin + +- Animation can help anchor an interface element. For example a menu can scale up from the button that opened it. +- Animation can help give a sense of place; for example a sidebar can animate in from the side, implying it was always hidden off-screen. +- Design your animations as if you're working with real-world materials. Imagine your user interface elements are made of real materials — when not on screen, where are they? Use animation to help express that. + +### Speed + +- Animations should never block a user interaction. They should be fast, almost always complete in less than 0.2 seconds. +- A user should not have to wait for an animation to finish before they can interact. +- Animations should be performant. Use `transform` CSS properties when you can, these render elements on the GPU, making them smooth. +- If an animation can't be made fast & performant, leave it out. + +### Simple + +- Don't bounce if the material isn't made of rubber. +- Don't rotate, fold, or animate on a curved path. Keep it simple. + +### Consistency + +In creating consistent animations, we have to establish physical rules for how elements behave when animated. When all animations follow these rules, they feel consistent, related, and predictable. An animation should match user expectations, if it doesn't, it's probably not the right animation for the job. + +Reuse animations if one already exists for your task. + +## Inventory of Reused Animations + +The generic `Animate` component is used to animate different parts of the interface. See [the component documentation](/packages/components/src/animate/README.md) for more details about the available animations. diff --git a/docs/manifest.json b/docs/manifest.json index 31ad58370f291..4e8380c93aaf7 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -377,6 +377,12 @@ "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/designers/design-resources.md", "parent": "designers" }, + { + "title": "Animation", + "slug": "animation", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/designers/animation.md", + "parent": "designers" + }, { "title": "Contributors Guide", "slug": "contributors", @@ -815,6 +821,12 @@ "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/wordcount/README.md", "parent": "packages" }, + { + "title": "Animate", + "slug": "animate", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/animate/README.md", + "parent": "components" + }, { "title": "Autocomplete", "slug": "autocomplete", diff --git a/docs/toc.json b/docs/toc.json index 10baee1552306..cf299daf60c62 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -72,7 +72,8 @@ {"docs/designers-developers/designers/README.md": [ {"docs/designers-developers/designers/block-design.md": []}, {"docs/designers-developers/designers/design-patterns.md": []}, - {"docs/designers-developers/designers/design-resources.md": []} + {"docs/designers-developers/designers/design-resources.md": []}, + {"docs/designers-developers/designers/animation.md": []} ]} ]}, {"docs/contributors/readme.md": [ diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 11879149e231d..d0489fb5c4a4c 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -1,5 +1,9 @@ ## 7.1.0 (Unreleased) +### New Features + +- Added a new `Animate` component. + ### Improvements - `withFilters` has been optimized to avoid binding hook handlers for each mounted instance of the component, instead using a single centralized hook delegator. diff --git a/packages/components/src/animate/README.md b/packages/components/src/animate/README.md new file mode 100644 index 0000000000000..5ad580bac51e6 --- /dev/null +++ b/packages/components/src/animate/README.md @@ -0,0 +1,39 @@ +# Animate + +Simple interface to introduce animations to components. + +## Usage + +```jsx +import { Animate } from '@wordpress/components'; + +const MyAnimatedNotice = () => ( + <Animate todo="Add missing props"> + { ( { className } ) => ( + <Notice className={ className } status="success"> + <p>Animation finished.</p> + </Notice> + ) } + </Animate> +); +``` + +## Props + +Name | Type | Default | Description +--- | --- | --- | --- +`type` | `string` | `undefined` | Type of the animation to use. +`options` | `object` | `{}` | Options of the chosen animation. +`children` | `function` | `undefined` | A callback receiving a list of props ( `className` ) to apply to the DOM element to animate. + +## Available Animation Types + +### appear + +This animation is meant for popover/modal content, such as menus appearing. It shows the height and width of the animated element scaling from 0 to full size, from its point of origin. + +#### Options + +Name | Type | Default | Description +--- | --- | --- | --- +`origin` | `string` | `top center` | Point of origin (`top`, `bottom`,` middle right`, `left`, `center`). diff --git a/packages/components/src/animate/index.js b/packages/components/src/animate/index.js new file mode 100644 index 0000000000000..a144e6ee8b27d --- /dev/null +++ b/packages/components/src/animate/index.js @@ -0,0 +1,25 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +function Animate( { type, options = {}, children } ) { + if ( type === 'appear' ) { + const { origin = 'top' } = options; + const [ yAxis, xAxis = 'center' ] = origin.split( ' ' ); + + return children( { + className: classnames( + 'components-animate__appear', + { + [ 'is-from-' + xAxis ]: xAxis !== 'center', + [ 'is-from-' + yAxis ]: yAxis !== 'middle', + }, + ), + } ); + } + + return children( {} ); +} + +export default Animate; diff --git a/packages/components/src/animate/style.scss b/packages/components/src/animate/style.scss new file mode 100644 index 0000000000000..0a725ce0032d4 --- /dev/null +++ b/packages/components/src/animate/style.scss @@ -0,0 +1,28 @@ +.components-animate__appear { + animation: components-animate__appear-animation 0.1s cubic-bezier(0, 0, 0.2, 1) 0s; + animation-fill-mode: forwards; + + &.is-from-top, + &.is-from-top.is-from-left { + transform-origin: top left; + } + &.is-from-top.is-from-right { + transform-origin: top right; + } + &.is-from-bottom, + &.is-from-bottom.is-from-left { + transform-origin: bottom left; + } + &.is-from-bottom.is-from-right { + transform-origin: bottom right; + } +} + +@keyframes components-animate__appear-animation { + from { + transform: translateY(-2em) scaleY(0) scaleX(0); + } + to { + transform: translateY(0%) scaleY(1) scaleX(1); + } +} diff --git a/packages/components/src/index.js b/packages/components/src/index.js index f892867c7a400..2bc81c542f019 100644 --- a/packages/components/src/index.js +++ b/packages/components/src/index.js @@ -1,6 +1,7 @@ // Components export * from './primitives'; // eslint-disable-next-line camelcase +export { default as Animate } from './animate'; export { default as Autocomplete } from './autocomplete'; export { default as BaseControl } from './base-control'; export { default as Button } from './button'; diff --git a/packages/components/src/popover/index.js b/packages/components/src/popover/index.js index 643d032984181..052e53189ba84 100644 --- a/packages/components/src/popover/index.js +++ b/packages/components/src/popover/index.js @@ -22,6 +22,7 @@ import IconButton from '../icon-button'; import ScrollLock from '../scroll-lock'; import IsolatedEventContainer from '../isolated-event-container'; import { Slot, Fill, Consumer } from '../slot-fill'; +import Animate from '../animate'; const FocusManaged = withConstrainedTabbing( withFocusReturn( ( { children } ) => children ) ); @@ -55,6 +56,11 @@ class Popover extends Component { contentWidth: null, isMobile: false, popoverSize: null, + + // Delay the animation after the initial render + // because the animation have impact on the height of the popover + // causing the computed position to be wrong. + isReadyToAnimate: false, }; // Property used keep track of the previous anchor rect @@ -150,7 +156,7 @@ class Popover extends Component { popoverSize.height !== this.state.popoverSize.height ); if ( didPopoverSizeChange ) { - this.setState( { popoverSize } ); + this.setState( { popoverSize, isReadyToAnimate: true } ); } this.anchorRect = anchorRect; this.computePopoverPosition( popoverSize, anchorRect ); @@ -258,6 +264,7 @@ class Popover extends Component { focusOnMount, getAnchorRect, expandOnMobile, + animate = true, /* eslint-enable no-unused-vars */ ...contentProps } = this.props; @@ -270,8 +277,21 @@ class Popover extends Component { contentWidth, popoverSize, isMobile, + isReadyToAnimate, } = this.state; + // Compute the animation position + const yAxisMapping = { + top: 'bottom', + bottom: 'top', + }; + const xAxisMapping = { + left: 'right', + right: 'left', + }; + const animateYAxis = yAxisMapping[ yAxis ] || 'middle'; + const animateXAxis = xAxisMapping[ xAxis ] || 'center'; + const classes = classnames( 'components-popover', className, @@ -289,36 +309,43 @@ class Popover extends Component { /* eslint-disable jsx-a11y/no-static-element-interactions */ let content = ( <PopoverDetectOutside onClickOutside={ onClickOutside }> - <IsolatedEventContainer - className={ classes } - style={ { - top: ! isMobile && popoverTop ? popoverTop + 'px' : undefined, - left: ! isMobile && popoverLeft ? popoverLeft + 'px' : undefined, - visibility: popoverSize ? undefined : 'hidden', - } } - { ...contentProps } - onKeyDown={ this.maybeClose } + <Animate + type={ animate && isReadyToAnimate ? 'appear' : null } + options={ { origin: animateYAxis + ' ' + animateXAxis } } > - { isMobile && ( - <div className="components-popover__header"> - <span className="components-popover__header-title"> - { headerTitle } - </span> - <IconButton className="components-popover__close" icon="no-alt" onClick={ onClose } /> - </div> + { ( { className: animateClassName } ) => ( + <IsolatedEventContainer + className={ classnames( classes, animateClassName ) } + style={ { + top: ! isMobile && popoverTop ? popoverTop + 'px' : undefined, + left: ! isMobile && popoverLeft ? popoverLeft + 'px' : undefined, + visibility: popoverSize ? undefined : 'hidden', + } } + { ...contentProps } + onKeyDown={ this.maybeClose } + > + { isMobile && ( + <div className="components-popover__header"> + <span className="components-popover__header-title"> + { headerTitle } + </span> + <IconButton className="components-popover__close" icon="no-alt" onClick={ onClose } /> + </div> + ) } + <div + ref={ this.contentNode } + className="components-popover__content" + style={ { + maxHeight: ! isMobile && contentHeight ? contentHeight + 'px' : undefined, + maxWidth: ! isMobile && contentWidth ? contentWidth + 'px' : undefined, + } } + tabIndex="-1" + > + { children } + </div> + </IsolatedEventContainer> ) } - <div - ref={ this.contentNode } - className="components-popover__content" - style={ { - maxHeight: ! isMobile && contentHeight ? contentHeight + 'px' : undefined, - maxWidth: ! isMobile && contentWidth ? contentWidth + 'px' : undefined, - } } - tabIndex="-1" - > - { children } - </div> - </IsolatedEventContainer> + </Animate> </PopoverDetectOutside> ); /* eslint-enable jsx-a11y/no-static-element-interactions */ diff --git a/packages/components/src/popover/test/__snapshots__/index.js.snap b/packages/components/src/popover/test/__snapshots__/index.js.snap index ff10c3fe5ceb3..a9c4ed32102d3 100644 --- a/packages/components/src/popover/test/__snapshots__/index.js.snap +++ b/packages/components/src/popover/test/__snapshots__/index.js.snap @@ -7,7 +7,7 @@ exports[`Popover #render() should pass additional props to portaled element 1`] > <div> <div - class="components-popover is-bottom is-center" + class="components-popover is-bottom is-center components-animate__appear is-from-top" role="tooltip" style="" > @@ -30,7 +30,7 @@ exports[`Popover #render() should render content 1`] = ` > <div> <div - class="components-popover is-bottom is-center" + class="components-popover is-bottom is-center components-animate__appear is-from-top" style="" > <div diff --git a/packages/components/src/style.scss b/packages/components/src/style.scss index 046c7b6264efd..66d20f3427df8 100644 --- a/packages/components/src/style.scss +++ b/packages/components/src/style.scss @@ -1,3 +1,4 @@ +@import "./animate/style.scss"; @import "./autocomplete/style.scss"; @import "./base-control/style.scss"; @import "./button-group/style.scss"; diff --git a/packages/components/src/tooltip/index.js b/packages/components/src/tooltip/index.js index 3bb1f6c8b7ccb..f20bb647c3cb8 100644 --- a/packages/components/src/tooltip/index.js +++ b/packages/components/src/tooltip/index.js @@ -115,6 +115,7 @@ class Tooltip extends Component { position={ position } className="components-tooltip" aria-hidden="true" + animate={ false } > { text } <Shortcut className="components-tooltip__shortcut" shortcut={ shortcut } /> diff --git a/packages/e2e-test-utils/src/click-on-more-menu-item.js b/packages/e2e-test-utils/src/click-on-more-menu-item.js index 0465441ec6496..9484a434a8f06 100644 --- a/packages/e2e-test-utils/src/click-on-more-menu-item.js +++ b/packages/e2e-test-utils/src/click-on-more-menu-item.js @@ -3,6 +3,11 @@ */ import { first } from 'lodash'; +/** + * Internal dependencies + */ +import { waitForAnimation } from './wait-for-animation'; + /** * Clicks on More Menu item, searches for the button with the text provided and clicks it. * @@ -12,6 +17,7 @@ export async function clickOnMoreMenuItem( buttonLabel ) { await expect( page ).toClick( '.edit-post-more-menu [aria-label="Show more tools & options"]' ); + await waitForAnimation(); const moreMenuContainerSelector = '//*[contains(concat(" ", @class, " "), " edit-post-more-menu__content ")]'; let elementToClick = first( await page.$x( diff --git a/packages/e2e-test-utils/src/index.js b/packages/e2e-test-utils/src/index.js index 5534c981f46a9..58b388e38c361 100644 --- a/packages/e2e-test-utils/src/index.js +++ b/packages/e2e-test-utils/src/index.js @@ -40,6 +40,7 @@ export { toggleScreenOption } from './toggle-screen-option'; export { transformBlockTo } from './transform-block-to'; export { uninstallPlugin } from './uninstall-plugin'; export { visitAdminPage } from './visit-admin-page'; +export { waitForAnimation } from './wait-for-animation'; export { waitForWindowDimensions } from './wait-for-window-dimensions'; export * from './mocks'; diff --git a/packages/e2e-test-utils/src/search-for-block.js b/packages/e2e-test-utils/src/search-for-block.js index e96b92839221a..f4bd5cc92a802 100644 --- a/packages/e2e-test-utils/src/search-for-block.js +++ b/packages/e2e-test-utils/src/search-for-block.js @@ -1,3 +1,8 @@ +/** + * Internal dependencies + */ +import { waitForAnimation } from './wait-for-animation'; + /** * Search for block in the global inserter * @@ -5,6 +10,7 @@ */ export async function searchForBlock( searchTerm ) { await page.click( '.edit-post-header [aria-label="Add block"]' ); + await waitForAnimation(); // Waiting here is necessary because sometimes the inserter takes more time to // render than Puppeteer takes to complete the 'click' action await page.waitForSelector( '.editor-inserter__menu' ); diff --git a/packages/e2e-test-utils/src/switch-editor-mode-to.js b/packages/e2e-test-utils/src/switch-editor-mode-to.js index 6978b2ee81247..78225e18f422d 100644 --- a/packages/e2e-test-utils/src/switch-editor-mode-to.js +++ b/packages/e2e-test-utils/src/switch-editor-mode-to.js @@ -1,3 +1,8 @@ +/** + * Internal dependencies + */ +import { waitForAnimation } from './wait-for-animation'; + /** * Switches editor mode. * @@ -7,6 +12,7 @@ export async function switchEditorModeTo( mode ) { await page.click( '.edit-post-more-menu [aria-label="Show more tools & options"]' ); + await waitForAnimation(); const [ button ] = await page.$x( `//button[contains(text(), '${ mode } Editor')]` ); diff --git a/packages/e2e-test-utils/src/transform-block-to.js b/packages/e2e-test-utils/src/transform-block-to.js index cbf43b526479c..8fd2db2182521 100644 --- a/packages/e2e-test-utils/src/transform-block-to.js +++ b/packages/e2e-test-utils/src/transform-block-to.js @@ -1,3 +1,8 @@ +/** + * Internal dependencies + */ +import { waitForAnimation } from './wait-for-animation'; + /** * Converts editor's block type. * @@ -7,5 +12,7 @@ export async function transformBlockTo( name ) { await page.mouse.move( 200, 300, { steps: 10 } ); await page.mouse.move( 250, 350, { steps: 10 } ); await page.click( '.editor-block-switcher__toggle' ); + await waitForAnimation(); + await page.waitForSelector( `.editor-block-types-list__item[aria-label="${ name }"]` ); await page.click( `.editor-block-types-list__item[aria-label="${ name }"]` ); } diff --git a/packages/e2e-test-utils/src/wait-for-animation.js b/packages/e2e-test-utils/src/wait-for-animation.js new file mode 100644 index 0000000000000..63ff4499dff46 --- /dev/null +++ b/packages/e2e-test-utils/src/wait-for-animation.js @@ -0,0 +1,10 @@ +/** + * Waits for the small delay until the animation finishes. + * + * @param {number} delay Animation Delay. + * + * @return {Promise} Promise resolving after a small delay. + */ +export const waitForAnimation = async ( delay = 100 ) => { + return new Promise( ( resolve ) => setTimeout( resolve, delay ) ); +}; diff --git a/packages/e2e-tests/specs/block-deletion.test.js b/packages/e2e-tests/specs/block-deletion.test.js index b08eacd1f6d62..dfaa83dfe2668 100644 --- a/packages/e2e-tests/specs/block-deletion.test.js +++ b/packages/e2e-tests/specs/block-deletion.test.js @@ -6,6 +6,7 @@ import { getEditedPostContent, createNewPost, pressKeyWithModifier, + waitForAnimation, } from '@wordpress/e2e-test-utils'; const addThreeParagraphsToNewPost = async () => { @@ -21,6 +22,7 @@ const addThreeParagraphsToNewPost = async () => { const clickOnBlockSettingsMenuItem = async ( buttonLabel ) => { await expect( page ).toClick( '.editor-block-settings-menu__toggle' ); + await waitForAnimation(); const itemButton = ( await page.$x( `//*[contains(@class, "editor-block-settings-menu__popover")]//button[contains(text(), '${ buttonLabel }')]` ) )[ 0 ]; await itemButton.click(); }; diff --git a/packages/e2e-tests/specs/block-hierarchy-navigation.test.js b/packages/e2e-tests/specs/block-hierarchy-navigation.test.js index e93a3dfb21854..c22a572399f11 100644 --- a/packages/e2e-tests/specs/block-hierarchy-navigation.test.js +++ b/packages/e2e-tests/specs/block-hierarchy-navigation.test.js @@ -7,10 +7,12 @@ import { getEditedPostContent, pressKeyTimes, pressKeyWithModifier, + waitForAnimation, } from '@wordpress/e2e-test-utils'; async function openBlockNavigator() { - return pressKeyWithModifier( 'access', 'o' ); + await pressKeyWithModifier( 'access', 'o' ); + await waitForAnimation(); } describe( 'Navigating the block hierarchy', () => { @@ -26,6 +28,7 @@ describe( 'Navigating the block hierarchy', () => { // Navigate to the columns blocks. await page.click( '[aria-label="Block Navigation"]' ); + await waitForAnimation(); const columnsBlockMenuItem = ( await page.$x( "//button[contains(@class,'editor-block-navigation__item') and contains(text(), 'Columns')]" ) )[ 0 ]; await columnsBlockMenuItem.click(); diff --git a/packages/e2e-tests/specs/editor-modes.test.js b/packages/e2e-tests/specs/editor-modes.test.js index a8a3d86a31c56..21e746f648eb0 100644 --- a/packages/e2e-tests/specs/editor-modes.test.js +++ b/packages/e2e-tests/specs/editor-modes.test.js @@ -1,7 +1,12 @@ /** * WordPress dependencies */ -import { clickBlockAppender, createNewPost, switchEditorModeTo } from '@wordpress/e2e-test-utils'; +import { + clickBlockAppender, + createNewPost, + switchEditorModeTo, + waitForAnimation, +} from '@wordpress/e2e-test-utils'; describe( 'Editing modes (visual/HTML)', () => { beforeEach( async () => { @@ -21,6 +26,7 @@ describe( 'Editing modes (visual/HTML)', () => { // Change editing mode from "Visual" to "HTML". await page.waitForSelector( 'button[aria-label="More options"]' ); await page.click( 'button[aria-label="More options"]' ); + await waitForAnimation(); let changeModeButton = await page.waitForXPath( '//button[text()="Edit as HTML"]' ); await changeModeButton.click(); @@ -34,6 +40,7 @@ describe( 'Editing modes (visual/HTML)', () => { // Change editing mode from "HTML" back to "Visual". await page.waitForSelector( 'button[aria-label="More options"]' ); await page.click( 'button[aria-label="More options"]' ); + await waitForAnimation(); changeModeButton = await page.waitForXPath( '//button[text()="Edit visually"]' ); await changeModeButton.click(); @@ -49,6 +56,7 @@ describe( 'Editing modes (visual/HTML)', () => { // Change editing mode from "Visual" to "HTML". await page.waitForSelector( 'button[aria-label="More options"]' ); await page.click( 'button[aria-label="More options"]' ); + await waitForAnimation(); const changeModeButton = await page.waitForXPath( '//button[text()="Edit as HTML"]' ); await changeModeButton.click(); @@ -65,6 +73,7 @@ describe( 'Editing modes (visual/HTML)', () => { // Change editing mode from "Visual" to "HTML". await page.waitForSelector( 'button[aria-label="More options"]' ); await page.click( 'button[aria-label="More options"]' ); + await waitForAnimation(); const changeModeButton = await page.waitForXPath( '//button[text()="Edit as HTML"]' ); await changeModeButton.click(); diff --git a/packages/e2e-tests/specs/font-size-picker.test.js b/packages/e2e-tests/specs/font-size-picker.test.js index 648a6d4739833..28f9c39cd967c 100644 --- a/packages/e2e-tests/specs/font-size-picker.test.js +++ b/packages/e2e-tests/specs/font-size-picker.test.js @@ -6,6 +6,7 @@ import { getEditedPostContent, createNewPost, pressKeyTimes, + waitForAnimation, } from '@wordpress/e2e-test-utils'; describe( 'Font Size Picker', () => { @@ -17,8 +18,8 @@ describe( 'Font Size Picker', () => { // Create a paragraph block with some content. await clickBlockAppender(); await page.keyboard.type( 'Paragraph to be made "large"' ); - await page.click( '.components-font-size-picker__selector' ); + await waitForAnimation(); const changeSizeButton = await page.waitForSelector( '.components-button.is-font-large' ); await changeSizeButton.click(); diff --git a/packages/e2e-tests/specs/invalid-block.test.js b/packages/e2e-tests/specs/invalid-block.test.js index a068781ef42ac..c54ea208d8891 100644 --- a/packages/e2e-tests/specs/invalid-block.test.js +++ b/packages/e2e-tests/specs/invalid-block.test.js @@ -4,6 +4,7 @@ import { createNewPost, clickBlockAppender, + waitForAnimation, } from '@wordpress/e2e-test-utils'; describe( 'invalid blocks', () => { @@ -19,6 +20,7 @@ describe( 'invalid blocks', () => { // Click the 'more options' await page.mouse.move( 200, 300, { steps: 10 } ); await page.click( 'button[aria-label="More options"]' ); + await waitForAnimation(); // Change to HTML mode and close the options const changeModeButton = await page.waitForXPath( '//button[text()="Edit as HTML"]' ); diff --git a/packages/e2e-tests/specs/links.test.js b/packages/e2e-tests/specs/links.test.js index 85ba99badcf24..9b35e1aec4778 100644 --- a/packages/e2e-tests/specs/links.test.js +++ b/packages/e2e-tests/specs/links.test.js @@ -8,6 +8,7 @@ import { pressKeyWithModifier, pressKeyTimes, insertBlock, + waitForAnimation, } from '@wordpress/e2e-test-utils'; /** @@ -40,6 +41,7 @@ describe( 'Links', () => { // Click on the Link button await page.click( 'button[aria-label="Link"]' ); + await waitForAnimation(); // Wait for the URL field to auto-focus await waitForAutoFocus(); @@ -64,6 +66,7 @@ describe( 'Links', () => { // Press Cmd+K to insert a link await pressKeyWithModifier( 'primary', 'K' ); + await waitForAnimation(); // Wait for the URL field to auto-focus await waitForAutoFocus(); @@ -88,6 +91,7 @@ describe( 'Links', () => { // Press Cmd+K to insert a link await pressKeyWithModifier( 'primary', 'K' ); + await waitForAnimation(); // Wait for the URL field to auto-focus await waitForAutoFocus(); @@ -130,6 +134,7 @@ describe( 'Links', () => { // Click on the Link button await page.click( 'button[aria-label="Link"]' ); + await waitForAnimation(); // Wait for the URL field to auto-focus await waitForAutoFocus(); @@ -151,6 +156,7 @@ describe( 'Links', () => { // Click on the Link button await page.click( 'button[aria-label="Link"]' ); + await waitForAnimation(); // Wait for the URL field to auto-focus await waitForAutoFocus(); @@ -207,6 +213,7 @@ describe( 'Links', () => { await clickBlockAppender(); await page.keyboard.type( 'Text' ); await page.click( 'button[aria-label="Link"]' ); + await waitForAnimation(); // Typing "left" should not close the dialog await page.keyboard.press( 'ArrowLeft' ); @@ -229,6 +236,7 @@ describe( 'Links', () => { await moveMouse(); await page.waitForSelector( 'button[aria-label="Link"]' ); await page.click( 'button[aria-label="Link"]' ); + await waitForAnimation(); // Typing "left" should not close the dialog await page.keyboard.press( 'ArrowLeft' ); @@ -285,12 +293,14 @@ describe( 'Links', () => { await page.keyboard.type( 'This is Gutenberg' ); await pressKeyWithModifier( 'shiftAlt', 'ArrowLeft' ); await page.click( 'button[aria-label="Link"]' ); + await waitForAnimation(); // Wait for the URL field to auto-focus await waitForAutoFocus(); await page.keyboard.type( titleText ); await page.waitForSelector( '.editor-url-input__suggestion' ); + await waitForAnimation(); const autocompleteSuggestions = await page.$x( `//*[contains(@class, "editor-url-input__suggestion")]//button[contains(text(), '${ titleText }')]` ); // Expect there to be some autocomplete suggestions. @@ -327,12 +337,14 @@ describe( 'Links', () => { // Press Cmd+K to insert a link await pressKeyWithModifier( 'primary', 'K' ); + await waitForAnimation(); // Wait for the URL field to auto-focus await waitForAutoFocus(); await page.keyboard.type( titleText ); await page.waitForSelector( '.editor-url-input__suggestion' ); + await waitForAnimation(); const autocompleteSuggestions = await page.$x( `//*[contains(@class, "editor-url-input__suggestion")]//button[contains(text(), '${ titleText }')]` ); // Expect there to be some autocomplete suggestions. @@ -363,6 +375,7 @@ describe( 'Links', () => { // Press Cmd+K to insert a link await pressKeyWithModifier( 'primary', 'K' ); + await waitForAnimation(); // Wait for the URL field to auto-focus await waitForAutoFocus(); @@ -371,6 +384,7 @@ describe( 'Links', () => { // Trigger the autocomplete suggestion list and select the first suggestion. await page.keyboard.type( titleText ); await page.waitForSelector( '.editor-url-input__suggestion' ); + await waitForAnimation(); await page.keyboard.press( 'ArrowDown' ); // Expect the the escape key to dismiss the popover when the autocomplete suggestion list is open. @@ -379,6 +393,7 @@ describe( 'Links', () => { // Press Cmd+K to insert a link await pressKeyWithModifier( 'primary', 'K' ); + await waitForAnimation(); // Wait for the URL field to auto-focus await waitForAutoFocus(); @@ -412,6 +427,7 @@ describe( 'Links', () => { await page.keyboard.type( 'This is Gutenberg' ); await pressKeyWithModifier( 'shiftAlt', 'ArrowLeft' ); await pressKeyWithModifier( 'primary', 'K' ); + await waitForAnimation(); await waitForAutoFocus(); await page.keyboard.type( URL ); await page.keyboard.press( 'Enter' ); @@ -429,6 +445,7 @@ describe( 'Links', () => { // Press Cmd+K to edit the link and the url-input should become // focused with the value previously inserted. await pressKeyWithModifier( 'primary', 'K' ); + await waitForAnimation(); await waitForAutoFocus(); const activeElementParentClasses = await page.evaluate( () => Object.values( document.activeElement.parentElement.classList ) ); expect( activeElementParentClasses ).toContain( 'editor-url-input' ); @@ -441,6 +458,7 @@ describe( 'Links', () => { await page.keyboard.type( 'This is Gutenberg' ); await pressKeyWithModifier( 'shiftAlt', 'ArrowLeft' ); await pressKeyWithModifier( 'primary', 'K' ); + await waitForAnimation(); await waitForAutoFocus(); await page.keyboard.type( 'http://#test.com' ); await page.keyboard.press( 'Enter' ); @@ -464,6 +482,7 @@ describe( 'Links', () => { // Click on the Link button await page.click( 'button[aria-label="Link"]' ); + await waitForAnimation(); // Wait for the URL field to auto-focus await waitForAutoFocus(); @@ -491,6 +510,7 @@ describe( 'Links', () => { // Select "WordPress". await pressKeyWithModifier( 'shiftAlt', 'ArrowLeft' ); await pressKeyWithModifier( 'primary', 'k' ); + await waitForAnimation(); await waitForAutoFocus(); await page.keyboard.type( 'w.org' ); // Navigate to the settings toggle. diff --git a/packages/e2e-tests/specs/nux.test.js b/packages/e2e-tests/specs/nux.test.js index d8a64be089eca..4d141c9dcec8b 100644 --- a/packages/e2e-tests/specs/nux.test.js +++ b/packages/e2e-tests/specs/nux.test.js @@ -7,6 +7,7 @@ import { createNewPost, saveDraft, toggleScreenOption, + waitForAnimation, } from '@wordpress/e2e-test-utils'; describe( 'New User Experience (NUX)', () => { @@ -17,6 +18,7 @@ describe( 'New User Experience (NUX)', () => { for ( let i = 1; i < numberOfTips; i++ ) { await page.click( '.nux-dot-tip .components-button.is-link' ); + await waitForAnimation(); } return { numberOfTips, tips }; diff --git a/packages/e2e-tests/specs/plugins/annotations.test.js b/packages/e2e-tests/specs/plugins/annotations.test.js index 5fe99a352eec6..c07555c618380 100644 --- a/packages/e2e-tests/specs/plugins/annotations.test.js +++ b/packages/e2e-tests/specs/plugins/annotations.test.js @@ -6,10 +6,12 @@ import { clickOnMoreMenuItem, createNewPost, deactivatePlugin, + waitForAnimation, } from '@wordpress/e2e-test-utils'; const clickOnBlockSettingsMenuItem = async ( buttonLabel ) => { await expect( page ).toClick( '.editor-block-settings-menu__toggle' ); + await waitForAnimation(); const itemButton = ( await page.$x( `//*[contains(@class, "editor-block-settings-menu__popover")]//button[contains(text(), '${ buttonLabel }')]` ) )[ 0 ]; await itemButton.click(); }; diff --git a/packages/e2e-tests/specs/plugins/container-blocks.test.js b/packages/e2e-tests/specs/plugins/container-blocks.test.js index 2d32224bb3503..0e71003c9bcc9 100644 --- a/packages/e2e-tests/specs/plugins/container-blocks.test.js +++ b/packages/e2e-tests/specs/plugins/container-blocks.test.js @@ -8,6 +8,7 @@ import { getEditedPostContent, insertBlock, switchEditorModeTo, + waitForAnimation, } from '@wordpress/e2e-test-utils'; describe( 'InnerBlocks Template Sync', () => { @@ -79,6 +80,7 @@ describe( 'Container block without paragraph support', () => { // Open the specific appender used when there's no paragraph support. await page.click( '.editor-inner-blocks .block-list-appender .block-list-appender__toggle' ); + await waitForAnimation(); // Insert an image block. await page.click( '.editor-inserter__results button[aria-label="Image"]' ); diff --git a/packages/e2e-tests/specs/reusable-blocks.test.js b/packages/e2e-tests/specs/reusable-blocks.test.js index f1ef297f311ba..bd21960e6adce 100644 --- a/packages/e2e-tests/specs/reusable-blocks.test.js +++ b/packages/e2e-tests/specs/reusable-blocks.test.js @@ -7,6 +7,7 @@ import { pressKeyWithModifier, searchForBlock, getEditedPostContent, + waitForAnimation, } from '@wordpress/e2e-test-utils'; function waitForAndAcceptDialog() { @@ -41,6 +42,7 @@ describe( 'Reusable Blocks', () => { // Convert block to a reusable block await page.waitForSelector( 'button[aria-label="More options"]' ); await page.click( 'button[aria-label="More options"]' ); + await waitForAnimation(); const convertButton = await page.waitForXPath( '//button[text()="Add to Reusable Blocks"]' ); await convertButton.click(); @@ -88,6 +90,7 @@ describe( 'Reusable Blocks', () => { // Convert block to a reusable block await page.waitForSelector( 'button[aria-label="More options"]' ); await page.click( 'button[aria-label="More options"]' ); + await waitForAnimation(); const convertButton = await page.waitForXPath( '//button[text()="Add to Reusable Blocks"]' ); await convertButton.click(); @@ -165,6 +168,7 @@ describe( 'Reusable Blocks', () => { // Convert block to a regular block await page.click( 'button[aria-label="More options"]' ); + await waitForAnimation(); const convertButton = await page.waitForXPath( '//button[text()="Convert to Regular Block"]' ); @@ -188,6 +192,7 @@ describe( 'Reusable Blocks', () => { // Delete the block and accept the confirmation dialog await page.click( 'button[aria-label="More options"]' ); + await waitForAnimation(); const deleteButton = await page.waitForXPath( '//button[text()="Remove from Reusable Blocks"]' ); await Promise.all( [ waitForAndAcceptDialog(), deleteButton.click() ] ); @@ -229,6 +234,7 @@ describe( 'Reusable Blocks', () => { // Convert block to a reusable block await page.waitForSelector( 'button[aria-label="More options"]' ); await page.click( 'button[aria-label="More options"]' ); + await waitForAnimation(); const convertButton = await page.waitForXPath( '//button[text()="Add to Reusable Blocks"]' ); await convertButton.click(); @@ -270,6 +276,7 @@ describe( 'Reusable Blocks', () => { // Convert block to a regular block await page.click( 'button[aria-label="More options"]' ); + await waitForAnimation(); const convertButton = await page.waitForXPath( '//button[text()="Convert to Regular Block"]' ); From 1481251e9d5aaf6d104c37926f48a762c776536c Mon Sep 17 00:00:00 2001 From: Alex Kirk <akirk@users.noreply.github.com> Date: Thu, 7 Feb 2019 17:44:23 +0100 Subject: [PATCH 371/691] Babel Makepot: Translator comments have been extracted from the file (#9440) * Comments have been extracted from the file * Update POT comment behavior in PHP file generation * Convert function names * Babel Plugin Makepot: Add CHANGELOG entry for translator -> extracted --- packages/babel-plugin-makepot/CHANGELOG.md | 1 + packages/babel-plugin-makepot/src/index.js | 14 +++++++------- packages/babel-plugin-makepot/test/index.js | 6 +++--- packages/i18n/tools/pot-to-php.js | 10 +++++----- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/packages/babel-plugin-makepot/CHANGELOG.md b/packages/babel-plugin-makepot/CHANGELOG.md index 8079f5197a2fd..e48eeff4bca3e 100644 --- a/packages/babel-plugin-makepot/CHANGELOG.md +++ b/packages/babel-plugin-makepot/CHANGELOG.md @@ -3,6 +3,7 @@ ### Bug Fix - Fixed Babel plugin for POT file generation to properly handle plural numbers specified in the passed header. ([#13577](https://github.com/WordPress/gutenberg/pull/13577)) +- Fix extracted translator comments to be written as prefixed by `#.` ([#9440](https://github.com/WordPress/gutenberg/pull/9440)) ## 2.1.0 (2018-09-05) diff --git a/packages/babel-plugin-makepot/src/index.js b/packages/babel-plugin-makepot/src/index.js index cf66b8bbc80f3..ad0cde941aecb 100644 --- a/packages/babel-plugin-makepot/src/index.js +++ b/packages/babel-plugin-makepot/src/index.js @@ -107,15 +107,15 @@ function getNodeAsString( node ) { } /** - * Returns translator comment for a given AST traversal path if one exists. + * Returns the extracted comment for a given AST traversal path if one exists. * * @param {Object} path Traversal path. * @param {number} _originalNodeLine Private: In recursion, line number of * the original node passed. * - * @return {?string} Translator comment. + * @return {?string} Extracted comment. */ -function getTranslatorComment( path, _originalNodeLine ) { +function getExtractedComment( path, _originalNodeLine ) { const { node, parent, parentPath } = path; // Assign original node line so we can keep track in recursion whether a @@ -152,7 +152,7 @@ function getTranslatorComment( path, _originalNodeLine ) { // Only recurse as long as parent node is on the same or previous line const { line } = parent.loc.start; if ( line >= _originalNodeLine - 1 && line <= _originalNodeLine ) { - return getTranslatorComment( parentPath, _originalNodeLine ); + return getExtractedComment( parentPath, _originalNodeLine ); } } @@ -266,9 +266,9 @@ module.exports = function() { }; // If exists, also assign translator comment - const translator = getTranslatorComment( path ); + const translator = getExtractedComment( path ); if ( translator ) { - translation.comments.translator = translator; + translation.comments.extracted = translator; } // Create context grouping for translation if not yet exists @@ -340,6 +340,6 @@ module.exports = function() { }; module.exports.getNodeAsString = getNodeAsString; -module.exports.getTranslatorComment = getTranslatorComment; +module.exports.getExtractedComment = getExtractedComment; module.exports.isValidTranslationKey = isValidTranslationKey; module.exports.isSameTranslation = isSameTranslation; diff --git a/packages/babel-plugin-makepot/test/index.js b/packages/babel-plugin-makepot/test/index.js index 17af021380b9e..52ae1dcc5b913 100644 --- a/packages/babel-plugin-makepot/test/index.js +++ b/packages/babel-plugin-makepot/test/index.js @@ -12,7 +12,7 @@ import babelPlugin from '../src'; describe( 'babel-plugin', () => { const { getNodeAsString, - getTranslatorComment, + getExtractedComment, isValidTranslationKey, isSameTranslation, } = babelPlugin; @@ -43,12 +43,12 @@ describe( 'babel-plugin', () => { } ); } ); - describe( '.getTranslatorComment()', () => { + describe( '.getExtractedComment()', () => { function getCommentFromString( string ) { let comment; traverse( transformSync( string, { ast: true } ).ast, { CallExpression( path ) { - comment = getTranslatorComment( path ); + comment = getExtractedComment( path ); }, } ); diff --git a/packages/i18n/tools/pot-to-php.js b/packages/i18n/tools/pot-to-php.js index 0e5a4f9c68c6f..29aafe3abcf59 100755 --- a/packages/i18n/tools/pot-to-php.js +++ b/packages/i18n/tools/pot-to-php.js @@ -53,17 +53,17 @@ function convertTranslationToPHP( translation, textdomain, context = '' ) { NEWLINE; } - if ( ! isEmpty( comments.extracted ) ) { + if ( ! isEmpty( comments.translator ) ) { // All extracted comments are split by newlines, add a tab to line them up nicely. - const extracted = comments.extracted + const translator = comments.translator .split( NEWLINE ) .join( NEWLINE + TAB + ' ' ); - php += TAB + `/* ${ extracted } */${ NEWLINE }`; + php += TAB + `/* ${ translator } */${ NEWLINE }`; } - if ( ! isEmpty( comments.translator ) ) { - php += TAB + `/* translators: ${ comments.translator } */${ NEWLINE }`; + if ( ! isEmpty( comments.extracted ) ) { + php += TAB + `/* translators: ${ comments.extracted } */${ NEWLINE }`; } } From c9357bf26132fb1b715f35f6ecb0ecb8a73d054d Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Thu, 7 Feb 2019 09:49:42 -0800 Subject: [PATCH 372/691] Add JavaScript Documentation around setting up Webpack, Babel, and Building (#13629) * Add JS Build Setup * Update docs/designers-developers/developers/tutorials/javascript/js-build-setup.md Co-Authored-By: mkaz <marcus@mkaz.com> * Whitespace and code brackets * Update with better npm install instructions using --save-dev * Update docs/designers-developers/developers/tutorials/javascript/versions-and-building.md Co-Authored-By: mkaz <marcus@mkaz.com> * Update docs/designers-developers/developers/tutorials/javascript/js-build-setup.md Co-Authored-By: mkaz <marcus@mkaz.com> * Updates node, cli, and pragma per review * Update why to use ESNext * Apply suggestions from code review Co-Authored-By: mkaz <marcus@mkaz.com> * Apply suggestions from code review Co-Authored-By: mkaz <marcus@mkaz.com> --- .../tutorials/javascript/js-build-setup.md | 239 ++++++++++++++++++ .../javascript/versions-and-building.md | 5 +- docs/manifest.json | 90 ++++--- docs/toc.json | 17 +- 4 files changed, 299 insertions(+), 52 deletions(-) create mode 100644 docs/designers-developers/developers/tutorials/javascript/js-build-setup.md diff --git a/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md b/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md new file mode 100644 index 0000000000000..916e154de0763 --- /dev/null +++ b/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md @@ -0,0 +1,239 @@ +# JavaScript Build Setup + +This page covers how to set up your development environment to use the ESNext syntax. ESNext is JavaScript code written using features that are only available in a specification greater than ECMAScript 5 (ES5 for short) or that includes a custom syntax such as [JSX](https://reactjs.org/docs/introducing-jsx.html). + +This documentation covers development for your plugin to work with Gutenberg. If you want to setup a development environment for developing Gutenberg itself, see the [CONTRIBUTING.md](https://github.com/WordPress/gutenberg/blob/master/CONTRIBUTING.md) documentation. + +Most browsers can not interpret or run ESNext and JSX syntaxes, so we use a transformation step to convert these syntaxes to code that browsers can understand. + +There are a few reasons to use ESNext and the extra step. First, it makes for simpler code that is easier to read and write. Using a transformation step allows for tools to optimize the code to work on the widest variety of browsers. Also, by using a build step you can organize your code into smaller modules and files that can be bundled together into a single download. + +There are many tools that can perform this transformation or build step, but in this tutorial we will focus on Webpack and Babel. + +[Webpack](https://webpack.js.org/) is a pluggable tool that processes JavaScript creating a compiled bundle that runs in a browser. [Babel](https://babeljs.io/) transforms JavaScript from one format to another. You use Babel as a plugin to Webpack to transform both ESNext and JSX to JavaScript. + +## Quick Start + +For a quick start, you can use one of the examples from the [Gutenberg Examples repository](https://github.com/wordpress/gutenberg-examples/). Each one of the `-esnext` directories contain the necessary files for working with ESNext and JSX. + +## Setup + +Both Webpack and Babel are tools written in JavaScript and run using [Node.js](https://nodejs.org/) (node). Node.js is a runtime environment for JavaScript outside of a browser. Simply put, node allows you to run JavaScript code on the command-line. + +First, you need to set up node for your development environment. The steps required change depending on your operating system, but if you have a package manager installed, setup can be as straightforward as: + +- Ubuntu: `apt install node` +- macOS: `brew install node` +- Windows: `choco install node` + +Additionally, the [Node.js download page](https://nodejs.org/en/download/) includes installers and binaries. + +**Note:** The build tools and process occur on the command-line, so some basic familiarity using a terminal application is required. Some text editors have a terminal built-in which is fine to use; Visual Studio Code and PhpStorm are two popular options. + +### Node Package Manager (npm) + +The Node Package Manager (npm) is a tool included with node. npm allows you to install and manage JavaScript packages. npm can also generate and process a special file called `package.json`, which contains some information about your project and the packages your project uses. + +To start a new node project, first create a directory to work in. + +``` +mkdir myguten-block +``` + +You create a new package.json running `npm init` in your terminal. This will walk you through creating your package.json file: + +``` +npm init + +package name: (myguten-block) myguten-block +version: (1.0.0) +description: Test block +entry point: (index.js) block.js +test command: +git repository: +keywords: +author: mkaz +license: (ISC) GPL-2.0-only +About to write to /home/mkaz/src/wp/scratch/package.json: + +{ + "name": "myguten-block", + "version": "1.0.0", + "description": "Test block", + "main": "block.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "mkaz", + "license": "GPL-2.0-only" +} + + +Is this OK? (yes) yes +``` + +### Using npm to install packages + +The next step is to install the packages required. You can install packages using the npm command `npm install`. If you pass the `--save-dev` parameter, npm will write the package as a dev dependency in the package.json file. + +Run `npm install --save-dev webpack` + +After installing, a `node_modules` directory is created with the webpack module and its dependencies. + +Also, if you look at package.json file it will include a new section: + +```json +"dependencies": { + "webpack": "^4.29.0" +} +``` + +## Webpack & Babel + +Next, we will configure webpack to process the `block.js` file and run babel to transform the JSX within it. + +Create the file `webpack.config.js` + +```js +// sets mode webpack runs under +const NODE_ENV = process.env.NODE_ENV || 'development'; + +module.exports = { + mode: NODE_ENV, + + // entry is the source script + entry: './block.js', + + // output is where to write the built file + output: { + path: __dirname, + filename: 'block.build.js', + }, + module: { + // the list of rules used to process files + // this looks for .js files, exclude files + // in node_modules directory, and uses the + // babel-loader to process + rules: [ + { + test: /.js$/, + exclude: /node_modules/, + loader: 'babel-loader', + }, + ], + }, +}; +``` + +Next, you need to install babel, the webpack loader, and the JSX plugin using: + +``` +npm install --save babel-loader babel-core babel-plugin-transform-react-jsx +``` + +You configure babel by creating a `.babelrc` file: + +``` +{ + "plugins": [ + [ "transform-react-jsx", { + "pragma": "wp.element.createElement" + } ] + ] +} +``` + +This pragma setting instructs Babel that any JSX syntax such as `<Hello />` should be transformed into `wp.element.createElement( Hello )`. The name of the setting (`transform-react-jsx`) is derived from the fact that it overrides the default assumption to transform to `React.createElement( Hello )`. + +With both configs in place, you can now run webpack. + +First you need a basic block.js to build. Create `block.js` with the following content: + +```js +const { registerBlockType } = wp.blocks; + +registerBlockType( 'myguten/test-block', { + title: 'Basic Example', + icon: 'smiley', + category: 'layout', + edit() { + return <div>Hola, mundo!</div>; + }, + save() { + return <div>Hola, mundo!</div>; + }, +} ); +``` + +To configure npm to run a script, you use the scripts section in `package.json` webpack: + +```json + "scripts": { + "build": "webpack" + }, +``` + +You can then run the build using: `npm run build`. + +After the build finishes, you will see the built file created at `block.build.js`. + +## Finishing Touches + +### Development Mode + +The basics are in place to build. You might have noticed the webpack.config.js sets a default mode of "development". Webpack can also run in a "production" mode, which shrinks the code down so it downloads faster, but makes it difficult to read. + +The mode is setup so it can be configured using environment variables, which can be added in the scripts section of `package.json`. + +```json + "scripts": { + "dev": "webpack --watch", + "build": "cross-env NODE_ENV=production webpack" + }, +``` + +This sets the environment variables, but different environments handle these settings in different ways. Using the `cross-env` helper module can help to handle this. Be sure to install the `cross-env` package using `npm install --save cross-env`. + +Additionally, webpack has a `--watch` flag that will keep the process running, watching for any changes to the `block.js` file and re-building as changes occur. This is useful during development, when you might have a lot of changes in progress. + +You can start the watcher by running `npm run dev` in a terminal. You can then edit away in your text editor; after each save, webpack will automatically build. You can then use the familiar edit/save/reload development process. + +**Note:** keep an eye on your terminal for any errors. If you make a typo or syntax error, the build will fail and the error will be in the terminal. + +### Babel Browser Targeting + +Babel has the ability to build JavaScript using rules that target certain browsers and versions. By setting a reasonable set of modern browsers, Babel can optimize the JavaScript it generates. + +WordPress has a preset default you can use to target the minimum supported browsers by WordPress. + +Install the module using: `npm install --save @wordpress/babel-preset-default` + +You then update `.babelrc` by adding a "presets" section: + +``` +{ + "presets": [ "@wordpress/babel-preset-default" ], + "plugins": [ + [ "transform-react-jsx", { + "pragma": "wp.element.createElement" + } ] + ] +} +``` + +### Source Control + +Because a typical `node_modules` folder will contain thousands of files that change with every software update, you should exclude `node_modules/` from your source control. If you ever start from a fresh clone, simply run `npm install` in the same folder your `package.json` is located to pull your required packages. + +Likewise, you do not need to include `node_modules` or any of the above configuration files in your plugin because they will be bundled inside the file that webpack builds. **Be sure to enqueue the `block.build.js` file** in your plugin PHP. This is the only JavaScript file needed for your block to run. + +## Summary + +Yes, the initial setup is rather tedious, and there are a number of different tools and configs to learn. However, as the quick start alluded to, copying an existing config is the typical way most people start. + +With a setup in place, the standard workflow is: + +- Install dependencies: `npm install` +- Start development builds: `npm run dev` +- Develop. Test. Repeat. +- Create production build: `npm run build` diff --git a/docs/designers-developers/developers/tutorials/javascript/versions-and-building.md b/docs/designers-developers/developers/tutorials/javascript/versions-and-building.md index fb52d247d7861..416afe6254934 100644 --- a/docs/designers-developers/developers/tutorials/javascript/versions-and-building.md +++ b/docs/designers-developers/developers/tutorials/javascript/versions-and-building.md @@ -6,6 +6,7 @@ ES5 code is compatible with WordPress's minimum [target for browser support](htt "ESNext" doesn't refer to a specific version of JavaScript, but is "dynamic" and refers to the next language definitions, whatever they might be. Because some browsers won't support these features yet (because they're new or proposed), an extra build step is required to transform the code to a syntax that works in all browsers. Webpack and babel are the tools that perform this transformation step. -Additionally, the ESNext code examples in the Gutenberg handbook include [JSX syntax](https://reactjs.org/docs/introducing-jsx.html), a syntax that blends HTML and JavaScript. It makes it easier to read and write markup code, but likewise requires the build step using webpack and babel to transform into compatible code. +Additionally, the ESNext code examples in the Gutenberg handbook include [JSX syntax](https://reactjs.org/docs/introducing-jsx.html), a syntax that blends HTML and JavaScript. It makes it easier to read and write markup code, but likewise requires the build step using Webpack and Babel to transform into compatible code. + +For simplicity, the JavaScript tutorial uses the ES5 definition, without JSX. This code can run straight in your browser and does not require an additional build step. In many cases, it will be perfectly fine to follow the same approach to quickly start experimenting with your plugin or theme. As soon as your codebase grows to hundreds of lines it might be a good idea to get familiar with the [JavaScript Build Setup documentation](/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md) for setting up a development environment to use ESNext syntax. -For simplicity, this tutorial uses the ES5 definition of JavaScript, without JSX. This code can run straight in your browser and does not require an additional build step. diff --git a/docs/manifest.json b/docs/manifest.json index 4e8380c93aaf7..675c7034e52a1 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -161,48 +161,6 @@ "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/readme.md", "parent": "developers" }, - { - "title": "Blocks", - "slug": "block-tutorial", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/readme.md", - "parent": "tutorials" - }, - { - "title": "Writing Your First Block Type", - "slug": "writing-your-first-block-type", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md", - "parent": "block-tutorial" - }, - { - "title": "Applying Styles From a Stylesheet", - "slug": "applying-styles-with-stylesheets", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md", - "parent": "block-tutorial" - }, - { - "title": "Introducing Attributes and Editable Fields", - "slug": "introducing-attributes-and-editable-fields", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md", - "parent": "block-tutorial" - }, - { - "title": "Block Controls: Toolbars and Inspector", - "slug": "block-controls-toolbars-and-inspector", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbars-and-inspector.md", - "parent": "block-tutorial" - }, - { - "title": "Creating dynamic blocks", - "slug": "creating-dynamic-blocks", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md", - "parent": "block-tutorial" - }, - { - "title": "Generate Blocks with WP-CLI", - "slug": "generate-blocks-with-wp-cli", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/generate-blocks-with-wp-cli.md", - "parent": "block-tutorial" - }, { "title": "Getting Started with JavaScript", "slug": "javascript", @@ -245,6 +203,54 @@ "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/scope-your-code.md", "parent": "javascript" }, + { + "title": "JavaScript Build Setup", + "slug": "js-build-setup", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md", + "parent": "javascript" + }, + { + "title": "Blocks", + "slug": "block-tutorial", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/readme.md", + "parent": "tutorials" + }, + { + "title": "Writing Your First Block Type", + "slug": "writing-your-first-block-type", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md", + "parent": "block-tutorial" + }, + { + "title": "Applying Styles From a Stylesheet", + "slug": "applying-styles-with-stylesheets", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md", + "parent": "block-tutorial" + }, + { + "title": "Introducing Attributes and Editable Fields", + "slug": "introducing-attributes-and-editable-fields", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md", + "parent": "block-tutorial" + }, + { + "title": "Block Controls: Toolbars and Inspector", + "slug": "block-controls-toolbars-and-inspector", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbars-and-inspector.md", + "parent": "block-tutorial" + }, + { + "title": "Creating dynamic blocks", + "slug": "creating-dynamic-blocks", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md", + "parent": "block-tutorial" + }, + { + "title": "Generate Blocks with WP-CLI", + "slug": "generate-blocks-with-wp-cli", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/generate-blocks-with-wp-cli.md", + "parent": "block-tutorial" + }, { "title": "Meta Boxes", "slug": "metabox", diff --git a/docs/toc.json b/docs/toc.json index cf299daf60c62..d3e7b6048a130 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -30,6 +30,15 @@ {"docs/designers-developers/developers/backward-compatibility/meta-box.md": []} ]}, {"docs/designers-developers/developers/tutorials/readme.md": [ + {"docs/designers-developers/developers/tutorials/javascript/readme.md": [ + {"docs/designers-developers/developers/tutorials/javascript/plugins-background.md": []}, + {"docs/designers-developers/developers/tutorials/javascript/loading-javascript.md": []}, + {"docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md": []}, + {"docs/designers-developers/developers/tutorials/javascript/troubleshooting.md": []}, + {"docs/designers-developers/developers/tutorials/javascript/versions-and-building.md": []}, + {"docs/designers-developers/developers/tutorials/javascript/scope-your-code.md": []}, + {"docs/designers-developers/developers/tutorials/javascript/js-build-setup.md": []} + ]}, {"docs/designers-developers/developers/tutorials/block-tutorial/readme.md" :[ {"docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md" :[]}, {"docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md" :[]}, @@ -38,14 +47,6 @@ {"docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md" :[]}, {"docs/designers-developers/developers/tutorials/block-tutorial/generate-blocks-with-wp-cli.md" :[]} ]}, - {"docs/designers-developers/developers/tutorials/javascript/readme.md": [ - {"docs/designers-developers/developers/tutorials/javascript/plugins-background.md": []}, - {"docs/designers-developers/developers/tutorials/javascript/loading-javascript.md": []}, - {"docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md": []}, - {"docs/designers-developers/developers/tutorials/javascript/troubleshooting.md": []}, - {"docs/designers-developers/developers/tutorials/javascript/versions-and-building.md": []}, - {"docs/designers-developers/developers/tutorials/javascript/scope-your-code.md": []} - ]}, {"docs/designers-developers/developers/tutorials/metabox/readme.md": [ {"docs/designers-developers/developers/tutorials/metabox/meta-block-1-intro.md": []}, {"docs/designers-developers/developers/tutorials/metabox/meta-block-2-register-meta.md": []}, From 23a72978bbd1c856f58e2e09d359b44b8f8c1f7a Mon Sep 17 00:00:00 2001 From: etoledom <etoledom@icloud.com> Date: Thu, 7 Feb 2019 18:51:47 +0100 Subject: [PATCH 373/691] [Mobile] UI for size image settings (#13728) * Mobile: Importing SelectControl as native picker for iOS and Android * Adding iOS version of SelectControl as UIActionSheet. * Mobile: select-cell name change * Adding Android selector control based on modal. * Fix lint issues * Fix lint issues * Updated Android selector to show as BottomSheet * Moving SelectControl to components/mobile as `Picker` * Fix lint issues * Remove mobile `modal` component import * Updated SelectCell to PickerCell * Fix lint issues * Renaming styles.scss to styles.native.scss --- .../block-library/src/image/edit.native.js | 45 +++++++++++++- .../mobile/bottom-sheet/cell.native.js | 39 +++++++----- .../mobile/bottom-sheet/index.native.js | 3 + .../mobile/bottom-sheet/picker-cell.native.js | 33 ++++++++++ .../{styles.scss => styles.native.scss} | 1 - .../components/mobile/picker/index.android.js | 62 +++++++++++++++++++ .../src/components/mobile/picker/index.ios.js | 37 +++++++++++ .../mobile/picker/styles.android.scss | 10 +++ 8 files changed, 214 insertions(+), 16 deletions(-) create mode 100644 packages/editor/src/components/mobile/bottom-sheet/picker-cell.native.js rename packages/editor/src/components/mobile/bottom-sheet/{styles.scss => styles.native.scss} (98%) create mode 100644 packages/editor/src/components/mobile/picker/index.android.js create mode 100644 packages/editor/src/components/mobile/picker/index.ios.js create mode 100644 packages/editor/src/components/mobile/picker/styles.android.scss diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index 66b35ea5ef726..822723ce141f6 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -12,6 +12,10 @@ import { requestImageFailedRetryDialog, requestImageUploadCancelDialog, } from 'react-native-gutenberg-bridge'; +import { + map, + compact, +} from 'lodash'; /** * Internal dependencies @@ -22,6 +26,8 @@ import { __ } from '@wordpress/i18n'; import ImageSize from './image-size'; import { isURL } from '@wordpress/url'; import styles from './styles.scss'; +import { compose } from '@wordpress/compose'; +import { withSelect } from '@wordpress/data'; const MEDIA_UPLOAD_STATE_UPLOADING = 1; const MEDIA_UPLOAD_STATE_SUCCEEDED = 2; @@ -30,7 +36,7 @@ const MEDIA_UPLOAD_STATE_RESET = 4; const LINK_DESTINATION_CUSTOM = 'custom'; -export default class ImageEdit extends React.Component { +class ImageEdit extends React.Component { constructor( props ) { super( props ); @@ -47,6 +53,7 @@ export default class ImageEdit extends React.Component { this.finishMediaUploadWithSuccess = this.finishMediaUploadWithSuccess.bind( this ); this.finishMediaUploadWithFailure = this.finishMediaUploadWithFailure.bind( this ); this.updateAlt = this.updateAlt.bind( this ); + this.updateImageURL = this.updateImageURL.bind( this ); this.onSetLinkDestination = this.onSetLinkDestination.bind( this ); this.onImagePressed = this.onImagePressed.bind( this ); } @@ -136,6 +143,10 @@ export default class ImageEdit extends React.Component { this.props.setAttributes( { alt: newAlt } ); } + updateImageURL( url ) { + this.props.setAttributes( { url, width: undefined, height: undefined } ); + } + onSetLinkDestination( href ) { this.props.setAttributes( { linkDestination: LINK_DESTINATION_CUSTOM, @@ -143,6 +154,16 @@ export default class ImageEdit extends React.Component { } ); } + getImageSizeOptions() { + const { imageSizes } = this.props; + return compact( map( imageSizes, ( { label, slug } ) => { + return { + value: this.props.attributes.url + slug, //temporary url + label, + }; + } ) ); + } + render() { const { attributes, isSelected, setAttributes } = this.props; const { url, caption, height, width, alt, href } = attributes; @@ -201,6 +222,8 @@ export default class ImageEdit extends React.Component { </Toolbar> ); + const sizeOptions = this.getImageSizeOptions(); + const getInspectorControls = () => ( <BottomSheet isVisible={ this.state.showSettings } @@ -216,6 +239,13 @@ export default class ImageEdit extends React.Component { autoCapitalize="none" autoCorrect={ false } /> + <BottomSheet.PickerCell + icon="editor-expand" + label={ __( 'Image Size' ) } + value={ 'Large' } // Temporary for UI implementation. + options={ sizeOptions } + onChangeValue={ () => {} } // Temporary for UI implementation. + /> <BottomSheet.Cell icon={ 'editor-textcolor' } label={ __( 'Alt Text' ) } @@ -304,3 +334,16 @@ export default class ImageEdit extends React.Component { ); } } + +export default compose( [ + withSelect( ( select ) => { + const { getEditorSettings } = select( 'core/editor' ); + const { maxWidth, isRTL, imageSizes } = getEditorSettings(); + + return { + maxWidth, + isRTL, + imageSizes, + }; + } ), +] )( ImageEdit ); diff --git a/packages/editor/src/components/mobile/bottom-sheet/cell.native.js b/packages/editor/src/components/mobile/bottom-sheet/cell.native.js index 1a24d12952256..89970e69b4598 100644 --- a/packages/editor/src/components/mobile/bottom-sheet/cell.native.js +++ b/packages/editor/src/components/mobile/bottom-sheet/cell.native.js @@ -24,11 +24,13 @@ export default function Cell( props ) { labelStyle = {}, valueStyle = {}, onChangeValue, + children, + editable = true, ...valueProps } = props; const showValue = value !== undefined; - const isValueEditable = onChangeValue !== undefined; + const isValueEditable = editable && onChangeValue !== undefined; const defaultLabelStyle = showValue ? styles.cellLabel : styles.cellLabelCentered; const separatorStyle = showValue ? styles.cellSeparator : styles.separator; let valueTextInput; @@ -41,6 +43,26 @@ export default function Cell( props ) { } }; + const getValueComponent = () => { + return isValueEditable ? ( + <TextInput + ref={ ( c ) => valueTextInput = c } + numberOfLines={ 1 } + style={ { ...styles.cellValue, ...valueStyle } } + value={ value } + placeholder={ valuePlaceholder } + placeholderTextColor={ '#87a6bc' } + onChangeText={ onChangeValue } + editable={ isValueEditable } + { ...valueProps } + /> + ) : ( + <Text style={ { ...styles.cellValue, ...valueStyle } }> + { value } + </Text> + ); + }; + return ( <TouchableOpacity onPress={ onCellPress } > <View style={ styles.cellContainer }> @@ -55,19 +77,8 @@ export default function Cell( props ) { { label } </Text> </View> - { showValue && ( - <TextInput - ref={ ( c ) => valueTextInput = c } - numberOfLines={ 1 } - style={ { ...styles.cellValue, ...valueStyle } } - value={ value } - placeholder={ valuePlaceholder } - placeholderTextColor={ '#87a6bc' } - onChangeText={ onChangeValue } - editable={ isValueEditable } - { ...valueProps } - /> - ) } + { showValue && getValueComponent() } + { children } </View> { drawSeparator && ( <View style={ separatorStyle } /> diff --git a/packages/editor/src/components/mobile/bottom-sheet/index.native.js b/packages/editor/src/components/mobile/bottom-sheet/index.native.js index d4754a3909dfa..d05eb73e1c925 100644 --- a/packages/editor/src/components/mobile/bottom-sheet/index.native.js +++ b/packages/editor/src/components/mobile/bottom-sheet/index.native.js @@ -16,6 +16,7 @@ import { Component } from '@wordpress/element'; import styles from './styles.scss'; import Button from './button'; import Cell from './cell'; +import PickerCell from './picker-cell'; class BottomSheet extends Component { constructor() { @@ -54,6 +55,7 @@ class BottomSheet extends Component { animationOutTiming={ 500 } backdropTransitionInTiming={ 500 } backdropTransitionOutTiming={ 500 } + backdropOpacity={ 0.2 } onBackdropPress={ this.props.onClose } onSwipe={ this.props.onClose } swipeDirection="down" @@ -94,5 +96,6 @@ class BottomSheet extends Component { BottomSheet.Button = Button; BottomSheet.Cell = Cell; +BottomSheet.PickerCell = PickerCell; export default BottomSheet; diff --git a/packages/editor/src/components/mobile/bottom-sheet/picker-cell.native.js b/packages/editor/src/components/mobile/bottom-sheet/picker-cell.native.js new file mode 100644 index 0000000000000..888acd3db54b2 --- /dev/null +++ b/packages/editor/src/components/mobile/bottom-sheet/picker-cell.native.js @@ -0,0 +1,33 @@ +/** + * Internal dependencies + */ +import Cell from './cell'; +import Picker from '../picker'; + +export default function PickerCell( props ) { + const { + options, + onChangeValue, + ...cellProps + } = props; + + let picker; + + const onCellPress = () => { + picker.presentPicker(); + }; + + const onChange = ( newValue ) => { + onChangeValue( newValue ); + }; + + return ( + <Cell onPress={ onCellPress } editable={ false } { ...cellProps } > + <Picker + ref={ ( instance ) => picker = instance } + options={ options } + onChange={ onChange } + /> + </Cell> + ); +} diff --git a/packages/editor/src/components/mobile/bottom-sheet/styles.scss b/packages/editor/src/components/mobile/bottom-sheet/styles.native.scss similarity index 98% rename from packages/editor/src/components/mobile/bottom-sheet/styles.scss rename to packages/editor/src/components/mobile/bottom-sheet/styles.native.scss index 139c79d88de56..3081519dbb08d 100644 --- a/packages/editor/src/components/mobile/bottom-sheet/styles.scss +++ b/packages/editor/src/components/mobile/bottom-sheet/styles.native.scss @@ -17,7 +17,6 @@ background-color: $light-gray-400; height: 1px; width: 100%; - margin-bottom: 14px; } .content { diff --git a/packages/editor/src/components/mobile/picker/index.android.js b/packages/editor/src/components/mobile/picker/index.android.js new file mode 100644 index 0000000000000..8dd1d909b1281 --- /dev/null +++ b/packages/editor/src/components/mobile/picker/index.android.js @@ -0,0 +1,62 @@ +/** + * External dependencies + */ +import React from 'react'; +import { View } from 'react-native'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Component } from '@wordpress/element'; +import { BottomSheet } from '@wordpress/editor'; + +export default class Picker extends Component { + constructor() { + super( ...arguments ); + this.onClose = this.onClose.bind( this ); + this.onCellPress = this.onCellPress.bind( this ); + + this.state = { + isVisible: false, + }; + } + + presentPicker() { + this.setState( { isVisible: true } ); + } + + onClose() { + this.setState( { isVisible: false } ); + } + + onCellPress( value ) { + this.props.onChange( value ); + this.onClose(); + } + + render() { + return ( + <BottomSheet + isVisible={ this.state.isVisible } + onClose={ this.onClose } + hideHeader + > + <View> + { this.props.options.map( ( option, index ) => + <BottomSheet.Cell + key={ index } + label={ option.label } + onPress={ () => this.onCellPress( option.value ) } + /> + ) } + <BottomSheet.Cell + label={ __( 'Cancel' ) } + onPress={ this.onClose } + drawSeparator={ false } + /> + </View> + </BottomSheet> + ); + } +} diff --git a/packages/editor/src/components/mobile/picker/index.ios.js b/packages/editor/src/components/mobile/picker/index.ios.js new file mode 100644 index 0000000000000..0a0b3d8a2dd41 --- /dev/null +++ b/packages/editor/src/components/mobile/picker/index.ios.js @@ -0,0 +1,37 @@ +/** + * External dependencies + */ +import { ActionSheetIOS } from 'react-native'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Component } from '@wordpress/element'; + +class Picker extends Component { + presentPicker() { + const { options, onChange } = this.props; + const labels = options.map( ( { label } ) => label ); + const fullOptions = [ __( 'Cancel' ) ].concat( labels ); + + ActionSheetIOS.showActionSheetWithOptions( { + options: fullOptions, + cancelButtonIndex: 0, + }, + ( buttonIndex ) => { + if ( buttonIndex === 0 ) { + return; + } + const selected = options[ buttonIndex - 1 ]; + onChange( selected.value ); + }, + ); + } + + render() { + return null; + } +} + +export default Picker; diff --git a/packages/editor/src/components/mobile/picker/styles.android.scss b/packages/editor/src/components/mobile/picker/styles.android.scss new file mode 100644 index 0000000000000..56579b4916535 --- /dev/null +++ b/packages/editor/src/components/mobile/picker/styles.android.scss @@ -0,0 +1,10 @@ +.cellContainer { + min-height: 48; + align-items: flex-start; +} + +.cellLabel { + font-size: 17px; + color: #2e4453; + margin-right: 12px; +} From 8ba4a209b4b1a1bf6e3037d3dea86392d03c2c44 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Thu, 7 Feb 2019 11:42:37 -0800 Subject: [PATCH 374/691] Fix typo : query aeg => query arg (#13746) --- packages/url/src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/url/src/index.js b/packages/url/src/index.js index da3077640e243..8e9bad1165a0c 100644 --- a/packages/url/src/index.js +++ b/packages/url/src/index.js @@ -214,7 +214,7 @@ export function getQueryArg( url, arg ) { * @param {string} url URL * @param {string} arg Query arg name * - * @return {boolean} Whether or not the URL contains the query aeg. + * @return {boolean} Whether or not the URL contains the query arg. */ export function hasQueryArg( url, arg ) { return getQueryArg( url, arg ) !== undefined; From 6ae4ddde2d0700c8fd309ee562beeac2080e79f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Fri, 8 Feb 2019 09:05:17 +0100 Subject: [PATCH 375/691] Update JSDoc (#13742) --- packages/rich-text/src/concat.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rich-text/src/concat.js b/packages/rich-text/src/concat.js index 838f36b0501a6..9f14091f9b1c8 100644 --- a/packages/rich-text/src/concat.js +++ b/packages/rich-text/src/concat.js @@ -8,7 +8,7 @@ import { normaliseFormats } from './normalise-formats'; * Combine all Rich Text values into one. This is similar to * `String.prototype.concat`. * - * @param {...[object]} values An array of all values to combine. + * @param {...Object} values Objects to combine. * * @return {Object} A new value combining all given records. */ From 9d51c3e591da9ef53fe9868fb21605aa861bcaa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Fri, 8 Feb 2019 09:53:55 +0100 Subject: [PATCH 376/691] Update docs (#13733) --- .../developers/backward-compatibility/meta-box.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/designers-developers/developers/backward-compatibility/meta-box.md b/docs/designers-developers/developers/backward-compatibility/meta-box.md index 18feca91ceed2..affb67fa65a10 100644 --- a/docs/designers-developers/developers/backward-compatibility/meta-box.md +++ b/docs/designers-developers/developers/backward-compatibility/meta-box.md @@ -17,9 +17,7 @@ add_meta_box( 'my-meta-box', 'My Meta Box', 'my_meta_box_callback', ); ``` -WordPress will fall back to the Classic editor, where the meta box will continue working as before. - -Explicitly setting `__block_editor_compatible_meta_box` to `true` will cause WordPress to stay in Gutenberg (assuming another meta box doesn't cause a fallback). +WordPress won't show the meta box but a message saying that it isn't compatible with the block editor, including a link to the Classic Editor plugin. By default, `__block_editor_compatible_meta_box` is `true`. After a meta box is converted to a block, it can be declared as existing for backward compatibility: @@ -32,7 +30,7 @@ add_meta_box( 'my-meta-box', 'My Meta Box', 'my_meta_box_callback', ); ``` -When Gutenberg is used, this meta box will no longer be displayed in the meta box area, as it now only exists for backward compatibility purposes. It will continue to display correctly in the Classic editor, should some other meta box cause a fallback. +When Gutenberg is used, this meta box will no longer be displayed in the meta box area, as it now only exists for backward compatibility purposes. It will continue to display correctly in the Classic editor. ### Meta Box Data Collection From 9632d34522e65b160719a090f4be004241c68a5c Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Fri, 8 Feb 2019 10:13:07 +0100 Subject: [PATCH 377/691] Add registry-aware controls (#13722) --- packages/core-data/src/controls.js | 8 ++-- packages/data/src/factory.js | 15 ++++++- packages/data/src/index.js | 2 +- packages/data/src/namespace-store.js | 32 +++++++------- packages/data/src/plugins/controls/index.js | 6 ++- .../data/src/plugins/controls/test/index.js | 44 +++++++++++++++++++ 6 files changed, 85 insertions(+), 22 deletions(-) create mode 100644 packages/data/src/plugins/controls/test/index.js diff --git a/packages/core-data/src/controls.js b/packages/core-data/src/controls.js index 32adbc62aa346..d7544e5b30c9b 100644 --- a/packages/core-data/src/controls.js +++ b/packages/core-data/src/controls.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { default as triggerApiFetch } from '@wordpress/api-fetch'; -import { select as selectData } from '@wordpress/data'; +import { createRegistryControl } from '@wordpress/data'; /** * Trigger an API Fetch request. @@ -37,9 +37,9 @@ const controls = { return triggerApiFetch( request ); }, - SELECT( { selectorName, args } ) { - return selectData( 'core' )[ selectorName ]( ...args ); - }, + SELECT: createRegistryControl( ( registry ) => ( { selectorName, args } ) => { + return registry.select( 'core' )[ selectorName ]( ...args ); + } ), }; export default controls; diff --git a/packages/data/src/factory.js b/packages/data/src/factory.js index 866ce64052839..f7dbb7f269a86 100644 --- a/packages/data/src/factory.js +++ b/packages/data/src/factory.js @@ -1,5 +1,5 @@ /** - * Mark a function as a registry selector. + * Mark a selector as a registry selector. * * @param {function} registrySelector Function receiving a registry object and returning a state selector. * @@ -10,3 +10,16 @@ export function createRegistrySelector( registrySelector ) { return registrySelector; } + +/** + * Mark a control as a registry control. + * + * @param {function} registryControl Function receiving a registry object and returning a control. + * + * @return {function} marked registry control. + */ +export function createRegistryControl( registryControl ) { + registryControl.isRegistryControl = true; + + return registryControl; +} diff --git a/packages/data/src/index.js b/packages/data/src/index.js index a5d1a51214654..06f074bec1541 100644 --- a/packages/data/src/index.js +++ b/packages/data/src/index.js @@ -15,7 +15,7 @@ export { default as RegistryProvider, RegistryConsumer } from './components/regi export { default as __experimentalAsyncModeProvider } from './components/async-mode-provider'; export { createRegistry } from './registry'; export { plugins }; -export { createRegistrySelector } from './factory'; +export { createRegistrySelector, createRegistryControl } from './factory'; /** * The combineReducers helper function turns an object whose values are different diff --git a/packages/data/src/namespace-store.js b/packages/data/src/namespace-store.js index 2e5f2b3022a3f..bb1259d3c52af 100644 --- a/packages/data/src/namespace-store.js +++ b/packages/data/src/namespace-store.js @@ -105,24 +105,26 @@ function createReduxStore( reducer, key, registry ) { * @return {Object} Selectors mapped to the redux store provided. */ function mapSelectors( selectors, store, registry ) { - const createStateSelector = ( registeredSelector ) => function runSelector() { + const createStateSelector = ( registeredSelector ) => { const selector = registeredSelector.isRegistrySelector ? registeredSelector( registry ) : registeredSelector; - // This function is an optimized implementation of: - // - // selector( store.getState(), ...arguments ) - // - // Where the above would incur an `Array#concat` in its application, - // the logic here instead efficiently constructs an arguments array via - // direct assignment. - const argsLength = arguments.length; - const args = new Array( argsLength + 1 ); - args[ 0 ] = store.getState(); - for ( let i = 0; i < argsLength; i++ ) { - args[ i + 1 ] = arguments[ i ]; - } + return function runSelector() { + // This function is an optimized implementation of: + // + // selector( store.getState(), ...arguments ) + // + // Where the above would incur an `Array#concat` in its application, + // the logic here instead efficiently constructs an arguments array via + // direct assignment. + const argsLength = arguments.length; + const args = new Array( argsLength + 1 ); + args[ 0 ] = store.getState(); + for ( let i = 0; i < argsLength; i++ ) { + args[ i + 1 ] = arguments[ i ]; + } - return selector( ...args ); + return selector( ...args ); + }; }; return mapValues( selectors, createStateSelector ); diff --git a/packages/data/src/plugins/controls/index.js b/packages/data/src/plugins/controls/index.js index bc19c9cfad897..a023dd6916b10 100644 --- a/packages/data/src/plugins/controls/index.js +++ b/packages/data/src/plugins/controls/index.js @@ -2,6 +2,7 @@ * External dependencies */ import { applyMiddleware } from 'redux'; +import { mapValues } from 'lodash'; /** * WordPress dependencies @@ -14,7 +15,10 @@ export default function( registry ) { const store = registry.registerStore( reducerKey, options ); if ( options.controls ) { - const middleware = createMiddleware( options.controls ); + const normalizedControls = mapValues( options.controls, ( control ) => { + return control.isRegistryControl ? control( registry ) : control; + } ); + const middleware = createMiddleware( normalizedControls ); const enhancer = applyMiddleware( middleware ); const createStore = () => store; diff --git a/packages/data/src/plugins/controls/test/index.js b/packages/data/src/plugins/controls/test/index.js new file mode 100644 index 0000000000000..d60cb9053211d --- /dev/null +++ b/packages/data/src/plugins/controls/test/index.js @@ -0,0 +1,44 @@ +/** + * Internal dependencies + */ +import { createRegistry } from '../../../registry'; +import { createRegistryControl } from '../../../factory'; +import controlsPlugin from '../'; + +describe( 'controls', () => { + let registry; + + beforeEach( () => { + registry = createRegistry(); + registry.use( controlsPlugin ); + } ); + + describe( 'should call registry-aware controls', () => { + it( 'registers multiple selectors to the public API', () => { + const action1 = jest.fn( () => ( { type: 'NOTHING' } ) ); + const action2 = function * () { + yield { type: 'DISPATCH', store: 'store1', action: 'action1' }; + }; + registry.registerStore( 'store1', { + reducer: () => 'state1', + actions: { + action1, + }, + } ); + registry.registerStore( 'store2', { + reducer: () => 'state2', + actions: { + action2, + }, + controls: { + DISPATCH: createRegistryControl( ( reg ) => ( { store, action } ) => { + return reg.dispatch( store )[ action ](); + } ), + }, + } ); + + registry.dispatch( 'store2' ).action2(); + expect( action1 ).toBeCalled(); + } ); + } ); +} ); From ed4f8c8966186909b22b06ba894445c0f5291482 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Fri, 8 Feb 2019 10:21:21 +0100 Subject: [PATCH 378/691] Escape HTML characters (#13744) --- packages/autop/src/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/autop/src/index.js b/packages/autop/src/index.js index e3b9fbca887b7..916f56a4be5f2 100644 --- a/packages/autop/src/index.js +++ b/packages/autop/src/index.js @@ -112,7 +112,7 @@ function replaceInHtmlTags( haystack, replacePairs ) { * * A group of regex replaces used to identify text formatted with newlines and * replace double line-breaks with HTML paragraph tags. The remaining line- - * breaks after conversion become <<br />> tags, unless br is set to 'false'. + * breaks after conversion become `<br />` tags, unless br is set to 'false'. * * @param {string} text The text which has to be formatted. * @param {boolean} br Optional. If set, will convert all remaining line- @@ -278,10 +278,10 @@ export function autop( text, br = true ) { } /** - * Replaces <p> tags with two line breaks. "Opposite" of autop(). + * Replaces `<p>` tags with two line breaks. "Opposite" of autop(). * - * Replaces <p> tags with two line breaks except where the <p> has attributes. - * Unifies whitespace. Indents <li>, <dt> and <dd> for better readability. + * Replaces `<p>` tags with two line breaks except where the `<p>` has attributes. + * Unifies whitespace. Indents `<li>`, `<dt>` and `<dd>` for better readability. * * @param {string} html The content from the editor. * @return {string} The content with stripped paragraph tags. From c5f9bd88125282a0c35f887cc8d835f065893112 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Fri, 8 Feb 2019 11:13:15 +0100 Subject: [PATCH 379/691] Adjust nosolosw notifs (#13766) --- .github/CODEOWNERS | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index df5de975fac4f..d156343b97019 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -14,7 +14,7 @@ /packages/block-serialization-default-parser @youknowriad @gziolo @aduth /packages/blocks @youknowriad @gziolo @aduth @noisysocks /packages/edit-post @youknowriad @gziolo @talldan @noisysocks -/packages/editor @youknowriad @gziolo @nosolosw @talldan @noisysocks +/packages/editor @youknowriad @gziolo @talldan @noisysocks /packages/list-reusable-blocks @youknowriad @aduth @noisysocks /packages/shortcode @youknowriad @aduth @@ -27,17 +27,17 @@ /packages/custom-templated-path-webpack-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra /packages/e2e-test-utils @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra /packages/e2e-tests @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @talldan -/packages/eslint-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra +/packages/eslint-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw /packages/jest-console @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra /packages/jest-preset-default @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra /packages/jest-puppeteer-axe @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra /packages/library-export-default-webpack-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra /packages/npm-package-json-lint-config @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra /packages/postcss-themes @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra -/packages/scripts @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra +/packages/scripts @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw # UI Components -/packages/components @youknowriad @gziolo @aduth @chrisvanpatten @ajitbohra @jaymanpandya @nosolosw @jorgefilipecosta @talldan @noisysocks +/packages/components @youknowriad @gziolo @aduth @chrisvanpatten @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks /packages/compose @youknowriad @gziolo @aduth @chrisvanpatten @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks /packages/element @youknowriad @gziolo @aduth @chrisvanpatten @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks /packages/notices @youknowriad @gziolo @aduth @chrisvanpatten @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks @@ -49,7 +49,7 @@ /packages/blob @youknowriad @gziolo @aduth /packages/date @youknowriad @gziolo @aduth /packages/deprecated @youknowriad @gziolo @aduth -/packages/dom @youknowriad @gziolo @aduth +/packages/dom @youknowriad @gziolo @aduth @nosolosw /packages/dom-ready @youknowriad @gziolo @aduth /packages/escape-html @youknowriad @gziolo @aduth /packages/html-entities @youknowriad @gziolo @aduth From be36d1a6c098337d39ca09dea35def7beee66ca2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20K=C3=A4gy?= <fabian@arvernus.info> Date: Fri, 8 Feb 2019 12:13:16 +0100 Subject: [PATCH 380/691] adding missing props to save func in es5 example (#13752) In the ES5 example for the RichText component the props are not passed into the save function. Inside the save function they are called though. --- packages/editor/src/components/rich-text/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor/src/components/rich-text/README.md b/packages/editor/src/components/rich-text/README.md index 4e543219177e4..6774ca3879f49 100644 --- a/packages/editor/src/components/rich-text/README.md +++ b/packages/editor/src/components/rich-text/README.md @@ -83,7 +83,7 @@ wp.blocks.registerBlockType( /* ... */, { } ); }, - save: function() { + save: function( props ) { return wp.element.createElement( wp.editor.RichText.Content, { tagName: 'h2', value: props.attributes.content } ); From f345f96458fab800a91c28ba2c11bde49957862c Mon Sep 17 00:00:00 2001 From: Raymond Johnson <raymondcmjohnson@gmail.com> Date: Fri, 8 Feb 2019 06:43:02 -0500 Subject: [PATCH 381/691] Update Gutenberg lifecycle clarity and grammar (#13574) * Update Gutenberg lifecycle clarity and grammar Simplified sentence structure, semantics of the closing paragraph. Also made redundant content relevant to the overall aims of the passage. * Update key-concepts.md --- docs/designers-developers/key-concepts.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/designers-developers/key-concepts.md b/docs/designers-developers/key-concepts.md index 3252337ba2b33..e42f5aa044ba1 100644 --- a/docs/designers-developers/key-concepts.md +++ b/docs/designers-developers/key-concepts.md @@ -143,4 +143,6 @@ A purely dynamic block that is to be server rendered before display could look l ## The Gutenberg Lifecycle -In summary, the workflow for editing a Gutenberg post starts with taking the persisted version of the document and generating the in-memory tree, aided by the presence of token delimiters. It ends with the reverse: serialization of blocks into `post_content`. During editing, all manipulations happen within the block tree. In summary, a Gutenberg post is built upon an in-memory data structure which gets persisted somehow in an fully-isomorphic way. Right now that persistence is via a serialization/parser pair but could just as easily be replaced through a plugin to store the data structure as a JSON blob somewhere else. +In summary, the Gutenberg workflow parses the saved document to an in-memory tree of blocks, using token delimiters to help. During editing, all manipulations happen within the block tree. The process ends by serializing the blocks back to the `post_content`. + +The workflow process relies on a serialization/parser pair to persist posts. Hypothetically, the post data structure could be stored using a plugin or retrieved from a remote JSON file to be converted to the block tree. From 6ba4775e21d5b6227243af4a2eb35c63531b7dc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Fri, 8 Feb 2019 13:29:03 +0100 Subject: [PATCH 382/691] Use example tag in JSDoc (#13745) --- .../src/mocks/set-up-response-mocking.js | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/packages/e2e-test-utils/src/mocks/set-up-response-mocking.js b/packages/e2e-test-utils/src/mocks/set-up-response-mocking.js index 8316ba9046f67..b2ba2d31db0ac 100644 --- a/packages/e2e-test-utils/src/mocks/set-up-response-mocking.js +++ b/packages/e2e-test-utils/src/mocks/set-up-response-mocking.js @@ -10,21 +10,25 @@ let requestMocks = []; /** * Sets up mock checks and responses. Accepts a list of mock settings with the following properties: - * - match: function to check if a request should be mocked. - * - onRequestMatch: async function to respond to the request. * - * Example: - * const MOCK_RESPONSES = [ - * { - * match: isEmbedding( 'https://wordpress.org/gutenberg/handbook/' ), - * onRequestMatch: JSONResponse( MOCK_BAD_WORDPRESS_RESPONSE ), - * }, - * { - * match: isEmbedding( 'https://wordpress.org/gutenberg/handbook/block-api/attributes/' ), - * onRequestMatch: JSONResponse( MOCK_EMBED_WORDPRESS_SUCCESS_RESPONSE ), - * } - * ]; - * setUpResponseMocking( MOCK_RESPONSES ); + * - `match`: function to check if a request should be mocked. + * - `onRequestMatch`: async function to respond to the request. + * + * @example + * + * ```js + * const MOCK_RESPONSES = [ + * { + * match: isEmbedding( 'https://wordpress.org/gutenberg/handbook/' ), + * onRequestMatch: JSONResponse( MOCK_BAD_WORDPRESS_RESPONSE ), + * }, + * { + * match: isEmbedding( 'https://wordpress.org/gutenberg/handbook/block-api/attributes/' ), + * onRequestMatch: JSONResponse( MOCK_EMBED_WORDPRESS_SUCCESS_RESPONSE ), + * } + * ]; + * setUpResponseMocking( MOCK_RESPONSES ); + * ``` * * If none of the mock settings match the request, the request is allowed to continue. * From 50ae68cd8ed990cf9aeb96a6216c7522b692d6ed Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Fri, 8 Feb 2019 15:22:08 +0100 Subject: [PATCH 383/691] Add a plugin to disable the CSS animation for more stable e2e tests (#13769) --- .../e2e-test-utils/src/activate-plugin.js | 4 ++++ .../src/click-on-more-menu-item.js | 6 ------ packages/e2e-test-utils/src/index.js | 1 - .../e2e-test-utils/src/search-for-block.js | 6 ------ .../src/switch-editor-mode-to.js | 6 ------ .../e2e-test-utils/src/transform-block-to.js | 6 ------ .../e2e-test-utils/src/wait-for-animation.js | 10 --------- .../e2e-tests/config/setup-test-framework.js | 2 ++ .../e2e-tests/plugins/disable-animations.php | 18 ++++++++++++++++ .../e2e-tests/specs/block-deletion.test.js | 2 -- .../specs/block-hierarchy-navigation.test.js | 3 --- packages/e2e-tests/specs/editor-modes.test.js | 5 ----- .../e2e-tests/specs/font-size-picker.test.js | 2 -- .../e2e-tests/specs/invalid-block.test.js | 2 -- packages/e2e-tests/specs/links.test.js | 21 ------------------- packages/e2e-tests/specs/nux.test.js | 2 -- .../specs/plugins/annotations.test.js | 2 -- .../specs/plugins/container-blocks.test.js | 2 -- .../e2e-tests/specs/reusable-blocks.test.js | 7 ------- 19 files changed, 24 insertions(+), 83 deletions(-) delete mode 100644 packages/e2e-test-utils/src/wait-for-animation.js create mode 100644 packages/e2e-tests/plugins/disable-animations.php diff --git a/packages/e2e-test-utils/src/activate-plugin.js b/packages/e2e-test-utils/src/activate-plugin.js index ce52f380f266b..1cfe3786f4cdb 100644 --- a/packages/e2e-test-utils/src/activate-plugin.js +++ b/packages/e2e-test-utils/src/activate-plugin.js @@ -13,6 +13,10 @@ import { visitAdminPage } from './visit-admin-page'; export async function activatePlugin( slug ) { await switchUserToAdmin(); await visitAdminPage( 'plugins.php' ); + const disableLink = await page.$( `tr[data-slug="${ slug }"] .deactivate a` ); + if ( disableLink ) { + return; + } await page.click( `tr[data-slug="${ slug }"] .activate a` ); await page.waitForSelector( `tr[data-slug="${ slug }"] .deactivate a` ); await switchUserToTest(); diff --git a/packages/e2e-test-utils/src/click-on-more-menu-item.js b/packages/e2e-test-utils/src/click-on-more-menu-item.js index 9484a434a8f06..0465441ec6496 100644 --- a/packages/e2e-test-utils/src/click-on-more-menu-item.js +++ b/packages/e2e-test-utils/src/click-on-more-menu-item.js @@ -3,11 +3,6 @@ */ import { first } from 'lodash'; -/** - * Internal dependencies - */ -import { waitForAnimation } from './wait-for-animation'; - /** * Clicks on More Menu item, searches for the button with the text provided and clicks it. * @@ -17,7 +12,6 @@ export async function clickOnMoreMenuItem( buttonLabel ) { await expect( page ).toClick( '.edit-post-more-menu [aria-label="Show more tools & options"]' ); - await waitForAnimation(); const moreMenuContainerSelector = '//*[contains(concat(" ", @class, " "), " edit-post-more-menu__content ")]'; let elementToClick = first( await page.$x( diff --git a/packages/e2e-test-utils/src/index.js b/packages/e2e-test-utils/src/index.js index 58b388e38c361..5534c981f46a9 100644 --- a/packages/e2e-test-utils/src/index.js +++ b/packages/e2e-test-utils/src/index.js @@ -40,7 +40,6 @@ export { toggleScreenOption } from './toggle-screen-option'; export { transformBlockTo } from './transform-block-to'; export { uninstallPlugin } from './uninstall-plugin'; export { visitAdminPage } from './visit-admin-page'; -export { waitForAnimation } from './wait-for-animation'; export { waitForWindowDimensions } from './wait-for-window-dimensions'; export * from './mocks'; diff --git a/packages/e2e-test-utils/src/search-for-block.js b/packages/e2e-test-utils/src/search-for-block.js index f4bd5cc92a802..e96b92839221a 100644 --- a/packages/e2e-test-utils/src/search-for-block.js +++ b/packages/e2e-test-utils/src/search-for-block.js @@ -1,8 +1,3 @@ -/** - * Internal dependencies - */ -import { waitForAnimation } from './wait-for-animation'; - /** * Search for block in the global inserter * @@ -10,7 +5,6 @@ import { waitForAnimation } from './wait-for-animation'; */ export async function searchForBlock( searchTerm ) { await page.click( '.edit-post-header [aria-label="Add block"]' ); - await waitForAnimation(); // Waiting here is necessary because sometimes the inserter takes more time to // render than Puppeteer takes to complete the 'click' action await page.waitForSelector( '.editor-inserter__menu' ); diff --git a/packages/e2e-test-utils/src/switch-editor-mode-to.js b/packages/e2e-test-utils/src/switch-editor-mode-to.js index 78225e18f422d..6978b2ee81247 100644 --- a/packages/e2e-test-utils/src/switch-editor-mode-to.js +++ b/packages/e2e-test-utils/src/switch-editor-mode-to.js @@ -1,8 +1,3 @@ -/** - * Internal dependencies - */ -import { waitForAnimation } from './wait-for-animation'; - /** * Switches editor mode. * @@ -12,7 +7,6 @@ export async function switchEditorModeTo( mode ) { await page.click( '.edit-post-more-menu [aria-label="Show more tools & options"]' ); - await waitForAnimation(); const [ button ] = await page.$x( `//button[contains(text(), '${ mode } Editor')]` ); diff --git a/packages/e2e-test-utils/src/transform-block-to.js b/packages/e2e-test-utils/src/transform-block-to.js index 8fd2db2182521..902d98d35c254 100644 --- a/packages/e2e-test-utils/src/transform-block-to.js +++ b/packages/e2e-test-utils/src/transform-block-to.js @@ -1,8 +1,3 @@ -/** - * Internal dependencies - */ -import { waitForAnimation } from './wait-for-animation'; - /** * Converts editor's block type. * @@ -12,7 +7,6 @@ export async function transformBlockTo( name ) { await page.mouse.move( 200, 300, { steps: 10 } ); await page.mouse.move( 250, 350, { steps: 10 } ); await page.click( '.editor-block-switcher__toggle' ); - await waitForAnimation(); await page.waitForSelector( `.editor-block-types-list__item[aria-label="${ name }"]` ); await page.click( `.editor-block-types-list__item[aria-label="${ name }"]` ); } diff --git a/packages/e2e-test-utils/src/wait-for-animation.js b/packages/e2e-test-utils/src/wait-for-animation.js deleted file mode 100644 index 63ff4499dff46..0000000000000 --- a/packages/e2e-test-utils/src/wait-for-animation.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Waits for the small delay until the animation finishes. - * - * @param {number} delay Animation Delay. - * - * @return {Promise} Promise resolving after a small delay. - */ -export const waitForAnimation = async ( delay = 100 ) => { - return new Promise( ( resolve ) => setTimeout( resolve, delay ) ); -}; diff --git a/packages/e2e-tests/config/setup-test-framework.js b/packages/e2e-tests/config/setup-test-framework.js index 20d96e29fb570..5a28430dda2fb 100644 --- a/packages/e2e-tests/config/setup-test-framework.js +++ b/packages/e2e-tests/config/setup-test-framework.js @@ -13,6 +13,7 @@ import { enablePageDialogAccept, setBrowserViewport, visitAdminPage, + activatePlugin, } from '@wordpress/e2e-test-utils'; /** @@ -154,6 +155,7 @@ beforeAll( async () => { await trashExistingPosts(); await setupBrowser(); + await activatePlugin( 'gutenberg-test-plugin-disables-the-css-animations' ); } ); afterEach( async () => { diff --git a/packages/e2e-tests/plugins/disable-animations.php b/packages/e2e-tests/plugins/disable-animations.php new file mode 100644 index 0000000000000..f69e743d430d8 --- /dev/null +++ b/packages/e2e-tests/plugins/disable-animations.php @@ -0,0 +1,18 @@ +<?php +/** + * Plugin Name: Gutenberg Test Plugin, Disables the CSS animations + * Plugin URI: https://github.com/WordPress/gutenberg + * Author: Gutenberg Team + * + * @package gutenberg-test-disable-animations + */ + +/** + * Enqueue CSS stylesheet disabling animations. + */ +function enqueue_disable_animations_stylesheet() { + $custom_css = '* { animation-duration: 1ms !important; }'; + wp_add_inline_style( 'wp-components', $custom_css ); +} + +add_action( 'enqueue_block_editor_assets', 'enqueue_disable_animations_stylesheet' ); diff --git a/packages/e2e-tests/specs/block-deletion.test.js b/packages/e2e-tests/specs/block-deletion.test.js index dfaa83dfe2668..b08eacd1f6d62 100644 --- a/packages/e2e-tests/specs/block-deletion.test.js +++ b/packages/e2e-tests/specs/block-deletion.test.js @@ -6,7 +6,6 @@ import { getEditedPostContent, createNewPost, pressKeyWithModifier, - waitForAnimation, } from '@wordpress/e2e-test-utils'; const addThreeParagraphsToNewPost = async () => { @@ -22,7 +21,6 @@ const addThreeParagraphsToNewPost = async () => { const clickOnBlockSettingsMenuItem = async ( buttonLabel ) => { await expect( page ).toClick( '.editor-block-settings-menu__toggle' ); - await waitForAnimation(); const itemButton = ( await page.$x( `//*[contains(@class, "editor-block-settings-menu__popover")]//button[contains(text(), '${ buttonLabel }')]` ) )[ 0 ]; await itemButton.click(); }; diff --git a/packages/e2e-tests/specs/block-hierarchy-navigation.test.js b/packages/e2e-tests/specs/block-hierarchy-navigation.test.js index c22a572399f11..4745768f812c7 100644 --- a/packages/e2e-tests/specs/block-hierarchy-navigation.test.js +++ b/packages/e2e-tests/specs/block-hierarchy-navigation.test.js @@ -7,12 +7,10 @@ import { getEditedPostContent, pressKeyTimes, pressKeyWithModifier, - waitForAnimation, } from '@wordpress/e2e-test-utils'; async function openBlockNavigator() { await pressKeyWithModifier( 'access', 'o' ); - await waitForAnimation(); } describe( 'Navigating the block hierarchy', () => { @@ -28,7 +26,6 @@ describe( 'Navigating the block hierarchy', () => { // Navigate to the columns blocks. await page.click( '[aria-label="Block Navigation"]' ); - await waitForAnimation(); const columnsBlockMenuItem = ( await page.$x( "//button[contains(@class,'editor-block-navigation__item') and contains(text(), 'Columns')]" ) )[ 0 ]; await columnsBlockMenuItem.click(); diff --git a/packages/e2e-tests/specs/editor-modes.test.js b/packages/e2e-tests/specs/editor-modes.test.js index 21e746f648eb0..c8572f5526103 100644 --- a/packages/e2e-tests/specs/editor-modes.test.js +++ b/packages/e2e-tests/specs/editor-modes.test.js @@ -5,7 +5,6 @@ import { clickBlockAppender, createNewPost, switchEditorModeTo, - waitForAnimation, } from '@wordpress/e2e-test-utils'; describe( 'Editing modes (visual/HTML)', () => { @@ -26,7 +25,6 @@ describe( 'Editing modes (visual/HTML)', () => { // Change editing mode from "Visual" to "HTML". await page.waitForSelector( 'button[aria-label="More options"]' ); await page.click( 'button[aria-label="More options"]' ); - await waitForAnimation(); let changeModeButton = await page.waitForXPath( '//button[text()="Edit as HTML"]' ); await changeModeButton.click(); @@ -40,7 +38,6 @@ describe( 'Editing modes (visual/HTML)', () => { // Change editing mode from "HTML" back to "Visual". await page.waitForSelector( 'button[aria-label="More options"]' ); await page.click( 'button[aria-label="More options"]' ); - await waitForAnimation(); changeModeButton = await page.waitForXPath( '//button[text()="Edit visually"]' ); await changeModeButton.click(); @@ -56,7 +53,6 @@ describe( 'Editing modes (visual/HTML)', () => { // Change editing mode from "Visual" to "HTML". await page.waitForSelector( 'button[aria-label="More options"]' ); await page.click( 'button[aria-label="More options"]' ); - await waitForAnimation(); const changeModeButton = await page.waitForXPath( '//button[text()="Edit as HTML"]' ); await changeModeButton.click(); @@ -73,7 +69,6 @@ describe( 'Editing modes (visual/HTML)', () => { // Change editing mode from "Visual" to "HTML". await page.waitForSelector( 'button[aria-label="More options"]' ); await page.click( 'button[aria-label="More options"]' ); - await waitForAnimation(); const changeModeButton = await page.waitForXPath( '//button[text()="Edit as HTML"]' ); await changeModeButton.click(); diff --git a/packages/e2e-tests/specs/font-size-picker.test.js b/packages/e2e-tests/specs/font-size-picker.test.js index 28f9c39cd967c..35c4104207381 100644 --- a/packages/e2e-tests/specs/font-size-picker.test.js +++ b/packages/e2e-tests/specs/font-size-picker.test.js @@ -6,7 +6,6 @@ import { getEditedPostContent, createNewPost, pressKeyTimes, - waitForAnimation, } from '@wordpress/e2e-test-utils'; describe( 'Font Size Picker', () => { @@ -19,7 +18,6 @@ describe( 'Font Size Picker', () => { await clickBlockAppender(); await page.keyboard.type( 'Paragraph to be made "large"' ); await page.click( '.components-font-size-picker__selector' ); - await waitForAnimation(); const changeSizeButton = await page.waitForSelector( '.components-button.is-font-large' ); await changeSizeButton.click(); diff --git a/packages/e2e-tests/specs/invalid-block.test.js b/packages/e2e-tests/specs/invalid-block.test.js index c54ea208d8891..a068781ef42ac 100644 --- a/packages/e2e-tests/specs/invalid-block.test.js +++ b/packages/e2e-tests/specs/invalid-block.test.js @@ -4,7 +4,6 @@ import { createNewPost, clickBlockAppender, - waitForAnimation, } from '@wordpress/e2e-test-utils'; describe( 'invalid blocks', () => { @@ -20,7 +19,6 @@ describe( 'invalid blocks', () => { // Click the 'more options' await page.mouse.move( 200, 300, { steps: 10 } ); await page.click( 'button[aria-label="More options"]' ); - await waitForAnimation(); // Change to HTML mode and close the options const changeModeButton = await page.waitForXPath( '//button[text()="Edit as HTML"]' ); diff --git a/packages/e2e-tests/specs/links.test.js b/packages/e2e-tests/specs/links.test.js index 9b35e1aec4778..03e8b6073ac22 100644 --- a/packages/e2e-tests/specs/links.test.js +++ b/packages/e2e-tests/specs/links.test.js @@ -8,7 +8,6 @@ import { pressKeyWithModifier, pressKeyTimes, insertBlock, - waitForAnimation, } from '@wordpress/e2e-test-utils'; /** @@ -41,7 +40,6 @@ describe( 'Links', () => { // Click on the Link button await page.click( 'button[aria-label="Link"]' ); - await waitForAnimation(); // Wait for the URL field to auto-focus await waitForAutoFocus(); @@ -66,7 +64,6 @@ describe( 'Links', () => { // Press Cmd+K to insert a link await pressKeyWithModifier( 'primary', 'K' ); - await waitForAnimation(); // Wait for the URL field to auto-focus await waitForAutoFocus(); @@ -91,7 +88,6 @@ describe( 'Links', () => { // Press Cmd+K to insert a link await pressKeyWithModifier( 'primary', 'K' ); - await waitForAnimation(); // Wait for the URL field to auto-focus await waitForAutoFocus(); @@ -134,7 +130,6 @@ describe( 'Links', () => { // Click on the Link button await page.click( 'button[aria-label="Link"]' ); - await waitForAnimation(); // Wait for the URL field to auto-focus await waitForAutoFocus(); @@ -156,7 +151,6 @@ describe( 'Links', () => { // Click on the Link button await page.click( 'button[aria-label="Link"]' ); - await waitForAnimation(); // Wait for the URL field to auto-focus await waitForAutoFocus(); @@ -213,7 +207,6 @@ describe( 'Links', () => { await clickBlockAppender(); await page.keyboard.type( 'Text' ); await page.click( 'button[aria-label="Link"]' ); - await waitForAnimation(); // Typing "left" should not close the dialog await page.keyboard.press( 'ArrowLeft' ); @@ -236,7 +229,6 @@ describe( 'Links', () => { await moveMouse(); await page.waitForSelector( 'button[aria-label="Link"]' ); await page.click( 'button[aria-label="Link"]' ); - await waitForAnimation(); // Typing "left" should not close the dialog await page.keyboard.press( 'ArrowLeft' ); @@ -293,14 +285,12 @@ describe( 'Links', () => { await page.keyboard.type( 'This is Gutenberg' ); await pressKeyWithModifier( 'shiftAlt', 'ArrowLeft' ); await page.click( 'button[aria-label="Link"]' ); - await waitForAnimation(); // Wait for the URL field to auto-focus await waitForAutoFocus(); await page.keyboard.type( titleText ); await page.waitForSelector( '.editor-url-input__suggestion' ); - await waitForAnimation(); const autocompleteSuggestions = await page.$x( `//*[contains(@class, "editor-url-input__suggestion")]//button[contains(text(), '${ titleText }')]` ); // Expect there to be some autocomplete suggestions. @@ -337,14 +327,12 @@ describe( 'Links', () => { // Press Cmd+K to insert a link await pressKeyWithModifier( 'primary', 'K' ); - await waitForAnimation(); // Wait for the URL field to auto-focus await waitForAutoFocus(); await page.keyboard.type( titleText ); await page.waitForSelector( '.editor-url-input__suggestion' ); - await waitForAnimation(); const autocompleteSuggestions = await page.$x( `//*[contains(@class, "editor-url-input__suggestion")]//button[contains(text(), '${ titleText }')]` ); // Expect there to be some autocomplete suggestions. @@ -375,7 +363,6 @@ describe( 'Links', () => { // Press Cmd+K to insert a link await pressKeyWithModifier( 'primary', 'K' ); - await waitForAnimation(); // Wait for the URL field to auto-focus await waitForAutoFocus(); @@ -384,7 +371,6 @@ describe( 'Links', () => { // Trigger the autocomplete suggestion list and select the first suggestion. await page.keyboard.type( titleText ); await page.waitForSelector( '.editor-url-input__suggestion' ); - await waitForAnimation(); await page.keyboard.press( 'ArrowDown' ); // Expect the the escape key to dismiss the popover when the autocomplete suggestion list is open. @@ -393,7 +379,6 @@ describe( 'Links', () => { // Press Cmd+K to insert a link await pressKeyWithModifier( 'primary', 'K' ); - await waitForAnimation(); // Wait for the URL field to auto-focus await waitForAutoFocus(); @@ -427,7 +412,6 @@ describe( 'Links', () => { await page.keyboard.type( 'This is Gutenberg' ); await pressKeyWithModifier( 'shiftAlt', 'ArrowLeft' ); await pressKeyWithModifier( 'primary', 'K' ); - await waitForAnimation(); await waitForAutoFocus(); await page.keyboard.type( URL ); await page.keyboard.press( 'Enter' ); @@ -445,7 +429,6 @@ describe( 'Links', () => { // Press Cmd+K to edit the link and the url-input should become // focused with the value previously inserted. await pressKeyWithModifier( 'primary', 'K' ); - await waitForAnimation(); await waitForAutoFocus(); const activeElementParentClasses = await page.evaluate( () => Object.values( document.activeElement.parentElement.classList ) ); expect( activeElementParentClasses ).toContain( 'editor-url-input' ); @@ -458,7 +441,6 @@ describe( 'Links', () => { await page.keyboard.type( 'This is Gutenberg' ); await pressKeyWithModifier( 'shiftAlt', 'ArrowLeft' ); await pressKeyWithModifier( 'primary', 'K' ); - await waitForAnimation(); await waitForAutoFocus(); await page.keyboard.type( 'http://#test.com' ); await page.keyboard.press( 'Enter' ); @@ -482,8 +464,6 @@ describe( 'Links', () => { // Click on the Link button await page.click( 'button[aria-label="Link"]' ); - await waitForAnimation(); - // Wait for the URL field to auto-focus await waitForAutoFocus(); @@ -510,7 +490,6 @@ describe( 'Links', () => { // Select "WordPress". await pressKeyWithModifier( 'shiftAlt', 'ArrowLeft' ); await pressKeyWithModifier( 'primary', 'k' ); - await waitForAnimation(); await waitForAutoFocus(); await page.keyboard.type( 'w.org' ); // Navigate to the settings toggle. diff --git a/packages/e2e-tests/specs/nux.test.js b/packages/e2e-tests/specs/nux.test.js index 4d141c9dcec8b..d8a64be089eca 100644 --- a/packages/e2e-tests/specs/nux.test.js +++ b/packages/e2e-tests/specs/nux.test.js @@ -7,7 +7,6 @@ import { createNewPost, saveDraft, toggleScreenOption, - waitForAnimation, } from '@wordpress/e2e-test-utils'; describe( 'New User Experience (NUX)', () => { @@ -18,7 +17,6 @@ describe( 'New User Experience (NUX)', () => { for ( let i = 1; i < numberOfTips; i++ ) { await page.click( '.nux-dot-tip .components-button.is-link' ); - await waitForAnimation(); } return { numberOfTips, tips }; diff --git a/packages/e2e-tests/specs/plugins/annotations.test.js b/packages/e2e-tests/specs/plugins/annotations.test.js index c07555c618380..5fe99a352eec6 100644 --- a/packages/e2e-tests/specs/plugins/annotations.test.js +++ b/packages/e2e-tests/specs/plugins/annotations.test.js @@ -6,12 +6,10 @@ import { clickOnMoreMenuItem, createNewPost, deactivatePlugin, - waitForAnimation, } from '@wordpress/e2e-test-utils'; const clickOnBlockSettingsMenuItem = async ( buttonLabel ) => { await expect( page ).toClick( '.editor-block-settings-menu__toggle' ); - await waitForAnimation(); const itemButton = ( await page.$x( `//*[contains(@class, "editor-block-settings-menu__popover")]//button[contains(text(), '${ buttonLabel }')]` ) )[ 0 ]; await itemButton.click(); }; diff --git a/packages/e2e-tests/specs/plugins/container-blocks.test.js b/packages/e2e-tests/specs/plugins/container-blocks.test.js index 0e71003c9bcc9..2d32224bb3503 100644 --- a/packages/e2e-tests/specs/plugins/container-blocks.test.js +++ b/packages/e2e-tests/specs/plugins/container-blocks.test.js @@ -8,7 +8,6 @@ import { getEditedPostContent, insertBlock, switchEditorModeTo, - waitForAnimation, } from '@wordpress/e2e-test-utils'; describe( 'InnerBlocks Template Sync', () => { @@ -80,7 +79,6 @@ describe( 'Container block without paragraph support', () => { // Open the specific appender used when there's no paragraph support. await page.click( '.editor-inner-blocks .block-list-appender .block-list-appender__toggle' ); - await waitForAnimation(); // Insert an image block. await page.click( '.editor-inserter__results button[aria-label="Image"]' ); diff --git a/packages/e2e-tests/specs/reusable-blocks.test.js b/packages/e2e-tests/specs/reusable-blocks.test.js index bd21960e6adce..f1ef297f311ba 100644 --- a/packages/e2e-tests/specs/reusable-blocks.test.js +++ b/packages/e2e-tests/specs/reusable-blocks.test.js @@ -7,7 +7,6 @@ import { pressKeyWithModifier, searchForBlock, getEditedPostContent, - waitForAnimation, } from '@wordpress/e2e-test-utils'; function waitForAndAcceptDialog() { @@ -42,7 +41,6 @@ describe( 'Reusable Blocks', () => { // Convert block to a reusable block await page.waitForSelector( 'button[aria-label="More options"]' ); await page.click( 'button[aria-label="More options"]' ); - await waitForAnimation(); const convertButton = await page.waitForXPath( '//button[text()="Add to Reusable Blocks"]' ); await convertButton.click(); @@ -90,7 +88,6 @@ describe( 'Reusable Blocks', () => { // Convert block to a reusable block await page.waitForSelector( 'button[aria-label="More options"]' ); await page.click( 'button[aria-label="More options"]' ); - await waitForAnimation(); const convertButton = await page.waitForXPath( '//button[text()="Add to Reusable Blocks"]' ); await convertButton.click(); @@ -168,7 +165,6 @@ describe( 'Reusable Blocks', () => { // Convert block to a regular block await page.click( 'button[aria-label="More options"]' ); - await waitForAnimation(); const convertButton = await page.waitForXPath( '//button[text()="Convert to Regular Block"]' ); @@ -192,7 +188,6 @@ describe( 'Reusable Blocks', () => { // Delete the block and accept the confirmation dialog await page.click( 'button[aria-label="More options"]' ); - await waitForAnimation(); const deleteButton = await page.waitForXPath( '//button[text()="Remove from Reusable Blocks"]' ); await Promise.all( [ waitForAndAcceptDialog(), deleteButton.click() ] ); @@ -234,7 +229,6 @@ describe( 'Reusable Blocks', () => { // Convert block to a reusable block await page.waitForSelector( 'button[aria-label="More options"]' ); await page.click( 'button[aria-label="More options"]' ); - await waitForAnimation(); const convertButton = await page.waitForXPath( '//button[text()="Add to Reusable Blocks"]' ); await convertButton.click(); @@ -276,7 +270,6 @@ describe( 'Reusable Blocks', () => { // Convert block to a regular block await page.click( 'button[aria-label="More options"]' ); - await waitForAnimation(); const convertButton = await page.waitForXPath( '//button[text()="Convert to Regular Block"]' ); From 6ee1944cc96c53a2716522bdf3273d4d7459338b Mon Sep 17 00:00:00 2001 From: Pinar Olguc <pinarolguc@gmail.com> Date: Fri, 8 Feb 2019 17:48:17 +0300 Subject: [PATCH 384/691] [Mobile]Release 0.3.5 updates (#13768) * [Mobile]Remove unnecessary <strong> tags from post title (#13763) * Add extra rootTagsToEliminate prop * Fix lint issues * Revert unnecessary prop * Add code comment for the workaround fix --- .../src/components/post-title/index.native.js | 1 + .../src/components/rich-text/index.native.js | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/editor/src/components/post-title/index.native.js b/packages/editor/src/components/post-title/index.native.js index c793cb4d0a113..f9534487cfdaa 100644 --- a/packages/editor/src/components/post-title/index.native.js +++ b/packages/editor/src/components/post-title/index.native.js @@ -48,6 +48,7 @@ class PostTitle extends Component { return ( <RichText tagName={ 'p' } + rootTagsToEliminate={ [ 'strong' ] } onFocus={ this.onSelect } onBlur={ this.props.onBlur } // always assign onBlur as a props multiline={ false } diff --git a/packages/editor/src/components/rich-text/index.native.js b/packages/editor/src/components/rich-text/index.native.js index c52f31cbc96ed..ac1f8593d73c9 100644 --- a/packages/editor/src/components/rich-text/index.native.js +++ b/packages/editor/src/components/rich-text/index.native.js @@ -170,8 +170,19 @@ export class RichText extends Component { */ removeRootTagsProduceByAztec( html ) { - const openingTagRegexp = RegExp( '^<' + this.props.tagName + '>', 'gim' ); - const closingTagRegexp = RegExp( '</' + this.props.tagName + '>$', 'gim' ); + let result = this.removeRootTag( this.props.tagName, html ); + // Temporary workaround for https://github.com/WordPress/gutenberg/pull/13763 + if ( this.props.rootTagsToEliminate ) { + this.props.rootTagsToEliminate.forEach( ( element ) => { + result = this.removeRootTag( element, result ); + } ); + } + return result; + } + + removeRootTag( tag, html ) { + const openingTagRegexp = RegExp( '^<' + tag + '>', 'gim' ); + const closingTagRegexp = RegExp( '</' + tag + '>$', 'gim' ); return html.replace( openingTagRegexp, '' ).replace( closingTagRegexp, '' ); } From dcc4cbd2b3b7947cd7440a2fa5b1f49cfa32e732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Fri, 8 Feb 2019 17:28:15 +0100 Subject: [PATCH 385/691] Disable animations completely in e2e tests (#13779) --- packages/e2e-tests/plugins/disable-animations.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/e2e-tests/plugins/disable-animations.php b/packages/e2e-tests/plugins/disable-animations.php index f69e743d430d8..f13c2c9003a2d 100644 --- a/packages/e2e-tests/plugins/disable-animations.php +++ b/packages/e2e-tests/plugins/disable-animations.php @@ -11,7 +11,7 @@ * Enqueue CSS stylesheet disabling animations. */ function enqueue_disable_animations_stylesheet() { - $custom_css = '* { animation-duration: 1ms !important; }'; + $custom_css = '* { animation-duration: 0ms !important; }'; wp_add_inline_style( 'wp-components', $custom_css ); } From 3317476e6d3d6cf3bb4c9634f377cede661a3aa0 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 8 Feb 2019 12:26:39 -0500 Subject: [PATCH 386/691] ESLint Plugin: Add rule dependency-group (#13757) * Framework: Normalize dependencies docblocks * ESLint Plugin: Add rule dependencies-docblocks * ESLint Plugin: Add fixer implementation for dependency-group --- packages/a11y/src/index.js | 9 +- packages/a11y/src/test/addContainer.test.js | 3 + packages/a11y/src/test/clear.test.js | 3 + packages/a11y/src/test/filterMessage.test.js | 3 + packages/a11y/src/test/index.test.js | 14 +- packages/blob/src/test/index.js | 3 + .../block-library/src/image/edit.native.js | 10 +- .../src/image/image-size.native.js | 4 +- packages/block-library/src/nextpage/index.js | 4 + .../src/paragraph/edit.native.js | 2 +- .../block-library/src/preformatted/index.js | 2 +- packages/block-library/src/pullquote/index.js | 3 + packages/block-library/src/spacer/index.js | 2 +- packages/block-library/src/verse/index.js | 2 +- .../test/index.js | 6 +- packages/blocks/src/api/raw-handling/utils.js | 2 +- packages/blocks/src/index.js | 5 + .../components/src/checkbox-control/index.js | 2 +- .../components/src/color-palette/index.js | 2 +- .../test/lib/token-field-wrapper.js | 6 +- packages/components/src/icon/index.js | 4 + packages/components/src/icon/test/index.js | 2 +- .../src/isolated-event-container/index.js | 2 +- packages/components/src/popover/index.js | 2 +- .../components/src/primitives/svg/index.js | 2 +- .../src/server-side-render/test/index.js | 3 + .../components/src/spinner/index.native.js | 3 + .../src/toolbar/toolbar-container.native.js | 3 + .../src/with-global-events/test/index.js | 2 +- packages/data/src/default-registry.js | 3 + packages/dom-ready/src/test/index.test.js | 3 + packages/e2e-test-utils/src/create-url.js | 3 + .../e2e-tests/specs/publish-button.test.js | 3 + .../e2e-tests/specs/publish-panel.test.js | 3 + .../src/components/block-mover/drag-handle.js | 2 +- .../default-block-appender/index.native.js | 3 + .../media-placeholder/index.native.js | 10 +- .../mobile/bottom-sheet/button.native.js | 4 +- .../mobile/bottom-sheet/cell.native.js | 4 +- .../post-publish-panel/maybe-tags-panel.js | 3 + packages/editor/src/editor-styles/index.js | 2 +- packages/editor/src/editor-styles/traverse.js | 6 +- .../src/hooks/test/generated-class-name.js | 2 +- packages/element/src/raw-html.js | 2 +- packages/eslint-plugin/CHANGELOG.md | 1 + packages/eslint-plugin/README.md | 3 +- packages/eslint-plugin/configs/custom.js | 1 + .../docs/rules/dependency-group.md | 36 ++++ .../rules/no-unused-vars-before-return.md | 4 +- .../rules/__tests__/dependencies-docblocks.js | 67 ++++++++ .../eslint-plugin/rules/dependency-group.js | 159 ++++++++++++++++++ packages/hooks/src/createAddHook.js | 3 + packages/hooks/src/createDidHook.js | 3 + packages/hooks/src/createHooks.js | 3 + packages/hooks/src/createRemoveHook.js | 3 + packages/hooks/src/index.js | 3 + packages/redux-routine/src/is-action.js | 2 +- packages/rich-text/src/index.js | 3 + packages/rich-text/src/is-empty.js | 3 + packages/rich-text/src/to-html-string.js | 2 +- packages/scripts/utils/test/index.js | 35 ++-- packages/shortcode/src/index.js | 2 +- packages/wordcount/src/index.js | 7 + test/integration/is-valid-block.spec.js | 2 +- test/unit/__mocks__/@wordpress/data.js | 5 + 65 files changed, 453 insertions(+), 57 deletions(-) create mode 100644 packages/eslint-plugin/docs/rules/dependency-group.md create mode 100644 packages/eslint-plugin/rules/__tests__/dependencies-docblocks.js create mode 100644 packages/eslint-plugin/rules/dependency-group.js diff --git a/packages/a11y/src/index.js b/packages/a11y/src/index.js index 7129b2957e20c..e082e15ca6fb7 100644 --- a/packages/a11y/src/index.js +++ b/packages/a11y/src/index.js @@ -1,6 +1,13 @@ +/** + * WordPress dependencies + */ +import domReady from '@wordpress/dom-ready'; + +/** + * Internal dependencies + */ import addContainer from './addContainer'; import clear from './clear'; -import domReady from '@wordpress/dom-ready'; import filterMessage from './filterMessage'; /** diff --git a/packages/a11y/src/test/addContainer.test.js b/packages/a11y/src/test/addContainer.test.js index 7b09cc630ba6d..e6c722078f74f 100644 --- a/packages/a11y/src/test/addContainer.test.js +++ b/packages/a11y/src/test/addContainer.test.js @@ -1,3 +1,6 @@ +/** + * Internal dependencies + */ import addContainer from '../addContainer'; describe( 'addContainer', () => { diff --git a/packages/a11y/src/test/clear.test.js b/packages/a11y/src/test/clear.test.js index c139f0d664ab5..02438dda1a4ec 100644 --- a/packages/a11y/src/test/clear.test.js +++ b/packages/a11y/src/test/clear.test.js @@ -1,3 +1,6 @@ +/** + * Internal dependencies + */ import clear from '../clear'; describe( 'clear', () => { diff --git a/packages/a11y/src/test/filterMessage.test.js b/packages/a11y/src/test/filterMessage.test.js index 3dcdf54e9b18d..95302abb8d1ce 100644 --- a/packages/a11y/src/test/filterMessage.test.js +++ b/packages/a11y/src/test/filterMessage.test.js @@ -1,3 +1,6 @@ +/** + * Internal dependencies + */ import filterMessage from '../filterMessage'; describe( 'filterMessage', () => { diff --git a/packages/a11y/src/test/index.test.js b/packages/a11y/src/test/index.test.js index 3737e4e444754..08c9489bed2ae 100644 --- a/packages/a11y/src/test/index.test.js +++ b/packages/a11y/src/test/index.test.js @@ -1,4 +1,14 @@ +/** + * WordPress dependencies + */ +import domReady from '@wordpress/dom-ready'; + +/** + * Internal dependencies + */ import { setup, speak } from '../'; +import clear from '../clear'; +import filterMessage from '../filterMessage'; jest.mock( '../clear', () => { return jest.fn(); @@ -14,10 +24,6 @@ jest.mock( '../filterMessage', () => { } ); } ); -import clear from '../clear'; -import domReady from '@wordpress/dom-ready'; -import filterMessage from '../filterMessage'; - describe( 'speak', () => { let containerPolite = document.getElementById( 'a11y-speak-polite' ); let containerAssertive = document.getElementById( 'a11y-speak-assertive' ); diff --git a/packages/blob/src/test/index.js b/packages/blob/src/test/index.js index 49adedc33e58c..7604e3956b6d2 100644 --- a/packages/blob/src/test/index.js +++ b/packages/blob/src/test/index.js @@ -1,3 +1,6 @@ +/** + * Internal dependencies + */ import { isBlobURL, } from '../'; diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index 822723ce141f6..1035a6391ce11 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -18,17 +18,21 @@ import { } from 'lodash'; /** - * Internal dependencies + * WordPress dependencies */ import { MediaPlaceholder, RichText, BlockControls, InspectorControls, BottomSheet } from '@wordpress/editor'; import { Toolbar, ToolbarButton, Spinner, Dashicon } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import ImageSize from './image-size'; import { isURL } from '@wordpress/url'; -import styles from './styles.scss'; import { compose } from '@wordpress/compose'; import { withSelect } from '@wordpress/data'; +/** + * Internal dependencies + */ +import ImageSize from './image-size'; +import styles from './styles.scss'; + const MEDIA_UPLOAD_STATE_UPLOADING = 1; const MEDIA_UPLOAD_STATE_SUCCEEDED = 2; const MEDIA_UPLOAD_STATE_FAILED = 3; diff --git a/packages/block-library/src/image/image-size.native.js b/packages/block-library/src/image/image-size.native.js index 8b29b9607e81e..a337a5d14548c 100644 --- a/packages/block-library/src/image/image-size.native.js +++ b/packages/block-library/src/image/image-size.native.js @@ -4,8 +4,8 @@ import { Component } from '@wordpress/element'; /** -* External dependencies -*/ + * External dependencies + */ import { View, Image } from 'react-native'; /** diff --git a/packages/block-library/src/nextpage/index.js b/packages/block-library/src/nextpage/index.js index 7386aceea805b..8ee142f82934f 100644 --- a/packages/block-library/src/nextpage/index.js +++ b/packages/block-library/src/nextpage/index.js @@ -5,6 +5,10 @@ import { __ } from '@wordpress/i18n'; import { RawHTML } from '@wordpress/element'; import { createBlock } from '@wordpress/blocks'; import { G, Path, SVG } from '@wordpress/components'; + +/** + * Internal dependencies + */ import edit from './edit'; export const name = 'core/nextpage'; diff --git a/packages/block-library/src/paragraph/edit.native.js b/packages/block-library/src/paragraph/edit.native.js index 85183973dddea..3ed4225f7b26d 100644 --- a/packages/block-library/src/paragraph/edit.native.js +++ b/packages/block-library/src/paragraph/edit.native.js @@ -12,7 +12,7 @@ import { parse, createBlock } from '@wordpress/blocks'; import { RichText } from '@wordpress/editor'; /** - * Import style + * Internal dependencies */ import styles from './style.scss'; diff --git a/packages/block-library/src/preformatted/index.js b/packages/block-library/src/preformatted/index.js index 6170275f629d9..f0ee53a942221 100644 --- a/packages/block-library/src/preformatted/index.js +++ b/packages/block-library/src/preformatted/index.js @@ -1,5 +1,5 @@ /** - * WordPress + * WordPress dependencies */ import { __ } from '@wordpress/i18n'; import { createBlock, getPhrasingContentSchema } from '@wordpress/blocks'; diff --git a/packages/block-library/src/pullquote/index.js b/packages/block-library/src/pullquote/index.js index 1dc6126394678..3bbcec5b88d86 100644 --- a/packages/block-library/src/pullquote/index.js +++ b/packages/block-library/src/pullquote/index.js @@ -18,6 +18,9 @@ import { } from '@wordpress/data'; import { Path, Polygon, SVG } from '@wordpress/components'; +/** + * Internal dependencies + */ import { default as edit, SOLID_COLOR_STYLE_NAME, diff --git a/packages/block-library/src/spacer/index.js b/packages/block-library/src/spacer/index.js index 12f362b40f623..d671a24b994c0 100644 --- a/packages/block-library/src/spacer/index.js +++ b/packages/block-library/src/spacer/index.js @@ -4,7 +4,7 @@ import classnames from 'classnames'; /** - * WordPress + * WordPress dependencies */ import { Fragment } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; diff --git a/packages/block-library/src/verse/index.js b/packages/block-library/src/verse/index.js index 0993bfa4f9c7e..17306b3284f00 100644 --- a/packages/block-library/src/verse/index.js +++ b/packages/block-library/src/verse/index.js @@ -1,5 +1,5 @@ /** - * WordPress + * WordPress dependencies */ import { __ } from '@wordpress/i18n'; import { Fragment } from '@wordpress/element'; diff --git a/packages/block-serialization-default-parser/test/index.js b/packages/block-serialization-default-parser/test/index.js index a8749c1c0f262..a3c67e280ef94 100644 --- a/packages/block-serialization-default-parser/test/index.js +++ b/packages/block-serialization-default-parser/test/index.js @@ -4,9 +4,13 @@ import path from 'path'; /** - * Internal dependencies + * WordPress dependencies */ import { jsTester, phpTester } from '@wordpress/block-serialization-spec-parser'; + +/** + * Internal dependencies + */ import { parse } from '../'; describe( 'block-serialization-default-parser-js', jsTester( parse ) ); diff --git a/packages/blocks/src/api/raw-handling/utils.js b/packages/blocks/src/api/raw-handling/utils.js index d917b681f9456..90e6f463ad465 100644 --- a/packages/blocks/src/api/raw-handling/utils.js +++ b/packages/blocks/src/api/raw-handling/utils.js @@ -7,11 +7,11 @@ import { mapValues, mergeWith, includes, noop } from 'lodash'; * WordPress dependencies */ import { unwrap, insertAfter, remove } from '@wordpress/dom'; -import { hasBlockSupport } from '..'; /** * Internal dependencies */ +import { hasBlockSupport } from '..'; import { isPhrasingContent } from './phrasing-content'; /** diff --git a/packages/blocks/src/index.js b/packages/blocks/src/index.js index bb7cde035780f..579665d14b853 100644 --- a/packages/blocks/src/index.js +++ b/packages/blocks/src/index.js @@ -7,6 +7,11 @@ // // Blocks are inferred from the HTML source of a post through a parsing mechanism // and then stored as objects in state, from which it is then rendered for editing. + +/** + * Internal dependencies + */ import './store'; + export * from './api'; export { withBlockContentContext } from './block-content-provider'; diff --git a/packages/components/src/checkbox-control/index.js b/packages/components/src/checkbox-control/index.js index 04d584cdff6e1..d84be2d56e24c 100644 --- a/packages/components/src/checkbox-control/index.js +++ b/packages/components/src/checkbox-control/index.js @@ -1,5 +1,5 @@ /** - * External dependencies + * WordPress dependencies */ import { withInstanceId } from '@wordpress/compose'; diff --git a/packages/components/src/color-palette/index.js b/packages/components/src/color-palette/index.js index c70db00206f49..99f76c99d64cf 100644 --- a/packages/components/src/color-palette/index.js +++ b/packages/components/src/color-palette/index.js @@ -8,7 +8,6 @@ import { map } from 'lodash'; * WordPress dependencies */ import { __, sprintf } from '@wordpress/i18n'; -import Dashicon from '../dashicon'; /** * Internal dependencies @@ -17,6 +16,7 @@ import Button from '../button'; import Dropdown from '../dropdown'; import Tooltip from '../tooltip'; import ColorPicker from '../color-picker'; +import Dashicon from '../dashicon'; export default function ColorPalette( { colors, disableCustomColors = false, value, onChange, className } ) { function applyOrUnset( color ) { diff --git a/packages/components/src/form-token-field/test/lib/token-field-wrapper.js b/packages/components/src/form-token-field/test/lib/token-field-wrapper.js index ff7bc86eb162f..15729d4404041 100644 --- a/packages/components/src/form-token-field/test/lib/token-field-wrapper.js +++ b/packages/components/src/form-token-field/test/lib/token-field-wrapper.js @@ -1,8 +1,12 @@ +/** + * External dependencies + */ +import { unescape } from 'lodash'; + /** * WordPress dependencies */ import { Component } from '@wordpress/element'; -import { unescape } from 'lodash'; /** * Internal dependencies diff --git a/packages/components/src/icon/index.js b/packages/components/src/icon/index.js index fbb2c1bc49df7..f106f7c561511 100644 --- a/packages/components/src/icon/index.js +++ b/packages/components/src/icon/index.js @@ -2,6 +2,10 @@ * WordPress dependencies */ import { cloneElement, createElement, Component, isValidElement } from '@wordpress/element'; + +/** + * Internal dependencies + */ import { Dashicon, SVG } from '../'; function Icon( { icon = null, size, className } ) { diff --git a/packages/components/src/icon/test/index.js b/packages/components/src/icon/test/index.js index a7fea71cb9fac..ec632ef74677b 100644 --- a/packages/components/src/icon/test/index.js +++ b/packages/components/src/icon/test/index.js @@ -7,12 +7,12 @@ import { shallow } from 'enzyme'; * WordPress dependencies */ import { Component } from '@wordpress/element'; -import { Path, SVG } from '../../'; /** * Internal dependencies */ import Icon from '../'; +import { Path, SVG } from '../../'; describe( 'Icon', () => { const className = 'example-class'; diff --git a/packages/components/src/isolated-event-container/index.js b/packages/components/src/isolated-event-container/index.js index 10f26bceb89ed..282e6dc5c5b7a 100644 --- a/packages/components/src/isolated-event-container/index.js +++ b/packages/components/src/isolated-event-container/index.js @@ -1,5 +1,5 @@ /** - * External dependencies + * WordPress dependencies */ import { Component } from '@wordpress/element'; diff --git a/packages/components/src/popover/index.js b/packages/components/src/popover/index.js index 052e53189ba84..47212a3e12f9c 100644 --- a/packages/components/src/popover/index.js +++ b/packages/components/src/popover/index.js @@ -2,7 +2,6 @@ * External dependencies */ import classnames from 'classnames'; -import isShallowEqual from '@wordpress/is-shallow-equal'; /** * WordPress dependencies @@ -10,6 +9,7 @@ import isShallowEqual from '@wordpress/is-shallow-equal'; import { Component, createRef } from '@wordpress/element'; import { focus } from '@wordpress/dom'; import { ESCAPE } from '@wordpress/keycodes'; +import isShallowEqual from '@wordpress/is-shallow-equal'; /** * Internal dependencies diff --git a/packages/components/src/primitives/svg/index.js b/packages/components/src/primitives/svg/index.js index 8b54a777f053b..abb15e121dc2b 100644 --- a/packages/components/src/primitives/svg/index.js +++ b/packages/components/src/primitives/svg/index.js @@ -1,5 +1,5 @@ /** - * External dependencies + * WordPress dependencies */ import { createElement } from '@wordpress/element'; diff --git a/packages/components/src/server-side-render/test/index.js b/packages/components/src/server-side-render/test/index.js index b276987fcafec..c09814771f7a7 100644 --- a/packages/components/src/server-side-render/test/index.js +++ b/packages/components/src/server-side-render/test/index.js @@ -1,3 +1,6 @@ +/** + * Internal dependencies + */ import { rendererPath } from '../index'; describe( 'rendererPath', function() { diff --git a/packages/components/src/spinner/index.native.js b/packages/components/src/spinner/index.native.js index 305019bc8b40d..0c7e9902ffe39 100644 --- a/packages/components/src/spinner/index.native.js +++ b/packages/components/src/spinner/index.native.js @@ -1,3 +1,6 @@ +/** + * External dependencies + */ import { View } from 'react-native'; export default function Spinner( props ) { diff --git a/packages/components/src/toolbar/toolbar-container.native.js b/packages/components/src/toolbar/toolbar-container.native.js index d147c547add6d..887991d2ea123 100644 --- a/packages/components/src/toolbar/toolbar-container.native.js +++ b/packages/components/src/toolbar/toolbar-container.native.js @@ -3,6 +3,9 @@ */ import { View } from 'react-native'; +/** + * Internal dependencies + */ import styles from './style.scss'; const ToolbarContainer = ( props ) => ( diff --git a/packages/compose/src/with-global-events/test/index.js b/packages/compose/src/with-global-events/test/index.js index 31c89bffd77be..4001f219ad594 100644 --- a/packages/compose/src/with-global-events/test/index.js +++ b/packages/compose/src/with-global-events/test/index.js @@ -4,7 +4,7 @@ import TestRenderer from 'react-test-renderer'; /** - * External dependencies + * WordPress dependencies */ import { Component } from '@wordpress/element'; diff --git a/packages/data/src/default-registry.js b/packages/data/src/default-registry.js index f593e59530c31..eabb5ba272f37 100644 --- a/packages/data/src/default-registry.js +++ b/packages/data/src/default-registry.js @@ -1,3 +1,6 @@ +/** + * Internal dependencies + */ import { createRegistry } from './registry'; export default createRegistry(); diff --git a/packages/dom-ready/src/test/index.test.js b/packages/dom-ready/src/test/index.test.js index 63a0d6f4b3ae4..36a5bbbc64056 100644 --- a/packages/dom-ready/src/test/index.test.js +++ b/packages/dom-ready/src/test/index.test.js @@ -1,3 +1,6 @@ +/** + * Internal dependencies + */ import domReady from '../'; describe( 'domReady', () => { diff --git a/packages/e2e-test-utils/src/create-url.js b/packages/e2e-test-utils/src/create-url.js index ff7271c8ebf8f..14437e0476666 100644 --- a/packages/e2e-test-utils/src/create-url.js +++ b/packages/e2e-test-utils/src/create-url.js @@ -4,6 +4,9 @@ import { join } from 'path'; import { URL } from 'url'; +/** + * Internal dependencies + */ import { WP_BASE_URL } from './shared/config'; /** diff --git a/packages/e2e-tests/specs/publish-button.test.js b/packages/e2e-tests/specs/publish-button.test.js index da5cf1d616124..ecdd6bf76b000 100644 --- a/packages/e2e-tests/specs/publish-button.test.js +++ b/packages/e2e-tests/specs/publish-button.test.js @@ -1,3 +1,6 @@ +/** + * WordPress dependencies + */ import { arePrePublishChecksEnabled, disablePrePublishChecks, diff --git a/packages/e2e-tests/specs/publish-panel.test.js b/packages/e2e-tests/specs/publish-panel.test.js index 026c3d8ee35fd..c28a3837d7443 100644 --- a/packages/e2e-tests/specs/publish-panel.test.js +++ b/packages/e2e-tests/specs/publish-panel.test.js @@ -1,3 +1,6 @@ +/** + * WordPress dependencies + */ import { arePrePublishChecksEnabled, disablePrePublishChecks, diff --git a/packages/editor/src/components/block-mover/drag-handle.js b/packages/editor/src/components/block-mover/drag-handle.js index 560a0f50bc078..005487e11c5c7 100644 --- a/packages/editor/src/components/block-mover/drag-handle.js +++ b/packages/editor/src/components/block-mover/drag-handle.js @@ -4,7 +4,7 @@ import classnames from 'classnames'; /** - * WordPress dependencies + * Internal dependencies */ import BlockDraggable from '../block-draggable'; diff --git a/packages/editor/src/components/default-block-appender/index.native.js b/packages/editor/src/components/default-block-appender/index.native.js index 436ecc5772b00..22563f75a086f 100644 --- a/packages/editor/src/components/default-block-appender/index.native.js +++ b/packages/editor/src/components/default-block-appender/index.native.js @@ -11,6 +11,9 @@ import { compose } from '@wordpress/compose'; import { decodeEntities } from '@wordpress/html-entities'; import { withSelect, withDispatch } from '@wordpress/data'; +/** + * Internal dependencies + */ import styles from './style.scss'; export function DefaultBlockAppender( { diff --git a/packages/editor/src/components/media-placeholder/index.native.js b/packages/editor/src/components/media-placeholder/index.native.js index 0b70472c2407f..0c2a234ad9798 100644 --- a/packages/editor/src/components/media-placeholder/index.native.js +++ b/packages/editor/src/components/media-placeholder/index.native.js @@ -3,10 +3,16 @@ */ import { View, Text, Button } from 'react-native'; -import styles from './styles.scss'; - +/** + * WordPress dependencies + */ import { __ } from '@wordpress/i18n'; +/** + * Internal dependencies + */ +import styles from './styles.scss'; + function MediaPlaceholder( props ) { return ( <View style={ styles.emptyStateContainer }> diff --git a/packages/editor/src/components/mobile/bottom-sheet/button.native.js b/packages/editor/src/components/mobile/bottom-sheet/button.native.js index 439e056b93187..c8011f80511f8 100644 --- a/packages/editor/src/components/mobile/bottom-sheet/button.native.js +++ b/packages/editor/src/components/mobile/bottom-sheet/button.native.js @@ -1,6 +1,6 @@ /** -* External dependencies -*/ + * External dependencies + */ import { TouchableOpacity, View, Text } from 'react-native'; /** diff --git a/packages/editor/src/components/mobile/bottom-sheet/cell.native.js b/packages/editor/src/components/mobile/bottom-sheet/cell.native.js index 89970e69b4598..6e01487d388f6 100644 --- a/packages/editor/src/components/mobile/bottom-sheet/cell.native.js +++ b/packages/editor/src/components/mobile/bottom-sheet/cell.native.js @@ -1,6 +1,6 @@ /** -* External dependencies -*/ + * External dependencies + */ import { TouchableOpacity, Text, View, TextInput } from 'react-native'; /** diff --git a/packages/editor/src/components/post-publish-panel/maybe-tags-panel.js b/packages/editor/src/components/post-publish-panel/maybe-tags-panel.js index f2f53c78c26cb..69d600fcae9e4 100644 --- a/packages/editor/src/components/post-publish-panel/maybe-tags-panel.js +++ b/packages/editor/src/components/post-publish-panel/maybe-tags-panel.js @@ -12,6 +12,9 @@ import { compose, ifCondition } from '@wordpress/compose'; import { withSelect } from '@wordpress/data'; import { PanelBody } from '@wordpress/components'; +/** + * Internal dependencies + */ import FlatTermSelector from '../post-taxonomies/flat-term-selector'; const TagsPanel = () => { diff --git a/packages/editor/src/editor-styles/index.js b/packages/editor/src/editor-styles/index.js index c5de582ce074c..5d98c856ed116 100644 --- a/packages/editor/src/editor-styles/index.js +++ b/packages/editor/src/editor-styles/index.js @@ -9,7 +9,7 @@ import { map } from 'lodash'; import { compose } from '@wordpress/compose'; /** - * External dependencies + * Internal dependencies */ import traverse from './traverse'; import urlRewrite from './transforms/url-rewrite'; diff --git a/packages/editor/src/editor-styles/traverse.js b/packages/editor/src/editor-styles/traverse.js index 43b18251bd7df..6a0cc298697ee 100644 --- a/packages/editor/src/editor-styles/traverse.js +++ b/packages/editor/src/editor-styles/traverse.js @@ -1,9 +1,13 @@ /** * External dependencies */ -import { parse, stringify } from './ast'; import traverse from 'traverse'; +/** + * Internal dependencies + */ +import { parse, stringify } from './ast'; + function traverseCSS( css, callback ) { try { const parsed = parse( css ); diff --git a/packages/editor/src/hooks/test/generated-class-name.js b/packages/editor/src/hooks/test/generated-class-name.js index 509b06e61d06b..397ec9ab333ce 100644 --- a/packages/editor/src/hooks/test/generated-class-name.js +++ b/packages/editor/src/hooks/test/generated-class-name.js @@ -4,7 +4,7 @@ import { noop } from 'lodash'; /** - * External dependencies + * WordPress dependencies */ import { applyFilters } from '@wordpress/hooks'; diff --git a/packages/element/src/raw-html.js b/packages/element/src/raw-html.js index 8a604e54e900c..d985c9376d4a2 100644 --- a/packages/element/src/raw-html.js +++ b/packages/element/src/raw-html.js @@ -1,5 +1,5 @@ /** - * External dependencies + * Internal dependencies */ import { createElement } from './react'; diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 775abcac66305..9562f47730d07 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -7,6 +7,7 @@ ### New Features - New Rule: [`@wordpress/no-unused-vars-before-return`](https://github.com/WordPress/gutenberg/blob/master/packages/eslint-plugin/docs/rules/no-unused-vars-before-return.md) +- New Rule: [`@wordpress/dependency-group`](https://github.com/WordPress/gutenberg/blob/master/packages/eslint-plugin/docs/rules/dependency-group.md) ## 1.0.0 (2018-12-12) diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 1f001411b7319..5e21e8fc99463 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -49,7 +49,8 @@ The granular rulesets will not define any environment globals. As such, if they Rule|Description ---|--- -[no-unused-vars-before-return](docs/rules/no-unused-vars-before-return.md)|Disallow assigning variable values if unused before a return +[no-unused-vars-before-return](/packages/eslint-plugin/docs/rules/no-unused-vars-before-return.md)|Disallow assigning variable values if unused before a return +[dependency-group](/packages/eslint-plugin/docs/rules/dependency-group.md)|Enforce dependencies docblocks formatting ### Legacy diff --git a/packages/eslint-plugin/configs/custom.js b/packages/eslint-plugin/configs/custom.js index 5691c3c5d0c2b..9eb42283099b0 100644 --- a/packages/eslint-plugin/configs/custom.js +++ b/packages/eslint-plugin/configs/custom.js @@ -3,6 +3,7 @@ module.exports = { '@wordpress', ], rules: { + '@wordpress/dependency-group': 'error', '@wordpress/no-unused-vars-before-return': 'error', 'no-restricted-syntax': [ 'error', diff --git a/packages/eslint-plugin/docs/rules/dependency-group.md b/packages/eslint-plugin/docs/rules/dependency-group.md new file mode 100644 index 0000000000000..02c1c4ccb95fe --- /dev/null +++ b/packages/eslint-plugin/docs/rules/dependency-group.md @@ -0,0 +1,36 @@ +# Enforce dependencies docblocks formatting (dependency-group) + +Ensures that all top-level package imports adhere to the dependencies grouping conventions as outlined in the [Coding Guidelines](https://github.com/WordPress/gutenberg/blob/master/docs/contributors/coding-guidelines.md#imports). + +Specifically, this ensures that: + +- An import is preceded by "External dependencies", "WordPress dependencies", or "Internal dependencies" as appropriate by the import source. + +## Rule details + +Examples of **incorrect** code for this rule: + +```js +import { get } from 'lodash'; +import { Component } from '@wordpress/element'; +import edit from './edit'; +``` + +Examples of **correct** code for this rule: + +```js +/* + * External dependencies + */ +import { get } from 'lodash'; + +/* + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; + +/* + * Internal dependencies + */ +import edit from './edit'; +``` diff --git a/packages/eslint-plugin/docs/rules/no-unused-vars-before-return.md b/packages/eslint-plugin/docs/rules/no-unused-vars-before-return.md index 7dba90d405973..357d0d948ff16 100644 --- a/packages/eslint-plugin/docs/rules/no-unused-vars-before-return.md +++ b/packages/eslint-plugin/docs/rules/no-unused-vars-before-return.md @@ -4,7 +4,7 @@ To avoid wastefully computing the result of a function call when assigning a var ## Rule details -The following patterns are considered warnings: +Examples of **incorrect** code for this rule: ```js function example( number ) { @@ -17,7 +17,7 @@ function example( number ) { } ``` -The following patterns are not considered warnings: +Examples of **correct** code for this rule: ```js function example( number ) { diff --git a/packages/eslint-plugin/rules/__tests__/dependencies-docblocks.js b/packages/eslint-plugin/rules/__tests__/dependencies-docblocks.js new file mode 100644 index 0000000000000..74e41626bfcd8 --- /dev/null +++ b/packages/eslint-plugin/rules/__tests__/dependencies-docblocks.js @@ -0,0 +1,67 @@ +/** + * External dependencies + */ +import { RuleTester } from 'eslint'; + +/** + * Internal dependencies + */ +import rule from '../dependency-group'; + +const ruleTester = new RuleTester( { + parserOptions: { + sourceType: 'module', + ecmaVersion: 6, + }, +} ); + +ruleTester.run( 'dependency-group', rule, { + valid: [ + { + code: ` +/* + * External dependencies + */ +import { get } from 'lodash'; +import classnames from 'classnames'; + +/* + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; + +/* + * Internal dependencies + */ +import edit from './edit';`, + }, + ], + invalid: [ + { + code: ` +import { get } from 'lodash'; +import classnames from 'classnames'; +import { Component } from '@wordpress/element'; +import edit from './edit';`, + errors: [ + { message: 'Expected preceding "External dependencies" comment block' }, + { message: 'Expected preceding "WordPress dependencies" comment block' }, + { message: 'Expected preceding "Internal dependencies" comment block' }, + ], + output: ` +/** + * External dependencies + */ +import { get } from 'lodash'; +import classnames from 'classnames'; +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; +/** + * Internal dependencies + */ +import edit from './edit';`, + }, + ], +} ); diff --git a/packages/eslint-plugin/rules/dependency-group.js b/packages/eslint-plugin/rules/dependency-group.js new file mode 100644 index 0000000000000..c92345a905cb7 --- /dev/null +++ b/packages/eslint-plugin/rules/dependency-group.js @@ -0,0 +1,159 @@ +module.exports = { + meta: { + type: 'layout', + docs: { + description: 'Enforce dependencies docblocks formatting', + url: 'https://github.com/WordPress/gutenberg/blob/master/packages/eslint-plugin/docs/rules/dependency-group.md', + }, + schema: [], + fixable: true, + }, + create( context ) { + const comments = context.getSourceCode().getAllComments(); + + /** + * Locality classification of an import, one of "External", + * "WordPress", "Internal". + * + * @typedef {string} WPPackageLocality + */ + + /** + * Given an import source string, returns the locality classification + * of the import sort. + * + * @param {string} source Import source string. + * + * @return {WPPackageLocality} Package locality. + */ + function getPackageLocality( source ) { + if ( source.startsWith( '.' ) ) { + return 'Internal'; + } else if ( source.startsWith( '@wordpress/' ) ) { + return 'WordPress'; + } + + return 'External'; + } + + /** + * Returns true if the given comment node satisfies a desired locality, + * or false otherwise. + * + * @param {espree.Node} node Comment node to check. + * @param {WPPackageLocality} locality Desired package locality. + * + * @return {boolean} Whether comment node satisfies locality. + */ + function isLocalityDependencyBlock( node, locality ) { + const { type, value } = node; + if ( type !== 'Block' ) { + return false; + } + + // (Temporary) Tolerances: + // - Normalize `/**` and `/*` + // - Case insensitive "Dependencies" vs. "dependencies" + // - Ending period + // - "Node" dependencies as an alias for External + + if ( locality === 'External' ) { + locality = '(External|Node)'; + } + + const pattern = new RegExp( `^\\*?\\n \\* ${ locality } [dD]ependencies\\.?\\n $` ); + return pattern.test( value ); + } + + /** + * Returns true if the given node occurs prior in code to a reference, + * or false otherwise. + * + * @param {espree.Node} node Node to test being before reference. + * @param {espree.Node} reference Node against which to compare. + * + * @return {boolean} Whether node occurs before reference. + */ + function isBefore( node, reference ) { + return node.start < reference.start; + } + + /** + * Returns true if a given node is preceded by a comment block + * satisfying a locality requirement, or false otherwise. + * + * @param {espree.Node} node Node to test. + * @param {WPPackageLocality} locality Desired package locality. + * + * @return {boolean} Whether node preceded by locality comment block. + */ + function isPrecededByDependencyBlock( node, locality ) { + return comments.some( ( comment ) => { + return isLocalityDependencyBlock( comment, locality ) && isBefore( comment, node ); + } ); + } + + return { + Program( node ) { + /** + * The set of package localities which have been reported for + * the current program. Each locality is reported at most one + * time, since otherwise the fixer would insert a comment + * block for each individual import statement. + * + * @type {Set<WPPackageLocality>} + */ + const reported = new Set(); + + // Since we only care to enforce imports which occur at the + // top-level scope, match on Program and test its children, + // rather than matching the import nodes directly. + node.body.forEach( ( child ) => { + let source; + switch ( child.type ) { + case 'ImportDeclaration': + source = child.source.value; + break; + + case 'CallExpression': + const { callee, arguments: args } = child; + if ( + callee.name === 'require' && + args.length === 1 && + args[ 0 ].type === 'Literal' && + typeof args[ 0 ].value === 'string' + ) { + source = args[ 0 ].value; + } + break; + } + + if ( ! source ) { + return; + } + + const locality = getPackageLocality( source ); + if ( + reported.has( locality ) || + isPrecededByDependencyBlock( child, locality ) + ) { + return; + } + + reported.add( locality ); + + context.report( { + node: child, + message: `Expected preceding "${ locality } dependencies" comment block`, + fix( fixer ) { + return fixer.insertTextBefore( + child, + `/**\n * ${ locality } dependencies\n */\n` + ); + }, + } ); + } ); + }, + }; + }, +}; diff --git a/packages/hooks/src/createAddHook.js b/packages/hooks/src/createAddHook.js index 81b576d9d14e3..79b3d6f0b91d3 100644 --- a/packages/hooks/src/createAddHook.js +++ b/packages/hooks/src/createAddHook.js @@ -1,3 +1,6 @@ +/** + * Internal dependencies + */ import validateNamespace from './validateNamespace.js'; import validateHookName from './validateHookName.js'; import { doAction } from './'; diff --git a/packages/hooks/src/createDidHook.js b/packages/hooks/src/createDidHook.js index 4aac41d4033c0..b5cf3cf687195 100644 --- a/packages/hooks/src/createDidHook.js +++ b/packages/hooks/src/createDidHook.js @@ -1,3 +1,6 @@ +/** + * Internal dependencies + */ import validateHookName from './validateHookName.js'; /** diff --git a/packages/hooks/src/createHooks.js b/packages/hooks/src/createHooks.js index 9915a1167e31f..d61effc36a4ac 100644 --- a/packages/hooks/src/createHooks.js +++ b/packages/hooks/src/createHooks.js @@ -1,3 +1,6 @@ +/** + * Internal dependencies + */ import createAddHook from './createAddHook'; import createRemoveHook from './createRemoveHook'; import createHasHook from './createHasHook'; diff --git a/packages/hooks/src/createRemoveHook.js b/packages/hooks/src/createRemoveHook.js index d9a99468f7a5f..bbca6a6188e56 100644 --- a/packages/hooks/src/createRemoveHook.js +++ b/packages/hooks/src/createRemoveHook.js @@ -1,3 +1,6 @@ +/** + * Internal dependencies + */ import validateNamespace from './validateNamespace.js'; import validateHookName from './validateHookName.js'; import { doAction } from './'; diff --git a/packages/hooks/src/index.js b/packages/hooks/src/index.js index 508e27fc0c140..78a68df809086 100644 --- a/packages/hooks/src/index.js +++ b/packages/hooks/src/index.js @@ -1,3 +1,6 @@ +/** + * Internal dependencies + */ import createHooks from './createHooks'; const { diff --git a/packages/redux-routine/src/is-action.js b/packages/redux-routine/src/is-action.js index 383e2c55f74e2..152119d001fe2 100644 --- a/packages/redux-routine/src/is-action.js +++ b/packages/redux-routine/src/is-action.js @@ -1,5 +1,5 @@ /** - * External imports + * External dependencies */ import { isPlainObject, isString } from 'lodash'; diff --git a/packages/rich-text/src/index.js b/packages/rich-text/src/index.js index 86f7cd1b29ae1..7ce8ed970e6eb 100644 --- a/packages/rich-text/src/index.js +++ b/packages/rich-text/src/index.js @@ -1,3 +1,6 @@ +/** + * Internal dependencies + */ import './store'; export { applyFormat } from './apply-format'; diff --git a/packages/rich-text/src/is-empty.js b/packages/rich-text/src/is-empty.js index f7078eb3440aa..aa7bad7407615 100644 --- a/packages/rich-text/src/is-empty.js +++ b/packages/rich-text/src/is-empty.js @@ -1,3 +1,6 @@ +/** + * Internal dependencies + */ import { LINE_SEPARATOR } from './special-characters'; /** diff --git a/packages/rich-text/src/to-html-string.js b/packages/rich-text/src/to-html-string.js index 7b6d2edfac2c3..324dd2d65d287 100644 --- a/packages/rich-text/src/to-html-string.js +++ b/packages/rich-text/src/to-html-string.js @@ -1,5 +1,5 @@ /** - * Internal dependencies + * WordPress dependencies */ import { diff --git a/packages/scripts/utils/test/index.js b/packages/scripts/utils/test/index.js index 47e1598667a7d..d451a7540411d 100644 --- a/packages/scripts/utils/test/index.js +++ b/packages/scripts/utils/test/index.js @@ -1,22 +1,11 @@ -jest.mock( '../package', () => { - const module = require.requireActual( '../package' ); - - jest.spyOn( module, 'getPackagePath' ); - - return module; -} ); -jest.mock( '../process', () => { - const module = require.requireActual( '../process' ); - - jest.spyOn( module, 'exit' ); - jest.spyOn( module, 'getCliArgs' ); +/** + * External dependencies + */ +import crossSpawn from 'cross-spawn'; - return module; -} ); /** * Internal dependencies */ -import crossSpawn from 'cross-spawn'; import { hasCliArg, hasProjectFile, @@ -30,6 +19,22 @@ import { getCliArgs as getCliArgsMock, } from '../process'; +jest.mock( '../package', () => { + const module = require.requireActual( '../package' ); + + jest.spyOn( module, 'getPackagePath' ); + + return module; +} ); +jest.mock( '../process', () => { + const module = require.requireActual( '../process' ); + + jest.spyOn( module, 'exit' ); + jest.spyOn( module, 'getCliArgs' ); + + return module; +} ); + describe( 'utils', () => { const crossSpawnMock = jest.spyOn( crossSpawn, 'sync' ); diff --git a/packages/shortcode/src/index.js b/packages/shortcode/src/index.js index 56d97f855afd4..d9a3de767d97a 100644 --- a/packages/shortcode/src/index.js +++ b/packages/shortcode/src/index.js @@ -1,5 +1,5 @@ /** - * Internal dependencies + * External dependencies */ import { extend, pick, isString, isEqual, forEach, isNumber } from 'lodash'; import memize from 'memize'; diff --git a/packages/wordcount/src/index.js b/packages/wordcount/src/index.js index a20fefefea630..8d70df55f0a27 100644 --- a/packages/wordcount/src/index.js +++ b/packages/wordcount/src/index.js @@ -1,4 +1,11 @@ +/** + * External dependencies + */ import { extend, flow } from 'lodash'; + +/** + * Internal dependencies + */ import { defaultSettings } from './defaultSettings'; import stripTags from './stripTags'; import transposeAstralsToCountableChar from './transposeAstralsToCountableChar'; diff --git a/test/integration/is-valid-block.spec.js b/test/integration/is-valid-block.spec.js index ac969f1ecb72c..f10ef5f34d09f 100644 --- a/test/integration/is-valid-block.spec.js +++ b/test/integration/is-valid-block.spec.js @@ -1,5 +1,5 @@ /** - * External dependencies + * WordPress dependencies */ import { isValidBlockContent } from '@wordpress/blocks'; import { createElement } from '@wordpress/element'; diff --git a/test/unit/__mocks__/@wordpress/data.js b/test/unit/__mocks__/@wordpress/data.js index 1de2900bc8b20..729e53648b211 100644 --- a/test/unit/__mocks__/@wordpress/data.js +++ b/test/unit/__mocks__/@wordpress/data.js @@ -1,3 +1,8 @@ +/** + * Internal dependencies + */ import { use, plugins } from '../../../../packages/data/src'; + use( plugins.controls ); + export * from '../../../../packages/data/src'; From 3148d8cc8412f6c2829390b569d14973b0b525f4 Mon Sep 17 00:00:00 2001 From: Marty Helmick <info@martyhelmick.com> Date: Fri, 8 Feb 2019 14:40:18 -0500 Subject: [PATCH 387/691] Make p tag "go with the flow". (#12998) This commit sets the `p` tag to inherit whatever `font-size` and `line-height` are set on the `body` i.e., `.editor-styles-wrapper`. Currently the `p` tag here overrides a theme's most basic of font styles unless `font-size` and `line-height` are set on `p` in that theme. And from what I've seen, being that specific on `p` is an uncommon pattern. This also makes it easier and requires less specificity to style paragraphs on a per block basis by simply adding the font-size to the block container. --- packages/editor/src/editor-styles.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/editor/src/editor-styles.scss b/packages/editor/src/editor-styles.scss index 6715bd4d0c987..d005d0bf844fa 100644 --- a/packages/editor/src/editor-styles.scss +++ b/packages/editor/src/editor-styles.scss @@ -6,8 +6,8 @@ body { } p { - font-size: $editor-font-size; - line-height: $editor-line-height; + font-size: inherit; + line-height: inherit; } ul, From 1a7354c4e6c0387ac5bc0b60eb86c465dd03cac6 Mon Sep 17 00:00:00 2001 From: Antonio Villegas <lotendil@gmail.com> Date: Fri, 8 Feb 2019 20:41:26 +0100 Subject: [PATCH 388/691] Remove stroke from CSS rules that colorize plugin icons (#13719) * Remove stroke from CSS rules that colorize plugin icons * Restored stroke values and added stroke-width property --- .../edit-post/src/components/header/pinned-plugins/style.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/edit-post/src/components/header/pinned-plugins/style.scss b/packages/edit-post/src/components/header/pinned-plugins/style.scss index cfa4c61a34707..b01dd0480d5ee 100644 --- a/packages/edit-post/src/components/header/pinned-plugins/style.scss +++ b/packages/edit-post/src/components/header/pinned-plugins/style.scss @@ -14,6 +14,7 @@ .components-icon-button:not(.is-toggled) svg * { stroke: $dark-gray-500; fill: $dark-gray-500; + stroke-width: 0; // !important is omitted here, so stroke-only icons can override easily. } // Forcefully colorize hover and toggled plugin icon states to ensure legibility and consistency. @@ -21,11 +22,13 @@ .components-icon-button.is-toggled svg * { stroke: $white !important; fill: $white !important; + stroke-width: 0; // !important is omitted here, so stroke-only icons can override easily. } .components-icon-button:hover svg, .components-icon-button:hover svg * { stroke: $dark-gray-900 !important; fill: $dark-gray-900 !important; + stroke-width: 0; // !important is omitted here, so stroke-only icons can override easily. } } From b5a1e0f8b700e19243f691d9837dc1947496bd54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20Van=C2=A0Durpe?= <iseulde@automattic.com> Date: Fri, 8 Feb 2019 20:54:19 +0100 Subject: [PATCH 389/691] RichText: Format boundaries without DOM (#13697) * Avoid flickering by setting boundary class when creating the DOM tree * Remove TinyMCE boundaries * Remove TinyMCE so it doesn't interfere with our boundaries * Fix failing unit tests * Only have one boundary class at a time * Remove filterString param * Build in own padding and boundary classes * Update styling * valueToFormat shouldn't create editable tree * Add zero with spaces around formatting * Remove old boundary compatibility code * Update dom diff, prevent browser selection overwrite on boundary * Update UI * Fix inserting format with shortcut * Add check * Remove nbsp inserted by TinyMCE from test * Do not mutate attirbutes object * Fix link e2e tests * WIP * WIP: try boundaries without DOM * Remove zero width space everywhere * Fix e2e tests * Clean up * Revert selection snapshots * Correctly remove dependencies * Add nested boundary test * Add backtick test * Fix annotation tests: they now have boundaries * Merge logic * Fix getActiveFormat test * Update some docs and dependencies * Fix block CSS * Return focus to editable after pressing format button * Update docs * Update changelogs * Add changelog headings * Remove obsolete onFocus class method from Editable * Restore :focus * Update TinyMCE mentions * Use Escape instead of mouse move * Update isHorizontalEdge comment * Add docs for fromFormat * Create test content manually --- ...roducing-attributes-and-editable-fields.md | 8 +- docs/designers-developers/faq.md | 4 +- docs/designers-developers/glossary.md | 2 +- lib/packages-dependencies.php | 2 - package-lock.json | 6 - packages/block-library/src/button/editor.scss | 8 +- .../button/test/__snapshots__/index.js.snap | 6 +- packages/block-library/src/cover/editor.scss | 9 +- .../block-library/src/gallery/editor.scss | 6 +- .../block-library/src/heading/editor.scss | 8 +- .../heading/test/__snapshots__/index.js.snap | 6 +- packages/block-library/src/list/editor.scss | 5 +- .../src/list/test/__snapshots__/index.js.snap | 6 +- .../block-library/src/paragraph/editor.scss | 2 +- .../test/__snapshots__/index.js.snap | 6 +- .../test/__snapshots__/index.js.snap | 6 +- .../block-library/src/pullquote/editor.scss | 13 - .../test/__snapshots__/index.js.snap | 6 +- .../quote/test/__snapshots__/index.js.snap | 6 +- .../src/text-columns/editor.scss | 2 +- .../test/__snapshots__/index.js.snap | 12 +- .../verse/test/__snapshots__/index.js.snap | 6 +- packages/dom/CHANGELOG.md | 6 + packages/dom/src/dom.js | 19 +- .../src/get-edited-post-content.js | 19 +- .../__snapshots__/rich-text.test.js.snap | 12 + .../specs/__snapshots__/undo.test.js.snap | 2 +- .../__snapshots__/writing-flow.test.js.snap | 20 +- packages/e2e-tests/specs/links.test.js | 4 +- .../e2e-tests/specs/navigable-toolbar.test.js | 2 +- .../specs/plugins/annotations.test.js | 7 +- packages/e2e-tests/specs/rich-text.test.js | 23 +- .../e2e-tests/specs/splitting-merging.test.js | 2 +- packages/e2e-tests/specs/writing-flow.test.js | 53 ++- packages/editor/CHANGELOG.md | 4 +- packages/editor/package.json | 1 - .../src/components/rich-text/editable.js | 193 +++++++++ .../editor/src/components/rich-text/index.js | 188 +++++++-- .../src/components/rich-text/style.scss | 51 +-- .../src/components/rich-text/tinymce.js | 381 ------------------ .../src/components/writing-flow/index.js | 9 +- packages/rich-text/CHANGELOG.md | 10 + packages/rich-text/README.md | 7 +- packages/rich-text/src/apply-format.js | 5 +- packages/rich-text/src/create.js | 122 ++---- packages/rich-text/src/get-active-format.js | 14 +- packages/rich-text/src/get-active-formats.js | 23 ++ packages/rich-text/src/special-characters.js | 1 - .../src/test/__snapshots__/to-dom.js.snap | 127 +++--- packages/rich-text/src/test/apply-format.js | 5 +- packages/rich-text/src/test/create.js | 2 - .../rich-text/src/test/get-active-format.js | 15 +- packages/rich-text/src/test/helpers/index.js | 161 +------- packages/rich-text/src/test/to-dom.js | 1 - packages/rich-text/src/to-dom.js | 108 ++--- packages/rich-text/src/to-tree.js | 87 ++-- 56 files changed, 793 insertions(+), 1026 deletions(-) create mode 100644 packages/editor/src/components/rich-text/editable.js delete mode 100644 packages/editor/src/components/rich-text/tinymce.js create mode 100644 packages/rich-text/src/get-active-formats.js diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md b/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md index 7fa730c73abce..c51bd505bdd2e 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md @@ -118,7 +118,7 @@ In the code snippet above, when loading the editor, we will extract the `content Earlier examples used the `createElement` function to create DOM nodes, but it's also possible to encapsulate this behavior into ["components"](). This abstraction helps as a pattern to share common behaviors and to hide complexity into self-contained units. There are a number of components available to use in implementing your blocks. You can see one such component in the snippet above: the [`RichText` component](). -The `RichText` component can be considered as a super-powered `textarea` element, enabling rich content editing including bold, italics, hyperlinks, etc. It is not too much unlike the single editor region of the legacy post editor, and is in fact powered by the same TinyMCE library. +The `RichText` component can be considered as a super-powered `textarea` element, enabling rich content editing including bold, italics, hyperlinks, etc. To use the `RichText` component, add `wp-editor` to the array of registered script handles when calling `wp_register_script`. @@ -126,9 +126,9 @@ To use the `RichText` component, add `wp-editor` to the array of registered scri wp_register_script( 'gutenberg-boilerplate-es5-step03', plugins_url( 'step-03/block.js', __FILE__ ), - array( - 'wp-blocks', - 'wp-element', + array( + 'wp-blocks', + 'wp-element', 'wp-editor', // Note the addition of wp-editor to the dependencies ) ); diff --git a/docs/designers-developers/faq.md b/docs/designers-developers/faq.md index 1f54d5d78e7ea..4fcccc36723bf 100644 --- a/docs/designers-developers/faq.md +++ b/docs/designers-developers/faq.md @@ -241,7 +241,7 @@ Here is a brief animation illustrating how to find and use the keyboard shortcut ## Is Gutenberg built on top of TinyMCE? -No. [TinyMCE](https://www.tinymce.com/) is one of the best tools for enabling rich text on the web. In Gutenberg, TinyMCE does exactly that. Nearly every text field you'll find is augmented with TinyMCE for rich text. Whether it be text, lists, or even just a single caption, TinyMCE can be invoked on blocks for rich text enhancements. +No. [TinyMCE](https://www.tinymce.com/) is only used for the "Classic" block. ## What browsers will Gutenberg support? @@ -320,7 +320,7 @@ We realize it's a big change. We also think there will be many new opportunities ## Will I be able to opt out of Gutenberg for my site? -There is a “Classic” block, which is virtually the same as the current editor, except in block form. +There is a “Classic” block, which is virtually the same as the current editor, except in block form. There is also the [Classic Editor Plugin](https://wordpress.org/plugins/classic-editor/) which restores the previous editor, see the plugin for more information. The WordPress Core team has committed to supporting the Classic Editor Plugin [until December 2021](https://make.wordpress.org/core/2018/11/07/classic-editor-plugin-support-window/). diff --git a/docs/designers-developers/glossary.md b/docs/designers-developers/glossary.md index f51c0d16d5dba..69f9e497a0bc4 100644 --- a/docs/designers-developers/glossary.md +++ b/docs/designers-developers/glossary.md @@ -35,7 +35,7 @@ <dd>A sidebar region containing metadata fields for the post, including scheduling, visibility, terms, and featured image.</dd> <dt>RichText</dt> -<dd>A common component enabling rich content editing including bold, italics, hyperlinks, etc. It is not too much unlike the single editor region of the legacy post editor, and is in fact powered by the same TinyMCE library.</dd> +<dd>A common component enabling rich content editing including bold, italics, hyperlinks, etc.</dd> <dt>Reusable block</dt> <dd>A block that is saved and then can be shared as a reusable, repeatable piece of content.</dd> diff --git a/lib/packages-dependencies.php b/lib/packages-dependencies.php index 60a125997f3bc..fbf5eda814717 100644 --- a/lib/packages-dependencies.php +++ b/lib/packages-dependencies.php @@ -102,7 +102,6 @@ ), 'wp-dom' => array( 'lodash', - 'wp-tinymce', ), 'wp-dom-ready' => array(), 'wp-edit-post' => array( @@ -153,7 +152,6 @@ 'wp-notices', 'wp-nux', 'wp-rich-text', - 'wp-tinymce', 'wp-token-list', 'wp-url', 'wp-viewport', diff --git a/package-lock.json b/package-lock.json index 9182e63d2d2e2..5e24aea3137af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2571,7 +2571,6 @@ "refx": "^3.0.0", "rememo": "^3.0.0", "tinycolor2": "^1.4.1", - "tinymce": "^4.7.2", "traverse": "^0.6.6" } }, @@ -19621,11 +19620,6 @@ "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz", "integrity": "sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g=" }, - "tinymce": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-4.7.2.tgz", - "integrity": "sha1-JL9k/x0eaBkOFUYaZY3CV6Qmxe4=" - }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", diff --git a/packages/block-library/src/button/editor.scss b/packages/block-library/src/button/editor.scss index 1330ff2cdf96f..0b65eac8bcb78 100644 --- a/packages/block-library/src/button/editor.scss +++ b/packages/block-library/src/button/editor.scss @@ -14,17 +14,17 @@ margin-bottom: 0; position: relative; - .editor-rich-text__tinymce.mce-content-body { + [contenteditable] { cursor: text; } // Make placeholder text white unless custom colors or outline versions are chosen. - &:not(.has-text-color):not(.is-style-outline) .editor-rich-text__tinymce[data-is-placeholder-visible="true"] + .editor-rich-text__tinymce { + &:not(.has-text-color):not(.is-style-outline) .editor-rich-text__editable[data-is-placeholder-visible="true"] + .editor-rich-text__editable { color: $white; } // Increase placeholder opacity to meet contrast ratios. - .editor-rich-text__tinymce[data-is-placeholder-visible="true"] + .editor-rich-text__tinymce { + .editor-rich-text__editable[data-is-placeholder-visible="true"] + .editor-rich-text__editable { opacity: 0.8; } @@ -33,7 +33,7 @@ max-width: 100%; // Polish the empty placeholder text for the button in variation previews. - .editor-rich-text__tinymce[data-is-placeholder-visible="true"] { + .editor-rich-text__editable[data-is-placeholder-visible="true"] { height: auto; } diff --git a/packages/block-library/src/button/test/__snapshots__/index.js.snap b/packages/block-library/src/button/test/__snapshots__/index.js.snap index e153279891096..fec3d15878b50 100644 --- a/packages/block-library/src/button/test/__snapshots__/index.js.snap +++ b/packages/block-library/src/button/test/__snapshots__/index.js.snap @@ -18,17 +18,17 @@ exports[`core/button block edit matches snapshot 1`] = ` aria-autocomplete="list" aria-label="Add text…" aria-multiline="true" - class="wp-block-button__link editor-rich-text__tinymce" + class="wp-block-button__link editor-rich-text__editable" contenteditable="true" data-is-placeholder-visible="true" role="textbox" > <br - data-mce-bogus="1" + data-rich-text-padding="true" /> </div> <div - class="editor-rich-text__tinymce wp-block-button__link" + class="editor-rich-text__editable wp-block-button__link" > Add text… </div> diff --git a/packages/block-library/src/cover/editor.scss b/packages/block-library/src/cover/editor.scss index dffeeca07dc3c..00d3fed4c8fca 100644 --- a/packages/block-library/src/cover/editor.scss +++ b/packages/block-library/src/cover/editor.scss @@ -1,13 +1,6 @@ .wp-block-cover-image, .wp-block-cover { - .editor-rich-text__tinymce[data-is-empty="true"]::before { - position: inherit; - } - - .editor-rich-text__tinymce:focus a[data-mce-selected] { - padding: 0 2px; - margin: 0 -2px; - border-radius: 2px; + .editor-rich-text__editable:focus a[data-rich-text-format-boundary] { box-shadow: none; background: rgba(255, 255, 255, 0.3); } diff --git a/packages/block-library/src/gallery/editor.scss b/packages/block-library/src/gallery/editor.scss index 9140d86007d07..7501869574b66 100644 --- a/packages/block-library/src/gallery/editor.scss +++ b/packages/block-library/src/gallery/editor.scss @@ -48,7 +48,7 @@ ul.wp-block-gallery li { } // Make extra space for the inline toolbar. - .editor-rich-text__tinymce { + figcaption { padding-top: 48px; } } @@ -78,12 +78,12 @@ ul.wp-block-gallery li { } } - .editor-rich-text .editor-rich-text__tinymce { + .editor-rich-text figcaption { a { color: $white; } - &:focus a[data-mce-selected] { + &:focus a[data-rich-text-format-boundary] { color: rgba(0, 0, 0, 0.2); } } diff --git a/packages/block-library/src/heading/editor.scss b/packages/block-library/src/heading/editor.scss index b3b695a511e8a..2a58a2a4155ba 100644 --- a/packages/block-library/src/heading/editor.scss +++ b/packages/block-library/src/heading/editor.scss @@ -35,13 +35,13 @@ } // Adjust line spacing for larger headings. - h1.editor-rich-text__tinymce, - h2.editor-rich-text__tinymce, - h3.editor-rich-text__tinymce { + h1, + h2, + h3 { line-height: 1.4; } - h4.editor-rich-text__tinymce { + h4 { line-height: 1.5; } } diff --git a/packages/block-library/src/heading/test/__snapshots__/index.js.snap b/packages/block-library/src/heading/test/__snapshots__/index.js.snap index 0131b6104ae6a..0df94748b04b5 100644 --- a/packages/block-library/src/heading/test/__snapshots__/index.js.snap +++ b/packages/block-library/src/heading/test/__snapshots__/index.js.snap @@ -13,17 +13,17 @@ exports[`core/heading block edit matches snapshot 1`] = ` aria-autocomplete="list" aria-label="Write heading…" aria-multiline="true" - class="editor-rich-text__tinymce" + class="editor-rich-text__editable" contenteditable="true" data-is-placeholder-visible="true" role="textbox" > <br - data-mce-bogus="1" + data-rich-text-padding="true" /> </h2> <h2 - class="editor-rich-text__tinymce" + class="editor-rich-text__editable" > Write heading… </h2> diff --git a/packages/block-library/src/list/editor.scss b/packages/block-library/src/list/editor.scss index 9ab60e0c6f9e2..a9dd5efb6460b 100644 --- a/packages/block-library/src/list/editor.scss +++ b/packages/block-library/src/list/editor.scss @@ -1,6 +1,5 @@ -.block-library-list .editor-rich-text__tinymce, -.block-library-list .editor-rich-text__tinymce ul, -.block-library-list .editor-rich-text__tinymce ol { +.editor-styles-wrapper .block-library-list ul, +.editor-styles-wrapper .block-library-list ol { padding-left: 1.3em; margin-left: 1.3em; } diff --git a/packages/block-library/src/list/test/__snapshots__/index.js.snap b/packages/block-library/src/list/test/__snapshots__/index.js.snap index d2da3e3413525..3015cc0fcaff3 100644 --- a/packages/block-library/src/list/test/__snapshots__/index.js.snap +++ b/packages/block-library/src/list/test/__snapshots__/index.js.snap @@ -13,19 +13,19 @@ exports[`core/list block edit matches snapshot 1`] = ` aria-autocomplete="list" aria-label="Write list…" aria-multiline="true" - class="editor-rich-text__tinymce" + class="editor-rich-text__editable" contenteditable="true" data-is-placeholder-visible="true" role="textbox" > <li> <br - data-mce-bogus="1" + data-rich-text-padding="true" /> </li> </ul> <ul - class="editor-rich-text__tinymce" + class="editor-rich-text__editable" > <li> Write list… diff --git a/packages/block-library/src/paragraph/editor.scss b/packages/block-library/src/paragraph/editor.scss index 1110e5d5832d4..6cec65a0b8227 100644 --- a/packages/block-library/src/paragraph/editor.scss +++ b/packages/block-library/src/paragraph/editor.scss @@ -1,7 +1,7 @@ // Specific to the empty paragraph placeholder: // when shown on mobile and in nested contexts, one or more icons show up on the right. // This padding makes sure it doesn't overlap text. -.editor-rich-text__tinymce[data-is-placeholder-visible="true"] + .editor-rich-text__tinymce.wp-block-paragraph { +.editor-rich-text__editable[data-is-placeholder-visible="true"] + .editor-rich-text__editable.wp-block-paragraph { padding-right: $icon-button-size * 3; // In nested contexts only one icon shows up. diff --git a/packages/block-library/src/paragraph/test/__snapshots__/index.js.snap b/packages/block-library/src/paragraph/test/__snapshots__/index.js.snap index 036646b041a21..52a4f07a4b653 100644 --- a/packages/block-library/src/paragraph/test/__snapshots__/index.js.snap +++ b/packages/block-library/src/paragraph/test/__snapshots__/index.js.snap @@ -15,17 +15,17 @@ exports[`core/paragraph block edit matches snapshot 1`] = ` aria-autocomplete="list" aria-label="Empty block; start writing or type forward slash to choose a block" aria-multiline="true" - class="wp-block-paragraph editor-rich-text__tinymce" + class="wp-block-paragraph editor-rich-text__editable" contenteditable="true" data-is-placeholder-visible="true" role="textbox" > <br - data-mce-bogus="1" + data-rich-text-padding="true" /> </p> <p - class="editor-rich-text__tinymce wp-block-paragraph" + class="editor-rich-text__editable wp-block-paragraph" > Start writing or type / to choose a block </p> diff --git a/packages/block-library/src/preformatted/test/__snapshots__/index.js.snap b/packages/block-library/src/preformatted/test/__snapshots__/index.js.snap index d89042667a97c..a18b12cb5728f 100644 --- a/packages/block-library/src/preformatted/test/__snapshots__/index.js.snap +++ b/packages/block-library/src/preformatted/test/__snapshots__/index.js.snap @@ -13,17 +13,17 @@ exports[`core/preformatted block edit matches snapshot 1`] = ` aria-autocomplete="list" aria-label="Write preformatted text…" aria-multiline="true" - class="editor-rich-text__tinymce" + class="editor-rich-text__editable" contenteditable="true" data-is-placeholder-visible="true" role="textbox" > <br - data-mce-bogus="1" + data-rich-text-padding="true" /> </pre> <pre - class="editor-rich-text__tinymce" + class="editor-rich-text__editable" > Write preformatted text… </pre> diff --git a/packages/block-library/src/pullquote/editor.scss b/packages/block-library/src/pullquote/editor.scss index a05314749bf98..6a2eae7f0c6f7 100644 --- a/packages/block-library/src/pullquote/editor.scss +++ b/packages/block-library/src/pullquote/editor.scss @@ -1,7 +1,6 @@ .editor-block-list__block[data-type="core/pullquote"] { &[data-align="left"], &[data-align="right"] { - & .block-library-pullquote__content .editor-rich-text__tinymce[data-is-empty="true"]::before, & .editor-rich-text p { font-size: 20px; } @@ -9,18 +8,6 @@ } .wp-block-pullquote { - cite .editor-rich-text__tinymce[data-is-empty="true"]::before { - font-size: 14px; - font-family: $default-font; - } - - .editor-rich-text__tinymce[data-is-empty="true"]::before { - width: 100%; - left: 50%; - transform: translateX(-50%); - } - - & blockquote > .block-library-pullquote__content .editor-rich-text__tinymce[data-is-empty="true"]::before, & blockquote > .editor-rich-text p { font-size: 28px; line-height: 1.6; diff --git a/packages/block-library/src/pullquote/test/__snapshots__/index.js.snap b/packages/block-library/src/pullquote/test/__snapshots__/index.js.snap index aed466a208142..824b6c491a8ad 100644 --- a/packages/block-library/src/pullquote/test/__snapshots__/index.js.snap +++ b/packages/block-library/src/pullquote/test/__snapshots__/index.js.snap @@ -17,19 +17,19 @@ exports[`core/pullquote block edit matches snapshot 1`] = ` aria-autocomplete="list" aria-label="Write quote…" aria-multiline="true" - class="editor-rich-text__tinymce" + class="editor-rich-text__editable" contenteditable="true" data-is-placeholder-visible="true" role="textbox" > <p> <br - data-mce-bogus="1" + data-rich-text-padding="true" /> </p> </div> <div - class="editor-rich-text__tinymce" + class="editor-rich-text__editable" > <p> Write quote… diff --git a/packages/block-library/src/quote/test/__snapshots__/index.js.snap b/packages/block-library/src/quote/test/__snapshots__/index.js.snap index 1365b8581bcff..886ef7e4560a8 100644 --- a/packages/block-library/src/quote/test/__snapshots__/index.js.snap +++ b/packages/block-library/src/quote/test/__snapshots__/index.js.snap @@ -16,19 +16,19 @@ exports[`core/quote block edit matches snapshot 1`] = ` aria-autocomplete="list" aria-label="Write quote…" aria-multiline="true" - class="editor-rich-text__tinymce" + class="editor-rich-text__editable" contenteditable="true" data-is-placeholder-visible="true" role="textbox" > <p> <br - data-mce-bogus="1" + data-rich-text-padding="true" /> </p> </div> <div - class="editor-rich-text__tinymce" + class="editor-rich-text__editable" > <p> Write quote… diff --git a/packages/block-library/src/text-columns/editor.scss b/packages/block-library/src/text-columns/editor.scss index 1f8cb6f8c5723..b1a6589806fce 100644 --- a/packages/block-library/src/text-columns/editor.scss +++ b/packages/block-library/src/text-columns/editor.scss @@ -1,5 +1,5 @@ .wp-block-text-columns { - .editor-rich-text__tinymce:focus { + .editor-rich-text__editable:focus { outline: $border-width solid $light-gray-500; } } diff --git a/packages/block-library/src/text-columns/test/__snapshots__/index.js.snap b/packages/block-library/src/text-columns/test/__snapshots__/index.js.snap index aabf7c36e121f..4f89f08374630 100644 --- a/packages/block-library/src/text-columns/test/__snapshots__/index.js.snap +++ b/packages/block-library/src/text-columns/test/__snapshots__/index.js.snap @@ -19,17 +19,17 @@ exports[`core/text-columns block edit matches snapshot 1`] = ` aria-autocomplete="list" aria-label="New Column" aria-multiline="true" - class="editor-rich-text__tinymce" + class="editor-rich-text__editable" contenteditable="true" data-is-placeholder-visible="true" role="textbox" > <br - data-mce-bogus="1" + data-rich-text-padding="true" /> </p> <p - class="editor-rich-text__tinymce" + class="editor-rich-text__editable" > New Column </p> @@ -53,17 +53,17 @@ exports[`core/text-columns block edit matches snapshot 1`] = ` aria-autocomplete="list" aria-label="New Column" aria-multiline="true" - class="editor-rich-text__tinymce" + class="editor-rich-text__editable" contenteditable="true" data-is-placeholder-visible="true" role="textbox" > <br - data-mce-bogus="1" + data-rich-text-padding="true" /> </p> <p - class="editor-rich-text__tinymce" + class="editor-rich-text__editable" > New Column </p> diff --git a/packages/block-library/src/verse/test/__snapshots__/index.js.snap b/packages/block-library/src/verse/test/__snapshots__/index.js.snap index 8595e740857ac..617e1296fbcad 100644 --- a/packages/block-library/src/verse/test/__snapshots__/index.js.snap +++ b/packages/block-library/src/verse/test/__snapshots__/index.js.snap @@ -13,17 +13,17 @@ exports[`core/verse block edit matches snapshot 1`] = ` aria-autocomplete="list" aria-label="Write…" aria-multiline="true" - class="editor-rich-text__tinymce" + class="editor-rich-text__editable" contenteditable="true" data-is-placeholder-visible="true" role="textbox" > <br - data-mce-bogus="1" + data-rich-text-padding="true" /> </pre> <pre - class="editor-rich-text__tinymce" + class="editor-rich-text__editable" > Write… </pre> diff --git a/packages/dom/CHANGELOG.md b/packages/dom/CHANGELOG.md index fa0529b1f69f2..3ec9b7a1bf70c 100644 --- a/packages/dom/CHANGELOG.md +++ b/packages/dom/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.0.9 (Unreleased) + +### Bug Fix + +- Update `isHorizontalEdge` to account for empty text nodes. + ## 2.0.8 (2019-01-03) ## 2.0.7 (2018-11-20) diff --git a/packages/dom/src/dom.js b/packages/dom/src/dom.js index 7bfe0d101ddb9..26f2985e5c48b 100644 --- a/packages/dom/src/dom.js +++ b/packages/dom/src/dom.js @@ -128,16 +128,23 @@ export function isHorizontalEdge( container, isReverse ) { } // If confirmed to be at extent, traverse up through DOM, verifying that - // the node is at first or last child for reverse or forward respectively. - // Continue until container is reached. - const order = isReverse ? 'first' : 'last'; + // the node is at first or last child for reverse or forward respectively + // (ignoring empty text nodes). Continue until container is reached. + const order = isReverse ? 'previous' : 'next'; + while ( node !== container ) { - const parentNode = node.parentNode; - if ( parentNode[ `${ order }Child` ] !== node ) { + let next = node[ `${ order }Sibling` ]; + + // Skip over empty text nodes. + while ( next && next.nodeType === TEXT_NODE && next.data === '' ) { + next = next[ `${ order }Sibling` ]; + } + + if ( next ) { return false; } - node = parentNode; + node = node.parentNode; } // If reached, range is assumed to be at edge. diff --git a/packages/e2e-test-utils/src/get-edited-post-content.js b/packages/e2e-test-utils/src/get-edited-post-content.js index 2352eb6c519b5..7a595133f22a0 100644 --- a/packages/e2e-test-utils/src/get-edited-post-content.js +++ b/packages/e2e-test-utils/src/get-edited-post-content.js @@ -1,25 +1,10 @@ -/** - * Regular expression matching zero-width space characters. - * - * @type {RegExp} - */ -const REGEXP_ZWSP = /[\u200B\u200C\u200D\uFEFF]/; - /** * Returns a promise which resolves with the edited post content (HTML string). * * @return {Promise} Promise resolving with post content markup. */ export async function getEditedPostContent() { - const content = await page.evaluate( () => { - const { select } = window.wp.data; - return select( 'core/editor' ).getEditedPostContent(); + return await page.evaluate( () => { + return window.wp.data.select( 'core/editor' ).getEditedPostContent(); } ); - - // Globally guard against zero-width characters. - if ( REGEXP_ZWSP.test( content ) ) { - throw new Error( 'Unexpected zero-width space character in editor content.' ); - } - - return content; } diff --git a/packages/e2e-tests/specs/__snapshots__/rich-text.test.js.snap b/packages/e2e-tests/specs/__snapshots__/rich-text.test.js.snap index 275080b436f2d..0cace5805e698 100644 --- a/packages/e2e-tests/specs/__snapshots__/rich-text.test.js.snap +++ b/packages/e2e-tests/specs/__snapshots__/rich-text.test.js.snap @@ -24,12 +24,24 @@ exports[`RichText should handle change in tag name gracefully 1`] = ` <!-- /wp:heading -->" `; +exports[`RichText should not format text after code backtick 1`] = ` +"<!-- wp:paragraph --> +<p>A <code>backtick</code> and more.</p> +<!-- /wp:paragraph -->" +`; + exports[`RichText should only mutate text data on input 1`] = ` "<!-- wp:paragraph --> <p>1<strong>2</strong>34</p> <!-- /wp:paragraph -->" `; +exports[`RichText should return focus when pressing formatting button 1`] = ` +"<!-- wp:paragraph --> +<p>Some <strong>bold</strong>.</p> +<!-- /wp:paragraph -->" +`; + exports[`RichText should transform backtick to code 1`] = ` "<!-- wp:paragraph --> <p>A <code>backtick</code></p> diff --git a/packages/e2e-tests/specs/__snapshots__/undo.test.js.snap b/packages/e2e-tests/specs/__snapshots__/undo.test.js.snap index 49a8c19fe89fa..799e5c2c0879c 100644 --- a/packages/e2e-tests/specs/__snapshots__/undo.test.js.snap +++ b/packages/e2e-tests/specs/__snapshots__/undo.test.js.snap @@ -28,7 +28,7 @@ exports[`undo should undo typing after a pause 2`] = ` exports[`undo should undo typing after non input change 1`] = ` "<!-- wp:paragraph --> -<p>before keyboard <strong>after keyboard</strong></p> +<p>before keyboard <strong>after keyboard</strong></p> <!-- /wp:paragraph -->" `; diff --git a/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap b/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap index 3852f38781625..0d02a8e655bfa 100644 --- a/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap +++ b/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap @@ -24,14 +24,6 @@ exports[`adding blocks Should navigate inner blocks with arrow keys 1`] = ` <!-- /wp:paragraph -->" `; -exports[`adding blocks should clean TinyMCE content 1`] = `""`; - -exports[`adding blocks should clean TinyMCE content 2`] = ` -"<!-- wp:paragraph --> -<p><strong>Inside</strong></p> -<!-- /wp:paragraph -->" -`; - exports[`adding blocks should create valid paragraph blocks when rapidly pressing Enter 1`] = ` "<!-- wp:paragraph --> <p></p> @@ -122,6 +114,18 @@ exports[`adding blocks should navigate around inline boundaries 1`] = ` <!-- /wp:paragraph -->" `; +exports[`adding blocks should navigate around nested inline boundaries 1`] = ` +"<!-- wp:paragraph --> +<p><strong><em>1</em> <em>2</em></strong></p> +<!-- /wp:paragraph -->" +`; + +exports[`adding blocks should navigate around nested inline boundaries 2`] = ` +"<!-- wp:paragraph --> +<p>a<strong>b<em>c1d</em>e f<em>g2h</em>i</strong>j</p> +<!-- /wp:paragraph -->" +`; + exports[`adding blocks should not delete surrounding space when deleting a selected word 1`] = ` "<!-- wp:paragraph --> <p>alpha gamma</p> diff --git a/packages/e2e-tests/specs/links.test.js b/packages/e2e-tests/specs/links.test.js index 03e8b6073ac22..0899494cef66f 100644 --- a/packages/e2e-tests/specs/links.test.js +++ b/packages/e2e-tests/specs/links.test.js @@ -6,7 +6,6 @@ import { getEditedPostContent, createNewPost, pressKeyWithModifier, - pressKeyTimes, insertBlock, } from '@wordpress/e2e-test-utils'; @@ -244,7 +243,8 @@ describe( 'Links', () => { it( 'can be edited with collapsed selection', async () => { await createAndReselectLink(); // Make a collapsed selection inside the link - await pressKeyTimes( 'ArrowRight', 3 ); + await page.keyboard.press( 'ArrowLeft' ); + await page.keyboard.press( 'ArrowRight' ); await moveMouse(); await page.click( 'button[aria-label="Edit"]' ); await waitForAutoFocus(); diff --git a/packages/e2e-tests/specs/navigable-toolbar.test.js b/packages/e2e-tests/specs/navigable-toolbar.test.js index e14a45e122442..1ff5f6cb2c953 100644 --- a/packages/e2e-tests/specs/navigable-toolbar.test.js +++ b/packages/e2e-tests/specs/navigable-toolbar.test.js @@ -26,7 +26,7 @@ describe( 'block toolbar', () => { } ); const isInRichTextEditable = () => page.evaluate( () => ( - document.activeElement.classList.contains( 'editor-rich-text__tinymce' ) + document.activeElement.contentEditable === 'true' ) ); const isInBlockToolbar = () => page.evaluate( () => ( diff --git a/packages/e2e-tests/specs/plugins/annotations.test.js b/packages/e2e-tests/specs/plugins/annotations.test.js index 5fe99a352eec6..1fe3554ad2f5e 100644 --- a/packages/e2e-tests/specs/plugins/annotations.test.js +++ b/packages/e2e-tests/specs/plugins/annotations.test.js @@ -80,7 +80,7 @@ describe( 'Using Plugins API', () => { * @return {Promise<string>} Inner HTML. */ async function getRichTextInnerHTML() { - const htmlContent = await page.$$( '.editor-rich-text__tinymce' ); + const htmlContent = await page.$$( '*[contenteditable]' ); return await page.evaluate( ( el ) => { return el.innerHTML; }, htmlContent[ 0 ] ); @@ -124,7 +124,7 @@ describe( 'Using Plugins API', () => { await page.keyboard.type( 'D' ); await removeAnnotations(); - const htmlContent = await page.$$( '.editor-rich-text__tinymce' ); + const htmlContent = await page.$$( '*[contenteditable]' ); const html = await page.evaluate( ( el ) => { return el.innerHTML; }, htmlContent[ 0 ] ); @@ -138,6 +138,8 @@ describe( 'Using Plugins API', () => { await annotateFirstBlock( 1, 2 ); + await page.keyboard.press( 'ArrowLeft' ); + await page.keyboard.press( 'ArrowLeft' ); await page.keyboard.press( 'ArrowLeft' ); await page.keyboard.press( 'ArrowLeft' ); @@ -158,6 +160,7 @@ describe( 'Using Plugins API', () => { await annotateFirstBlock( 1, 2 ); + await page.keyboard.press( 'ArrowLeft' ); await page.keyboard.press( 'ArrowLeft' ); // Put an 1 after the A, it should not be annotated. diff --git a/packages/e2e-tests/specs/rich-text.test.js b/packages/e2e-tests/specs/rich-text.test.js index 0773f295566ad..e2af127f623e8 100644 --- a/packages/e2e-tests/specs/rich-text.test.js +++ b/packages/e2e-tests/specs/rich-text.test.js @@ -17,7 +17,8 @@ describe( 'RichText', () => { it( 'should handle change in tag name gracefully', async () => { // Regression test: The heading block changes the tag name of its // RichText element. Historically this has been prone to breakage, - // specifically in destroying / reinitializing the TinyMCE instance. + // because the Editable component prevents rerenders, so React cannot + // update the element by itself. // // See: https://github.com/WordPress/gutenberg/issues/3091 await insertBlock( 'Heading' ); @@ -57,6 +58,19 @@ describe( 'RichText', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + it( 'should return focus when pressing formatting button', async () => { + await clickBlockAppender(); + await page.keyboard.type( 'Some ' ); + await page.keyboard.press( 'Escape' ); + await page.click( '[aria-label="Bold"]' ); + await page.keyboard.type( 'bold' ); + await page.keyboard.press( 'Escape' ); + await page.click( '[aria-label="Bold"]' ); + await page.keyboard.type( '.' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + it( 'should transform backtick to code', async () => { await clickBlockAppender(); await page.keyboard.type( 'A `backtick`' ); @@ -68,6 +82,13 @@ describe( 'RichText', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + it( 'should not format text after code backtick', async () => { + await clickBlockAppender(); + await page.keyboard.type( 'A `backtick` and more.' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + it( 'should only mutate text data on input', async () => { await clickBlockAppender(); await page.keyboard.type( '1' ); diff --git a/packages/e2e-tests/specs/splitting-merging.test.js b/packages/e2e-tests/specs/splitting-merging.test.js index b5b24ce6345cd..1120f04a2b767 100644 --- a/packages/e2e-tests/specs/splitting-merging.test.js +++ b/packages/e2e-tests/specs/splitting-merging.test.js @@ -139,7 +139,7 @@ describe( 'splitting and merging blocks', () => { } ); it( 'should forward delete from an empty paragraph', async () => { - // Regression test: Bogus nodes in a TinyMCE container can interfere + // Regression test: Bogus nodes in a RichText container can interfere // with isHorizontalEdge detection, preventing forward deletion. // // See: https://github.com/WordPress/gutenberg/issues/8731 diff --git a/packages/e2e-tests/specs/writing-flow.test.js b/packages/e2e-tests/specs/writing-flow.test.js index ee5b2b2d1bc2f..d0b46970c87f4 100644 --- a/packages/e2e-tests/specs/writing-flow.test.js +++ b/packages/e2e-tests/specs/writing-flow.test.js @@ -92,17 +92,6 @@ describe( 'adding blocks', () => { // Arrow left from selected bold should collapse to before the inline // boundary. Arrow once more to traverse into first paragraph. - // - // See native behavior example: http://fiddle.tinymce.com/kvgaab - // - // 1. Select all of second paragraph, end to beginning - // 2. Press ArrowLeft - // 3. Type - // 4. Note that text is not bolded - // - // This is technically different than how other word processors treat - // the collapse while a bolded segment is selected, but our behavior - // is consistent with TinyMCE. await page.keyboard.press( 'ArrowLeft' ); await page.keyboard.press( 'ArrowLeft' ); await page.keyboard.type( 'After' ); @@ -141,20 +130,44 @@ describe( 'adding blocks', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); - it( 'should clean TinyMCE content', async () => { - // Ensure no zero-width space character. Notably, this can occur when - // save occurs while at an inline boundary edge. + it( 'should navigate around nested inline boundaries', async () => { await clickBlockAppender(); await pressKeyWithModifier( 'primary', 'b' ); + await page.keyboard.type( '1 2' ); + await page.keyboard.down( 'Shift' ); + await page.keyboard.press( 'ArrowLeft' ); + await page.keyboard.up( 'Shift' ); + await pressKeyWithModifier( 'primary', 'i' ); + await page.keyboard.press( 'ArrowLeft' ); + await page.keyboard.press( 'ArrowLeft' ); + await page.keyboard.down( 'Shift' ); + await page.keyboard.press( 'ArrowLeft' ); + await page.keyboard.up( 'Shift' ); + await pressKeyWithModifier( 'primary', 'i' ); + await page.keyboard.press( 'ArrowLeft' ); + expect( await getEditedPostContent() ).toMatchSnapshot(); - // Backspace to remove the content in this block, resetting it. - await page.keyboard.press( 'Backspace' ); + await page.keyboard.type( 'a' ); + await page.keyboard.press( 'ArrowRight' ); + await page.keyboard.type( 'b' ); + await page.keyboard.press( 'ArrowRight' ); + await page.keyboard.type( 'c' ); + await page.keyboard.press( 'ArrowRight' ); + await page.keyboard.type( 'd' ); + await page.keyboard.press( 'ArrowRight' ); + await page.keyboard.type( 'e' ); + await page.keyboard.press( 'ArrowRight' ); + await page.keyboard.type( 'f' ); + await page.keyboard.press( 'ArrowRight' ); + await page.keyboard.type( 'g' ); + await page.keyboard.press( 'ArrowRight' ); + await page.keyboard.type( 'h' ); + await page.keyboard.press( 'ArrowRight' ); + await page.keyboard.type( 'i' ); + await page.keyboard.press( 'ArrowRight' ); + await page.keyboard.type( 'j' ); - // Ensure no data-mce-selected. Notably, this can occur when content - // is saved while typing within an inline boundary. - await pressKeyWithModifier( 'primary', 'b' ); - await page.keyboard.type( 'Inside' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); diff --git a/packages/editor/CHANGELOG.md b/packages/editor/CHANGELOG.md index c5365bd4b44df..8b64a350c46ac 100644 --- a/packages/editor/CHANGELOG.md +++ b/packages/editor/CHANGELOG.md @@ -15,7 +15,9 @@ ### Internal -- Removed `jQuery` dependency +- Removed `jQuery` dependency. +- Removed `TinyMCE` dependency. +- RichText: improve format boundaries. ## 9.0.7 (2019-01-03) diff --git a/packages/editor/package.json b/packages/editor/package.json index 64c21004d5462..36b3ed87d7ec4 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -55,7 +55,6 @@ "refx": "^3.0.0", "rememo": "^3.0.0", "tinycolor2": "^1.4.1", - "tinymce": "^4.7.2", "traverse": "^0.6.6" }, "publishConfig": { diff --git a/packages/editor/src/components/rich-text/editable.js b/packages/editor/src/components/rich-text/editable.js new file mode 100644 index 0000000000000..cd668ada44a8a --- /dev/null +++ b/packages/editor/src/components/rich-text/editable.js @@ -0,0 +1,193 @@ +/** + * External dependencies + */ +import { isEqual } from 'lodash'; +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { Component, createElement } from '@wordpress/element'; +import { BACKSPACE, DELETE } from '@wordpress/keycodes'; + +/** + * Internal dependencies + */ +import { diffAriaProps, pickAriaProps } from './aria'; + +/** + * Browser dependencies + */ + +const { userAgent } = window.navigator; + +/** + * Applies a fix that provides `input` events for contenteditable in Internet Explorer. + * + * @param {Element} editorNode The root editor node. + * + * @return {Function} A function to remove the fix (for cleanup). + */ +function applyInternetExplorerInputFix( editorNode ) { + /** + * Dispatches `input` events in response to `textinput` events. + * + * IE provides a `textinput` event that is similar to an `input` event, + * and we use it to manually dispatch an `input` event. + * `textinput` is dispatched for text entry but for not deletions. + * + * @param {Event} textInputEvent An Internet Explorer `textinput` event. + */ + function mapTextInputEvent( textInputEvent ) { + textInputEvent.stopImmediatePropagation(); + + const inputEvent = document.createEvent( 'Event' ); + inputEvent.initEvent( 'input', true, false ); + inputEvent.data = textInputEvent.data; + textInputEvent.target.dispatchEvent( inputEvent ); + } + + /** + * Dispatches `input` events in response to Delete and Backspace keyup. + * + * It would be better dispatch an `input` event after each deleting + * `keydown` because the DOM is updated after each, but it is challenging + * to determine the right time to dispatch `input` since propagation of + * `keydown` can be stopped at any point. + * + * It's easier to listen for `keyup` in the capture phase and dispatch + * `input` before `keyup` propagates further. It's not perfect, but should + * be good enough. + * + * @param {KeyboardEvent} keyUp + * @param {Node} keyUp.target The event target. + * @param {number} keyUp.keyCode The key code. + */ + function mapDeletionKeyUpEvents( { target, keyCode } ) { + const isDeletion = BACKSPACE === keyCode || DELETE === keyCode; + + if ( isDeletion && editorNode.contains( target ) ) { + const inputEvent = document.createEvent( 'Event' ); + inputEvent.initEvent( 'input', true, false ); + inputEvent.data = null; + target.dispatchEvent( inputEvent ); + } + } + + editorNode.addEventListener( 'textinput', mapTextInputEvent ); + document.addEventListener( 'keyup', mapDeletionKeyUpEvents, true ); + return function removeInternetExplorerInputFix() { + editorNode.removeEventListener( 'textinput', mapTextInputEvent ); + document.removeEventListener( 'keyup', mapDeletionKeyUpEvents, true ); + }; +} + +const IS_PLACEHOLDER_VISIBLE_ATTR_NAME = 'data-is-placeholder-visible'; +const CLASS_NAME = 'editor-rich-text__editable'; + +/** + * Whether or not the user agent is Internet Explorer. + * + * @type {boolean} + */ +const IS_IE = userAgent.indexOf( 'Trident' ) >= 0; + +export default class Editable extends Component { + constructor() { + super(); + this.bindEditorNode = this.bindEditorNode.bind( this ); + } + + // We must prevent rerenders because the browser will modify the DOM. React + // will rerender the DOM fine, but we're losing selection and it would be + // more expensive to do so as it would just set the inner HTML through + // `dangerouslySetInnerHTML`. Instead RichText does it's own diffing and + // selection setting. + // + // Because we never update the component, we have to look through props and + // update the attributes on the wrapper nodes here. `componentDidUpdate` + // will never be called. + shouldComponentUpdate( nextProps ) { + this.configureIsPlaceholderVisible( nextProps.isPlaceholderVisible ); + + if ( ! isEqual( this.props.style, nextProps.style ) ) { + this.editorNode.setAttribute( 'style', '' ); + Object.assign( this.editorNode.style, nextProps.style ); + } + + if ( ! isEqual( this.props.className, nextProps.className ) ) { + this.editorNode.className = classnames( nextProps.className, CLASS_NAME ); + } + + const { removedKeys, updatedKeys } = diffAriaProps( this.props, nextProps ); + removedKeys.forEach( ( key ) => + this.editorNode.removeAttribute( key ) ); + updatedKeys.forEach( ( key ) => + this.editorNode.setAttribute( key, nextProps[ key ] ) ); + + return false; + } + + configureIsPlaceholderVisible( isPlaceholderVisible ) { + const isPlaceholderVisibleString = String( !! isPlaceholderVisible ); + if ( this.editorNode.getAttribute( IS_PLACEHOLDER_VISIBLE_ATTR_NAME ) !== isPlaceholderVisibleString ) { + this.editorNode.setAttribute( IS_PLACEHOLDER_VISIBLE_ATTR_NAME, isPlaceholderVisibleString ); + } + } + + bindEditorNode( editorNode ) { + this.editorNode = editorNode; + + if ( this.props.setRef ) { + this.props.setRef( editorNode ); + } + + if ( IS_IE ) { + if ( editorNode ) { + // Mounting: + this.removeInternetExplorerInputFix = applyInternetExplorerInputFix( editorNode ); + } else { + // Unmounting: + this.removeInternetExplorerInputFix(); + } + } + } + + render() { + const ariaProps = pickAriaProps( this.props ); + const { + tagName = 'div', + style, + record, + valueToEditableHTML, + className, + isPlaceholderVisible, + onPaste, + onInput, + onKeyDown, + onCompositionEnd, + onFocus, + onBlur, + } = this.props; + + ariaProps.role = 'textbox'; + ariaProps[ 'aria-multiline' ] = true; + + return createElement( tagName, { + ...ariaProps, + className: classnames( className, CLASS_NAME ), + contentEditable: true, + [ IS_PLACEHOLDER_VISIBLE_ATTR_NAME ]: isPlaceholderVisible, + ref: this.bindEditorNode, + style, + suppressContentEditableWarning: true, + dangerouslySetInnerHTML: { __html: valueToEditableHTML( record ) }, + onPaste, + onInput, + onFocus, + onBlur, + onKeyDown, + onCompositionEnd, + } ); + } +} diff --git a/packages/editor/src/components/rich-text/index.js b/packages/editor/src/components/rich-text/index.js index 21ae5a91b5cac..ea58c212ee3a2 100644 --- a/packages/editor/src/components/rich-text/index.js +++ b/packages/editor/src/components/rich-text/index.js @@ -19,7 +19,7 @@ import memize from 'memize'; import { Component, Fragment, RawHTML } from '@wordpress/element'; import { isHorizontalEdge } from '@wordpress/dom'; import { createBlobURL } from '@wordpress/blob'; -import { BACKSPACE, DELETE, ENTER } from '@wordpress/keycodes'; +import { BACKSPACE, DELETE, ENTER, LEFT, RIGHT } from '@wordpress/keycodes'; import { withDispatch, withSelect } from '@wordpress/data'; import { pasteHandler, children, getBlockTransforms, findTransform } from '@wordpress/blocks'; import { withInstanceId, withSafeTimeout, compose } from '@wordpress/compose'; @@ -55,7 +55,7 @@ import Autocomplete from '../autocomplete'; import BlockFormatControls from '../block-format-controls'; import FormatEdit from './format-edit'; import FormatToolbar from './format-toolbar'; -import TinyMCE, { TINYMCE_ZWSP } from './tinymce'; +import Editable from './editable'; import { pickAriaProps } from './aria'; import { getPatterns } from './patterns'; import { withBlockEditContext } from '../block-edit/context'; @@ -109,6 +109,7 @@ export class RichText extends Component { this.valueToFormat = this.valueToFormat.bind( this ); this.setRef = this.setRef.bind( this ); this.valueToEditableHTML = this.valueToEditableHTML.bind( this ); + this.handleHorizontalNavigation = this.handleHorizontalNavigation.bind( this ); this.formatToValue = memize( this.formatToValue.bind( this ), { size: 1 } ); @@ -149,9 +150,9 @@ export class RichText extends Component { */ getRecord() { const { formats, text } = this.formatToValue( this.props.value ); - const { start, end } = this.state; + const { start, end, selectedFormat } = this.state; - return { formats, text, start, end }; + return { formats, text, start, end, selectedFormat }; } createRecord() { @@ -162,10 +163,6 @@ export class RichText extends Component { range, multilineTag: this.multilineTag, multilineWrapperTags: this.multilineWrapperTags, - removeNode: ( node ) => node.getAttribute( 'data-mce-bogus' ) === 'all', - unwrapNode: ( node ) => !! node.getAttribute( 'data-mce-bogus' ), - removeAttribute: ( attribute ) => attribute.indexOf( 'data-mce-' ) === 0, - filterString: ( string ) => string.replace( TINYMCE_ZWSP, '' ), prepareEditableTree: this.props.prepareEditableTree, } ); } @@ -176,11 +173,6 @@ export class RichText extends Component { current: this.editableRef, multilineTag: this.multilineTag, multilineWrapperTags: this.multilineWrapperTags, - createLinePadding( doc ) { - const element = doc.createElement( 'br' ); - element.setAttribute( 'data-mce-bogus', '1' ); - return element; - }, prepareEditableTree: this.props.prepareEditableTree, } ); } @@ -355,10 +347,33 @@ export class RichText extends Component { return; } - const record = this.createRecord(); - const transformed = this.patterns.reduce( ( accumlator, transform ) => transform( accumlator ), record ); + let { selectedFormat } = this.state; + const { formats, text, start, end } = this.patterns.reduce( + ( accumlator, transform ) => transform( accumlator ), + this.createRecord() + ); + + if ( this.formatPlaceholder ) { + formats[ this.state.start ] = formats[ this.state.start ] || []; + formats[ this.state.start ].push( this.formatPlaceholder ); + selectedFormat = formats[ this.state.start ].length; + } else if ( selectedFormat ) { + const formatsBefore = formats[ start - 1 ] || []; + const formatsAfter = formats[ start ] || []; + + let source = formatsBefore; + + if ( formatsAfter.length > formatsBefore.length ) { + source = formatsAfter; + } + + source = source.slice( 0, selectedFormat ); + formats[ this.state.start ] = source; + } else { + delete formats[ this.state.start ]; + } - this.onChange( transformed, { + this.onChange( { formats, text, start, end, selectedFormat }, { withoutHistory: true, } ); @@ -377,17 +392,36 @@ export class RichText extends Component { * Handles the `selectionchange` event: sync the selection to local state. */ onSelectionChange() { - const { start, end, formats } = this.createRecord(); + if ( this.ignoreSelectionChange ) { + delete this.ignoreSelectionChange; + return; + } + + const value = this.createRecord(); + const { start, end, formats } = value; if ( start !== this.state.start || end !== this.state.end ) { const isCaretWithinFormattedText = this.props.isCaretWithinFormattedText; + if ( ! isCaretWithinFormattedText && formats[ start ] ) { this.props.onEnterFormattedText(); } else if ( isCaretWithinFormattedText && ! formats[ start ] ) { this.props.onExitFormattedText(); } - this.setState( { start, end } ); + let selectedFormat; + + if ( isCollapsed( value ) ) { + const formatsBefore = formats[ start - 1 ] || []; + const formatsAfter = formats[ start ] || []; + + selectedFormat = Math.min( formatsBefore.length, formatsAfter.length ); + } + + this.setState( { start, end, selectedFormat } ); + this.applyRecord( { ...value, selectedFormat } ); + + delete this.formatPlaceholder; } } @@ -415,13 +449,14 @@ export class RichText extends Component { onChange( record, { withoutHistory } = {} ) { this.applyRecord( record ); - const { start, end } = record; + const { start, end, formatPlaceholder, selectedFormat } = record; + this.formatPlaceholder = formatPlaceholder; this.onChangeEditableValue( record ); this.savedContent = this.valueToFormat( record ); this.props.onChange( this.savedContent ); - this.setState( { start, end } ); + this.setState( { start, end, selectedFormat } ); if ( ! withoutHistory ) { this.onCreateUndoLevel(); @@ -464,9 +499,9 @@ export class RichText extends Component { const empty = this.isEmpty(); // It is important to consider emptiness because an empty container - // will include a bogus TinyMCE BR node _after_ the caret, so in a - // forward deletion the isHorizontalEdge function will incorrectly - // interpret the presence of the bogus node as not being at the edge. + // will include a padding BR node _after_ the caret, so in a forward + // deletion the isHorizontalEdge function will incorrectly interpret the + // presence of the BR node as not being at the edge. const isEdge = ( empty || isHorizontalEdge( this.editableRef, isReverse ) ); if ( ! isEdge ) { @@ -494,7 +529,14 @@ export class RichText extends Component { * @param {KeyboardEvent} event The keydown event. */ onKeyDown( event ) { - const { keyCode } = event; + const { keyCode, shiftKey, altKey, metaKey } = event; + + if ( + ! shiftKey && ! altKey && ! metaKey && + ( keyCode === LEFT || keyCode === RIGHT ) + ) { + this.handleHorizontalNavigation( event ); + } if ( keyCode === DELETE || keyCode === BACKSPACE ) { const value = this.createRecord(); @@ -584,6 +626,85 @@ export class RichText extends Component { } } + handleHorizontalNavigation( event ) { + const value = this.createRecord(); + const { formats, text, start, end } = value; + const { selectedFormat } = this.state; + const collapsed = isCollapsed( value ); + const isReverse = event.keyCode === LEFT; + + delete this.formatPlaceholder; + + // If the selection is collapsed and at the very start, do nothing if + // navigating backward. + // If the selection is collapsed and at the very end, do nothing if + // navigating forward. + if ( collapsed && selectedFormat === 0 ) { + if ( start === 0 && isReverse ) { + return; + } + + if ( end === text.length && ! isReverse ) { + return; + } + } + + // If the selection is not collapsed, let the browser handle collapsing + // the selection for now. Later we could expand this logic to set + // boundary positions if needed. + if ( ! collapsed ) { + return; + } + + // In all other cases, prevent default behaviour. + event.preventDefault(); + + // Ignore the selection change handler when setting selection, all state + // will be set here. + this.ignoreSelectionChange = true; + + const formatsBefore = formats[ start - 1 ] || []; + const formatsAfter = formats[ start ] || []; + + let newSelectedFormat = selectedFormat; + + // If the amount of formats before the caret and after the caret is + // different, the caret is at a format boundary. + if ( formatsBefore.length < formatsAfter.length ) { + if ( ! isReverse && selectedFormat < formatsAfter.length ) { + newSelectedFormat++; + } + + if ( isReverse && selectedFormat > formatsBefore.length ) { + newSelectedFormat--; + } + } else if ( formatsBefore.length > formatsAfter.length ) { + if ( ! isReverse && selectedFormat > formatsAfter.length ) { + newSelectedFormat--; + } + + if ( isReverse && selectedFormat < formatsBefore.length ) { + newSelectedFormat++; + } + } + + if ( newSelectedFormat !== selectedFormat ) { + this.applyRecord( { ...value, selectedFormat: newSelectedFormat } ); + this.setState( { selectedFormat: newSelectedFormat } ); + return; + } + + const newPos = value.start + ( isReverse ? -1 : 1 ); + + this.setState( { start: newPos, end: newPos } ); + this.applyRecord( { + ...value, + start: newPos, + end: newPos, + selectedFormat: isReverse ? formatsBefore.length : formatsAfter.length, + } ); + } + /** * Splits the content at the location of the selection. * @@ -739,11 +860,6 @@ export class RichText extends Component { value, multilineTag: this.multilineTag, multilineWrapperTags: this.multilineWrapperTags, - createLinePadding( doc ) { - const element = doc.createElement( 'br' ); - element.setAttribute( 'data-mce-bogus', '1' ); - return element; - }, prepareEditableTree: this.props.prepareEditableTree, } ).body.innerHTML; } @@ -783,6 +899,7 @@ export class RichText extends Component { value, multilineTag: this.multilineTag, multilineWrapperTags: this.multilineWrapperTags, + isEditableTree: false, } ).body.childNodes ); } @@ -812,13 +929,12 @@ export class RichText extends Component { onTagNameChange, } = this.props; + // Generating a key that includes `tagName` ensures that if the tag + // changes, we replace the relevant element. This is needed because we + // prevent Editable component updates. + const key = Tagname; const MultilineTag = this.multilineTag; const ariaProps = pickAriaProps( this.props ); - - // Generating a key that includes `tagName` ensures that if the tag - // changes, we unmount and destroy the previous TinyMCE element, then - // mount and initialize a new child element in its place. - const key = [ 'editor', Tagname ].join(); const isPlaceholderVisible = placeholder && ( ! isSelected || keepPlaceholderOnFocus ) && this.isEmpty(); const classes = classnames( wrapperClassName, 'editor-rich-text' ); const record = this.getRecord(); @@ -853,7 +969,7 @@ export class RichText extends Component { > { ( { listBoxId, activeId } ) => ( <Fragment> - <TinyMCE + <Editable tagName={ Tagname } style={ style } record={ record } @@ -878,7 +994,7 @@ export class RichText extends Component { /> { isPlaceholderVisible && <Tagname - className={ classnames( 'editor-rich-text__tinymce', className ) } + className={ classnames( 'editor-rich-text__editable', className ) } style={ style } > { MultilineTag ? <MultilineTag>{ placeholder }</MultilineTag> : placeholder } diff --git a/packages/editor/src/components/rich-text/style.scss b/packages/editor/src/components/rich-text/style.scss index 0907de79535d1..097fa156607dd 100644 --- a/packages/editor/src/components/rich-text/style.scss +++ b/packages/editor/src/components/rich-text/style.scss @@ -4,7 +4,7 @@ position: relative; } -.editor-rich-text__tinymce { +.editor-rich-text__editable { margin: 0; position: relative; line-height: $editor-line-height; @@ -48,33 +48,20 @@ } } - // Style TinyMCE inline boundaries on select inline text elements. &:focus { - a, - b, - i, - strong, - em, - del, - ins, - sup, - sub { - &[data-mce-selected] { - padding: 0 2px; - margin: 0 -2px; - border-radius: 2px; - box-shadow: 0 0 0 1px $light-gray-400; - background: $light-gray-400; - - // Enforce a dark text color so active inline boundaries - // are always readable. - // See https://github.com/WordPress/gutenberg/issues/9508 - color: $dark-gray-900; - } + *[data-rich-text-format-boundary] { + border-radius: 2px; + box-shadow: 0 0 0 1px $light-gray-400; + background: $light-gray-400; + + // Enforce a dark text color so active inline boundaries + // are always readable. + // See https://github.com/WordPress/gutenberg/issues/9508 + color: $dark-gray-900; } // Link inline boundaries get special colors. - a[data-mce-selected] { + a[data-rich-text-format-boundary] { box-shadow: 0 0 0 1px $blue-medium-100; background: $blue-medium-100; color: $blue-medium-900; @@ -82,22 +69,12 @@ // <code> inline boundaries need special treatment because their // un-selected style is already padded. - code[data-mce-selected] { + code[data-rich-text-format-boundary] { background: $light-gray-400; box-shadow: 0 0 0 1px $light-gray-400; } } - img { - &[data-mce-selected] { - outline: none; - } - - &::selection { - background: none !important; - } - } - &[data-is-placeholder-visible="true"] { position: absolute; top: 0; @@ -113,7 +90,7 @@ } // Placeholder text. - & + .editor-rich-text__tinymce { + & + .editor-rich-text__editable { pointer-events: none; // Use opacity to work in various editor styles. @@ -126,7 +103,7 @@ // Captions may have lighter (gray) text, or be shown on a range of different background luminosites. // To ensure legibility, we increase the default placeholder opacity to ensure contrast. - &[data-is-placeholder-visible="true"] + figcaption.editor-rich-text__tinymce { + &[data-is-placeholder-visible="true"] + figcaption.editor-rich-text__editable { opacity: 0.8; } } diff --git a/packages/editor/src/components/rich-text/tinymce.js b/packages/editor/src/components/rich-text/tinymce.js deleted file mode 100644 index 529fa15c1a54c..0000000000000 --- a/packages/editor/src/components/rich-text/tinymce.js +++ /dev/null @@ -1,381 +0,0 @@ -/** - * External dependencies - */ -import tinymce from 'tinymce'; -import { isEqual, noop } from 'lodash'; -import classnames from 'classnames'; - -/** - * WordPress dependencies - */ -import { Component, createElement } from '@wordpress/element'; -import { BACKSPACE, DELETE, ENTER, LEFT, RIGHT } from '@wordpress/keycodes'; -import { isEntirelySelected } from '@wordpress/dom'; - -/** - * Internal dependencies - */ -import { diffAriaProps, pickAriaProps } from './aria'; - -/** - * Browser dependencies - */ - -const { getSelection } = window; -const { TEXT_NODE } = window.Node; -const { userAgent } = window.navigator; - -/** - * Zero-width space character used by TinyMCE as a caret landing point for - * inline boundary nodes. - * - * @see tinymce/src/core/main/ts/text/Zwsp.ts - * - * @type {string} - */ -export const TINYMCE_ZWSP = '\uFEFF'; - -/** - * Applies a fix that provides `input` events for contenteditable in Internet Explorer. - * - * @param {Element} editorNode The root editor node. - * - * @return {Function} A function to remove the fix (for cleanup). - */ -function applyInternetExplorerInputFix( editorNode ) { - /** - * Dispatches `input` events in response to `textinput` events. - * - * IE provides a `textinput` event that is similar to an `input` event, - * and we use it to manually dispatch an `input` event. - * `textinput` is dispatched for text entry but for not deletions. - * - * @param {Event} textInputEvent An Internet Explorer `textinput` event. - */ - function mapTextInputEvent( textInputEvent ) { - textInputEvent.stopImmediatePropagation(); - - const inputEvent = document.createEvent( 'Event' ); - inputEvent.initEvent( 'input', true, false ); - inputEvent.data = textInputEvent.data; - textInputEvent.target.dispatchEvent( inputEvent ); - } - - /** - * Dispatches `input` events in response to Delete and Backspace keyup. - * - * It would be better dispatch an `input` event after each deleting - * `keydown` because the DOM is updated after each, but it is challenging - * to determine the right time to dispatch `input` since propagation of - * `keydown` can be stopped at any point. - * - * It's easier to listen for `keyup` in the capture phase and dispatch - * `input` before `keyup` propagates further. It's not perfect, but should - * be good enough. - * - * @param {KeyboardEvent} keyUp - * @param {Node} keyUp.target The event target. - * @param {number} keyUp.keyCode The key code. - */ - function mapDeletionKeyUpEvents( { target, keyCode } ) { - const isDeletion = BACKSPACE === keyCode || DELETE === keyCode; - - if ( isDeletion && editorNode.contains( target ) ) { - const inputEvent = document.createEvent( 'Event' ); - inputEvent.initEvent( 'input', true, false ); - inputEvent.data = null; - target.dispatchEvent( inputEvent ); - } - } - - editorNode.addEventListener( 'textinput', mapTextInputEvent ); - document.addEventListener( 'keyup', mapDeletionKeyUpEvents, true ); - return function removeInternetExplorerInputFix() { - editorNode.removeEventListener( 'textinput', mapTextInputEvent ); - document.removeEventListener( 'keyup', mapDeletionKeyUpEvents, true ); - }; -} - -const IS_PLACEHOLDER_VISIBLE_ATTR_NAME = 'data-is-placeholder-visible'; - -/** - * Whether or not the user agent is Internet Explorer. - * - * @type {boolean} - */ -const IS_IE = userAgent.indexOf( 'Trident' ) >= 0; - -export default class TinyMCE extends Component { - constructor() { - super(); - this.bindEditorNode = this.bindEditorNode.bind( this ); - this.onFocus = this.onFocus.bind( this ); - this.onKeyDown = this.onKeyDown.bind( this ); - this.initialize = this.initialize.bind( this ); - } - - onFocus() { - if ( this.props.onFocus ) { - this.props.onFocus(); - } - - this.initialize(); - } - - // We must prevent rerenders because RichText, the browser, and TinyMCE will - // modify the DOM. React will rerender the DOM fine, but we're losing - // selection and it would be more expensive to do so as it would just set - // the inner HTML through `dangerouslySetInnerHTML`. Instead RichText does - // it's own diffing and selection setting. - // - // Because we never update the component, we have to look through props and - // update the attributes on the wrapper nodes here. `componentDidUpdate` - // will never be called. - shouldComponentUpdate( nextProps ) { - this.configureIsPlaceholderVisible( nextProps.isPlaceholderVisible ); - - if ( ! isEqual( this.props.style, nextProps.style ) ) { - this.editorNode.setAttribute( 'style', '' ); - Object.assign( this.editorNode.style, nextProps.style ); - } - - if ( ! isEqual( this.props.className, nextProps.className ) ) { - this.editorNode.className = classnames( nextProps.className, 'editor-rich-text__tinymce' ); - } - - const { removedKeys, updatedKeys } = diffAriaProps( this.props, nextProps ); - removedKeys.forEach( ( key ) => - this.editorNode.removeAttribute( key ) ); - updatedKeys.forEach( ( key ) => - this.editorNode.setAttribute( key, nextProps[ key ] ) ); - - return false; - } - - componentWillUnmount() { - if ( ! this.editor ) { - return; - } - - this.editor.destroy(); - delete this.editor; - } - - configureIsPlaceholderVisible( isPlaceholderVisible ) { - const isPlaceholderVisibleString = String( !! isPlaceholderVisible ); - if ( this.editorNode.getAttribute( IS_PLACEHOLDER_VISIBLE_ATTR_NAME ) !== isPlaceholderVisibleString ) { - this.editorNode.setAttribute( IS_PLACEHOLDER_VISIBLE_ATTR_NAME, isPlaceholderVisibleString ); - } - } - - /** - * Initializes TinyMCE. Can only be called once per instance. - */ - initialize() { - if ( this.initialize.called ) { - return; - } - - this.initialize.called = true; - - const { multilineTag } = this.props; - const settings = { - theme: false, - inline: true, - toolbar: false, - browser_spellcheck: true, - entity_encoding: 'raw', - convert_urls: false, - // Disables TinyMCE's parsing to verify HTML. It makes - // initialisation a bit faster. Since we're setting raw HTML - // already with dangerouslySetInnerHTML, we don't need this to be - // verified. - verify_html: false, - inline_boundaries_selector: 'a[href],code,b,i,strong,em,del,ins,sup,sub', - plugins: [], - forced_root_block: multilineTag || false, - // Allow TinyMCE to keep one undo level for comparing changes. - // Prevent it otherwise from accumulating any history. - custom_undo_redo_levels: 1, - lists_indent_on_tab: false, - }; - - tinymce.init( { - ...settings, - target: this.editorNode, - setup: ( editor ) => { - this.editor = editor; - - // TinyMCE resets the element content on initialization, even - // when it's already identical to what exists currently. This - // behavior clobbers a selection which exists at the time of - // initialization, thus breaking writing flow navigation. The - // hack here neutralizes setHTML during initialization. - let setHTML; - - editor.on( 'preinit', () => { - setHTML = editor.dom.setHTML; - editor.dom.setHTML = () => {}; - } ); - - editor.on( 'init', () => { - // History is handled internally by RichText. - // - // See: https://github.com/tinymce/tinymce/blob/master/src/core/main/ts/api/UndoManager.ts - [ 'z', 'y' ].forEach( ( character ) => { - editor.shortcuts.remove( `meta+${ character }` ); - } ); - editor.shortcuts.remove( 'meta+shift+z' ); - - // Reset TinyMCE's default formatting shortcuts, since - // RichText supports only registered formats. - // - // See: https://github.com/tinymce/tinymce/blob/master/src/core/main/ts/keyboard/FormatShortcuts.ts - [ 'b', 'i', 'u' ].forEach( ( character ) => { - editor.shortcuts.remove( `meta+${ character }` ); - } ); - [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ].forEach( ( number ) => { - editor.shortcuts.remove( `access+${ number }` ); - } ); - - // Restore the original `setHTML` once initialized. - editor.dom.setHTML = setHTML; - - // In IE11, focus is lost to parent after initialising - // TinyMCE, so we have to set it back. - if ( - IS_IE && - document.activeElement !== this.editorNode && - document.activeElement.contains( this.editorNode ) - ) { - this.editorNode.focus(); - } - } ); - - editor.on( 'keydown', this.onKeyDown, true ); - }, - } ); - } - - bindEditorNode( editorNode ) { - this.editorNode = editorNode; - - if ( this.props.setRef ) { - this.props.setRef( editorNode ); - } - - if ( IS_IE ) { - if ( editorNode ) { - // Mounting: - this.removeInternetExplorerInputFix = applyInternetExplorerInputFix( editorNode ); - } else { - // Unmounting: - this.removeInternetExplorerInputFix(); - } - } - } - - onKeyDown( event ) { - const { keyCode } = event; - const isDelete = keyCode === DELETE || keyCode === BACKSPACE; - - // Disables TinyMCE behaviour. - if ( - keyCode === ENTER || - ( isDelete && isEntirelySelected( this.editorNode ) ) - ) { - event.preventDefault(); - // For some reason this is needed to also prevent the insertion of - // line breaks. - return false; - } - - // Handles a horizontal navigation key down event to handle the case - // where TinyMCE attempts to preventDefault when on the outside edge of - // an inline boundary when arrowing _away_ from the boundary, not within - // it. Replaces the TinyMCE event `preventDefault` behavior with a noop, - // such that those relying on `defaultPrevented` are not misinformed - // about the arrow event. - // - // If TinyMCE#4476 is resolved, this handling may be removed. - // - // @see https://github.com/tinymce/tinymce/issues/4476 - if ( keyCode !== LEFT && keyCode !== RIGHT ) { - return; - } - - const { focusNode } = getSelection(); - const { nodeType, nodeValue } = focusNode; - - if ( nodeType !== TEXT_NODE ) { - return; - } - - if ( nodeValue.length !== 1 || nodeValue[ 0 ] !== TINYMCE_ZWSP ) { - return; - } - - // Consider to be moving away from inline boundary based on: - // - // 1. Within a text fragment consisting only of ZWSP. - // 2. If in reverse, there is no previous sibling. If forward, there is - // no next sibling (i.e. end of node). - const isReverse = event.keyCode === LEFT; - const edgeSibling = isReverse ? 'previousSibling' : 'nextSibling'; - if ( ! focusNode[ edgeSibling ] ) { - // Note: This is not reassigning on the native event, rather the - // "fixed" TinyMCE copy, which proxies its preventDefault to the - // native event. By reassigning here, we're effectively preventing - // the proxied call on the native event, but not otherwise mutating - // the original event object. - event.preventDefault = noop; - } - } - - render() { - const ariaProps = pickAriaProps( this.props ); - const { - tagName = 'div', - style, - record, - valueToEditableHTML, - className, - isPlaceholderVisible, - onPaste, - onInput, - onKeyDown, - onCompositionEnd, - onBlur, - } = this.props; - - /* - * The role=textbox and aria-multiline=true must always be used together - * as TinyMCE always behaves like a sort of textarea where text wraps in - * multiple lines. Only the table block editable element is excluded. - */ - if ( tagName !== 'table' ) { - ariaProps.role = 'textbox'; - ariaProps[ 'aria-multiline' ] = true; - } - - // If a default value is provided, render it into the DOM even before - // TinyMCE finishes initializing. This avoids a short delay by allowing - // us to show and focus the content before it's truly ready to edit. - return createElement( tagName, { - ...ariaProps, - className: classnames( className, 'editor-rich-text__tinymce' ), - contentEditable: true, - [ IS_PLACEHOLDER_VISIBLE_ATTR_NAME ]: isPlaceholderVisible, - ref: this.bindEditorNode, - style, - suppressContentEditableWarning: true, - dangerouslySetInnerHTML: { __html: valueToEditableHTML( record ) }, - onPaste, - onInput, - onFocus: this.onFocus, - onBlur, - onKeyDown, - onCompositionEnd, - } ); - } -} diff --git a/packages/editor/src/components/writing-flow/index.js b/packages/editor/src/components/writing-flow/index.js index 235e32fb1dc94..0d4ea96eaa7c4 100644 --- a/packages/editor/src/components/writing-flow/index.js +++ b/packages/editor/src/components/writing-flow/index.js @@ -237,7 +237,8 @@ class WritingFlow extends Component { // This logic inside this condition needs to be checked before // the check for event.nativeEvent.defaultPrevented. - // The logic handles meta+a keypress and this event is default prevented by TinyMCE. + // The logic handles meta+a keypress and this event is default prevented + // by RichText. if ( ! isNav ) { // Set immediately before the meta+a combination can be pressed. if ( isKeyboardEvent.primary( event ) ) { @@ -246,8 +247,8 @@ class WritingFlow extends Component { if ( isKeyboardEvent.primary( event, 'a' ) ) { // When the target is contentEditable, selection will already - // have been set by TinyMCE earlier in this call stack. We need - // check the previous result, otherwise all blocks will be + // have been set by the browser earlier in this call stack. We + // need check the previous result, otherwise all blocks will be // selected right away. if ( target.isContentEditable ? this.isEntirelySelected : isEntirelySelected( target ) ) { onMultiSelect( first( blocks ), last( blocks ) ); @@ -262,7 +263,7 @@ class WritingFlow extends Component { return; } - // Abort if navigation has already been handled (e.g. TinyMCE inline + // Abort if navigation has already been handled (e.g. RichText inline // boundaries). if ( event.nativeEvent.defaultPrevented ) { return; diff --git a/packages/rich-text/CHANGELOG.md b/packages/rich-text/CHANGELOG.md index b0cb3b8617db7..cd141e933a9b8 100644 --- a/packages/rich-text/CHANGELOG.md +++ b/packages/rich-text/CHANGELOG.md @@ -1,3 +1,13 @@ +## 3.1.0 (Unreleased) + +### Enhancement + +- Added format boundaries. +- Removed parameters from `create` to filter out content. +- Removed the `createLinePadding` from `apply`, which is now built in. +- Improved format placeholder. +- Improved dom diffing. + ## 3.0.4 (2019-01-03) ## 3.0.3 (2018-12-12) diff --git a/packages/rich-text/README.md b/packages/rich-text/README.md index c769039253f9c..be3d703b2678f 100644 --- a/packages/rich-text/README.md +++ b/packages/rich-text/README.md @@ -24,14 +24,10 @@ create( { ?range: Range, ?multilineTag: string, ?multilineWrapperTags: Array, - ?removeNode: Function, - ?unwrapNode: Function, - ?filterString: Function, - ?removeAttribute: Function, } ): Object ``` -Create a RichText value from an `Element` tree (DOM), an HTML string or a plain text string, with optionally a `Range` object to set the selection. If called without any arguments, an empty value will be created. If `multilineTag` is provided, any content of direct children whose type matches `multilineTag` will be separated by a line separator. The remaining parameters can be used to filter out content. +Create a RichText value from an `Element` tree (DOM), an HTML string or a plain text string, with optionally a `Range` object to set the selection. If called without any arguments, an empty value will be created. If `multilineTag` is provided, any content of direct children whose type matches `multilineTag` will be separated by a line separator. ### toHTMLString @@ -53,7 +49,6 @@ apply( { current: Element, ?multilineTag: string ?multilineWrapperTags: Array, - ?createLinePadding: Function, } ): void ``` diff --git a/packages/rich-text/src/apply-format.js b/packages/rich-text/src/apply-format.js index 5f0f8dd00c386..271be1176931d 100644 --- a/packages/rich-text/src/apply-format.js +++ b/packages/rich-text/src/apply-format.js @@ -59,10 +59,7 @@ export function applyFormat( text, start, end, - formatPlaceholder: { - index: startIndex, - format: hasType ? undefined : format, - }, + formatPlaceholder: hasType ? undefined : format, }; } } else { diff --git a/packages/rich-text/src/create.js b/packages/rich-text/src/create.js index d320e5fcfcdf2..90d54c82c4561 100644 --- a/packages/rich-text/src/create.js +++ b/packages/rich-text/src/create.js @@ -98,14 +98,6 @@ function toFormat( { type, attributes } ) { * multiline. * @param {?Array} $1.multilineWrapperTags Tags where lines can be found if * nesting is possible. - * @param {?Function} $1.removeNode Function to declare whether the - * given node should be removed. - * @param {?Function} $1.unwrapNode Function to declare whether the - * given node should be unwrapped. - * @param {?Function} $1.filterString Function to filter the given - * string. - * @param {?Function} $1.removeAttribute Wether to remove an attribute - * based on the name. * * @return {Object} A rich text value. */ @@ -116,10 +108,6 @@ export function create( { range, multilineTag, multilineWrapperTags, - removeNode, - unwrapNode, - filterString, - removeAttribute, } = {} ) { if ( typeof text === 'string' && text.length > 0 ) { return { @@ -140,10 +128,6 @@ export function create( { return createFromElement( { element, range, - removeNode, - unwrapNode, - filterString, - removeAttribute, } ); } @@ -152,10 +136,6 @@ export function create( { range, multilineTag, multilineWrapperTags, - removeNode, - unwrapNode, - filterString, - removeAttribute, } ); } @@ -252,6 +232,12 @@ function filterRange( node, range, filter ) { return { startContainer, startOffset, endContainer, endOffset }; } +function filterString( string ) { + // Reduce any whitespace used for HTML formatting to one space + // character, because it will also be displayed as such by the browser. + return string.replace( /[\n\r\t]+/g, ' ' ); +} + /** * Creates a Rich Text value from a DOM element and range. * @@ -262,14 +248,6 @@ function filterRange( node, range, filter ) { * multiline. * @param {?Array} $1.multilineWrapperTags Tags where lines can be found if * nesting is possible. - * @param {?Function} $1.removeNode Function to declare whether the - * given node should be removed. - * @param {?Function} $1.unwrapNode Function to declare whether the - * given node should be unwrapped. - * @param {?Function} $1.filterString Function to filter the given - * string. - * @param {?Function} $1.removeAttribute Wether to remove an attribute - * based on the name. * * @return {Object} A rich text value. */ @@ -279,10 +257,6 @@ function createFromElement( { multilineTag, multilineWrapperTags, currentWrapperTags = [], - removeNode, - unwrapNode, - filterString, - removeAttribute, } ) { const accumulator = createEmptyValue(); @@ -297,26 +271,14 @@ function createFromElement( { const length = element.childNodes.length; - const filterStringComplete = ( string ) => { - // Reduce any whitespace used for HTML formatting to one space - // character, because it will also be displayed as such by the browser. - string = string.replace( /[\n\r\t]+/g, ' ' ); - - if ( filterString ) { - string = filterString( string ); - } - - return string; - }; - // Optimise for speed. for ( let index = 0; index < length; index++ ) { const node = element.childNodes[ index ]; const type = node.nodeName.toLowerCase(); if ( node.nodeType === TEXT_NODE ) { - const text = filterStringComplete( node.nodeValue ); - range = filterRange( node, range, filterStringComplete ); + const text = filterString( node.nodeValue ); + range = filterRange( node, range, filterString ); accumulateSelection( accumulator, node, range, { text } ); accumulator.text += text; // Create a sparse array of the same length as `text`, in which @@ -329,10 +291,7 @@ function createFromElement( { continue; } - if ( - ( removeNode && removeNode( node ) ) || - ( unwrapNode && unwrapNode( node ) && ! node.hasChildNodes() ) - ) { + if ( node.getAttribute( 'data-rich-text-padding' ) ) { accumulateSelection( accumulator, node, range, createEmptyValue() ); continue; } @@ -346,38 +305,30 @@ function createFromElement( { const lastFormats = accumulator.formats[ accumulator.formats.length - 1 ]; const lastFormat = lastFormats && lastFormats[ lastFormats.length - 1 ]; - let format; - let value; + const newFormat = toFormat( { + type, + attributes: getAttributes( { element: node } ), + } ); - if ( ! unwrapNode || ! unwrapNode( node ) ) { - const newFormat = toFormat( { - type, - attributes: getAttributes( { - element: node, - removeAttribute, - } ), - } ); + let format; - if ( newFormat ) { - // Reuse the last format if it's equal. - if ( isFormatEqual( newFormat, lastFormat ) ) { - format = lastFormat; - } else { - format = newFormat; - } + if ( newFormat ) { + // Reuse the last format if it's equal. + if ( isFormatEqual( newFormat, lastFormat ) ) { + format = lastFormat; + } else { + format = newFormat; } } + let value; + if ( multilineWrapperTags && multilineWrapperTags.indexOf( type ) !== -1 ) { value = createFromMultilineElement( { element: node, range, multilineTag, multilineWrapperTags, - removeNode, - unwrapNode, - filterString, - removeAttribute, currentWrapperTags: [ ...currentWrapperTags, format ], } ); format = undefined; @@ -387,10 +338,6 @@ function createFromElement( { range, multilineTag, multilineWrapperTags, - removeNode, - unwrapNode, - filterString, - removeAttribute, } ); } @@ -458,14 +405,6 @@ function createFromElement( { * multiline. * @param {?Array} $1.multilineWrapperTags Tags where lines can be found if * nesting is possible. - * @param {?Function} $1.removeNode Function to declare whether the - * given node should be removed. - * @param {?Function} $1.unwrapNode Function to declare whether the - * given node should be unwrapped. - * @param {?Function} $1.filterString Function to filter the given - * string. - * @param {?Function} $1.removeAttribute Wether to remove an attribute - * based on the name. * @param {boolean} $1.currentWrapperTags Whether to prepend a line * separator. * @@ -476,10 +415,6 @@ function createFromMultilineElement( { range, multilineTag, multilineWrapperTags, - removeNode, - unwrapNode, - filterString, - removeAttribute, currentWrapperTags = [], } ) { const accumulator = createEmptyValue(); @@ -504,10 +439,6 @@ function createFromMultilineElement( { multilineTag, multilineWrapperTags, currentWrapperTags, - removeNode, - unwrapNode, - filterString, - removeAttribute, } ); // If a line consists of one single line break (invisible), consider the @@ -548,16 +479,11 @@ function createFromMultilineElement( { * * @param {Object} $1 Named argements. * @param {Element} $1.element Element to get attributes from. - * @param {?Function} $1.removeAttribute Wether to remove an attribute based on - * the name. * * @return {?Object} Attribute object or `undefined` if the element has no * attributes. */ -function getAttributes( { - element, - removeAttribute, -} ) { +function getAttributes( { element } ) { if ( ! element.hasAttributes() ) { return; } @@ -569,7 +495,7 @@ function getAttributes( { for ( let i = 0; i < length; i++ ) { const { name, value } = element.attributes[ i ]; - if ( removeAttribute && removeAttribute( name ) ) { + if ( name === 'data-rich-text-format-boundary' ) { continue; } diff --git a/packages/rich-text/src/get-active-format.js b/packages/rich-text/src/get-active-format.js index 090a9f1d8ccbb..89fcb5fc7711c 100644 --- a/packages/rich-text/src/get-active-format.js +++ b/packages/rich-text/src/get-active-format.js @@ -4,6 +4,12 @@ import { find } from 'lodash'; +/** + * Internal dependencies + */ + +import { getActiveFormats } from './get-active-formats'; + /** * Gets the format object by type at the start of the selection. This can be * used to get e.g. the URL of a link format at the current selection, but also @@ -15,10 +21,6 @@ import { find } from 'lodash'; * * @return {?Object} Active format object of the specified type, or undefined. */ -export function getActiveFormat( { formats, start }, formatType ) { - if ( start === undefined ) { - return; - } - - return find( formats[ start ], { type: formatType } ); +export function getActiveFormat( value, formatType ) { + return find( getActiveFormats( value ), { type: formatType } ); } diff --git a/packages/rich-text/src/get-active-formats.js b/packages/rich-text/src/get-active-formats.js new file mode 100644 index 0000000000000..fd0f869877bfb --- /dev/null +++ b/packages/rich-text/src/get-active-formats.js @@ -0,0 +1,23 @@ +/** + * Gets the all format objects at the start of the selection. + * + * @param {Object} value Value to inspect. + * + * @return {?Object} Active format objects. + */ +export function getActiveFormats( { formats, start, selectedFormat } ) { + if ( start === undefined ) { + return []; + } + + const formatsBefore = formats[ start - 1 ] || []; + const formatsAfter = formats[ start ] || []; + + let source = formatsAfter; + + if ( formatsBefore.length > formatsAfter.length ) { + source = formatsBefore; + } + + return source.slice( 0, selectedFormat ); +} diff --git a/packages/rich-text/src/special-characters.js b/packages/rich-text/src/special-characters.js index 04ffc18ce8862..611869f8dbe56 100644 --- a/packages/rich-text/src/special-characters.js +++ b/packages/rich-text/src/special-characters.js @@ -1,3 +1,2 @@ export const LINE_SEPARATOR = '\u2028'; export const OBJECT_REPLACEMENT_CHARACTER = '\ufffc'; -export const ZERO_WIDTH_NO_BREAK_SPACE = '\uFEFF'; diff --git a/packages/rich-text/src/test/__snapshots__/to-dom.js.snap b/packages/rich-text/src/test/__snapshots__/to-dom.js.snap index 0153822ba7ca8..78378090137bd 100644 --- a/packages/rich-text/src/test/__snapshots__/to-dom.js.snap +++ b/packages/rich-text/src/test/__snapshots__/to-dom.js.snap @@ -2,7 +2,9 @@ exports[`recordToDom should create a value with formatting 1`] = ` <body> - <em> + <em + data-rich-text-format-boundary="true" + > test </em> @@ -11,7 +13,9 @@ exports[`recordToDom should create a value with formatting 1`] = ` exports[`recordToDom should create a value with formatting for split tags 1`] = ` <body> - <em> + <em + data-rich-text-format-boundary="true" + > test </em> @@ -21,6 +25,7 @@ exports[`recordToDom should create a value with formatting for split tags 1`] = exports[`recordToDom should create a value with formatting with attributes 1`] = ` <body> <a + data-rich-text-format-boundary="true" href="#" > test @@ -79,7 +84,9 @@ exports[`recordToDom should create a value with image object and text before 1`] exports[`recordToDom should create a value with nested formatting 1`] = ` <body> <em> - <strong> + <strong + data-rich-text-format-boundary="true" + > test </strong> </em> @@ -96,57 +103,32 @@ exports[`recordToDom should create a value without formatting 1`] = ` exports[`recordToDom should create an empty value 1`] = ` <body> - <br /> + <br + data-rich-text-padding="true" + /> </body> `; exports[`recordToDom should create an empty value from empty tags 1`] = ` <body> - <br /> + <br + data-rich-text-padding="true" + /> </body> `; -exports[`recordToDom should filter format attributes with settings 1`] = ` +exports[`recordToDom should filter format boundary attributes 1`] = ` <body> - <strong> + <strong + data-rich-text-format-boundary="true" + > test </strong> </body> `; -exports[`recordToDom should filter text at end with settings 1`] = ` -<body> - test -</body> -`; - -exports[`recordToDom should filter text in format with settings 1`] = ` -<body> - <em> - test - </em> - -</body> -`; - -exports[`recordToDom should filter text outside format with settings 1`] = ` -<body> - <em> - test - </em> - -</body> -`; - -exports[`recordToDom should filter text with settings 1`] = ` -<body> - - <br /> -</body> -`; - exports[`recordToDom should handle br 1`] = ` <body> @@ -157,7 +139,9 @@ exports[`recordToDom should handle br 1`] = ` exports[`recordToDom should handle br with formatting 1`] = ` <body> - <em> + <em + data-rich-text-format-boundary="true" + > <br /> @@ -188,7 +172,9 @@ exports[`recordToDom should handle empty list value 1`] = ` <body> <li> - <br /> + <br + data-rich-text-padding="true" + /> </li> </body> `; @@ -197,7 +183,9 @@ exports[`recordToDom should handle empty multiline value 1`] = ` <body> <p> - <br /> + <br + data-rich-text-padding="true" + /> </p> </body> `; @@ -206,15 +194,21 @@ exports[`recordToDom should handle middle empty list value 1`] = ` <body> <li> - <br /> + <br + data-rich-text-padding="true" + /> </li> <li> - <br /> + <br + data-rich-text-padding="true" + /> </li> <li> - <br /> + <br + data-rich-text-padding="true" + /> </li> </body> `; @@ -272,7 +266,9 @@ exports[`recordToDom should handle multiline value with empty 1`] = ` </p> <p> - <br /> + <br + data-rich-text-padding="true" + /> </p> </body> `; @@ -280,11 +276,15 @@ exports[`recordToDom should handle multiline value with empty 1`] = ` exports[`recordToDom should handle nested empty list value 1`] = ` <body> <li> - <br /> + <br + data-rich-text-padding="true" + /> <ul> <li> - <br /> + <br + data-rich-text-padding="true" + /> </li> </ul> </li> @@ -325,7 +325,9 @@ exports[`recordToDom should preserve emoji 1`] = ` exports[`recordToDom should preserve emoji in formatting 1`] = ` <body> - <em> + <em + data-rich-text-format-boundary="true" + > 🍒 </em> @@ -338,23 +340,12 @@ exports[`recordToDom should preserve non breaking space 1`] = ` </body> `; -exports[`recordToDom should remove br with settings 1`] = ` +exports[`recordToDom should remove padding 1`] = ` <body> - <br /> -</body> -`; - -exports[`recordToDom should remove with children with settings 1`] = ` -<body> - two -</body> -`; - -exports[`recordToDom should remove with settings 1`] = ` -<body> - - <br /> + <br + data-rich-text-padding="true" + /> </body> `; @@ -363,13 +354,3 @@ exports[`recordToDom should replace characters to format HTML with space 1`] = ` </body> `; - -exports[`recordToDom should unwrap with settings 1`] = ` -<body> - te - <em> - st - </em> - -</body> -`; diff --git a/packages/rich-text/src/test/apply-format.js b/packages/rich-text/src/test/apply-format.js index 3aa51c927e71c..de4f7b69e5f5e 100644 --- a/packages/rich-text/src/test/apply-format.js +++ b/packages/rich-text/src/test/apply-format.js @@ -61,10 +61,7 @@ describe( 'applyFormat', () => { }; const expected = { ...record, - formatPlaceholder: { - format: a2, - index: 0, - }, + formatPlaceholder: a2, }; const result = applyFormat( deepFreeze( record ), a2 ); diff --git a/packages/rich-text/src/test/create.js b/packages/rich-text/src/test/create.js index e08f6b82be6b4..5128c55273d78 100644 --- a/packages/rich-text/src/test/create.js +++ b/packages/rich-text/src/test/create.js @@ -29,7 +29,6 @@ describe( 'create', () => { description, multilineTag, multilineWrapperTags, - settings, html, createRange, record, @@ -46,7 +45,6 @@ describe( 'create', () => { range, multilineTag, multilineWrapperTags, - ...settings, } ); const formatsLength = getSparseArrayLength( record.formats ); const createdFormatsLength = getSparseArrayLength( createdRecord.formats ); diff --git a/packages/rich-text/src/test/get-active-format.js b/packages/rich-text/src/test/get-active-format.js index bce7808040990..4fa0ddaa3e3ec 100644 --- a/packages/rich-text/src/test/get-active-format.js +++ b/packages/rich-text/src/test/get-active-format.js @@ -18,14 +18,27 @@ describe( 'getActiveFormat', () => { expect( getActiveFormat( record, 'em' ) ).toEqual( em ); } ); - it( 'should get format by selection using the start', () => { + it( 'should not get any format if outside boundary position', () => { const record = { formats: [ [ em ], , [ em ] ], text: 'one', start: 1, end: 1, + selectedFormat: 0, }; expect( getActiveFormat( record, 'em' ) ).toBe( undefined ); } ); + + it( 'should get format if inside boundary position', () => { + const record = { + formats: [ [ em ], , [ em ] ], + text: 'one', + start: 1, + end: 1, + selectedFormat: 1, + }; + + expect( getActiveFormat( record, 'em' ) ).toBe( em ); + } ); } ); diff --git a/packages/rich-text/src/test/helpers/index.js b/packages/rich-text/src/test/helpers/index.js index 9673619d57be2..2828b36718ca7 100644 --- a/packages/rich-text/src/test/helpers/index.js +++ b/packages/rich-text/src/test/helpers/index.js @@ -527,27 +527,6 @@ export const spec = [ text: 'one', }, }, - { - description: 'should remove with settings', - settings: { - unwrapNode: ( node ) => !! node.getAttribute( 'data-mce-bogus' ), - }, - html: '<strong data-mce-bogus="true"></strong>', - createRange: ( element ) => ( { - startOffset: 0, - startContainer: element, - endOffset: 1, - endContainer: element, - } ), - startPath: [ 0, 0 ], - endPath: [ 0, 0 ], - record: { - start: 0, - end: 0, - formats: [], - text: '', - }, - }, { description: 'should ignore formats at line separator', multilineTag: 'p', @@ -559,11 +538,8 @@ export const spec = [ }, }, { - description: 'should remove br with settings', - settings: { - unwrapNode: ( node ) => !! node.getAttribute( 'data-mce-bogus' ), - }, - html: '<br data-mce-bogus="true">', + description: 'should remove padding', + html: '<br data-rich-text-padding="true">', createRange: ( element ) => ( { startOffset: 0, startContainer: element, @@ -580,53 +556,8 @@ export const spec = [ }, }, { - description: 'should unwrap with settings', - settings: { - unwrapNode: ( node ) => !! node.getAttribute( 'data-mce-bogus' ), - }, - html: '<strong data-mce-bogus="true">te<em>st</em></strong>', - createRange: ( element ) => ( { - startOffset: 0, - startContainer: element, - endOffset: 1, - endContainer: element, - } ), - startPath: [ 0, 0 ], - endPath: [ 1, 0, 2 ], - record: { - start: 0, - end: 4, - formats: [ , , [ em ], [ em ] ], - text: 'test', - }, - }, - { - description: 'should remove with children with settings', - settings: { - removeNode: ( node ) => node.getAttribute( 'data-mce-bogus' ) === 'all', - }, - html: '<strong data-mce-bogus="all">one</strong>two', - createRange: ( element ) => ( { - startOffset: 0, - startContainer: element.lastChild, - endOffset: 1, - endContainer: element.lastChild, - } ), - startPath: [ 0, 0 ], - endPath: [ 0, 1 ], - record: { - start: 0, - end: 1, - formats: [ , , , ], - text: 'two', - }, - }, - { - description: 'should filter format attributes with settings', - settings: { - removeAttribute: ( attribute ) => attribute.indexOf( 'data-mce-' ) === 0, - }, - html: '<strong data-mce-selected="inline-boundary">test</strong>', + description: 'should filter format boundary attributes', + html: '<strong data-rich-text-format-boundary="true">test</strong>', createRange: ( element ) => ( { startOffset: 0, startContainer: element, @@ -642,90 +573,6 @@ export const spec = [ text: 'test', }, }, - { - description: 'should filter text with settings', - settings: { - filterString: ( string ) => string.replace( '\uFEFF', '' ), - }, - html: '&#65279;', - createRange: ( element ) => ( { - startOffset: 0, - startContainer: element, - endOffset: 1, - endContainer: element, - } ), - startPath: [ 0, 0 ], - endPath: [ 0, 0 ], - record: { - start: 0, - end: 0, - formats: [], - text: '', - }, - }, - { - description: 'should filter text at end with settings', - settings: { - filterString: ( string ) => string.replace( '\uFEFF', '' ), - }, - html: 'test&#65279;', - createRange: ( element ) => ( { - startOffset: 4, - startContainer: element.firstChild, - endOffset: 4, - endContainer: element.firstChild, - } ), - startPath: [ 0, 4 ], - endPath: [ 0, 4 ], - record: { - start: 4, - end: 4, - formats: [ , , , , ], - text: 'test', - }, - }, - { - description: 'should filter text in format with settings', - settings: { - filterString: ( string ) => string.replace( '\uFEFF', '' ), - }, - html: '<em>test&#65279;</em>', - createRange: ( element ) => ( { - startOffset: 5, - startContainer: element.querySelector( 'em' ).firstChild, - endOffset: 5, - endContainer: element.querySelector( 'em' ).firstChild, - } ), - startPath: [ 0, 0, 4 ], - endPath: [ 0, 0, 4 ], - record: { - start: 4, - end: 4, - formats: [ [ em ], [ em ], [ em ], [ em ] ], - text: 'test', - }, - }, - { - description: 'should filter text outside format with settings', - settings: { - filterString: ( string ) => string.replace( '\uFEFF', '' ), - }, - html: '<em>test</em>&#65279;', - createRange: ( element ) => ( { - startOffset: 1, - startContainer: element.lastChild, - endOffset: 1, - endContainer: element.lastChild, - } ), - startPath: [ 0, 0, 4 ], - endPath: [ 0, 0, 4 ], - record: { - start: 4, - end: 4, - formats: [ [ em ], [ em ], [ em ], [ em ] ], - text: 'test', - }, - }, ]; export const specWithRegistration = [ diff --git a/packages/rich-text/src/test/to-dom.js b/packages/rich-text/src/test/to-dom.js index 16fed3f66d0a2..1df1e8e25b229 100644 --- a/packages/rich-text/src/test/to-dom.js +++ b/packages/rich-text/src/test/to-dom.js @@ -34,7 +34,6 @@ describe( 'recordToDom', () => { value: record, multilineTag, multilineWrapperTags, - createLinePadding: ( doc ) => doc.createElement( 'br' ), } ); expect( body ).toMatchSnapshot(); expect( selection ).toEqual( { startPath, endPath } ); diff --git a/packages/rich-text/src/to-dom.js b/packages/rich-text/src/to-dom.js index c811bbc618059..3d02aab725bd9 100644 --- a/packages/rich-text/src/to-dom.js +++ b/packages/rich-text/src/to-dom.js @@ -9,7 +9,7 @@ import { createElement } from './create-element'; * Browser dependencies */ -const { TEXT_NODE, ELEMENT_NODE } = window.Node; +const { TEXT_NODE } = window.Node; /** * Creates a path as an array of indices from the given root node to the given @@ -113,7 +113,13 @@ function remove( node ) { return node.parentNode.removeChild( node ); } -function padEmptyLines( { element, createLinePadding, multilineWrapperTags } ) { +function createLinePadding( doc ) { + const element = doc.createElement( 'br' ); + element.setAttribute( 'data-rich-text-padding', 'true' ); + return element; +} + +function padEmptyLines( { element, multilineWrapperTags } ) { const length = element.childNodes.length; const doc = element.ownerDocument; @@ -135,7 +141,7 @@ function padEmptyLines( { element, createLinePadding, multilineWrapperTags } ) { element.insertBefore( createLinePadding( doc ), child ); } - padEmptyLines( { element: child, createLinePadding, multilineWrapperTags } ); + padEmptyLines( { element: child, multilineWrapperTags } ); } } } @@ -150,8 +156,8 @@ export function toDom( { value, multilineTag, multilineWrapperTags, - createLinePadding, prepareEditableTree, + isEditableTree = true, } ) { let startPath = []; let endPath = []; @@ -177,11 +183,11 @@ export function toDom( { onEndIndex( body, pointer ) { endPath = createPathToNode( pointer, body, [ pointer.nodeValue.length ] ); }, - isEditableTree: true, + isEditableTree, } ); - if ( createLinePadding ) { - padEmptyLines( { element: tree, createLinePadding, multilineWrapperTags } ); + if ( isEditableTree ) { + padEmptyLines( { element: tree, multilineWrapperTags } ); } return { @@ -205,7 +211,6 @@ export function apply( { current, multilineTag, multilineWrapperTags, - createLinePadding, prepareEditableTree, } ) { // Construct a new element tree in memory. @@ -213,7 +218,6 @@ export function apply( { value, multilineTag, multilineWrapperTags, - createLinePadding, prepareEditableTree, } ); @@ -234,7 +238,38 @@ export function applyValue( future, current ) { if ( ! currentChild ) { current.appendChild( futureChild ); } else if ( ! currentChild.isEqualNode( futureChild ) ) { - current.replaceChild( futureChild, currentChild ); + if ( + currentChild.nodeName !== futureChild.nodeName || + ( currentChild.nodeType === TEXT_NODE && currentChild.data !== futureChild.data ) + ) { + current.replaceChild( futureChild, currentChild ); + } else { + const currentAttributes = currentChild.attributes; + const futureAttributes = futureChild.attributes; + + if ( currentAttributes ) { + for ( let ii = 0; ii < currentAttributes.length; ii++ ) { + const { name } = currentAttributes[ ii ]; + + if ( ! futureChild.getAttribute( name ) ) { + currentChild.removeAttribute( name ); + } + } + } + + if ( futureAttributes ) { + for ( let ii = 0; ii < futureAttributes.length; ii++ ) { + const { name, value } = futureAttributes[ ii ]; + + if ( currentChild.getAttribute( name ) !== value ) { + currentChild.setAttribute( name, value ); + } + } + } + + applyValue( futureChild, currentChild ); + future.removeChild( futureChild ); + } } else { future.removeChild( futureChild ); } @@ -266,47 +301,30 @@ function isRangeEqual( a, b ) { ); } -export function applySelection( selection, current ) { - const { node: startContainer, offset: startOffset } = getNodeByPath( current, selection.startPath ); - const { node: endContainer, offset: endOffset } = getNodeByPath( current, selection.endPath ); - - const windowSelection = window.getSelection(); - const range = current.ownerDocument.createRange(); - const collapsed = startContainer === endContainer && startOffset === endOffset; - - if ( - collapsed && - startOffset === 0 && - startContainer.previousSibling && - startContainer.previousSibling.nodeType === ELEMENT_NODE && - startContainer.previousSibling.nodeName !== 'BR' - ) { - startContainer.insertData( 0, '\uFEFF' ); - range.setStart( startContainer, 1 ); - range.setEnd( endContainer, 1 ); - } else if ( - collapsed && - startOffset === 0 && - startContainer === TEXT_NODE && - startContainer.nodeValue.length === 0 - ) { - startContainer.insertData( 0, '\uFEFF' ); - range.setStart( startContainer, 1 ); - range.setEnd( endContainer, 1 ); - } else { - range.setStart( startContainer, startOffset ); - range.setEnd( endContainer, endOffset ); - } +export function applySelection( { startPath, endPath }, current ) { + const { node: startContainer, offset: startOffset } = getNodeByPath( current, startPath ); + const { node: endContainer, offset: endOffset } = getNodeByPath( current, endPath ); + const selection = window.getSelection(); + const { ownerDocument } = current; + const range = ownerDocument.createRange(); + + range.setStart( startContainer, startOffset ); + range.setEnd( endContainer, endOffset ); - if ( windowSelection.rangeCount > 0 ) { + if ( selection.rangeCount > 0 ) { // If the to be added range and the live range are the same, there's no // need to remove the live range and add the equivalent range. - if ( isRangeEqual( range, windowSelection.getRangeAt( 0 ) ) ) { + if ( isRangeEqual( range, selection.getRangeAt( 0 ) ) ) { + // Set back focus if focus is lost. + if ( ownerDocument.activeElement !== current ) { + current.focus(); + } + return; } - windowSelection.removeAllRanges(); + selection.removeAllRanges(); } - windowSelection.addRange( range ); + selection.addRange( range ); } diff --git a/packages/rich-text/src/to-tree.js b/packages/rich-text/src/to-tree.js index be23219851a96..9ce962d540d49 100644 --- a/packages/rich-text/src/to-tree.js +++ b/packages/rich-text/src/to-tree.js @@ -2,24 +2,50 @@ * Internal dependencies */ +import { getActiveFormats } from './get-active-formats'; import { getFormatType } from './get-format-type'; import { LINE_SEPARATOR, OBJECT_REPLACEMENT_CHARACTER, - ZERO_WIDTH_NO_BREAK_SPACE, } from './special-characters'; -function fromFormat( { type, attributes, unregisteredAttributes, object } ) { +/** + * Converts a format object to information that can be used to create an element + * from (type, attributes and object). + * + * @param {Object} $1 Named parameters. + * @param {string} $1.type The format type. + * @param {Object} $1.attributes The format attributes. + * @param {Object} $1.unregisteredAttributes The unregistered format + * attributes. + * @param {boolean} $1.object Wether or not it is an object + * format. + * @param {boolean} $1.boundaryClass Wether or not to apply a boundary + * class. + * @return {Object} Information to be used for + * element creation. + */ +function fromFormat( { type, attributes, unregisteredAttributes, object, boundaryClass } ) { const formatType = getFormatType( type ); + let elementAttributes = {}; + + if ( boundaryClass ) { + elementAttributes[ 'data-rich-text-format-boundary' ] = 'true'; + } + if ( ! formatType ) { - return { type, attributes, object }; + if ( attributes ) { + elementAttributes = { ...attributes, ...elementAttributes }; + } + + return { type, attributes: elementAttributes, object }; } - const elementAttributes = { ...unregisteredAttributes }; + elementAttributes = { ...unregisteredAttributes, ...elementAttributes }; for ( const name in attributes ) { - const key = formatType.attributes[ name ]; + const key = formatType.attributes ? formatType.attributes[ name ] : false; if ( key ) { elementAttributes[ key ] = attributes[ name ]; @@ -43,6 +69,17 @@ function fromFormat( { type, attributes, unregisteredAttributes, object } ) { }; } +function getDeepestActiveFormat( value ) { + const activeFormats = getActiveFormats( value ); + const { selectedFormat } = value; + + if ( selectedFormat === undefined ) { + return activeFormats[ activeFormats.length - 1 ]; + } + + return activeFormats[ selectedFormat - 1 ]; +} + export function toTree( { value, multilineTag, @@ -59,10 +96,11 @@ export function toTree( { onEndIndex, isEditableTree, } ) { - const { formats, text, start, end, formatPlaceholder } = value; + const { formats, text, start, end } = value; const formatsLength = formats.length + 1; const tree = createEmpty(); const multilineFormat = { type: multilineTag }; + const deepestActiveFormat = getDeepestActiveFormat( value ); let lastSeparatorFormats; let lastCharacterFormats; @@ -76,22 +114,6 @@ export function toTree( { append( tree, '' ); } - function setFormatPlaceholder( pointer, index ) { - if ( isEditableTree && formatPlaceholder && formatPlaceholder.index === index ) { - const parent = getParent( pointer ); - - if ( formatPlaceholder.format === undefined ) { - pointer = getParent( parent ); - } else { - pointer = append( parent, fromFormat( formatPlaceholder.format ) ); - } - - pointer = append( pointer, ZERO_WIDTH_NO_BREAK_SPACE ); - } - - return pointer; - } - for ( let i = 0; i < formatsLength; i++ ) { const character = text.charAt( i ); let characterFormats = formats[ i ]; @@ -146,8 +168,23 @@ export function toTree( { return; } + const { type, attributes, unregisteredAttributes, object } = format; + + const boundaryClass = ( + isEditableTree && + ! object && + character !== LINE_SEPARATOR && + format === deepestActiveFormat + ); + const parent = getParent( pointer ); - const newNode = append( parent, fromFormat( format ) ); + const newNode = append( parent, fromFormat( { + type, + attributes, + unregisteredAttributes, + object, + boundaryClass, + } ) ); if ( isText( pointer ) && getText( pointer ).length === 0 ) { remove( pointer ); @@ -164,8 +201,6 @@ export function toTree( { continue; } - pointer = setFormatPlaceholder( pointer, 0 ); - // If there is selection at 0, handle it before characters are inserted. if ( i === 0 ) { if ( onStartIndex && start === 0 ) { @@ -189,8 +224,6 @@ export function toTree( { } } - pointer = setFormatPlaceholder( pointer, i + 1 ); - if ( onStartIndex && start === i + 1 ) { onStartIndex( tree, pointer ); } From a59aaf24ce0e081e772e19a2ce0e5c88afa62c77 Mon Sep 17 00:00:00 2001 From: Paul Sealock <psealock@gmail.com> Date: Sat, 9 Feb 2019 09:23:33 +1300 Subject: [PATCH 390/691] Datepicker: Allow null value for currentDate on mounting (#12963) * Datepicker: Allow null value for currentDate on mounting * flexible assertion, FTW --- packages/components/CHANGELOG.md | 1 + packages/components/src/date-time/README.md | 5 +- packages/components/src/date-time/date.js | 19 ++- .../components/src/date-time/test/date.js | 110 ++++++++++++++++++ 4 files changed, 131 insertions(+), 4 deletions(-) create mode 100644 packages/components/src/date-time/test/date.js diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index d0489fb5c4a4c..a21b3d305a7e2 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -18,6 +18,7 @@ - `Dropdown` now has a `focusOnMount` prop which is passed directly to the contained `Popover`. - `DatePicker` has new prop `isInvalidDate` exposing react-dates' `isOutsideRange`. +- `DatePicker` allows `null` as accepted value for `currentDate` prop to signify no date selection. ## 7.0.5 (2019-01-03) diff --git a/packages/components/src/date-time/README.md b/packages/components/src/date-time/README.md index 1366be8312635..f0fa63c69b6ce 100644 --- a/packages/components/src/date-time/README.md +++ b/packages/components/src/date-time/README.md @@ -41,10 +41,11 @@ The component accepts the following props: ### currentDate -The current date and time at initialization. +The current date and time at initialization. Optionally pass in a `null` value to specify no date is currently selected. - Type: `string` -- Required: Yes +- Required: No +- Default: today's date ### onChange diff --git a/packages/components/src/date-time/date.js b/packages/components/src/date-time/date.js index 92be3a3970a90..a8e4130e739c8 100644 --- a/packages/components/src/date-time/date.js +++ b/packages/components/src/date-time/date.js @@ -25,6 +25,7 @@ class DatePicker extends Component { onChangeMoment( newDate ) { const { currentDate, onChange } = this.props; + // If currentDate is null, use now as momentTime to designate hours, minutes, seconds. const momentDate = currentDate ? moment( currentDate ) : moment(); const momentTime = { hours: momentDate.hours(), @@ -35,10 +36,24 @@ class DatePicker extends Component { onChange( newDate.set( momentTime ).format( TIMEZONELESS_FORMAT ) ); } + /** + * Create a Moment object from a date string. With no currentDate supplied, default to a Moment + * object representing now. If a null value is passed, return a null value. + * + * @param {?string} currentDate Date representing the currently selected date or null to signify no selection. + * @return {?Moment} Moment object for selected date or null. + */ + getMomentDate( currentDate ) { + if ( null === currentDate ) { + return null; + } + return currentDate ? moment( currentDate ) : moment(); + } + render() { const { currentDate, isInvalidDate } = this.props; - const momentDate = currentDate ? moment( currentDate ) : moment(); + const momentDate = this.getMomentDate( currentDate ); return ( <div className="components-datetime__date"> @@ -49,7 +64,7 @@ class DatePicker extends Component { hideKeyboardShortcutsPanel // This is a hack to force the calendar to update on month or year change // https://github.com/airbnb/react-dates/issues/240#issuecomment-361776665 - key={ `datepicker-controller-${ momentDate.format( 'MM-YYYY' ) }` } + key={ `datepicker-controller-${ momentDate ? momentDate.format( 'MM-YYYY' ) : 'null' }` } noBorder numberOfMonths={ 1 } onDateChange={ this.onChangeMoment } diff --git a/packages/components/src/date-time/test/date.js b/packages/components/src/date-time/test/date.js new file mode 100644 index 0000000000000..a12999dbbc784 --- /dev/null +++ b/packages/components/src/date-time/test/date.js @@ -0,0 +1,110 @@ +/** + * External dependencies + */ +import { shallow } from 'enzyme'; +import moment from 'moment'; + +/** + * Internal dependencies + */ +import DatePicker from '../date'; + +const TIMEZONELESS_FORMAT = 'YYYY-MM-DDTHH:mm:ss'; + +describe( 'DatePicker', () => { + it( 'should pass down a moment object for currentDate', () => { + const currentDate = '1986-10-18T23:00:00'; + const wrapper = shallow( <DatePicker currentDate={ currentDate } /> ); + const date = wrapper.children().props().date; + expect( moment.isMoment( date ) ).toBe( true ); + expect( date.isSame( moment( currentDate ) ) ).toBe( true ); + } ); + + it( 'should pass down a null date when currentDate is set to null', () => { + const wrapper = shallow( <DatePicker currentDate={ null } /> ); + expect( wrapper.children().props().date ).toBeNull(); + } ); + + it( 'should pass down a moment object for now when currentDate is undefined', () => { + const wrapper = shallow( <DatePicker /> ); + const date = wrapper.children().props().date; + expect( moment.isMoment( date ) ).toBe( true ); + expect( date.isSame( moment(), 'second' ) ).toBe( true ); + } ); + + describe( 'getMomentDate', () => { + it( 'should return a Moment object representing a given date string', () => { + const currentDate = '1986-10-18T23:00:00'; + const wrapper = shallow( <DatePicker /> ); + const momentDate = wrapper.instance().getMomentDate( currentDate ); + + expect( moment.isMoment( momentDate ) ).toBe( true ); + expect( momentDate.isSame( moment( currentDate ) ) ).toBe( true ); + } ); + + it( 'should return null when given a null agrument', () => { + const currentDate = null; + const wrapper = shallow( <DatePicker /> ); + const momentDate = wrapper.instance().getMomentDate( currentDate ); + + expect( momentDate ).toBeNull(); + } ); + + it( 'should return a Moment object representing now when given an undefined argument', () => { + const wrapper = shallow( <DatePicker /> ); + const momentDate = wrapper.instance().getMomentDate(); + + expect( moment.isMoment( momentDate ) ).toBe( true ); + expect( momentDate.isSame( moment(), 'second' ) ).toBe( true ); + } ); + } ); + + describe( 'onChangeMoment', () => { + it( 'should call onChange with a formated date of the input', () => { + const onChangeSpy = jest.fn(); + const currentDate = '1986-10-18T11:00:00'; + const wrapper = shallow( <DatePicker currentDate={ currentDate } onChange={ onChangeSpy } /> ); + const newDate = moment(); + + wrapper.instance().onChangeMoment( newDate ); + + expect( onChangeSpy ).toHaveBeenCalledWith( newDate.format( TIMEZONELESS_FORMAT ) ); + } ); + + it( 'should call onChange with hours, minutes, seconds of the current time when currentDate is undefined', () => { + let onChangeSpyArgument; + const onChangeSpy = ( arg ) => onChangeSpyArgument = arg; + const wrapper = shallow( <DatePicker onChange={ onChangeSpy } /> ); + const newDate = moment( '1986-10-18T11:00:00' ); + const current = moment(); + const newDateWithCurrentTime = newDate + .clone() + .set( { + hours: current.hours(), + minutes: current.minutes(), + seconds: current.seconds(), + } ); + wrapper.instance().onChangeMoment( newDate ); + + expect( moment( onChangeSpyArgument ).isSame( newDateWithCurrentTime, 'minute' ) ).toBe( true ); + } ); + + it( 'should call onChange with hours, minutes, seconds of the current time when currentDate is null', () => { + let onChangeSpyArgument; + const onChangeSpy = ( arg ) => onChangeSpyArgument = arg; + const wrapper = shallow( <DatePicker currentDate={ null } onChange={ onChangeSpy } /> ); + const newDate = moment( '1986-10-18T11:00:00' ); + const current = moment(); + const newDateWithCurrentTime = newDate + .clone() + .set( { + hours: current.hours(), + minutes: current.minutes(), + seconds: current.seconds(), + } ); + wrapper.instance().onChangeMoment( newDate ); + + expect( moment( onChangeSpyArgument ).isSame( newDateWithCurrentTime, 'minute' ) ).toBe( true ); + } ); + } ); +} ); From 0a33b84c705475d8b5544c77c7668d7fd7862f91 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Sun, 10 Feb 2019 07:01:06 -0800 Subject: [PATCH 391/691] Use codetabs to label ESNext code as such (#13781) --- packages/components/src/button/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/components/src/button/README.md b/packages/components/src/button/README.md index 6f45cf5db7a64..7f7667004b7de 100644 --- a/packages/components/src/button/README.md +++ b/packages/components/src/button/README.md @@ -10,6 +10,8 @@ Note that this component may sometimes be confused with the Button block, which Renders a button with default style. +{% codetabs %} +{% ESNext %} ```jsx import { Button } from "@wordpress/components"; @@ -19,6 +21,7 @@ const MyButton = () => ( </Button> ); ``` +{% end %} ## Props From 87b8613c2a7c902ed094fadaa68caf86a87fa952 Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.p.richards@gmail.com> Date: Mon, 11 Feb 2019 14:47:51 +0800 Subject: [PATCH 392/691] Update test filename to match renamed source code filename (#13808) --- .../__tests__/{dependencies-docblocks.js => dependency-group.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/eslint-plugin/rules/__tests__/{dependencies-docblocks.js => dependency-group.js} (100%) diff --git a/packages/eslint-plugin/rules/__tests__/dependencies-docblocks.js b/packages/eslint-plugin/rules/__tests__/dependency-group.js similarity index 100% rename from packages/eslint-plugin/rules/__tests__/dependencies-docblocks.js rename to packages/eslint-plugin/rules/__tests__/dependency-group.js From 5019d401612afc92164415e54cfce561702ac8a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20Van=C2=A0Durpe?= <iseulde@automattic.com> Date: Mon, 11 Feb 2019 09:54:50 +0100 Subject: [PATCH 393/691] Call onInput in onCompositionEnd (#13807) --- packages/editor/src/components/rich-text/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/editor/src/components/rich-text/index.js b/packages/editor/src/components/rich-text/index.js index ea58c212ee3a2..f91f5f77b17a4 100644 --- a/packages/editor/src/components/rich-text/index.js +++ b/packages/editor/src/components/rich-text/index.js @@ -343,7 +343,7 @@ export class RichText extends Component { // (CJK), do not trigger a change if characters are being composed. // Browsers setting `isComposing` to `true` will usually emit a final // `input` event when the characters are composed. - if ( event.nativeEvent.isComposing ) { + if ( event && event.nativeEvent.isComposing ) { return; } @@ -385,7 +385,7 @@ export class RichText extends Component { onCompositionEnd() { // Ensure the value is up-to-date for browsers that don't emit a final // input event after composition. - this.onChange( this.createRecord() ); + this.onInput(); } /** From ae4067ffb396b8eaec319497a2dfdb663fff05ba Mon Sep 17 00:00:00 2001 From: Nicola Heald <nicola@notnowlewis.com> Date: Mon, 11 Feb 2019 12:01:39 +0000 Subject: [PATCH 394/691] Require an initial click on embed previews for interactivity (#12981) This fixes the block selection UX for video embeds. Previously the click on the video to select the block would start the video playing. Now that previous require a click to become interactive, the initial click selects the block without side effects. --- packages/block-library/src/embed/editor.scss | 9 ++ .../block-library/src/embed/embed-preview.js | 127 ++++++++++++------ packages/components/src/sandbox/index.js | 3 +- 3 files changed, 96 insertions(+), 43 deletions(-) diff --git a/packages/block-library/src/embed/editor.scss b/packages/block-library/src/embed/editor.scss index 29cbd9fb932cf..af0b050764ce7 100644 --- a/packages/block-library/src/embed/editor.scss +++ b/packages/block-library/src/embed/editor.scss @@ -31,3 +31,12 @@ word-break: break-word; } } + +.block-library-embed__interactive-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + opacity: 0; +} diff --git a/packages/block-library/src/embed/embed-preview.js b/packages/block-library/src/embed/embed-preview.js index efc11b7a5809b..cf8d35460ae3d 100644 --- a/packages/block-library/src/embed/embed-preview.js +++ b/packages/block-library/src/embed/embed-preview.js @@ -17,58 +17,101 @@ import classnames from 'classnames/dedupe'; import { __, sprintf } from '@wordpress/i18n'; import { Placeholder, SandBox } from '@wordpress/components'; import { RichText, BlockIcon } from '@wordpress/editor'; +import { Component } from '@wordpress/element'; /** * Internal dependencies */ import WpEmbedPreview from './wp-embed-preview'; -const EmbedPreview = ( props ) => { - const { preview, url, type, caption, onCaptionChange, isSelected, className, icon, label } = props; - const { scripts } = preview; +class EmbedPreview extends Component { + constructor() { + super( ...arguments ); + this.hideOverlay = this.hideOverlay.bind( this ); + this.state = { + interactive: false, + }; + } - const html = 'photo' === type ? getPhotoHtml( preview ) : preview.html; - const parsedHost = parse( url ).host.split( '.' ); - const parsedHostBaseUrl = parsedHost.splice( parsedHost.length - 2, parsedHost.length - 1 ).join( '.' ); - const cannotPreview = includes( HOSTS_NO_PREVIEWS, parsedHostBaseUrl ); - // translators: %s: host providing embed content e.g: www.youtube.com - const iframeTitle = sprintf( __( 'Embedded content from %s' ), parsedHostBaseUrl ); - const sandboxClassnames = classnames( type, className, 'wp-block-embed__wrapper' ); + static getDerivedStateFromProps( nextProps, state ) { + if ( ! nextProps.isSelected && state.interactive ) { + // We only want to change this when the block is not selected, because changing it when + // the block becomes selected makes the overlap disappear too early. Hiding the overlay + // happens on mouseup when the overlay is clicked. + return { interactive: false }; + } - const embedWrapper = 'wp-embed' === type ? ( - <WpEmbedPreview - html={ html } - /> - ) : ( - <div className="wp-block-embed__wrapper"> - <SandBox + return null; + } + + hideOverlay() { + // This is called onMouseUp on the overlay. We can't respond to the `isSelected` prop + // changing, because that happens on mouse down, and the overlay immediately disappears, + // and the mouse event can end up in the preview content. We can't use onClick on + // the overlay to hide it either, because then the editor misses the mouseup event, and + // thinks we're multi-selecting blocks. + this.setState( { interactive: true } ); + } + + render() { + const { preview, url, type, caption, onCaptionChange, isSelected, className, icon, label } = this.props; + const { scripts } = preview; + const { interactive } = this.state; + + const html = 'photo' === type ? getPhotoHtml( preview ) : preview.html; + const parsedHost = parse( url ).host.split( '.' ); + const parsedHostBaseUrl = parsedHost.splice( parsedHost.length - 2, parsedHost.length - 1 ).join( '.' ); + const cannotPreview = includes( HOSTS_NO_PREVIEWS, parsedHostBaseUrl ); + // translators: %s: host providing embed content e.g: www.youtube.com + const iframeTitle = sprintf( __( 'Embedded content from %s' ), parsedHostBaseUrl ); + const sandboxClassnames = classnames( type, className, 'wp-block-embed__wrapper' ); + + // Disabled because the overlay div doesn't actually have a role or functionality + // as far as the user is concerned. We're just catching the first click so that + // the block can be selected without interacting with the embed preview that the overlay covers. + /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ + /* eslint-disable jsx-a11y/no-static-element-interactions */ + const embedWrapper = 'wp-embed' === type ? ( + <WpEmbedPreview html={ html } - scripts={ scripts } - title={ iframeTitle } - type={ sandboxClassnames } /> - </div> - ); - - return ( - <figure className={ classnames( className, 'wp-block-embed', { 'is-type-video': 'video' === type } ) }> - { ( cannotPreview ) ? ( - <Placeholder icon={ <BlockIcon icon={ icon } showColors /> } label={ label }> - <p className="components-placeholder__error"><a href={ url }>{ url }</a></p> - <p className="components-placeholder__error">{ __( 'Sorry, this embedded content cannot be previewed in the editor.' ) }</p> - </Placeholder> - ) : embedWrapper } - { ( ! RichText.isEmpty( caption ) || isSelected ) && ( - <RichText - tagName="figcaption" - placeholder={ __( 'Write caption…' ) } - value={ caption } - onChange={ onCaptionChange } - inlineToolbar + ) : ( + <div className="wp-block-embed__wrapper"> + <SandBox + html={ html } + scripts={ scripts } + title={ iframeTitle } + type={ sandboxClassnames } + onFocus={ this.hideOverlay } /> - ) } - </figure> - ); -}; + { ! interactive && <div + className="block-library-embed__interactive-overlay" + onMouseUp={ this.hideOverlay } /> } + </div> + ); + /* eslint-enable jsx-a11y/no-static-element-interactions */ + /* eslint-enable jsx-a11y/no-noninteractive-element-interactions */ + + return ( + <figure className={ classnames( className, 'wp-block-embed', { 'is-type-video': 'video' === type } ) }> + { ( cannotPreview ) ? ( + <Placeholder icon={ <BlockIcon icon={ icon } showColors /> } label={ label }> + <p className="components-placeholder__error"><a href={ url }>{ url }</a></p> + <p className="components-placeholder__error">{ __( 'Sorry, this embedded content cannot be previewed in the editor.' ) }</p> + </Placeholder> + ) : embedWrapper } + { ( ! RichText.isEmpty( caption ) || isSelected ) && ( + <RichText + tagName="figcaption" + placeholder={ __( 'Write caption…' ) } + value={ caption } + onChange={ onCaptionChange } + inlineToolbar + /> + ) } + </figure> + ); + } +} export default EmbedPreview; diff --git a/packages/components/src/sandbox/index.js b/packages/components/src/sandbox/index.js index 1862c00db9754..01f2e8f8e3b7f 100644 --- a/packages/components/src/sandbox/index.js +++ b/packages/components/src/sandbox/index.js @@ -198,7 +198,7 @@ class Sandbox extends Component { } render() { - const { title } = this.props; + const { title, onFocus } = this.props; return ( <FocusableIframe @@ -207,6 +207,7 @@ class Sandbox extends Component { className="components-sandbox" sandbox="allow-scripts allow-same-origin allow-presentation" onLoad={ this.trySandbox } + onFocus={ onFocus } width={ Math.ceil( this.state.width ) } height={ Math.ceil( this.state.height ) } /> ); From a4412e64d6dcc733c4dc243d0676687781c616fa Mon Sep 17 00:00:00 2001 From: Marty Helmick <info@martyhelmick.com> Date: Mon, 11 Feb 2019 08:27:24 -0500 Subject: [PATCH 395/691] Reduce paragraph block CSS specificity (#13025) * move dropcap focus to editor.css. Show hint of DC on focus * Move dropcap focus to editor.css. Reduce specificity * Easing up on paragraph block specificity. --- .../block-library/src/paragraph/style.scss | 66 +++++++++---------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/packages/block-library/src/paragraph/style.scss b/packages/block-library/src/paragraph/style.scss index becb47973704d..10ed6de1b484f 100644 --- a/packages/block-library/src/paragraph/style.scss +++ b/packages/block-library/src/paragraph/style.scss @@ -1,46 +1,44 @@ -p { - &.is-small-text { - font-size: 14px; - } +.is-small-text { + font-size: 14px; +} - &.is-regular-text { - font-size: 16px; - } +.is-regular-text { + font-size: 16px; +} - &.is-large-text { - font-size: 36px; - } +.is-large-text { + font-size: 36px; +} - &.is-larger-text { - font-size: 48px; - } +.is-larger-text { + font-size: 48px; +} - // Don't show the drop cap when editing the paragraph's content. It causes a - // number of bugs in combination with `contenteditable` fields. The caret - // cannot be set around it, caret position calculation fails in Chrome, and - // typing at the end of the paragraph doesn't work. - &.has-drop-cap:not(:focus)::first-letter { - float: left; - font-size: 8.4em; - line-height: 0.68; - font-weight: 100; - margin: 0.05em 0.1em 0 0; - text-transform: uppercase; - font-style: normal; - } +// Don't show the drop cap when editing the paragraph's content. It causes a +// number of bugs in combination with `contenteditable` fields. The caret +// cannot be set around it, caret position calculation fails in Chrome, and +// typing at the end of the paragraph doesn't work. +.has-drop-cap:not(:focus)::first-letter { + float: left; + font-size: 8.4em; + line-height: 0.68; + font-weight: 100; + margin: 0.05em 0.1em 0 0; + text-transform: uppercase; + font-style: normal; +} - &.has-drop-cap:not(:focus)::after { - content: ""; - display: table; - clear: both; - padding-top: $block-padding; - } +.has-drop-cap:not(:focus)::after { + content: ""; + display: table; + clear: both; + padding-top: $block-padding; } -p.has-background { +.has-background { padding: 20px 30px; } -p.has-text-color a { +.has-text-color a { color: inherit; } From 52e8384cff48748a30546467aa22a4b0958cd751 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Mon, 11 Feb 2019 08:48:10 -0500 Subject: [PATCH 396/691] ESLint Plugin: Add rule valid-sprintf (#13756) * Blocks: Embed: Avoid unneccessary sprintf * ESLint Plugin: Add rule valid-sprintf * i18n: Disable valid-sprintf for intentional error test case --- packages/block-library/src/embed/settings.js | 5 +- packages/eslint-plugin/CHANGELOG.md | 1 + packages/eslint-plugin/README.md | 1 + packages/eslint-plugin/configs/custom.js | 1 + .../eslint-plugin/docs/rules/valid-sprintf.md | 28 ++++ .../rules/__tests__/valid-sprintf.js | 75 +++++++++ packages/eslint-plugin/rules/valid-sprintf.js | 150 ++++++++++++++++++ packages/i18n/src/test/index.js | 2 + 8 files changed, 260 insertions(+), 3 deletions(-) create mode 100644 packages/eslint-plugin/docs/rules/valid-sprintf.md create mode 100644 packages/eslint-plugin/rules/__tests__/valid-sprintf.js create mode 100644 packages/eslint-plugin/rules/valid-sprintf.js diff --git a/packages/block-library/src/embed/settings.js b/packages/block-library/src/embed/settings.js index d42a406b608ac..7995939c325a3 100644 --- a/packages/block-library/src/embed/settings.js +++ b/packages/block-library/src/embed/settings.js @@ -11,7 +11,7 @@ import classnames from 'classnames/dedupe'; /** * WordPress dependencies */ -import { __, sprintf } from '@wordpress/i18n'; +import { __ } from '@wordpress/i18n'; import { compose } from '@wordpress/compose'; import { RichText } from '@wordpress/editor'; import { withSelect, withDispatch } from '@wordpress/data'; @@ -38,8 +38,7 @@ const embedAttributes = { }; export function getEmbedBlockSettings( { title, description, icon, category = 'embed', transforms, keywords = [], supports = {}, responsive = true } ) { - // translators: %s: Name of service (e.g. VideoPress, YouTube) - const blockDescription = description || sprintf( __( 'Add a block that displays content pulled from other sites, like Twitter, Instagram or YouTube.' ), title ); + const blockDescription = description || __( 'Add a block that displays content pulled from other sites, like Twitter, Instagram or YouTube.' ); const edit = getEmbedEditComponent( title, icon, responsive ); return { title, diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 9562f47730d07..daf663e8157e9 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -8,6 +8,7 @@ - New Rule: [`@wordpress/no-unused-vars-before-return`](https://github.com/WordPress/gutenberg/blob/master/packages/eslint-plugin/docs/rules/no-unused-vars-before-return.md) - New Rule: [`@wordpress/dependency-group`](https://github.com/WordPress/gutenberg/blob/master/packages/eslint-plugin/docs/rules/dependency-group.md) +- New Rule: [`@wordpress/valid-sprintf`](https://github.com/WordPress/gutenberg/blob/master/packages/eslint-plugin/docs/rules/valid-sprintf.md) ## 1.0.0 (2018-12-12) diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 5e21e8fc99463..2af095ba3afec 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -51,6 +51,7 @@ Rule|Description ---|--- [no-unused-vars-before-return](/packages/eslint-plugin/docs/rules/no-unused-vars-before-return.md)|Disallow assigning variable values if unused before a return [dependency-group](/packages/eslint-plugin/docs/rules/dependency-group.md)|Enforce dependencies docblocks formatting +[valid-sprintf](/packages/eslint-plugin/docs/rules/valid-sprintf.md)|Disallow assigning variable values if unused before a return ### Legacy diff --git a/packages/eslint-plugin/configs/custom.js b/packages/eslint-plugin/configs/custom.js index 9eb42283099b0..7d79a3e055b18 100644 --- a/packages/eslint-plugin/configs/custom.js +++ b/packages/eslint-plugin/configs/custom.js @@ -5,6 +5,7 @@ module.exports = { rules: { '@wordpress/dependency-group': 'error', '@wordpress/no-unused-vars-before-return': 'error', + '@wordpress/valid-sprintf': 'error', 'no-restricted-syntax': [ 'error', { diff --git a/packages/eslint-plugin/docs/rules/valid-sprintf.md b/packages/eslint-plugin/docs/rules/valid-sprintf.md new file mode 100644 index 0000000000000..0ff0ccd67431f --- /dev/null +++ b/packages/eslint-plugin/docs/rules/valid-sprintf.md @@ -0,0 +1,28 @@ +# Enforce valid sprintf usage (valid-sprintf) + +[`sprintf`](https://github.com/WordPress/gutenberg/blob/master/packages/i18n/README.md#api) must be called with a valid format string with at least one placeholder, and with a valid set of placeholder substitute values. + +## Rule details + +Examples of **incorrect** code for this rule: + +```js +sprintf(); +sprintf( '%s' ); +sprintf( 1, 'substitute' ); +sprintf( [], 'substitute' ); +sprintf( '%%', 'substitute' ); +sprintf( __( '%%' ), 'substitute' ); +sprintf( _n( '%s', '' ), 'substitute' ); +sprintf( _n( '%s', '%s %s' ), 'substitute' ); +``` + +Examples of **correct** code for this rule: + +```js +sprintf( '%s', 'substitute' ); +sprintf( __( '%s' ), 'substitute' ); +sprintf( _x( '%s' ), 'substitute' ); +sprintf( _n( '%s', '%s' ), 'substitute' ); +sprintf( _nx( '%s', '%s' ), 'substitute' ); +``` diff --git a/packages/eslint-plugin/rules/__tests__/valid-sprintf.js b/packages/eslint-plugin/rules/__tests__/valid-sprintf.js new file mode 100644 index 0000000000000..06664969d8148 --- /dev/null +++ b/packages/eslint-plugin/rules/__tests__/valid-sprintf.js @@ -0,0 +1,75 @@ +/** + * External dependencies + */ +import { RuleTester } from 'eslint'; + +/** + * Internal dependencies + */ +import rule from '../valid-sprintf'; + +const ruleTester = new RuleTester( { + parserOptions: { + ecmaVersion: 6, + }, +} ); + +ruleTester.run( 'valid-sprintf', rule, { + valid: [ + { + code: `sprintf( '%s', 'substitute' )`, + }, + { + code: `sprintf( __( '%s' ), 'substitute' )`, + }, + { + code: `sprintf( _x( '%s' ), 'substitute' )`, + }, + { + code: `sprintf( _n( '%s', '%s' ), 'substitute' )`, + }, + { + code: `sprintf( _nx( '%s', '%s' ), 'substitute' )`, + }, + { + code: `var getValue = () => ''; sprintf( getValue(), 'substitute' )`, + }, + { + code: `var value = ''; sprintf( value, 'substitute' )`, + }, + ], + invalid: [ + { + code: `sprintf()`, + errors: [ { message: 'sprintf must be called with a format string' } ], + }, + { + code: `sprintf( '%s' )`, + errors: [ { message: 'sprintf must be called with placeholder value argument(s)' } ], + }, + { + code: `sprintf( 1, 'substitute' )`, + errors: [ { message: 'sprintf must be called with a valid format string' } ], + }, + { + code: `sprintf( [], 'substitute' )`, + errors: [ { message: 'sprintf must be called with a valid format string' } ], + }, + { + code: `sprintf( '%%', 'substitute' )`, + errors: [ { message: 'sprintf format string must contain at least one placeholder' } ], + }, + { + code: `sprintf( __( '%%' ), 'substitute' )`, + errors: [ { message: 'sprintf format string must contain at least one placeholder' } ], + }, + { + code: `sprintf( _n( '%s', '' ), 'substitute' )`, + errors: [ { message: 'sprintf format string options must have the same number of placeholders' } ], + }, + { + code: `sprintf( _n( '%s', '%s %s' ), 'substitute' )`, + errors: [ { message: 'sprintf format string options must have the same number of placeholders' } ], + }, + ], +} ); diff --git a/packages/eslint-plugin/rules/valid-sprintf.js b/packages/eslint-plugin/rules/valid-sprintf.js new file mode 100644 index 0000000000000..73101d12b7d67 --- /dev/null +++ b/packages/eslint-plugin/rules/valid-sprintf.js @@ -0,0 +1,150 @@ +/** + * Regular expression matching the presence of a printf format string + * placeholder. This naive pattern which does not validate the format. + * + * @type {RegExp} + */ +const REGEXP_PLACEHOLDER = /%[^%]/g; + +/** + * Given a function name and array of argument Node values, returns all + * possible string results from the corresponding translate function, or + * undefined if the function is not a translate function. + * + * @param {string} functionName Function name. + * @param {espree.Node[]} args Espree argument Node objects. + * + * @return {?Array<string>} All possible translate function string results. + */ +function getTranslateStrings( functionName, args ) { + switch ( functionName ) { + case '__': + case '_x': + args = args.slice( 0, 1 ); + break; + + case '_n': + case '_nx': + args = args.slice( 0, 2 ); + break; + + default: + return; + } + + return args + .filter( ( arg ) => arg.type === 'Literal' ) + .map( ( arg ) => arg.value ); +} + +module.exports = { + meta: { + type: 'problem', + schema: [], + }, + create( context ) { + return { + CallExpression( node ) { + const { callee, arguments: args } = node; + if ( callee.name !== 'sprintf' ) { + return; + } + + if ( ! args.length ) { + context.report( + node, + 'sprintf must be called with a format string' + ); + return; + } + + if ( args.length < 2 ) { + context.report( + node, + 'sprintf must be called with placeholder value argument(s)' + ); + return; + } + + let candidates; + switch ( args[ 0 ].type ) { + case 'Literal': + candidates = [ args[ 0 ].value ].filter( ( arg ) => { + // Since a Literal may be a number, verify the + // value is a string. + return typeof arg === 'string'; + } ); + break; + + case 'CallExpression': + // All possible options (arguments) from a translate + // function must be valid. + candidates = getTranslateStrings( + args[ 0 ].callee.name, + args[ 0 ].arguments + ); + + // An unknown function call may produce a valid string + // value. Ideally its result is verified, but this is + // not straight-forward to implement. Thus, bail. + if ( candidates === undefined ) { + return; + } + + break; + + case 'Identifier': + // Identifiers may refer to a valid string variable. + // Ideally its reference value is verified, but this is + // not straight-forward to implement. Thus, bail. + return; + + default: + candidates = []; + } + + if ( ! candidates.length ) { + context.report( + node, + 'sprintf must be called with a valid format string' + ); + return; + } + + let numPlaceholders; + for ( let i = 0; i < candidates.length; i++ ) { + const match = candidates[ i ].match( REGEXP_PLACEHOLDER ); + + // Prioritize placeholder number consistency over matching + // placeholder, since it's a more common error to omit a + // placeholder from the singular form of pluralization. + if ( + numPlaceholders !== undefined && + ( ! match || numPlaceholders !== match.length ) + ) { + context.report( + node, + 'sprintf format string options must have the same number of placeholders' + ); + return; + } + + if ( ! match ) { + context.report( + node, + 'sprintf format string must contain at least one placeholder' + ); + return; + } + + if ( numPlaceholders === undefined ) { + // Track the number of placeholders discovered in the + // string to verify that all other candidate options + // have the same number. + numPlaceholders = match.length; + } + } + }, + }; + }, +}; diff --git a/packages/i18n/src/test/index.js b/packages/i18n/src/test/index.js index c79d24fb43bc2..19d398daa47ef 100644 --- a/packages/i18n/src/test/index.js +++ b/packages/i18n/src/test/index.js @@ -68,6 +68,8 @@ describe( 'i18n', () => { describe( 'sprintf()', () => { it( 'absorbs errors', () => { + // Disable reason: Failing case is the purpose of the test. + // eslint-disable-next-line @wordpress/valid-sprintf const result = sprintf( 'Hello %(placeholder-not-provided)s' ); expect( console ).toHaveErrored(); From 1c583b2c80d304ac474110fbfd94427e1e22fc89 Mon Sep 17 00:00:00 2001 From: etoledom <etoledom@icloud.com> Date: Mon, 11 Feb 2019 17:19:04 +0100 Subject: [PATCH 397/691] Mobile: Remove size settings (#13750) * Mobile: Remove Size Setting from Image Block. * Organizing imports on mobile ImageEdit * Fix lint issues --- .../block-library/src/image/edit.native.js | 53 +++++-------------- 1 file changed, 14 insertions(+), 39 deletions(-) diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index 1035a6391ce11..77d4ce4438b47 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -12,20 +12,25 @@ import { requestImageFailedRetryDialog, requestImageUploadCancelDialog, } from 'react-native-gutenberg-bridge'; -import { - map, - compact, -} from 'lodash'; /** * WordPress dependencies */ -import { MediaPlaceholder, RichText, BlockControls, InspectorControls, BottomSheet } from '@wordpress/editor'; -import { Toolbar, ToolbarButton, Spinner, Dashicon } from '@wordpress/components'; +import { + Toolbar, + ToolbarButton, + Spinner, + Dashicon, +} from '@wordpress/components'; +import { + MediaPlaceholder, + RichText, + BlockControls, + InspectorControls, + BottomSheet, +} from '@wordpress/editor'; import { __ } from '@wordpress/i18n'; import { isURL } from '@wordpress/url'; -import { compose } from '@wordpress/compose'; -import { withSelect } from '@wordpress/data'; /** * Internal dependencies @@ -158,16 +163,6 @@ class ImageEdit extends React.Component { } ); } - getImageSizeOptions() { - const { imageSizes } = this.props; - return compact( map( imageSizes, ( { label, slug } ) => { - return { - value: this.props.attributes.url + slug, //temporary url - label, - }; - } ) ); - } - render() { const { attributes, isSelected, setAttributes } = this.props; const { url, caption, height, width, alt, href } = attributes; @@ -226,8 +221,6 @@ class ImageEdit extends React.Component { </Toolbar> ); - const sizeOptions = this.getImageSizeOptions(); - const getInspectorControls = () => ( <BottomSheet isVisible={ this.state.showSettings } @@ -243,13 +236,6 @@ class ImageEdit extends React.Component { autoCapitalize="none" autoCorrect={ false } /> - <BottomSheet.PickerCell - icon="editor-expand" - label={ __( 'Image Size' ) } - value={ 'Large' } // Temporary for UI implementation. - options={ sizeOptions } - onChangeValue={ () => {} } // Temporary for UI implementation. - /> <BottomSheet.Cell icon={ 'editor-textcolor' } label={ __( 'Alt Text' ) } @@ -339,15 +325,4 @@ class ImageEdit extends React.Component { } } -export default compose( [ - withSelect( ( select ) => { - const { getEditorSettings } = select( 'core/editor' ); - const { maxWidth, isRTL, imageSizes } = getEditorSettings(); - - return { - maxWidth, - isRTL, - imageSizes, - }; - } ), -] )( ImageEdit ); +export default ImageEdit; From f0261ce340105c8b572c8cab772b4b5d8e0aceb5 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell@kjellr.com> Date: Mon, 11 Feb 2019 12:59:27 -0500 Subject: [PATCH 398/691] Assign default font and font size styles to the notice component. (#13817) --- packages/components/src/notice/style.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/components/src/notice/style.scss b/packages/components/src/notice/style.scss index 8003d33384a3c..93c7e1f31c182 100644 --- a/packages/components/src/notice/style.scss +++ b/packages/components/src/notice/style.scss @@ -1,4 +1,6 @@ .components-notice { + font-family: $default-font; + font-size: $default-font-size; background-color: $blue-medium-100; border-left: 4px solid $blue-medium-500; margin: 5px 15px 2px; From a658eaebfae2a5702fa725c346e731aafcd2e5f1 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Mon, 11 Feb 2019 18:17:42 +0000 Subject: [PATCH 399/691] Update release docs to document the need for a temporary brach (#13694) --- docs/contributors/release.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/contributors/release.md b/docs/contributors/release.md index 6aa69cb4f9906..b59a5472167f3 100644 --- a/docs/contributors/release.md +++ b/docs/contributors/release.md @@ -114,13 +114,17 @@ Creating a release involves: 1. Checkout the release branch `git checkout release/x.x`. -**Note:** This branch should never be removed or rebased. This means in case of conflicts when creating PRs from this branch, create temporary branches in order to merge these PRs and avoid touching the release branch. +**Note:** This branch should never be removed or rebased. When we want to merge something from it to master and conflicts exist/may exist we use a temporary branch `bump/x.x`. 2. Create [a commit like this](https://github.com/WordPress/gutenberg/commit/00d01049685f11f9bb721ad3437cb928814ab2a2#diff-b9cfc7f2cdf78a7f4b91a753d10865a2), removing the `-rc.X` from the version number in `gutenberg.php`, `package.json`, and `package-lock.json`. -3. Create a Pull Request from the release branch into `master` using the changelog as a description and ensure the tests pass properly. -4. Tag the version. `git tag vx.x.0` from the release branch. -5. Push the tag `git push --tags`. -6. Merge the version bump pull request and avoid removing the release branch. +3. Create a new branch called `bump/x.x` from `release/x.x` and switch to it: `git checkout -b bump/x.x`. +4. Create a pull request from `bump/x.x` to `master`. Verify the continuous integrations tests pass, before continuing to the next step even if conflicts exist. +5. Rebase `bump/x.x` against `origin/master` using `git fetch origin && git rebase origin/master`. +6. Force push the branch `bump/x.x` using `git push --force-with-lease`. +7. Switch to the `release/x.x` branch. Tag the version from the release branch `git tag vx.x.0`. +8. Push the tag `git push --tags`. +9. Merge the version bump pull request. + ##### Build the Plugin From 285875a1e2899b4999bb1396277be8a163354249 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Mon, 11 Feb 2019 14:37:01 -0500 Subject: [PATCH 400/691] ESLint Plugin: Restrict tolerances as fixable error (#13785) * Framework: Resolve dependency grouping inconsistencies * ESLint Plugin: Restrict tolerances as fixable error --- packages/annotations/src/store/index.js | 2 +- packages/block-library/src/gallery/edit.js | 2 +- .../src/gallery/gallery-image.js | 4 +- .../src/latest-comments/index.js | 4 +- packages/components/src/draggable/index.js | 2 +- .../higher-order/navigate-regions/index.js | 6 +- packages/components/src/modal/header.js | 2 +- .../src/navigable-container/container.js | 4 +- .../src/navigable-container/menu.js | 4 +- .../src/navigable-container/tabbable.js | 2 +- .../src/responsive-wrapper/index.js | 2 +- .../src/server-side-render/index.js | 6 +- .../components/src/slot-fill/test/slot.js | 2 +- packages/e2e-test-utils/src/create-url.js | 2 +- packages/e2e-test-utils/src/is-current-url.js | 2 +- .../e2e-test-utils/src/visit-admin-page.js | 2 +- .../specs/adding-inline-tokens.test.js | 2 +- .../specs/manage-reusable-blocks.test.js | 2 +- .../components/fullscreen-mode/test/index.js | 2 +- .../components/header/feature-toggle/index.js | 2 +- .../header/fullscreen-mode-close/index.js | 4 +- .../header/options-menu-item/index.js | 2 +- .../header/post-publish-button-or-toggle.js | 4 +- .../src/components/header/test/index.js | 2 +- .../components/header/writing-menu/index.js | 2 +- .../edit-post/src/components/sidebar/index.js | 2 +- .../components/sidebar/post-status/index.js | 2 +- .../sidebar/post-taxonomies/taxonomy-panel.js | 2 +- .../sidebar/settings-sidebar/index.js | 2 +- .../hooks/components/media-upload/index.js | 2 +- .../index.js | 2 +- packages/edit-post/src/store/index.js | 2 +- .../src/components/block-drop-zone/index.js | 2 +- .../src/components/block-inspector/index.js | 2 +- .../src/components/block-list/block-html.js | 4 +- .../src/components/block-toolbar/index.js | 4 +- .../multi-selection-inspector/index.js | 2 +- .../src/components/post-permalink/index.js | 2 +- .../components/post-publish-panel/index.js | 2 +- .../maybe-post-format-panel.js | 4 +- .../post-publish-panel/postpublish.js | 4 +- .../post-publish-panel/prepublish.js | 2 +- .../src/components/post-taxonomies/check.js | 2 +- .../src/components/post-taxonomies/index.js | 2 +- .../src/components/post-visibility/index.js | 2 +- .../src/components/post-visibility/label.js | 2 +- .../src/components/post-visibility/utils.js | 2 +- .../editor/src/components/provider/index.js | 2 +- .../components/server-side-render/index.js | 2 +- .../skip-to-selected-block/index.js | 2 +- .../src/editor-styles/ast/stringify/index.js | 2 +- packages/editor/src/store/actions.js | 2 +- packages/editor/src/store/index.js | 2 +- .../src/utils/media-upload/media-upload.js | 2 +- .../rules/__tests__/dependency-group.js | 9 +- .../eslint-plugin/rules/dependency-group.js | 94 +++++++++++++++---- packages/is-shallow-equal/test/index.js | 2 +- packages/wordcount/src/test/index.test.js | 2 +- 58 files changed, 151 insertions(+), 92 deletions(-) diff --git a/packages/annotations/src/store/index.js b/packages/annotations/src/store/index.js index 917a342ad9f49..1c7f27dccbc35 100644 --- a/packages/annotations/src/store/index.js +++ b/packages/annotations/src/store/index.js @@ -1,5 +1,5 @@ /** - * WordPress Dependencies + * WordPress dependencies */ import { registerStore } from '@wordpress/data'; diff --git a/packages/block-library/src/gallery/edit.js b/packages/block-library/src/gallery/edit.js index 1199b881f93b3..f83704b9c8b85 100644 --- a/packages/block-library/src/gallery/edit.js +++ b/packages/block-library/src/gallery/edit.js @@ -1,5 +1,5 @@ /** - * External Dependencies + * External dependencies */ import classnames from 'classnames'; import { filter, pick, map, get } from 'lodash'; diff --git a/packages/block-library/src/gallery/gallery-image.js b/packages/block-library/src/gallery/gallery-image.js index bbdd1db6a222c..ff95dd1cc49cb 100644 --- a/packages/block-library/src/gallery/gallery-image.js +++ b/packages/block-library/src/gallery/gallery-image.js @@ -1,10 +1,10 @@ /** - * External Dependencies + * External dependencies */ import classnames from 'classnames'; /** - * WordPress Dependencies + * WordPress dependencies */ import { Component, Fragment } from '@wordpress/element'; import { IconButton, Spinner } from '@wordpress/components'; diff --git a/packages/block-library/src/latest-comments/index.js b/packages/block-library/src/latest-comments/index.js index 2fc124997196b..d742c1dcbbc24 100644 --- a/packages/block-library/src/latest-comments/index.js +++ b/packages/block-library/src/latest-comments/index.js @@ -1,11 +1,11 @@ /** - * WordPress dependencies. + * WordPress dependencies */ import { __ } from '@wordpress/i18n'; import { G, Path, SVG } from '@wordpress/components'; /** - * Internal dependencies. + * Internal dependencies */ import edit from './edit'; diff --git a/packages/components/src/draggable/index.js b/packages/components/src/draggable/index.js index 773b39c0b7bbb..ab660a7de1dd4 100644 --- a/packages/components/src/draggable/index.js +++ b/packages/components/src/draggable/index.js @@ -4,7 +4,7 @@ import { noop } from 'lodash'; /** - * WordPress Dependencies + * WordPress dependencies */ import { Component } from '@wordpress/element'; import { withSafeTimeout } from '@wordpress/compose'; diff --git a/packages/components/src/higher-order/navigate-regions/index.js b/packages/components/src/higher-order/navigate-regions/index.js index 88103941bd738..ed5fb89dc7cf6 100644 --- a/packages/components/src/higher-order/navigate-regions/index.js +++ b/packages/components/src/higher-order/navigate-regions/index.js @@ -1,16 +1,16 @@ /** - * External Dependencies + * External dependencies */ import classnames from 'classnames'; /** - * WordPress Dependencies + * WordPress dependencies */ import { Component } from '@wordpress/element'; import { createHigherOrderComponent } from '@wordpress/compose'; /** - * Internal Dependencies + * Internal dependencies */ import KeyboardShortcuts from '../../keyboard-shortcuts'; diff --git a/packages/components/src/modal/header.js b/packages/components/src/modal/header.js index 7ce8b7473e89c..0e92567ae54ef 100644 --- a/packages/components/src/modal/header.js +++ b/packages/components/src/modal/header.js @@ -4,7 +4,7 @@ import { __ } from '@wordpress/i18n'; /** - * Internal dependencies. + * Internal dependencies */ import IconButton from '../icon-button'; diff --git a/packages/components/src/navigable-container/container.js b/packages/components/src/navigable-container/container.js index 7471243cd48f9..fe056fa25ddbf 100644 --- a/packages/components/src/navigable-container/container.js +++ b/packages/components/src/navigable-container/container.js @@ -1,10 +1,10 @@ /** - * External Dependencies + * External dependencies */ import { omit, noop, isFunction } from 'lodash'; /** - * WordPress Dependencies + * WordPress dependencies */ import { Component, forwardRef } from '@wordpress/element'; import { focus } from '@wordpress/dom'; diff --git a/packages/components/src/navigable-container/menu.js b/packages/components/src/navigable-container/menu.js index e97c2aece7c0a..16411eec386a5 100644 --- a/packages/components/src/navigable-container/menu.js +++ b/packages/components/src/navigable-container/menu.js @@ -1,10 +1,10 @@ /** - * External Dependencies + * External dependencies */ import { includes } from 'lodash'; /** - * WordPress Dependencies + * WordPress dependencies */ import { forwardRef } from '@wordpress/element'; import { UP, DOWN, LEFT, RIGHT } from '@wordpress/keycodes'; diff --git a/packages/components/src/navigable-container/tabbable.js b/packages/components/src/navigable-container/tabbable.js index 3e1191d998e2d..bc572522e9c9f 100644 --- a/packages/components/src/navigable-container/tabbable.js +++ b/packages/components/src/navigable-container/tabbable.js @@ -1,5 +1,5 @@ /** - * WordPress Dependencies + * WordPress dependencies */ import { forwardRef } from '@wordpress/element'; import { TAB } from '@wordpress/keycodes'; diff --git a/packages/components/src/responsive-wrapper/index.js b/packages/components/src/responsive-wrapper/index.js index 5965b425d5eb8..7236f5eff1dc3 100644 --- a/packages/components/src/responsive-wrapper/index.js +++ b/packages/components/src/responsive-wrapper/index.js @@ -4,7 +4,7 @@ import classnames from 'classnames'; /** - * WordPress Dependencies + * WordPress dependencies */ import { cloneElement, Children } from '@wordpress/element'; diff --git a/packages/components/src/server-side-render/index.js b/packages/components/src/server-side-render/index.js index 71d649dfbf644..660521d5ce519 100644 --- a/packages/components/src/server-side-render/index.js +++ b/packages/components/src/server-side-render/index.js @@ -1,10 +1,10 @@ /** - * External dependencies. + * External dependencies */ import { isEqual, debounce } from 'lodash'; /** - * WordPress dependencies. + * WordPress dependencies */ import { Component, @@ -15,7 +15,7 @@ import apiFetch from '@wordpress/api-fetch'; import { addQueryArgs } from '@wordpress/url'; /** - * Internal dependencies. + * Internal dependencies */ import Placeholder from '../placeholder'; import Spinner from '../spinner'; diff --git a/packages/components/src/slot-fill/test/slot.js b/packages/components/src/slot-fill/test/slot.js index 47ba2e16de524..bacc5e55e0fe3 100644 --- a/packages/components/src/slot-fill/test/slot.js +++ b/packages/components/src/slot-fill/test/slot.js @@ -12,7 +12,7 @@ import Fill from '../fill'; import Provider from '../context'; /** - * WordPress Dependencies + * WordPress dependencies */ import { Component } from '@wordpress/element'; diff --git a/packages/e2e-test-utils/src/create-url.js b/packages/e2e-test-utils/src/create-url.js index 14437e0476666..5bc9e6df3a59d 100644 --- a/packages/e2e-test-utils/src/create-url.js +++ b/packages/e2e-test-utils/src/create-url.js @@ -1,5 +1,5 @@ /** - * Node dependencies + * External dependencies */ import { join } from 'path'; import { URL } from 'url'; diff --git a/packages/e2e-test-utils/src/is-current-url.js b/packages/e2e-test-utils/src/is-current-url.js index 01b78ffde0576..6062f241d4854 100644 --- a/packages/e2e-test-utils/src/is-current-url.js +++ b/packages/e2e-test-utils/src/is-current-url.js @@ -1,5 +1,5 @@ /** - * Node dependencies + * External dependencies */ import { URL } from 'url'; diff --git a/packages/e2e-test-utils/src/visit-admin-page.js b/packages/e2e-test-utils/src/visit-admin-page.js index 4878f69d277f0..12a8f327e544b 100644 --- a/packages/e2e-test-utils/src/visit-admin-page.js +++ b/packages/e2e-test-utils/src/visit-admin-page.js @@ -1,5 +1,5 @@ /** - * Node dependencies + * External dependencies */ import { join } from 'path'; diff --git a/packages/e2e-tests/specs/adding-inline-tokens.test.js b/packages/e2e-tests/specs/adding-inline-tokens.test.js index ee2572e1556b7..6fe5d42e84912 100644 --- a/packages/e2e-tests/specs/adding-inline-tokens.test.js +++ b/packages/e2e-tests/specs/adding-inline-tokens.test.js @@ -1,5 +1,5 @@ /** - * Node dependencies + * External dependencies */ import path from 'path'; import fs from 'fs'; diff --git a/packages/e2e-tests/specs/manage-reusable-blocks.test.js b/packages/e2e-tests/specs/manage-reusable-blocks.test.js index 2eaa22e219f25..05af841189a83 100644 --- a/packages/e2e-tests/specs/manage-reusable-blocks.test.js +++ b/packages/e2e-tests/specs/manage-reusable-blocks.test.js @@ -1,5 +1,5 @@ /** - * Node dependencies + * External dependencies */ import path from 'path'; diff --git a/packages/edit-post/src/components/fullscreen-mode/test/index.js b/packages/edit-post/src/components/fullscreen-mode/test/index.js index e61f5e1838ff2..928722b340da2 100644 --- a/packages/edit-post/src/components/fullscreen-mode/test/index.js +++ b/packages/edit-post/src/components/fullscreen-mode/test/index.js @@ -4,7 +4,7 @@ import { shallow } from 'enzyme'; /** - * Internal dependencies. + * Internal dependencies */ import { FullscreenMode } from '..'; diff --git a/packages/edit-post/src/components/header/feature-toggle/index.js b/packages/edit-post/src/components/header/feature-toggle/index.js index c2de21f3ba713..4e0df7f3749b0 100644 --- a/packages/edit-post/src/components/header/feature-toggle/index.js +++ b/packages/edit-post/src/components/header/feature-toggle/index.js @@ -4,7 +4,7 @@ import { flow } from 'lodash'; /** - * WordPress Dependencies + * WordPress dependencies */ import { withSelect, withDispatch } from '@wordpress/data'; import { compose } from '@wordpress/compose'; diff --git a/packages/edit-post/src/components/header/fullscreen-mode-close/index.js b/packages/edit-post/src/components/header/fullscreen-mode-close/index.js index 2cbde749470f6..3b1c42472db6d 100644 --- a/packages/edit-post/src/components/header/fullscreen-mode-close/index.js +++ b/packages/edit-post/src/components/header/fullscreen-mode-close/index.js @@ -1,10 +1,10 @@ /** - * External Dependencies + * External dependencies */ import { get } from 'lodash'; /** - * WordPress Dependencies + * WordPress dependencies */ import { withSelect } from '@wordpress/data'; import { IconButton, Toolbar } from '@wordpress/components'; diff --git a/packages/edit-post/src/components/header/options-menu-item/index.js b/packages/edit-post/src/components/header/options-menu-item/index.js index 66fcb5cd7cb3c..812da4a7e49b3 100644 --- a/packages/edit-post/src/components/header/options-menu-item/index.js +++ b/packages/edit-post/src/components/header/options-menu-item/index.js @@ -1,5 +1,5 @@ /** - * WordPress Dependencies + * WordPress dependencies */ import { withDispatch } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; diff --git a/packages/edit-post/src/components/header/post-publish-button-or-toggle.js b/packages/edit-post/src/components/header/post-publish-button-or-toggle.js index edf6699fc9eaf..828ba3ef6fc04 100644 --- a/packages/edit-post/src/components/header/post-publish-button-or-toggle.js +++ b/packages/edit-post/src/components/header/post-publish-button-or-toggle.js @@ -1,10 +1,10 @@ /** - * External Dependencies + * External dependencies */ import { get } from 'lodash'; /** - * WordPress dependencies. + * WordPress dependencies */ import { compose } from '@wordpress/compose'; import { withDispatch, withSelect } from '@wordpress/data'; diff --git a/packages/edit-post/src/components/header/test/index.js b/packages/edit-post/src/components/header/test/index.js index b672c74607b2f..e5e0516a94951 100644 --- a/packages/edit-post/src/components/header/test/index.js +++ b/packages/edit-post/src/components/header/test/index.js @@ -4,7 +4,7 @@ import { shallow } from 'enzyme'; /** - * Internal dependencies. + * Internal dependencies */ import { PostPublishButtonOrToggle } from '../post-publish-button-or-toggle'; diff --git a/packages/edit-post/src/components/header/writing-menu/index.js b/packages/edit-post/src/components/header/writing-menu/index.js index 6a3b6f62c4482..5a909d1c6ab38 100644 --- a/packages/edit-post/src/components/header/writing-menu/index.js +++ b/packages/edit-post/src/components/header/writing-menu/index.js @@ -1,5 +1,5 @@ /** - * WordPress Dependencies + * WordPress dependencies */ import { MenuGroup } from '@wordpress/components'; import { __, _x } from '@wordpress/i18n'; diff --git a/packages/edit-post/src/components/sidebar/index.js b/packages/edit-post/src/components/sidebar/index.js index 900b8725e6d77..b043403d8bf32 100644 --- a/packages/edit-post/src/components/sidebar/index.js +++ b/packages/edit-post/src/components/sidebar/index.js @@ -1,5 +1,5 @@ /** - * WordPress Dependencies + * WordPress dependencies */ import { createSlotFill, withFocusReturn } from '@wordpress/components'; import { withSelect } from '@wordpress/data'; diff --git a/packages/edit-post/src/components/sidebar/post-status/index.js b/packages/edit-post/src/components/sidebar/post-status/index.js index 4fedfa5fe286a..ba8705cd19641 100644 --- a/packages/edit-post/src/components/sidebar/post-status/index.js +++ b/packages/edit-post/src/components/sidebar/post-status/index.js @@ -8,7 +8,7 @@ import { withSelect, withDispatch } from '@wordpress/data'; import { compose } from '@wordpress/compose'; /** - * Internal Dependencies + * Internal dependencies */ import PostVisibility from '../post-visibility'; import PostTrash from '../post-trash'; diff --git a/packages/edit-post/src/components/sidebar/post-taxonomies/taxonomy-panel.js b/packages/edit-post/src/components/sidebar/post-taxonomies/taxonomy-panel.js index d354464caabce..5c85446e0ac14 100644 --- a/packages/edit-post/src/components/sidebar/post-taxonomies/taxonomy-panel.js +++ b/packages/edit-post/src/components/sidebar/post-taxonomies/taxonomy-panel.js @@ -1,5 +1,5 @@ /** - * External Dependencies + * External dependencies */ import { get } from 'lodash'; diff --git a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js index 3d4fa8dfb0ced..d6874aa100f04 100644 --- a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js +++ b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js @@ -8,7 +8,7 @@ import { BlockInspector } from '@wordpress/editor'; import { Fragment } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; /** - * Internal Dependencies + * Internal dependencies */ import Sidebar from '../'; import SettingsHeader from '../settings-header'; diff --git a/packages/edit-post/src/hooks/components/media-upload/index.js b/packages/edit-post/src/hooks/components/media-upload/index.js index c855216803389..0b4fe40ce4ea9 100644 --- a/packages/edit-post/src/hooks/components/media-upload/index.js +++ b/packages/edit-post/src/hooks/components/media-upload/index.js @@ -1,5 +1,5 @@ /** - * External Dependencies + * External dependencies */ import { castArray, defaults, pick } from 'lodash'; diff --git a/packages/edit-post/src/plugins/keyboard-shortcuts-help-menu-item/index.js b/packages/edit-post/src/plugins/keyboard-shortcuts-help-menu-item/index.js index b8c84c0645e59..92587f90f3afa 100644 --- a/packages/edit-post/src/plugins/keyboard-shortcuts-help-menu-item/index.js +++ b/packages/edit-post/src/plugins/keyboard-shortcuts-help-menu-item/index.js @@ -1,5 +1,5 @@ /** - * WordPress Dependencies + * WordPress dependencies */ import { MenuItem } from '@wordpress/components'; import { withDispatch } from '@wordpress/data'; diff --git a/packages/edit-post/src/store/index.js b/packages/edit-post/src/store/index.js index 7158916711a77..9294d7cd90ac6 100644 --- a/packages/edit-post/src/store/index.js +++ b/packages/edit-post/src/store/index.js @@ -1,5 +1,5 @@ /** - * WordPress Dependencies + * WordPress dependencies */ import { registerStore } from '@wordpress/data'; diff --git a/packages/editor/src/components/block-drop-zone/index.js b/packages/editor/src/components/block-drop-zone/index.js index a6f426aa6e266..1a8bb142aae89 100644 --- a/packages/editor/src/components/block-drop-zone/index.js +++ b/packages/editor/src/components/block-drop-zone/index.js @@ -1,5 +1,5 @@ /** - * External Dependencies + * External dependencies */ import classnames from 'classnames'; diff --git a/packages/editor/src/components/block-inspector/index.js b/packages/editor/src/components/block-inspector/index.js index 737b0863cbc2e..ee2405a3bac14 100644 --- a/packages/editor/src/components/block-inspector/index.js +++ b/packages/editor/src/components/block-inspector/index.js @@ -13,7 +13,7 @@ import { withSelect } from '@wordpress/data'; import { Fragment } from '@wordpress/element'; /** - * Internal Dependencies + * Internal dependencies */ import SkipToSelectedBlock from '../skip-to-selected-block'; import BlockIcon from '../block-icon'; diff --git a/packages/editor/src/components/block-list/block-html.js b/packages/editor/src/components/block-list/block-html.js index ca2c057dc8a40..fc7b3da86ef08 100644 --- a/packages/editor/src/components/block-list/block-html.js +++ b/packages/editor/src/components/block-list/block-html.js @@ -1,12 +1,12 @@ /** - * External Dependencies + * External dependencies */ import TextareaAutosize from 'react-autosize-textarea'; import { isEqual } from 'lodash'; /** - * WordPress Dependencies + * WordPress dependencies */ import { Component } from '@wordpress/element'; import { compose } from '@wordpress/compose'; diff --git a/packages/editor/src/components/block-toolbar/index.js b/packages/editor/src/components/block-toolbar/index.js index e37fbdb2e46aa..3da857dbd1b99 100644 --- a/packages/editor/src/components/block-toolbar/index.js +++ b/packages/editor/src/components/block-toolbar/index.js @@ -1,11 +1,11 @@ /** - * WordPress Dependencies + * WordPress dependencies */ import { withSelect } from '@wordpress/data'; import { Fragment } from '@wordpress/element'; /** - * Internal Dependencies + * Internal dependencies */ import BlockSwitcher from '../block-switcher'; import MultiBlocksSwitcher from '../block-switcher/multi-blocks-switcher'; diff --git a/packages/editor/src/components/multi-selection-inspector/index.js b/packages/editor/src/components/multi-selection-inspector/index.js index 5378c5fe6f539..51b48333923a5 100644 --- a/packages/editor/src/components/multi-selection-inspector/index.js +++ b/packages/editor/src/components/multi-selection-inspector/index.js @@ -11,7 +11,7 @@ import { } from '@wordpress/components'; /** - * Internal Dependencies + * Internal dependencies */ import BlockIcon from '../block-icon'; diff --git a/packages/editor/src/components/post-permalink/index.js b/packages/editor/src/components/post-permalink/index.js index c28a1d1754485..5205997f76e5e 100644 --- a/packages/editor/src/components/post-permalink/index.js +++ b/packages/editor/src/components/post-permalink/index.js @@ -15,7 +15,7 @@ import { ClipboardButton, Button, ExternalLink } from '@wordpress/components'; import { safeDecodeURI } from '@wordpress/url'; /** - * Internal Dependencies + * Internal dependencies */ import PostPermalinkEditor from './editor.js'; import { getWPAdminURL, cleanForSlug } from '../../utils/url'; diff --git a/packages/editor/src/components/post-publish-panel/index.js b/packages/editor/src/components/post-publish-panel/index.js index 0aef7ae9a3393..9f5e0e6814e17 100644 --- a/packages/editor/src/components/post-publish-panel/index.js +++ b/packages/editor/src/components/post-publish-panel/index.js @@ -19,7 +19,7 @@ import { withSelect, withDispatch } from '@wordpress/data'; import { compose } from '@wordpress/compose'; /** - * Internal Dependencies + * Internal dependencies */ import PostPublishButton from '../post-publish-button'; import PostPublishPanelPrepublish from './prepublish'; diff --git a/packages/editor/src/components/post-publish-panel/maybe-post-format-panel.js b/packages/editor/src/components/post-publish-panel/maybe-post-format-panel.js index 81e89698b4bf9..d3a853b4eb713 100644 --- a/packages/editor/src/components/post-publish-panel/maybe-post-format-panel.js +++ b/packages/editor/src/components/post-publish-panel/maybe-post-format-panel.js @@ -4,7 +4,7 @@ import { find, get, includes } from 'lodash'; /** - * WordPress dependencies. + * WordPress dependencies */ import { __, sprintf } from '@wordpress/i18n'; import { ifCondition, compose } from '@wordpress/compose'; @@ -12,7 +12,7 @@ import { withDispatch, withSelect } from '@wordpress/data'; import { Button, PanelBody } from '@wordpress/components'; /** - * Internal dependencies. + * Internal dependencies */ import { POST_FORMATS } from '../post-format'; diff --git a/packages/editor/src/components/post-publish-panel/postpublish.js b/packages/editor/src/components/post-publish-panel/postpublish.js index 0e0cc7f81862b..97fb7ff7ca3cd 100644 --- a/packages/editor/src/components/post-publish-panel/postpublish.js +++ b/packages/editor/src/components/post-publish-panel/postpublish.js @@ -1,10 +1,10 @@ /** - * External Dependencies + * External dependencies */ import { get } from 'lodash'; /** - * WordPress Dependencies + * WordPress dependencies */ import { PanelBody, Button, ClipboardButton, TextControl } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; diff --git a/packages/editor/src/components/post-publish-panel/prepublish.js b/packages/editor/src/components/post-publish-panel/prepublish.js index e05f97e5784ac..6006ec5ce5b46 100644 --- a/packages/editor/src/components/post-publish-panel/prepublish.js +++ b/packages/editor/src/components/post-publish-panel/prepublish.js @@ -12,7 +12,7 @@ import { PanelBody } from '@wordpress/components'; import { withSelect } from '@wordpress/data'; /** - * Internal Dependencies + * Internal dependencies */ import PostVisibility from '../post-visibility'; import PostVisibilityLabel from '../post-visibility/label'; diff --git a/packages/editor/src/components/post-taxonomies/check.js b/packages/editor/src/components/post-taxonomies/check.js index 432e1e15506a7..ab078b1bc70da 100644 --- a/packages/editor/src/components/post-taxonomies/check.js +++ b/packages/editor/src/components/post-taxonomies/check.js @@ -1,5 +1,5 @@ /** - * External Dependencies + * External dependencies */ import { some, includes } from 'lodash'; diff --git a/packages/editor/src/components/post-taxonomies/index.js b/packages/editor/src/components/post-taxonomies/index.js index b6d10a1a9b3ac..75159bd2f6875 100644 --- a/packages/editor/src/components/post-taxonomies/index.js +++ b/packages/editor/src/components/post-taxonomies/index.js @@ -1,5 +1,5 @@ /** - * External Dependencies + * External dependencies */ import { filter, identity, includes } from 'lodash'; diff --git a/packages/editor/src/components/post-visibility/index.js b/packages/editor/src/components/post-visibility/index.js index 672a450c88d1a..7115ee7ba1225 100644 --- a/packages/editor/src/components/post-visibility/index.js +++ b/packages/editor/src/components/post-visibility/index.js @@ -7,7 +7,7 @@ import { withInstanceId, compose } from '@wordpress/compose'; import { withSelect, withDispatch } from '@wordpress/data'; /** - * Internal Dependencies + * Internal dependencies */ import { visibilityOptions } from './utils'; diff --git a/packages/editor/src/components/post-visibility/label.js b/packages/editor/src/components/post-visibility/label.js index 1b296bc45ad3a..7c54c3f5b2de8 100644 --- a/packages/editor/src/components/post-visibility/label.js +++ b/packages/editor/src/components/post-visibility/label.js @@ -9,7 +9,7 @@ import { find } from 'lodash'; import { withSelect } from '@wordpress/data'; /** - * Internal Dependencies + * Internal dependencies */ import { visibilityOptions } from './utils'; diff --git a/packages/editor/src/components/post-visibility/utils.js b/packages/editor/src/components/post-visibility/utils.js index 53163db074303..42268ff062ad2 100644 --- a/packages/editor/src/components/post-visibility/utils.js +++ b/packages/editor/src/components/post-visibility/utils.js @@ -1,5 +1,5 @@ /** - * WordPress Dependencies + * WordPress dependencies */ import { __ } from '@wordpress/i18n'; diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index a889bb01c14ac..f22f129cc8acf 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -4,7 +4,7 @@ import { flow, map } from 'lodash'; /** - * WordPress Dependencies + * WordPress dependencies */ import { createElement, Component } from '@wordpress/element'; import { DropZoneProvider, SlotFillProvider } from '@wordpress/components'; diff --git a/packages/editor/src/components/server-side-render/index.js b/packages/editor/src/components/server-side-render/index.js index 8c69cfca9a132..5830612ebcc36 100644 --- a/packages/editor/src/components/server-side-render/index.js +++ b/packages/editor/src/components/server-side-render/index.js @@ -1,5 +1,5 @@ /** - * WordPress dependencies. + * WordPress dependencies */ import { ServerSideRender } from '@wordpress/components'; import { select } from '@wordpress/data'; diff --git a/packages/editor/src/components/skip-to-selected-block/index.js b/packages/editor/src/components/skip-to-selected-block/index.js index d55f4b04bdd7c..7fb58d88b59da 100644 --- a/packages/editor/src/components/skip-to-selected-block/index.js +++ b/packages/editor/src/components/skip-to-selected-block/index.js @@ -6,7 +6,7 @@ import { __ } from '@wordpress/i18n'; import { Button } from '@wordpress/components'; /** - * Internal Dependencies + * Internal dependencies */ import { getBlockFocusableWrapper } from '../../utils/dom'; diff --git a/packages/editor/src/editor-styles/ast/stringify/index.js b/packages/editor/src/editor-styles/ast/stringify/index.js index 0253ad968cc67..9a5b995f059b0 100644 --- a/packages/editor/src/editor-styles/ast/stringify/index.js +++ b/packages/editor/src/editor-styles/ast/stringify/index.js @@ -2,7 +2,7 @@ // because we needed to remove source map support. /** - * Internal dependencies. + * Internal dependencies */ import Compressed from './compress'; import Identity from './identity'; diff --git a/packages/editor/src/store/actions.js b/packages/editor/src/store/actions.js index c12a0cb0c6bf7..2043f21ed9a03 100644 --- a/packages/editor/src/store/actions.js +++ b/packages/editor/src/store/actions.js @@ -1,5 +1,5 @@ /** - * External Dependencies + * External dependencies */ import { castArray } from 'lodash'; diff --git a/packages/editor/src/store/index.js b/packages/editor/src/store/index.js index fb8f9fe11d5cc..11fa1c76eaf8c 100644 --- a/packages/editor/src/store/index.js +++ b/packages/editor/src/store/index.js @@ -1,5 +1,5 @@ /** - * WordPress Dependencies + * WordPress dependencies */ import { registerStore } from '@wordpress/data'; diff --git a/packages/editor/src/utils/media-upload/media-upload.js b/packages/editor/src/utils/media-upload/media-upload.js index 3cba5212f6271..3aed464f5c4c6 100644 --- a/packages/editor/src/utils/media-upload/media-upload.js +++ b/packages/editor/src/utils/media-upload/media-upload.js @@ -1,5 +1,5 @@ /** - * External Dependencies + * External dependencies */ import { compact, diff --git a/packages/eslint-plugin/rules/__tests__/dependency-group.js b/packages/eslint-plugin/rules/__tests__/dependency-group.js index 74e41626bfcd8..2089b9ab3ec02 100644 --- a/packages/eslint-plugin/rules/__tests__/dependency-group.js +++ b/packages/eslint-plugin/rules/__tests__/dependency-group.js @@ -19,18 +19,18 @@ ruleTester.run( 'dependency-group', rule, { valid: [ { code: ` -/* +/** * External dependencies */ import { get } from 'lodash'; import classnames from 'classnames'; -/* +/** * WordPress dependencies */ import { Component } from '@wordpress/element'; -/* +/** * Internal dependencies */ import edit from './edit';`, @@ -41,6 +41,9 @@ import edit from './edit';`, code: ` import { get } from 'lodash'; import classnames from 'classnames'; +/* + * wordpress dependencies. + */ import { Component } from '@wordpress/element'; import edit from './edit';`, errors: [ diff --git a/packages/eslint-plugin/rules/dependency-group.js b/packages/eslint-plugin/rules/dependency-group.js index c92345a905cb7..995250e53c187 100644 --- a/packages/eslint-plugin/rules/dependency-group.js +++ b/packages/eslint-plugin/rules/dependency-group.js @@ -18,6 +18,28 @@ module.exports = { * @typedef {string} WPPackageLocality */ + /** + * Object describing a dependency block correction to be made. + * + * @property {?espree.Node} comment Comment node on which to replace + * value, if one can be salvaged. + * @property {string} value Expected comment node value. + * + * @typedef {Object} WPDependencyBlockCorrection + */ + + /** + * Given a desired locality, generates the expected comment node value + * property. + * + * @param {WPPackageLocality} locality Desired package locality. + * + * @return {string} Expected comment node value. + */ + function getCommentValue( locality ) { + return `*\n * ${ locality } dependencies\n `; + } + /** * Given an import source string, returns the locality classification * of the import sort. @@ -51,7 +73,7 @@ module.exports = { return false; } - // (Temporary) Tolerances: + // Tolerances: // - Normalize `/**` and `/*` // - Case insensitive "Dependencies" vs. "dependencies" // - Ending period @@ -61,7 +83,7 @@ module.exports = { locality = '(External|Node)'; } - const pattern = new RegExp( `^\\*?\\n \\* ${ locality } [dD]ependencies\\.?\\n $` ); + const pattern = new RegExp( `^\\*?\\n \\* ${ locality } dependencies\\.?\\n $`, 'i' ); return pattern.test( value ); } @@ -79,18 +101,44 @@ module.exports = { } /** - * Returns true if a given node is preceded by a comment block - * satisfying a locality requirement, or false otherwise. + * Tests source comments to determine whether a comment exists which + * satisfies the desired locality. If a match is found and requires no + * updates, the function returns undefined. Otherwise, it will return + * a WPDependencyBlockCorrection object describing a correction. * * @param {espree.Node} node Node to test. * @param {WPPackageLocality} locality Desired package locality. * - * @return {boolean} Whether node preceded by locality comment block. + * @return {?WPDependencyBlockCorrection} Correction, if applicable. */ - function isPrecededByDependencyBlock( node, locality ) { - return comments.some( ( comment ) => { - return isLocalityDependencyBlock( comment, locality ) && isBefore( comment, node ); - } ); + function getDependencyBlockCorrection( node, locality ) { + const value = getCommentValue( locality ); + + let comment; + for ( let i = 0; i < comments.length; i++ ) { + comment = comments[ i ]; + + if ( ! isBefore( comment, node ) ) { + // Exhausted options. + break; + } + + if ( ! isLocalityDependencyBlock( comment, locality ) ) { + // Not usable (either not an block comment, or not one + // matching a tolerable pattern). + continue; + } + + if ( comment.value === value ) { + // No change needed. (OK) + return; + } + + // Found a comment needing correction. + return { comment, value }; + } + + return { value }; } return { @@ -103,7 +151,7 @@ module.exports = { * * @type {Set<WPPackageLocality>} */ - const reported = new Set(); + const verified = new Set(); // Since we only care to enforce imports which occur at the // top-level scope, match on Program and test its children, @@ -133,23 +181,31 @@ module.exports = { } const locality = getPackageLocality( source ); - if ( - reported.has( locality ) || - isPrecededByDependencyBlock( child, locality ) - ) { + if ( verified.has( locality ) ) { return; } - reported.add( locality ); + // Avoid verifying any other imports for the locality, + // regardless whether a correction must be made. + verified.add( locality ); + + // Determine whether a correction must be made. + const correction = getDependencyBlockCorrection( child, locality ); + if ( ! correction ) { + return; + } context.report( { node: child, message: `Expected preceding "${ locality } dependencies" comment block`, fix( fixer ) { - return fixer.insertTextBefore( - child, - `/**\n * ${ locality } dependencies\n */\n` - ); + const { comment, value } = correction; + const text = `/*${ value }*/`; + if ( comment ) { + return fixer.replaceText( comment, text ); + } + + return fixer.insertTextBefore( child, text + '\n' ); }, } ); } ); diff --git a/packages/is-shallow-equal/test/index.js b/packages/is-shallow-equal/test/index.js index 49f2526dbdf27..8a7e80cf02156 100644 --- a/packages/is-shallow-equal/test/index.js +++ b/packages/is-shallow-equal/test/index.js @@ -1,5 +1,5 @@ /** - * Internal Dependencies + * Internal dependencies */ import isShallowEqual from '../'; import isShallowEqualArrays from '../arrays'; diff --git a/packages/wordcount/src/test/index.test.js b/packages/wordcount/src/test/index.test.js index 467b5f8c5359d..2537ca49e213a 100644 --- a/packages/wordcount/src/test/index.test.js +++ b/packages/wordcount/src/test/index.test.js @@ -1,5 +1,5 @@ /** - * Internal Dependencies + * Internal dependencies */ import { count } from '../'; From 0c30ee371e7ae6c372ee6e544232e2fd01a78d62 Mon Sep 17 00:00:00 2001 From: Karol Gorski <naerriel@gmail.com> Date: Mon, 11 Feb 2019 23:16:02 +0100 Subject: [PATCH 401/691] Components: FontSizePicker: Stop displaying font-size-picker when there are no fonts. (#13782) --- .../components/src/font-size-picker/index.js | 82 ++++++++++--------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/packages/components/src/font-size-picker/index.js b/packages/components/src/font-size-picker/index.js index 2908b3879c1eb..2e7877cf20613 100644 --- a/packages/components/src/font-size-picker/index.js +++ b/packages/components/src/font-size-picker/index.js @@ -41,47 +41,49 @@ function FontSizePicker( { return ( <BaseControl label={ __( 'Font Size' ) }> <div className="components-font-size-picker__buttons"> - <Dropdown - className="components-font-size-picker__dropdown" - contentClassName="components-font-size-picker__dropdown-content" - position="bottom" - renderToggle={ ( { isOpen, onToggle } ) => ( - <Button - className="components-font-size-picker__selector" - isLarge - onClick={ onToggle } - aria-expanded={ isOpen } - aria-label={ sprintf( - /* translators: %s: font size name */ - __( 'Font size: %s' ), currentFontSizeName - ) } - > - { currentFontSizeName } - </Button> - ) } - renderContent={ () => ( - <NavigableMenu> - { map( fontSizes, ( { name, size, slug } ) => { - const isSelected = ( value === size || ( ! value && slug === 'normal' ) ); + { ( fontSizes.length > 0 ) && + <Dropdown + className="components-font-size-picker__dropdown" + contentClassName="components-font-size-picker__dropdown-content" + position="bottom" + renderToggle={ ( { isOpen, onToggle } ) => ( + <Button + className="components-font-size-picker__selector" + isLarge + onClick={ onToggle } + aria-expanded={ isOpen } + aria-label={ sprintf( + /* translators: %s: font size name */ + __( 'Font size: %s' ), currentFontSizeName + ) } + > + { currentFontSizeName } + </Button> + ) } + renderContent={ () => ( + <NavigableMenu> + { map( fontSizes, ( { name, size, slug } ) => { + const isSelected = ( value === size || ( ! value && slug === 'normal' ) ); - return ( - <Button - key={ slug } - onClick={ () => onChange( slug === 'normal' ? undefined : size ) } - className={ `is-font-${ slug }` } - role="menuitemradio" - aria-checked={ isSelected } - > - { isSelected && <Dashicon icon="saved" /> } - <span className="components-font-size-picker__dropdown-text-size" style={ { fontSize: size } }> - { name } - </span> - </Button> - ); - } ) } - </NavigableMenu> - ) } - /> + return ( + <Button + key={ slug } + onClick={ () => onChange( slug === 'normal' ? undefined : size ) } + className={ `is-font-${ slug }` } + role="menuitemradio" + aria-checked={ isSelected } + > + { isSelected && <Dashicon icon="saved" /> } + <span className="components-font-size-picker__dropdown-text-size" style={ { fontSize: size } }> + { name } + </span> + </Button> + ); + } ) } + </NavigableMenu> + ) } + /> + } { ( ! withSlider && ! disableCustomFontSizes ) && <input className="components-range-control__number" From 134214d5129fdbb4dcbc9a21a03e82d1fb464c28 Mon Sep 17 00:00:00 2001 From: Darren Ethier <darren@roughsmootheng.in> Date: Tue, 12 Feb 2019 03:02:03 -0500 Subject: [PATCH 402/691] Fix recursion in sync routine. (#13818) --- packages/redux-routine/CHANGELOG.md | 3 ++- packages/redux-routine/src/runtime.js | 2 +- packages/redux-routine/src/test/index.js | 17 +++++++++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/redux-routine/CHANGELOG.md b/packages/redux-routine/CHANGELOG.md index dbddb8321016b..1bec9149f6099 100644 --- a/packages/redux-routine/CHANGELOG.md +++ b/packages/redux-routine/CHANGELOG.md @@ -4,7 +4,8 @@ - Fix unhandled promise rejection error caused by returning null from registered generator ([#13314](https://github.com/WordPress/gutenberg/pull/13314)) - The middleware will no longer attempt to coerce an error to an instance of `Error`, and instead passes through the thrown value directly. This resolves issues where an `Error` would be thrown when the underlying values were not of type `Error` or `string` (e.g. a thrown object) and the message would end up not being useful (e.g. `[Object object]`). -([#13315](https://github.com/WordPress/gutenberg/pull/13315)) +([#13315](https://github.com/WordPress/gutenberg/pull/13315)) +- Fix unintended recursion when invoking sync routine ([#13818](https://github.com/WordPress/gutenberg/pull/13818)) ## 3.0.3 (2018-10-19) diff --git a/packages/redux-routine/src/runtime.js b/packages/redux-routine/src/runtime.js index d3de265c3f2b1..ec984a148435e 100644 --- a/packages/redux-routine/src/runtime.js +++ b/packages/redux-routine/src/runtime.js @@ -28,7 +28,7 @@ export default function createRuntime( controls = {}, dispatch ) { // Async control routine awaits resolution. routine.then( yieldNext, yieldError ); } else { - next( routine ); + yieldNext( routine ); } return true; } ); diff --git a/packages/redux-routine/src/test/index.js b/packages/redux-routine/src/test/index.js index 79a35673f70a5..4a25b4771580f 100644 --- a/packages/redux-routine/src/test/index.js +++ b/packages/redux-routine/src/test/index.js @@ -143,4 +143,21 @@ describe( 'createMiddleware', () => { expect( store.getState() ).toBe( 2 ); } ); + + it( 'does not recurse when action like object returns from a sync ' + + 'control', () => { + const post = { type: 'post' }; + const middleware = createMiddleware( { + UPDATE: () => post, + } ); + const store = createStoreWithMiddleware( middleware ); + function* getPostAction() { + const nextState = yield { type: 'UPDATE' }; + return { type: 'CHANGE', nextState }; + } + + store.dispatch( getPostAction() ); + + expect( store.getState() ).toEqual( post ); + } ); } ); From 33e6d42229cdab77c5a8d72daef54fc1230db151 Mon Sep 17 00:00:00 2001 From: Joen Asmussen <joen@automattic.com> Date: Tue, 12 Feb 2019 09:17:02 +0100 Subject: [PATCH 403/691] Try: Show background and increased opacity on disabled switcher (#13721) * Try: Show background and increased opacity on disabled switcher This one goes out to all the @chrivanpatten's in the crowd! Well, specifically, it is in response to this conversation we had here: https://github.com/WordPress/gutenberg/issues/11669#issuecomment-456808244 The problem: we always show the Block Switcher interface, even if no transformations are available. When no transformations are available, the buttonis _disabled_, which usually means it needs to have very contrast. However in this case, the block switcher also works as a block type indicator, which makes it valuable to be able to see the icon in question. This PR tries to marry the two challenges and shows the button as disabled adding a light gray background, but still increasing the opacity. Thoughts? * Try darker gray and monochrome icon. --- .../src/components/block-switcher/style.scss | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/editor/src/components/block-switcher/style.scss b/packages/editor/src/components/block-switcher/style.scss index a994ff7542404..0f062116d1b00 100644 --- a/packages/editor/src/components/block-switcher/style.scss +++ b/packages/editor/src/components/block-switcher/style.scss @@ -11,15 +11,30 @@ padding: 3px; } - .components-icon-button.editor-block-switcher__no-switcher-icon { width: $icon-button-size + 6px + 6px; + .editor-block-icon { margin-right: auto; margin-left: auto; } } +// When the block switcher does not have any transformations, we show it but as disabled. +// The background and opacity change helps make the icon legible, despite being disabled. +.components-button.editor-block-switcher__no-switcher-icon:disabled { + background: $light-gray-200; + border-radius: 0; + opacity: 0.84; + + // Also make the icon monochrome to further imply disabled state. + // We use !important here because icon colors are set as inline styles, + // and should be overridden when disabled. + .editor-block-icon.has-colors { + color: $dark-gray-500 !important; + } +} + // Style this the same as the block buttons in the library. // Needs specificiity to override the icon button. .components-icon-button.editor-block-switcher__toggle { From 9dc1a33f2b175b639f9678aa94604bfb29b3838a Mon Sep 17 00:00:00 2001 From: Joen Asmussen <joen@automattic.com> Date: Tue, 12 Feb 2019 09:37:14 +0100 Subject: [PATCH 404/691] Add animation to the sidebar. (#13635) --- packages/components/src/animate/README.md | 10 +++++++ packages/components/src/animate/index.js | 11 ++++++++ packages/components/src/animate/style.scss | 15 +++++++++++ .../src/find-sidebar-panel-with-title.js | 2 +- .../src/components/layout/style.scss | 4 +-- .../edit-post/src/components/sidebar/index.js | 27 ++++++++++++------- 6 files changed, 57 insertions(+), 12 deletions(-) diff --git a/packages/components/src/animate/README.md b/packages/components/src/animate/README.md index 5ad580bac51e6..4d643698cf77e 100644 --- a/packages/components/src/animate/README.md +++ b/packages/components/src/animate/README.md @@ -37,3 +37,13 @@ This animation is meant for popover/modal content, such as menus appearing. It s Name | Type | Default | Description --- | --- | --- | --- `origin` | `string` | `top center` | Point of origin (`top`, `bottom`,` middle right`, `left`, `center`). + +### slide-in + +This animation is meant for sidebars and sliding menus. It shows the height and width of the animated element moving from a hidden position to its normal one. + +#### Options + +Name | Type | Default | Description +--- | --- | --- | --- +`origin` | `string` | `left` | Point of origin (`left`). diff --git a/packages/components/src/animate/index.js b/packages/components/src/animate/index.js index a144e6ee8b27d..03abc44eae0ed 100644 --- a/packages/components/src/animate/index.js +++ b/packages/components/src/animate/index.js @@ -19,6 +19,17 @@ function Animate( { type, options = {}, children } ) { } ); } + if ( type === 'slide-in' ) { + const { origin = 'left' } = options; + + return children( { + className: classnames( + 'components-animate__slide-in', + 'is-from-' + origin, + ), + } ); + } + return children( {} ); } diff --git a/packages/components/src/animate/style.scss b/packages/components/src/animate/style.scss index 0a725ce0032d4..2d5dfcc9b5abd 100644 --- a/packages/components/src/animate/style.scss +++ b/packages/components/src/animate/style.scss @@ -26,3 +26,18 @@ transform: translateY(0%) scaleY(1) scaleX(1); } } + +.components-animate__slide-in { + animation: components-animate__slide-in-animation 0.1s cubic-bezier(0, 0, 0.2, 1); + animation-fill-mode: forwards; + + &.is-from-left { + transform: translateX(+100%); + } +} + +@keyframes components-animate__slide-in-animation { + 100% { + transform: translateX(0%); + } +} diff --git a/packages/e2e-test-utils/src/find-sidebar-panel-with-title.js b/packages/e2e-test-utils/src/find-sidebar-panel-with-title.js index fae3f33b53910..bd25778712675 100644 --- a/packages/e2e-test-utils/src/find-sidebar-panel-with-title.js +++ b/packages/e2e-test-utils/src/find-sidebar-panel-with-title.js @@ -11,5 +11,5 @@ import { first } from 'lodash'; * @return {?ElementHandle} Object that represents an in-page DOM element. */ export async function findSidebarPanelWithTitle( panelTitle ) { - return first( await page.$x( `//div[@class="edit-post-sidebar"]//button[@class="components-button components-panel__body-toggle"][contains(text(),"${ panelTitle }")]` ) ); + return first( await page.$x( `//div[contains(@class,"edit-post-sidebar")]//button[@class="components-button components-panel__body-toggle"][contains(text(),"${ panelTitle }")]` ) ); } diff --git a/packages/edit-post/src/components/layout/style.scss b/packages/edit-post/src/components/layout/style.scss index 963862b7105d7..f848a8cfd7ccd 100644 --- a/packages/edit-post/src/components/layout/style.scss +++ b/packages/edit-post/src/components/layout/style.scss @@ -173,7 +173,7 @@ width: $sidebar-width; border-left: $border-width solid $light-gray-500; transform: translateX(+100%); - animation: edit-post-layout__slide-in-animation 0.1s forwards; + animation: edit-post-post-publish-panel__slide-in-animation 0.1s forwards; body.is-fullscreen-mode & { top: 0; @@ -181,7 +181,7 @@ } } -@keyframes edit-post-layout__slide-in-animation { +@keyframes edit-post-post-publish-panel__slide-in-animation { 100% { transform: translateX(0%); } diff --git a/packages/edit-post/src/components/sidebar/index.js b/packages/edit-post/src/components/sidebar/index.js index b043403d8bf32..a9da35865e875 100644 --- a/packages/edit-post/src/components/sidebar/index.js +++ b/packages/edit-post/src/components/sidebar/index.js @@ -1,7 +1,12 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + /** * WordPress dependencies */ -import { createSlotFill, withFocusReturn } from '@wordpress/components'; +import { createSlotFill, withFocusReturn, Animate } from '@wordpress/components'; import { withSelect } from '@wordpress/data'; import { ifCondition, compose } from '@wordpress/compose'; @@ -15,14 +20,18 @@ const { Fill, Slot } = createSlotFill( 'Sidebar' ); const Sidebar = ( { children, label } ) => { return ( <Fill> - <div - className="edit-post-sidebar" - role="region" - aria-label={ label } - tabIndex="-1" - > - { children } - </div> + <Animate type="slide-in" options={ { origin: 'left' } }> + { ( { className } ) => ( + <div + className={ classnames( 'edit-post-sidebar', className ) } + role="region" + aria-label={ label } + tabIndex="-1" + > + { children } + </div> + ) } + </Animate> </Fill> ); }; From 15ecbb0dfc1b6e01fef7779abe5e0974c73290b1 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Tue, 12 Feb 2019 09:15:33 +0000 Subject: [PATCH 405/691] Fix: Impossible to set empty array on editor-font-sizes. (#13824) --- lib/client-assets.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/client-assets.php b/lib/client-assets.php index 97afc6084a41c..e2a6cd048467d 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -1118,7 +1118,7 @@ function gutenberg_editor_scripts_and_styles( $hook ) { $editor_settings['colors'] = $color_palette; } - if ( ! empty( $font_sizes ) ) { + if ( false !== $font_sizes ) { $editor_settings['fontSizes'] = $font_sizes; } From 6d6e3c492032897f17861fda18fdeba09fe17739 Mon Sep 17 00:00:00 2001 From: Jonny Harris <spacedmonkey@users.noreply.github.com> Date: Tue, 12 Feb 2019 09:17:32 +0000 Subject: [PATCH 406/691] Check if thumbnail_url is set in getPhotoHtml (#13825) thumbnail_url is an optional field in oembed endpoints. Check it is set first before using and fallback to url if not. --- packages/block-library/src/embed/util.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/embed/util.js b/packages/block-library/src/embed/util.js index 5161d13523c6b..085ea4b22ff8c 100644 --- a/packages/block-library/src/embed/util.js +++ b/packages/block-library/src/embed/util.js @@ -51,8 +51,9 @@ export const isFromWordPress = ( html ) => { export const getPhotoHtml = ( photo ) => { // 100% width for the preview so it fits nicely into the document, some "thumbnails" are - // actually the full size photo. - const photoPreview = <p><img src={ photo.thumbnail_url } alt={ photo.title } width="100%" /></p>; + // actually the full size photo. If thumbnails not found, use full image. + const imageUrl = ( photo.thumbnail_url ) ? photo.thumbnail_url : photo.url; + const photoPreview = <p><img src={ imageUrl } alt={ photo.title } width="100%" /></p>; return renderToString( photoPreview ); }; From 9ef2cf57d2f59ee7b46db2efc8fa534b4cebc650 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20Van=C2=A0Durpe?= <iseulde@automattic.com> Date: Tue, 12 Feb 2019 10:56:20 +0100 Subject: [PATCH 407/691] WIP: make shift+enter work in lists (#13546) * WIP: make shift+enter work in lists * Merge similar logic * Add e2e tests --- .../blocks/__snapshots__/list.test.js.snap | 12 +++++++ packages/e2e-tests/specs/blocks/list.test.js | 21 ++++++++++++ .../editor/src/components/rich-text/index.js | 21 +++--------- packages/rich-text/src/index.js | 1 + packages/rich-text/src/insert-line-break.js | 34 +++++++++++++++++++ 5 files changed, 73 insertions(+), 16 deletions(-) create mode 100644 packages/rich-text/src/insert-line-break.js diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap b/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap index 71118e8d65118..ef176d16bb9d7 100644 --- a/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap +++ b/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap @@ -144,6 +144,18 @@ exports[`List should indent and outdent level 2 3`] = ` <!-- /wp:list -->" `; +exports[`List should insert a line break on shift+enter 1`] = ` +"<!-- wp:list --> +<ul><li>a<br><br></li></ul> +<!-- /wp:list -->" +`; + +exports[`List should insert a line break on shift+enter in a non trailing list item 1`] = ` +"<!-- wp:list --> +<ul><li>a</li><li>b<br><br></li><li>c</li></ul> +<!-- /wp:list -->" +`; + exports[`List should outdent with children 1`] = ` "<!-- wp:list --> <ul><li>a<ul><li>b<ul><li>c</li></ul></li></ul></li></ul> diff --git a/packages/e2e-tests/specs/blocks/list.test.js b/packages/e2e-tests/specs/blocks/list.test.js index 98b7f74bd2536..915c9da4f537a 100644 --- a/packages/e2e-tests/specs/blocks/list.test.js +++ b/packages/e2e-tests/specs/blocks/list.test.js @@ -280,4 +280,25 @@ describe( 'List', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + + it( 'should insert a line break on shift+enter', async () => { + await insertBlock( 'List' ); + await page.keyboard.type( 'a' ); + await pressKeyWithModifier( 'shift', 'Enter' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'should insert a line break on shift+enter in a non trailing list item', async () => { + await insertBlock( 'List' ); + await page.keyboard.type( 'a' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( 'b' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( 'c' ); + await page.keyboard.press( 'ArrowUp' ); + await pressKeyWithModifier( 'shift', 'Enter' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); } ); diff --git a/packages/editor/src/components/rich-text/index.js b/packages/editor/src/components/rich-text/index.js index f91f5f77b17a4..59ba6e6e4225c 100644 --- a/packages/editor/src/components/rich-text/index.js +++ b/packages/editor/src/components/rich-text/index.js @@ -33,6 +33,7 @@ import { toHTMLString, getTextContent, insert, + insertLineBreak, insertLineSeparator, isEmptyLine, unstableToDom, @@ -599,27 +600,15 @@ export class RichText extends Component { } if ( this.multilineTag ) { - if ( this.onSplit && isEmptyLine( record ) ) { + if ( event.shiftKey ) { + this.onChange( insertLineBreak( record ) ); + } else if ( this.onSplit && isEmptyLine( record ) ) { this.onSplit( ...split( record ).map( this.valueToFormat ) ); } else { this.onChange( insertLineSeparator( record ) ); } } else if ( event.shiftKey || ! this.onSplit ) { - const text = getTextContent( record ); - const length = text.length; - let toInsert = '\n'; - - // If the caret is at the end of the text, and there is no - // trailing line break or no text at all, we have to insert two - // line breaks in order to create a new line visually and place - // the caret there. - if ( record.end === length && ( - text.charAt( length - 1 ) !== '\n' || length === 0 - ) ) { - toInsert = '\n\n'; - } - - this.onChange( insert( record, toInsert ) ); + this.onChange( insertLineBreak( record ) ); } else { this.splitContent(); } diff --git a/packages/rich-text/src/index.js b/packages/rich-text/src/index.js index 7ce8ed970e6eb..423046079132b 100644 --- a/packages/rich-text/src/index.js +++ b/packages/rich-text/src/index.js @@ -19,6 +19,7 @@ export { removeFormat } from './remove-format'; export { remove } from './remove'; export { replace } from './replace'; export { insert } from './insert'; +export { insertLineBreak } from './insert-line-break'; export { insertLineSeparator } from './insert-line-separator'; export { insertObject } from './insert-object'; export { slice } from './slice'; diff --git a/packages/rich-text/src/insert-line-break.js b/packages/rich-text/src/insert-line-break.js new file mode 100644 index 0000000000000..540214d614f3c --- /dev/null +++ b/packages/rich-text/src/insert-line-break.js @@ -0,0 +1,34 @@ +/** + * Internal dependencies + */ + +import { insert } from './insert'; +import { LINE_SEPARATOR } from './special-characters'; + +/** + * Inserts a line break at the given or selected position. Inserts two line + * breaks if at the end of a line. + * + * @param {Object} value Value to modify. + * + * @return {Object} The value with the line break(s) inserted. + */ +export function insertLineBreak( value ) { + const { text, end } = value; + const length = text.length; + + let toInsert = '\n'; + + // If the caret is at the end of the text, and there is no + // trailing line break or no text at all, we have to insert two + // line breaks in order to create a new line visually and place + // the caret there. + if ( + ( end === length || text[ end ] === LINE_SEPARATOR ) && + ( text[ end - 1 ] !== '\n' || length === 0 ) + ) { + toInsert = '\n\n'; + } + + return insert( value, toInsert ); +} From 444c91987840b8d466740174c114a97eb00fcb16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20Van=C2=A0Durpe?= <iseulde@automattic.com> Date: Tue, 12 Feb 2019 13:11:35 +0100 Subject: [PATCH 408/691] List Block: Do not split on line breaks when multiple blocks are transformed (#13832) --- packages/block-library/src/list/index.js | 14 +++++++++++--- .../specs/blocks/__snapshots__/list.test.js.snap | 6 ++++++ packages/e2e-tests/specs/blocks/list.test.js | 15 +++++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/packages/block-library/src/list/index.js b/packages/block-library/src/list/index.js index e7a6174ef2508..4137dcdf3a673 100644 --- a/packages/block-library/src/list/index.js +++ b/packages/block-library/src/list/index.js @@ -73,9 +73,17 @@ export const settings = { transform: ( blockAttributes ) => { return createBlock( 'core/list', { values: toHTMLString( { - value: join( blockAttributes.map( ( { content } ) => - replace( create( { html: content } ), /\n/g, LINE_SEPARATOR ) - ), LINE_SEPARATOR ), + value: join( blockAttributes.map( ( { content } ) => { + const value = create( { html: content } ); + + if ( blockAttributes.length > 1 ) { + return value; + } + + // When converting only one block, transform + // every line to a list item. + return replace( value, /\n/g, LINE_SEPARATOR ); + } ), LINE_SEPARATOR ), multilineTag: 'li', } ), } ); diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap b/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap index ef176d16bb9d7..efc3b6d274373 100644 --- a/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap +++ b/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap @@ -156,6 +156,12 @@ exports[`List should insert a line break on shift+enter in a non trailing list i <!-- /wp:list -->" `; +exports[`List should not transform lines in block when transforming multiple blocks 1`] = ` +"<!-- wp:list --> +<ul><li>one<br>...</li><li>two</li></ul> +<!-- /wp:list -->" +`; + exports[`List should outdent with children 1`] = ` "<!-- wp:list --> <ul><li>a<ul><li>b<ul><li>c</li></ul></li></ul></li></ul> diff --git a/packages/e2e-tests/specs/blocks/list.test.js b/packages/e2e-tests/specs/blocks/list.test.js index 915c9da4f537a..ca56e691a1050 100644 --- a/packages/e2e-tests/specs/blocks/list.test.js +++ b/packages/e2e-tests/specs/blocks/list.test.js @@ -95,6 +95,21 @@ describe( 'List', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + it( 'should not transform lines in block when transforming multiple blocks', async () => { + await clickBlockAppender(); + await page.keyboard.type( 'one' ); + await pressKeyWithModifier( 'shift', 'Enter' ); + await page.keyboard.type( '...' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( 'two' ); + await page.keyboard.down( 'Shift' ); + await page.click( '[data-type="core/paragraph"]' ); + await page.keyboard.up( 'Shift' ); + await transformBlockTo( 'List' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + it( 'can be converted to paragraphs', async () => { await insertBlock( 'List' ); await page.keyboard.type( 'one' ); From e9f6ddd16171cb9de19385eeb2b4387229394ec8 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell@kjellr.com> Date: Tue, 12 Feb 2019 08:27:46 -0500 Subject: [PATCH 409/691] Restore more specific CSS for paragraph block custom color classes. (#13821) This extra specificity was removed in #13025, but should be restored because other blocks (buttons, pullquotes, etc), use the `has-background` and `has-text-color` classes. We don't want the styles here to interfere. Using a `p` selector here, rather than something like `.wp-block-paragraph` so that this gets picked up correctly on the front-end as well. --- packages/block-library/src/paragraph/style.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/paragraph/style.scss b/packages/block-library/src/paragraph/style.scss index 10ed6de1b484f..5fe83b763c70c 100644 --- a/packages/block-library/src/paragraph/style.scss +++ b/packages/block-library/src/paragraph/style.scss @@ -35,10 +35,10 @@ padding-top: $block-padding; } -.has-background { +p.has-background { padding: 20px 30px; } -.has-text-color a { +p.has-text-color a { color: inherit; } From ba38d23b4dbb47e59c1ff491357e49a9871e9de1 Mon Sep 17 00:00:00 2001 From: etoledom <etoledom@icloud.com> Date: Tue, 12 Feb 2019 14:58:25 +0100 Subject: [PATCH 410/691] Mobile: Implementing `Clear All Settings` button on Image Settings (#13753) * Mobile: Implementing `Reset to Original` button on Image Settings * Mobile: Change `Revert to Origin` button title to 'Clear All Settings' * Mobile Image block: Changed method name to `onClearSettings` for clarity --- packages/block-library/src/image/edit.native.js | 16 +++++++++++++--- .../block-library/src/image/styles.native.scss | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index 77d4ce4438b47..521b143e6c2a2 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -44,6 +44,7 @@ const MEDIA_UPLOAD_STATE_FAILED = 3; const MEDIA_UPLOAD_STATE_RESET = 4; const LINK_DESTINATION_CUSTOM = 'custom'; +const LINK_DESTINATION_NONE = 'none'; class ImageEdit extends React.Component { constructor( props ) { @@ -65,6 +66,7 @@ class ImageEdit extends React.Component { this.updateImageURL = this.updateImageURL.bind( this ); this.onSetLinkDestination = this.onSetLinkDestination.bind( this ); this.onImagePressed = this.onImagePressed.bind( this ); + this.onClearSettings = this.onClearSettings.bind( this ); } componentDidMount() { @@ -163,6 +165,14 @@ class ImageEdit extends React.Component { } ); } + onClearSettings() { + this.props.setAttributes( { + alt: '', + linkDestination: LINK_DESTINATION_NONE, + href: undefined, + } ); + } + render() { const { attributes, isSelected, setAttributes } = this.props; const { url, caption, height, width, alt, href } = attributes; @@ -244,10 +254,10 @@ class ImageEdit extends React.Component { onChangeValue={ this.updateAlt } /> <BottomSheet.Cell - label={ __( 'Reset to Original' ) } - labelStyle={ styles.resetSettingsButton } + label={ __( 'Clear All Settings' ) } + labelStyle={ styles.clearSettingsButton } drawSeparator={ false } - onPress={ () => {} } + onPress={ this.onClearSettings } /> </BottomSheet> ); diff --git a/packages/block-library/src/image/styles.native.scss b/packages/block-library/src/image/styles.native.scss index cc3566080f666..ec59c4caf0a98 100644 --- a/packages/block-library/src/image/styles.native.scss +++ b/packages/block-library/src/image/styles.native.scss @@ -24,6 +24,6 @@ font-family: $default-regular-font; } -.resetSettingsButton { +.clearSettingsButton { color: $alert-red; } From c36f32c6effad04911fd308b88fdf49709935524 Mon Sep 17 00:00:00 2001 From: Pinar Olguc <pinarolguc@gmail.com> Date: Tue, 12 Feb 2019 17:19:06 +0300 Subject: [PATCH 411/691] Mobile - Remove unnecessary forceUpdate (#13705) * Remove force update on content size change * Remove setting minHeight since it is already set in paragraph and heading --- packages/editor/src/components/rich-text/index.native.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/editor/src/components/rich-text/index.native.js b/packages/editor/src/components/rich-text/index.native.js index ac1f8593d73c9..f2b88a4cf8a83 100644 --- a/packages/editor/src/components/rich-text/index.native.js +++ b/packages/editor/src/components/rich-text/index.native.js @@ -205,7 +205,6 @@ export class RichText extends Component { onContentSizeChange( contentSize ) { const contentHeight = contentSize.height; - this.forceUpdate(); // force re-render the component skipping shouldComponentUpdate() See: https://reactjs.org/docs/react-component.html#forceupdate this.props.onContentSizeChange( { aztecHeight: contentHeight, } From 3341723f7e086db0c0de5902941df7961e60ee29 Mon Sep 17 00:00:00 2001 From: Damon Cook <colorful-tones@users.noreply.github.com> Date: Tue, 12 Feb 2019 10:42:13 -0500 Subject: [PATCH 412/691] Enhancing Quote block styling for different text alignments (#13248) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * adjust borders for Quote block text-align * update attribute selector [style*=“text-align: right”] * add [style*="text-align:right"] styling for Quote * add asterisk to [style*=“text-align:center”] * add [style*="text-align: center"] override as well --- packages/block-library/src/quote/theme.scss | 23 +++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/packages/block-library/src/quote/theme.scss b/packages/block-library/src/quote/theme.scss index 8fbe1ef7b3b21..3657883ec833f 100644 --- a/packages/block-library/src/quote/theme.scss +++ b/packages/block-library/src/quote/theme.scss @@ -1,5 +1,7 @@ .wp-block-quote { + border-left: 4px solid $black; margin: 20px 0; + padding-left: 1em; cite, footer, @@ -10,9 +12,22 @@ position: relative; font-style: normal; } -} -.wp-block-quote:not(.is-large):not(.is-style-large) { - border-left: 4px solid $black; - padding-left: 1em; + &[style*="text-align:right"], + &[style*="text-align: right"] { + border-left: none; + border-right: 4px solid $black; + padding-left: 0; + padding-right: 1em; + } + + &[style*="text-align:center"], + &[style*="text-align: center"] { + border: none; + } + + &.is-style-large, + &.is-large { + border: none; + } } From 36efc71667731632bcfea41c8fd9969fc73c05c2 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Tue, 12 Feb 2019 10:59:18 -0500 Subject: [PATCH 413/691] Project: Add dmsnell as code owner of parser packages (#13820) --- .github/CODEOWNERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d156343b97019..f4b26d27b078d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -10,8 +10,8 @@ # Editor /packages/annotations @youknowriad @gziolo @aduth @atimmer /packages/autop @youknowriad @aduth -/packages/block-serialization-spec-parser @youknowriad @gziolo @aduth -/packages/block-serialization-default-parser @youknowriad @gziolo @aduth +/packages/block-serialization-spec-parser @youknowriad @gziolo @aduth @dmsnell +/packages/block-serialization-default-parser @youknowriad @gziolo @aduth @dmsnell /packages/blocks @youknowriad @gziolo @aduth @noisysocks /packages/edit-post @youknowriad @gziolo @talldan @noisysocks /packages/editor @youknowriad @gziolo @talldan @noisysocks From 9f5f1c363b6c78e864ab38a662c23ba0da75abf1 Mon Sep 17 00:00:00 2001 From: etoledom <etoledom@icloud.com> Date: Tue, 12 Feb 2019 18:35:00 +0100 Subject: [PATCH 414/691] Mobile: Fix BottomSheet TextInput Cell alignment issue for RTL layout. (#13815) * Fix BottomSheet TextInput Cell alignment issue in RTL layout. * Fix lint issues --- .../src/components/mobile/bottom-sheet/cell.native.js | 7 +++++-- .../src/components/mobile/bottom-sheet/styles.native.scss | 4 ++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/editor/src/components/mobile/bottom-sheet/cell.native.js b/packages/editor/src/components/mobile/bottom-sheet/cell.native.js index 6e01487d388f6..1ca7127416e2c 100644 --- a/packages/editor/src/components/mobile/bottom-sheet/cell.native.js +++ b/packages/editor/src/components/mobile/bottom-sheet/cell.native.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { TouchableOpacity, Text, View, TextInput } from 'react-native'; +import { TouchableOpacity, Text, View, TextInput, I18nManager } from 'react-native'; /** * WordPress dependencies @@ -44,11 +44,14 @@ export default function Cell( props ) { }; const getValueComponent = () => { + const styleRTL = I18nManager.isRTL && styles.cellValueRTL; + const style = { ...styles.cellValue, ...valueStyle, ...styleRTL }; + return isValueEditable ? ( <TextInput ref={ ( c ) => valueTextInput = c } numberOfLines={ 1 } - style={ { ...styles.cellValue, ...valueStyle } } + style={ style } value={ value } placeholder={ valuePlaceholder } placeholderTextColor={ '#87a6bc' } diff --git a/packages/editor/src/components/mobile/bottom-sheet/styles.native.scss b/packages/editor/src/components/mobile/bottom-sheet/styles.native.scss index 3081519dbb08d..6255ddfce859c 100644 --- a/packages/editor/src/components/mobile/bottom-sheet/styles.native.scss +++ b/packages/editor/src/components/mobile/bottom-sheet/styles.native.scss @@ -98,3 +98,7 @@ text-align: right; flex: 1; } + +.cellValueRTL { + text-align: left; +} From 8f9738d5a83c5db821f856de9f320a6b039d676e Mon Sep 17 00:00:00 2001 From: Mikael Korpela <mikael@ihminen.org> Date: Wed, 13 Feb 2019 07:21:50 +0100 Subject: [PATCH 415/691] Gallery: translate string, not results of sprintf (#13830) --- packages/block-library/src/gallery/edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/gallery/edit.js b/packages/block-library/src/gallery/edit.js index f83704b9c8b85..203403023abd2 100644 --- a/packages/block-library/src/gallery/edit.js +++ b/packages/block-library/src/gallery/edit.js @@ -279,7 +279,7 @@ class GalleryEdit extends Component { { dropZone } { images.map( ( img, index ) => { /* translators: %1$d is the order number of the image, %2$d is the total number of images. */ - const ariaLabel = __( sprintf( 'image %1$d of %2$d in gallery', ( index + 1 ), images.length ) ); + const ariaLabel = sprintf( __( 'image %1$d of %2$d in gallery' ), ( index + 1 ), images.length ); return ( <li className="blocks-gallery-item" key={ img.id || img.url }> From ca2d424a1b596661e528cd5876c82cb4377051b6 Mon Sep 17 00:00:00 2001 From: Mikael Korpela <mikael@ihminen.org> Date: Wed, 13 Feb 2019 07:25:23 +0100 Subject: [PATCH 416/691] Docs: mention browserslist usage via browserslistrc file (#13810) See docs https://github.com/browserslist/browserslist#readme --- packages/browserslist-config/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/browserslist-config/README.md b/packages/browserslist-config/README.md index 4718d1cc398e4..995cd1a9d69a6 100644 --- a/packages/browserslist-config/README.md +++ b/packages/browserslist-config/README.md @@ -20,6 +20,12 @@ Add this to your `package.json` file: ] ``` +Alternatively, add this to `.browserslistrc` file: + +``` +extends @wordpress/browserslist-config +``` + This package when imported returns an array of supported browsers, for more configuration examples including Autoprefixer, Babel, ESLint, PostCSS, and stylelint see the [Browserslist examples](https://github.com/ai/browserslist-example#browserslist-example) repo. <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> From 9802a0c06ebf73804dda88b64deaeb731b9fb06a Mon Sep 17 00:00:00 2001 From: Joen Asmussen <joen@automattic.com> Date: Wed, 13 Feb 2019 07:35:05 +0100 Subject: [PATCH 417/691] Fix icon size regression in Switcher (#13767) * Fix icon size regression in Switcher In #12901, a small margin was introduced to the IconButton component when text was present. This caused an issue with block icons, scaling them down when they shouldn't be. A 20x20px icon should show as 20x20, and a 24x24px icon should show as 24x24px. The additional margin was applied even when the IconButton didn't actually have text. It simply needed `children`, and in the case of the Switcher, it has a dropdown arrow. Combined with the fixed width of the switcher, that meant a 24x24 icon would be rendered as 20x24. This PR adds a length check so if your IconButton includes more than 2 letters, it's considered to have a text label. This fixes the issue. But as you can tell, maybe it's not the best way to check whether there's text or not. Suggestions most welcome. * Address feedback. Props @aduth for essentially writing this PR. --- .../editor/src/components/block-switcher/index.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/editor/src/components/block-switcher/index.js b/packages/editor/src/components/block-switcher/index.js index 3bd61ff38e210..4a7413fb36422 100644 --- a/packages/editor/src/components/block-switcher/index.js +++ b/packages/editor/src/components/block-switcher/index.js @@ -116,10 +116,13 @@ export class BlockSwitcher extends Component { label={ label } tooltip={ label } onKeyDown={ openOnArrowDown } - > - <BlockIcon icon={ icon } showColors /> - <SVG className="editor-block-switcher__transform" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><Path d="M6.5 8.9c.6-.6 1.4-.9 2.2-.9h6.9l-1.3 1.3 1.4 1.4L19.4 7l-3.7-3.7-1.4 1.4L15.6 6H8.7c-1.4 0-2.6.5-3.6 1.5l-2.8 2.8 1.4 1.4 2.8-2.8zm13.8 2.4l-2.8 2.8c-.6.6-1.3.9-2.1.9h-7l1.3-1.3-1.4-1.4L4.6 16l3.7 3.7 1.4-1.4L8.4 17h6.9c1.3 0 2.6-.5 3.5-1.5l2.8-2.8-1.3-1.4z" /></SVG> - </IconButton> + icon={ ( + <Fragment> + <BlockIcon icon={ icon } showColors /> + <SVG className="editor-block-switcher__transform" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><Path d="M6.5 8.9c.6-.6 1.4-.9 2.2-.9h6.9l-1.3 1.3 1.4 1.4L19.4 7l-3.7-3.7-1.4 1.4L15.6 6H8.7c-1.4 0-2.6.5-3.6 1.5l-2.8 2.8 1.4 1.4 2.8-2.8zm13.8 2.4l-2.8 2.8c-.6.6-1.3.9-2.1.9h-7l1.3-1.3-1.4-1.4L4.6 16l3.7 3.7 1.4-1.4L8.4 17h6.9c1.3 0 2.6-.5 3.5-1.5l2.8-2.8-1.3-1.4z" /></SVG> + </Fragment> + ) } + /> </Toolbar> ); } } From 458b94572573b6fc88204d7df2133bbe584f3e64 Mon Sep 17 00:00:00 2001 From: Tim Wright <timwright12@gmail.com> Date: Wed, 13 Feb 2019 01:39:40 -0500 Subject: [PATCH 418/691] Fix/issue 12033 url label (#12959) * merge from upstream * local save * darked focus state outline for block lists * sasve * updated permalink labelling * reverted package lock * updated label and added helper link * post link text update * Update index.js --- .../src/components/sidebar/post-link/index.js | 66 +++++++++++-------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/packages/edit-post/src/components/sidebar/post-link/index.js b/packages/edit-post/src/components/sidebar/post-link/index.js index 760f71109911c..c6305b6311066 100644 --- a/packages/edit-post/src/components/sidebar/post-link/index.js +++ b/packages/edit-post/src/components/sidebar/post-link/index.js @@ -53,38 +53,46 @@ function PostLink( { onToggle={ onTogglePanel } > { isEditable && ( - <TextControl - label={ __( 'URL' ) } - value={ forceEmptyField ? '' : currentSlug } - onChange={ ( newValue ) => { - editPermalink( newValue ); - // When we delete the field the permalink gets - // reverted to the original value. - // The forceEmptyField logic allows the user to have - // the field temporarily empty while typing. - if ( ! newValue ) { - if ( ! forceEmptyField ) { + <div className="editor-post-link"> + <TextControl + label={ __( 'URL Slug' ) } + value={ forceEmptyField ? '' : currentSlug } + onChange={ ( newValue ) => { + editPermalink( newValue ); + // When we delete the field the permalink gets + // reverted to the original value. + // The forceEmptyField logic allows the user to have + // the field temporarily empty while typing. + if ( ! newValue ) { + if ( ! forceEmptyField ) { + setState( { + forceEmptyField: true, + } ); + } + return; + } + if ( forceEmptyField ) { + setState( { + forceEmptyField: false, + } ); + } + } } + onBlur={ ( event ) => { + editPermalink( cleanForSlug( event.target.value ) ); + if ( forceEmptyField ) { setState( { - forceEmptyField: true, + forceEmptyField: false, } ); } - return; - } - if ( forceEmptyField ) { - setState( { - forceEmptyField: false, - } ); - } - } } - onBlur={ ( event ) => { - editPermalink( cleanForSlug( event.target.value ) ); - if ( forceEmptyField ) { - setState( { - forceEmptyField: false, - } ); - } - } } - /> + } } + /> + <p> + { __( 'The last part of the URL. ' ) } + <ExternalLink href="https://codex.wordpress.org/Posts_Add_New_Screen"> + { __( 'Read about permalinks' ) } + </ExternalLink> + </p> + </div> ) } <p className="edit-post-post-link__preview-label"> { __( 'Preview' ) } From 26e32554db4bf3925b2200dda83a7dce8fd9f182 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Wed, 13 Feb 2019 09:37:03 +0100 Subject: [PATCH 419/691] Remove the limit of 3 keywords for the block registration (#13848) * Fixes #11949: Error: The block "xxx" can have a maximum of 3 keywords. * Fixes #11949: Error: The block "xxx" can have a maximum of 3 keywords. * Fixes #11949: Error: The block xxx can have a maximum of 3 keywords. --- .../developers/block-api/block-registration.md | 2 +- packages/blocks/src/api/registration.js | 6 ------ packages/blocks/src/api/test/registration.js | 7 ------- 3 files changed, 1 insertion(+), 14 deletions(-) diff --git a/docs/designers-developers/developers/block-api/block-registration.md b/docs/designers-developers/developers/block-api/block-registration.md index 4bc32fb9f4e0f..f4a214b741fbe 100644 --- a/docs/designers-developers/developers/block-api/block-registration.md +++ b/docs/designers-developers/developers/block-api/block-registration.md @@ -104,7 +104,7 @@ icon: { * **Type:** `Array` -Sometimes a block could have aliases that help users discover it while searching. For example, an `image` block could also want to be discovered by `photo`. You can do so by providing an array of terms (which can be translated). It is only allowed to add as much as three terms per block. +Sometimes a block could have aliases that help users discover it while searching. For example, an `image` block could also want to be discovered by `photo`. You can do so by providing an array of terms (which can be translated). ```js // Make it easier to discover a block with keyword aliases. diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js index 14cf00652b728..d32a49f8b7ff0 100644 --- a/packages/blocks/src/api/registration.js +++ b/packages/blocks/src/api/registration.js @@ -103,12 +103,6 @@ export function registerBlockType( name, settings ) { ); return; } - if ( 'keywords' in settings && settings.keywords.length > 3 ) { - console.error( - 'The block "' + name + '" can have a maximum of 3 keywords.' - ); - return; - } if ( ! ( 'category' in settings ) ) { console.error( 'The block "' + name + '" must have a category.' diff --git a/packages/blocks/src/api/test/registration.js b/packages/blocks/src/api/test/registration.js index e881943d5168e..f7b20bcda9db0 100644 --- a/packages/blocks/src/api/test/registration.js +++ b/packages/blocks/src/api/test/registration.js @@ -119,13 +119,6 @@ describe( 'blocks', () => { expect( block ).toBeUndefined(); } ); - it( 'should reject blocks with more than 3 keywords', () => { - const blockType = { save: noop, keywords: [ 'apple', 'orange', 'lemon', 'pineapple' ], category: 'common', title: 'block title' }, - block = registerBlockType( 'my-plugin/fancy-block-7', blockType ); - expect( console ).toHaveErroredWith( 'The block "my-plugin/fancy-block-7" can have a maximum of 3 keywords.' ); - expect( block ).toBeUndefined(); - } ); - it( 'should reject blocks without category', () => { const blockType = { settingName: 'settingValue', save: noop, title: 'block title' }, block = registerBlockType( 'my-plugin/fancy-block-8', blockType ); From 73683aa4e5fd1abd8a12376b93374b99cc02f694 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Wed, 13 Feb 2019 10:20:35 +0000 Subject: [PATCH 420/691] Do not render font size picker when no font sizes are available and custom font sizes are disabled. (#13844) --- packages/components/src/font-size-picker/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/components/src/font-size-picker/index.js b/packages/components/src/font-size-picker/index.js index 2e7877cf20613..79f18faad4947 100644 --- a/packages/components/src/font-size-picker/index.js +++ b/packages/components/src/font-size-picker/index.js @@ -26,6 +26,9 @@ function FontSizePicker( { value, withSlider = false, } ) { + if ( disableCustomFontSizes && ! fontSizes.length ) { + return null; + } const onChangeValue = ( event ) => { const newValue = event.target.value; if ( newValue === '' ) { From 40872d38af36b09f64245d986e039ec7a692eee1 Mon Sep 17 00:00:00 2001 From: Emmanuel Hesry <ehesry@gmail.com> Date: Wed, 13 Feb 2019 12:32:59 +0100 Subject: [PATCH 421/691] Change deasync to its latest version (#13839) --- package-lock.json | 60 +++++++++++++++++------------------------------ package.json | 8 +++---- 2 files changed, 26 insertions(+), 42 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5e24aea3137af..1dc3524f02f50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2974,7 +2974,6 @@ "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", "dev": true, - "optional": true, "requires": { "kind-of": "^3.0.2", "longest": "^1.0.1", @@ -2986,7 +2985,6 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, - "optional": true, "requires": { "is-buffer": "^1.1.5" } @@ -6103,13 +6101,13 @@ "dev": true }, "deasync": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/deasync/-/deasync-0.1.13.tgz", - "integrity": "sha512-/6ngYM7AapueqLtvOzjv9+11N2fHDSrkxeMF1YPE20WIfaaawiBg+HZH1E5lHrcJxlKR42t6XPOEmMmqcAsU1g==", + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/deasync/-/deasync-0.1.14.tgz", + "integrity": "sha512-wN8sIuEqIwyQh72AG7oY6YQODCxIp1eXzEZlZznBuwDF8Q03Tdy9QNp1BNZXeadXoklNrw+Ip1fch+KXo/+ASw==", "dev": true, "requires": { "bindings": "~1.2.1", - "nan": "^2.0.7" + "node-addon-api": "^1.6.0" } }, "debug": { @@ -8104,8 +8102,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -8126,14 +8123,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -8148,20 +8143,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -8278,8 +8270,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -8291,7 +8282,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -8306,7 +8296,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -8314,14 +8303,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -8340,7 +8327,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -8421,8 +8407,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -8434,7 +8419,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -8520,8 +8504,7 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -8557,7 +8540,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -8577,7 +8559,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -8621,14 +8602,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -12726,8 +12705,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", - "dev": true, - "optional": true + "dev": true }, "longest-streak": { "version": "2.0.2", @@ -13499,6 +13477,12 @@ "integrity": "sha512-2NpiFHqC87y/zFke0fC0spBXL3bBsoh/p5H1EFhshxjCR5+0g2d6BiXbUFz9v1sAcxsk2htp2eQnNIci2dIYcA==", "dev": true }, + "node-addon-api": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.6.2.tgz", + "integrity": "sha512-479Bjw9nTE5DdBSZZWprFryHGjUaQC31y1wHo19We/k0BZlrmhqQitWoUL0cD8+scljCbIUL+E58oRDEakdGGA==", + "dev": true + }, "node-fetch": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", diff --git a/package.json b/package.json index 1b53a0ce186dd..40559e9c8d477 100644 --- a/package.json +++ b/package.json @@ -80,17 +80,17 @@ "core-js": "2.5.7", "cross-env": "3.2.4", "cssnano": "4.0.3", - "enzyme": "3.7.0", - "deasync": "0.1.13", + "deasync": "0.1.14", "deep-freeze": "0.0.1", "doctrine": "2.1.0", + "enzyme": "3.7.0", "eslint-plugin-jest": "21.5.0", "espree": "3.5.4", "fbjs": "0.8.17", "glob": "7.1.2", "husky": "0.14.3", - "is-plain-obj": "1.1.0", "is-equal-shallow": "0.1.3", + "is-plain-obj": "1.1.0", "jsdom": "11.12.0", "lerna": "3.4.3", "lint-staged": "7.3.0", @@ -109,8 +109,8 @@ "shallow-equal": "1.0.0", "shallow-equals": "1.0.0", "shallowequal": "1.1.0", - "sprintf-js": "1.1.1", "source-map-loader": "0.2.3", + "sprintf-js": "1.1.1", "stylelint-config-wordpress": "13.1.0", "uuid": "3.3.2", "webpack-bundle-analyzer": "3.0.2", From bc53911632ba1f542bc3933ca13fc59e008cc04f Mon Sep 17 00:00:00 2001 From: Laurel <laurelfulford@gmail.com> Date: Wed, 13 Feb 2019 05:14:47 -0800 Subject: [PATCH 422/691] Remove left padding from quote block when its centred. (#13846) --- packages/block-library/src/quote/theme.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/block-library/src/quote/theme.scss b/packages/block-library/src/quote/theme.scss index 3657883ec833f..8e6edd7d7ff86 100644 --- a/packages/block-library/src/quote/theme.scss +++ b/packages/block-library/src/quote/theme.scss @@ -24,6 +24,7 @@ &[style*="text-align:center"], &[style*="text-align: center"] { border: none; + padding-left: 0; } &.is-style-large, From a99c0b5487ff3639c05a7ce1dbf7823b821382f8 Mon Sep 17 00:00:00 2001 From: Joen Asmussen <joen@automattic.com> Date: Wed, 13 Feb 2019 14:30:35 +0100 Subject: [PATCH 423/691] Add background color to menu items being hovered (#13732) * Add background color to menu items being hovered This PR adds some clarity to which menu item you're highlighting. It: - Adds a background color to a menu item hovered - Increases the contrast (opacity) of the keyboard shortcut indicator when hovering - It widens the hit are of the button to go edge to edge It's a relatively small PR, but it's a really nice improvement to have. * Darken other hover colors to match. * Address feedback. * Apply to font size picker too. --- assets/stylesheets/_mixins.scss | 3 ++- packages/components/src/autocomplete/style.scss | 8 +++++--- packages/components/src/font-size-picker/style.scss | 8 ++++++++ packages/components/src/menu-group/style.scss | 3 ++- packages/components/src/menu-item/style.scss | 12 ++++++++---- packages/components/src/panel/style.scss | 2 +- .../src/components/block-settings-menu/style.scss | 7 +++---- 7 files changed, 29 insertions(+), 14 deletions(-) diff --git a/assets/stylesheets/_mixins.scss b/assets/stylesheets/_mixins.scss index 54f812844eb04..fa79974e2a112 100644 --- a/assets/stylesheets/_mixins.scss +++ b/assets/stylesheets/_mixins.scss @@ -209,6 +209,7 @@ color: $dark-gray-900; border: none; box-shadow: none; + background: $light-gray-200; } @mixin menu-style__focus() { @@ -226,7 +227,7 @@ } @mixin block-style__hover { - background: $light-gray-100; + background: $light-gray-200; color: $dark-gray-900; } diff --git a/packages/components/src/autocomplete/style.scss b/packages/components/src/autocomplete/style.scss index 2928c7626881c..49a602b678940 100644 --- a/packages/components/src/autocomplete/style.scss +++ b/packages/components/src/autocomplete/style.scss @@ -23,14 +23,16 @@ flex-grow: 1; flex-shrink: 0; align-items: center; - padding: 6px; + padding: 6px 8px; + margin-left: -3px; + margin-right: -3px; text-align: left; &.is-selected { - @include button-style__focus-active; + @include menu-style__focus; } &:hover { - @include button-style__hover; + @include menu-style__hover; } } diff --git a/packages/components/src/font-size-picker/style.scss b/packages/components/src/font-size-picker/style.scss index e8bb60c971b0b..6435f4dfd6dee 100644 --- a/packages/components/src/font-size-picker/style.scss +++ b/packages/components/src/font-size-picker/style.scss @@ -38,6 +38,14 @@ top: calc(50% - 10px); left: 10px; } + + &:hover { + @include menu-style__hover; + } + + &:focus { + @include menu-style__focus; + } } .components-font-size-picker__buttons .components-font-size-picker__selector { diff --git a/packages/components/src/menu-group/style.scss b/packages/components/src/menu-group/style.scss index 92dd7747ff483..6682581a5bfab 100644 --- a/packages/components/src/menu-group/style.scss +++ b/packages/components/src/menu-group/style.scss @@ -1,9 +1,10 @@ .components-menu-group { width: 100%; - padding: $grid-size - $border-width; + padding: ($grid-size - $border-width) 0; } .components-menu-group__label { margin-bottom: $grid-size; color: $dark-gray-300; + padding: 0 ($grid-size - $border-width); } diff --git a/packages/components/src/menu-item/style.scss b/packages/components/src/menu-item/style.scss index 85a0a1b580356..8f750618c2a15 100644 --- a/packages/components/src/menu-item/style.scss +++ b/packages/components/src/menu-item/style.scss @@ -1,7 +1,7 @@ .components-menu-item__button, .components-menu-item__button.components-icon-button { width: 100%; - padding: 8px; + padding: $grid-size ($grid-size-large - $border-width); text-align: left; color: $dark-gray-600; @@ -24,6 +24,10 @@ @include break-medium() { @include menu-style__hover; } + + .components-menu-item__shortcut { + opacity: 1; + } } &:focus:not(:disabled):not([aria-disabled="true"]) { @@ -39,15 +43,15 @@ .components-menu-item__info { margin-top: $grid-size-small; font-size: $default-font-size - 1px; - opacity: 0.82; + opacity: 0.84; } .components-menu-item__shortcut { align-self: center; - opacity: 0.5; + opacity: 0.84; margin-right: 0; margin-left: auto; - padding-left: 8px; + padding-left: $grid-size; // Hide the keyboard shortcuts on mobile, because they aren't super-useful // for most mobile users and it frees up screen space for any item diff --git a/packages/components/src/panel/style.scss b/packages/components/src/panel/style.scss index e4f2d58af2645..5b10bbdf8e358 100644 --- a/packages/components/src/panel/style.scss +++ b/packages/components/src/panel/style.scss @@ -73,7 +73,7 @@ // Hover States .components-panel__body > .components-panel__body-title:hover, .edit-post-last-revision__panel > .components-icon-button:not(:disabled):not([aria-disabled="true"]):not(.is-default):hover { - background: $light-gray-100; + background: $light-gray-200; } .components-panel__body-toggle.components-button { diff --git a/packages/editor/src/components/block-settings-menu/style.scss b/packages/editor/src/components/block-settings-menu/style.scss index 6cdfd96b023cd..418343d72a446 100644 --- a/packages/editor/src/components/block-settings-menu/style.scss +++ b/packages/editor/src/components/block-settings-menu/style.scss @@ -10,14 +10,14 @@ } .editor-block-settings-menu__content { - padding: $grid-size - $border-width; + padding: ($grid-size - $border-width) 0; } .editor-block-settings-menu__separator { margin-top: $grid-size; margin-bottom: $grid-size; - margin-left: -$grid-size + $border-width; - margin-right: -$grid-size + $border-width; + margin-left: 0; + margin-right: 0; border-top: $border-width solid $light-gray-500; // Check if the separator is the last child in the node and if so, hide itself @@ -36,7 +36,6 @@ .editor-block-settings-menu__control { width: 100%; justify-content: flex-start; - padding: 8px; background: none; outline: none; border-radius: 0; From 06abb235f193165a584550d7a70b30ac3a297283 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Wed, 13 Feb 2019 10:54:33 -0800 Subject: [PATCH 424/691] Add mkaz to scripts (#13865) --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f4b26d27b078d..3471d99f3f45d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -34,7 +34,7 @@ /packages/library-export-default-webpack-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra /packages/npm-package-json-lint-config @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra /packages/postcss-themes @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra -/packages/scripts @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw +/packages/scripts @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw @mkaz # UI Components /packages/components @youknowriad @gziolo @aduth @chrisvanpatten @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks From 3f1324b53cc8bb45d08d12d5321d6f88510bed09 Mon Sep 17 00:00:00 2001 From: Thorsten Frommen <info@tfrommen.de> Date: Wed, 13 Feb 2019 20:03:05 +0100 Subject: [PATCH 425/691] FlatTermSelector: Set Correct Loading State (#13758) --- .../src/components/post-taxonomies/flat-term-selector.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/editor/src/components/post-taxonomies/flat-term-selector.js b/packages/editor/src/components/post-taxonomies/flat-term-selector.js index 411890ae5d615..233f07cc3e2aa 100644 --- a/packages/editor/src/components/post-taxonomies/flat-term-selector.js +++ b/packages/editor/src/components/post-taxonomies/flat-term-selector.js @@ -70,7 +70,7 @@ class FlatTermSelector extends Component { this.searchTerms = throttle( this.searchTerms.bind( this ), 500 ); this.findOrCreateTerm = this.findOrCreateTerm.bind( this ); this.state = { - loading: false, + loading: ! isEmpty( this.props.terms ), availableTerms: [], selectedTerms: [], }; @@ -78,7 +78,6 @@ class FlatTermSelector extends Component { componentDidMount() { if ( ! isEmpty( this.props.terms ) ) { - this.setState( { loading: false } ); this.initRequest = this.fetchTerms( { include: this.props.terms.join( ',' ), per_page: -1, From 7b776b694463f6643440aa2c971f2474793efbe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Wed, 13 Feb 2019 23:19:12 +0100 Subject: [PATCH 426/691] Project: Add styles and project management groups to CODEOWNERS file (#13854) * Add unowned styles group to CODEOWNERS file * Add project management section * Extended the list of project management reviewers --- .github/CODEOWNERS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3471d99f3f45d..ddc0dfec2b7df 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -76,6 +76,9 @@ # Documentation /docs @youknowriad @gziolo @chrisvanpatten @mkaz @ajitbohra @nosolosw @notnownikki +# Styles (Unowned) +*.scss @ghost + # Native (Unowned) *.native.js @ghost *.android.js @ghost @@ -83,3 +86,6 @@ *.native.scss @ghost *.android.scss @ghost *.ios.scss @ghost + +# Project Management +/.github @youknowriad @mapk @karmatosed From 0ede174e6ff482085ee51b6a99bea0801c11d609 Mon Sep 17 00:00:00 2001 From: Ashwin P Chandran <ashwinpc1993@gmail.com> Date: Thu, 14 Feb 2019 00:18:51 -0500 Subject: [PATCH 427/691] Fixed Component URLInput not honouring className (#13800) * Fixed Component URLInput not honouring className Related: #13754 This pull request fixes the URIInput Component not honouring. This add the className to the parent div for the component * Updated URLInput Readme for className prop --- packages/editor/src/components/url-input/README.md | 4 ++++ packages/editor/src/components/url-input/index.js | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/editor/src/components/url-input/README.md b/packages/editor/src/components/url-input/README.md index 19b67bf452b5e..89001d45b3c4e 100644 --- a/packages/editor/src/components/url-input/README.md +++ b/packages/editor/src/components/url-input/README.md @@ -131,6 +131,10 @@ Renders the URL input field used by the `URLInputButton` component. It can be us If you are not conditionally rendering this component set this property to `false`. +### `className: String` + +*Optional.* Adds and optional class to the parent `div` that wraps the URLInput field and popover + ## Example {% codetabs %} diff --git a/packages/editor/src/components/url-input/index.js b/packages/editor/src/components/url-input/index.js index 0d763957ce356..db658990632e7 100644 --- a/packages/editor/src/components/url-input/index.js +++ b/packages/editor/src/components/url-input/index.js @@ -227,11 +227,11 @@ class URLInput extends Component { } render() { - const { value = '', autoFocus = true, instanceId } = this.props; + const { value = '', autoFocus = true, instanceId, className } = this.props; const { showSuggestions, posts, selectedSuggestion, loading } = this.state; /* eslint-disable jsx-a11y/no-autofocus */ return ( - <div className="editor-url-input"> + <div className={ classnames( 'editor-url-input', className ) }> <input autoFocus={ autoFocus } type="text" From 6991141e4ea6012d2625eeca7915be9f8993c10c Mon Sep 17 00:00:00 2001 From: Joen Asmussen <joen@automattic.com> Date: Thu, 14 Feb 2019 07:22:33 +0100 Subject: [PATCH 428/691] Make notices push down content (#13614) * Make notices push down content This PR restores the good stuff from #12301. That is: it allows notices to push down content. Dismissible notices are sticky at the top, non-dismisible notices scroll out of view. This is mostly an exact copy of the other PR, but fresh. The behavior has a number of benefits: - If you have multiple non-dismissible notices, you can still actually use the editor. - Notices no longer cover the scrollbar. - Notices no longer cover the permalink interface. - Notices now only cover content if you do not dismiss the notices. * Address top toolbar issues. * Remove the overly specific is-pinned. --- assets/stylesheets/_z-index.scss | 6 +- packages/components/src/notice/list.js | 5 +- packages/components/src/notice/test/list.js | 26 +++++++ .../edit-post/src/components/layout/index.js | 3 +- .../src/components/layout/style.scss | 74 ++++++++++--------- .../src/components/block-list/style.scss | 1 - .../src/components/editor-notices/index.js | 17 ++++- .../components/editor-notices/test/index.js | 35 +++++++++ 8 files changed, 123 insertions(+), 44 deletions(-) create mode 100644 packages/components/src/notice/test/list.js create mode 100644 packages/editor/src/components/editor-notices/test/index.js diff --git a/assets/stylesheets/_z-index.scss b/assets/stylesheets/_z-index.scss index b8f00482e84af..462824c88c578 100644 --- a/assets/stylesheets/_z-index.scss +++ b/assets/stylesheets/_z-index.scss @@ -67,9 +67,9 @@ $z-layers: ( // but bellow #adminmenuback { z-index: 100 } ".edit-post-sidebar {greater than small}": 90, - // Show notices below expanded wp-admin submenus: - // #adminmenuwrap { z-index: 9990 } - ".components-notice-list": 9989, + // Show notices below expanded editor bar + // .edit-post-header { z-index: 30 } + ".components-notice-list": 29, // Show modal under the wp-admin menus and the popover ".components-modal__screen-overlay": 100000, diff --git a/packages/components/src/notice/list.js b/packages/components/src/notice/list.js index 4ff12e2a45118..dcb39dc49b582 100644 --- a/packages/components/src/notice/list.js +++ b/packages/components/src/notice/list.js @@ -1,6 +1,7 @@ /** * External dependencies */ +import classnames from 'classnames'; import { noop, omit } from 'lodash'; /** @@ -18,9 +19,11 @@ import Notice from './'; * @param {Object} $0.children Array of children to be rendered inside the notice list. * @return {Object} The rendered notices list. */ -function NoticeList( { notices, onRemove = noop, className = 'components-notice-list', children } ) { +function NoticeList( { notices, onRemove = noop, className, children } ) { const removeNotice = ( id ) => () => onRemove( id ); + className = classnames( 'components-notice-list', className ); + return ( <div className={ className }> { children } diff --git a/packages/components/src/notice/test/list.js b/packages/components/src/notice/test/list.js new file mode 100644 index 0000000000000..034ff9b7dfd82 --- /dev/null +++ b/packages/components/src/notice/test/list.js @@ -0,0 +1,26 @@ +/** + * External dependencies + */ +import ShallowRenderer from 'react-test-renderer/shallow'; + +/** + * WordPress dependencies + */ +import TokenList from '@wordpress/token-list'; + +/** + * Internal dependencies + */ +import NoticeList from '../list'; + +describe( 'NoticeList', () => { + it( 'should merge className', () => { + const renderer = new ShallowRenderer(); + + renderer.render( <NoticeList notices={ [] } className="is-ok" /> ); + + const classes = new TokenList( renderer.getRenderOutput().props.className ); + expect( classes.contains( 'is-ok' ) ).toBe( true ); + expect( classes.contains( 'components-notice-list' ) ).toBe( true ); + } ); +} ); diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index 1c67baed29d22..f0876f4672287 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -78,7 +78,8 @@ function Layout( { aria-label={ __( 'Editor content' ) } tabIndex="-1" > - <EditorNotices /> + <EditorNotices dismissible={ false } className="is-pinned" /> + <EditorNotices dismissible={ true } /> <PreserveScrollInReorder /> <EditorModeKeyboardShortcuts /> <KeyboardShortcutHelpModal /> diff --git a/packages/edit-post/src/components/layout/style.scss b/packages/edit-post/src/components/layout/style.scss index f848a8cfd7ccd..dfbfaf77dd356 100644 --- a/packages/edit-post/src/components/layout/style.scss +++ b/packages/edit-post/src/components/layout/style.scss @@ -13,49 +13,36 @@ color: $dark-gray-900; @include break-small { - position: fixed; - top: inherit; + top: 0; + } + + // Non-dismissible notices. + &.is-pinned { + position: relative; + left: 0; + top: 0; } + } - .components-notice { - margin: 0 0 5px; - padding: 6px 12px; - min-height: $panel-header-height; + .components-notice { + margin: 0 0 5px; + padding: 6px 12px; + min-height: $panel-header-height; - .components-notice__dismiss { - margin: 10px 5px; - } + .components-notice__dismiss { + margin: 10px 5px; } } - // On mobile, toolbars behave differently. + // Beyond the mobile breakpoint, the editor bar is fixed, so make room for it eabove the layout content. @include break-small { padding-top: $header-height; } - &.has-fixed-toolbar { - .edit-post-layout__content { - padding-top: $block-controls-height; - } - - // On mobile, toolbars behave differently. - @include break-small { - padding-top: $header-height + $block-toolbar-height; - - .edit-post-layout__content { - padding-top: 0; - } - } - - @include break-large { - padding-top: $header-height; - } - } + // Beyond the medium breakpoint, the main scrolling area itself becomes fixed so the padding then becomes + // unnecessary, but until then it's still needed. } -@include editor-left(".components-notice-list"); -@include editor-right(".components-notice-list"); - .edit-post-layout__metaboxes:not(:empty) { border-top: $border-width solid $light-gray-500; margin-top: 10px; @@ -121,16 +108,33 @@ } } + // For users with the Top Toolbar option enabled, special rules apply to the height of the content area. + .has-fixed-toolbar & { + // From the medium breakpoint it sits below the editor bar. + @include break-medium() { + top: $header-height + $admin-bar-height + $block-controls-height; + } + + // From the xlarge breakpoint it sits in the editor bar. + @include break-xlarge() { + top: $header-height + $admin-bar-height; + } + } + // Pad the scroll box so content on the bottom can be scrolled up. padding-bottom: 50vh; @include break-small { padding-bottom: 0; } - // On mobile the main content area has to scroll otherwise you can invoke - // the overscroll bounce on the non-scrolling container, causing - // (ノಠ益ಠ)ノ彡┻━┻ - overflow-y: auto; + // On mobile the main content (html or body) area has to scroll. + // If, like we do on the desktop, scroll an element (.edit-post-layout__content) you can invoke + // the overscroll bounce on the non-scrolling container, causing for a frustrating scrolling experience. + // The following rule enables this scrolling beyond the mobile breakpoint, because on the desktop + // breakpoints scrolling an isolated element helps avoid scroll bleed. + @include break-small() { + overflow-y: auto; + } -webkit-overflow-scrolling: touch; // This rule ensures that if you've scrolled to the end of a container, diff --git a/packages/editor/src/components/block-list/style.scss b/packages/editor/src/components/block-list/style.scss index a024bb2198d06..043ddd2613607 100644 --- a/packages/editor/src/components/block-list/style.scss +++ b/packages/editor/src/components/block-list/style.scss @@ -784,7 +784,6 @@ .editor-block-list__block { .editor-block-contextual-toolbar { - position: sticky; z-index: z-index(".editor-block-contextual-toolbar"); white-space: nowrap; text-align: left; diff --git a/packages/editor/src/components/editor-notices/index.js b/packages/editor/src/components/editor-notices/index.js index f11c76181cbb5..f899f4a61e0ff 100644 --- a/packages/editor/src/components/editor-notices/index.js +++ b/packages/editor/src/components/editor-notices/index.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import { filter } from 'lodash'; + /** * WordPress dependencies */ @@ -10,10 +15,16 @@ import { compose } from '@wordpress/compose'; */ import TemplateValidationNotice from '../template-validation-notice'; -function EditorNotices( props ) { +export function EditorNotices( { dismissible, notices, ...props } ) { + if ( dismissible !== undefined ) { + notices = filter( notices, { isDismissible: dismissible } ); + } + return ( - <NoticeList { ...props }> - <TemplateValidationNotice /> + <NoticeList notices={ notices } { ...props }> + { dismissible !== false && ( + <TemplateValidationNotice /> + ) } </NoticeList> ); } diff --git a/packages/editor/src/components/editor-notices/test/index.js b/packages/editor/src/components/editor-notices/test/index.js new file mode 100644 index 0000000000000..655a990aa174f --- /dev/null +++ b/packages/editor/src/components/editor-notices/test/index.js @@ -0,0 +1,35 @@ +/** + * External dependencies + */ +import { shallow } from 'enzyme'; + +/** + * Internal dependencies + */ +import { EditorNotices } from '../'; + +describe( 'EditorNotices', () => { + const notices = [ + { content: 'Eat your vegetables!', isDismissible: true }, + { content: 'Brush your teeth!', isDismissible: true }, + { content: 'Existence is fleeting!', isDismissible: false }, + ]; + + it( 'renders all notices', () => { + const wrapper = shallow( <EditorNotices notices={ notices } /> ); + expect( wrapper.prop( 'notices' ) ).toHaveLength( 3 ); + expect( wrapper.children() ).toHaveLength( 1 ); + } ); + + it( 'renders only dismissible notices', () => { + const wrapper = shallow( <EditorNotices notices={ notices } dismissible={ true } /> ); + expect( wrapper.prop( 'notices' ) ).toHaveLength( 2 ); + expect( wrapper.children() ).toHaveLength( 1 ); + } ); + + it( 'renders only non-dismissible notices', () => { + const wrapper = shallow( <EditorNotices notices={ notices } dismissible={ false } /> ); + expect( wrapper.prop( 'notices' ) ).toHaveLength( 1 ); + expect( wrapper.children() ).toHaveLength( 0 ); + } ); +} ); From af0054159daa169964da9b09509cbce9288e40de Mon Sep 17 00:00:00 2001 From: Joen Asmussen <joen@automattic.com> Date: Thu, 14 Feb 2019 07:59:14 +0100 Subject: [PATCH 429/691] Try to add a min-width to embeds. (#13590) * Try to add a min-width to embeds. Embeds are increasingly responsive (as they should be). This branch is very much a "try" branch, in that it simply zeroes out the minimum width of embeds to make the Amazon book embed be responsive. This allows us to create a nice book recommendation grid like this. However, Amazon themselves include a "Share" modal inside, which is 320px wide and is `opacity: 0;` (until invoked) instead of `display: none;`. So even though the embed itself is smart enough to not even show the Share button when there isn't room, that zero opacity share sheet still causes a horizontal scrollbar, which due to the sandboxing of iframes, we cannot fix. So this PR is more of a discussion point: what can we do with these embeds? I'm not sure we can do that much. Notably Instagram widgets can't be smaller than 326px, so we can't do this in a blanket way either, or it'll just mess up other embeds. What are your thoughts? * Make min-width not affect placeholders. * Change to suggested fix. --- packages/block-library/src/embed/editor.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/block-library/src/embed/editor.scss b/packages/block-library/src/embed/editor.scss index af0b050764ce7..d26b048b862eb 100644 --- a/packages/block-library/src/embed/editor.scss +++ b/packages/block-library/src/embed/editor.scss @@ -9,6 +9,11 @@ // Instagram widgets have a min-width of 326px, so go a bit beyond that. @include break-small() { min-width: 360px; + + // The placeholder should not have this min-width. + &.components-placeholder { + min-width: 0; + } } &.is-loading { From 51ccfe627c957e10b546d13fa04fa4b68a6d0456 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Thu, 14 Feb 2019 03:21:13 -0500 Subject: [PATCH 430/691] Editor: Avoid scheduling autosave if same edit reference as last completion (#12624) --- .../src/components/autosave-monitor/index.js | 25 +++++++++++++++++-- .../components/autosave-monitor/test/index.js | 22 ++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/packages/editor/src/components/autosave-monitor/index.js b/packages/editor/src/components/autosave-monitor/index.js index f3a7d81bd6481..c1be1b20e0a40 100644 --- a/packages/editor/src/components/autosave-monitor/index.js +++ b/packages/editor/src/components/autosave-monitor/index.js @@ -7,14 +7,33 @@ import { withSelect, withDispatch } from '@wordpress/data'; export class AutosaveMonitor extends Component { componentDidUpdate( prevProps ) { - const { isDirty, editsReference, isAutosaveable } = this.props; + const { isDirty, editsReference, isAutosaveable, isAutosaving } = this.props; + + // The edits reference is held for comparison to avoid scheduling an + // autosave if an edit has not been made since the last autosave + // completion. This is assigned when the autosave completes, and reset + // when an edit occurs. + // + // See: https://github.com/WordPress/gutenberg/issues/12318 + + if ( editsReference !== prevProps.editsReference ) { + this.didAutosaveForEditsReference = false; + } + + if ( ! isAutosaving && prevProps.isAutosaving ) { + this.didAutosaveForEditsReference = true; + } if ( prevProps.isDirty !== isDirty || prevProps.isAutosaveable !== isAutosaveable || prevProps.editsReference !== editsReference ) { - this.toggleTimer( isDirty && isAutosaveable ); + this.toggleTimer( + isDirty && + isAutosaveable && + ! this.didAutosaveForEditsReference + ); } } @@ -45,6 +64,7 @@ export default compose( [ isEditedPostAutosaveable, getEditorSettings, getReferenceByDistinctEdits, + isAutosavingPost, } = select( 'core/editor' ); const { autosaveInterval } = getEditorSettings(); @@ -53,6 +73,7 @@ export default compose( [ isDirty: isEditedPostDirty(), isAutosaveable: isEditedPostAutosaveable(), editsReference: getReferenceByDistinctEdits(), + isAutosaving: isAutosavingPost(), autosaveInterval, }; } ), diff --git a/packages/editor/src/components/autosave-monitor/test/index.js b/packages/editor/src/components/autosave-monitor/test/index.js index e518586d6e719..efba08ed31d14 100644 --- a/packages/editor/src/components/autosave-monitor/test/index.js +++ b/packages/editor/src/components/autosave-monitor/test/index.js @@ -82,6 +82,28 @@ describe( 'AutosaveMonitor', () => { expect( toggleTimer ).toHaveBeenCalledWith( false ); } ); + + it( 'should avoid scheduling autosave if still dirty but already autosaved for edits', () => { + // Explanation: When a published post is autosaved, it's still in a + // dirty state since the edits are not saved to the post until the + // user clicks "Update". To avoid recurring autosaves, ensure that + // an edit has occurred since the last autosave had completed. + + const beforeReference = []; + const afterReference = []; + + // A post is non-dirty while autosave is in-flight. + wrapper.setProps( { isDirty: false, isAutosaving: true, isAutosaveable: true, editsReference: beforeReference } ); + toggleTimer.mockClear(); + wrapper.setProps( { isDirty: true, isAutosaving: false, isAutosaveable: true, editsReference: beforeReference } ); + + expect( toggleTimer ).toHaveBeenCalledWith( false ); + + // Once edit occurs after autosave, resume scheduling. + wrapper.setProps( { isDirty: true, isAutosaving: false, isAutosaveable: true, editsReference: afterReference } ); + + expect( toggleTimer.mock.calls[ 1 ][ 0 ] ).toBe( true ); + } ); } ); describe( '#componentWillUnmount()', () => { From fbc9f891e2463f4e3a31c9a8cbe609fe28203e10 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Thu, 14 Feb 2019 03:21:51 -0500 Subject: [PATCH 431/691] Plugin: Deprecate gutenberg_can_edit_* functions (#13470) --- .../backward-compatibility/deprecations.md | 2 + gutenberg.php | 2 +- lib/register.php | 60 ++------------- phpunit/class-admin-test.php | 74 ------------------- 4 files changed, 9 insertions(+), 129 deletions(-) diff --git a/docs/designers-developers/developers/backward-compatibility/deprecations.md b/docs/designers-developers/developers/backward-compatibility/deprecations.md index a5db81f7514f5..966b33d77cafe 100644 --- a/docs/designers-developers/developers/backward-compatibility/deprecations.md +++ b/docs/designers-developers/developers/backward-compatibility/deprecations.md @@ -7,6 +7,8 @@ The Gutenberg project's deprecation policy is intended to support backward compa - The PHP function `gutenberg_redirect_to_classic_editor_when_saving_posts` has been removed. - The PHP function `gutenberg_revisions_link_to_editor` has been removed. - The PHP function `gutenberg_remember_classic_editor_when_saving_posts` has been removed. +- The PHP function `gutenberg_can_edit_post_type` has been removed. Use [`use_block_editor_for_post_type`](https://developer.wordpress.org/reference/functions/use_block_editor_for_post_type/) instead. +- The PHP function `gutenberg_can_edit_post` has been removed. Use [`use_block_editor_for_post`](https://developer.wordpress.org/reference/functions/use_block_editor_for_post/) instead. ## 5.2.0 diff --git a/gutenberg.php b/gutenberg.php index 11e173a033767..990395c0b9c6b 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -118,7 +118,7 @@ function is_gutenberg_page() { return false; } - if ( ! gutenberg_can_edit_post( $post ) ) { + if ( ! use_block_editor_for_post( $post ) ) { return false; } diff --git a/lib/register.php b/lib/register.php index f8f7410d31253..16d5aa85b623e 100644 --- a/lib/register.php +++ b/lib/register.php @@ -25,44 +25,15 @@ function gutenberg_collect_meta_box_data() { * Return whether the post can be edited in Gutenberg and by the current user. * * @since 0.5.0 + * @deprecated 5.0.0 use_block_editor_for_post * * @param int|WP_Post $post Post ID or WP_Post object. * @return bool Whether the post can be edited with Gutenberg. */ function gutenberg_can_edit_post( $post ) { - $post = get_post( $post ); - $can_edit = true; + _deprecated_function( __FUNCTION__, '5.0.0', 'use_block_editor_for_post' ); - if ( ! $post ) { - $can_edit = false; - } - - if ( $can_edit && 'trash' === $post->post_status ) { - $can_edit = false; - } - - if ( $can_edit && ! gutenberg_can_edit_post_type( $post->post_type ) ) { - $can_edit = false; - } - - if ( $can_edit && ! current_user_can( 'edit_post', $post->ID ) ) { - $can_edit = false; - } - - // Disable the editor if on the blog page and there is no content. - if ( $can_edit && absint( get_option( 'page_for_posts' ) ) === $post->ID && empty( $post->post_content ) ) { - $can_edit = false; - } - - /** - * Filter to allow plugins to enable/disable Gutenberg for particular post. - * - * @since 3.5 - * - * @param bool $can_edit Whether the post can be edited or not. - * @param WP_Post $post The post being checked. - */ - return apply_filters( 'gutenberg_can_edit_post', $can_edit, $post ); + return use_block_editor_for_post( $post ); } @@ -73,34 +44,15 @@ function gutenberg_can_edit_post( $post ) { * REST API, then the post cannot be edited in Gutenberg. * * @since 1.5.2 + * @deprecated 5.0.0 use_block_editor_for_post_type * * @param string $post_type The post type. * @return bool Whether the post type can be edited with Gutenberg. */ function gutenberg_can_edit_post_type( $post_type ) { - $can_edit = true; - if ( ! post_type_exists( $post_type ) ) { - $can_edit = false; - } - - if ( ! post_type_supports( $post_type, 'editor' ) ) { - $can_edit = false; - } - - $post_type_object = get_post_type_object( $post_type ); - if ( $post_type_object && ! $post_type_object->show_in_rest ) { - $can_edit = false; - } + _deprecated_function( __FUNCTION__, '5.0.0', 'use_block_editor_for_post_type' ); - /** - * Filter to allow plugins to enable/disable Gutenberg for particular post types. - * - * @since 1.5.2 - * - * @param bool $can_edit Whether the post type can be edited or not. - * @param string $post_type The post type being checked. - */ - return apply_filters( 'gutenberg_can_edit_post_type', $can_edit, $post_type ); + return use_block_editor_for_post_type( $post_type ); } /** diff --git a/phpunit/class-admin-test.php b/phpunit/class-admin-test.php index d7342213b853e..e96f4958366ca 100644 --- a/phpunit/class-admin-test.php +++ b/phpunit/class-admin-test.php @@ -10,13 +10,6 @@ */ class Admin_Test extends WP_UnitTestCase { - /** - * Editor user ID. - * - * @var int - */ - protected static $editor_user_id; - /** * ID for a post containing blocks. * @@ -35,11 +28,6 @@ class Admin_Test extends WP_UnitTestCase { * Set up before class. */ public static function wpSetUpBeforeClass() { - self::$editor_user_id = self::factory()->user->create( - array( - 'role' => 'editor', - ) - ); self::$post_with_blocks = self::factory()->post->create( array( 'post_title' => 'Example', @@ -54,68 +42,6 @@ public static function wpSetUpBeforeClass() { ); } - /** - * Tests gutenberg_can_edit_post(). - * - * @covers ::gutenberg_can_edit_post - */ - function test_gutenberg_can_edit_post() { - $this->assertFalse( gutenberg_can_edit_post( -1 ) ); - $bogus_post_id = $this->factory()->post->create( - array( - 'post_type' => 'bogus', - ) - ); - $this->assertFalse( gutenberg_can_edit_post( $bogus_post_id ) ); - - register_post_type( - 'restless', - array( - 'show_in_rest' => false, - ) - ); - $restless_post_id = $this->factory()->post->create( - array( - 'post_type' => 'restless', - ) - ); - $this->assertFalse( gutenberg_can_edit_post( $restless_post_id ) ); - - $generic_post_id = $this->factory()->post->create(); - - wp_set_current_user( 0 ); - $this->assertFalse( gutenberg_can_edit_post( $generic_post_id ) ); - - wp_set_current_user( self::$editor_user_id ); - $this->assertTrue( gutenberg_can_edit_post( $generic_post_id ) ); - - $blog_page_without_content = self::factory()->post->create( - array( - 'post_title' => 'Blog', - 'post_content' => '', - ) - ); - update_option( 'page_for_posts', $blog_page_without_content ); - $this->assertFalse( gutenberg_can_edit_post( $blog_page_without_content ) ); - - $blog_page_with_content = self::factory()->post->create( - array( - 'post_title' => 'Blog', - 'post_content' => 'Hello World!', - ) - ); - update_option( 'page_for_posts', $blog_page_with_content ); - $this->assertTrue( gutenberg_can_edit_post( $blog_page_with_content ) ); - - add_filter( 'gutenberg_can_edit_post', '__return_false' ); - $this->assertFalse( gutenberg_can_edit_post( $generic_post_id ) ); - remove_filter( 'gutenberg_can_edit_post', '__return_false' ); - - add_filter( 'gutenberg_can_edit_post', '__return_true' ); - $this->assertTrue( gutenberg_can_edit_post( $restless_post_id ) ); - remove_filter( 'gutenberg_can_edit_post', '__return_true' ); - } - /** * Tests gutenberg_post_has_blocks(). * From bd6b68e9e8f4b92a52890baa6b6fd615352953c0 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Thu, 14 Feb 2019 08:26:50 +0000 Subject: [PATCH 432/691] Fix: missing className on latest comments block (#13834) --- packages/block-library/src/latest-comments/index.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/block-library/src/latest-comments/index.php b/packages/block-library/src/latest-comments/index.php index fcc17c3cd6a00..26a8f4d63607d 100644 --- a/packages/block-library/src/latest-comments/index.php +++ b/packages/block-library/src/latest-comments/index.php @@ -117,6 +117,9 @@ function render_block_core_latest_comments( $attributes = array() ) { } $class = 'wp-block-latest-comments'; + if ( ! empty( $attributes['className'] ) ) { + $class .= ' ' . $attributes['className']; + } if ( isset( $attributes['align'] ) ) { $class .= " align{$attributes['align']}"; } From 1315354f904936b149971ea07c5d0781017a4161 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Thu, 14 Feb 2019 09:57:14 +0000 Subject: [PATCH 433/691] Add calendar block v1 (#13772) The first version of the calendar block. The design tries to follow the mockup proposed. --- lib/load.php | 3 + packages/block-library/src/calendar/edit.js | 67 +++++++++++++++++ packages/block-library/src/calendar/index.js | 33 +++++++++ packages/block-library/src/calendar/index.php | 71 +++++++++++++++++++ .../block-library/src/calendar/style.scss | 37 ++++++++++ packages/block-library/src/index.js | 2 + packages/block-library/src/style.scss | 1 + .../full-content/fixtures/core__calendar.html | 1 + .../full-content/fixtures/core__calendar.json | 10 +++ .../fixtures/core__calendar.parsed.json | 18 +++++ .../fixtures/core__calendar.serialized.html | 1 + 11 files changed, 244 insertions(+) create mode 100644 packages/block-library/src/calendar/edit.js create mode 100644 packages/block-library/src/calendar/index.js create mode 100644 packages/block-library/src/calendar/index.php create mode 100644 packages/block-library/src/calendar/style.scss create mode 100644 test/integration/full-content/fixtures/core__calendar.html create mode 100644 test/integration/full-content/fixtures/core__calendar.json create mode 100644 test/integration/full-content/fixtures/core__calendar.parsed.json create mode 100644 test/integration/full-content/fixtures/core__calendar.serialized.html diff --git a/lib/load.php b/lib/load.php index b3c81971a353f..54be9e51c49bd 100644 --- a/lib/load.php +++ b/lib/load.php @@ -36,6 +36,9 @@ } // Currently merged in core as `gutenberg_render_block_core_latest_comments`, // expected to change soon. +if ( ! function_exists( 'render_block_core_calendar' ) ) { + require dirname( __FILE__ ) . '/../packages/block-library/src/calendar/index.php'; +} if ( ! function_exists( 'render_block_core_latest_comments' ) && ! function_exists( 'gutenberg_render_block_core_latest_comments' ) ) { require dirname( __FILE__ ) . '/../packages/block-library/src/latest-comments/index.php'; diff --git a/packages/block-library/src/calendar/edit.js b/packages/block-library/src/calendar/edit.js new file mode 100644 index 0000000000000..67d89d99bd457 --- /dev/null +++ b/packages/block-library/src/calendar/edit.js @@ -0,0 +1,67 @@ +/** + * External dependencies + */ +import moment from 'moment'; +import memoize from 'memize'; + +/** + * WordPress dependencies + */ +import { + Disabled, + ServerSideRender, +} from '@wordpress/components'; +import { Component } from '@wordpress/element'; +import { withSelect } from '@wordpress/data'; + +class CalendarEdit extends Component { + constructor() { + super( ...arguments ); + this.getYearMonth = memoize( + this.getYearMonth.bind( this ), + { maxSize: 1 } + ); + this.getServerSideAttributes = memoize( + this.getServerSideAttributes.bind( this ), + { maxSize: 1 } + ); + } + + getYearMonth( date ) { + if ( ! date ) { + return {}; + } + const momentDate = moment( date ); + return { + year: momentDate.year(), + month: momentDate.month() + 1, + }; + } + + getServerSideAttributes( attributes, date ) { + return { + ...attributes, + ...this.getYearMonth( date ), + }; + } + + render() { + return ( + <Disabled> + <ServerSideRender + block="core/calendar" + attributes={ this.getServerSideAttributes( + this.props.attributes, + this.props.date + ) } + /> + </Disabled> + ); + } +} + +export default withSelect( ( select ) => { + return { + date: select( 'core/editor' ).getEditedPostAttribute( 'date' ), + }; +} )( CalendarEdit ); diff --git a/packages/block-library/src/calendar/index.js b/packages/block-library/src/calendar/index.js new file mode 100644 index 0000000000000..da8f02dbe4038 --- /dev/null +++ b/packages/block-library/src/calendar/index.js @@ -0,0 +1,33 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import edit from './edit'; + +export const name = 'core/calendar'; + +export const settings = { + title: __( 'Calendar' ), + + description: __( 'A calendar of your site’s posts.' ), + + icon: 'calendar', + + category: 'widgets', + + keywords: [ __( 'posts' ), __( 'archive' ) ], + + supports: { + align: true, + }, + + edit, + + save() { + return null; + }, +}; diff --git a/packages/block-library/src/calendar/index.php b/packages/block-library/src/calendar/index.php new file mode 100644 index 0000000000000..9177997692f40 --- /dev/null +++ b/packages/block-library/src/calendar/index.php @@ -0,0 +1,71 @@ +<?php +/** + * Server-side rendering of the `core/calendar` block. + * + * @package WordPress + */ + +/** + * Renders the `core/calendar` block on server. + * + * @param array $attributes The block attributes. + * + * @return string Returns the block content. + */ +function render_block_core_calendar( $attributes ) { + global $monthnum, $year, $post; + $previous_monthnum = $monthnum; + $previous_year = $year; + + if ( isset( $attributes['month'] ) ) { + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.OverrideProhibited + $monthnum = $attributes['month']; + } + + if ( isset( $attributes['year'] ) ) { + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.OverrideProhibited + $year = $attributes['year']; + } + + $custom_class_name = empty( $attributes['className'] ) ? '' : ' ' . $attributes['className']; + $align_class_name = empty( $attributes['align'] ) ? '' : ' ' . "align{$attributes['align']}"; + + return sprintf( + '<div class="%1$s">%2$s</div>', + esc_attr( 'wp-block-calendar' . $custom_class_name . $align_class_name ), + get_calendar( true, false ) + ); + + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.OverrideProhibited + $monthnum = $previous_monthnum; + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.OverrideProhibited + $year = $previous_year; +} + +/** + * Registers the `core/calendar` block on server. + */ +function register_block_core_calendar() { + register_block_type( + 'core/calendar', + array( + 'attributes' => array( + 'align' => array( + 'type' => 'string', + ), + 'className' => array( + 'type' => 'string', + ), + 'month' => array( + 'type' => 'integer', + ), + 'year' => array( + 'type' => 'integer', + ), + ), + 'render_callback' => 'render_block_core_calendar', + ) + ); +} + +add_action( 'init', 'register_block_core_calendar' ); diff --git a/packages/block-library/src/calendar/style.scss b/packages/block-library/src/calendar/style.scss new file mode 100644 index 0000000000000..f6fc8f3a5ad7b --- /dev/null +++ b/packages/block-library/src/calendar/style.scss @@ -0,0 +1,37 @@ +.wp-block-calendar { + text-align: center; + + th, + tbody td { + padding: 4px; + border: 1px solid $light-gray-500; + } + + tfoot td { + border: none; + } + + table { + width: 100%; + border-collapse: collapse; + font-family: $default-font; + } + + table th { + font-weight: 440; + background: $light-gray-300; + } + + a { + text-decoration: underline; + } + + tfoot a { + color: $blue-medium-800; + } + + table tbody, + table caption { + color: $dark-gray-600; + } +} diff --git a/packages/block-library/src/index.js b/packages/block-library/src/index.js index aac4e56fc7a8b..1439edcc350ec 100644 --- a/packages/block-library/src/index.js +++ b/packages/block-library/src/index.js @@ -20,6 +20,7 @@ import * as gallery from './gallery'; import * as archives from './archives'; import * as audio from './audio'; import * as button from './button'; +import * as calendar from './calendar'; import * as categories from './categories'; import * as code from './code'; import * as columns from './columns'; @@ -68,6 +69,7 @@ export const registerCoreBlocks = () => { archives, audio, button, + calendar, categories, code, columns, diff --git a/packages/block-library/src/style.scss b/packages/block-library/src/style.scss index 35168d85ab237..f9a53b9221417 100644 --- a/packages/block-library/src/style.scss +++ b/packages/block-library/src/style.scss @@ -2,6 +2,7 @@ @import "./block/edit-panel/style.scss"; @import "./block/indicator/style.scss"; @import "./button/style.scss"; +@import "./calendar/style.scss"; @import "./categories/style.scss"; @import "./columns/style.scss"; @import "./cover/style.scss"; diff --git a/test/integration/full-content/fixtures/core__calendar.html b/test/integration/full-content/fixtures/core__calendar.html new file mode 100644 index 0000000000000..bef2c9d140267 --- /dev/null +++ b/test/integration/full-content/fixtures/core__calendar.html @@ -0,0 +1 @@ +<!-- wp:calendar /--> diff --git a/test/integration/full-content/fixtures/core__calendar.json b/test/integration/full-content/fixtures/core__calendar.json new file mode 100644 index 0000000000000..e0d590b3b9dc1 --- /dev/null +++ b/test/integration/full-content/fixtures/core__calendar.json @@ -0,0 +1,10 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/calendar", + "isValid": true, + "attributes": {}, + "innerBlocks": [], + "originalContent": "" + } +] diff --git a/test/integration/full-content/fixtures/core__calendar.parsed.json b/test/integration/full-content/fixtures/core__calendar.parsed.json new file mode 100644 index 0000000000000..eceba9dddbe8e --- /dev/null +++ b/test/integration/full-content/fixtures/core__calendar.parsed.json @@ -0,0 +1,18 @@ +[ + { + "blockName": "core/calendar", + "attrs": {}, + "innerBlocks": [], + "innerHTML": "", + "innerContent": [] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/test/integration/full-content/fixtures/core__calendar.serialized.html b/test/integration/full-content/fixtures/core__calendar.serialized.html new file mode 100644 index 0000000000000..bef2c9d140267 --- /dev/null +++ b/test/integration/full-content/fixtures/core__calendar.serialized.html @@ -0,0 +1 @@ +<!-- wp:calendar /--> From c6255ed40efc3a8650723b6b2ce4a9f5485f6d26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20Van=C2=A0Durpe?= <iseulde@automattic.com> Date: Thu, 14 Feb 2019 11:14:03 +0100 Subject: [PATCH 434/691] RichText: remove obsolete rule from stylesheet (#13871) --- packages/editor/src/components/rich-text/style.scss | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/editor/src/components/rich-text/style.scss b/packages/editor/src/components/rich-text/style.scss index 097fa156607dd..c1de17bf6076e 100644 --- a/packages/editor/src/components/rich-text/style.scss +++ b/packages/editor/src/components/rich-text/style.scss @@ -19,18 +19,10 @@ // to be saved. white-space: pre-wrap; - > p:empty { - min-height: $editor-font-size * $editor-line-height; - } - > p:first-child { margin-top: 0; } - &:focus { - outline: none; - } - a { color: $blue-medium-700; } @@ -49,6 +41,9 @@ } &:focus { + // Removes outline added by the browser. + outline: none; + *[data-rich-text-format-boundary] { border-radius: 2px; box-shadow: 0 0 0 1px $light-gray-400; From 5336ce71ffbeb0138658a1c10020963392276743 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Thu, 14 Feb 2019 13:56:53 +0000 Subject: [PATCH 435/691] Add end 2 end test tag creation (#13129) --- ...-sidebar-panel-toggle-button-with-title.js | 15 ++++ .../src/find-sidebar-panel-with-title.js | 7 +- packages/e2e-test-utils/src/index.js | 1 + .../specs/new-post-default-content.test.js | 4 +- .../specs/plugins/meta-boxes.test.js | 4 +- packages/e2e-tests/specs/taxonomies.test.js | 76 ++++++++++++++++++- 6 files changed, 97 insertions(+), 10 deletions(-) create mode 100644 packages/e2e-test-utils/src/find-sidebar-panel-toggle-button-with-title.js diff --git a/packages/e2e-test-utils/src/find-sidebar-panel-toggle-button-with-title.js b/packages/e2e-test-utils/src/find-sidebar-panel-toggle-button-with-title.js new file mode 100644 index 0000000000000..a4167fe6e2e6c --- /dev/null +++ b/packages/e2e-test-utils/src/find-sidebar-panel-toggle-button-with-title.js @@ -0,0 +1,15 @@ +/** + * External dependencies + */ +import { first } from 'lodash'; + +/** + * Finds a sidebar panel with the provided title. + * + * @param {string} panelTitle The name of sidebar panel. + * + * @return {?ElementHandle} Object that represents an in-page DOM element. + */ +export async function findSidebarPanelToggleButtonWithTitle( panelTitle ) { + return first( await page.$x( `//div[contains(@class,"edit-post-sidebar")]//button[@class="components-button components-panel__body-toggle"][contains(text(),"${ panelTitle }")]` ) ); +} diff --git a/packages/e2e-test-utils/src/find-sidebar-panel-with-title.js b/packages/e2e-test-utils/src/find-sidebar-panel-with-title.js index bd25778712675..ff60dca5f0078 100644 --- a/packages/e2e-test-utils/src/find-sidebar-panel-with-title.js +++ b/packages/e2e-test-utils/src/find-sidebar-panel-with-title.js @@ -4,12 +4,15 @@ import { first } from 'lodash'; /** - * Finds a sidebar panel with the provided title. + * Finds the button responsible for toggling the sidebar panel with the provided title. * * @param {string} panelTitle The name of sidebar panel. * * @return {?ElementHandle} Object that represents an in-page DOM element. */ export async function findSidebarPanelWithTitle( panelTitle ) { - return first( await page.$x( `//div[contains(@class,"edit-post-sidebar")]//button[@class="components-button components-panel__body-toggle"][contains(text(),"${ panelTitle }")]` ) ); + const classSelect = ( className ) => `[contains(concat(" ", @class, " "), " ${ className } ")]`; + const buttonSelector = `//div${ classSelect( 'edit-post-sidebar' ) }//button${ classSelect( 'components-button' ) }${ classSelect( 'components-panel__body-toggle' ) }[contains(text(),"${ panelTitle }")]`; + const panelSelector = `${ buttonSelector }/ancestor::*[contains(concat(" ", @class, " "), " components-panel__body ")]`; + return first( await await page.$x( panelSelector ) ); } diff --git a/packages/e2e-test-utils/src/index.js b/packages/e2e-test-utils/src/index.js index 5534c981f46a9..6f48ab320a206 100644 --- a/packages/e2e-test-utils/src/index.js +++ b/packages/e2e-test-utils/src/index.js @@ -12,6 +12,7 @@ export { disablePrePublishChecks } from './disable-pre-publish-checks'; export { enablePageDialogAccept } from './enable-page-dialog-accept'; export { enablePrePublishChecks } from './enable-pre-publish-checks'; export { ensureSidebarOpened } from './ensure-sidebar-opened'; +export { findSidebarPanelToggleButtonWithTitle } from './find-sidebar-panel-toggle-button-with-title'; export { findSidebarPanelWithTitle } from './find-sidebar-panel-with-title'; export { getAllBlocks } from './get-all-blocks'; export { getAvailableBlockTransforms } from './get-available-block-transforms'; diff --git a/packages/e2e-tests/specs/new-post-default-content.test.js b/packages/e2e-tests/specs/new-post-default-content.test.js index 7460b2aaeac31..3fdad97d46f64 100644 --- a/packages/e2e-tests/specs/new-post-default-content.test.js +++ b/packages/e2e-tests/specs/new-post-default-content.test.js @@ -5,7 +5,7 @@ import { activatePlugin, createNewPost, deactivatePlugin, - findSidebarPanelWithTitle, + findSidebarPanelToggleButtonWithTitle, getEditedPostContent, openDocumentSettingsSidebar, } from '@wordpress/e2e-test-utils'; @@ -33,7 +33,7 @@ describe( 'new editor filtered state', () => { // open the sidebar, we want to see the excerpt. await openDocumentSettingsSidebar(); - const excerptButton = await findSidebarPanelWithTitle( 'Excerpt' ); + const excerptButton = await findSidebarPanelToggleButtonWithTitle( 'Excerpt' ); if ( excerptButton ) { await excerptButton.click( 'button' ); } diff --git a/packages/e2e-tests/specs/plugins/meta-boxes.test.js b/packages/e2e-tests/specs/plugins/meta-boxes.test.js index 94a0c04c81bcb..b2e7649f53b57 100644 --- a/packages/e2e-tests/specs/plugins/meta-boxes.test.js +++ b/packages/e2e-tests/specs/plugins/meta-boxes.test.js @@ -5,7 +5,7 @@ import { activatePlugin, createNewPost, deactivatePlugin, - findSidebarPanelWithTitle, + findSidebarPanelToggleButtonWithTitle, insertBlock, openDocumentSettingsSidebar, publishPost, @@ -98,7 +98,7 @@ describe( 'Meta boxes', () => { // Open the excerpt panel await openDocumentSettingsSidebar(); - const excerptButton = await findSidebarPanelWithTitle( 'Excerpt' ); + const excerptButton = await findSidebarPanelToggleButtonWithTitle( 'Excerpt' ); if ( excerptButton ) { await excerptButton.click( 'button' ); } diff --git a/packages/e2e-tests/specs/taxonomies.test.js b/packages/e2e-tests/specs/taxonomies.test.js index adb596e9d296a..087da816f4d6a 100644 --- a/packages/e2e-tests/specs/taxonomies.test.js +++ b/packages/e2e-tests/specs/taxonomies.test.js @@ -36,6 +36,17 @@ describe( 'Taxonomies', () => { ); }; + const getCurrentTags = async () => { + const tagsPanel = await findSidebarPanelWithTitle( 'Tags' ); + return page.evaluate( ( node ) => { + return Array.from( node.querySelectorAll( + '.components-form-token-field__token-text span:not(.screen-reader-text)' + ) ).map( ( field ) => { + return field.innerText; + } ); + }, tagsPanel ); + }; + it( 'should be able to open the categories panel and create a new main category if the user has the right capabilities', async () => { await createNewPost(); @@ -44,14 +55,14 @@ describe( 'Taxonomies', () => { const categoriesPanel = await findSidebarPanelWithTitle( 'Categories' ); expect( categoriesPanel ).toBeDefined(); - // Open the categories panel. - await categoriesPanel.click( 'button' ); - // If the user has no permission to add a new category finish the test. - if ( ! ( await canCreatTermInTaxonomy( 'category' ) ) ) { + if ( ! ( await canCreatTermInTaxonomy( 'categories' ) ) ) { return; } + // Open the categories panel. + await categoriesPanel.click( 'button' ); + await page.waitForSelector( 'button.editor-post-taxonomies__hierarchical-terms-add' ); // Click add new category button. @@ -93,4 +104,61 @@ describe( 'Taxonomies', () => { expect( selectedCategories ).toHaveLength( 1 ); expect( selectedCategories[ 0 ] ).toEqual( 'z rand category 1' ); } ); + + it( 'should be able to open the tags panel and create a new tag if the user has the right capabilities', async () => { + await createNewPost(); + + await openDocumentSettingsSidebar(); + + const tagsPanel = await findSidebarPanelWithTitle( 'Tags' ); + + //expect( await page.evaluate( ( el ) => el.outerHTML, tagsPanel ) ).toEqual( 'tag1 ok' ); + expect( tagsPanel ).toBeDefined(); + + // If the user has no permission to add a new tag finish the test. + if ( ! ( await canCreatTermInTaxonomy( 'tags' ) ) ) { + return; + } + + // Open the tags panel. + await tagsPanel.click( 'button' ); + + const tagInput = await tagsPanel.$( '.components-form-token-field__input' ); + + // Click the tag input field. + await tagInput.click(); + + // Type the category name in the field. + await tagInput.type( 'tag1' ); + + // Press enter to create a new tag. + await tagInput.press( 'Enter' ); + + await page.waitForSelector( '.components-form-token-field__token' ); + + // Get an array with the tags of the post. + let tags = await getCurrentTags(); + + // The post should only contain the tag we added. + expect( tags ).toHaveLength( 1 ); + expect( tags[ 0 ] ).toEqual( 'tag1' ); + + // Type something in the title so we can publish the post. + await page.type( '.editor-post-title__input', 'Hello World' ); + + // Publish the post. + await publishPost(); + + // Reload the editor. + await page.reload(); + + // Wait for the tags to load. + page.waitForSelector( '.components-form-token-field__token' ); + + tags = await getCurrentTags(); + + // The tag selection was persisted after the publish process. + expect( tags ).toHaveLength( 1 ); + expect( tags[ 0 ] ).toEqual( 'tag1' ); + } ); } ); From a779901c4fba1d8cd32475d8489708c2a134bb9f Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell@kjellr.com> Date: Thu, 14 Feb 2019 08:57:43 -0500 Subject: [PATCH 436/691] Remove unused line height style for mce-content-body (#13867) * Remove unused line height style for mce-content-body * Remove unnecessary line-height property for .editor-rich-text-editable This line height can be inherited with no negative effects. Removing this allows for less specific rules, making editor styles a bit easier. --- packages/editor/src/components/rich-text/style.scss | 1 - packages/editor/src/editor-styles.scss | 4 ---- 2 files changed, 5 deletions(-) diff --git a/packages/editor/src/components/rich-text/style.scss b/packages/editor/src/components/rich-text/style.scss index c1de17bf6076e..f731eda584184 100644 --- a/packages/editor/src/components/rich-text/style.scss +++ b/packages/editor/src/components/rich-text/style.scss @@ -7,7 +7,6 @@ .editor-rich-text__editable { margin: 0; position: relative; - line-height: $editor-line-height; // In HTML, leading and trailing spaces are not visible, and multiple spaces // elsewhere are visually reduced to one space. This rule prevents spaces // from collapsing so all space is visible in the editor and can be removed. diff --git a/packages/editor/src/editor-styles.scss b/packages/editor/src/editor-styles.scss index d005d0bf844fa..1efafd9445f49 100644 --- a/packages/editor/src/editor-styles.scss +++ b/packages/editor/src/editor-styles.scss @@ -33,7 +33,3 @@ ul ul, ol ul { list-style-type: circle; } - -.mce-content-body { - line-height: $editor-line-height; -} From baf733cc0b8359ab43c7073c2bcffd95eabe45b6 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell@kjellr.com> Date: Thu, 14 Feb 2019 12:10:03 -0500 Subject: [PATCH 437/691] Rename default block styles to "Default" (#13670) Resolves #13660. This aligns the classname with the actual name we're using in the UI, and also allows for a bit more flexibility for default block styles per theme. --- packages/block-library/src/button/index.js | 2 +- packages/block-library/src/pullquote/index.js | 2 +- packages/block-library/src/quote/index.js | 2 +- packages/block-library/src/separator/index.js | 2 +- packages/block-library/src/table/index.js | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/block-library/src/button/index.js b/packages/block-library/src/button/index.js index 3b8b028e9ad10..9c92a4cda6828 100644 --- a/packages/block-library/src/button/index.js +++ b/packages/block-library/src/button/index.js @@ -78,7 +78,7 @@ export const settings = { }, styles: [ - { name: 'default', label: _x( 'Rounded', 'block style' ), isDefault: true }, + { name: 'default', label: _x( 'Default', 'block style' ), isDefault: true }, { name: 'outline', label: __( 'Outline' ) }, { name: 'squared', label: _x( 'Squared', 'block style' ) }, ], diff --git a/packages/block-library/src/pullquote/index.js b/packages/block-library/src/pullquote/index.js index 3bbcec5b88d86..b7782b68a0551 100644 --- a/packages/block-library/src/pullquote/index.js +++ b/packages/block-library/src/pullquote/index.js @@ -69,7 +69,7 @@ export const settings = { attributes: blockAttributes, styles: [ - { name: 'default', label: _x( 'Regular', 'block style' ), isDefault: true }, + { name: 'default', label: _x( 'Default', 'block style' ), isDefault: true }, { name: SOLID_COLOR_STYLE_NAME, label: __( 'Solid Color' ) }, ], diff --git a/packages/block-library/src/quote/index.js b/packages/block-library/src/quote/index.js index 525042540a2cb..6a2506eb5f2e0 100644 --- a/packages/block-library/src/quote/index.js +++ b/packages/block-library/src/quote/index.js @@ -51,7 +51,7 @@ export const settings = { attributes: blockAttributes, styles: [ - { name: 'default', label: _x( 'Regular', 'block style' ), isDefault: true }, + { name: 'default', label: _x( 'Default', 'block style' ), isDefault: true }, { name: 'large', label: _x( 'Large', 'block style' ) }, ], diff --git a/packages/block-library/src/separator/index.js b/packages/block-library/src/separator/index.js index adcc34297d2ff..f802bec8364ad 100644 --- a/packages/block-library/src/separator/index.js +++ b/packages/block-library/src/separator/index.js @@ -19,7 +19,7 @@ export const settings = { keywords: [ __( 'horizontal-line' ), 'hr', __( 'divider' ) ], styles: [ - { name: 'default', label: __( 'Short Line' ), isDefault: true }, + { name: 'default', label: __( 'Default' ), isDefault: true }, { name: 'wide', label: __( 'Wide Line' ) }, { name: 'dots', label: __( 'Dots' ) }, ], diff --git a/packages/block-library/src/table/index.js b/packages/block-library/src/table/index.js index af53386b40f9a..6cf466f50dc36 100644 --- a/packages/block-library/src/table/index.js +++ b/packages/block-library/src/table/index.js @@ -95,7 +95,7 @@ export const settings = { }, styles: [ - { name: 'regular', label: _x( 'Regular', 'block style' ), isDefault: true }, + { name: 'regular', label: _x( 'Default', 'block style' ), isDefault: true }, { name: 'stripes', label: __( 'Stripes' ) }, ], From b514fb663916a8d2d6d6c60ac6f85da1509e509c Mon Sep 17 00:00:00 2001 From: Marko Savic <savicmarko1985@gmail.com> Date: Thu, 14 Feb 2019 20:16:40 +0100 Subject: [PATCH 438/691] Mobile: Implemented media options sheet (#13656) * Implemented media options sheet --- .../block-library/src/image/edit.native.js | 94 +++++++++++++------ .../editor/src/components/index.native.js | 1 + .../media-placeholder/index.native.js | 25 +++-- .../media-placeholder/styles.native.scss | 21 +++-- .../mobile/bottom-sheet/index.native.js | 1 + .../components/mobile/picker/index.android.js | 5 +- 6 files changed, 95 insertions(+), 52 deletions(-) diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index 521b143e6c2a2..2ba6f707422eb 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -28,6 +28,7 @@ import { BlockControls, InspectorControls, BottomSheet, + Picker, } from '@wordpress/editor'; import { __ } from '@wordpress/i18n'; import { isURL } from '@wordpress/url'; @@ -43,6 +44,10 @@ const MEDIA_UPLOAD_STATE_SUCCEEDED = 2; const MEDIA_UPLOAD_STATE_FAILED = 3; const MEDIA_UPLOAD_STATE_RESET = 4; +const MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_CHOOSE_FROM_DEVICE = 'choose_from_device'; +const MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_TAKE_PHOTO = 'take_photo'; +const MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_WORD_PRESS_LIBRARY = 'wordpress_media_library'; + const LINK_DESTINATION_CUSTOM = 'custom'; const LINK_DESTINATION_NONE = 'none'; @@ -173,6 +178,14 @@ class ImageEdit extends React.Component { } ); } + getMediaOptionsItems() { + return [ + { icon: 'wordpress-alt', value: MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_CHOOSE_FROM_DEVICE, label: __( 'Choose from device' ) }, + { icon: 'camera', value: MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_TAKE_PHOTO, label: __( 'Take a Photo' ) }, + { icon: 'format-image', value: MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_WORD_PRESS_LIBRARY, label: __( 'WordPress Media Library' ) }, + ]; + } + render() { const { attributes, isSelected, setAttributes } = this.props; const { url, caption, height, width, alt, href } = attributes; @@ -185,33 +198,23 @@ class ImageEdit extends React.Component { } ); }; - if ( ! url ) { - const onMediaUploadButtonPressed = () => { - requestMediaPickFromDeviceLibrary( ( mediaId, mediaUri ) => { - if ( mediaUri ) { - this.addMediaUploadListener( ); - setAttributes( { url: mediaUri, id: mediaId } ); - } - } ); - }; - - const onMediaCaptureButtonPressed = () => { - requestMediaPickFromDeviceCamera( ( mediaId, mediaUri ) => { - if ( mediaUri ) { - this.addMediaUploadListener( ); - setAttributes( { url: mediaUri, id: mediaId } ); - } - } ); - }; + const onMediaUploadButtonPressed = () => { + requestMediaPickFromDeviceLibrary( ( mediaId, mediaUri ) => { + if ( mediaUri ) { + this.addMediaUploadListener( ); + setAttributes( { url: mediaUri, id: mediaId } ); + } + } ); + }; - return ( - <MediaPlaceholder - onUploadMediaPressed={ onMediaUploadButtonPressed } - onMediaLibraryPressed={ onMediaLibraryButtonPressed } - onCapturePhotoPressed={ onMediaCaptureButtonPressed } - /> - ); - } + const onMediaCaptureButtonPressed = () => { + requestMediaPickFromDeviceCamera( ( mediaId, mediaUri ) => { + if ( mediaUri ) { + this.addMediaUploadListener( ); + setAttributes( { url: mediaUri, id: mediaId } ); + } + } ); + }; const onImageSettingsButtonPressed = () => { this.setState( { showSettings: true } ); @@ -221,12 +224,18 @@ class ImageEdit extends React.Component { this.setState( { showSettings: false } ); }; + let picker; + + const onMediaOptionsButtonPressed = () => { + picker.presentPicker(); + }; + const toolbarEditButton = ( <Toolbar> <ToolbarButton label={ __( 'Edit image' ) } icon="edit" - onClick={ onMediaLibraryButtonPressed } + onClick={ onMediaOptionsButtonPressed } /> </Toolbar> ); @@ -262,6 +271,36 @@ class ImageEdit extends React.Component { </BottomSheet> ); + const mediaOptions = this.getMediaOptionsItems(); + + const getMediaOptions = () => ( + <Picker + hideCancelButton={ true } + ref={ ( instance ) => picker = instance } + options={ mediaOptions } + onChange={ ( value ) => { + if ( value === MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_CHOOSE_FROM_DEVICE ) { + onMediaUploadButtonPressed(); + } else if ( value === MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_TAKE_PHOTO ) { + onMediaCaptureButtonPressed(); + } else if ( value === MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_WORD_PRESS_LIBRARY ) { + onMediaLibraryButtonPressed(); + } + } } + /> + ); + + if ( ! url ) { + return ( + <View style={ { flex: 1 } } > + { getMediaOptions() } + <MediaPlaceholder + onMediaOptionsPressed={ onMediaOptionsButtonPressed } + /> + </View> + ); + } + const showSpinner = this.state.isUploadInProgress; const opacity = this.state.isUploadInProgress ? 0.3 : 1; const progress = this.state.progress * 100; @@ -300,6 +339,7 @@ class ImageEdit extends React.Component { return ( <View style={ { flex: 1 } } > { getInspectorControls() } + { getMediaOptions() } <ImageBackground style={ { width: finalWidth, height: finalHeight, opacity } } resizeMethod="scale" diff --git a/packages/editor/src/components/index.native.js b/packages/editor/src/components/index.native.js index ebb3fb5b3e94e..fc41a93fa6af3 100644 --- a/packages/editor/src/components/index.native.js +++ b/packages/editor/src/components/index.native.js @@ -12,3 +12,4 @@ export { default as EditorHistoryRedo } from './editor-history/redo'; export { default as EditorHistoryUndo } from './editor-history/undo'; export { default as InspectorControls } from './inspector-controls'; export { default as BottomSheet } from './mobile/bottom-sheet'; +export { default as Picker } from './mobile/picker'; diff --git a/packages/editor/src/components/media-placeholder/index.native.js b/packages/editor/src/components/media-placeholder/index.native.js index 0c2a234ad9798..1bd983c37699a 100644 --- a/packages/editor/src/components/media-placeholder/index.native.js +++ b/packages/editor/src/components/media-placeholder/index.native.js @@ -1,12 +1,13 @@ /** * External dependencies */ -import { View, Text, Button } from 'react-native'; +import { View, Text, TouchableWithoutFeedback } from 'react-native'; /** * WordPress dependencies */ import { __ } from '@wordpress/i18n'; +import { Dashicon } from '@wordpress/components'; /** * Internal dependencies @@ -15,19 +16,17 @@ import styles from './styles.scss'; function MediaPlaceholder( props ) { return ( - <View style={ styles.emptyStateContainer }> - <Text style={ styles.emptyStateTitle }> - { __( 'Image' ) } - </Text> - <Text style={ styles.emptyStateDescription }> - { __( 'Upload a new image or select a file from your library.' ) } - </Text> - <View style={ styles.emptyStateButtonsContainer }> - <Button title={ __( 'Device Library' ) } onPress={ props.onUploadMediaPressed } /> - <Button title={ __( 'Take photo' ) } onPress={ props.onCapturePhotoPressed } /> + <TouchableWithoutFeedback onPress={ props.onMediaOptionsPressed }> + <View style={ styles.emptyStateContainer }> + <Dashicon icon={ 'format-image' } /> + <Text style={ styles.emptyStateTitle }> + { __( 'Image' ) } + </Text> + <Text style={ styles.emptyStateDescription }> + { __( 'CHOOSE IMAGE' ) } + </Text> </View> - <Button title={ __( 'Media Library' ) } onPress={ props.onMediaLibraryPressed } /> - </View> + </TouchableWithoutFeedback> ); } diff --git a/packages/editor/src/components/media-placeholder/styles.native.scss b/packages/editor/src/components/media-placeholder/styles.native.scss index 0014afe089ac5..964bb4371310d 100644 --- a/packages/editor/src/components/media-placeholder/styles.native.scss +++ b/packages/editor/src/components/media-placeholder/styles.native.scss @@ -1,5 +1,9 @@ .emptyStateContainer { + flex: 1; + height: 142; + flex-direction: column; align-items: center; + justify-content: center; background-color: #f2f2f2; padding-left: 12; padding-right: 12; @@ -9,18 +13,15 @@ .emptyStateTitle { text-align: center; - font-weight: 600; - padding-bottom: 12; + margin-top: 8; + margin-bottom: 10; + font-size: 14; + color: #2e4453; } .emptyStateDescription { text-align: center; -} - -.emptyStateButtonsContainer { - margin-top: 15; - margin-bottom: 15; - flex-direction: row; - justify-content: space-evenly; - width: 100%; + color: #0087be; + font-size: 14; + font-weight: 500; } diff --git a/packages/editor/src/components/mobile/bottom-sheet/index.native.js b/packages/editor/src/components/mobile/bottom-sheet/index.native.js index d05eb73e1c925..986e220e41a36 100644 --- a/packages/editor/src/components/mobile/bottom-sheet/index.native.js +++ b/packages/editor/src/components/mobile/bottom-sheet/index.native.js @@ -57,6 +57,7 @@ class BottomSheet extends Component { backdropTransitionOutTiming={ 500 } backdropOpacity={ 0.2 } onBackdropPress={ this.props.onClose } + onBackButtonPress={ this.props.onClose } onSwipe={ this.props.onClose } swipeDirection="down" > diff --git a/packages/editor/src/components/mobile/picker/index.android.js b/packages/editor/src/components/mobile/picker/index.android.js index 8dd1d909b1281..47dd8abb0dc35 100644 --- a/packages/editor/src/components/mobile/picker/index.android.js +++ b/packages/editor/src/components/mobile/picker/index.android.js @@ -45,16 +45,17 @@ export default class Picker extends Component { <View> { this.props.options.map( ( option, index ) => <BottomSheet.Cell + icon={ option.icon } key={ index } label={ option.label } onPress={ () => this.onCellPress( option.value ) } /> ) } - <BottomSheet.Cell + { ! this.props.hideCancelButton && <BottomSheet.Cell label={ __( 'Cancel' ) } onPress={ this.onClose } drawSeparator={ false } - /> + /> } </View> </BottomSheet> ); From 9679df4d48294c918f91e08ed93196760382a214 Mon Sep 17 00:00:00 2001 From: Miguel Fonseca <miguelcsf@gmail.com> Date: Thu, 14 Feb 2019 20:53:35 +0000 Subject: [PATCH 439/691] Shortcode block: Change source of `text` attribute to `html` (#13609) --- packages/block-library/src/shortcode/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/shortcode/index.js b/packages/block-library/src/shortcode/index.js index 06464c52e7082..7204cd1eadf43 100644 --- a/packages/block-library/src/shortcode/index.js +++ b/packages/block-library/src/shortcode/index.js @@ -22,7 +22,7 @@ export const settings = { attributes: { text: { type: 'string', - source: 'text', + source: 'html', }, }, From bb6dfea267980c959080fbf9dab8c362159bba47 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez <diegoreymendez@users.noreply.github.com> Date: Fri, 15 Feb 2019 00:28:32 -0300 Subject: [PATCH 440/691] Implement setRef for post-title in Gutenberg Mobile. (#13874) * WIP * Rolls back the changes to PostTitle. * Implements setRef for post-title. --- packages/editor/src/components/post-title/index.native.js | 2 +- packages/editor/src/components/rich-text/index.native.js | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/editor/src/components/post-title/index.native.js b/packages/editor/src/components/post-title/index.native.js index f9534487cfdaa..59079073a62c9 100644 --- a/packages/editor/src/components/post-title/index.native.js +++ b/packages/editor/src/components/post-title/index.native.js @@ -48,7 +48,6 @@ class PostTitle extends Component { return ( <RichText tagName={ 'p' } - rootTagsToEliminate={ [ 'strong' ] } onFocus={ this.onSelect } onBlur={ this.props.onBlur } // always assign onBlur as a props multiline={ false } @@ -66,6 +65,7 @@ class PostTitle extends Component { placeholder={ decodedPlaceholder } value={ title } onSplit={ this.props.onEnterPress } + setRef={ this.props.setRef } > </RichText> ); diff --git a/packages/editor/src/components/rich-text/index.native.js b/packages/editor/src/components/rich-text/index.native.js index f2b88a4cf8a83..72ff2d49c5b18 100644 --- a/packages/editor/src/components/rich-text/index.native.js +++ b/packages/editor/src/components/rich-text/index.native.js @@ -385,6 +385,10 @@ export class RichText extends Component { <RCTAztecView ref={ ( ref ) => { this._editor = ref; + + if ( this.props.setRef ) { + this.props.setRef( ref ); + } } } text={ { text: html, eventCount: this.lastEventCount } } From 9fa933fd607a14ccb99b47ac37bad0cf645d5e7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Fri, 15 Feb 2019 08:38:23 +0100 Subject: [PATCH 441/691] Run npm audit fix to get rid of vulnerabilities (#13876) * Run npm audit fix to get rid of vulnerabilities * Bump all Babel packages to the latest version * Update lodash version in PHP file --- lib/client-assets.php | 2 +- package-lock.json | 2925 +++++++++++------ package.json | 14 +- packages/a11y/package.json | 2 +- packages/annotations/package.json | 4 +- packages/api-fetch/package.json | 2 +- packages/autop/package.json | 2 +- .../package.json | 2 +- packages/babel-plugin-makepot/package.json | 4 +- packages/babel-preset-default/package.json | 14 +- packages/blob/package.json | 2 +- packages/block-library/package.json | 4 +- .../package.json | 2 +- packages/blocks/package.json | 4 +- packages/components/package.json | 4 +- packages/compose/package.json | 4 +- packages/core-data/package.json | 4 +- .../package.json | 2 +- packages/data/package.json | 4 +- packages/date/package.json | 2 +- packages/deprecated/package.json | 2 +- packages/dom-ready/package.json | 2 +- packages/dom/package.json | 4 +- packages/e2e-test-utils/package.json | 2 +- packages/e2e-tests/package.json | 2 +- packages/edit-post/package.json | 4 +- packages/editor/package.json | 4 +- packages/element/package.json | 4 +- packages/escape-html/package.json | 2 +- packages/format-library/package.json | 2 +- packages/hooks/package.json | 2 +- packages/html-entities/package.json | 2 +- packages/i18n/package.json | 4 +- packages/is-shallow-equal/package.json | 2 +- packages/jest-console/package.json | 4 +- packages/keycodes/package.json | 4 +- .../package.json | 4 +- packages/list-reusable-blocks/package.json | 4 +- packages/notices/package.json | 4 +- packages/nux/package.json | 4 +- packages/plugins/package.json | 4 +- packages/postcss-themes/package.json | 2 +- packages/priority-queue/package.json | 2 +- packages/redux-routine/package.json | 2 +- packages/rich-text/package.json | 4 +- packages/shortcode/package.json | 4 +- packages/token-list/package.json | 4 +- packages/url/package.json | 2 +- packages/viewport/package.json | 4 +- packages/wordcount/package.json | 4 +- 50 files changed, 1923 insertions(+), 1174 deletions(-) diff --git a/lib/client-assets.php b/lib/client-assets.php index e2a6cd048467d..2302345beaa8d 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -548,7 +548,7 @@ function gutenberg_register_vendor_scripts() { ); gutenberg_register_vendor_script( 'lodash', - 'https://unpkg.com/lodash@4.17.10/lodash' . $suffix . '.js' + 'https://unpkg.com/lodash@4.17.11/lodash' . $suffix . '.js' ); wp_add_inline_script( 'lodash', 'window.lodash = _.noConflict();' ); gutenberg_register_vendor_script( diff --git a/package-lock.json b/package-lock.json index 1dc3524f02f50..e59039690658a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,34 +14,66 @@ } }, "@babel/core": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.0.0.tgz", - "integrity": "sha512-nrvxS5u6QUN5gLl1GEakIcmOeoUHT1/gQtdMRq18WFURJ5osn4ppJLVSseMQo4zVWKJfBTF4muIYijXUnKlRLQ==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.2.2.tgz", + "integrity": "sha512-59vB0RWt09cAct5EIe58+NzGP4TFSD3Bz//2/ELy3ZeTeKF6VTD1AXlH8BGGbCX0PuobZBsIzO7IAI9PH67eKw==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.0.0", - "@babel/helpers": "^7.0.0", - "@babel/parser": "^7.0.0", - "@babel/template": "^7.0.0", - "@babel/traverse": "^7.0.0", - "@babel/types": "^7.0.0", + "@babel/generator": "^7.2.2", + "@babel/helpers": "^7.2.0", + "@babel/parser": "^7.2.2", + "@babel/template": "^7.2.2", + "@babel/traverse": "^7.2.2", + "@babel/types": "^7.2.2", "convert-source-map": "^1.1.0", - "debug": "^3.1.0", - "json5": "^0.5.0", + "debug": "^4.1.0", + "json5": "^2.1.0", "lodash": "^4.17.10", "resolve": "^1.3.2", "semver": "^5.4.1", "source-map": "^0.5.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "json5": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", + "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } } }, "@babel/generator": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.0.0.tgz", - "integrity": "sha512-/BM2vupkpbZXq22l1ALO7MqXJZH2k8bKVv8Y+pABFnzWdztDB/ZLveP5At21vLz5c2YtSE6p7j2FZEsqafMz5Q==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.3.2.tgz", + "integrity": "sha512-f3QCuPppXxtZOEm5GWPra/uYUjmNQlu9pbAD8D/9jze4pTY83rTtB1igTBSwvkeNlC5gR24zFFkz+2WHLFQhqQ==", "dev": true, "requires": { - "@babel/types": "^7.0.0", + "@babel/types": "^7.3.2", "jsesc": "^2.5.1", "lodash": "^4.17.10", "source-map": "^0.5.0", @@ -58,65 +90,65 @@ } }, "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.0.0.tgz", - "integrity": "sha512-9HdU8lrAc4FUZOy+y2w//kUhynSpkGIRYDzJW1oKJx7+v8m6UEAbAd2tSvxirsq2kJTXJZZS6Eo8FnUDUH0ZWw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz", + "integrity": "sha512-qNSR4jrmJ8M1VMM9tibvyRAHXQs2PmaksQF7c1CGJNipfe3D8p+wgNwgso/P2A2r2mdgBWAXljNWR0QRZAMW8w==", "dev": true, "requires": { - "@babel/helper-explode-assignable-expression": "^7.0.0", + "@babel/helper-explode-assignable-expression": "^7.1.0", "@babel/types": "^7.0.0" } }, "@babel/helper-builder-react-jsx": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.0.0.tgz", - "integrity": "sha512-ebJ2JM6NAKW0fQEqN8hOLxK84RbRz9OkUhGS/Xd5u56ejMfVbayJ4+LykERZCOUM6faa6Fp3SZNX3fcT16MKHw==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.3.0.tgz", + "integrity": "sha512-MjA9KgwCuPEkQd9ncSXvSyJ5y+j2sICHyrI0M3L+6fnS4wMSNDc1ARXsbTfbb2cXHn17VisSnU/sHFTCxVxSMw==", "dev": true, "requires": { - "@babel/types": "^7.0.0", + "@babel/types": "^7.3.0", "esutils": "^2.0.0" } }, "@babel/helper-call-delegate": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.0.0.tgz", - "integrity": "sha512-HdYG6vr4KgXHK0q1QRZ8guoYCF5rZjIdPlhcVY+j4EBK/FDR+cXRM5/6lQr3NIWDc7dO1KfgjG5rfH6lM89VBw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.1.0.tgz", + "integrity": "sha512-YEtYZrw3GUK6emQHKthltKNZwszBcHK58Ygcis+gVUrF4/FmTVr5CCqQNSfmvg2y+YDEANyYoaLz/SHsnusCwQ==", "dev": true, "requires": { "@babel/helper-hoist-variables": "^7.0.0", - "@babel/traverse": "^7.0.0", + "@babel/traverse": "^7.1.0", "@babel/types": "^7.0.0" } }, "@babel/helper-define-map": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.0.0.tgz", - "integrity": "sha512-acbCxYS9XufWxsBiclmXMK1CFz7en/XSYvHFcbb3Jb8BqjFEBrA46WlIsoSQTRG/eYN60HciUnzdyQxOZhrHfw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.1.0.tgz", + "integrity": "sha512-yPPcW8dc3gZLN+U1mhYV91QU3n5uTbx7DUdf8NnPbjS0RMwBuHi9Xt2MUgppmNz7CJxTBWsGczTiEp1CSOTPRg==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.0.0", + "@babel/helper-function-name": "^7.1.0", "@babel/types": "^7.0.0", "lodash": "^4.17.10" } }, "@babel/helper-explode-assignable-expression": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.0.0.tgz", - "integrity": "sha512-5gLPwdDnYf8GfPsjS+UmZUtYE1jaXTFm1P+ymGobqvXbA0q3ANgpH60+C6zDrRAWXYbQXYvzzQC/r0gJVNNltQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz", + "integrity": "sha512-NRQpfHrJ1msCHtKjbzs9YcMmJZOg6mQMmGRB+hbamEdG5PNpaSm95275VD92DvJKuyl0s2sFiDmMZ+EnnvufqA==", "dev": true, "requires": { - "@babel/traverse": "^7.0.0", + "@babel/traverse": "^7.1.0", "@babel/types": "^7.0.0" } }, "@babel/helper-function-name": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.0.0.tgz", - "integrity": "sha512-Zo+LGvfYp4rMtz84BLF3bavFTdf8y4rJtMPTe2J+rxYmnDOIeH8le++VFI/pRJU+rQhjqiXxE4LMaIau28Tv1Q==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", + "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", "dev": true, "requires": { "@babel/helper-get-function-arity": "^7.0.0", - "@babel/template": "^7.0.0", + "@babel/template": "^7.1.0", "@babel/types": "^7.0.0" } }, @@ -157,16 +189,16 @@ } }, "@babel/helper-module-transforms": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.0.0.tgz", - "integrity": "sha512-QdwmTTlPmT7TZcf30dnqm8pem+o48tVt991xXogE5CQCwqSpWKuzH2E9v8VWeccQ66a6/CmrLZ+bwp66JYeM5A==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.2.2.tgz", + "integrity": "sha512-YRD7I6Wsv+IHuTPkAmAS4HhY0dkPobgLftHp0cRGZSdrRvmZY8rFvae/GVu3bD00qscuvK3WPHB3YdNpBXUqrA==", "dev": true, "requires": { "@babel/helper-module-imports": "^7.0.0", - "@babel/helper-simple-access": "^7.0.0", + "@babel/helper-simple-access": "^7.1.0", "@babel/helper-split-export-declaration": "^7.0.0", - "@babel/template": "^7.0.0", - "@babel/types": "^7.0.0", + "@babel/template": "^7.2.2", + "@babel/types": "^7.2.2", "lodash": "^4.17.10" } }, @@ -195,37 +227,37 @@ } }, "@babel/helper-remap-async-to-generator": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.0.0.tgz", - "integrity": "sha512-3o4sYLOsK6m0A7t1P0saTanBPmk5MAlxVnp9773Of4L8PMVLukU7loZix5KoJgflxSo2c2ETTzseptc0rQEp7A==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz", + "integrity": "sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.0.0", - "@babel/helper-wrap-function": "^7.0.0", - "@babel/template": "^7.0.0", - "@babel/traverse": "^7.0.0", + "@babel/helper-wrap-function": "^7.1.0", + "@babel/template": "^7.1.0", + "@babel/traverse": "^7.1.0", "@babel/types": "^7.0.0" } }, "@babel/helper-replace-supers": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.0.0.tgz", - "integrity": "sha512-fsSv7VogxzMSmGch6DwhKHGsciVXo7hbfhBgH9ZrgJMXKMjO7ASQTUfbVL7MU1uCfviyqjucazGK7TWPT9weuQ==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.2.3.tgz", + "integrity": "sha512-GyieIznGUfPXPWu0yLS6U55Mz67AZD9cUk0BfirOWlPrXlBcan9Gz+vHGz+cPfuoweZSnPzPIm67VtQM0OWZbA==", "dev": true, "requires": { "@babel/helper-member-expression-to-functions": "^7.0.0", "@babel/helper-optimise-call-expression": "^7.0.0", - "@babel/traverse": "^7.0.0", + "@babel/traverse": "^7.2.3", "@babel/types": "^7.0.0" } }, "@babel/helper-simple-access": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.0.0.tgz", - "integrity": "sha512-CNeuX52jbQSq4j1n+R+21xrjbTjsnXa9n1aERbgHRD/p9h4Udkxr1n24yPMQmnTETHdnQDvkVSYWFw/ETAymYg==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz", + "integrity": "sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w==", "dev": true, "requires": { - "@babel/template": "^7.0.0", + "@babel/template": "^7.1.0", "@babel/types": "^7.0.0" } }, @@ -239,26 +271,26 @@ } }, "@babel/helper-wrap-function": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.0.0.tgz", - "integrity": "sha512-kjprWPDNVPZ/9pyLRXcZBvfjnFwqokmXTPTaC4AV8Ns7WRl7ewSxrB19AWZzQsC/WSPQLOw1ciR8uPYkAM1znA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz", + "integrity": "sha512-o9fP1BZLLSrYlxYEYyl2aS+Flun5gtjTIG8iln+XuEzQTs0PLagAGSXUcqruJwD5fM48jzIEggCKpIfWTcR7pQ==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.0.0", - "@babel/template": "^7.0.0", - "@babel/traverse": "^7.0.0", - "@babel/types": "^7.0.0" + "@babel/helper-function-name": "^7.1.0", + "@babel/template": "^7.1.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.2.0" } }, "@babel/helpers": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.0.0.tgz", - "integrity": "sha512-jbvgR8iLZPnyk6m/UqdXYsSxbVtRi7Pd3CzB4OPwPBnmhNG1DWjiiy777NTuoyIcniszK51R40L5pgfXAfHDtw==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.3.1.tgz", + "integrity": "sha512-Q82R3jKsVpUV99mgX50gOPCWwco9Ec5Iln/8Vyu4osNIOQgSrd9RFrQeUvmvddFNoLwMyOUWU+5ckioEKpDoGA==", "dev": true, "requires": { - "@babel/template": "^7.0.0", - "@babel/traverse": "^7.0.0", - "@babel/types": "^7.0.0" + "@babel/template": "^7.1.2", + "@babel/traverse": "^7.1.5", + "@babel/types": "^7.3.0" } }, "@babel/highlight": { @@ -273,56 +305,56 @@ } }, "@babel/parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.0.0.tgz", - "integrity": "sha512-RgJhNdRinpO8zibnoHbzTTexNs4c8ROkXFBanNDZTLHjwbdLk8J5cJSKulx/bycWTLYmKVNCkxRtVCoJnqPk+g==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.3.2.tgz", + "integrity": "sha512-QzNUC2RO1gadg+fs21fi0Uu0OuGNzRKEmgCxoLNzbCdoprLwjfmZwzUrpUNfJPaVRwBpDY47A17yYEGWyRelnQ==", "dev": true }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.0.0.tgz", - "integrity": "sha512-QsXmmjLrFADCcDQAfdQn7tfBRLjpTzRWaDpKpW4ZXW1fahPG4SvjcF1xfvVnXGC662RSExYXL+6DAqbtgqMXeA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz", + "integrity": "sha512-+Dfo/SCQqrwx48ptLVGLdE39YtWRuKc/Y9I5Fy0P1DDBB9lsAHpjcEJQt+4IifuSOSTLBKJObJqMvaO1pIE8LQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-remap-async-to-generator": "^7.0.0", - "@babel/plugin-syntax-async-generators": "^7.0.0" + "@babel/helper-remap-async-to-generator": "^7.1.0", + "@babel/plugin-syntax-async-generators": "^7.2.0" } }, "@babel/plugin-proposal-json-strings": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.0.0.tgz", - "integrity": "sha512-kfVdUkIAGJIVmHmtS/40i/fg/AGnw/rsZBCaapY5yjeO5RA9m165Xbw9KMOu2nqXP5dTFjEjHdfNdoVcHv133Q==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz", + "integrity": "sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-json-strings": "^7.0.0" + "@babel/plugin-syntax-json-strings": "^7.2.0" } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.0.0.tgz", - "integrity": "sha512-14fhfoPcNu7itSen7Py1iGN0gEm87hX/B+8nZPqkdmANyyYWYMY2pjA3r8WXbWVKMzfnSNS0xY8GVS0IjXi/iw==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.3.2.tgz", + "integrity": "sha512-DjeMS+J2+lpANkYLLO+m6GjoTMygYglKmRe6cDTbFv3L9i6mmiE8fe6B8MtCSLZpVXscD5kn7s6SgtHrDoBWoA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-object-rest-spread": "^7.0.0" + "@babel/plugin-syntax-object-rest-spread": "^7.2.0" } }, "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.0.0.tgz", - "integrity": "sha512-JPqAvLG1s13B/AuoBjdBYvn38RqW6n1TzrQO839/sIpqLpbnXKacsAgpZHzLD83Sm8SDXMkkrAvEnJ25+0yIpw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz", + "integrity": "sha512-mgYj3jCcxug6KUcX4OBoOJz3CMrwRfQELPQ5560F70YQUBZB7uac9fqaWamKR1iWUzGiK2t0ygzjTScZnVz75g==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.0.0" + "@babel/plugin-syntax-optional-catch-binding": "^7.2.0" } }, "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.0.0.tgz", - "integrity": "sha512-tM3icA6GhC3ch2SkmSxv7J/hCWKISzwycub6eGsDrFDgukD4dZ/I+x81XgW0YslS6mzNuQ1Cbzh5osjIMgepPQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.2.0.tgz", + "integrity": "sha512-LvRVYb7kikuOtIoUeWTkOxQEV1kYvL5B6U3iWEGCzPNRus1MzJweFqORTj+0jkxozkTSYNJozPOddxmqdqsRpw==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", @@ -331,83 +363,83 @@ } }, "@babel/plugin-syntax-async-generators": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.0.0.tgz", - "integrity": "sha512-im7ged00ddGKAjcZgewXmp1vxSZQQywuQXe2B1A7kajjZmDeY/ekMPmWr9zJgveSaQH0k7BcGrojQhcK06l0zA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz", + "integrity": "sha512-1ZrIRBv2t0GSlcwVoQ6VgSLpLgiN/FVQUzt9znxo7v2Ov4jJrs8RY8tv0wvDmFN3qIdMKWrmMMW6yZ0G19MfGg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-syntax-json-strings": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.0.0.tgz", - "integrity": "sha512-UlSfNydC+XLj4bw7ijpldc1uZ/HB84vw+U6BTuqMdIEmz/LDe63w/GHtpQMdXWdqQZFeAI9PjnHe/vDhwirhKA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz", + "integrity": "sha512-5UGYnMSLRE1dqqZwug+1LISpA403HzlSfsg6P9VXU6TBjcSHeNlw4DxDx7LgpF+iKZoOG/+uzqoRHTdcUpiZNg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-syntax-jsx": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.0.0.tgz", - "integrity": "sha512-PdmL2AoPsCLWxhIr3kG2+F9v4WH06Q3z+NoGVpQgnUNGcagXHq5sB3OXxkSahKq9TLdNMN/AJzFYSOo8UKDMHg==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.2.0.tgz", + "integrity": "sha512-VyN4QANJkRW6lDBmENzRszvZf3/4AXaj9YR7GwrWeeN9tEBPuXbmDYVU9bYBN0D70zCWVwUy0HWq2553VCb6Hw==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-syntax-object-rest-spread": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.0.0.tgz", - "integrity": "sha512-5A0n4p6bIiVe5OvQPxBnesezsgFJdHhSs3uFSvaPdMqtsovajLZ+G2vZyvNe10EzJBWWo3AcHGKhAFUxqwp2dw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz", + "integrity": "sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.0.0.tgz", - "integrity": "sha512-Wc+HVvwjcq5qBg1w5RG9o9RVzmCaAg/Vp0erHCKpAYV8La6I94o4GQAmFYNmkzoMO6gzoOSulpKeSSz6mPEoZw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.2.0.tgz", + "integrity": "sha512-bDe4xKNhb0LI7IvZHiA13kff0KEfaGX/Hv4lMA9+7TEc63hMNvfKo6ZFpXhKuEp+II/q35Gc4NoMeDZyaUbj9w==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-arrow-functions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.0.0.tgz", - "integrity": "sha512-2EZDBl1WIO/q4DIkIp4s86sdp4ZifL51MoIviLY/gG/mLSuOIEg7J8o6mhbxOTvUJkaN50n+8u41FVsr5KLy/w==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz", + "integrity": "sha512-ER77Cax1+8/8jCB9fo4Ud161OZzWN5qawi4GusDuRLcDbDG+bIGYY20zb2dfAFdTRGzrfq2xZPvF0R64EHnimg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-async-to-generator": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.0.0.tgz", - "integrity": "sha512-CiWNhSMZzj1n3uEKUUS/oL+a7Xi8hnPQB6GpC1WfL/ZYvxBLDBn14sHMo5EyOaeArccSonyk5jFIKMRRbrHOnQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.2.0.tgz", + "integrity": "sha512-CEHzg4g5UraReozI9D4fblBYABs7IM6UerAVG7EJVrTLC5keh00aEuLUT+O40+mJCEzaXkYfTCUKIyeDfMOFFQ==", "dev": true, "requires": { "@babel/helper-module-imports": "^7.0.0", "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-remap-async-to-generator": "^7.0.0" + "@babel/helper-remap-async-to-generator": "^7.1.0" } }, "@babel/plugin-transform-block-scoped-functions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.0.0.tgz", - "integrity": "sha512-AOBiyUp7vYTqz2Jibe1UaAWL0Hl9JUXEgjFvvvcSc9MVDItv46ViXFw2F7SVt1B5k+KWjl44eeXOAk3UDEaJjQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.2.0.tgz", + "integrity": "sha512-ntQPR6q1/NKuphly49+QiQiTN0O63uOwjdD6dhIjSWBI5xlrbUFh720TIpzBhpnrLfv2tNH/BXvLIab1+BAI0w==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-block-scoping": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.0.0.tgz", - "integrity": "sha512-GWEMCrmHQcYWISilUrk9GDqH4enf3UmhOEbNbNrlNAX1ssH3MsS1xLOS6rdjRVPgA7XXVPn87tRkdTEoA/dxEg==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.2.0.tgz", + "integrity": "sha512-vDTgf19ZEV6mx35yiPJe4fS02mPQUUcBNwWQSZFXSzTSbsJFQvHt7DqyS3LK8oOWALFOsJ+8bbqBgkirZteD5Q==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", @@ -415,43 +447,43 @@ } }, "@babel/plugin-transform-classes": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.0.0.tgz", - "integrity": "sha512-8LBm7XsHQiNISEmb+ejBiHi1pUihwUf+lrIwyVsXVbQ1vLqgkvhgayK5JnW3WXvQD2rmM0qxFAIyDE5vtMem2A==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.2.2.tgz", + "integrity": "sha512-gEZvgTy1VtcDOaQty1l10T3jQmJKlNVxLDCs+3rCVPr6nMkODLELxViq5X9l+rfxbie3XrfrMCYYY6eX3aOcOQ==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.0.0", - "@babel/helper-define-map": "^7.0.0", - "@babel/helper-function-name": "^7.0.0", + "@babel/helper-define-map": "^7.1.0", + "@babel/helper-function-name": "^7.1.0", "@babel/helper-optimise-call-expression": "^7.0.0", "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-replace-supers": "^7.0.0", + "@babel/helper-replace-supers": "^7.1.0", "@babel/helper-split-export-declaration": "^7.0.0", "globals": "^11.1.0" } }, "@babel/plugin-transform-computed-properties": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.0.0.tgz", - "integrity": "sha512-ubouZdChNAv4AAWAgU7QKbB93NU5sHwInEWfp+/OzJKA02E6Woh9RVoX4sZrbRwtybky/d7baTUqwFx+HgbvMA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.2.0.tgz", + "integrity": "sha512-kP/drqTxY6Xt3NNpKiMomfgkNn4o7+vKxK2DDKcBG9sHj51vHqMBGy8wbDS/J4lMxnqs153/T3+DmCEAkC5cpA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-destructuring": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.0.0.tgz", - "integrity": "sha512-Fr2GtF8YJSXGTyFPakPFB4ODaEKGU04bPsAllAIabwoXdFrPxL0LVXQX5dQWoxOjjgozarJcC9eWGsj0fD6Zsg==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.3.2.tgz", + "integrity": "sha512-Lrj/u53Ufqxl/sGxyjsJ2XNtNuEjDyjpqdhMNh5aZ+XFOdThL46KBj27Uem4ggoezSYBxKWAil6Hu8HtwqesYw==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-dotall-regex": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.0.0.tgz", - "integrity": "sha512-00THs8eJxOJUFVx1w8i1MBF4XH4PsAjKjQ1eqN/uCH3YKwP21GCKfrn6YZFZswbOk9+0cw1zGQPHVc1KBlSxig==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.2.0.tgz", + "integrity": "sha512-sKxnyHfizweTgKZf7XsXu/CNupKhzijptfTM+bozonIuyVrLWVUvYjE2bhuSBML8VQeMxq4Mm63Q9qvcvUcciQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", @@ -460,77 +492,77 @@ } }, "@babel/plugin-transform-duplicate-keys": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.0.0.tgz", - "integrity": "sha512-w2vfPkMqRkdxx+C71ATLJG30PpwtTpW7DDdLqYt2acXU7YjztzeWW2Jk1T6hKqCLYCcEA5UQM/+xTAm+QCSnuQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.2.0.tgz", + "integrity": "sha512-q+yuxW4DsTjNceUiTzK0L+AfQ0zD9rWaTLiUqHA8p0gxx7lu1EylenfzjeIWNkPy6e/0VG/Wjw9uf9LueQwLOw==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.0.0.tgz", - "integrity": "sha512-Ig74elCuFQ0mvHkWUq5qDCNI3qHWlop5w4TcDxdtJiOk8Egqe2uxDRY9XnXGSlmWClClmnixcoYumyvbAuj4dA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.2.0.tgz", + "integrity": "sha512-umh4hR6N7mu4Elq9GG8TOu9M0bakvlsREEC+ialrQN6ABS4oDQ69qJv1VtR3uxlKMCQMCvzk7vr17RHKcjx68A==", "dev": true, "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.0.0", + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.1.0", "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-for-of": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.0.0.tgz", - "integrity": "sha512-TlxKecN20X2tt2UEr2LNE6aqA0oPeMT1Y3cgz8k4Dn1j5ObT8M3nl9aA37LLklx0PBZKETC9ZAf9n/6SujTuXA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.2.0.tgz", + "integrity": "sha512-Kz7Mt0SsV2tQk6jG5bBv5phVbkd0gd27SgYD4hH1aLMJRchM0dzHaXvrWhVZ+WxAlDoAKZ7Uy3jVTW2mKXQ1WQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-function-name": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.0.0.tgz", - "integrity": "sha512-mR7JN9vkwsAIot74pSwzn/2Gq4nn2wN0HKtQyJLc1ghAarsymdBMTfh+Q/aeR2N3heXs3URQscTLrKe3yUU7Yw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.2.0.tgz", + "integrity": "sha512-kWgksow9lHdvBC2Z4mxTsvc7YdY7w/V6B2vy9cTIPtLEE9NhwoWivaxdNM/S37elu5bqlLP/qOY906LukO9lkQ==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.0.0", + "@babel/helper-function-name": "^7.1.0", "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-literals": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.0.0.tgz", - "integrity": "sha512-1NTDBWkeNXgpUcyoVFxbr9hS57EpZYXpje92zv0SUzjdu3enaRwF/l3cmyRnXLtIdyJASyiS6PtybK+CgKf7jA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.2.0.tgz", + "integrity": "sha512-2ThDhm4lI4oV7fVQ6pNNK+sx+c/GM5/SaML0w/r4ZB7sAneD/piDJtwdKlNckXeyGK7wlwg2E2w33C/Hh+VFCg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-modules-amd": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.0.0.tgz", - "integrity": "sha512-CtSVpT/0tty/4405qczoIHm41YfFbPChplsmfBwsi3RTq/M9cHgVb3ixI5bqqgdKkqWwSX2sXqejvMKLuTVU+Q==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.2.0.tgz", + "integrity": "sha512-mK2A8ucqz1qhrdqjS9VMIDfIvvT2thrEsIQzbaTdc5QFzhDjQv2CkJJ5f6BXIkgbmaoax3zBr2RyvV/8zeoUZw==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.0.0", + "@babel/helper-module-transforms": "^7.1.0", "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.0.0.tgz", - "integrity": "sha512-BIcQLgPFCxi7YygtNpz5xj+7HxhOprbCGZKeLW6Kxsn1eHS6sJZMw4MfmqFZagl/v6IVa0AJoMHdDXLVrpd3Aw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.2.0.tgz", + "integrity": "sha512-V6y0uaUQrQPXUrmj+hgnks8va2L0zcZymeU7TtWEgdRLNkceafKXEduv7QzgQAE4lT+suwooG9dC7LFhdRAbVQ==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.0.0", + "@babel/helper-module-transforms": "^7.1.0", "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-simple-access": "^7.0.0" + "@babel/helper-simple-access": "^7.1.0" } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.0.0.tgz", - "integrity": "sha512-8EDKMAsitLkiF/D4Zhe9CHEE2XLh4bfLbb9/Zf3FgXYQOZyZYyg7EAel/aT2A7bHv62jwHf09q2KU/oEexr83g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.2.0.tgz", + "integrity": "sha512-aYJwpAhoK9a+1+O625WIjvMY11wkB/ok0WClVwmeo3mCjcNRjt+/8gHWrB5i+00mUju0gWsBkQnPpdvQ7PImmQ==", "dev": true, "requires": { "@babel/helper-hoist-variables": "^7.0.0", @@ -538,15 +570,24 @@ } }, "@babel/plugin-transform-modules-umd": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.0.0.tgz", - "integrity": "sha512-EMyKpzgugxef+R1diXDwqw/Hmt5ls8VxfI8Gq5Lo8Qp3oKIepkYG4L/mvE2dmZSRalgL9sguoPKbnQ1m96hVFw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.2.0.tgz", + "integrity": "sha512-BV3bw6MyUH1iIsGhXlOK6sXhmSarZjtJ/vMiD9dNmpY8QXFFQTj+6v92pcfy1iqa8DeAfJFwoxcrS/TUZda6sw==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.0.0", + "@babel/helper-module-transforms": "^7.1.0", "@babel/helper-plugin-utils": "^7.0.0" } }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.3.0.tgz", + "integrity": "sha512-NxIoNVhk9ZxS+9lSoAQ/LM0V2UEvARLttEHUrRDGKFaAxOYQcrkN/nLRE+BbbicCAvZPl7wMP0X60HsHE5DtQw==", + "dev": true, + "requires": { + "regexp-tree": "^0.1.0" + } + }, "@babel/plugin-transform-new-target": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.0.0.tgz", @@ -557,35 +598,35 @@ } }, "@babel/plugin-transform-object-super": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.0.0.tgz", - "integrity": "sha512-BfAiF1l18Xr1shy1NyyQgLiHDvh/S7APiEM5+0wxTsQ+e3fgXO+NA47u4PvppzH0meJS21y0gZHcjnvUAJj8tQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.2.0.tgz", + "integrity": "sha512-VMyhPYZISFZAqAPVkiYb7dUe2AsVi2/wCT5+wZdsNO31FojQJa9ns40hzZ6U9f50Jlq4w6qwzdBB2uwqZ00ebg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-replace-supers": "^7.0.0" + "@babel/helper-replace-supers": "^7.1.0" } }, "@babel/plugin-transform-parameters": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.0.0.tgz", - "integrity": "sha512-eWngvRBWx0gScot0xa340JzrkA+8HGAk1OaCHDfXAjkrTFkp73Lcf+78s7AStSdRML5nzx5aXpnjN1MfrjkBoA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.2.0.tgz", + "integrity": "sha512-kB9+hhUidIgUoBQ0MsxMewhzr8i60nMa2KgeJKQWYrqQpqcBYtnpR+JgkadZVZoaEZ/eKu9mclFaVwhRpLNSzA==", "dev": true, "requires": { - "@babel/helper-call-delegate": "^7.0.0", + "@babel/helper-call-delegate": "^7.1.0", "@babel/helper-get-function-arity": "^7.0.0", "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-react-jsx": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.0.0.tgz", - "integrity": "sha512-0TMP21hXsSUjIQJmu/r7RiVxeFrXRcMUigbKu0BLegJK9PkYodHstaszcig7zxXfaBji2LYUdtqIkHs+hgYkJQ==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.3.0.tgz", + "integrity": "sha512-a/+aRb7R06WcKvQLOu4/TpjKOdvVEKRLWFpKcNuHhiREPgGRB4TQJxq07+EZLS8LFVYpfq1a5lDUnuMdcCpBKg==", "dev": true, "requires": { - "@babel/helper-builder-react-jsx": "^7.0.0", + "@babel/helper-builder-react-jsx": "^7.3.0", "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-jsx": "^7.0.0" + "@babel/plugin-syntax-jsx": "^7.2.0" } }, "@babel/plugin-transform-regenerator": { @@ -598,38 +639,47 @@ } }, "@babel/plugin-transform-runtime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.0.0.tgz", - "integrity": "sha512-yECRVxRu25Nsf6IY5v5XrXhcW9ZHomUQiq30VO8H7r3JYPcBJDTcxZmT+6v1O3QKKrDp1Wp40LinGbcd+jlp9A==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.2.0.tgz", + "integrity": "sha512-jIgkljDdq4RYDnJyQsiWbdvGeei/0MOTtSHKO/rfbd/mXBxNpdlulMx49L0HQ4pug1fXannxoqCI+fYSle9eSw==", "dev": true, "requires": { "@babel/helper-module-imports": "^7.0.0", "@babel/helper-plugin-utils": "^7.0.0", - "resolve": "^1.8.1" + "resolve": "^1.8.1", + "semver": "^5.5.1" + }, + "dependencies": { + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true + } } }, "@babel/plugin-transform-shorthand-properties": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.0.0.tgz", - "integrity": "sha512-g/99LI4vm5iOf5r1Gdxq5Xmu91zvjhEG5+yZDJW268AZELAu4J1EiFLnkSG3yuUsZyOipVOVUKoGPYwfsTymhw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz", + "integrity": "sha512-QP4eUM83ha9zmYtpbnyjTLAGKQritA5XW/iG9cjtuOI8s1RuL/3V6a3DeSHfKutJQ+ayUfeZJPcnCYEQzaPQqg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-spread": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.0.0.tgz", - "integrity": "sha512-L702YFy2EvirrR4shTj0g2xQp7aNwZoWNCkNu2mcoU0uyzMl0XRwDSwzB/xp6DSUFiBmEXuyAyEN16LsgVqGGQ==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.2.2.tgz", + "integrity": "sha512-KWfky/58vubwtS0hLqEnrWJjsMGaOeSBn90Ezn5Jeg9Z8KKHmELbP1yGylMlm5N6TPKeY9A2+UaSYLdxahg01w==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-sticky-regex": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.0.0.tgz", - "integrity": "sha512-LFUToxiyS/WD+XEWpkx/XJBrUXKewSZpzX68s+yEOtIbdnsRjpryDw9U06gYc6klYEij/+KQVRnD3nz3AoKmjw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.2.0.tgz", + "integrity": "sha512-KKYCoGaRAf+ckH8gEL3JHUaFVyNHKe3ASNsZ+AlktgHevvxGigoIttrEJb8iKN03Q7Eazlv1s6cx2B2cQ3Jabw==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", @@ -637,9 +687,9 @@ } }, "@babel/plugin-transform-template-literals": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.0.0.tgz", - "integrity": "sha512-vA6rkTCabRZu7Nbl9DfLZE1imj4tzdWcg5vtdQGvj+OH9itNNB6hxuRMHuIY8SGnEt1T9g5foqs9LnrHzsqEFg==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.2.0.tgz", + "integrity": "sha512-FkPix00J9A/XWXv4VoKJBMeSkyY9x/TqIh76wzcdfl57RJJcf8CehQ08uwfhCDNtRQYtHQKBTwKZDEyjE13Lwg==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.0.0", @@ -647,18 +697,18 @@ } }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.0.0.tgz", - "integrity": "sha512-1r1X5DO78WnaAIvs5uC48t41LLckxsYklJrZjNKcevyz83sF2l4RHbw29qrCPr/6ksFsdfRpT/ZgxNWHXRnffg==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.2.0.tgz", + "integrity": "sha512-2LNhETWYxiYysBtrBTqL8+La0jIoQQnIScUJc74OYvUGRmkskNY4EzLCnjHBzdmb38wqtTaixpo1NctEcvMDZw==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-unicode-regex": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.0.0.tgz", - "integrity": "sha512-uJBrJhBOEa3D033P95nPHu3nbFwFE9ZgXsfEitzoIXIwqAZWk7uXcg06yFKXz9FSxBH5ucgU/cYdX0IV8ldHKw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.2.0.tgz", + "integrity": "sha512-m48Y0lMhrbXEJnVUaYly29jRXbQ3ksxPrS1Tg8t+MHqzXhtBYAvI51euOBaoAlZLPHsieY9XPVMf80a5x0cPcA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", @@ -667,66 +717,68 @@ } }, "@babel/preset-env": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.0.0.tgz", - "integrity": "sha512-Fnx1wWaWv2w2rl+VHxA9si//Da40941IQ29fKiRejVR7oN1FxSEL8+SyAX/2oKIye2gPvY/GBbJVEKQ/oi43zQ==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.3.1.tgz", + "integrity": "sha512-FHKrD6Dxf30e8xgHQO0zJZpUPfVZg+Xwgz5/RdSWCbza9QLNk4Qbp40ctRoqDxml3O8RMzB1DU55SXeDG6PqHQ==", "dev": true, "requires": { "@babel/helper-module-imports": "^7.0.0", "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-async-generator-functions": "^7.0.0", - "@babel/plugin-proposal-json-strings": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.0.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.0.0", - "@babel/plugin-syntax-async-generators": "^7.0.0", - "@babel/plugin-syntax-object-rest-spread": "^7.0.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.0.0", - "@babel/plugin-transform-arrow-functions": "^7.0.0", - "@babel/plugin-transform-async-to-generator": "^7.0.0", - "@babel/plugin-transform-block-scoped-functions": "^7.0.0", - "@babel/plugin-transform-block-scoping": "^7.0.0", - "@babel/plugin-transform-classes": "^7.0.0", - "@babel/plugin-transform-computed-properties": "^7.0.0", - "@babel/plugin-transform-destructuring": "^7.0.0", - "@babel/plugin-transform-dotall-regex": "^7.0.0", - "@babel/plugin-transform-duplicate-keys": "^7.0.0", - "@babel/plugin-transform-exponentiation-operator": "^7.0.0", - "@babel/plugin-transform-for-of": "^7.0.0", - "@babel/plugin-transform-function-name": "^7.0.0", - "@babel/plugin-transform-literals": "^7.0.0", - "@babel/plugin-transform-modules-amd": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.0.0", - "@babel/plugin-transform-modules-systemjs": "^7.0.0", - "@babel/plugin-transform-modules-umd": "^7.0.0", + "@babel/plugin-proposal-async-generator-functions": "^7.2.0", + "@babel/plugin-proposal-json-strings": "^7.2.0", + "@babel/plugin-proposal-object-rest-spread": "^7.3.1", + "@babel/plugin-proposal-optional-catch-binding": "^7.2.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.2.0", + "@babel/plugin-syntax-async-generators": "^7.2.0", + "@babel/plugin-syntax-json-strings": "^7.2.0", + "@babel/plugin-syntax-object-rest-spread": "^7.2.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.2.0", + "@babel/plugin-transform-arrow-functions": "^7.2.0", + "@babel/plugin-transform-async-to-generator": "^7.2.0", + "@babel/plugin-transform-block-scoped-functions": "^7.2.0", + "@babel/plugin-transform-block-scoping": "^7.2.0", + "@babel/plugin-transform-classes": "^7.2.0", + "@babel/plugin-transform-computed-properties": "^7.2.0", + "@babel/plugin-transform-destructuring": "^7.2.0", + "@babel/plugin-transform-dotall-regex": "^7.2.0", + "@babel/plugin-transform-duplicate-keys": "^7.2.0", + "@babel/plugin-transform-exponentiation-operator": "^7.2.0", + "@babel/plugin-transform-for-of": "^7.2.0", + "@babel/plugin-transform-function-name": "^7.2.0", + "@babel/plugin-transform-literals": "^7.2.0", + "@babel/plugin-transform-modules-amd": "^7.2.0", + "@babel/plugin-transform-modules-commonjs": "^7.2.0", + "@babel/plugin-transform-modules-systemjs": "^7.2.0", + "@babel/plugin-transform-modules-umd": "^7.2.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.3.0", "@babel/plugin-transform-new-target": "^7.0.0", - "@babel/plugin-transform-object-super": "^7.0.0", - "@babel/plugin-transform-parameters": "^7.0.0", + "@babel/plugin-transform-object-super": "^7.2.0", + "@babel/plugin-transform-parameters": "^7.2.0", "@babel/plugin-transform-regenerator": "^7.0.0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0", - "@babel/plugin-transform-spread": "^7.0.0", - "@babel/plugin-transform-sticky-regex": "^7.0.0", - "@babel/plugin-transform-template-literals": "^7.0.0", - "@babel/plugin-transform-typeof-symbol": "^7.0.0", - "@babel/plugin-transform-unicode-regex": "^7.0.0", - "browserslist": "^4.1.0", + "@babel/plugin-transform-shorthand-properties": "^7.2.0", + "@babel/plugin-transform-spread": "^7.2.0", + "@babel/plugin-transform-sticky-regex": "^7.2.0", + "@babel/plugin-transform-template-literals": "^7.2.0", + "@babel/plugin-transform-typeof-symbol": "^7.2.0", + "@babel/plugin-transform-unicode-regex": "^7.2.0", + "browserslist": "^4.3.4", "invariant": "^2.2.2", "js-levenshtein": "^1.1.3", "semver": "^5.3.0" } }, "@babel/runtime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0.tgz", - "integrity": "sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.3.1.tgz", + "integrity": "sha512-7jGW8ppV0ant637pIqAcFfQDDH1orEPGJb8aXfUozuCU3QqX7rX4DA8iwrbPrR1hcH0FTTHz47yQnk+bl5xHQA==", "requires": { "regenerator-runtime": "^0.12.0" } }, "@babel/runtime-corejs2": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.0.0.tgz", - "integrity": "sha512-Yww0jXgolNtkhcK+Txo5JN+DjBpNmmAtD7G99HOebhEjBzjnACG09Tip9C8lSOF6PrhA56OeJWeOZduNJaKxBA==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.3.1.tgz", + "integrity": "sha512-YpO13776h3e6Wy8dl2J8T9Qwlvopr+b4trCEhHE+yek6yIqV8sx6g3KozdHMbXeBpjosbPi+Ii5Z7X9oXFHUKA==", "dev": true, "requires": { "core-js": "^2.5.7", @@ -734,37 +786,54 @@ } }, "@babel/template": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.0.0.tgz", - "integrity": "sha512-VLQZik/G5mjYJ6u19U3W2u7eM+rA/NGzH+GtHDFFkLTKLW66OasFrxZ/yK7hkyQcswrmvugFyZpDFRW0DjcjCw==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.2.2.tgz", + "integrity": "sha512-zRL0IMM02AUDwghf5LMSSDEz7sBCO2YnNmpg3uWTZj/v1rcG2BmQUvaGU8GhU8BvfMh1k2KIAYZ7Ji9KXPUg7g==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.0.0", - "@babel/types": "^7.0.0" + "@babel/parser": "^7.2.2", + "@babel/types": "^7.2.2" } }, "@babel/traverse": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.0.0.tgz", - "integrity": "sha512-ka/lwaonJZTlJyn97C4g5FYjPOx+Oxd3ab05hbDr1Mx9aP1FclJ+SUHyLx3Tx40sGmOVJApDxE6puJhd3ld2kw==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.2.3.tgz", + "integrity": "sha512-Z31oUD/fJvEWVR0lNZtfgvVt512ForCTNKYcJBGbPb1QZfve4WGH8Wsy7+Mev33/45fhP/hwQtvgusNdcCMgSw==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.0.0", - "@babel/helper-function-name": "^7.0.0", + "@babel/generator": "^7.2.2", + "@babel/helper-function-name": "^7.1.0", "@babel/helper-split-export-declaration": "^7.0.0", - "@babel/parser": "^7.0.0", - "@babel/types": "^7.0.0", - "debug": "^3.1.0", + "@babel/parser": "^7.2.3", + "@babel/types": "^7.2.2", + "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.10" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } } }, "@babel/types": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0.tgz", - "integrity": "sha512-5tPDap4bGKTLPtci2SUl/B7Gv8RnuJFuQoWx26RJobS0fFrz4reUA3JnwIM+HVHEmWE0C1mzKhDtTp8NsWY02Q==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.3.2.tgz", + "integrity": "sha512-3Y6H8xlUlpbGR+XvawiH0UXehqydTmNmEpozWcXymqwcrwYAl5KMvKtQ+TF6f6E08V6Jur7v/ykdDSF+WDEIXQ==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -773,56 +842,57 @@ } }, "@lerna/add": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/@lerna/add/-/add-3.4.1.tgz", - "integrity": "sha512-Vf54B42jlD6G52qnv/cAGH70cVQIa+LX//lfsbkxHvzkhIqBl5J4KsnTOPkA9uq3R+zP58ayicCHB9ReiEWGJg==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/add/-/add-3.11.0.tgz", + "integrity": "sha512-A2u889e+GeZzL28jCpcN53iHq2cPWVnuy5tv5nvG/MIg0PxoAQOUvphexKsIbqzVd9Damdmv5W0u9kS8y8TTow==", "dev": true, "requires": { - "@lerna/bootstrap": "^3.4.1", - "@lerna/command": "^3.3.0", - "@lerna/filter-options": "^3.3.2", - "@lerna/npm-conf": "^3.4.1", - "@lerna/validation-error": "^3.0.0", + "@lerna/bootstrap": "3.11.0", + "@lerna/command": "3.11.0", + "@lerna/filter-options": "3.11.0", + "@lerna/npm-conf": "3.7.0", + "@lerna/validation-error": "3.11.0", "dedent": "^0.7.0", - "npm-package-arg": "^6.0.0", + "npm-package-arg": "^6.1.0", "p-map": "^1.2.0", - "pacote": "^9.1.0", + "pacote": "^9.4.1", "semver": "^5.5.0" } }, "@lerna/batch-packages": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@lerna/batch-packages/-/batch-packages-3.1.2.tgz", - "integrity": "sha512-HAkpptrYeUVlBYbLScXgeCgk6BsNVXxDd53HVWgzzTWpXV4MHpbpeKrByyt7viXlNhW0w73jJbipb/QlFsHIhQ==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/batch-packages/-/batch-packages-3.11.0.tgz", + "integrity": "sha512-ETO3prVqDZs/cpZo00ij61JEZ8/ADJx1OG/d/KtTdHlyRfQsb09Xzf0w+boimqa8fIqhpM3o5FV9GKd6GQ3iFQ==", "dev": true, "requires": { - "@lerna/package-graph": "^3.1.2", - "@lerna/validation-error": "^3.0.0", + "@lerna/package-graph": "3.11.0", + "@lerna/validation-error": "3.11.0", "npmlog": "^4.1.2" } }, "@lerna/bootstrap": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/@lerna/bootstrap/-/bootstrap-3.4.1.tgz", - "integrity": "sha512-yZDJgNm/KDoRH2klzmQGmpWMg/XMzWgeWvauXkrfW/mj1wwmufOuh5pN4fBFxVmUUa/RFZdfMeaaJt3+W3PPBw==", - "dev": true, - "requires": { - "@lerna/batch-packages": "^3.1.2", - "@lerna/command": "^3.3.0", - "@lerna/filter-options": "^3.3.2", - "@lerna/has-npm-version": "^3.3.0", - "@lerna/npm-conf": "^3.4.1", - "@lerna/npm-install": "^3.3.0", - "@lerna/rimraf-dir": "^3.3.0", - "@lerna/run-lifecycle": "^3.4.1", - "@lerna/run-parallel-batches": "^3.0.0", - "@lerna/symlink-binary": "^3.3.0", - "@lerna/symlink-dependencies": "^3.3.0", - "@lerna/validation-error": "^3.0.0", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/bootstrap/-/bootstrap-3.11.0.tgz", + "integrity": "sha512-MqwviGJTy86joqSX2A3fmu2wXLBXc23tHJp5Xu4bVhynPegDnRrA3d9UI80UM3JcuYIQsxT4t2q2LNsZ4VdZKQ==", + "dev": true, + "requires": { + "@lerna/batch-packages": "3.11.0", + "@lerna/command": "3.11.0", + "@lerna/filter-options": "3.11.0", + "@lerna/has-npm-version": "3.10.0", + "@lerna/npm-install": "3.11.0", + "@lerna/package-graph": "3.11.0", + "@lerna/pulse-till-done": "3.11.0", + "@lerna/rimraf-dir": "3.11.0", + "@lerna/run-lifecycle": "3.11.0", + "@lerna/run-parallel-batches": "3.0.0", + "@lerna/symlink-binary": "3.11.0", + "@lerna/symlink-dependencies": "3.11.0", + "@lerna/validation-error": "3.11.0", "dedent": "^0.7.0", "get-port": "^3.2.0", "multimatch": "^2.1.0", - "npm-package-arg": "^6.0.0", + "npm-package-arg": "^6.1.0", "npmlog": "^4.1.2", "p-finally": "^1.0.0", "p-map": "^1.2.0", @@ -833,26 +903,26 @@ } }, "@lerna/changed": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/@lerna/changed/-/changed-3.4.1.tgz", - "integrity": "sha512-gT7fhl4zQWyGETDO4Yy5wsFnqNlBSsezncS1nkMW1uO6jwnolwYqcr1KbrMR8HdmsZBn/00Y0mRnbtbpPPey8w==", + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@lerna/changed/-/changed-3.11.1.tgz", + "integrity": "sha512-A21h3DvMjDwhksmCmTQ1+3KPHg7gHVHFs3zC5lR9W+whYlm0JI2Yp70vYnqMv2hPAcJx+2tlCrqJkzCFkNQdqg==", "dev": true, "requires": { - "@lerna/collect-updates": "^3.3.2", - "@lerna/command": "^3.3.0", - "@lerna/listable": "^3.0.0", - "@lerna/output": "^3.0.0", - "@lerna/version": "^3.4.1" + "@lerna/collect-updates": "3.11.0", + "@lerna/command": "3.11.0", + "@lerna/listable": "3.11.0", + "@lerna/output": "3.11.0", + "@lerna/version": "3.11.1" } }, "@lerna/check-working-tree": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@lerna/check-working-tree/-/check-working-tree-3.3.0.tgz", - "integrity": "sha512-oeEP1dNhiiKUaO0pmcIi73YXJpaD0n5JczNctvVNZ8fGZmrALZtEnmC28o6Z7JgQaqq5nd2kO7xbnjoitrC51g==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/check-working-tree/-/check-working-tree-3.11.0.tgz", + "integrity": "sha512-uWKKmX4BKdK57MyX3rGNHNz4JmFP3tHnaIDDVeuSlgK5KwncPFyRXi3E9H0eiq6DUvDDLtztNOfWeGP2IY656Q==", "dev": true, "requires": { - "@lerna/describe-ref": "^3.3.0", - "@lerna/validation-error": "^3.0.0" + "@lerna/describe-ref": "3.11.0", + "@lerna/validation-error": "3.11.0" } }, "@lerna/child-process": { @@ -916,32 +986,39 @@ } }, "@lerna/clean": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/@lerna/clean/-/clean-3.3.2.tgz", - "integrity": "sha512-mvqusgSp2ou5SGqQgTEoTvGJpGfH4+L6XSeN+Ims+eNFGXuMazmKCf+rz2PZBMFufaHJ/Os+JF0vPCcWI1Fzqg==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/clean/-/clean-3.11.0.tgz", + "integrity": "sha512-sHyMYv56MIVMH79+5vcxHVdgmd8BcsihI+RL2byW+PeoNlyDeGMjTRmnzLmbSD7dkinHGoa5cghlXy9GGIqpRw==", "dev": true, "requires": { - "@lerna/command": "^3.3.0", - "@lerna/filter-options": "^3.3.2", - "@lerna/prompt": "^3.3.1", - "@lerna/rimraf-dir": "^3.3.0", + "@lerna/command": "3.11.0", + "@lerna/filter-options": "3.11.0", + "@lerna/prompt": "3.11.0", + "@lerna/pulse-till-done": "3.11.0", + "@lerna/rimraf-dir": "3.11.0", "p-map": "^1.2.0", "p-map-series": "^1.0.0", "p-waterfall": "^1.0.0" } }, "@lerna/cli": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@lerna/cli/-/cli-3.2.0.tgz", - "integrity": "sha512-JdbLyTxHqxUlrkI+Ke+ltXbtyA+MPu9zR6kg/n8Fl6uaez/2fZWtReXzYi8MgLxfUFa7+1OHWJv4eAMZlByJ+Q==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/cli/-/cli-3.11.0.tgz", + "integrity": "sha512-dn2m2PgUxcb2NyTvwfYOFZf8yN5CMf1uKxht3ajQYdDjRgFi5pUQt/DmdguOZ3CMJkENa0i3yPOmrxGPXLD2aw==", "dev": true, "requires": { - "@lerna/global-options": "^3.1.3", + "@lerna/global-options": "3.10.6", "dedent": "^0.7.0", "npmlog": "^4.1.2", "yargs": "^12.0.1" }, "dependencies": { + "camelcase": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", + "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", + "dev": true + }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -955,23 +1032,14 @@ "which": "^1.2.9" } }, - "decamelize": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-2.0.0.tgz", - "integrity": "sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==", - "dev": true, - "requires": { - "xregexp": "4.0.0" - } - }, "execa": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", - "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", "dev": true, "requires": { "cross-spawn": "^6.0.0", - "get-stream": "^3.0.0", + "get-stream": "^4.0.0", "is-stream": "^1.1.0", "npm-run-path": "^2.0.0", "p-finally": "^1.0.0", @@ -988,6 +1056,15 @@ "locate-path": "^3.0.0" } }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, "invert-kv": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", @@ -1014,31 +1091,37 @@ } }, "mem": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.0.0.tgz", - "integrity": "sha512-WQxG/5xYc3tMbYLXoXPm81ET2WDULiU5FxbuIoNbJqLOOI8zehXFdZuiUEgfdrU2mVB1pxBZUGlYORSrpuJreA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.1.0.tgz", + "integrity": "sha512-I5u6Q1x7wxO0kdOpYBB28xueHADYps5uty/zg936CiG8NTe5sJL8EjrCuLneuDW3PlMdZBGDIn8BirEVdovZvg==", "dev": true, "requires": { "map-age-cleaner": "^0.1.1", "mimic-fn": "^1.0.0", - "p-is-promise": "^1.1.0" + "p-is-promise": "^2.0.0" } }, "os-locale": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.0.1.tgz", - "integrity": "sha512-7g5e7dmXPtzcP4bgsZ8ixDVqA7oWYuEz4lOSujeWyliPai4gfVDiFIcwBg3aGCPnmSGfzOKTK3ccPn0CKv3DBw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", "dev": true, "requires": { - "execa": "^0.10.0", + "execa": "^1.0.0", "lcid": "^2.0.0", "mem": "^4.0.0" } }, - "p-limit": { + "p-is-promise": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", - "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.0.0.tgz", + "integrity": "sha512-pzQPhYMCAgLAKPWD2jC3Se9fEfrD9npNos0y150EeqZll7akhEgGhTW/slB6lHku8AvYGiJ+YJ5hfHKePPgFWg==", + "dev": true + }, + "p-limit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", + "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -1059,14 +1142,24 @@ "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", "dev": true }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "yargs": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.2.tgz", - "integrity": "sha512-e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ==", + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", "dev": true, "requires": { "cliui": "^4.0.0", - "decamelize": "^2.0.0", + "decamelize": "^1.2.0", "find-up": "^3.0.0", "get-caller-file": "^1.0.1", "os-locale": "^3.0.0", @@ -1076,44 +1169,45 @@ "string-width": "^2.0.0", "which-module": "^2.0.0", "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^10.1.0" + "yargs-parser": "^11.1.1" } }, "yargs-parser": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", - "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", "dev": true, "requires": { - "camelcase": "^4.1.0" + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" } } } }, "@lerna/collect-updates": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/@lerna/collect-updates/-/collect-updates-3.3.2.tgz", - "integrity": "sha512-9WyBJI2S5sYgEZEScu525Lbi6nknNrdBKop35sCDIC9y6AIGvH6Dr5tkTd+Kg3n1dE+kHwW/xjERkx3+h7th3w==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/collect-updates/-/collect-updates-3.11.0.tgz", + "integrity": "sha512-O0Y18OC2P6j9/RFq+u5Kdq7YxsDd+up3ZRoW6+i0XHWktqxXA9P4JBQppkpYtJVK2yH8QyOzuVLQgtL0xtHdYA==", "dev": true, "requires": { - "@lerna/child-process": "^3.3.0", - "@lerna/describe-ref": "^3.3.0", + "@lerna/child-process": "3.3.0", + "@lerna/describe-ref": "3.11.0", "minimatch": "^3.0.4", "npmlog": "^4.1.2", "slash": "^1.0.0" } }, "@lerna/command": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@lerna/command/-/command-3.3.0.tgz", - "integrity": "sha512-NTOkLEKlWcBLHSvUr9tzVpV7RJ4GROLeOuZ6RfztGOW/31JPSwVVBD2kPifEXNZunldOx5GVWukR+7+NpAWhsg==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/command/-/command-3.11.0.tgz", + "integrity": "sha512-N+Z5kauVHSb2VhSIfQexG2VlCAAQ9xYKwVTxYh0JFOFUnZ/QPcoqx4VjynDXASFXXDgcXs4FLaGsJxq83Mf5Zg==", "dev": true, "requires": { - "@lerna/child-process": "^3.3.0", - "@lerna/package-graph": "^3.1.2", - "@lerna/project": "^3.0.0", - "@lerna/validation-error": "^3.0.0", - "@lerna/write-log-file": "^3.0.0", + "@lerna/child-process": "3.3.0", + "@lerna/package-graph": "3.11.0", + "@lerna/project": "3.11.0", + "@lerna/validation-error": "3.11.0", + "@lerna/write-log-file": "3.11.0", "dedent": "^0.7.0", "execa": "^1.0.0", "is-ci": "^1.0.10", @@ -1171,19 +1265,20 @@ } }, "@lerna/conventional-commits": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/@lerna/conventional-commits/-/conventional-commits-3.4.1.tgz", - "integrity": "sha512-3NETrA58aUkaEW3RdwdJ766Bg9NVpLzb26mtdlsJQcvB5sQBWH5dJSHIVQH1QsGloBeH2pE/mDUEVY8ZJXuR4w==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/conventional-commits/-/conventional-commits-3.11.0.tgz", + "integrity": "sha512-ix1Ki5NiZdk2eMlCWNgLchWPKQTgkJdLeNjneep6OCF3ydSINizReGbFvCftRivun641cOHWswgWMsIxbqhMQw==", "dev": true, "requires": { - "@lerna/validation-error": "^3.0.0", - "conventional-changelog-angular": "^5.0.1", - "conventional-changelog-core": "^3.1.0", - "conventional-recommended-bump": "^4.0.1", + "@lerna/validation-error": "3.11.0", + "conventional-changelog-angular": "^5.0.2", + "conventional-changelog-core": "^3.1.5", + "conventional-recommended-bump": "^4.0.4", "fs-extra": "^7.0.0", "get-stream": "^4.0.0", - "npm-package-arg": "^6.0.0", + "npm-package-arg": "^6.1.0", "npmlog": "^4.1.2", + "pify": "^3.0.0", "semver": "^5.5.0" }, "dependencies": { @@ -1196,6 +1291,12 @@ "pump": "^3.0.0" } }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -1209,21 +1310,23 @@ } }, "@lerna/create": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/@lerna/create/-/create-3.4.1.tgz", - "integrity": "sha512-l+4t2SRO5nvW0MNYY+EWxbaMHsAN8bkWH3nyt7EzhBjs4+TlRAJRIEqd8o9NWznheE3pzwczFz1Qfl3BWbyM5A==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/create/-/create-3.11.0.tgz", + "integrity": "sha512-1izS82QML+H/itwEu1GPrcoXyugFaP9z9r6KuIQRQq8RtmNCGEmK85aiOw6mukyRcRziq2akALgFDyrundznPQ==", "dev": true, "requires": { - "@lerna/child-process": "^3.3.0", - "@lerna/command": "^3.3.0", - "@lerna/npm-conf": "^3.4.1", - "@lerna/validation-error": "^3.0.0", - "camelcase": "^4.1.0", + "@lerna/child-process": "3.3.0", + "@lerna/command": "3.11.0", + "@lerna/npm-conf": "3.7.0", + "@lerna/validation-error": "3.11.0", + "camelcase": "^5.0.0", "dedent": "^0.7.0", "fs-extra": "^7.0.0", "globby": "^8.0.1", "init-package-json": "^1.10.3", - "npm-package-arg": "^6.0.0", + "npm-package-arg": "^6.1.0", + "p-reduce": "^1.0.0", + "pacote": "^9.4.1", "pify": "^3.0.0", "semver": "^5.5.0", "slash": "^1.0.0", @@ -1232,20 +1335,11 @@ "whatwg-url": "^7.0.0" }, "dependencies": { - "globby": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.1.tgz", - "integrity": "sha512-oMrYrJERnKBLXNLVTqhm3vPEdJ/b2ZE28xN4YARiix1NOIOBPEpOUnm844K1iu/BkphCaf2WNFwMszv8Soi1pw==", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "dir-glob": "^2.0.0", - "fast-glob": "^2.0.2", - "glob": "^7.1.2", - "ignore": "^3.3.5", - "pify": "^3.0.0", - "slash": "^1.0.0" - } + "camelcase": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", + "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", + "dev": true }, "pify": { "version": "3.0.0", @@ -1267,9 +1361,9 @@ } }, "@lerna/create-symlink": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@lerna/create-symlink/-/create-symlink-3.3.0.tgz", - "integrity": "sha512-0lb88Nnq1c/GG+fwybuReOnw3+ah4dB81PuWwWwuqUNPE0n50qUf/M/7FfSb5JEh/93fcdbZI0La8t3iysNW1w==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/create-symlink/-/create-symlink-3.11.0.tgz", + "integrity": "sha512-UDR32uos8FIEc1keMKxXj5goZAHpCbpUd4u/btHXymUL9WqIym3cgz2iMr3ZNdZtjdMyUoHup5Dp0zjSgKCaEA==", "dev": true, "requires": { "cmd-shim": "^2.0.2", @@ -1278,155 +1372,219 @@ } }, "@lerna/describe-ref": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@lerna/describe-ref/-/describe-ref-3.3.0.tgz", - "integrity": "sha512-4t7M4OupnYMSPNLrLUau8qkS+dgLEi4w+DkRkV0+A+KNYga1W0jVgNLPIIsxta7OHfodPkCNAqZCzNCw/dmAwA==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/describe-ref/-/describe-ref-3.11.0.tgz", + "integrity": "sha512-lX/NVMqeODg4q/igN06L/KjtVUpW1oawh6IgOINy2oqm4RUR+1yDpsdVu3JyZZ4nHB572mJfbW56dl8qoxEVvQ==", "dev": true, "requires": { - "@lerna/child-process": "^3.3.0", + "@lerna/child-process": "3.3.0", "npmlog": "^4.1.2" } }, "@lerna/diff": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@lerna/diff/-/diff-3.3.0.tgz", - "integrity": "sha512-sIoMjsm3NVxvmt6ofx8Uu/2fxgldQqLl0zmC9X1xW00j831o5hBffx1EoKj9CnmaEvoSP6j/KFjxy2RWjebCIg==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/diff/-/diff-3.11.0.tgz", + "integrity": "sha512-r3WASQix31ApA0tlkZejXhS8Z3SEg6Jw9YnKDt9V6wLjEUXGLauUDMrgx1YWu3cs9KB8/hqheRyRI7XAXGJS1w==", "dev": true, "requires": { - "@lerna/child-process": "^3.3.0", - "@lerna/command": "^3.3.0", - "@lerna/validation-error": "^3.0.0", + "@lerna/child-process": "3.3.0", + "@lerna/command": "3.11.0", + "@lerna/validation-error": "3.11.0", "npmlog": "^4.1.2" } }, "@lerna/exec": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/@lerna/exec/-/exec-3.3.2.tgz", - "integrity": "sha512-mN6vGxNir7JOGvWLwKr3DW3LNy1ecCo2ziZj5rO9Mw5Rew3carUu1XLmhF/4judtsvXViUY+rvGIcqHe0vvb+w==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/exec/-/exec-3.11.0.tgz", + "integrity": "sha512-oIkI+Hj74kpsnHhw0qJj12H4XMPSlDbBsshLWY+f3BiwKhn6wkXoQZ1FC8/OVNHM67GtSRv4bkcOaM4ucHm9Hw==", "dev": true, "requires": { - "@lerna/batch-packages": "^3.1.2", - "@lerna/child-process": "^3.3.0", - "@lerna/command": "^3.3.0", - "@lerna/filter-options": "^3.3.2", - "@lerna/run-parallel-batches": "^3.0.0", - "@lerna/validation-error": "^3.0.0" + "@lerna/batch-packages": "3.11.0", + "@lerna/child-process": "3.3.0", + "@lerna/command": "3.11.0", + "@lerna/filter-options": "3.11.0", + "@lerna/run-parallel-batches": "3.0.0", + "@lerna/validation-error": "3.11.0" } }, "@lerna/filter-options": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/@lerna/filter-options/-/filter-options-3.3.2.tgz", - "integrity": "sha512-0WHqdDgAnt5WKoByi1q+lFw8HWt5tEKP2DnLlGqWv3YFwVF5DsPRlO7xbzjY9sJgvyJtZcnkMtccdBPFhGGyIQ==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/filter-options/-/filter-options-3.11.0.tgz", + "integrity": "sha512-z0krgC/YBqz7i6MGHBsPLvsQ++XEpPdGnIkSpcN0Cjp5J67K9vb5gJ2hWp1c1bitNh3xiwZ69voGqN+DYk1mUg==", "dev": true, "requires": { - "@lerna/collect-updates": "^3.3.2", - "@lerna/filter-packages": "^3.0.0", + "@lerna/collect-updates": "3.11.0", + "@lerna/filter-packages": "3.11.0", "dedent": "^0.7.0" } }, "@lerna/filter-packages": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/filter-packages/-/filter-packages-3.0.0.tgz", - "integrity": "sha512-zwbY1J4uRjWRZ/FgYbtVkq7I3Nduwsg2V2HwLKSzwV2vPglfGqgovYOVkND6/xqe2BHwDX4IyA2+e7OJmLaLSA==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/filter-packages/-/filter-packages-3.11.0.tgz", + "integrity": "sha512-bnukkW1M0uMKWqM/m/IHou2PKRyk4fDAksAj3diHc1UVQkH2j8hXOfLl9+CgHA/cnTrf6/LARg8hKujqduqHyA==", "dev": true, "requires": { - "@lerna/validation-error": "^3.0.0", + "@lerna/validation-error": "3.11.0", "multimatch": "^2.1.0", "npmlog": "^4.1.2" } }, "@lerna/get-npm-exec-opts": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/get-npm-exec-opts/-/get-npm-exec-opts-3.0.0.tgz", - "integrity": "sha512-arcYUm+4xS8J3Palhl+5rRJXnZnFHsLFKHBxznkPIxjwGQeAEw7df38uHdVjEQ+HNeFmHnBgSqfbxl1VIw5DHg==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/get-npm-exec-opts/-/get-npm-exec-opts-3.11.0.tgz", + "integrity": "sha512-EDxsbuq2AbB3LWwH/4SOcn4gWOnoIYrSHfITWo7xz/SbEKeHtiva99l424ZRWUJqLPGIpQiMTlmOET2ZEI8WZg==", + "dev": true, + "requires": { + "npmlog": "^4.1.2" + } + }, + "@lerna/get-packed": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@lerna/get-packed/-/get-packed-3.7.0.tgz", + "integrity": "sha512-yuFtjsUZIHjeIvIYQ/QuytC+FQcHwo3peB+yGBST2uWCLUCR5rx6knoQcPzbxdFDCuUb5IFccFGd3B1fHFg3RQ==", + "dev": true, + "requires": { + "fs-extra": "^7.0.0", + "ssri": "^6.0.1", + "tar": "^4.4.8" + }, + "dependencies": { + "chownr": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", + "dev": true + }, + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "tar": { + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz", + "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", + "dev": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "dev": true + } + } + }, + "@lerna/github-client": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/github-client/-/github-client-3.11.0.tgz", + "integrity": "sha512-yPMBhzShuth3uJo0kKu84RvgjSZgOYNT8fKfhZmzTeVGuPbYBKlK+UQ6jjpb6E9WW2BVdiUCrFhqIsbK5Lqe7A==", "dev": true, "requires": { + "@lerna/child-process": "3.3.0", + "@octokit/plugin-enterprise-rest": "^2.1.0", + "@octokit/rest": "^16.15.0", + "git-url-parse": "^11.1.2", "npmlog": "^4.1.2" } }, "@lerna/global-options": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@lerna/global-options/-/global-options-3.1.3.tgz", - "integrity": "sha512-LVeZU/Zgc0XkHdGMRYn+EmHfDmmYNwYRv3ta59iCVFXLVp7FRFWF7oB1ss/WRa9x/pYU0o6L8as/5DomLUGASA==", + "version": "3.10.6", + "resolved": "https://registry.npmjs.org/@lerna/global-options/-/global-options-3.10.6.tgz", + "integrity": "sha512-k5Xkq1M/uREFC2R9uwN5gcvIgjj4iOXo0YyeEXCMWBiW3j2GL9xN4d1MmAIcrYlAzVYh6kLlWaFWl/rNIneHIw==", "dev": true }, "@lerna/has-npm-version": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@lerna/has-npm-version/-/has-npm-version-3.3.0.tgz", - "integrity": "sha512-GX7omRep1eBRZHgjZLRw3MpBJSdA5gPZFz95P7rxhpvsiG384Tdrr/cKFMhm0A09yq27Tk/nuYTaZIj7HsVE6g==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@lerna/has-npm-version/-/has-npm-version-3.10.0.tgz", + "integrity": "sha512-N4RRYxGeivuaKgPDzrhkQOQs1Sg4tOnxnEe3akfqu1wDA4Ng5V6Y2uW3DbkAjFL3aNJhWF5Vbf7sBsGtfgDQ8w==", "dev": true, "requires": { - "@lerna/child-process": "^3.3.0", + "@lerna/child-process": "3.3.0", "semver": "^5.5.0" } }, "@lerna/import": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@lerna/import/-/import-3.3.1.tgz", - "integrity": "sha512-2OzTQDkYKbBPpyP2iOI1sWfcvMjNLjjHjmREq/uOWJaSIk5J3Ukt71OPpcOHh4V2CBOlXidCcO+Hyb4FVIy8fw==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/import/-/import-3.11.0.tgz", + "integrity": "sha512-WgF0We+4k/MrC1vetT8pt3/SSJPMvXhyPYmL2W9rcvch3zV0IgLyso4tEs8gNbwZorDVEG1KcM+x8TG4v1nV5Q==", "dev": true, "requires": { - "@lerna/child-process": "^3.3.0", - "@lerna/command": "^3.3.0", - "@lerna/prompt": "^3.3.1", - "@lerna/validation-error": "^3.0.0", + "@lerna/child-process": "3.3.0", + "@lerna/command": "3.11.0", + "@lerna/prompt": "3.11.0", + "@lerna/pulse-till-done": "3.11.0", + "@lerna/validation-error": "3.11.0", "dedent": "^0.7.0", "fs-extra": "^7.0.0", "p-map-series": "^1.0.0" } }, "@lerna/init": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@lerna/init/-/init-3.3.0.tgz", - "integrity": "sha512-HvgRLkIG6nDIeAO6ix5sUVIVV+W9UMk2rSSmFT66CDOefRi7S028amiyYnFUK1QkIAaUbVUyOnYaErtbJwICuw==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/init/-/init-3.11.0.tgz", + "integrity": "sha512-JZC5jpCVJgK34grye52kGWjrYCyh4LB8c0WBLaS8MOUt6rxTtPqubwvCDKPOF2H0Se6awsgEfX4wWNuqiQVpRQ==", "dev": true, "requires": { - "@lerna/child-process": "^3.3.0", - "@lerna/command": "^3.3.0", + "@lerna/child-process": "3.3.0", + "@lerna/command": "3.11.0", "fs-extra": "^7.0.0", "p-map": "^1.2.0", "write-json-file": "^2.3.0" } }, "@lerna/link": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@lerna/link/-/link-3.3.0.tgz", - "integrity": "sha512-8CeXzGL7okrsVXsy2sHXI2KuBaczw3cblAnA2+FJPUqSKMPNbUTRzeU3bOlCjYtK0LbxC4ngENJTL3jJ8RaYQQ==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/link/-/link-3.11.0.tgz", + "integrity": "sha512-QN+kxRWb6P9jrKpE2t6K9sGnFpqy1KOEjf68NpGhmp+J9Yt6Kvz9kG43CWoqg4Zyqqgqgn3NVV2Z7zSDNhdH0g==", "dev": true, "requires": { - "@lerna/command": "^3.3.0", - "@lerna/package-graph": "^3.1.2", - "@lerna/symlink-dependencies": "^3.3.0", + "@lerna/command": "3.11.0", + "@lerna/package-graph": "3.11.0", + "@lerna/symlink-dependencies": "3.11.0", "p-map": "^1.2.0", "slash": "^1.0.0" } }, "@lerna/list": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/@lerna/list/-/list-3.3.2.tgz", - "integrity": "sha512-XXEVy7w+i/xx8NeJmGirw4upEoEF9OfD6XPLjISNQc24VgQV+frXdVJ02QcP7Y/PkY1rdIVrOjvo3ipKVLUxaQ==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/list/-/list-3.11.0.tgz", + "integrity": "sha512-hBAwZzEzF1LQOOB2/5vQkal/nSriuJbLY39BitIGkUxifsmu7JK0k3LYrwe1sxXv5SMf2HDaTLr+Z23mUslhaQ==", "dev": true, "requires": { - "@lerna/command": "^3.3.0", - "@lerna/filter-options": "^3.3.2", - "@lerna/listable": "^3.0.0", - "@lerna/output": "^3.0.0" + "@lerna/command": "3.11.0", + "@lerna/filter-options": "3.11.0", + "@lerna/listable": "3.11.0", + "@lerna/output": "3.11.0" } }, "@lerna/listable": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/listable/-/listable-3.0.0.tgz", - "integrity": "sha512-HX/9hyx1HLg2kpiKXIUc1EimlkK1T58aKQ7ovO7rQdTx9ForpefoMzyLnHE1n4XrUtEszcSWJIICJ/F898M6Ag==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/listable/-/listable-3.11.0.tgz", + "integrity": "sha512-nCrtGSS3YiAlh5dU5mmTAU9aLRlmIUn2FnahqsksN2uQ5O4o+614tneDuO298/eWLZo00eGw69EFngaQEl8quw==", "dev": true, "requires": { + "@lerna/batch-packages": "3.11.0", "chalk": "^2.3.1", "columnify": "^1.5.4" } }, "@lerna/log-packed": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@lerna/log-packed/-/log-packed-3.0.4.tgz", - "integrity": "sha512-vVQHgMagE2wnbxhNY9nFkdu+Cx2TsyWalkJfkxbNzmo6gOCrDsxCBDj9vTEV8Q+4aWx0C0Bsc0sB2Eb8y/+ofA==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/log-packed/-/log-packed-3.11.0.tgz", + "integrity": "sha512-TH//81TzSTMuNzJIQE7zqu+ymI5rH25jdEdmbYEWmaJ+T42GMQXKxP8cj2m+fWRaDML8ta0uzBOm5PKHdgoFYQ==", "dev": true, "requires": { "byte-size": "^4.0.3", @@ -1436,9 +1594,9 @@ } }, "@lerna/npm-conf": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/@lerna/npm-conf/-/npm-conf-3.4.1.tgz", - "integrity": "sha512-i9G6DnbCqiAqxKx2rSXej/n14qxlV/XOebL6QZonxJKzNTB+Q2wglnhTXmfZXTPJfoqimLaY4NfAEtbOXRWOXQ==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@lerna/npm-conf/-/npm-conf-3.7.0.tgz", + "integrity": "sha512-+WSMDfPKcKzMfqq283ydz9RRpOU6p9wfx0wy4hVSUY/6YUpsyuk8SShjcRtY8zTM5AOrxvFBuuV90H4YpZ5+Ng==", "dev": true, "requires": { "config-chain": "^1.1.11", @@ -1454,95 +1612,186 @@ } }, "@lerna/npm-dist-tag": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@lerna/npm-dist-tag/-/npm-dist-tag-3.3.0.tgz", - "integrity": "sha512-EtZJXzh3w5tqXEev+EBBPrWKWWn0WgJfxm4FihfS9VgyaAW8udIVZHGkIQ3f+tBtupcAzA9Q8cQNUkGF2efwmA==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/npm-dist-tag/-/npm-dist-tag-3.11.0.tgz", + "integrity": "sha512-WqZcyDb+wiqAKRFcYEK6R8AQfspyro85zGGHyjYw6ZPNgJX3qhwtQ+MidDmOesi2p5/0GfeVSWega+W7fPzVpg==", "dev": true, "requires": { - "@lerna/child-process": "^3.3.0", - "@lerna/get-npm-exec-opts": "^3.0.0", + "figgy-pudding": "^3.5.1", + "npm-package-arg": "^6.1.0", + "npm-registry-fetch": "^3.9.0", "npmlog": "^4.1.2" } }, "@lerna/npm-install": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@lerna/npm-install/-/npm-install-3.3.0.tgz", - "integrity": "sha512-WoVvKdS8ltROTGSNQwo6NDq0YKnjwhvTG4li1okcN/eHKOS3tL9bxbgPx7No0wOq5DKBpdeS9KhAfee6LFAZ5g==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/npm-install/-/npm-install-3.11.0.tgz", + "integrity": "sha512-iNKEgFvFHMmBqn9AnFye2rv7CdUBlYciwWSTNtpfVqtOnoL/lg+4A774oL4PDoxTCGmougztyxMkqLVSBYXTpw==", "dev": true, "requires": { - "@lerna/child-process": "^3.3.0", - "@lerna/get-npm-exec-opts": "^3.0.0", + "@lerna/child-process": "3.3.0", + "@lerna/get-npm-exec-opts": "3.11.0", "fs-extra": "^7.0.0", - "npm-package-arg": "^6.0.0", + "npm-package-arg": "^6.1.0", "npmlog": "^4.1.2", "signal-exit": "^3.0.2", "write-pkg": "^3.1.0" } }, "@lerna/npm-publish": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@lerna/npm-publish/-/npm-publish-3.3.1.tgz", - "integrity": "sha512-bVTlWIcBL6Zpyzqvr9C7rxXYcoPw+l7IPz5eqQDNREj1R39Wj18OWB2KTJq8l7LIX7Wf4C2A1uT5hJaEf9BuvA==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/npm-publish/-/npm-publish-3.11.0.tgz", + "integrity": "sha512-wgbb55gUXRlP8uTe60oW6c06ZhquaJu9xbi2vWNpb5Fmjh/KbZ2iNm9Kj2ciZlvb8D+k4Oc3qV7slBGxyMm8wg==", "dev": true, "requires": { - "@lerna/child-process": "^3.3.0", - "@lerna/get-npm-exec-opts": "^3.0.0", - "@lerna/has-npm-version": "^3.3.0", - "@lerna/log-packed": "^3.0.4", + "@lerna/run-lifecycle": "3.11.0", + "figgy-pudding": "^3.5.1", "fs-extra": "^7.0.0", + "libnpmpublish": "^1.1.1", "npmlog": "^4.1.2", - "p-map": "^1.2.0" + "pify": "^3.0.0", + "read-package-json": "^2.0.13" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } } }, "@lerna/npm-run-script": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@lerna/npm-run-script/-/npm-run-script-3.3.0.tgz", - "integrity": "sha512-YqDguWZzp4jIomaE4aWMUP7MIAJAFvRAf6ziQLpqwoQskfWLqK5mW0CcszT1oLjhfb3cY3MMfSTFaqwbdKmICg==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/npm-run-script/-/npm-run-script-3.11.0.tgz", + "integrity": "sha512-cLnTMrRQlK/N5bCr6joOFMBfRyW2EbMdk3imtjHk0LwZxsvQx3naAPUB/2RgNfC8fGf/yHF/0bmBrpb5sa2IlA==", "dev": true, "requires": { - "@lerna/child-process": "^3.3.0", - "@lerna/get-npm-exec-opts": "^3.0.0", + "@lerna/child-process": "3.3.0", + "@lerna/get-npm-exec-opts": "3.11.0", "npmlog": "^4.1.2" } }, "@lerna/output": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/output/-/output-3.0.0.tgz", - "integrity": "sha512-EFxnSbO0zDEVKkTKpoCUAFcZjc3gn3DwPlyTDxbeqPU7neCfxP4rA4+0a6pcOfTlRS5kLBRMx79F2TRCaMM3DA==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/output/-/output-3.11.0.tgz", + "integrity": "sha512-xHYGcEaZZ4cR0Jw368QgUgFvV27a6ZO5360BMNGNsjCjuY0aOPQC5+lBhgfydJtJteKjDna853PSjBK3uMhEjw==", "dev": true, "requires": { "npmlog": "^4.1.2" } }, + "@lerna/pack-directory": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/pack-directory/-/pack-directory-3.11.0.tgz", + "integrity": "sha512-bgA3TxZx5AyZeqUadSPspktdecW7nIpg/ODq0o0gKFr7j+DC9Fqu8vQa2xmFSKsXDtOYkCV0jox6Ox9XSFSM3A==", + "dev": true, + "requires": { + "@lerna/get-packed": "3.7.0", + "@lerna/package": "3.11.0", + "@lerna/run-lifecycle": "3.11.0", + "figgy-pudding": "^3.5.1", + "npm-packlist": "^1.1.12", + "npmlog": "^4.1.2", + "tar": "^4.4.8", + "temp-write": "^3.4.0" + }, + "dependencies": { + "chownr": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", + "dev": true + }, + "tar": { + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz", + "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", + "dev": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "dev": true + } + } + }, "@lerna/package": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/package/-/package-3.0.0.tgz", - "integrity": "sha512-djzEJxzn212wS8d9znBnlXkeRlPL7GqeAYBykAmsuq51YGvaQK67Umh5ejdO0uxexF/4r7yRwgrlRHpQs8Rfqg==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/package/-/package-3.11.0.tgz", + "integrity": "sha512-hMzBhFEubhg+Tis5C8skwIfgOk+GTl0qudvzfPU9gQqLV8u4/Hs6mka6N0rKgbUb4VFVc5MJVe1eZ6Rv+kJAWw==", "dev": true, "requires": { - "npm-package-arg": "^6.0.0", + "load-json-file": "^4.0.0", + "npm-package-arg": "^6.1.0", "write-pkg": "^3.1.0" + }, + "dependencies": { + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } } }, "@lerna/package-graph": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@lerna/package-graph/-/package-graph-3.1.2.tgz", - "integrity": "sha512-9wIWb49I1IJmyjPdEVZQ13IAi9biGfH/OZHOC04U2zXGA0GLiY+B3CAx6FQvqkZ8xEGfqzmXnv3LvZ0bQfc1aQ==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/package-graph/-/package-graph-3.11.0.tgz", + "integrity": "sha512-ICYiOZvCfcmeH1qfzOkFYh0t0QA56OddQfI3ydxCiWi5G+UupJXnCIWSTh3edTAtw/kyxhCOWny/PJsG4CQfjA==", "dev": true, "requires": { - "@lerna/validation-error": "^3.0.0", - "npm-package-arg": "^6.0.0", + "@lerna/validation-error": "3.11.0", + "npm-package-arg": "^6.1.0", "semver": "^5.5.0" } }, "@lerna/project": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/project/-/project-3.0.0.tgz", - "integrity": "sha512-XhDFVfqj79jG2Speggd15RpYaE8uiR25UKcQBDmumbmqvTS7xf2cvl2pq2UTvDafaJ0YwFF3xkxQZeZnFMwdkw==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/project/-/project-3.11.0.tgz", + "integrity": "sha512-j3DGds+q/q2YNpoBImaEsMpkWgu5gP0IGKz1o1Ju39NZKrTPza+ARIzEByL4Jqu87tcoOj7RbZzhhrBP8JBbTg==", "dev": true, "requires": { - "@lerna/package": "^3.0.0", - "@lerna/validation-error": "^3.0.0", + "@lerna/package": "3.11.0", + "@lerna/validation-error": "3.11.0", "cosmiconfig": "^5.0.2", "dedent": "^0.7.0", "dot-prop": "^4.2.0", @@ -1565,21 +1814,6 @@ "path-dirname": "^1.0.0" } }, - "globby": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.1.tgz", - "integrity": "sha512-oMrYrJERnKBLXNLVTqhm3vPEdJ/b2ZE28xN4YARiix1NOIOBPEpOUnm844K1iu/BkphCaf2WNFwMszv8Soi1pw==", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "dir-glob": "^2.0.0", - "fast-glob": "^2.0.2", - "glob": "^7.1.2", - "ignore": "^3.3.5", - "pify": "^3.0.0", - "slash": "^1.0.0" - } - }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -1638,267 +1872,183 @@ } }, "@lerna/prompt": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@lerna/prompt/-/prompt-3.3.1.tgz", - "integrity": "sha512-eJhofrUCUaItMIH6et8kI7YqHfhjWqGZoTsE+40NRCfAraOMWx+pDzfRfeoAl3qeRAH2HhNj1bkYn70FbUOxuQ==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/prompt/-/prompt-3.11.0.tgz", + "integrity": "sha512-SB/wvyDPQASze9txd+8/t24p6GiJuhhL30zxuRwvVwER5lIJR7kaXy1KhQ7kUAKPlNTVfCBm3GXReIMl4jhGhw==", "dev": true, "requires": { "inquirer": "^6.2.0", "npmlog": "^4.1.2" - }, - "dependencies": { - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, - "external-editor": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", - "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", - "dev": true, - "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "inquirer": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.0.tgz", - "integrity": "sha512-QIEQG4YyQ2UYZGDC4srMZ7BjHOmNk1lR2JQj5UknBapklm6WHA+VVH7N+sUdX3A7NeCfGF8o4X1S3Ao7nAcIeg==", - "dev": true, - "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.0", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.0", - "figures": "^2.0.0", - "lodash": "^4.17.10", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rxjs": "^6.1.0", - "string-width": "^2.1.0", - "strip-ansi": "^4.0.0", - "through": "^2.3.6" - } - }, - "rxjs": { - "version": "6.3.3", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", - "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - } } }, "@lerna/publish": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/@lerna/publish/-/publish-3.4.3.tgz", - "integrity": "sha512-baeRL8xmOR25p86cAaS9mL0jdRzdv4dUo04PlK2Wes+YlL705F55cSXeC9npNie+9rGwFyLzCTQe18WdbZyLuw==", - "dev": true, - "requires": { - "@lerna/batch-packages": "^3.1.2", - "@lerna/check-working-tree": "^3.3.0", - "@lerna/child-process": "^3.3.0", - "@lerna/collect-updates": "^3.3.2", - "@lerna/command": "^3.3.0", - "@lerna/describe-ref": "^3.3.0", - "@lerna/get-npm-exec-opts": "^3.0.0", - "@lerna/npm-conf": "^3.4.1", - "@lerna/npm-dist-tag": "^3.3.0", - "@lerna/npm-publish": "^3.3.1", - "@lerna/output": "^3.0.0", - "@lerna/prompt": "^3.3.1", - "@lerna/run-lifecycle": "^3.4.1", - "@lerna/run-parallel-batches": "^3.0.0", - "@lerna/validation-error": "^3.0.0", - "@lerna/version": "^3.4.1", + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@lerna/publish/-/publish-3.11.1.tgz", + "integrity": "sha512-UOvmSivuqzWoiTqoYWk+liPDZvC6O7NrT8DwoG2peRvjIPs5RKYMubwXPOrBBVVE+yX/vR6V1Y3o6vf3av52dg==", + "dev": true, + "requires": { + "@lerna/batch-packages": "3.11.0", + "@lerna/check-working-tree": "3.11.0", + "@lerna/child-process": "3.3.0", + "@lerna/collect-updates": "3.11.0", + "@lerna/command": "3.11.0", + "@lerna/describe-ref": "3.11.0", + "@lerna/log-packed": "3.11.0", + "@lerna/npm-conf": "3.7.0", + "@lerna/npm-dist-tag": "3.11.0", + "@lerna/npm-publish": "3.11.0", + "@lerna/output": "3.11.0", + "@lerna/pack-directory": "3.11.0", + "@lerna/prompt": "3.11.0", + "@lerna/pulse-till-done": "3.11.0", + "@lerna/run-lifecycle": "3.11.0", + "@lerna/run-parallel-batches": "3.0.0", + "@lerna/validation-error": "3.11.0", + "@lerna/version": "3.11.1", + "figgy-pudding": "^3.5.1", "fs-extra": "^7.0.0", - "libnpmaccess": "^3.0.0", - "npm-package-arg": "^6.0.0", - "npm-registry-fetch": "^3.8.0", + "libnpmaccess": "^3.0.1", + "npm-package-arg": "^6.1.0", + "npm-registry-fetch": "^3.9.0", "npmlog": "^4.1.2", "p-finally": "^1.0.0", "p-map": "^1.2.0", "p-pipe": "^1.2.0", "p-reduce": "^1.0.0", + "pacote": "^9.4.1", "semver": "^5.5.0" } }, - "@lerna/resolve-symlink": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@lerna/resolve-symlink/-/resolve-symlink-3.3.0.tgz", - "integrity": "sha512-KmoPDcFJ2aOK2inYHbrsiO9SodedUj0L1JDvDgirVNIjMUaQe2Q6Vi4Gh+VCJcyB27JtfHioV9R2NxU72Pk2hg==", + "@lerna/pulse-till-done": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/pulse-till-done/-/pulse-till-done-3.11.0.tgz", + "integrity": "sha512-nMwBa6S4+VI/ketN92oj1xr8y74Fz4ul2R5jdbrRqLLEU/IMBWIqn6NRM2P+OQBoLpPZ2MdWENLJVFNN8X1Q+A==", "dev": true, "requires": { - "fs-extra": "^7.0.0", - "npmlog": "^4.1.2", - "read-cmd-shim": "^1.0.1" - } - }, - "@lerna/rimraf-dir": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@lerna/rimraf-dir/-/rimraf-dir-3.3.0.tgz", - "integrity": "sha512-vSqOcZ4kZduiSprbt+y40qziyN3VKYh+ygiCdnbBbsaxpdKB6CfrSMUtrLhVFrqUfBHIZRzHIzgjTdtQex1KLw==", - "dev": true, - "requires": { - "@lerna/child-process": "^3.3.0", - "npmlog": "^4.1.2", - "path-exists": "^3.0.0", - "rimraf": "^2.6.2" - } - }, - "@lerna/run": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/@lerna/run/-/run-3.3.2.tgz", - "integrity": "sha512-cruwRGZZWnQ5I0M+AqcoT3Xpq2wj3135iVw4n59/Op6dZu50sMFXZNLiTTTZ15k8rTKjydcccJMdPSpTHbH7/A==", - "dev": true, - "requires": { - "@lerna/batch-packages": "^3.1.2", - "@lerna/command": "^3.3.0", - "@lerna/filter-options": "^3.3.2", - "@lerna/npm-run-script": "^3.3.0", - "@lerna/output": "^3.0.0", - "@lerna/run-parallel-batches": "^3.0.0", - "@lerna/validation-error": "^3.0.0", - "p-map": "^1.2.0" - } - }, - "@lerna/run-lifecycle": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/@lerna/run-lifecycle/-/run-lifecycle-3.4.1.tgz", - "integrity": "sha512-N/hi2srM9A4BWEkXccP7vCEbf4MmIuALF00DTBMvc0A/ccItwUpl3XNuM7+ADDRK0mkwE3hDw89lJ3A7f8oUQw==", - "dev": true, - "requires": { - "@lerna/npm-conf": "^3.4.1", - "npm-lifecycle": "^2.0.0", "npmlog": "^4.1.2" } }, - "@lerna/run-parallel-batches": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/run-parallel-batches/-/run-parallel-batches-3.0.0.tgz", - "integrity": "sha512-Mj1ravlXF7AkkewKd9YFq9BtVrsStNrvVLedD/b2wIVbNqcxp8lS68vehXVOzoL/VWNEDotvqCQtyDBilCodGw==", - "dev": true, - "requires": { - "p-map": "^1.2.0", - "p-map-series": "^1.0.0" - } - }, - "@lerna/symlink-binary": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@lerna/symlink-binary/-/symlink-binary-3.3.0.tgz", - "integrity": "sha512-zRo6CimhvH/VJqCFl9T4IC6syjpWyQIxEfO2sBhrapEcfwjtwbhoGgKwucsvt4rIpFazCw63jQ/AXMT27KUIHg==", + "@lerna/resolve-symlink": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/resolve-symlink/-/resolve-symlink-3.11.0.tgz", + "integrity": "sha512-lDer8zPXS36iL4vJdZwOk6AnuUjDXswoTWdYkl+HdAKXp7cBlS+VeGmcFIJS4R3mSSZE20h1oEDuH8h8GGORIQ==", "dev": true, "requires": { - "@lerna/create-symlink": "^3.3.0", - "@lerna/package": "^3.0.0", "fs-extra": "^7.0.0", - "p-map": "^1.2.0", - "read-pkg": "^3.0.0" - }, - "dependencies": { - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - } - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dev": true, - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - }, - "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "dev": true, - "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - } + "npmlog": "^4.1.2", + "read-cmd-shim": "^1.0.1" + } + }, + "@lerna/rimraf-dir": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/rimraf-dir/-/rimraf-dir-3.11.0.tgz", + "integrity": "sha512-roy4lKel7BMNLfFvyzK0HI251mgI9EwbpOccR2Waz0V22d0gaqLKzfVrzovat9dVHXrKNxAhJ5iKkKeT93IunQ==", + "dev": true, + "requires": { + "@lerna/child-process": "3.3.0", + "npmlog": "^4.1.2", + "path-exists": "^3.0.0", + "rimraf": "^2.6.2" + } + }, + "@lerna/run": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/run/-/run-3.11.0.tgz", + "integrity": "sha512-8c2yzbKJFzgO6VTOftWmB0fOLTL7G1GFAG5UTVDSk95Z2Gnjof3I/Xkvtbzq8L+DIOLpr+Tpj3fRBjZd8rONlA==", + "dev": true, + "requires": { + "@lerna/batch-packages": "3.11.0", + "@lerna/command": "3.11.0", + "@lerna/filter-options": "3.11.0", + "@lerna/npm-run-script": "3.11.0", + "@lerna/output": "3.11.0", + "@lerna/run-parallel-batches": "3.0.0", + "@lerna/timer": "3.5.0", + "@lerna/validation-error": "3.11.0", + "p-map": "^1.2.0" + } + }, + "@lerna/run-lifecycle": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/run-lifecycle/-/run-lifecycle-3.11.0.tgz", + "integrity": "sha512-3xeeVz9s3Dh2ljKqJI/Fl+gkZD9Y8JblAN62f4WNM76d/zFlgpCXDs62OpxNjEuXujA7YFix0sJ+oPKMm8mDrw==", + "dev": true, + "requires": { + "@lerna/npm-conf": "3.7.0", + "figgy-pudding": "^3.5.1", + "npm-lifecycle": "^2.1.0", + "npmlog": "^4.1.2" + } + }, + "@lerna/run-parallel-batches": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@lerna/run-parallel-batches/-/run-parallel-batches-3.0.0.tgz", + "integrity": "sha512-Mj1ravlXF7AkkewKd9YFq9BtVrsStNrvVLedD/b2wIVbNqcxp8lS68vehXVOzoL/VWNEDotvqCQtyDBilCodGw==", + "dev": true, + "requires": { + "p-map": "^1.2.0", + "p-map-series": "^1.0.0" + } + }, + "@lerna/symlink-binary": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/symlink-binary/-/symlink-binary-3.11.0.tgz", + "integrity": "sha512-5sOED+1O8jI+ckDS6DRUKtAtbKo7lbxFIJs6sWWEu5qKzM5e21O6E2wTWimJkad8nJ1SJAuyc8DC8M8ki4kT4w==", + "dev": true, + "requires": { + "@lerna/create-symlink": "3.11.0", + "@lerna/package": "3.11.0", + "fs-extra": "^7.0.0", + "p-map": "^1.2.0" } }, "@lerna/symlink-dependencies": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@lerna/symlink-dependencies/-/symlink-dependencies-3.3.0.tgz", - "integrity": "sha512-IRngSNCmuD5uBKVv23tHMvr7Mplti0lKHilFKcvhbvhAfu6m/Vclxhkfs/uLyHzG+DeRpl/9o86SQET3h4XDhg==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/symlink-dependencies/-/symlink-dependencies-3.11.0.tgz", + "integrity": "sha512-XKNX8oOgcOmiKHUn7qT5GvvmKP3w5otZPOjRixUDUILWTc3P8nO5I1VNILNF6IE5ajNw6yiXOWikSxc6KuFqBQ==", "dev": true, "requires": { - "@lerna/create-symlink": "^3.3.0", - "@lerna/resolve-symlink": "^3.3.0", - "@lerna/symlink-binary": "^3.3.0", + "@lerna/create-symlink": "3.11.0", + "@lerna/resolve-symlink": "3.11.0", + "@lerna/symlink-binary": "3.11.0", "fs-extra": "^7.0.0", "p-finally": "^1.0.0", "p-map": "^1.2.0", "p-map-series": "^1.0.0" } }, + "@lerna/timer": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@lerna/timer/-/timer-3.5.0.tgz", + "integrity": "sha512-TAb99hqQN6E3JBGtG9iyZNPq1/DbmqgBOeNrKtdJsGvIeX/NGLgUDWMrj2h04V4O+jpBFmSf6HIld6triKmxCA==", + "dev": true + }, "@lerna/validation-error": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/validation-error/-/validation-error-3.0.0.tgz", - "integrity": "sha512-5wjkd2PszV0kWvH+EOKZJWlHEqCTTKrWsvfHnHhcUaKBe/NagPZFWs+0xlsDPZ3DJt5FNfbAPAnEBQ05zLirFA==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/validation-error/-/validation-error-3.11.0.tgz", + "integrity": "sha512-/mS4o6QYm4OXUqfPJnW1mKudGhvhLe9uiQ9eK2cgSxkCAVq9G2Sl/KVohpnqAgeRI3nXordGxHS745CdAhg7pA==", "dev": true, "requires": { "npmlog": "^4.1.2" } }, "@lerna/version": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/@lerna/version/-/version-3.4.1.tgz", - "integrity": "sha512-oefNaQLBJSI2WLZXw5XxDXk4NyF5/ct0V9ys/J308NpgZthPgwRPjk9ZR0o1IOxW1ABi6z3E317W/dxHDjvAkg==", - "dev": true, - "requires": { - "@lerna/batch-packages": "^3.1.2", - "@lerna/check-working-tree": "^3.3.0", - "@lerna/child-process": "^3.3.0", - "@lerna/collect-updates": "^3.3.2", - "@lerna/command": "^3.3.0", - "@lerna/conventional-commits": "^3.4.1", - "@lerna/output": "^3.0.0", - "@lerna/prompt": "^3.3.1", - "@lerna/run-lifecycle": "^3.4.1", - "@lerna/validation-error": "^3.0.0", + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@lerna/version/-/version-3.11.1.tgz", + "integrity": "sha512-+lFq4D8BpchIslIz6jyUY6TZO1kuAgQ+G1LjaYwUBiP2SzXVWgPoPoq/9dnaSq38Hhhvlf7FF6i15d+q8gk1xQ==", + "dev": true, + "requires": { + "@lerna/batch-packages": "3.11.0", + "@lerna/check-working-tree": "3.11.0", + "@lerna/child-process": "3.3.0", + "@lerna/collect-updates": "3.11.0", + "@lerna/command": "3.11.0", + "@lerna/conventional-commits": "3.11.0", + "@lerna/github-client": "3.11.0", + "@lerna/output": "3.11.0", + "@lerna/prompt": "3.11.0", + "@lerna/run-lifecycle": "3.11.0", + "@lerna/validation-error": "3.11.0", "chalk": "^2.3.1", "dedent": "^0.7.0", "minimatch": "^3.0.4", @@ -1913,9 +2063,9 @@ } }, "@lerna/write-log-file": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/write-log-file/-/write-log-file-3.0.0.tgz", - "integrity": "sha512-SfbPp29lMeEVOb/M16lJwn4nnx5y+TwCdd7Uom9umd7KcZP0NOvpnX0PHehdonl7TyHZ1Xx2maklYuCLbQrd/A==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@lerna/write-log-file/-/write-log-file-3.11.0.tgz", + "integrity": "sha512-skpTDMDOkQAN4lCeAoI6/rPhbNE431eD0i6Ts3kExUOrYTr0m5CIwVtMZ31Flpky0Jfh4ET6rOl5SDNMLbf4VA==", "dev": true, "requires": { "npmlog": "^4.1.2", @@ -1933,11 +2083,74 @@ } }, "@nodelib/fs.stat": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.0.tgz", - "integrity": "sha512-LAQ1d4OPfSJ/BMbI2DuizmYrrkD9JMaTdi2hQTlI53lQ4kRQPyZQRS4CYQ7O66bnBBnP/oYdRxbk++X0xuFU6A==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", + "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", + "dev": true + }, + "@octokit/endpoint": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-3.1.2.tgz", + "integrity": "sha512-iRx4kDYybAv9tOrHDBE6HwlgiFi8qmbZl8SHliZWtxbUFuXLZXh2yv8DxGIK9wzD9J0wLDMZneO8vNYJNUSJ9Q==", + "dev": true, + "requires": { + "deepmerge": "3.1.0", + "is-plain-object": "^2.0.4", + "universal-user-agent": "^2.0.1", + "url-template": "^2.0.8" + }, + "dependencies": { + "deepmerge": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.1.0.tgz", + "integrity": "sha512-/TnecbwXEdycfbsM2++O3eGiatEFHjjNciHEwJclM+T5Kd94qD1AP+2elP/Mq0L5b9VZJao5znR01Mz6eX8Seg==", + "dev": true + } + } + }, + "@octokit/plugin-enterprise-rest": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-2.1.1.tgz", + "integrity": "sha512-DJNXHH0LptKCLpJ8y3vCA/O+s+3/sDU4JNN2V0M04tsMN0hVGLPzoGgejPJgaxGP8Il5aw+jA5Nl5mTfdt9NrQ==", "dev": true }, + "@octokit/request": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-2.3.0.tgz", + "integrity": "sha512-5YRqYNZOAaL7+nt7w3Scp6Sz4P2g7wKFP9npx1xdExMomk8/M/ICXVLYVam2wzxeY0cIc6wcKpjC5KI4jiNbGw==", + "dev": true, + "requires": { + "@octokit/endpoint": "^3.1.1", + "is-plain-object": "^2.0.4", + "node-fetch": "^2.3.0", + "universal-user-agent": "^2.0.1" + }, + "dependencies": { + "node-fetch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.3.0.tgz", + "integrity": "sha512-MOd8pV3fxENbryESLgVIeaGKrdl+uaYhCSSVkjeOb/31/njTpcis5aWfdqgNlHIrKOLRbMnfPINPOML2CIFeXA==", + "dev": true + } + } + }, + "@octokit/rest": { + "version": "16.15.0", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-16.15.0.tgz", + "integrity": "sha512-Un+e7rgh38RtPOTe453pT/KPM/p2KZICimBmuZCd2wEo8PacDa4h6RqTPZs+f2DPazTTqdM7QU4LKlUjgiBwWw==", + "dev": true, + "requires": { + "@octokit/request": "2.3.0", + "before-after-hook": "^1.2.0", + "btoa-lite": "^1.0.0", + "lodash.get": "^4.4.2", + "lodash.set": "^4.3.2", + "lodash.uniq": "^4.5.0", + "octokit-pagination-methods": "^1.1.0", + "universal-user-agent": "^2.0.0", + "url-template": "^2.0.8" + } + }, "@romainberger/css-diff": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@romainberger/css-diff/-/css-diff-1.0.3.tgz", @@ -2249,19 +2462,19 @@ "@wordpress/a11y": { "version": "file:packages/a11y", "requires": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/dom-ready": "file:packages/dom-ready" } }, "@wordpress/annotations": { "version": "file:packages/annotations", "requires": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/data": "file:packages/data", "@wordpress/hooks": "file:packages/hooks", "@wordpress/i18n": "file:packages/i18n", "@wordpress/rich-text": "file:packages/rich-text", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "memize": "^1.0.5", "rememo": "^3.0.0", "uuid": "^3.3.2" @@ -2270,7 +2483,7 @@ "@wordpress/api-fetch": { "version": "file:packages/api-fetch", "requires": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/i18n": "file:packages/i18n", "@wordpress/url": "file:packages/url" } @@ -2278,36 +2491,36 @@ "@wordpress/autop": { "version": "file:packages/autop", "requires": { - "@babel/runtime": "^7.0.0" + "@babel/runtime": "^7.3.1" } }, "@wordpress/babel-plugin-import-jsx-pragma": { "version": "file:packages/babel-plugin-import-jsx-pragma", "dev": true, "requires": { - "@babel/runtime": "^7.0.0" + "@babel/runtime": "^7.3.1" } }, "@wordpress/babel-plugin-makepot": { "version": "file:packages/babel-plugin-makepot", "dev": true, "requires": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "gettext-parser": "^1.3.1", - "lodash": "^4.17.10" + "lodash": "^4.17.11" } }, "@wordpress/babel-preset-default": { "version": "file:packages/babel-preset-default", "dev": true, "requires": { - "@babel/core": "^7.0.0", - "@babel/plugin-proposal-async-generator-functions": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.0.0", - "@babel/plugin-transform-react-jsx": "^7.0.0", - "@babel/plugin-transform-runtime": "^7.0.0", - "@babel/preset-env": "^7.0.0", - "@babel/runtime": "^7.0.0", + "@babel/core": "^7.2.2", + "@babel/plugin-proposal-async-generator-functions": "^7.2.0", + "@babel/plugin-proposal-object-rest-spread": "^7.3.2", + "@babel/plugin-transform-react-jsx": "^7.3.0", + "@babel/plugin-transform-runtime": "^7.2.0", + "@babel/preset-env": "^7.3.1", + "@babel/runtime": "^7.3.1", "@wordpress/browserslist-config": "file:packages/browserslist-config", "babel-core": "^7.0.0-bridge.0" } @@ -2315,13 +2528,13 @@ "@wordpress/blob": { "version": "file:packages/blob", "requires": { - "@babel/runtime": "^7.0.0" + "@babel/runtime": "^7.3.1" } }, "@wordpress/block-library": { "version": "file:packages/block-library", "requires": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/autop": "file:packages/autop", "@wordpress/blob": "file:packages/blob", "@wordpress/blocks": "file:packages/blocks", @@ -2337,7 +2550,7 @@ "@wordpress/keycodes": "file:packages/keycodes", "@wordpress/viewport": "file:packages/viewport", "classnames": "^2.2.5", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "memize": "^1.0.5", "url": "^0.11.0" } @@ -2345,7 +2558,7 @@ "@wordpress/block-serialization-default-parser": { "version": "file:packages/block-serialization-default-parser", "requires": { - "@babel/runtime": "^7.0.0" + "@babel/runtime": "^7.3.1" } }, "@wordpress/block-serialization-spec-parser": { @@ -2354,7 +2567,7 @@ "@wordpress/blocks": { "version": "file:packages/blocks", "requires": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/autop": "file:packages/autop", "@wordpress/blob": "file:packages/blob", "@wordpress/block-serialization-default-parser": "file:packages/block-serialization-default-parser", @@ -2368,7 +2581,7 @@ "@wordpress/is-shallow-equal": "file:packages/is-shallow-equal", "@wordpress/shortcode": "file:packages/shortcode", "hpq": "^1.3.0", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "rememo": "^3.0.0", "showdown": "^1.8.6", "simple-html-tokenizer": "^0.4.1", @@ -2383,7 +2596,7 @@ "@wordpress/components": { "version": "file:packages/components", "requires": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/a11y": "file:packages/a11y", "@wordpress/api-fetch": "file:packages/api-fetch", "@wordpress/compose": "file:packages/compose", @@ -2399,7 +2612,7 @@ "clipboard": "^2.0.1", "diff": "^3.5.0", "dom-scroll-into-view": "^1.2.1", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "memize": "^1.0.5", "moment": "^2.22.1", "mousetrap": "^1.6.2", @@ -2414,22 +2627,22 @@ "@wordpress/compose": { "version": "file:packages/compose", "requires": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/element": "file:packages/element", "@wordpress/is-shallow-equal": "file:packages/is-shallow-equal", - "lodash": "^4.17.10" + "lodash": "^4.17.11" } }, "@wordpress/core-data": { "version": "file:packages/core-data", "requires": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/api-fetch": "file:packages/api-fetch", "@wordpress/data": "file:packages/data", "@wordpress/deprecated": "file:packages/deprecated", "@wordpress/url": "file:packages/url", "equivalent-key-map": "^0.2.2", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "rememo": "^3.0.0" } }, @@ -2437,14 +2650,14 @@ "version": "file:packages/custom-templated-path-webpack-plugin", "dev": true, "requires": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "escape-string-regexp": "^1.0.5" } }, "@wordpress/data": { "version": "file:packages/data", "requires": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/compose": "file:packages/compose", "@wordpress/element": "file:packages/element", "@wordpress/is-shallow-equal": "file:packages/is-shallow-equal", @@ -2452,7 +2665,7 @@ "@wordpress/redux-routine": "file:packages/redux-routine", "equivalent-key-map": "^0.2.2", "is-promise": "^2.1.0", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "redux": "^4.0.0", "turbo-combine-reducers": "^1.0.2" } @@ -2460,7 +2673,7 @@ "@wordpress/date": { "version": "file:packages/date", "requires": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "moment": "^2.22.1", "moment-timezone": "^0.5.16" } @@ -2468,21 +2681,21 @@ "@wordpress/deprecated": { "version": "file:packages/deprecated", "requires": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/hooks": "file:packages/hooks" } }, "@wordpress/dom": { "version": "file:packages/dom", "requires": { - "@babel/runtime": "^7.0.0", - "lodash": "^4.17.10" + "@babel/runtime": "^7.3.1", + "lodash": "^4.17.11" } }, "@wordpress/dom-ready": { "version": "file:packages/dom-ready", "requires": { - "@babel/runtime": "^7.0.0" + "@babel/runtime": "^7.3.1" } }, "@wordpress/e2e-test-utils": { @@ -2491,7 +2704,7 @@ "requires": { "@wordpress/keycodes": "file:packages/keycodes", "@wordpress/url": "file:packages/url", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "node-fetch": "^1.7.3" } }, @@ -2503,13 +2716,13 @@ "@wordpress/jest-console": "file:packages/jest-console", "@wordpress/scripts": "file:packages/scripts", "expect-puppeteer": "^3.2.0", - "lodash": "^4.17.10" + "lodash": "^4.17.11" } }, "@wordpress/edit-post": { "version": "file:packages/edit-post", "requires": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/a11y": "file:packages/a11y", "@wordpress/api-fetch": "file:packages/api-fetch", "@wordpress/block-library": "file:packages/block-library", @@ -2529,14 +2742,14 @@ "@wordpress/url": "file:packages/url", "@wordpress/viewport": "file:packages/viewport", "classnames": "^2.2.5", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "refx": "^3.0.0" } }, "@wordpress/editor": { "version": "file:packages/editor", "requires": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/a11y": "file:packages/a11y", "@wordpress/api-fetch": "file:packages/api-fetch", "@wordpress/blob": "file:packages/blob", @@ -2563,7 +2776,7 @@ "classnames": "^2.2.5", "dom-scroll-into-view": "^1.2.1", "inherits": "^2.0.3", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "memize": "^1.0.5", "react-autosize-textarea": "^3.0.2", "redux-multi": "^0.1.12", @@ -2577,9 +2790,9 @@ "@wordpress/element": { "version": "file:packages/element", "requires": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/escape-html": "file:packages/escape-html", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "react": "^16.6.3", "react-dom": "^16.6.3" } @@ -2587,7 +2800,7 @@ "@wordpress/escape-html": { "version": "file:packages/escape-html", "requires": { - "@babel/runtime": "^7.0.0" + "@babel/runtime": "^7.3.1" } }, "@wordpress/eslint-plugin": { @@ -2603,7 +2816,7 @@ "@wordpress/format-library": { "version": "file:packages/format-library", "requires": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/components": "file:packages/components", "@wordpress/dom": "file:packages/dom", "@wordpress/editor": "file:packages/editor", @@ -2617,21 +2830,21 @@ "@wordpress/hooks": { "version": "file:packages/hooks", "requires": { - "@babel/runtime": "^7.0.0" + "@babel/runtime": "^7.3.1" } }, "@wordpress/html-entities": { "version": "file:packages/html-entities", "requires": { - "@babel/runtime": "^7.0.0" + "@babel/runtime": "^7.3.1" } }, "@wordpress/i18n": { "version": "file:packages/i18n", "requires": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "gettext-parser": "^1.3.1", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "memize": "^1.0.5", "sprintf-js": "^1.1.1", "tannin": "^1.0.1" @@ -2640,16 +2853,16 @@ "@wordpress/is-shallow-equal": { "version": "file:packages/is-shallow-equal", "requires": { - "@babel/runtime": "^7.0.0" + "@babel/runtime": "^7.3.1" } }, "@wordpress/jest-console": { "version": "file:packages/jest-console", "dev": true, "requires": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "jest-matcher-utils": "^23.6.0", - "lodash": "^4.17.10" + "lodash": "^4.17.11" } }, "@wordpress/jest-preset-default": { @@ -2673,39 +2886,39 @@ "@wordpress/keycodes": { "version": "file:packages/keycodes", "requires": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/i18n": "file:packages/i18n", - "lodash": "^4.17.10" + "lodash": "^4.17.11" } }, "@wordpress/library-export-default-webpack-plugin": { "version": "file:packages/library-export-default-webpack-plugin", "dev": true, "requires": { - "@babel/runtime": "^7.0.0", - "lodash": "^4.17.10", + "@babel/runtime": "^7.3.1", + "lodash": "^4.17.11", "webpack-sources": "^1.1.0" } }, "@wordpress/list-reusable-blocks": { "version": "file:packages/list-reusable-blocks", "requires": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/api-fetch": "file:packages/api-fetch", "@wordpress/components": "file:packages/components", "@wordpress/compose": "file:packages/compose", "@wordpress/element": "file:packages/element", "@wordpress/i18n": "file:packages/i18n", - "lodash": "^4.17.10" + "lodash": "^4.17.11" } }, "@wordpress/notices": { "version": "file:packages/notices", "requires": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/a11y": "file:packages/a11y", "@wordpress/data": "file:packages/data", - "lodash": "^4.17.10" + "lodash": "^4.17.11" } }, "@wordpress/npm-package-json-lint-config": { @@ -2715,31 +2928,31 @@ "@wordpress/nux": { "version": "file:packages/nux", "requires": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/components": "file:packages/components", "@wordpress/compose": "file:packages/compose", "@wordpress/data": "file:packages/data", "@wordpress/element": "file:packages/element", "@wordpress/i18n": "file:packages/i18n", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "rememo": "^3.0.0" } }, "@wordpress/plugins": { "version": "file:packages/plugins", "requires": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/compose": "file:packages/compose", "@wordpress/element": "file:packages/element", "@wordpress/hooks": "file:packages/hooks", - "lodash": "^4.17.10" + "lodash": "^4.17.11" } }, "@wordpress/postcss-themes": { "version": "file:packages/postcss-themes", "dev": true, "requires": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "autoprefixer": "^9.4.5", "postcss": "^7.0.13", "postcss-color-function": "^4.0.1" @@ -2748,13 +2961,13 @@ "@wordpress/priority-queue": { "version": "file:packages/priority-queue", "requires": { - "@babel/runtime": "^7.0.0" + "@babel/runtime": "^7.3.1" } }, "@wordpress/redux-routine": { "version": "file:packages/redux-routine", "requires": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "is-promise": "^2.1.0", "rungen": "^0.3.2" } @@ -2762,11 +2975,11 @@ "@wordpress/rich-text": { "version": "file:packages/rich-text", "requires": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/compose": "file:packages/compose", "@wordpress/data": "file:packages/data", "@wordpress/escape-html": "file:packages/escape-html", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "rememo": "^3.0.0" } }, @@ -2797,40 +3010,40 @@ "@wordpress/shortcode": { "version": "file:packages/shortcode", "requires": { - "@babel/runtime": "^7.0.0", - "lodash": "^4.17.10", + "@babel/runtime": "^7.3.1", + "lodash": "^4.17.11", "memize": "^1.0.5" } }, "@wordpress/token-list": { "version": "file:packages/token-list", "requires": { - "@babel/runtime": "^7.0.0", - "lodash": "^4.17.10" + "@babel/runtime": "^7.3.1", + "lodash": "^4.17.11" } }, "@wordpress/url": { "version": "file:packages/url", "requires": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "qs": "^6.5.2" } }, "@wordpress/viewport": { "version": "file:packages/viewport", "requires": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/compose": "file:packages/compose", "@wordpress/data": "file:packages/data", "@wordpress/element": "file:packages/element", - "lodash": "^4.17.10" + "lodash": "^4.17.11" } }, "@wordpress/wordcount": { "version": "file:packages/wordcount", "requires": { - "@babel/runtime": "^7.0.0", - "lodash": "^4.17.10" + "@babel/runtime": "^7.3.1", + "lodash": "^4.17.11" } }, "JSONStream": { @@ -2969,28 +3182,6 @@ "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=", "dev": true }, - "align-text": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", - "dev": true, - "requires": { - "kind-of": "^3.0.2", - "longest": "^1.0.1", - "repeat-string": "^1.5.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "alphanum-sort": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", @@ -3575,15 +3766,80 @@ } }, "babel-loader": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.0.tgz", - "integrity": "sha512-lBUGBz411lSfT+8MHPEaqIVQ44odS1D/wxuTMhijqHc9arZR6jhJEaJa0RpZlCSITZoeK6xoDXTaVTrSoFD7IQ==", + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.5.tgz", + "integrity": "sha512-NTnHnVRd2JnRqPC0vW+iOQWU5pchDbYXsG2E6DMXEpMfUcQKclF9gmf3G3ZMhzG7IG9ji4coL0cm+FxeWxDpnw==", "dev": true, "requires": { - "find-cache-dir": "^1.0.0", + "find-cache-dir": "^2.0.0", "loader-utils": "^1.0.2", "mkdirp": "^0.5.1", "util.promisify": "^1.0.0" + }, + "dependencies": { + "find-cache-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.0.0.tgz", + "integrity": "sha512-LDUY6V1Xs5eFskUVYtIwatojt6+9xC9Chnlk/jYOOvn3FAFfSaWddxahDGyNHh0b2dMXa6YW2m0tk8TdVaXHlA==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^1.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", + "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + } } }, "babel-messages": { @@ -3856,6 +4112,12 @@ "tweetnacl": "^0.14.3" } }, + "before-after-hook": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-1.3.2.tgz", + "integrity": "sha512-zyPgY5dgbf99c0uGUjhY4w+mxqEGxPKg9RQDl34VvrVh2bM31lFN+mwR1ZHepq/KA3VCPk1gwJZL6IIJqjLy2w==", + "dev": true + }, "benchmark": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/benchmark/-/benchmark-2.1.4.tgz", @@ -4207,6 +4469,12 @@ "node-int64": "^0.4.0" } }, + "btoa-lite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz", + "integrity": "sha1-M3dm2hWAEhD92VbCLpxokaudAzc=", + "dev": true + }, "buffer": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", @@ -4408,17 +4676,6 @@ "integrity": "sha512-Jt9tIBkRc9POUof7QA/VwWd+58fKkEEfI+/t1/eOlxKM7ZhrczNzMFefge7Ai+39y1pR/pP6cI19guHy3FSLmw==", "dev": true }, - "center-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", - "dev": true, - "optional": true, - "requires": { - "align-text": "^0.1.3", - "lazy-cache": "^1.0.3" - } - }, "chalk": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", @@ -4690,6 +4947,26 @@ "restore-cursor": "^2.0.0" } }, + "cli-table3": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.5.1.tgz", + "integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==", + "dev": true, + "requires": { + "colors": "^1.1.2", + "object-assign": "^4.1.0", + "string-width": "^2.1.1" + }, + "dependencies": { + "colors": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.3.tgz", + "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==", + "dev": true, + "optional": true + } + } + }, "cli-truncate": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz", @@ -7577,16 +7854,16 @@ "dev": true }, "fast-glob": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.2.tgz", - "integrity": "sha512-TR6zxCKftDQnUAPvkrCWdBgDq/gbqx8A3ApnBrR5rMvpp6+KMJI0Igw7fkWPgeVK0uhRXTXdvO3O+YP0CaUX2g==", + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.6.tgz", + "integrity": "sha512-0BvMaZc1k9F+MeWWMe8pL6YltFzZYcJsYU7D4JyDA6PAczaXvxqQQ/z+mDF7/4Mw01DeUc+i3CTKajnkANkV4w==", "dev": true, "requires": { "@mrmlnc/readdir-enhanced": "^2.2.1", - "@nodelib/fs.stat": "^1.0.1", + "@nodelib/fs.stat": "^1.1.2", "glob-parent": "^3.1.0", "is-glob": "^4.0.0", - "merge2": "^1.2.1", + "merge2": "^1.2.3", "micromatch": "^3.1.10" }, "dependencies": { @@ -8102,7 +8379,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -8123,12 +8401,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -8143,17 +8423,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -8270,7 +8553,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -8282,6 +8566,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -8296,6 +8581,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -8303,12 +8589,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -8327,6 +8615,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -8407,7 +8696,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -8419,6 +8709,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -8504,7 +8795,8 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -8540,6 +8832,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -8559,6 +8852,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -8602,12 +8896,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -9052,6 +9348,25 @@ } } }, + "git-up": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/git-up/-/git-up-4.0.1.tgz", + "integrity": "sha512-LFTZZrBlrCrGCG07/dm1aCjjpL1z9L3+5aEeI9SBhAqSc+kiA9Or1bgZhQFNppJX6h/f5McrvJt1mQXTFm6Qrw==", + "dev": true, + "requires": { + "is-ssh": "^1.3.0", + "parse-url": "^5.0.0" + } + }, + "git-url-parse": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-11.1.2.tgz", + "integrity": "sha512-gZeLVGY8QVKMIkckncX+iCq2/L8PlwncvDFKiWkBn9EtCfYDbliRTTp6qzyQ1VMdITUfq7293zDzfpjdiGASSQ==", + "dev": true, + "requires": { + "git-up": "^4.0.0" + } + }, "gitconfiglocal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz", @@ -9121,6 +9436,29 @@ "integrity": "sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==", "dev": true }, + "globby": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.2.tgz", + "integrity": "sha512-yTzMmKygLp8RUpG1Ymu2VXPSJQZjNAZPD4ywgYEaG7e4tBJeUQBO8OpXrf1RCNcEs5alsoJYPAMiIHP0cmeC7w==", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "dir-glob": "2.0.0", + "fast-glob": "^2.0.2", + "glob": "^7.1.2", + "ignore": "^3.3.5", + "pify": "^3.0.0", + "slash": "^1.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } + } + }, "globjoin": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", @@ -9194,31 +9532,22 @@ } }, "handlebars": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.11.tgz", - "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.0.tgz", + "integrity": "sha512-l2jRuU1NAWK6AW5qqcTATWQJvNPEwkM7NEKSiv/gqOsoSQbVoWyqVEY5GS+XPQ88zLNmqASRpzfdm8d79hJS+w==", "dev": true, "requires": { - "async": "^1.4.0", + "async": "^2.5.0", "optimist": "^0.6.1", - "source-map": "^0.4.4", - "uglify-js": "^2.6" + "source-map": "^0.6.1", + "uglify-js": "^3.1.4" }, "dependencies": { - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - }, "source-map": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", - "dev": true, - "requires": { - "amdefine": ">=0.0.4" - } + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true } } }, @@ -10056,6 +10385,15 @@ "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", "dev": true }, + "is-ssh": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.3.1.tgz", + "integrity": "sha512-0eRIASHZt1E68/ixClI8bp2YK2wmBPVWEismTs6M+M099jKgrzl/3E976zIbImSIob48N2/XGe9y7ZiYdImSlg==", + "dev": true, + "requires": { + "protocols": "^1.1.0" + } + }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", @@ -10227,9 +10565,9 @@ } }, "istanbul-lib-instrument": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.1.tgz", - "integrity": "sha512-1dYuzkOCbuR5GRJqySuZdsmsNKPL3PTuyPevQfoCXJePT9C8y1ga75neU+Tuy9+yS3G/dgx8wgOmp2KLpgdoeQ==", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz", + "integrity": "sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A==", "dev": true, "requires": { "babel-generator": "^6.18.0", @@ -10237,8 +10575,16 @@ "babel-traverse": "^6.18.0", "babel-types": "^6.18.0", "babylon": "^6.18.0", - "istanbul-lib-coverage": "^1.2.0", + "istanbul-lib-coverage": "^1.2.1", "semver": "^5.3.0" + }, + "dependencies": { + "istanbul-lib-coverage": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", + "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==", + "dev": true + } } }, "istanbul-lib-report": { @@ -12150,26 +12496,26 @@ "dev": true }, "lerna": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/lerna/-/lerna-3.4.3.tgz", - "integrity": "sha512-tWq1LvpHqkyB+FaJCmkEweivr88yShDMmauofPVdh0M5gU1cVucszYnIgWafulKYu2LMQ3IfUMUU5Pp3+MvADQ==", - "dev": true, - "requires": { - "@lerna/add": "^3.4.1", - "@lerna/bootstrap": "^3.4.1", - "@lerna/changed": "^3.4.1", - "@lerna/clean": "^3.3.2", - "@lerna/cli": "^3.2.0", - "@lerna/create": "^3.4.1", - "@lerna/diff": "^3.3.0", - "@lerna/exec": "^3.3.2", - "@lerna/import": "^3.3.1", - "@lerna/init": "^3.3.0", - "@lerna/link": "^3.3.0", - "@lerna/list": "^3.3.2", - "@lerna/publish": "^3.4.3", - "@lerna/run": "^3.3.2", - "@lerna/version": "^3.4.1", + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/lerna/-/lerna-3.11.1.tgz", + "integrity": "sha512-7an/cia9u6qVTts5PQ/adFq8QSgE7gzG1pUHhH+XKVU1seDKQ99JLu61n3/euv2qeQF+ww4WLKnFHIPa5+LJSQ==", + "dev": true, + "requires": { + "@lerna/add": "3.11.0", + "@lerna/bootstrap": "3.11.0", + "@lerna/changed": "3.11.1", + "@lerna/clean": "3.11.0", + "@lerna/cli": "3.11.0", + "@lerna/create": "3.11.0", + "@lerna/diff": "3.11.0", + "@lerna/exec": "3.11.0", + "@lerna/import": "3.11.0", + "@lerna/init": "3.11.0", + "@lerna/link": "3.11.0", + "@lerna/list": "3.11.0", + "@lerna/publish": "3.11.1", + "@lerna/run": "3.11.0", + "@lerna/version": "3.11.1", "import-local": "^1.0.0", "npmlog": "^4.1.2" } @@ -12191,9 +12537,9 @@ } }, "libnpmaccess": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/libnpmaccess/-/libnpmaccess-3.0.0.tgz", - "integrity": "sha512-SiE4AZAzMpD7pmmXHfgD7rof8QIQGoKaeyAS8exgx2CKA6tzRTbRljq1xM4Tgj8/tIg+KBJPJWkR0ifqKT3irQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/libnpmaccess/-/libnpmaccess-3.0.1.tgz", + "integrity": "sha512-RlZ7PNarCBt+XbnP7R6PoVgOq9t+kou5rvhaInoNibhPO7eMlRfS0B8yjatgn2yaHIwWNyoJDolC/6Lc5L/IQA==", "dev": true, "requires": { "aproba": "^2.0.0", @@ -12229,6 +12575,65 @@ } } }, + "libnpmpublish": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/libnpmpublish/-/libnpmpublish-1.1.1.tgz", + "integrity": "sha512-nefbvJd/wY38zdt+b9SHL6171vqBrMtZ56Gsgfd0duEKb/pB8rDT4/ObUQLrHz1tOfht1flt2zM+UGaemzAG5g==", + "dev": true, + "requires": { + "aproba": "^2.0.0", + "figgy-pudding": "^3.5.1", + "get-stream": "^4.0.0", + "lodash.clonedeep": "^4.5.0", + "normalize-package-data": "^2.4.0", + "npm-package-arg": "^6.1.0", + "npm-registry-fetch": "^3.8.0", + "semver": "^5.5.1", + "ssri": "^6.0.1" + }, + "dependencies": { + "aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true + }, + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + } + } + }, "line-height": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/line-height/-/line-height-0.3.1.tgz", @@ -12529,9 +12934,9 @@ } }, "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" }, "lodash._baseisequal": { "version": "3.0.7", @@ -12592,6 +12997,12 @@ "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", "dev": true }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, "lodash.isarguments": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", @@ -12649,6 +13060,12 @@ "integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==", "dev": true }, + "lodash.set": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", + "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=", + "dev": true + }, "lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", @@ -12701,12 +13118,6 @@ "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=", "dev": true }, - "longest": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", - "dev": true - }, "longest-streak": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.2.tgz", @@ -12741,6 +13152,12 @@ "yallist": "^2.1.2" } }, + "macos-release": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.0.0.tgz", + "integrity": "sha512-iCM3ZGeqIzlrH7KxYK+fphlJpCCczyHXc+HhRVbEu9uNTCrzYJjvvtefzeKTCVHd5AP/aD/fzC80JZ4ZP+dQ/A==", + "dev": true + }, "make-dir": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", @@ -12777,28 +13194,71 @@ "ssri": "^6.0.0" }, "dependencies": { + "bluebird": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz", + "integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==", + "dev": true + }, "cacache": { - "version": "11.3.1", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.1.tgz", - "integrity": "sha512-2PEw4cRRDu+iQvBTTuttQifacYjLPhET+SYO/gEFMy8uhi+jlJREDAjSF5FWSdV/Aw5h18caHA7vMTw2c+wDzA==", + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.2.tgz", + "integrity": "sha512-E0zP4EPGDOaT2chM08Als91eYnf8Z+eH1awwwVsngUmgppfM5jjJ8l3z5vO5p5w/I3LsiXawb1sW0VY65pQABg==", "dev": true, "requires": { - "bluebird": "^3.5.1", - "chownr": "^1.0.1", - "figgy-pudding": "^3.1.0", - "glob": "^7.1.2", - "graceful-fs": "^4.1.11", - "lru-cache": "^4.1.3", + "bluebird": "^3.5.3", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.3", + "graceful-fs": "^4.1.15", + "lru-cache": "^5.1.1", "mississippi": "^3.0.0", "mkdirp": "^0.5.1", "move-concurrently": "^1.0.1", "promise-inflight": "^1.0.1", "rimraf": "^2.6.2", - "ssri": "^6.0.0", - "unique-filename": "^1.1.0", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", "y18n": "^4.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + } + } + }, + "chownr": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", + "dev": true + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, "mississippi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", @@ -12836,11 +13296,26 @@ "figgy-pudding": "^3.5.1" } }, + "unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "dev": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, "y18n": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", "dev": true + }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "dev": true } } }, @@ -13149,9 +13624,9 @@ } }, "merge2": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.2.2.tgz", - "integrity": "sha512-bgM8twH86rWni21thii6WCMQMRMmwqqdW3sGWi9IipnVAszdLXRjwDwAnyrVXo6DuP3AjRMMttZKUB48QWIFGg==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.2.3.tgz", + "integrity": "sha512-gdUU1Fwj5ep4kplwcmftruWofEFt6lfpkkr3h860CXbAB9c3hGb55EOL2ali0Td5oebvW0E1+3Sr+Ur7XfKpRA==", "dev": true }, "methods": { @@ -13265,17 +13740,17 @@ }, "dependencies": { "yallist": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz", - "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", "dev": true } } }, "minizlib": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.1.1.tgz", - "integrity": "sha512-TrfjCjk4jLhcJyGMYymBH6oTXcWjYbUAXTHDbtnWHjZC25h0cdajHuPE1zxb4DVmu8crfh+HwH/WMuyLG0nHBg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", "dev": true, "requires": { "minipass": "^2.2.1" @@ -13909,10 +14384,16 @@ "integrity": "sha1-0LFF62kRicY6eNIB3E/bEpPvDAM=", "dev": true }, + "normalize-url": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", + "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==", + "dev": true + }, "npm-bundled": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.5.tgz", - "integrity": "sha512-m/e6jgWu8/v5niCUKQi9qQl8QdeEduFA96xHDDzFGqly0OOjI7c+60KM/2sppfnUU9JJagf+zs+yGhqSOFj71g==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz", + "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", "dev": true }, "npm-lifecycle": { @@ -13998,9 +14479,9 @@ } }, "npm-packlist": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.1.12.tgz", - "integrity": "sha512-WJKFOVMeAlsU/pjXuqVdzU0WfgtIBCupkEVwn+1Y0ERAbUfWw8R4GjgVbaKnUjRoD2FoQbHOCbOyT5Mbs9Lw4g==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.3.0.tgz", + "integrity": "sha512-qPBc6CnxEzpOcc4bjoIBJbYdy0D/LFFPUdxvfwor4/w3vxeE0h6TiOVurCEPpQ6trjN77u/ShyfeJGsbAfB3dA==", "dev": true, "requires": { "ignore-walk": "^3.0.1", @@ -14028,9 +14509,9 @@ } }, "npm-registry-fetch": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-3.8.0.tgz", - "integrity": "sha512-hrw8UMD+Nob3Kl3h8Z/YjmKamb1gf7D1ZZch2otrIXM3uFLB5vjEY6DhMlq80z/zZet6eETLbOXcuQudCB3Zpw==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-3.9.0.tgz", + "integrity": "sha512-srwmt8YhNajAoSAaDWndmZgx89lJwIZ1GWxOuckH4Coek4uHv5S+o/l9FLQe/awA+JwTnj4FJHldxhlXdZEBmw==", "dev": true, "requires": { "JSONStream": "^1.3.4", @@ -14227,6 +14708,12 @@ "has": "^1.0.1" } }, + "octokit-pagination-methods": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz", + "integrity": "sha512-fZ4qZdQ2nxJvtcasX7Ghl+WlWS/d9IgnBIwFZXVNNZUmzpno91SX5bc5vuxiuKoCtK78XxGGNuSCrDC7xYB3OQ==", + "dev": true + }, "on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -14314,6 +14801,16 @@ "mem": "^1.1.0" } }, + "os-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/os-name/-/os-name-3.0.0.tgz", + "integrity": "sha512-7c74tib2FsdFbQ3W+qj8Tyd1R3Z6tuVRNNxXjJcZ4NgjIEQU9N/prVMqcW29XZPXGACqaXN3jq58/6hoaoXH6g==", + "dev": true, + "requires": { + "macos-release": "^2.0.0", + "windows-release": "^3.1.0" + } + }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -14405,17 +14902,17 @@ } }, "pacote": { - "version": "9.2.3", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-9.2.3.tgz", - "integrity": "sha512-Y3+yY3nBRAxMlZWvr62XLJxOwCmG9UmkGZkFurWHoCjqF0cZL72cTOCRJTvWw8T4OhJS2RTg13x4oYYriauvEw==", + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-9.4.1.tgz", + "integrity": "sha512-YKSRsQqmeHxgra0KCdWA2FtVxDPUlBiCdmew+mSe44pzlx5t1ViRMWiQg18T+DREA+vSqYfKzynaToFR4hcKHw==", "dev": true, "requires": { - "bluebird": "^3.5.2", - "cacache": "^11.2.0", + "bluebird": "^3.5.3", + "cacache": "^11.3.2", "figgy-pudding": "^3.5.1", "get-stream": "^4.1.0", "glob": "^7.1.3", - "lru-cache": "^4.1.3", + "lru-cache": "^5.1.1", "make-fetch-happen": "^4.0.1", "minimatch": "^3.0.4", "minipass": "^2.3.5", @@ -14434,7 +14931,7 @@ "safe-buffer": "^5.1.2", "semver": "^5.6.0", "ssri": "^6.0.1", - "tar": "^4.4.6", + "tar": "^4.4.8", "unique-filename": "^1.1.1", "which": "^1.3.1" }, @@ -14446,27 +14943,33 @@ "dev": true }, "cacache": { - "version": "11.3.1", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.1.tgz", - "integrity": "sha512-2PEw4cRRDu+iQvBTTuttQifacYjLPhET+SYO/gEFMy8uhi+jlJREDAjSF5FWSdV/Aw5h18caHA7vMTw2c+wDzA==", + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.2.tgz", + "integrity": "sha512-E0zP4EPGDOaT2chM08Als91eYnf8Z+eH1awwwVsngUmgppfM5jjJ8l3z5vO5p5w/I3LsiXawb1sW0VY65pQABg==", "dev": true, "requires": { - "bluebird": "^3.5.1", - "chownr": "^1.0.1", - "figgy-pudding": "^3.1.0", - "glob": "^7.1.2", - "graceful-fs": "^4.1.11", - "lru-cache": "^4.1.3", + "bluebird": "^3.5.3", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.3", + "graceful-fs": "^4.1.15", + "lru-cache": "^5.1.1", "mississippi": "^3.0.0", "mkdirp": "^0.5.1", "move-concurrently": "^1.0.1", "promise-inflight": "^1.0.1", "rimraf": "^2.6.2", - "ssri": "^6.0.0", - "unique-filename": "^1.1.0", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", "y18n": "^4.0.0" } }, + "chownr": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", + "dev": true + }, "get-stream": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", @@ -14490,6 +14993,21 @@ "path-is-absolute": "^1.0.0" } }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, "mississippi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", @@ -14546,14 +15064,6 @@ "mkdirp": "^0.5.0", "safe-buffer": "^5.1.2", "yallist": "^3.0.2" - }, - "dependencies": { - "chownr": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", - "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", - "dev": true - } } }, "unique-filename": { @@ -14572,9 +15082,9 @@ "dev": true }, "yallist": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz", - "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", "dev": true } } @@ -14673,6 +15183,28 @@ "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", "dev": true }, + "parse-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-4.0.1.tgz", + "integrity": "sha512-d7yhga0Oc+PwNXDvQ0Jv1BuWkLVPXcAoQ/WREgd6vNNoKYaW52KI+RdOFjI63wjkmps9yUE8VS4veP+AgpQ/hA==", + "dev": true, + "requires": { + "is-ssh": "^1.3.0", + "protocols": "^1.4.0" + } + }, + "parse-url": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-5.0.1.tgz", + "integrity": "sha512-flNUPP27r3vJpROi0/R3/2efgKkyXqnXwyP1KQ2U0SfFRgdizOdWfvrrvJg1LuOoxs7GQhmxJlq23IpQ/BkByg==", + "dev": true, + "requires": { + "is-ssh": "^1.3.0", + "normalize-url": "^3.3.0", + "parse-path": "^4.0.0", + "protocols": "^1.4.0" + } + }, "parse5": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", @@ -15420,16 +15952,29 @@ } }, "@babel/generator": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.2.2.tgz", - "integrity": "sha512-I4o675J/iS8k+P38dvJ3IBGqObLXyQLTxtrR4u9cSUJOURvafeEWb/pFMOTwtNrmq73mJzyF6ueTbO1BtN0Zeg==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.3.2.tgz", + "integrity": "sha512-f3QCuPppXxtZOEm5GWPra/uYUjmNQlu9pbAD8D/9jze4pTY83rTtB1igTBSwvkeNlC5gR24zFFkz+2WHLFQhqQ==", "dev": true, "requires": { - "@babel/types": "^7.2.2", + "@babel/types": "^7.3.2", "jsesc": "^2.5.1", "lodash": "^4.17.10", "source-map": "^0.5.0", "trim-right": "^1.0.1" + }, + "dependencies": { + "@babel/types": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.3.2.tgz", + "integrity": "sha512-3Y6H8xlUlpbGR+XvawiH0UXehqydTmNmEpozWcXymqwcrwYAl5KMvKtQ+TF6f6E08V6Jur7v/ykdDSF+WDEIXQ==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.10", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/helper-function-name": { @@ -15444,14 +15989,27 @@ } }, "@babel/helpers": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.2.0.tgz", - "integrity": "sha512-Fr07N+ea0dMcMN8nFpuK6dUIT7/ivt9yKQdEEnjVS83tG2pHwPi03gYmk/tyuwONnZ+sY+GFFPlWGgCtW1hF9A==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.3.1.tgz", + "integrity": "sha512-Q82R3jKsVpUV99mgX50gOPCWwco9Ec5Iln/8Vyu4osNIOQgSrd9RFrQeUvmvddFNoLwMyOUWU+5ckioEKpDoGA==", "dev": true, "requires": { "@babel/template": "^7.1.2", "@babel/traverse": "^7.1.5", - "@babel/types": "^7.2.0" + "@babel/types": "^7.3.0" + }, + "dependencies": { + "@babel/types": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.3.2.tgz", + "integrity": "sha512-3Y6H8xlUlpbGR+XvawiH0UXehqydTmNmEpozWcXymqwcrwYAl5KMvKtQ+TF6f6E08V6Jur7v/ykdDSF+WDEIXQ==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.10", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/parser": { @@ -16662,6 +17220,12 @@ "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", "dev": true }, + "protocols": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/protocols/-/protocols-1.4.7.tgz", + "integrity": "sha512-Fx65lf9/YDn3hUX08XUc0J8rSux36rEsyiv21ZGUC1mOyeM3lTRpZLcrm8aAolzS4itwVfm7TAPyxC2E5zd6xg==", + "dev": true + }, "protoduck": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/protoduck/-/protoduck-5.0.1.tgz", @@ -17176,9 +17740,9 @@ } }, "read-package-tree": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/read-package-tree/-/read-package-tree-5.2.1.tgz", - "integrity": "sha512-2CNoRoh95LxY47LvqrehIAfUVda2JbuFE/HaGYs42bNrGG+ojbw1h3zOcPcQ+1GQ3+rkzNndZn85u1XyZ3UsIA==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/read-package-tree/-/read-package-tree-5.2.2.tgz", + "integrity": "sha512-rW3XWUUkhdKmN2JKB4FL563YAgtINifso5KShykufR03nJ5loGFlkUMe1g/yxmqX073SoYYTsgXu7XdDinKZuA==", "dev": true, "requires": { "debuglog": "^1.0.1", @@ -17405,6 +17969,194 @@ "safe-regex": "^1.1.0" } }, + "regexp-tree": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.1.tgz", + "integrity": "sha512-HwRjOquc9QOwKTgbxvZTcddS5mlNlwePMQ3NFL8broajMLD5CXDAqas8Y5yxJH5QtZp5iRor3YCILd5pz71Cgw==", + "dev": true, + "requires": { + "cli-table3": "^0.5.0", + "colors": "^1.1.2", + "yargs": "^12.0.5" + }, + "dependencies": { + "camelcase": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", + "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", + "dev": true + }, + "colors": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.3.tgz", + "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "mem": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.1.0.tgz", + "integrity": "sha512-I5u6Q1x7wxO0kdOpYBB28xueHADYps5uty/zg936CiG8NTe5sJL8EjrCuLneuDW3PlMdZBGDIn8BirEVdovZvg==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^1.0.0", + "p-is-promise": "^2.0.0" + } + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "p-is-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.0.0.tgz", + "integrity": "sha512-pzQPhYMCAgLAKPWD2jC3Se9fEfrD9npNos0y150EeqZll7akhEgGhTW/slB6lHku8AvYGiJ+YJ5hfHKePPgFWg==", + "dev": true + }, + "p-limit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", + "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, "regexpp": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", @@ -17696,16 +18448,6 @@ "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=", "dev": true }, - "right-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", - "dev": true, - "optional": true, - "requires": { - "align-text": "^0.1.1" - } - }, "rimraf": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", @@ -18309,9 +19051,9 @@ "dev": true }, "smart-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.0.1.tgz", - "integrity": "sha512-RFqinRVJVcCAL9Uh1oVqE6FZkqsyLiVOYEZ20TqIOjuX7iFVJ+zsbs4RIghnw/pTs7mZvt8ZHhvm1ZUrR4fykg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.0.2.tgz", + "integrity": "sha512-JDhEpTKzXusOqXZ0BUIdH+CjFdO/CR3tLlf5CN34IypI+xMmXW1uB16OOY8z3cICbJlDAVJzNbwBhNO0wt9OAw==", "dev": true }, "snapdragon": { @@ -18431,13 +19173,13 @@ } }, "socks": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.2.2.tgz", - "integrity": "sha512-g6wjBnnMOZpE0ym6e0uHSddz9p3a+WsBaaYQaBaSCJYvrC4IXykQR9MNGjLQf38e9iIIhp3b1/Zk8YZI3KGJ0Q==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.2.3.tgz", + "integrity": "sha512-+2r83WaRT3PXYoO/1z+RDEBE7Z2f9YcdQnJ0K/ncXXbV5gJ6wYfNAebYFYiiUjM6E4JyXnPY8cimwyvFYHVUUA==", "dev": true, "requires": { "ip": "^1.1.5", - "smart-buffer": "^4.0.1" + "smart-buffer": "4.0.2" } }, "socks-proxy-agent": { @@ -18873,12 +19615,11 @@ "dev": true }, "strong-log-transformer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strong-log-transformer/-/strong-log-transformer-2.0.0.tgz", - "integrity": "sha512-FQmNqAXJgOX8ygOcvPLlGWBNT41mvNJ9ALoYf0GTwVt9t30mGTqpmp/oJx5gLcu52DXK10kS7dVWhx8aPXDTlg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz", + "integrity": "sha512-B3Hgul+z0L9a236FAUC9iZsL+nVHgoCJnqCbN588DjYxvGXaXaaFbfmQ/JhvKjZwsOukuR72XbHv71Qkug0HxA==", "dev": true, "requires": { - "byline": "^5.0.0", "duplexer": "^0.1.1", "minimist": "^1.2.0", "through": "^2.3.4" @@ -19826,65 +20567,32 @@ "integrity": "sha512-LtzwHlVHwFGTptfNSgezHp7WUlwiqb0gA9AALRbKaERfxwJoiX0A73QbTToxteIAuIaFshhgIZfqK8s7clqgnA==" }, "uglify-js": { - "version": "2.8.29", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", - "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", + "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", "dev": true, "optional": true, "requires": { - "source-map": "~0.5.1", - "uglify-to-browserify": "~1.0.0", - "yargs": "~3.10.0" + "commander": "~2.17.1", + "source-map": "~0.6.1" }, "dependencies": { - "camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", "dev": true, "optional": true }, - "cliui": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", - "dev": true, - "optional": true, - "requires": { - "center-align": "^0.1.1", - "right-align": "^0.1.1", - "wordwrap": "0.0.2" - } - }, - "wordwrap": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "optional": true - }, - "yargs": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", - "dev": true, - "optional": true, - "requires": { - "camelcase": "^1.0.2", - "cliui": "^2.1.0", - "decamelize": "^1.0.0", - "window-size": "0.1.0" - } } } }, - "uglify-to-browserify": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", - "dev": true, - "optional": true - }, "uglifyjs-webpack-plugin": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.2.7.tgz", @@ -20119,6 +20827,15 @@ "unist-util-is": "^2.1.2" } }, + "universal-user-agent": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-2.0.3.tgz", + "integrity": "sha512-eRHEHhChCBHrZsA4WEhdgiOKgdvgrMIHwnwnqD0r5C6AO8kwKcG7qSku3iXdhvHL3YvsS9ZkSGN8h/hIpoFC8g==", + "dev": true, + "requires": { + "os-name": "^3.0.0" + } + }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -20214,6 +20931,12 @@ } } }, + "url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha1-/FZaPMy/93MMd19WQflVV5FDnyE=", + "dev": true + }, "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", @@ -21387,12 +22110,44 @@ "string-width": "^1.0.2 || 2" } }, - "window-size": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "windows-release": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-3.1.0.tgz", + "integrity": "sha512-hBb7m7acFgQPQc222uEQTmdcGLeBmQLNLFIh0rDk3CwFOBrfjefLzEfEfmpMq8Af/n/GnFf3eYf203FY1PmudA==", "dev": true, - "optional": true + "requires": { + "execa": "^0.10.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", + "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + } + } }, "wordwrap": { "version": "1.0.0", @@ -21536,12 +22291,6 @@ "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", "dev": true }, - "xregexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.0.0.tgz", - "integrity": "sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg==", - "dev": true - }, "xtend": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", diff --git a/package.json b/package.json index 40559e9c8d477..07cd37a6a9364 100644 --- a/package.json +++ b/package.json @@ -52,10 +52,10 @@ "@wordpress/wordcount": "file:packages/wordcount" }, "devDependencies": { - "@babel/core": "7.0.0", - "@babel/plugin-syntax-jsx": "7.0.0", - "@babel/runtime-corejs2": "7.0.0", - "@babel/traverse": "7.0.0", + "@babel/core": "7.2.2", + "@babel/plugin-syntax-jsx": "7.2.0", + "@babel/runtime-corejs2": "7.3.1", + "@babel/traverse": "7.2.3", "@wordpress/babel-plugin-import-jsx-pragma": "file:packages/babel-plugin-import-jsx-pragma", "@wordpress/babel-plugin-makepot": "file:packages/babel-plugin-makepot", "@wordpress/babel-preset-default": "file:packages/babel-preset-default", @@ -71,7 +71,7 @@ "@wordpress/npm-package-json-lint-config": "file:packages/npm-package-json-lint-config", "@wordpress/postcss-themes": "file:packages/postcss-themes", "@wordpress/scripts": "file:packages/scripts", - "babel-loader": "8.0.0", + "babel-loader": "8.0.5", "benchmark": "2.1.4", "browserslist": "4.4.1", "chalk": "2.4.1", @@ -92,9 +92,9 @@ "is-equal-shallow": "0.1.3", "is-plain-obj": "1.1.0", "jsdom": "11.12.0", - "lerna": "3.4.3", + "lerna": "3.11.1", "lint-staged": "7.3.0", - "lodash": "4.17.10", + "lodash": "4.17.11", "mkdirp": "0.5.1", "node-sass": "4.11.0", "node-watch": "0.6.0", diff --git a/packages/a11y/package.json b/packages/a11y/package.json index 48719ae8f3948..975ab31b994b3 100644 --- a/packages/a11y/package.json +++ b/packages/a11y/package.json @@ -21,7 +21,7 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/dom-ready": "file:../dom-ready" }, "publishConfig": { diff --git a/packages/annotations/package.json b/packages/annotations/package.json index a259d8a4d55ac..ebbc0b39ca84f 100644 --- a/packages/annotations/package.json +++ b/packages/annotations/package.json @@ -20,12 +20,12 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/data": "file:../data", "@wordpress/hooks": "file:../hooks", "@wordpress/i18n": "file:../i18n", "@wordpress/rich-text": "file:../rich-text", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "memize": "^1.0.5", "rememo": "^3.0.0", "uuid": "^3.3.2" diff --git a/packages/api-fetch/package.json b/packages/api-fetch/package.json index 7c3bc9c5ed17e..8f9b6568013a7 100644 --- a/packages/api-fetch/package.json +++ b/packages/api-fetch/package.json @@ -21,7 +21,7 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/i18n": "file:../i18n", "@wordpress/url": "file:../url" }, diff --git a/packages/autop/package.json b/packages/autop/package.json index c99aebd3b0f35..9735d5475aac5 100644 --- a/packages/autop/package.json +++ b/packages/autop/package.json @@ -20,7 +20,7 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.0.0" + "@babel/runtime": "^7.3.1" }, "publishConfig": { "access": "public" diff --git a/packages/babel-plugin-import-jsx-pragma/package.json b/packages/babel-plugin-import-jsx-pragma/package.json index 12637993733e6..7dac8d0786bfb 100644 --- a/packages/babel-plugin-import-jsx-pragma/package.json +++ b/packages/babel-plugin-import-jsx-pragma/package.json @@ -26,7 +26,7 @@ "main": "build/index.js", "module": "build-module/index.js", "dependencies": { - "@babel/runtime": "^7.0.0" + "@babel/runtime": "^7.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" diff --git a/packages/babel-plugin-makepot/package.json b/packages/babel-plugin-makepot/package.json index 27797796bc920..1dfd50b4c9984 100644 --- a/packages/babel-plugin-makepot/package.json +++ b/packages/babel-plugin-makepot/package.json @@ -25,9 +25,9 @@ "main": "build/index.js", "module": "build-module/index.js", "dependencies": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "gettext-parser": "^1.3.1", - "lodash": "^4.17.10" + "lodash": "^4.17.11" }, "peerDependencies": { "@babel/core": "^7.0.0" diff --git a/packages/babel-preset-default/package.json b/packages/babel-preset-default/package.json index 1a22f93300864..ae77d77c8768b 100644 --- a/packages/babel-preset-default/package.json +++ b/packages/babel-preset-default/package.json @@ -23,13 +23,13 @@ }, "main": "index.js", "dependencies": { - "@babel/core": "^7.0.0", - "@babel/plugin-proposal-async-generator-functions": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.0.0", - "@babel/plugin-transform-react-jsx": "^7.0.0", - "@babel/plugin-transform-runtime": "^7.0.0", - "@babel/preset-env": "^7.0.0", - "@babel/runtime": "^7.0.0", + "@babel/core": "^7.2.2", + "@babel/plugin-proposal-async-generator-functions": "^7.2.0", + "@babel/plugin-proposal-object-rest-spread": "^7.3.2", + "@babel/plugin-transform-react-jsx": "^7.3.0", + "@babel/plugin-transform-runtime": "^7.2.0", + "@babel/preset-env": "^7.3.1", + "@babel/runtime": "^7.3.1", "@wordpress/browserslist-config": "file:../browserslist-config", "babel-core": "^7.0.0-bridge.0" }, diff --git a/packages/blob/package.json b/packages/blob/package.json index 01119602f55ed..f8c08ee648276 100644 --- a/packages/blob/package.json +++ b/packages/blob/package.json @@ -20,7 +20,7 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.0.0" + "@babel/runtime": "^7.3.1" }, "publishConfig": { "access": "public" diff --git a/packages/block-library/package.json b/packages/block-library/package.json index d5287d21ff4a7..431108dd29f3a 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -20,7 +20,7 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/autop": "file:../autop", "@wordpress/blob": "file:../blob", "@wordpress/blocks": "file:../blocks", @@ -36,7 +36,7 @@ "@wordpress/keycodes": "file:../keycodes", "@wordpress/viewport": "file:../viewport", "classnames": "^2.2.5", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "memize": "^1.0.5", "url": "^0.11.0" }, diff --git a/packages/block-serialization-default-parser/package.json b/packages/block-serialization-default-parser/package.json index 5ff1c754c2b3f..d698187f122ec 100644 --- a/packages/block-serialization-default-parser/package.json +++ b/packages/block-serialization-default-parser/package.json @@ -21,7 +21,7 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.0.0" + "@babel/runtime": "^7.3.1" }, "publishConfig": { "access": "public" diff --git a/packages/blocks/package.json b/packages/blocks/package.json index 6ba8838680ead..077d6f5a228c9 100644 --- a/packages/blocks/package.json +++ b/packages/blocks/package.json @@ -20,7 +20,7 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/autop": "file:../autop", "@wordpress/blob": "file:../blob", "@wordpress/block-serialization-default-parser": "file:../block-serialization-default-parser", @@ -34,7 +34,7 @@ "@wordpress/is-shallow-equal": "file:../is-shallow-equal", "@wordpress/shortcode": "file:../shortcode", "hpq": "^1.3.0", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "rememo": "^3.0.0", "showdown": "^1.8.6", "simple-html-tokenizer": "^0.4.1", diff --git a/packages/components/package.json b/packages/components/package.json index 7cfe766f40617..963e7b1e3545e 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -20,7 +20,7 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/a11y": "file:../a11y", "@wordpress/api-fetch": "file:../api-fetch", "@wordpress/compose": "file:../compose", @@ -36,7 +36,7 @@ "clipboard": "^2.0.1", "diff": "^3.5.0", "dom-scroll-into-view": "^1.2.1", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "memize": "^1.0.5", "moment": "^2.22.1", "mousetrap": "^1.6.2", diff --git a/packages/compose/package.json b/packages/compose/package.json index dc744f79ba6f2..c02e9d263c2e2 100644 --- a/packages/compose/package.json +++ b/packages/compose/package.json @@ -21,10 +21,10 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/element": "file:../element", "@wordpress/is-shallow-equal": "file:../is-shallow-equal", - "lodash": "^4.17.10" + "lodash": "^4.17.11" }, "publishConfig": { "access": "public" diff --git a/packages/core-data/package.json b/packages/core-data/package.json index 388ab7b45e8f0..e9e2bb3c97a48 100644 --- a/packages/core-data/package.json +++ b/packages/core-data/package.json @@ -21,13 +21,13 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/api-fetch": "file:../api-fetch", "@wordpress/data": "file:../data", "@wordpress/deprecated": "file:../deprecated", "@wordpress/url": "file:../url", "equivalent-key-map": "^0.2.2", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "rememo": "^3.0.0" }, "publishConfig": { diff --git a/packages/custom-templated-path-webpack-plugin/package.json b/packages/custom-templated-path-webpack-plugin/package.json index 62c7effedda55..4f41dc79fb064 100644 --- a/packages/custom-templated-path-webpack-plugin/package.json +++ b/packages/custom-templated-path-webpack-plugin/package.json @@ -24,7 +24,7 @@ "main": "build/index.js", "module": "build-module/index.js", "dependencies": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "escape-string-regexp": "^1.0.5" }, "peerDependencies": { diff --git a/packages/data/package.json b/packages/data/package.json index 399cc933e689a..96264a852eadd 100644 --- a/packages/data/package.json +++ b/packages/data/package.json @@ -21,7 +21,7 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/compose": "file:../compose", "@wordpress/element": "file:../element", "@wordpress/is-shallow-equal": "file:../is-shallow-equal", @@ -29,7 +29,7 @@ "@wordpress/redux-routine": "file:../redux-routine", "equivalent-key-map": "^0.2.2", "is-promise": "^2.1.0", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "redux": "^4.0.0", "turbo-combine-reducers": "^1.0.2" }, diff --git a/packages/date/package.json b/packages/date/package.json index 7378306fff28c..3aa2437a296bd 100644 --- a/packages/date/package.json +++ b/packages/date/package.json @@ -20,7 +20,7 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "moment": "^2.22.1", "moment-timezone": "^0.5.16" }, diff --git a/packages/deprecated/package.json b/packages/deprecated/package.json index b54678dc9969a..6d0dccf40317b 100644 --- a/packages/deprecated/package.json +++ b/packages/deprecated/package.json @@ -20,7 +20,7 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/hooks": "file:../hooks" }, "publishConfig": { diff --git a/packages/dom-ready/package.json b/packages/dom-ready/package.json index 289590a356bae..2c377f2957314 100644 --- a/packages/dom-ready/package.json +++ b/packages/dom-ready/package.json @@ -19,7 +19,7 @@ "main": "build/index.js", "module": "build-module/index.js", "dependencies": { - "@babel/runtime": "^7.0.0" + "@babel/runtime": "^7.3.1" }, "publishConfig": { "access": "public" diff --git a/packages/dom/package.json b/packages/dom/package.json index 88e637933eae4..5577a2dc31ddc 100644 --- a/packages/dom/package.json +++ b/packages/dom/package.json @@ -20,8 +20,8 @@ "main": "build/index.js", "module": "build-module/index.js", "dependencies": { - "@babel/runtime": "^7.0.0", - "lodash": "^4.17.10" + "@babel/runtime": "^7.3.1", + "lodash": "^4.17.11" }, "publishConfig": { "access": "public" diff --git a/packages/e2e-test-utils/package.json b/packages/e2e-test-utils/package.json index c726fdc74dbb1..a297150615e24 100644 --- a/packages/e2e-test-utils/package.json +++ b/packages/e2e-test-utils/package.json @@ -26,7 +26,7 @@ "dependencies": { "@wordpress/keycodes": "file:../keycodes", "@wordpress/url": "file:../url", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "node-fetch": "^1.7.3" }, "peerDependencies": { diff --git a/packages/e2e-tests/package.json b/packages/e2e-tests/package.json index 98923f477d2b5..9dad36681bc9b 100644 --- a/packages/e2e-tests/package.json +++ b/packages/e2e-tests/package.json @@ -22,7 +22,7 @@ "@wordpress/jest-console": "file:../jest-console", "@wordpress/scripts": "file:../scripts", "expect-puppeteer": "^3.2.0", - "lodash": "^4.17.10" + "lodash": "^4.17.11" }, "peerDependencies": { "jest": ">=23", diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json index 875736be903a9..88a7818e2fac8 100644 --- a/packages/edit-post/package.json +++ b/packages/edit-post/package.json @@ -19,7 +19,7 @@ "main": "build/index.js", "module": "build-module/index.js", "dependencies": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/a11y": "file:../a11y", "@wordpress/api-fetch": "file:../api-fetch", "@wordpress/block-library": "file:../block-library", @@ -39,7 +39,7 @@ "@wordpress/url": "file:../url", "@wordpress/viewport": "file:../viewport", "classnames": "^2.2.5", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "refx": "^3.0.0" }, "publishConfig": { diff --git a/packages/editor/package.json b/packages/editor/package.json index 36b3ed87d7ec4..c82d754733f61 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -20,7 +20,7 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/a11y": "file:../a11y", "@wordpress/api-fetch": "file:../api-fetch", "@wordpress/blob": "file:../blob", @@ -47,7 +47,7 @@ "classnames": "^2.2.5", "dom-scroll-into-view": "^1.2.1", "inherits": "^2.0.3", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "memize": "^1.0.5", "react-autosize-textarea": "^3.0.2", "redux-multi": "^0.1.12", diff --git a/packages/element/package.json b/packages/element/package.json index 94c33e94f0cb5..24df06e6702af 100644 --- a/packages/element/package.json +++ b/packages/element/package.json @@ -21,9 +21,9 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/escape-html": "file:../escape-html", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "react": "^16.6.3", "react-dom": "^16.6.3" }, diff --git a/packages/escape-html/package.json b/packages/escape-html/package.json index c70aee37a4b3b..6fb68c88808ec 100644 --- a/packages/escape-html/package.json +++ b/packages/escape-html/package.json @@ -19,7 +19,7 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.0.0" + "@babel/runtime": "^7.3.1" }, "publishConfig": { "access": "public" diff --git a/packages/format-library/package.json b/packages/format-library/package.json index 1b11c71283258..c3e3d6b018950 100644 --- a/packages/format-library/package.json +++ b/packages/format-library/package.json @@ -20,7 +20,7 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/components": "file:../components", "@wordpress/dom": "file:../dom", "@wordpress/editor": "file:../editor", diff --git a/packages/hooks/package.json b/packages/hooks/package.json index 4e4cf9e95cf19..b4868eb1f4ed6 100644 --- a/packages/hooks/package.json +++ b/packages/hooks/package.json @@ -20,7 +20,7 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.0.0" + "@babel/runtime": "^7.3.1" }, "publishConfig": { "access": "public" diff --git a/packages/html-entities/package.json b/packages/html-entities/package.json index 94fccaa33295c..002fd8b8d4a4e 100644 --- a/packages/html-entities/package.json +++ b/packages/html-entities/package.json @@ -21,7 +21,7 @@ "main": "build/index.js", "module": "build-module/index.js", "dependencies": { - "@babel/runtime": "^7.0.0" + "@babel/runtime": "^7.3.1" }, "publishConfig": { "access": "public" diff --git a/packages/i18n/package.json b/packages/i18n/package.json index 57535a5b81f79..e05c99de5c587 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -23,9 +23,9 @@ "pot-to-php": "./tools/pot-to-php.js" }, "dependencies": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "gettext-parser": "^1.3.1", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "memize": "^1.0.5", "sprintf-js": "^1.1.1", "tannin": "^1.0.1" diff --git a/packages/is-shallow-equal/package.json b/packages/is-shallow-equal/package.json index 5826f4e45b8f7..d293276b1e946 100644 --- a/packages/is-shallow-equal/package.json +++ b/packages/is-shallow-equal/package.json @@ -24,7 +24,7 @@ ], "main": "index.js", "dependencies": { - "@babel/runtime": "^7.0.0" + "@babel/runtime": "^7.3.1" }, "publishConfig": { "access": "public" diff --git a/packages/jest-console/package.json b/packages/jest-console/package.json index f2b63d889ac04..424b590cf64ad 100644 --- a/packages/jest-console/package.json +++ b/packages/jest-console/package.json @@ -25,9 +25,9 @@ "main": "build/index.js", "module": "build-module/index.js", "dependencies": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "jest-matcher-utils": "^23.6.0", - "lodash": "^4.17.10" + "lodash": "^4.17.11" }, "peerDependencies": { "jest": ">=22" diff --git a/packages/keycodes/package.json b/packages/keycodes/package.json index 132759c8ed323..12a8c50895e66 100644 --- a/packages/keycodes/package.json +++ b/packages/keycodes/package.json @@ -20,9 +20,9 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/i18n": "file:../i18n", - "lodash": "^4.17.10" + "lodash": "^4.17.11" }, "publishConfig": { "access": "public" diff --git a/packages/library-export-default-webpack-plugin/package.json b/packages/library-export-default-webpack-plugin/package.json index c43a22704ef85..7222df5a7b4ff 100644 --- a/packages/library-export-default-webpack-plugin/package.json +++ b/packages/library-export-default-webpack-plugin/package.json @@ -23,8 +23,8 @@ ], "main": "build/index.js", "dependencies": { - "@babel/runtime": "^7.0.0", - "lodash": "^4.17.10", + "@babel/runtime": "^7.3.1", + "lodash": "^4.17.11", "webpack-sources": "^1.1.0" }, "peerDependencies": { diff --git a/packages/list-reusable-blocks/package.json b/packages/list-reusable-blocks/package.json index 06ac020007686..ca1bcefadedc6 100644 --- a/packages/list-reusable-blocks/package.json +++ b/packages/list-reusable-blocks/package.json @@ -19,13 +19,13 @@ "main": "build/index.js", "module": "build-module/index.js", "dependencies": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/api-fetch": "file:../api-fetch", "@wordpress/components": "file:../components", "@wordpress/compose": "file:../compose", "@wordpress/element": "file:../element", "@wordpress/i18n": "file:../i18n", - "lodash": "^4.17.10" + "lodash": "^4.17.11" }, "publishConfig": { "access": "public" diff --git a/packages/notices/package.json b/packages/notices/package.json index 6e3ad5374faba..aacbd324448f1 100644 --- a/packages/notices/package.json +++ b/packages/notices/package.json @@ -19,10 +19,10 @@ "main": "build/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/a11y": "file:../a11y", "@wordpress/data": "file:../data", - "lodash": "^4.17.10" + "lodash": "^4.17.11" }, "publishConfig": { "access": "public" diff --git a/packages/nux/package.json b/packages/nux/package.json index aadfc11b17e13..0476ec4e77a0f 100644 --- a/packages/nux/package.json +++ b/packages/nux/package.json @@ -20,13 +20,13 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/components": "file:../components", "@wordpress/compose": "file:../compose", "@wordpress/data": "file:../data", "@wordpress/element": "file:../element", "@wordpress/i18n": "file:../i18n", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "rememo": "^3.0.0" }, "publishConfig": { diff --git a/packages/plugins/package.json b/packages/plugins/package.json index 2475c6e9a557f..1050d8aa78554 100644 --- a/packages/plugins/package.json +++ b/packages/plugins/package.json @@ -19,11 +19,11 @@ "main": "build/index.js", "module": "build-module/index.js", "dependencies": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/compose": "file:../compose", "@wordpress/element": "file:../element", "@wordpress/hooks": "file:../hooks", - "lodash": "^4.17.10" + "lodash": "^4.17.11" }, "publishConfig": { "access": "public" diff --git a/packages/postcss-themes/package.json b/packages/postcss-themes/package.json index ced2f27afbd11..0836e71badf9a 100644 --- a/packages/postcss-themes/package.json +++ b/packages/postcss-themes/package.json @@ -26,7 +26,7 @@ ], "main": "build/index.js", "dependencies": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "autoprefixer": "^9.4.5", "postcss": "^7.0.13", "postcss-color-function": "^4.0.1" diff --git a/packages/priority-queue/package.json b/packages/priority-queue/package.json index 5b0ba8a400fb8..cf921b4739cef 100644 --- a/packages/priority-queue/package.json +++ b/packages/priority-queue/package.json @@ -21,7 +21,7 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.0.0" + "@babel/runtime": "^7.3.1" }, "publishConfig": { "access": "public" diff --git a/packages/redux-routine/package.json b/packages/redux-routine/package.json index a60b67801d0c1..c1a73f09f8f92 100644 --- a/packages/redux-routine/package.json +++ b/packages/redux-routine/package.json @@ -22,7 +22,7 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "is-promise": "^2.1.0", "rungen": "^0.3.2" }, diff --git a/packages/rich-text/package.json b/packages/rich-text/package.json index 541cccc79cc56..cfae343520b6e 100644 --- a/packages/rich-text/package.json +++ b/packages/rich-text/package.json @@ -20,11 +20,11 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/compose": "file:../compose", "@wordpress/data": "file:../data", "@wordpress/escape-html": "file:../escape-html", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "rememo": "^3.0.0" }, "publishConfig": { diff --git a/packages/shortcode/package.json b/packages/shortcode/package.json index adc6bddfd13c6..128647ed2d35a 100644 --- a/packages/shortcode/package.json +++ b/packages/shortcode/package.json @@ -19,8 +19,8 @@ "main": "build/index.js", "module": "build-module/index.js", "dependencies": { - "@babel/runtime": "^7.0.0", - "lodash": "^4.17.10", + "@babel/runtime": "^7.3.1", + "lodash": "^4.17.11", "memize": "^1.0.5" }, "publishConfig": { diff --git a/packages/token-list/package.json b/packages/token-list/package.json index 96543d049246f..57b74a9c092e8 100644 --- a/packages/token-list/package.json +++ b/packages/token-list/package.json @@ -18,8 +18,8 @@ "main": "build/index.js", "module": "build-module/index.js", "dependencies": { - "@babel/runtime": "^7.0.0", - "lodash": "^4.17.10" + "@babel/runtime": "^7.3.1", + "lodash": "^4.17.11" }, "publishConfig": { "access": "public" diff --git a/packages/url/package.json b/packages/url/package.json index 18bdecd4c1d96..6257dbc7c8674 100644 --- a/packages/url/package.json +++ b/packages/url/package.json @@ -20,7 +20,7 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "qs": "^6.5.2" }, "publishConfig": { diff --git a/packages/viewport/package.json b/packages/viewport/package.json index a1c94c73801c4..5c38011385207 100644 --- a/packages/viewport/package.json +++ b/packages/viewport/package.json @@ -20,11 +20,11 @@ "module": "build-module/index.js", "react-native": "src/index", "dependencies": { - "@babel/runtime": "^7.0.0", + "@babel/runtime": "^7.3.1", "@wordpress/compose": "file:../compose", "@wordpress/data": "file:../data", "@wordpress/element": "file:../element", - "lodash": "^4.17.10" + "lodash": "^4.17.11" }, "publishConfig": { "access": "public" diff --git a/packages/wordcount/package.json b/packages/wordcount/package.json index c759b1c50ea22..c31b0009aad30 100644 --- a/packages/wordcount/package.json +++ b/packages/wordcount/package.json @@ -19,8 +19,8 @@ "main": "build/index.js", "module": "build-module/index.js", "dependencies": { - "@babel/runtime": "^7.0.0", - "lodash": "^4.17.10" + "@babel/runtime": "^7.3.1", + "lodash": "^4.17.11" }, "publishConfig": { "access": "public" From 239b886b38863aa968869c457b39fd84c6491483 Mon Sep 17 00:00:00 2001 From: John <johng75@gmail.com> Date: Fri, 15 Feb 2019 10:20:19 +0000 Subject: [PATCH 442/691] Check for empty range before creating undo record (#11209) window.getSelection() can return an empty range, causing getRangeAt() to fail --- packages/editor/src/components/rich-text/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/editor/src/components/rich-text/index.js b/packages/editor/src/components/rich-text/index.js index 59ba6e6e4225c..ca4a598acb275 100644 --- a/packages/editor/src/components/rich-text/index.js +++ b/packages/editor/src/components/rich-text/index.js @@ -157,7 +157,8 @@ export class RichText extends Component { } createRecord() { - const range = getSelection().getRangeAt( 0 ); + const selection = getSelection(); + const range = selection.rangeCount > 0 ? selection.getRangeAt( 0 ) : null; return create( { element: this.editableRef, From 7db4af172860d50789b2430405db61c66ec77902 Mon Sep 17 00:00:00 2001 From: Andrea Fercia <a.fercia@gmail.com> Date: Fri, 15 Feb 2019 12:19:35 +0100 Subject: [PATCH 443/691] Navigate regions landmarks style and position improvements (#8554) * Improve publish panel landmark and button position. * Use outline for navigate regions focus style. * Avoid conflict with publish panel animation. * Make the outline animation expand inwards. * CSS lint after rebase. * Rebuild outline animation. * Try negative z-indexes. * Change animation timing to 0.1s. --- assets/stylesheets/_z-index.scss | 3 +- .../higher-order/navigate-regions/style.scss | 23 ++++++++++--- .../src/components/layout/style.scss | 34 ++++++++++++++----- .../src/components/sidebar/style.scss | 2 ++ 4 files changed, 49 insertions(+), 13 deletions(-) diff --git a/assets/stylesheets/_z-index.scss b/assets/stylesheets/_z-index.scss index 462824c88c578..8efd108ba73d0 100644 --- a/assets/stylesheets/_z-index.scss +++ b/assets/stylesheets/_z-index.scss @@ -10,7 +10,8 @@ $z-layers: ( ".editor-block-list__layout .reusable-block-indicator": 1, ".editor-block-list__breadcrumb": 2, ".components-form-toggle__input": 1, - ".components-panel__header.edit-post-sidebar__panel-tabs": 1, + ".components-panel__header.edit-post-sidebar__panel-tabs": -1, + ".edit-post-sidebar .components-panel": -2, ".editor-inserter__tabs": 1, ".editor-inserter__tab.is-active": 1, ".components-panel__header": 1, diff --git a/packages/components/src/higher-order/navigate-regions/style.scss b/packages/components/src/higher-order/navigate-regions/style.scss index 3d50aef11067f..add768095220d 100644 --- a/packages/components/src/higher-order/navigate-regions/style.scss +++ b/packages/components/src/higher-order/navigate-regions/style.scss @@ -1,4 +1,5 @@ .components-navigate-regions.is-focusing-regions [role="region"] { + // For browsers that don't support outline-offset (IE11). &:focus::after { content: ""; position: absolute; @@ -8,16 +9,30 @@ right: 0; pointer-events: none; outline: 4px solid transparent; // Shown in Windows High Contrast mode. - animation: editor-animation__region-focus 0.2s ease-out; - animation-fill-mode: forwards; + box-shadow: inset 0 0 0 4px $blue-medium-400; + } + + @supports ( outline-offset: 1px ) { + &:focus::after { + content: none; + } + + &:focus { + outline-style: solid; + outline-color: $blue-medium-400; + animation: editor-animation__region-focus 0.1s ease-out; + animation-fill-mode: forwards; + } } } @keyframes editor-animation__region-focus { from { - box-shadow: inset 0 0 0 0 $blue-medium-400; + outline-width: 0; + outline-offset: 0; } to { - box-shadow: inset 0 0 0 4px $blue-medium-400; + outline-width: 4px; + outline-offset: -4px; } } diff --git a/packages/edit-post/src/components/layout/style.scss b/packages/edit-post/src/components/layout/style.scss index dfbfaf77dd356..504b9fbe7ac82 100644 --- a/packages/edit-post/src/components/layout/style.scss +++ b/packages/edit-post/src/components/layout/style.scss @@ -182,6 +182,11 @@ body.is-fullscreen-mode & { top: 0; } + + // Keep it open on focus to avoid conflict with navigate-regions animation. + .is-focusing-regions & { + transform: translateX(0%); + } } } @@ -206,28 +211,41 @@ } .edit-post-toggle-publish-panel { - position: absolute; - bottom: 0; + position: fixed; + top: -9999em; + bottom: auto; + left: auto; right: 0; z-index: z-index(".edit-post-toggle-publish-panel"); + padding: 10px 10px 10px 0; width: $sidebar-width; - height: 0; - overflow: hidden; - &:focus-within { - height: auto; - padding: 20px 0 0 0; + background-color: $white; + + &:focus { + top: auto; + bottom: 0; } .edit-post-toggle-publish-panel__button { - float: right; width: auto; height: auto; + display: block; font-size: 14px; font-weight: 600; + margin: 0 0 0 auto; padding: 15px 23px 14px; line-height: normal; text-decoration: none; outline: none; background: #f1f1f1; + color: theme(secondary); + + &:focus { + position: fixed; + top: auto; + right: 10px; + bottom: 10px; + left: auto; + } } } diff --git a/packages/edit-post/src/components/sidebar/style.scss b/packages/edit-post/src/components/sidebar/style.scss index cd8256823d4e7..823256093ffbb 100644 --- a/packages/edit-post/src/components/sidebar/style.scss +++ b/packages/edit-post/src/components/sidebar/style.scss @@ -36,6 +36,8 @@ max-height: calc(100vh - #{ $admin-bar-height-big + $panel-header-height }); margin-top: -1px; margin-bottom: -1px; + position: relative; + z-index: z-index(".edit-post-sidebar .components-panel"); @include break-small() { overflow: inherit; From 37e8cff2ab003cf2ae30781c21f293b437d4fa35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Fri, 15 Feb 2019 12:32:06 +0100 Subject: [PATCH 444/691] Documentation: update install commands and babel config (#13853) --- .../tutorials/javascript/js-build-setup.md | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md b/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md index 916e154de0763..4be9fcc1efbd1 100644 --- a/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md +++ b/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md @@ -74,9 +74,9 @@ Is this OK? (yes) yes ### Using npm to install packages -The next step is to install the packages required. You can install packages using the npm command `npm install`. If you pass the `--save-dev` parameter, npm will write the package as a dev dependency in the package.json file. +The next step is to install the packages required. You can install packages using the npm command `npm install`. If you pass the `--save-dev` parameter, npm will write the package as a dev dependency in the package.json file. The `--save-exact` parameter instructs npm to save an exact version of a dependency, not a range of valid versions. See [npm install documentation](https://docs.npmjs.com/cli/install) for more details. -Run `npm install --save-dev webpack` +Run `npm install --save-dev --save-exact webpack webpack-cli` After installing, a `node_modules` directory is created with the webpack module and its dependencies. @@ -84,7 +84,7 @@ Also, if you look at package.json file it will include a new section: ```json "dependencies": { - "webpack": "^4.29.0" + "webpack": "4.29.0" } ``` @@ -127,16 +127,14 @@ module.exports = { Next, you need to install babel, the webpack loader, and the JSX plugin using: -``` -npm install --save babel-loader babel-core babel-plugin-transform-react-jsx -``` +> npm install --save-dev --save-exact babel-loader @babel/core @babel/plugin-transform-react-jsx You configure babel by creating a `.babelrc` file: -``` +```json { "plugins": [ - [ "transform-react-jsx", { + [ "@babel/plugin-transform-react-jsx", { "pragma": "wp.element.createElement" } ] ] @@ -192,7 +190,7 @@ The mode is setup so it can be configured using environment variables, which can }, ``` -This sets the environment variables, but different environments handle these settings in different ways. Using the `cross-env` helper module can help to handle this. Be sure to install the `cross-env` package using `npm install --save cross-env`. +This sets the environment variables, but different environments handle these settings in different ways. Using the `cross-env` helper module can help to handle this. Be sure to install the `cross-env` package using `npm install --save-dev --save-exact cross-env`. Additionally, webpack has a `--watch` flag that will keep the process running, watching for any changes to the `block.js` file and re-building as changes occur. This is useful during development, when you might have a lot of changes in progress. @@ -206,7 +204,7 @@ Babel has the ability to build JavaScript using rules that target certain browse WordPress has a preset default you can use to target the minimum supported browsers by WordPress. -Install the module using: `npm install --save @wordpress/babel-preset-default` +Install the module using: `npm install --save-dev --save-exact @wordpress/babel-preset-default` You then update `.babelrc` by adding a "presets" section: @@ -214,7 +212,7 @@ You then update `.babelrc` by adding a "presets" section: { "presets": [ "@wordpress/babel-preset-default" ], "plugins": [ - [ "transform-react-jsx", { + [ "@babel/plugin-transform-react-jsx", { "pragma": "wp.element.createElement" } ] ] From c1235d4486b36bab451f57866dc323451dc76963 Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.p.richards@gmail.com> Date: Fri, 15 Feb 2019 19:51:54 +0800 Subject: [PATCH 445/691] Add feature flags for Phase 2 (#13324) * Add new package for editor configuration, initially containing just feature flags Rework build commands to use correct NODE_ENV for feature flags * Revert "Rework build commands to use correct NODE_ENV for feature flags" This reverts commit 4cb0a39282d57abb8cd5c398ae20b9466d67b056. Revert "Add new package for editor configuration, initially containing just feature flags" This reverts commit 0c21fc215b4e6f0d4b415aabb9f8297635b7d148. * Switch to using webpack define plugin to inject a global GUTENBERG_PHASE variable * Iterate: use window.GUTENBERG_PHASE to avoid thrown errors from an undefined global * Add custom eslint rule for usage of GUTENBERG_PHASE * Disable new eslint rule when used in webpack config * Add readme * Include phase 2 features in e2e tests * Allow use of GUTENBERG_PHASE in a ternary and update documentation. * Add links to docs * Minor docs changes * Switch from window.GUTENBERG_PHASE to process.env.GUTENBERG_PHASE * Update docs for feature flags. Move `Basic Use` section higher up, and simplify a sentence * Ensure GUTENBERG_PHASE environment variable is available for webpack * Ensure GUTENBERG_PHASE is a number * Ensure GUTENBERG_PHASE is set in unit tests * Use <rootDir> in jest config * switch to using package.json config to define the value of GUTENBERG_PHASE * Sort custom lint rules alphabetically * Add comment about GUTENBERG_PHASE * Update jest config for GUTENBERG_PHASE * Add webpack as dependency to main package.json --- .../developers/feature-flags.md | 110 +++ docs/manifest.json | 6 + docs/toc.json | 1 + package-lock.json | 723 +++++++++++++++--- package.json | 4 + packages/e2e-tests/config/gutenberg-phase.js | 6 + packages/e2e-tests/jest.config.js | 3 + packages/eslint-plugin/CHANGELOG.md | 1 + packages/eslint-plugin/README.md | 3 +- packages/eslint-plugin/configs/custom.js | 1 + .../docs/rules/gutenberg-phase.md | 62 ++ .../rules/__tests__/gutenberg-phase.js | 60 ++ .../eslint-plugin/rules/gutenberg-phase.js | 165 ++++ test/unit/config/gutenberg-phase.js | 6 + test/unit/jest.config.json | 3 +- webpack.config.js | 6 + 16 files changed, 1068 insertions(+), 92 deletions(-) create mode 100644 docs/designers-developers/developers/feature-flags.md create mode 100644 packages/e2e-tests/config/gutenberg-phase.js create mode 100644 packages/eslint-plugin/docs/rules/gutenberg-phase.md create mode 100644 packages/eslint-plugin/rules/__tests__/gutenberg-phase.js create mode 100644 packages/eslint-plugin/rules/gutenberg-phase.js create mode 100644 test/unit/config/gutenberg-phase.js diff --git a/docs/designers-developers/developers/feature-flags.md b/docs/designers-developers/developers/feature-flags.md new file mode 100644 index 0000000000000..47a28d0fa42c3 --- /dev/null +++ b/docs/designers-developers/developers/feature-flags.md @@ -0,0 +1,110 @@ +# Feature Flags + +With phase 2 of the Gutenberg project there's a need for improved control over how code changes are released. Newer features developed for phase 2 and beyond should only be released to the Gutenberg plugin, while improvements and bug fixes should still continue to make their way into core releases. + +The technique for handling this is known as a 'feature flag'. + +## Introducing `process.env.GUTENBERG_PHASE` + +The `process.env.GUTENBERG_PHASE` is an environment variable containing a number that represents the phase. When the codebase is built for the plugin, this variable will be set to `2`. When building for core, it will be set to `1`. + +## Basic Use + +A phase 2 function or constant should be exported using the following ternary syntax: + +```js +function myPhaseTwoFeature() { + // implementation +} + +export const phaseTwoFeature = process.env.GUTENBERG_PHASE === 2 ? myPhaseTwoFeature : undefined; +``` + +In phase 1 environments the `phaseTwoFeature` export will be `undefined`. + +If you're attempting to import and call a phase 2 feature, be sure to wrap the call to the function in an if statement to avoid an error: +```js +import { phaseTwoFeature } from '@wordpress/foo'; + +if ( process.env.GUTENBERG_PHASE === 2) { + phaseTwoFeature(); +} +``` + +### How it works + +During the webpack build, any instances of `process.env.GUTENBERG_PHASE` will be replaced using webpack's define plugin (https://webpack.js.org/plugins/define-plugin/). + +If you write the following code: +```js +if ( process.env.GUTENBERG_PHASE === 2 ) { + phaseTwoFeature(); +} +``` + +When building the codebase for the plugin the variable will be replaced with the number literal `2`: +```js +if ( 2 === 2 ) { + phaseTwoFeature(); +} +``` + +Any code within the body of the if statement will be executed within the gutenberg plugin since `2 === 2` evaluates to `true`. + +For core, the `process.env.GUTENBERG_PHASE` variable is replaced with `1`, so the built code will look like: +```js +if ( 1 === 2 ) { + phaseTwoFeature(); +} +``` + +`1 === 2` evaluates to false so the phase 2 feature will not be executed within core. + +### Dead Code Elimination + +When building code for production, webpack 'minifies' code (https://en.wikipedia.org/wiki/Minification_(programming)), removing the amount of unnecessary JavaScript as much as possible. One of the steps involves something known as 'dead code elimination'. + +When the following code is encountered, webpack determines that the surrounding `if`statement is unnecessary: +```js +if ( 2 === 2 ) { + phaseTwoFeature(); +} +``` + + The condition will alway evaluates to `true`, so can be removed leaving just the code in the body: + ```js + phaseTwoFeature(); + ``` + +Similarly when building for core, the condition in the following `if` statement always resolves to false: +```js +if ( 1 === 2 ) { + phaseTwoFeature(); +} +``` + +The minification process will remove the entire `if` statement including the body, ensuring code destined for phase 2 is not included in the built JavaScript intended for core. + +## FAQ + +#### Why should I only use `===` or `!==` when comparing `process.env.GUTENBERG_PHASE` and not `>`, `>=`, `<` or `<=`? + +This is a restriction due to the behaviour of the greater than or less than operators in JavaScript when `process.env.GUTENBERG_PHASE` is undefined, as might be the case for third party users of WordPress npm packages. Both `process.env.GUTENBERG_PHASE < 2` and `process.env.GUTENBERG_PHASE > 1` resolve to false. When writing `if ( process.env.GUTENBERG_PHASE > 1 )`, the intention might be to avoid executing the phase 2 code in the following `if` statement's body. That's fine since it will evaluate to false. + +However, the following code doesn't quite have the intended behaviour: + +``` +function myPhaseTwoFeature() { + if ( process.env.GUTENBERG_PHASE < 2 ) { + return; + } + + // implementation of phase 2 feature +} +``` + +Here an early return is used to avoid execution of a phase 2 feature, but because the `if` condition resolves to false, the early return is bypassed and the phase 2 feature is incorrectly triggered. + +#### Why shouldn't I assign the result of an expression involving `GUTENBERG_PHASE` to a variable, e.g. `const isMyFeatureActive = process.env.GUTENBERG_PHASE === 2`? + +The aim here is to avoid introducing any complexity that could result in webpack's minifier not being able to eliminate dead code. See the [Dead Code Elimination](#dead-code-elimination) section for further details. diff --git a/docs/manifest.json b/docs/manifest.json index 675c7034e52a1..038b93e03f25a 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -107,6 +107,12 @@ "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/accessibility.md", "parent": "developers" }, + { + "title": "Feature Flags", + "slug": "feature-flags", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/feature-flags.md", + "parent": "developers" + }, { "title": "Data Module Reference", "slug": "data", diff --git a/docs/toc.json b/docs/toc.json index d3e7b6048a130..8d87e52bc8975 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -19,6 +19,7 @@ ]}, {"docs/designers-developers/developers/internationalization.md": []}, {"docs/designers-developers/developers/accessibility.md": []}, + {"docs/designers-developers/developers/feature-flags.md": []}, {"docs/designers-developers/developers/data/README.md": "{{data}}"}, {"docs/designers-developers/developers/packages.md": "{{packages}}"}, {"packages/components/README.md": "{{components}}"}, diff --git a/package-lock.json b/package-lock.json index e59039690658a..69e25252f2a91 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3177,9 +3177,9 @@ } }, "ajv-keywords": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", - "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.0.tgz", + "integrity": "sha512-aUjdRFISbuFOl0EIZc+9e4FfZp0bDZgAdOOf30bJmw8VM9v84SHyVyxDfbWxpGYbdZD/9XoKxfHVNmxPkhwyGw==", "dev": true }, "alphanum-sort": { @@ -4147,9 +4147,9 @@ "dev": true }, "binary-extensions": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", - "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.0.tgz", + "integrity": "sha512-EgmjVLMn22z7eGGv3kcnHwSnJXmFHjISTY9E/S5lIcTD3Oxw05QTcBLNkJFzcb3cNueUdF/IN4U+d78V0zO8Hw==", "dev": true }, "bindings": { @@ -4761,26 +4761,573 @@ } }, "chokidar": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", - "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.1.tgz", + "integrity": "sha512-gfw3p2oQV2wEt+8VuMlNsPjCxDxvvgnm/kz+uATu805mWVF8IJN7uz9DN7iBz+RMJISmiVbCOBFs9qBGMjtPfQ==", "dev": true, "requires": { "anymatch": "^2.0.0", - "async-each": "^1.0.0", - "braces": "^2.3.0", - "fsevents": "^1.2.2", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", "glob-parent": "^3.1.0", - "inherits": "^2.0.1", + "inherits": "^2.0.3", "is-binary-path": "^1.0.0", "is-glob": "^4.0.0", - "lodash.debounce": "^4.0.8", - "normalize-path": "^2.1.1", + "normalize-path": "^3.0.0", "path-is-absolute": "^1.0.0", - "readdirp": "^2.0.0", - "upath": "^1.0.5" + "readdirp": "^2.2.1", + "upath": "^1.1.0" }, "dependencies": { + "fsevents": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.7.tgz", + "integrity": "sha512-Pxm6sI2MeBD7RdD12RYsqaP0nMiwx8eZBXCa6z2L+mRHm2DYrOYwihmhjpkdjUHwQhslWQjRpEgNq4XvBmaAuw==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.9.2", + "node-pre-gyp": "^0.10.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.24", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true, + "optional": true + }, + "minipass": { + "version": "2.3.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.2.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.10.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true, + "dev": true, + "optional": true + } + } + }, "glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", @@ -4816,6 +5363,12 @@ "requires": { "is-extglob": "^2.1.1" } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true } } }, @@ -6772,9 +7325,9 @@ "dev": true }, "elliptic": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz", - "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz", + "integrity": "sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==", "dev": true, "requires": { "bn.js": "^4.4.0", @@ -7365,9 +7918,9 @@ } }, "events": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", - "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", + "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==", "dev": true }, "evp_bytestokey": { @@ -9652,9 +10205,9 @@ } }, "hash.js": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.5.tgz", - "integrity": "sha512-eWI5HG9Np+eHV1KQhisXWwM+4EPPYe5dFX1UZZH7k/E3JzDEazVH+VGlZi6R94ZqImq+A3D1mCEtrFIfg/E7sA==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", "dev": true, "requires": { "inherits": "^2.0.3", @@ -12908,9 +13461,9 @@ } }, "loader-runner": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.3.0.tgz", - "integrity": "sha1-9IKuqC1UPgeSFwDVpG7yb9rGuKI=", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", + "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", "dev": true }, "loader-utils": { @@ -12979,12 +13532,6 @@ "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", "dev": true }, - "lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", - "dev": true - }, "lodash.escape": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", @@ -13401,13 +13948,14 @@ "dev": true }, "md5.js": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", - "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", "dev": true, "requires": { "hash-base": "^3.0.0", - "inherits": "^2.0.1" + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" } }, "mdast-util-compact": { @@ -14013,9 +14561,9 @@ "dev": true }, "node-libs-browser": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.1.0.tgz", - "integrity": "sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.0.tgz", + "integrity": "sha512-5MQunG/oyOaBdttrL40dA7bUfPORLRWMUJLQtMg7nluxUvk5XwnLdL9twQHFAjRx/y7mIMkLKT9++qPbbk6BZA==", "dev": true, "requires": { "assert": "^1.1.1", @@ -14025,7 +14573,7 @@ "constants-browserify": "^1.0.0", "crypto-browserify": "^3.11.0", "domain-browser": "^1.1.1", - "events": "^1.0.0", + "events": "^3.0.0", "https-browserify": "^1.0.0", "os-browserify": "^0.3.0", "path-browserify": "0.0.0", @@ -14039,7 +14587,7 @@ "timers-browserify": "^2.0.4", "tty-browserify": "0.0.0", "url": "^0.11.0", - "util": "^0.10.3", + "util": "^0.11.0", "vm-browserify": "0.0.4" }, "dependencies": { @@ -15090,9 +15638,9 @@ } }, "pako": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", - "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.8.tgz", + "integrity": "sha512-6i0HVbUfcKaTv+EG8ZTr75az7GFXcLYk9UyLEg7Notv/Ma+z/UG3TCoz6GiNeOrn1E/e63I0X/Hpw18jHOTUnA==", "dev": true }, "parallel-transform": { @@ -15124,16 +15672,17 @@ } }, "parse-asn1": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", - "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.4.tgz", + "integrity": "sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw==", "dev": true, "requires": { "asn1.js": "^4.0.0", "browserify-aes": "^1.0.0", "create-hash": "^1.1.0", "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3" + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" } }, "parse-entities": { @@ -15299,9 +15848,9 @@ } }, "pbkdf2": { - "version": "3.0.16", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.16.tgz", - "integrity": "sha512-y4CXP3thSxqf7c0qmOF+9UeOTrifiVTIM+u7NWlq+PRsHbr7r7dpCmvzrZxa96JJUNi0Y5w9VqG5ZNeCVMoDcA==", + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", + "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", "dev": true, "requires": { "create-hash": "^1.1.2", @@ -17278,16 +17827,17 @@ "dev": true }, "public-encrypt": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.2.tgz", - "integrity": "sha512-4kJ5Esocg8X3h8YgJsKAuoesBgB7mqH3eowiDzMUPKiRDDE7E/BqqZD1hnTByIaAFiwAw246YEltSq7tdrOH0Q==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", "dev": true, "requires": { "bn.js": "^4.1.0", "browserify-rsa": "^4.0.0", "create-hash": "^1.1.0", "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1" + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" } }, "pump": { @@ -17835,15 +18385,14 @@ } }, "readdirp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", - "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "minimatch": "^3.0.2", - "readable-stream": "^2.0.2", - "set-immediate-shim": "^1.0.1" + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" } }, "realpath-native": { @@ -18757,9 +19306,9 @@ } }, "schema-utils": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.5.tgz", - "integrity": "sha512-yYrjb9TX2k/J1Y5UNy3KYdZq10xhYcF8nMpAW6o3hy6Q8WSIEf9lJHG/ePnOBfziPM3fvQwfOwa13U/Fh8qTfA==", + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz", + "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==", "dev": true, "requires": { "ajv": "^6.1.0", @@ -18767,15 +19316,15 @@ }, "dependencies": { "ajv": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.2.tgz", - "integrity": "sha512-hOs7GfvI6tUI1LfZddH82ky6mOMyTuY0mk7kE2pWpmhhUSkumzaTO5vbVwij39MdwPQWCV4Zv57Eo06NtL/GVA==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.9.1.tgz", + "integrity": "sha512-XDN92U311aINL77ieWHmqCcNlwjoP5cHXDxIxbf2MaPYuCXOHS7gHH8jktxeK5omgd52XbSTX6a4Piwd1pQmzA==", "dev": true, "requires": { "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.1" + "uri-js": "^4.2.2" } }, "fast-deep-equal": { @@ -18891,12 +19440,6 @@ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, - "set-immediate-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", - "dev": true - }, "set-value": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", @@ -19453,9 +19996,9 @@ "dev": true }, "stream-browserify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", - "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", "dev": true, "requires": { "inherits": "~2.0.1", @@ -20594,9 +21137,9 @@ } }, "uglifyjs-webpack-plugin": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.2.7.tgz", - "integrity": "sha512-1VicfKhCYHLS8m1DCApqBhoulnASsEoJ/BvpUpP4zoNAPpKzdH+ghk0olGJMmwX2/jprK2j3hAHdUbczBSy2FA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.3.0.tgz", + "integrity": "sha512-ovHIch0AMlxjD/97j9AYovZxG5wnHOPkL7T1GKochBADp/Zwc44pEWNqpKl1Loupp1WhFg7SlYmHZRUfdAacgw==", "dev": true, "requires": { "cacache": "^10.0.4", @@ -20944,9 +21487,9 @@ "dev": true }, "util": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", - "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", + "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", "dev": true, "requires": { "inherits": "2.0.3" @@ -21245,9 +21788,9 @@ }, "dependencies": { "ajv": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.1.tgz", - "integrity": "sha512-ZoJjft5B+EJBjUyu9C9Hc0OZyPZSSlOF+plzouTrg6UlA8f+e/n8NIgBFG/9tppJtpPWfthHakK7juJdNDODww==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.9.1.tgz", + "integrity": "sha512-XDN92U311aINL77ieWHmqCcNlwjoP5cHXDxIxbf2MaPYuCXOHS7gHH8jktxeK5omgd52XbSTX6a4Piwd1pQmzA==", "dev": true, "requires": { "fast-deep-equal": "^2.0.1", diff --git a/package.json b/package.json index 07cd37a6a9364..758213c258bea 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,9 @@ "WordPress", "editor" ], + "config": { + "GUTENBERG_PHASE": 2 + }, "dependencies": { "@wordpress/a11y": "file:packages/a11y", "@wordpress/annotations": "file:packages/annotations", @@ -113,6 +116,7 @@ "sprintf-js": "1.1.1", "stylelint-config-wordpress": "13.1.0", "uuid": "3.3.2", + "webpack": "4.8.3", "webpack-bundle-analyzer": "3.0.2", "webpack-livereload-plugin": "2.1.1", "webpack-rtl-plugin": "github:yoavf/webpack-rtl-plugin#develop" diff --git a/packages/e2e-tests/config/gutenberg-phase.js b/packages/e2e-tests/config/gutenberg-phase.js new file mode 100644 index 0000000000000..1b6117b3236a6 --- /dev/null +++ b/packages/e2e-tests/config/gutenberg-phase.js @@ -0,0 +1,6 @@ +global.process.env = { + ...global.process.env, + // Inject the `GUTENBERG_PHASE` global, used for feature flagging. + // eslint-disable-next-line @wordpress/gutenberg-phase + GUTENBERG_PHASE: parseInt( process.env.npm_package_config_GUTENBERG_PHASE, 10 ), +}; diff --git a/packages/e2e-tests/jest.config.js b/packages/e2e-tests/jest.config.js index 9984044fd0ccd..f241715197b97 100644 --- a/packages/e2e-tests/jest.config.js +++ b/packages/e2e-tests/jest.config.js @@ -1,4 +1,7 @@ module.exports = { ...require( '@wordpress/scripts/config/jest-e2e.config' ), setupTestFrameworkScriptFile: '<rootDir>/config/setup-test-framework.js', + setupFiles: [ + '<rootDir>/config/gutenberg-phase.js', + ], }; diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index daf663e8157e9..4e3f6f8430996 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -9,6 +9,7 @@ - New Rule: [`@wordpress/no-unused-vars-before-return`](https://github.com/WordPress/gutenberg/blob/master/packages/eslint-plugin/docs/rules/no-unused-vars-before-return.md) - New Rule: [`@wordpress/dependency-group`](https://github.com/WordPress/gutenberg/blob/master/packages/eslint-plugin/docs/rules/dependency-group.md) - New Rule: [`@wordpress/valid-sprintf`](https://github.com/WordPress/gutenberg/blob/master/packages/eslint-plugin/docs/rules/valid-sprintf.md) +- New Rule: [`@wordpress/gutenberg-phase`](https://github.com/WordPress/gutenberg/blob/master/packages/eslint-plugin/docs/rules/gutenberg-phase.md) ## 1.0.0 (2018-12-12) diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 2af095ba3afec..cd1ab687823cd 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -49,8 +49,9 @@ The granular rulesets will not define any environment globals. As such, if they Rule|Description ---|--- -[no-unused-vars-before-return](/packages/eslint-plugin/docs/rules/no-unused-vars-before-return.md)|Disallow assigning variable values if unused before a return [dependency-group](/packages/eslint-plugin/docs/rules/dependency-group.md)|Enforce dependencies docblocks formatting +[gutenberg-phase](docs/rules/gutenberg-phase.md)|Governs the use of the `process.env.GUTENBERG_PHASE` constant +[no-unused-vars-before-return](/packages/eslint-plugin/docs/rules/no-unused-vars-before-return.md)|Disallow assigning variable values if unused before a return [valid-sprintf](/packages/eslint-plugin/docs/rules/valid-sprintf.md)|Disallow assigning variable values if unused before a return ### Legacy diff --git a/packages/eslint-plugin/configs/custom.js b/packages/eslint-plugin/configs/custom.js index 7d79a3e055b18..82ba39265eb43 100644 --- a/packages/eslint-plugin/configs/custom.js +++ b/packages/eslint-plugin/configs/custom.js @@ -4,6 +4,7 @@ module.exports = { ], rules: { '@wordpress/dependency-group': 'error', + '@wordpress/gutenberg-phase': 'error', '@wordpress/no-unused-vars-before-return': 'error', '@wordpress/valid-sprintf': 'error', 'no-restricted-syntax': [ diff --git a/packages/eslint-plugin/docs/rules/gutenberg-phase.md b/packages/eslint-plugin/docs/rules/gutenberg-phase.md new file mode 100644 index 0000000000000..abe70575c9cfb --- /dev/null +++ b/packages/eslint-plugin/docs/rules/gutenberg-phase.md @@ -0,0 +1,62 @@ +# The `GUTENBERG_PHASE` global (gutenberg-phase) + +To enable the use of feature flags in Gutenberg, the GUTENBERG_PHASE global constant was introduced. This constant is replaced with a number value at build time using webpack's define plugin. + +There are a few rules around using this constant: + +- Only access `GUTENBERG_PHASE` via `process.env`, e.g. `process.env.GUTENBERG_PHASE`. This is required since webpack's define plugin only replaces exact matches of `process.env.GUTENBERG_PHASE` in the codebase. +- The `GUTENBERG_PHASE` variable should only be used in a strict equality comparison with a number, e.g. `process.env.GUTENBERG_PHASE === 2` or `process.env.GUTENBERG_PHASE !== 2`. The value of the injected variable should always be a number, so this ensures the correct evaluation of the expression. Furthermore, when `process.env.GUTENBERG_PHASE` is undefined this comparison still returns either true (for `!==`) or false (for `===`), whereas both the `<` and `>` operators will always return false. +- `GUTENBERG_PHASE` should only be used within the condition of an if statement, e.g. `if ( process.env.GUTENBERG_PHASE === 2 ) { // implement feature here }` or ternary `process.env.GUTENBERG_PHASE === 2 ? something : somethingElse`. This rule ensures code that is disabled through a feature flag is removed by dead code elimination. + + +## Rule details + +Examples of **incorrect** code for this rule: + +```js +if ( GUTENBERG_PHASE === 2 ) { + // implement feature here. +} +``` + +```js +if ( window[ 'GUTENBERG_PHASE' ] === 2 ) { + // implement feature here. +} +``` + +```js +if ( process.env.GUTENBERG_PHASE === '2' ) { + // implement feature here. +} +``` + +```js +if ( process.env.GUTENBERG_PHASE > 2 ) { + // implement feature here. +} +``` + +```js +if ( true || process.env.GUTENBERG_PHASE > 2 ) { + // implement feature here. +} +``` + +```js +const isMyFeatureActive = process.env.GUTENBERG_PHASE === 2; +``` + +Examples of **correct** code for this rule: + +```js +if ( process.env.GUTENBERG_PHASE === 2 ) { + // implement feature here. +} +``` + +```js +if ( process.env.GUTENBERG_PHASE !== 2 ) { + return; +} +``` diff --git a/packages/eslint-plugin/rules/__tests__/gutenberg-phase.js b/packages/eslint-plugin/rules/__tests__/gutenberg-phase.js new file mode 100644 index 0000000000000..579e127855090 --- /dev/null +++ b/packages/eslint-plugin/rules/__tests__/gutenberg-phase.js @@ -0,0 +1,60 @@ +/** + * External dependencies + */ +import { RuleTester } from 'eslint'; + +/** + * Internal dependencies + */ +import rule from '../gutenberg-phase'; + +const ruleTester = new RuleTester( { + parserOptions: { + ecmaVersion: 6, + }, +} ); + +const ACCESS_ERROR = 'The `GUTENBERG_PHASE` constant should be accessed using `process.env.GUTENBERG_PHASE`.'; +const EQUALITY_ERROR = 'The `GUTENBERG_PHASE` constant should only be used in a strict equality comparison with a primitive number.'; +const IF_ERROR = 'The `GUTENBERG_PHASE` constant should only be used as part of the condition in an if statement or ternary expression.'; + +ruleTester.run( 'gutenberg-phase', rule, { + valid: [ + { code: `if ( process.env.GUTENBERG_PHASE === 2 ) {}` }, + { code: `if ( process.env.GUTENBERG_PHASE !== 2 ) {}` }, + { code: `if ( 2 === process.env.GUTENBERG_PHASE ) {}` }, + { code: `if ( 2 !== process.env.GUTENBERG_PHASE ) {}` }, + { code: `const test = process.env.GUTENBERG_PHASE === 2 ? foo : bar` }, + { code: `const test = process.env.GUTENBERG_PHASE !== 2 ? foo : bar` }, + ], + invalid: [ + { + code: `if ( GUTENBERG_PHASE === 1 ) {}`, + errors: [ { message: ACCESS_ERROR } ], + }, + { + code: `if ( window[ 'GUTENBERG_PHASE' ] === 1 ) {}`, + errors: [ { message: ACCESS_ERROR } ], + }, + { + code: `if ( process.env.GUTENBERG_PHASE > 1 ) {}`, + errors: [ { message: EQUALITY_ERROR } ], + }, + { + code: `if ( process.env.GUTENBERG_PHASE === '2' ) {}`, + errors: [ { message: EQUALITY_ERROR } ], + }, + { + code: `if ( true ) { process.env.GUTENBERG_PHASE === 2 }`, + errors: [ { message: IF_ERROR } ], + }, + { + code: `if ( true || process.env.GUTENBERG_PHASE === 2 ) {}`, + errors: [ { message: IF_ERROR } ], + }, + { + code: `const isFeatureActive = process.env.GUTENBERG_PHASE === 2;`, + errors: [ { message: IF_ERROR } ], + }, + ], +} ); diff --git a/packages/eslint-plugin/rules/gutenberg-phase.js b/packages/eslint-plugin/rules/gutenberg-phase.js new file mode 100644 index 0000000000000..98c7a4216a4f7 --- /dev/null +++ b/packages/eslint-plugin/rules/gutenberg-phase.js @@ -0,0 +1,165 @@ +/** + * Traverse up through the chain of parent AST nodes returning the first parent + * the predicate returns a truthy value for. + * + * @param {Object} sourceNode The AST node to search from. + * @param {function} predicate A predicate invoked for each parent. + * + * @return {?Object } The first encountered parent node where the predicate + * returns a truthy value. + */ +function findParent( sourceNode, predicate ) { + if ( ! sourceNode.parent ) { + return; + } + + if ( predicate( sourceNode.parent ) ) { + return sourceNode.parent; + } + + return findParent( sourceNode.parent, predicate ); +} + +/** + * Tests whether the GUTENBERG_PHASE variable is accessed via + * `process.env.GUTENBERG_PHASE`. + * + * @example + * ```js + * // good + * if ( process.env.GUTENBERG_PHASE === 2 ) { + * + * // bad + * if ( GUTENBERG_PHASE === 2 ) { + * ``` + * + * @param {Object} node The GUTENBERG_PHASE identifier node. + * @param {Object} context The eslint context object. + */ +function testIsAccessedViaProcessEnv( node, context ) { + const parent = node.parent; + + if ( + parent && + parent.type === 'MemberExpression' && + context.getSource( parent ) === 'process.env.GUTENBERG_PHASE' + + ) { + return; + } + + context.report( + node, + 'The `GUTENBERG_PHASE` constant should be accessed using `process.env.GUTENBERG_PHASE`.', + ); +} + +/** + * Tests whether the GUTENBERG_PHASE variable is used in a strict binary + * equality expression in a comparison with a number, triggering a + * violation if not. + * + * @example + * ```js + * // good + * if ( process.env.GUTENBERG_PHASE === 2 ) { + * + * // bad + * if ( process.env.GUTENBERG_PHASE >= '2' ) { + * ``` + * + * @param {Object} node The GUTENBERG_PHASE identifier node. + * @param {Object} context The eslint context object. + */ +function testIsUsedInStrictBinaryExpression( node, context ) { + const parent = findParent( node, ( candidate ) => candidate.type === 'BinaryExpression' ); + + if ( parent ) { + const comparisonNode = node.parent.type === 'MemberExpression' ? node.parent : node; + + // Test for process.env.GUTENBERG_PHASE === <number> or <number> === process.env.GUTENBERG_PHASE + const hasCorrectOperator = [ '===', '!==' ].includes( parent.operator ); + const hasCorrectOperands = ( + ( parent.left === comparisonNode && typeof parent.right.value === 'number' ) || + ( parent.right === comparisonNode && typeof parent.left.value === 'number' ) + ); + + if ( hasCorrectOperator && hasCorrectOperands ) { + return; + } + } + + context.report( + node, + 'The `GUTENBERG_PHASE` constant should only be used in a strict equality comparison with a primitive number.', + ); +} + +/** + * Tests whether the GUTENBERG_PHASE variable is used as the condition for an + * if statement, triggering a violation if not. + * + * @example + * ```js + * // good + * if ( process.env.GUTENBERG_PHASE === 2 ) { + * + * // bad + * const isFeatureActive = process.env.GUTENBERG_PHASE === 2; + * ``` + * + * @param {Object} node The GUTENBERG_PHASE identifier node. + * @param {Object} context The eslint context object. + */ +function testIsUsedInIfOrTernary( node, context ) { + const conditionalParent = findParent( + node, + ( candidate ) => [ 'IfStatement', 'ConditionalExpression' ].includes( candidate.type ) + ); + const binaryParent = findParent( node, ( candidate ) => candidate.type === 'BinaryExpression' ); + + if ( conditionalParent && + binaryParent && + conditionalParent.test && + conditionalParent.test.start === binaryParent.start && + conditionalParent.test.end === binaryParent.end + ) { + return; + } + + context.report( + node, + 'The `GUTENBERG_PHASE` constant should only be used as part of the condition in an if statement or ternary expression.', + ); +} + +module.exports = { + meta: { + type: 'problem', + schema: [], + }, + create( context ) { + return { + Identifier( node ) { + // Bypass any identifiers with a node name different to `GUTENBERG_PHASE`. + if ( node.name !== 'GUTENBERG_PHASE' ) { + return; + } + + testIsAccessedViaProcessEnv( node, context ); + testIsUsedInStrictBinaryExpression( node, context ); + testIsUsedInIfOrTernary( node, context ); + }, + Literal( node ) { + // Bypass any identifiers with a node value different to `GUTENBERG_PHASE`. + if ( node.value !== 'GUTENBERG_PHASE' ) { + return; + } + + if ( node.parent && node.parent.type === 'MemberExpression' ) { + testIsAccessedViaProcessEnv( node, context ); + } + }, + }; + }, +}; diff --git a/test/unit/config/gutenberg-phase.js b/test/unit/config/gutenberg-phase.js new file mode 100644 index 0000000000000..1b6117b3236a6 --- /dev/null +++ b/test/unit/config/gutenberg-phase.js @@ -0,0 +1,6 @@ +global.process.env = { + ...global.process.env, + // Inject the `GUTENBERG_PHASE` global, used for feature flagging. + // eslint-disable-next-line @wordpress/gutenberg-phase + GUTENBERG_PHASE: parseInt( process.env.npm_package_config_GUTENBERG_PHASE, 10 ), +}; diff --git a/test/unit/jest.config.json b/test/unit/jest.config.json index ed9e9ebc52774..3b7d0ca732a99 100644 --- a/test/unit/jest.config.json +++ b/test/unit/jest.config.json @@ -6,7 +6,8 @@ }, "preset": "@wordpress/jest-preset-default", "setupFiles": [ - "core-js/fn/symbol/async-iterator" + "core-js/fn/symbol/async-iterator", + "<rootDir>/test/unit/config/gutenberg-phase.js" ], "testURL": "http://localhost", "testPathIgnorePatterns": [ diff --git a/webpack.config.js b/webpack.config.js index 7d9877d7c15e3..998cabd9c9ace 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,6 +1,7 @@ /** * External dependencies */ +const { DefinePlugin } = require( 'webpack' ); const WebpackRTLPlugin = require( 'webpack-rtl-plugin' ); const LiveReloadPlugin = require( 'webpack-livereload-plugin' ); const CopyWebpackPlugin = require( 'copy-webpack-plugin' ); @@ -105,6 +106,11 @@ const config = { ], }, plugins: [ + new DefinePlugin( { + // Inject the `GUTENBERG_PHASE` global, used for feature flagging. + // eslint-disable-next-line @wordpress/gutenberg-phase + 'process.env.GUTENBERG_PHASE': JSON.stringify( parseInt( process.env.npm_package_config_GUTENBERG_PHASE, 10 ) || 1 ), + } ), // Create RTL files with a -rtl suffix new WebpackRTLPlugin( { suffix: '-rtl', From 858719bf3e5c7cd37c14824f5f3d4b8db49cc8f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Fri, 15 Feb 2019 12:58:44 +0100 Subject: [PATCH 446/691] Add API docs to e2e-test-utils package (#13856) --- packages/e2e-test-utils/README.md | 525 ++++++++++++++++++++++++++++++ 1 file changed, 525 insertions(+) diff --git a/packages/e2e-test-utils/README.md b/packages/e2e-test-utils/README.md index bfc0ea26c4fa7..6743b099d8abb 100644 --- a/packages/e2e-test-utils/README.md +++ b/packages/e2e-test-utils/README.md @@ -10,4 +10,529 @@ Install the module npm install @wordpress/e2e-test-utils --save-dev ``` +## API + +### activatePlugin + +[src/index.js#L1-L1](src/index.js#L1-L1) + +Activates an installed plugin. + +**Parameters** + +- **slug** `string`: Plugin slug. + +### arePrePublishChecksEnabled + +[src/index.js#L2-L2](src/index.js#L2-L2) + +Verifies if publish checks are enabled. + +**Returns** + +`boolean` Boolean which represents the state of prepublish checks. + +### clearLocalStorage + +[src/index.js#L3-L3](src/index.js#L3-L3) + +Clears the local storage. + +### clickBlockAppender + +[src/index.js#L4-L4](src/index.js#L4-L4) + +Clicks the default block appender. + +### clickButton + +[src/index.js#L5-L5](src/index.js#L5-L5) + +Clicks a button based on the text on the button. + +**Parameters** + +- **buttonText** `string`: The text that appears on the button to click. + +### clickOnCloseModalButton + +[src/index.js#L6-L6](src/index.js#L6-L6) + +Click on the close button of an open modal. + +**Parameters** + +- **modalClassName** `?string`: Class name for the modal to close + +### clickOnMoreMenuItem + +[src/index.js#L7-L7](src/index.js#L7-L7) + +Clicks on More Menu item, searches for the button with the text provided and clicks it. + +**Parameters** + +- **buttonLabel** `string`: The label to search the button for. + +### createEmbeddingMatcher + +[src/index.js#L45-L45](src/index.js#L45-L45) + +Creates a function to determine if a request is embedding a certain URL. + +**Parameters** + +- **url** `string`: The URL to check against a request. + +**Returns** + +`function` Function that determines if a request is for the embed API, embedding a specific URL. + +### createJSONResponse + +[src/index.js#L45-L45](src/index.js#L45-L45) + +Respond to a request with a JSON response. + +**Parameters** + +- **mockResponse** `string`: The mock object to wrap in a JSON response. + +**Returns** + +`Promise` Promise that responds to a request with the mock JSON response. + +### createNewPost + +[src/index.js#L8-L8](src/index.js#L8-L8) + +Creates new post. + +**Parameters** + +- **obj** `Object`: Object to create new post, along with tips enabling option. + +### createURL + +[src/index.js#L9-L9](src/index.js#L9-L9) + +Creates new URL by parsing base URL, WPPath and query string. + +**Parameters** + +- **WPPath** `string`: String to be serialized as pathname. +- **query** `?string`: String to be serialized as query portion of URL. + +**Returns** + +`string` String which represents full URL. + +### createURLMatcher + +[src/index.js#L45-L45](src/index.js#L45-L45) + +Creates a function to determine if a request is calling a URL with the substring present. + +**Parameters** + +- **substring** `string`: The substring to check for. + +**Returns** + +`function` Function that determines if a request's URL contains substring. + +### deactivatePlugin + +[src/index.js#L10-L10](src/index.js#L10-L10) + +Deactivates an active plugin. + +**Parameters** + +- **slug** `string`: Plugin slug. + +### disablePrePublishChecks + +[src/index.js#L11-L11](src/index.js#L11-L11) + +Disables Pre-publish checks. + +### enablePageDialogAccept + +[src/index.js#L12-L12](src/index.js#L12-L12) + +Enables even listener which accepts a page dialog which +may appear when navigating away from Gutenberg. + +### enablePrePublishChecks + +[src/index.js#L13-L13](src/index.js#L13-L13) + +Enables Pre-publish checks. + +### ensureSidebarOpened + +[src/index.js#L14-L14](src/index.js#L14-L14) + +Verifies that the edit post sidebar is opened, and if it is not, opens it. + +**Returns** + +`Promise` Promise resolving once the edit post sidebar is opened. + +### findSidebarPanelWithTitle + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Finds a sidebar panel with the provided title. + +**Parameters** + +- **panelTitle** `string`: The name of sidebar panel. + +**Returns** + +`?ElementHandle` Object that represents an in-page DOM element. + +### getAllBlocks + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Returns an array with all blocks; Equivalent to calling wp.data.select( 'core/editor' ).getBlocks(); + +**Returns** + +`Promise` Promise resolving with an array containing all blocks in the document. + +### getAvailableBlockTransforms + +[src/index.js#L17-L17](src/index.js#L17-L17) + +Returns an array of strings with all block titles, +that the current selected block can be transformed into. + +**Returns** + +`Promise` Promise resolving with an array containing all possible block transforms + +### getEditedPostContent + +[src/index.js#L18-L18](src/index.js#L18-L18) + +Returns a promise which resolves with the edited post content (HTML string). + +**Returns** + +`Promise` Promise resolving with post content markup. + +### hasBlockSwitcher + +[src/index.js#L19-L19](src/index.js#L19-L19) + +Returns a boolean indicating if the current selected block has a block switcher or not. + +**Returns** + +`Promise` Promise resolving with a boolean. + +### insertBlock + +[src/index.js#L20-L20](src/index.js#L20-L20) + +Opens the inserter, searches for the given term, then selects the first +result that appears. + +**Parameters** + +- **searchTerm** `string`: The text to search the inserter for. +- **panelName** `string`: The inserter panel to open (if it's closed by default). + +### installPlugin + +[src/index.js#L21-L21](src/index.js#L21-L21) + +Installs a plugin from the WP.org repository. + +**Parameters** + +- **slug** `string`: Plugin slug. +- **searchTerm** `?string`: If the plugin is not findable by its slug use an alternative term to search. + +### isCurrentURL + +[src/index.js#L22-L22](src/index.js#L22-L22) + +Checks if current URL is a WordPress path. + +**Parameters** + +- **WPPath** `string`: String to be serialized as pathname. +- **query** `?string`: String to be serialized as query portion of URL. + +**Returns** + +`boolean` Boolean represents whether current URL is or not a WordPress path. + +### loginUser + +[src/index.js#L23-L23](src/index.js#L23-L23) + +Performs log in with specified username and password. + +**Parameters** + +- **username** `?string`: String to be used as user credential. +- **password** `?string`: String to be used as user credential. + +### mockOrTransform + +[src/index.js#L45-L45](src/index.js#L45-L45) + +Mocks a request with the supplied mock object, or allows it to run with an optional transform, based on the +deserialised JSON response for the request. + +**Parameters** + +- **mockCheck** `function`: function that returns true if the request should be mocked. +- **mock** `Object`: A mock object to wrap in a JSON response, if the request should be mocked. +- **responseObjectTransform** `(function|undefined)`: An optional function that transforms the response's object before the response is used. + +**Returns** + +`Promise` Promise that uses `mockCheck` to see if a request should be mocked with `mock`, and optionally transforms the response with `responseObjectTransform`. + +### observeFocusLoss + +[src/index.js#L24-L24](src/index.js#L24-L24) + +Binds to the document on page load which throws an error if a `focusout` +event occurs without a related target (i.e. focus loss). + +### openDocumentSettingsSidebar + +[src/index.js#L25-L25](src/index.js#L25-L25) + +Clicks on the button in the header which opens Document Settings sidebar when it is closed. + +### openPublishPanel + +[src/index.js#L26-L26](src/index.js#L26-L26) + +Opens the publish panel. + +### pressKeyTimes + +[src/index.js#L27-L27](src/index.js#L27-L27) + +Presses the given keyboard key a number of times in sequence. + +**Parameters** + +- **key** `string`: Key to press. +- **count** `number`: Number of times to press. + +**Returns** + +`Promise` Promise resolving when key presses complete. + +### pressKeyWithModifier + +[src/index.js#L28-L28](src/index.js#L28-L28) + +Performs a key press with modifier (Shift, Control, Meta, Alt), where each modifier +is normalized to platform-specific modifier. + +**Parameters** + +- **modifier** `string`: Modifier key. +- **key** `string`: Key to press while modifier held. + +### publishPost + +[src/index.js#L29-L29](src/index.js#L29-L29) + +Publishes the post, resolving once the request is complete (once a notice +is displayed). + +**Returns** + +`Promise` Promise resolving when publish is complete. + +### publishPostWithPrePublishChecksDisabled + +[src/index.js#L30-L30](src/index.js#L30-L30) + +Publishes the post without the pre-publish checks, +resolving once the request is complete (once a notice is displayed). + +**Returns** + +`Promise` Promise resolving when publish is complete. + +### saveDraft + +[src/index.js#L31-L31](src/index.js#L31-L31) + +Saves the post as a draft, resolving once the request is complete (once the +"Saved" indicator is displayed). + +**Returns** + +`Promise` Promise resolving when draft save is complete. + +### searchForBlock + +[src/index.js#L32-L32](src/index.js#L32-L32) + +Search for block in the global inserter + +**Parameters** + +- **searchTerm** `string`: The text to search the inserter for. + +### selectBlockByClientId + +[src/index.js#L33-L33](src/index.js#L33-L33) + +Given the clientId of a block, selects the block on the editor. + +**Parameters** + +- **clientId** `string`: Identified of the block. + +### setBrowserViewport + +[src/index.js#L34-L34](src/index.js#L34-L34) + +Sets browser viewport to specified type. + +**Parameters** + +- **type** `string`: String to represent dimensions type; can be either small or large. + +### setPostContent + +[src/index.js#L35-L35](src/index.js#L35-L35) + +Sets code editor content + +**Parameters** + +- **content** `string`: New code editor content. + +**Returns** + +`Promise` Promise resolving with an array containing all blocks in the document. + +### setUpResponseMocking + +[src/index.js#L45-L45](src/index.js#L45-L45) + +Sets up mock checks and responses. Accepts a list of mock settings with the following properties: + +- `match`: function to check if a request should be mocked. +- `onRequestMatch`: async function to respond to the request. + +**Usage** + +```js +const MOCK_RESPONSES = [ + { + match: isEmbedding( 'https://wordpress.org/gutenberg/handbook/' ), + onRequestMatch: JSONResponse( MOCK_BAD_WORDPRESS_RESPONSE ), + }, + { + match: isEmbedding( 'https://wordpress.org/gutenberg/handbook/block-api/attributes/' ), + onRequestMatch: JSONResponse( MOCK_EMBED_WORDPRESS_SUCCESS_RESPONSE ), + } +]; +setUpResponseMocking( MOCK_RESPONSES ); +``` + +If none of the mock settings match the request, the request is allowed to continue. + +**Parameters** + +- **mocks** `Array`: Array of mock settings. + +### switchEditorModeTo + +[src/index.js#L36-L36](src/index.js#L36-L36) + +Switches editor mode. + +**Parameters** + +- **mode** `string`: String editor mode. + +### switchUserToAdmin + +[src/index.js#L37-L37](src/index.js#L37-L37) + +Switches the current user to the admin user (if the user +running the test is not already the admin user). + +### switchUserToTest + +[src/index.js#L38-L38](src/index.js#L38-L38) + +Switches the current user to whichever user we should be +running the tests as (if we're not already that user). + +### toggleScreenOption + +[src/index.js#L39-L39](src/index.js#L39-L39) + +Toggles the screen option with the given label. + +**Parameters** + +- **label** `string`: The label of the screen option, e.g. 'Show Tips'. +- **shouldBeChecked** `?boolean`: If true, turns the option on. If false, off. If undefined, the option will be toggled. + +### transformBlockTo + +[src/index.js#L40-L40](src/index.js#L40-L40) + +Converts editor's block type. + +**Parameters** + +- **name** `string`: Block name. + +### uninstallPlugin + +[src/index.js#L41-L41](src/index.js#L41-L41) + +Uninstalls a plugin. + +**Parameters** + +- **slug** `string`: Plugin slug. + +### visitAdminPage + +[src/index.js#L42-L42](src/index.js#L42-L42) + +Visits admin page; if user is not logged in then it logging in it first, then visits admin page. + +**Parameters** + +- **adminPath** `string`: String to be serialized as pathname. +- **query** `string`: String to be serialized as query portion of URL. + +### waitForWindowDimensions + +[src/index.js#L43-L43](src/index.js#L43-L43) + +Function that waits until the page viewport has the required dimensions. +It is being used to address a problem where after using setViewport the execution may continue, +without the new dimensions being applied. +<https://github.com/GoogleChrome/puppeteer/issues/1751> + +**Parameters** + +- **width** `number`: Width of the window. +- **height** `height`: Height of the window. + <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> From af68f20a256eb1467a1acd0d993fc0ff3c0c645d Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Fri, 15 Feb 2019 16:08:01 +0100 Subject: [PATCH 447/691] Only expose the select function in registry selectors (#13889) --- packages/core-data/src/selectors.js | 4 ++-- packages/data/src/namespace-store.js | 2 +- packages/data/src/test/registry.js | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core-data/src/selectors.js b/packages/core-data/src/selectors.js index 36130c3439df6..54f9ba4adbe49 100644 --- a/packages/core-data/src/selectors.js +++ b/packages/core-data/src/selectors.js @@ -25,8 +25,8 @@ import { getQueriedItems } from './queried-data'; * * @return {boolean} Whether a request is in progress for an embed preview. */ -export const isRequestingEmbedPreview = createRegistrySelector( ( registry ) => ( state, url ) => { - return registry.select( 'core/data' ).isResolving( REDUCER_KEY, 'getEmbedPreview', [ url ] ); +export const isRequestingEmbedPreview = createRegistrySelector( ( select ) => ( state, url ) => { + return select( 'core/data' ).isResolving( REDUCER_KEY, 'getEmbedPreview', [ url ] ); } ); /** diff --git a/packages/data/src/namespace-store.js b/packages/data/src/namespace-store.js index bb1259d3c52af..510e218152e81 100644 --- a/packages/data/src/namespace-store.js +++ b/packages/data/src/namespace-store.js @@ -106,7 +106,7 @@ function createReduxStore( reducer, key, registry ) { */ function mapSelectors( selectors, store, registry ) { const createStateSelector = ( registeredSelector ) => { - const selector = registeredSelector.isRegistrySelector ? registeredSelector( registry ) : registeredSelector; + const selector = registeredSelector.isRegistrySelector ? registeredSelector( registry.select ) : registeredSelector; return function runSelector() { // This function is an optimized implementation of: diff --git a/packages/data/src/test/registry.js b/packages/data/src/test/registry.js index e85495240e1f3..752d21dedb721 100644 --- a/packages/data/src/test/registry.js +++ b/packages/data/src/test/registry.js @@ -445,8 +445,8 @@ describe( 'createRegistry', () => { it( 'should run the registry selectors properly', () => { const selector1 = () => 'result1'; - const selector2 = createRegistrySelector( ( reg ) => () => - reg.select( 'reducer1' ).selector1() + const selector2 = createRegistrySelector( ( select ) => () => + select( 'reducer1' ).selector1() ); registry.registerStore( 'reducer1', { reducer: () => 'state1', From 9fb2353092120957e88c4b35a4b1829eea3822f8 Mon Sep 17 00:00:00 2001 From: Kamata Ryo <kamataryo@users.noreply.github.com> Date: Sat, 16 Feb 2019 00:27:32 +0900 Subject: [PATCH 448/691] Add whole babelrc configuration example (#13891) --- packages/babel-plugin-makepot/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/babel-plugin-makepot/README.md b/packages/babel-plugin-makepot/README.md index 9f43abf09a00e..6cade321228b3 100644 --- a/packages/babel-plugin-makepot/README.md +++ b/packages/babel-plugin-makepot/README.md @@ -3,9 +3,11 @@ Babel plugin used to scan JavaScript files for use of localization functions. It then compiles these into a [gettext POT formatted](https://en.wikipedia.org/wiki/Gettext) file as a template for translation. By default the output file will be written to `gettext.pot` of the root project directory. This can be overridden using the `"output"` option of the plugin. ```json -[ "@wordpress/babel-plugin-makepot", { - "output": "languages/myplugin.pot" -} ] +{ + "plugins": [ + [ "@wordpress/babel-plugin-makepot", { "output": "languages/myplugin.pot" } ], + ] +} ``` ## Installation From 3e56d7ceafad7e28d76eb11bdbb7794790cd835a Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Fri, 15 Feb 2019 17:16:30 +0100 Subject: [PATCH 449/691] Fix small regression introduced in the region navigation styles (#13897) --- .../src/higher-order/navigate-regions/style.scss | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/packages/components/src/higher-order/navigate-regions/style.scss b/packages/components/src/higher-order/navigate-regions/style.scss index add768095220d..4653a08ee3862 100644 --- a/packages/components/src/higher-order/navigate-regions/style.scss +++ b/packages/components/src/higher-order/navigate-regions/style.scss @@ -20,19 +20,8 @@ &:focus { outline-style: solid; outline-color: $blue-medium-400; - animation: editor-animation__region-focus 0.1s ease-out; - animation-fill-mode: forwards; + outline-width: 4px; + outline-offset: -4px; } } } - -@keyframes editor-animation__region-focus { - from { - outline-width: 0; - outline-offset: 0; - } - to { - outline-width: 4px; - outline-offset: -4px; - } -} From b58fed39e5769e904f1394277b636597569aa04f Mon Sep 17 00:00:00 2001 From: Javier Villanueva <javiervd@gmail.com> Date: Fri, 15 Feb 2019 16:23:01 +0000 Subject: [PATCH 450/691] Blocks: Add a new Tag Cloud block (#7875) --- lib/load.php | 3 + packages/block-library/src/editor.scss | 1 + packages/block-library/src/index.js | 2 + packages/block-library/src/tag-cloud/edit.js | 101 ++++++++++++++++++ .../block-library/src/tag-cloud/editor.scss | 13 +++ packages/block-library/src/tag-cloud/index.js | 32 ++++++ .../block-library/src/tag-cloud/index.php | 71 ++++++++++++ .../fixtures/core__tag-cloud.html | 1 + .../fixtures/core__tag-cloud.json | 13 +++ .../fixtures/core__tag-cloud.parsed.json | 11 ++ .../fixtures/core__tag-cloud.serialized.html | 1 + .../core__tag-cloud__showTagCounts.html | 1 + .../core__tag-cloud__showTagCounts.json | 13 +++ ...core__tag-cloud__showTagCounts.parsed.json | 12 +++ ...__tag-cloud__showTagCounts.serialized.html | 1 + .../full-content/server-registered.json | 2 +- 16 files changed, 277 insertions(+), 1 deletion(-) create mode 100644 packages/block-library/src/tag-cloud/edit.js create mode 100644 packages/block-library/src/tag-cloud/editor.scss create mode 100644 packages/block-library/src/tag-cloud/index.js create mode 100644 packages/block-library/src/tag-cloud/index.php create mode 100644 test/integration/full-content/fixtures/core__tag-cloud.html create mode 100644 test/integration/full-content/fixtures/core__tag-cloud.json create mode 100644 test/integration/full-content/fixtures/core__tag-cloud.parsed.json create mode 100644 test/integration/full-content/fixtures/core__tag-cloud.serialized.html create mode 100644 test/integration/full-content/fixtures/core__tag-cloud__showTagCounts.html create mode 100644 test/integration/full-content/fixtures/core__tag-cloud__showTagCounts.json create mode 100644 test/integration/full-content/fixtures/core__tag-cloud__showTagCounts.parsed.json create mode 100644 test/integration/full-content/fixtures/core__tag-cloud__showTagCounts.serialized.html diff --git a/lib/load.php b/lib/load.php index 54be9e51c49bd..2027600792886 100644 --- a/lib/load.php +++ b/lib/load.php @@ -52,6 +52,9 @@ if ( ! function_exists( 'render_block_core_shortcode' ) ) { require dirname( __FILE__ ) . '/../packages/block-library/src/shortcode/index.php'; } +if ( ! function_exists( 'render_block_core_tag_cloud' ) ) { + require dirname( __FILE__ ) . '/../packages/block-library/src/tag-cloud/index.php'; +} if ( ! function_exists( 'render_block_core_search' ) ) { require dirname( __FILE__ ) . '/../packages/block-library/src/search/index.php'; } diff --git a/packages/block-library/src/editor.scss b/packages/block-library/src/editor.scss index b5778d5b472ff..517218be5e715 100644 --- a/packages/block-library/src/editor.scss +++ b/packages/block-library/src/editor.scss @@ -28,6 +28,7 @@ @import "./spacer/editor.scss"; @import "./subhead/editor.scss"; @import "./table/editor.scss"; +@import "./tag-cloud/editor.scss"; @import "./text-columns/editor.scss"; @import "./verse/editor.scss"; @import "./video/editor.scss"; diff --git a/packages/block-library/src/index.js b/packages/block-library/src/index.js index 1439edcc350ec..4b15aac33c4a4 100644 --- a/packages/block-library/src/index.js +++ b/packages/block-library/src/index.js @@ -50,6 +50,7 @@ import * as template from './template'; import * as textColumns from './text-columns'; import * as verse from './verse'; import * as video from './video'; +import * as tagCloud from './tag-cloud'; import * as classic from './classic'; @@ -96,6 +97,7 @@ export const registerCoreBlocks = () => { spacer, subhead, table, + tagCloud, template, textColumns, verse, diff --git a/packages/block-library/src/tag-cloud/edit.js b/packages/block-library/src/tag-cloud/edit.js new file mode 100644 index 0000000000000..2fb7179e2ca22 --- /dev/null +++ b/packages/block-library/src/tag-cloud/edit.js @@ -0,0 +1,101 @@ +/** + * External dependencies + */ +import { map, filter } from 'lodash'; + +/** + * WordPress dependencies + */ +import { Component, Fragment } from '@wordpress/element'; +import { + PanelBody, + ToggleControl, + SelectControl, + ServerSideRender, +} from '@wordpress/components'; +import { withSelect } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; +import { InspectorControls } from '@wordpress/editor'; + +class TagCloudEdit extends Component { + constructor() { + super( ...arguments ); + + this.state = { + editing: ! this.props.attributes.taxonomy, + }; + + this.setTaxonomy = this.setTaxonomy.bind( this ); + this.toggleShowTagCounts = this.toggleShowTagCounts.bind( this ); + } + + getTaxonomyOptions() { + const taxonomies = filter( this.props.taxonomies, 'show_cloud' ); + const selectOption = { + label: __( '- Select -' ), + value: '', + }; + const taxonomyOptions = map( taxonomies, ( taxonomy ) => { + return { + value: taxonomy.slug, + label: taxonomy.name, + }; + } ); + + return [ selectOption, ...taxonomyOptions ]; + } + + setTaxonomy( taxonomy ) { + const { setAttributes } = this.props; + + setAttributes( { taxonomy } ); + } + + toggleShowTagCounts() { + const { attributes, setAttributes } = this.props; + const { showTagCounts } = attributes; + + setAttributes( { showTagCounts: ! showTagCounts } ); + } + + render() { + const { attributes } = this.props; + const { taxonomy, showTagCounts } = attributes; + const taxonomyOptions = this.getTaxonomyOptions(); + + const inspectorControls = ( + <InspectorControls> + <PanelBody title={ __( 'Tag Cloud Settings' ) }> + <SelectControl + label={ __( 'Taxonomy' ) } + options={ taxonomyOptions } + value={ taxonomy } + onChange={ this.setTaxonomy } + /> + <ToggleControl + label={ __( 'Show post counts' ) } + checked={ showTagCounts } + onChange={ this.toggleShowTagCounts } + /> + </PanelBody> + </InspectorControls> + ); + + return ( + <Fragment> + { inspectorControls } + <ServerSideRender + key="tag-cloud" + block="core/tag-cloud" + attributes={ attributes } + /> + </Fragment> + ); + } +} + +export default withSelect( ( select ) => { + return { + taxonomies: select( 'core' ).getTaxonomies(), + }; +} )( TagCloudEdit ); diff --git a/packages/block-library/src/tag-cloud/editor.scss b/packages/block-library/src/tag-cloud/editor.scss new file mode 100644 index 0000000000000..c70f152286080 --- /dev/null +++ b/packages/block-library/src/tag-cloud/editor.scss @@ -0,0 +1,13 @@ +.block-editor .wp-block-tag-cloud { + a { + display: inline-block; + margin-right: 5px; + } + + span { + display: inline-block; + margin-left: 5px; + color: $dark-gray-100; + text-decoration: none; + } +} diff --git a/packages/block-library/src/tag-cloud/index.js b/packages/block-library/src/tag-cloud/index.js new file mode 100644 index 0000000000000..cc785fdb3167a --- /dev/null +++ b/packages/block-library/src/tag-cloud/index.js @@ -0,0 +1,32 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import edit from './edit'; + +export const name = 'core/tag-cloud'; + +export const settings = { + title: __( 'Tag Cloud' ), + + description: __( 'A cloud of your most used tags.' ), + + icon: 'tag', + + category: 'widgets', + + supports: { + html: false, + align: true, + }, + + edit, + + save() { + return null; + }, +}; diff --git a/packages/block-library/src/tag-cloud/index.php b/packages/block-library/src/tag-cloud/index.php new file mode 100644 index 0000000000000..c60f8a1dd6f54 --- /dev/null +++ b/packages/block-library/src/tag-cloud/index.php @@ -0,0 +1,71 @@ +<?php +/** + * Server-side rendering of the `core/tag-cloud` block. + * + * @package WordPress + */ + +/** + * Renders the `core/tag-cloud` block on server. + * + * @param array $attributes The block attributes. + * + * @return string Returns the tag cloud for selected taxonomy. + */ +function render_block_core_tag_cloud( $attributes ) { + $class = isset( $attributes['align'] ) ? + "wp-block-tag-cloud align{$attributes['align']}" : + 'wp-block-tag-cloud'; + + if ( isset( $attributes['className'] ) ) { + $class .= ' ' . $attributes['className']; + } + + $args = array( + 'echo' => false, + 'taxonomy' => $attributes['taxonomy'], + 'show_count' => $attributes['showTagCounts'], + ); + + $tag_cloud = wp_tag_cloud( $args ); + + if ( ! $tag_cloud ) { + $tag_cloud = esc_html( __( 'No terms to show.' ) ); + } + + return sprintf( + '<p class="%1$s">%2$s</p>', + esc_attr( $class ), + $tag_cloud + ); +} + +/** + * Registers the `core/tag-cloud` block on server. + */ +function register_block_core_tag_cloud() { + register_block_type( + 'core/tag-cloud', + array( + 'attributes' => array( + 'taxonomy' => array( + 'type' => 'string', + 'default' => 'post_tag', + ), + 'className' => array( + 'type' => 'string', + ), + 'showTagCounts' => array( + 'type' => 'boolean', + 'default' => false, + ), + 'align' => array( + 'type' => 'string', + ), + ), + 'render_callback' => 'render_block_core_tag_cloud', + ) + ); +} + +add_action( 'init', 'register_block_core_tag_cloud' ); diff --git a/test/integration/full-content/fixtures/core__tag-cloud.html b/test/integration/full-content/fixtures/core__tag-cloud.html new file mode 100644 index 0000000000000..05ef749fc0b35 --- /dev/null +++ b/test/integration/full-content/fixtures/core__tag-cloud.html @@ -0,0 +1 @@ +<!-- wp:tag-cloud {"taxonomy":"category"} /--> \ No newline at end of file diff --git a/test/integration/full-content/fixtures/core__tag-cloud.json b/test/integration/full-content/fixtures/core__tag-cloud.json new file mode 100644 index 0000000000000..da9762c711adb --- /dev/null +++ b/test/integration/full-content/fixtures/core__tag-cloud.json @@ -0,0 +1,13 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/tag-cloud", + "isValid": true, + "attributes": { + "taxonomy": "category", + "showTagCounts": false + }, + "innerBlocks": [], + "originalContent": "" + } +] diff --git a/test/integration/full-content/fixtures/core__tag-cloud.parsed.json b/test/integration/full-content/fixtures/core__tag-cloud.parsed.json new file mode 100644 index 0000000000000..cb601766c2b8e --- /dev/null +++ b/test/integration/full-content/fixtures/core__tag-cloud.parsed.json @@ -0,0 +1,11 @@ +[ + { + "blockName": "core/tag-cloud", + "attrs": { + "taxonomy": "category" + }, + "innerBlocks": [], + "innerHTML": "", + "innerContent": [] + } +] diff --git a/test/integration/full-content/fixtures/core__tag-cloud.serialized.html b/test/integration/full-content/fixtures/core__tag-cloud.serialized.html new file mode 100644 index 0000000000000..d342963579e29 --- /dev/null +++ b/test/integration/full-content/fixtures/core__tag-cloud.serialized.html @@ -0,0 +1 @@ +<!-- wp:tag-cloud {"taxonomy":"category"} /--> diff --git a/test/integration/full-content/fixtures/core__tag-cloud__showTagCounts.html b/test/integration/full-content/fixtures/core__tag-cloud__showTagCounts.html new file mode 100644 index 0000000000000..3f22f21fc6cfe --- /dev/null +++ b/test/integration/full-content/fixtures/core__tag-cloud__showTagCounts.html @@ -0,0 +1 @@ +<!-- wp:tag-cloud {"taxonomy":"category","showTagCounts":true} /--> \ No newline at end of file diff --git a/test/integration/full-content/fixtures/core__tag-cloud__showTagCounts.json b/test/integration/full-content/fixtures/core__tag-cloud__showTagCounts.json new file mode 100644 index 0000000000000..5946f7330f8b5 --- /dev/null +++ b/test/integration/full-content/fixtures/core__tag-cloud__showTagCounts.json @@ -0,0 +1,13 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/tag-cloud", + "isValid": true, + "attributes": { + "taxonomy": "category", + "showTagCounts": true + }, + "innerBlocks": [], + "originalContent": "" + } +] diff --git a/test/integration/full-content/fixtures/core__tag-cloud__showTagCounts.parsed.json b/test/integration/full-content/fixtures/core__tag-cloud__showTagCounts.parsed.json new file mode 100644 index 0000000000000..d9f82c3576245 --- /dev/null +++ b/test/integration/full-content/fixtures/core__tag-cloud__showTagCounts.parsed.json @@ -0,0 +1,12 @@ +[ + { + "blockName": "core/tag-cloud", + "attrs": { + "taxonomy": "category", + "showTagCounts": true + }, + "innerBlocks": [], + "innerHTML": "", + "innerContent": [] + } +] diff --git a/test/integration/full-content/fixtures/core__tag-cloud__showTagCounts.serialized.html b/test/integration/full-content/fixtures/core__tag-cloud__showTagCounts.serialized.html new file mode 100644 index 0000000000000..9711e8c6ad6c0 --- /dev/null +++ b/test/integration/full-content/fixtures/core__tag-cloud__showTagCounts.serialized.html @@ -0,0 +1 @@ +<!-- wp:tag-cloud {"taxonomy":"category","showTagCounts":true} /--> diff --git a/test/integration/full-content/server-registered.json b/test/integration/full-content/server-registered.json index 65eb48d6d53f2..080865f39bb10 100644 --- a/test/integration/full-content/server-registered.json +++ b/test/integration/full-content/server-registered.json @@ -1 +1 @@ -{"core\/block":{"attributes":{"ref":{"type":"number"}}},"core\/latest-comments":{"attributes":{"className":{"type":"string"},"commentsToShow":{"type":"number","default":5,"minimum":1,"maximum":100},"displayAvatar":{"type":"boolean","default":true},"displayDate":{"type":"boolean","default":true},"displayExcerpt":{"type":"boolean","default":true},"align":{"type":"string","enum":["center","left","right","wide","full",""]}}},"core\/archives":{"attributes":{"align":{"type":"string"},"className":{"type":"string"},"displayAsDropdown":{"type":"boolean","default":false},"showPostCounts":{"type":"boolean","default":false}}},"core\/latest-posts":{"attributes":{"categories":{"type":"string"},"className":{"type":"string"},"postsToShow":{"type":"number","default":5},"displayPostDate":{"type":"boolean","default":false},"postLayout":{"type":"string","default":"list"},"columns":{"type":"number","default":3},"align":{"type":"string"},"order":{"type":"string","default":"desc"},"orderBy":{"type":"string","default":"date"}}}} \ No newline at end of file +{"core\/block":{"attributes":{"ref":{"type":"number"}}},"core\/latest-comments":{"attributes":{"className":{"type":"string"},"commentsToShow":{"type":"number","default":5,"minimum":1,"maximum":100},"displayAvatar":{"type":"boolean","default":true},"displayDate":{"type":"boolean","default":true},"displayExcerpt":{"type":"boolean","default":true},"align":{"type":"string","enum":["center","left","right","wide","full",""]}}},"core\/archives":{"attributes":{"align":{"type":"string"},"className":{"type":"string"},"displayAsDropdown":{"type":"boolean","default":false},"showPostCounts":{"type":"boolean","default":false}}},"core\/latest-posts":{"attributes":{"categories":{"type":"string"},"className":{"type":"string"},"postsToShow":{"type":"number","default":5},"displayPostDate":{"type":"boolean","default":false},"postLayout":{"type":"string","default":"list"},"columns":{"type":"number","default":3},"align":{"type":"string"},"order":{"type":"string","default":"desc"},"orderBy":{"type":"string","default":"date"}}},"core\/tag-cloud":{"attributes":{"taxonomy":{"type":"string","default":"tags"},"className":{"type":"string"},"showTagCounts":{"type":"boolean","default":false},"align":{"type":"string"}}}} \ No newline at end of file From 2ce61158a29885b95b33dca92e28d851d8e66d87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20Van=C2=A0Durpe?= <iseulde@automattic.com> Date: Fri, 15 Feb 2019 17:26:17 +0100 Subject: [PATCH 451/691] RichText: don't set selection during selectionchange (#13896) * RichText: don't set selection during selectionchange * Add e2e test --- .../__snapshots__/rich-text.test.js.snap | 6 ++++++ packages/e2e-tests/specs/rich-text.test.js | 21 +++++++++++++++++++ .../editor/src/components/rich-text/index.js | 5 +++-- packages/rich-text/src/to-dom.js | 3 ++- 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/packages/e2e-tests/specs/__snapshots__/rich-text.test.js.snap b/packages/e2e-tests/specs/__snapshots__/rich-text.test.js.snap index 0cace5805e698..b408c3acfc60d 100644 --- a/packages/e2e-tests/specs/__snapshots__/rich-text.test.js.snap +++ b/packages/e2e-tests/specs/__snapshots__/rich-text.test.js.snap @@ -30,6 +30,12 @@ exports[`RichText should not format text after code backtick 1`] = ` <!-- /wp:paragraph -->" `; +exports[`RichText should not lose selection direction 1`] = ` +"<!-- wp:paragraph --> +<p><strong>1</strong>2-3</p> +<!-- /wp:paragraph -->" +`; + exports[`RichText should only mutate text data on input 1`] = ` "<!-- wp:paragraph --> <p>1<strong>2</strong>34</p> diff --git a/packages/e2e-tests/specs/rich-text.test.js b/packages/e2e-tests/specs/rich-text.test.js index e2af127f623e8..5153032953899 100644 --- a/packages/e2e-tests/specs/rich-text.test.js +++ b/packages/e2e-tests/specs/rich-text.test.js @@ -159,4 +159,25 @@ describe( 'RichText', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + + it( 'should not lose selection direction', async () => { + await clickBlockAppender(); + await pressKeyWithModifier( 'primary', 'b' ); + await page.keyboard.type( '1' ); + await pressKeyWithModifier( 'primary', 'b' ); + await page.keyboard.type( '23' ); + await page.keyboard.press( 'ArrowLeft' ); + await page.keyboard.down( 'Shift' ); + await page.keyboard.press( 'ArrowLeft' ); + await page.keyboard.press( 'ArrowLeft' ); + await page.keyboard.press( 'ArrowRight' ); + await page.keyboard.press( 'ArrowRight' ); + await page.keyboard.up( 'Shift' ); + + // There should be no selection. The following should insert "-" without + // deleting the numbers. + await page.keyboard.type( '-' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); } ); diff --git a/packages/editor/src/components/rich-text/index.js b/packages/editor/src/components/rich-text/index.js index ca4a598acb275..58e9acbde686a 100644 --- a/packages/editor/src/components/rich-text/index.js +++ b/packages/editor/src/components/rich-text/index.js @@ -169,13 +169,14 @@ export class RichText extends Component { } ); } - applyRecord( record ) { + applyRecord( record, { domOnly } = {} ) { apply( { value: record, current: this.editableRef, multilineTag: this.multilineTag, multilineWrapperTags: this.multilineWrapperTags, prepareEditableTree: this.props.prepareEditableTree, + __unstableDomOnly: domOnly, } ); } @@ -421,7 +422,7 @@ export class RichText extends Component { } this.setState( { start, end, selectedFormat } ); - this.applyRecord( { ...value, selectedFormat } ); + this.applyRecord( { ...value, selectedFormat }, { domOnly: true } ); delete this.formatPlaceholder; } diff --git a/packages/rich-text/src/to-dom.js b/packages/rich-text/src/to-dom.js index 3d02aab725bd9..222d06ad80074 100644 --- a/packages/rich-text/src/to-dom.js +++ b/packages/rich-text/src/to-dom.js @@ -212,6 +212,7 @@ export function apply( { multilineTag, multilineWrapperTags, prepareEditableTree, + __unstableDomOnly, } ) { // Construct a new element tree in memory. const { body, selection } = toDom( { @@ -223,7 +224,7 @@ export function apply( { applyValue( body, current ); - if ( value.start !== undefined ) { + if ( value.start !== undefined && ! __unstableDomOnly ) { applySelection( selection, current ); } } From 0f18024e477bafec475ec3785bb2209562a961c4 Mon Sep 17 00:00:00 2001 From: Joen Asmussen <joen@automattic.com> Date: Sat, 16 Feb 2019 05:49:06 +0100 Subject: [PATCH 452/691] Fix issue with mover colors in dark themes. (#13869) The block editor supports an inverted UI to ensure contrast in themes that register themselves as having dark backgrounds (see https://wordpress.org/gutenberg/handbook/designers-developers/developers/themes/theme-support/#dark-backgrounds). What this mode does, is invert the UI wherever it can, so dark gray borders are light gray on black backgrounds, and the mover icons are white instead of black. But they shouldn't be in nested contexts, because in nested contexts the movers have a white background. This PR fixes that. --- packages/editor/src/components/block-mover/style.scss | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/editor/src/components/block-mover/style.scss b/packages/editor/src/components/block-mover/style.scss index 12ebccbe0035e..39a908806ad5b 100644 --- a/packages/editor/src/components/block-mover/style.scss +++ b/packages/editor/src/components/block-mover/style.scss @@ -42,6 +42,11 @@ color: $light-opacity-300; } + // Nested movers have a background, so don't invert the colors there. + .is-dark-theme .wp-block .wp-block & { + color: $dark-opacity-300; + } + &[aria-disabled="true"] { cursor: default; pointer-events: none; @@ -72,6 +77,11 @@ .is-dark-theme & { color: $light-opacity-500; } + + // Nested movers have a background, so don't invert the colors there. + .is-dark-theme .wp-block .wp-block & { + color: $dark-opacity-500; + } } &:not(:disabled):not([aria-disabled="true"]):not(.is-default):active { From 3db4ff57ce81a9556bd69723744ed4d4d8a53b1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20Van=C2=A0Durpe?= <iseulde@automattic.com> Date: Mon, 18 Feb 2019 01:50:51 +0100 Subject: [PATCH 453/691] RichText: Fix browser formatting buttons (#13833) * RichText: Fix browser formatting buttons * Simplify * componentDidMount instead of componentWillMount --- packages/editor/src/components/index.js | 1 + .../editor/src/components/rich-text/index.js | 16 ++++++++++ .../src/components/rich-text/input-event.js | 30 +++++++++++++++++++ packages/format-library/src/bold/index.js | 6 +++- packages/format-library/src/italic/index.js | 6 +++- .../format-library/src/underline/index.js | 6 +++- 6 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 packages/editor/src/components/rich-text/input-event.js diff --git a/packages/editor/src/components/index.js b/packages/editor/src/components/index.js index b02da43dbfe57..40ce2cc74d559 100644 --- a/packages/editor/src/components/index.js +++ b/packages/editor/src/components/index.js @@ -23,6 +23,7 @@ export { RichTextShortcut, RichTextToolbarButton, RichTextInserterItem, + RichTextInputEvent, } from './rich-text'; export { default as ServerSideRender } from './server-side-render'; export { default as MediaPlaceholder } from './media-placeholder'; diff --git a/packages/editor/src/components/rich-text/index.js b/packages/editor/src/components/rich-text/index.js index 58e9acbde686a..6db85925c595e 100644 --- a/packages/editor/src/components/rich-text/index.js +++ b/packages/editor/src/components/rich-text/index.js @@ -350,6 +350,21 @@ export class RichText extends Component { return; } + if ( event ) { + const { inputType } = event.nativeEvent; + + // The browser formatted something or tried to insert a list. + // Overwrite it. It will be handled later by the format library if + // needed. + if ( + inputType.indexOf( 'format' ) === 0 || + ( inputType.indexOf( 'insert' ) === 0 && inputType !== 'insertText' ) + ) { + this.applyRecord( this.getRecord() ); + return; + } + } + let { selectedFormat } = this.state; const { formats, text, start, end } = this.patterns.reduce( ( accumlator, transform ) => transform( accumlator ), @@ -1106,3 +1121,4 @@ export default RichTextContainer; export { RichTextShortcut } from './shortcut'; export { RichTextToolbarButton } from './toolbar-button'; export { RichTextInserterItem } from './inserter-list-item'; +export { RichTextInputEvent } from './input-event'; diff --git a/packages/editor/src/components/rich-text/input-event.js b/packages/editor/src/components/rich-text/input-event.js new file mode 100644 index 0000000000000..3f7e19d45b75d --- /dev/null +++ b/packages/editor/src/components/rich-text/input-event.js @@ -0,0 +1,30 @@ +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; + +export class RichTextInputEvent extends Component { + constructor() { + super( ...arguments ); + + this.onInput = this.onInput.bind( this ); + } + + onInput( event ) { + if ( event.inputType === this.props.inputType ) { + this.props.onInput(); + } + } + + componentDidMount() { + document.addEventListener( 'input', this.onInput, true ); + } + + componentWillUnmount() { + document.removeEventListener( 'input', this.onInput, true ); + } + + render() { + return null; + } +} diff --git a/packages/format-library/src/bold/index.js b/packages/format-library/src/bold/index.js index dd236edf66798..910c59a2085e2 100644 --- a/packages/format-library/src/bold/index.js +++ b/packages/format-library/src/bold/index.js @@ -4,7 +4,7 @@ import { __ } from '@wordpress/i18n'; import { Fragment } from '@wordpress/element'; import { toggleFormat } from '@wordpress/rich-text'; -import { RichTextToolbarButton, RichTextShortcut } from '@wordpress/editor'; +import { RichTextToolbarButton, RichTextShortcut, RichTextInputEvent } from '@wordpress/editor'; const name = 'core/bold'; @@ -32,6 +32,10 @@ export const bold = { shortcutType="primary" shortcutCharacter="b" /> + <RichTextInputEvent + inputType="formatBold" + onInput={ onToggle } + /> </Fragment> ); }, diff --git a/packages/format-library/src/italic/index.js b/packages/format-library/src/italic/index.js index 3677c6e82acc9..30bf72acd5778 100644 --- a/packages/format-library/src/italic/index.js +++ b/packages/format-library/src/italic/index.js @@ -4,7 +4,7 @@ import { __ } from '@wordpress/i18n'; import { Fragment } from '@wordpress/element'; import { toggleFormat } from '@wordpress/rich-text'; -import { RichTextToolbarButton, RichTextShortcut } from '@wordpress/editor'; +import { RichTextToolbarButton, RichTextShortcut, RichTextInputEvent } from '@wordpress/editor'; const name = 'core/italic'; @@ -32,6 +32,10 @@ export const italic = { shortcutType="primary" shortcutCharacter="i" /> + <RichTextInputEvent + inputType="formatItalic" + onInput={ onToggle } + /> </Fragment> ); }, diff --git a/packages/format-library/src/underline/index.js b/packages/format-library/src/underline/index.js index 3aee9cf943877..bab50a0452eb4 100644 --- a/packages/format-library/src/underline/index.js +++ b/packages/format-library/src/underline/index.js @@ -4,7 +4,7 @@ import { __ } from '@wordpress/i18n'; import { Fragment } from '@wordpress/element'; import { toggleFormat } from '@wordpress/rich-text'; -import { RichTextShortcut } from '@wordpress/editor'; +import { RichTextShortcut, RichTextInputEvent } from '@wordpress/editor'; const name = 'core/underline'; @@ -34,6 +34,10 @@ export const underline = { character="u" onUse={ onToggle } /> + <RichTextInputEvent + inputType="formatUnderline" + onInput={ onToggle } + /> </Fragment> ); }, From fcb90f8123a9e64381c97dc92496547898c4d8ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20Van=C2=A0Durpe?= <iseulde@automattic.com> Date: Mon, 18 Feb 2019 01:51:33 +0100 Subject: [PATCH 454/691] WIP: Add inline image resizing UI (#13737) * Basic functionality * Only update width on submit * Fix styles * Push a little polish * Select objects when clicked * Fix getDerivedStateFromProps * Reselect image after update * Move PositionedAtSelection * Export PositionAtSelection in same way we export other @wordpress/components --- lib/packages-dependencies.php | 1 - package-lock.json | 1 - packages/components/src/index.js | 1 + .../src/positioned-at-selection/index.js} | 4 +- .../src/components/rich-text/editable.js | 4 + .../editor/src/components/rich-text/index.js | 29 ++++++ packages/format-library/package.json | 1 - packages/format-library/src/image/index.js | 88 ++++++++++++++++++- packages/format-library/src/image/style.scss | 21 +++++ packages/format-library/src/link/inline.js | 2 +- packages/format-library/src/style.scss | 1 + 11 files changed, 144 insertions(+), 9 deletions(-) rename packages/{format-library/src/link/positioned-at-selection.js => components/src/positioned-at-selection/index.js} (95%) create mode 100644 packages/format-library/src/image/style.scss diff --git a/lib/packages-dependencies.php b/lib/packages-dependencies.php index fbf5eda814717..c559fce161807 100644 --- a/lib/packages-dependencies.php +++ b/lib/packages-dependencies.php @@ -166,7 +166,6 @@ 'wp-escape-html' => array(), 'wp-format-library' => array( 'wp-components', - 'wp-dom', 'wp-editor', 'wp-element', 'wp-i18n', diff --git a/package-lock.json b/package-lock.json index 69e25252f2a91..581be0214a14f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2818,7 +2818,6 @@ "requires": { "@babel/runtime": "^7.3.1", "@wordpress/components": "file:packages/components", - "@wordpress/dom": "file:packages/dom", "@wordpress/editor": "file:packages/editor", "@wordpress/element": "file:packages/element", "@wordpress/i18n": "file:packages/i18n", diff --git a/packages/components/src/index.js b/packages/components/src/index.js index 2bc81c542f019..6e45caa6e1e05 100644 --- a/packages/components/src/index.js +++ b/packages/components/src/index.js @@ -43,6 +43,7 @@ export { default as PanelHeader } from './panel/header'; export { default as PanelRow } from './panel/row'; export { default as Placeholder } from './placeholder'; export { default as Popover } from './popover'; +export { default as PositionedAtSelection } from './positioned-at-selection'; export { default as QueryControls } from './query-controls'; export { default as RadioControl } from './radio-control'; export { default as RangeControl } from './range-control'; diff --git a/packages/format-library/src/link/positioned-at-selection.js b/packages/components/src/positioned-at-selection/index.js similarity index 95% rename from packages/format-library/src/link/positioned-at-selection.js rename to packages/components/src/positioned-at-selection/index.js index 484cd56debcf5..050050ed1fe93 100644 --- a/packages/format-library/src/link/positioned-at-selection.js +++ b/packages/components/src/positioned-at-selection/index.js @@ -43,7 +43,7 @@ function getCurrentCaretPositionStyle() { * * @type {WPComponent} */ -class PositionedAtSelection extends Component { +export default class PositionedAtSelection extends Component { constructor() { super( ...arguments ); @@ -63,5 +63,3 @@ class PositionedAtSelection extends Component { ); } } - -export default PositionedAtSelection; diff --git a/packages/editor/src/components/rich-text/editable.js b/packages/editor/src/components/rich-text/editable.js index cd668ada44a8a..e375794e907be 100644 --- a/packages/editor/src/components/rich-text/editable.js +++ b/packages/editor/src/components/rich-text/editable.js @@ -168,6 +168,8 @@ export default class Editable extends Component { onCompositionEnd, onFocus, onBlur, + onMouseDown, + onTouchStart, } = this.props; ariaProps.role = 'textbox'; @@ -188,6 +190,8 @@ export default class Editable extends Component { onBlur, onKeyDown, onCompositionEnd, + onMouseDown, + onTouchStart, } ); } } diff --git a/packages/editor/src/components/rich-text/index.js b/packages/editor/src/components/rich-text/index.js index 6db85925c595e..8029dfa0a640d 100644 --- a/packages/editor/src/components/rich-text/index.js +++ b/packages/editor/src/components/rich-text/index.js @@ -111,6 +111,7 @@ export class RichText extends Component { this.setRef = this.setRef.bind( this ); this.valueToEditableHTML = this.valueToEditableHTML.bind( this ); this.handleHorizontalNavigation = this.handleHorizontalNavigation.bind( this ); + this.onPointerDown = this.onPointerDown.bind( this ); this.formatToValue = memize( this.formatToValue.bind( this ), { size: 1 } ); @@ -758,6 +759,32 @@ export class RichText extends Component { this.onSplit( before, after, ...blocks ); } + /** + * Select object when they are clicked. The browser will not set any + * selection when clicking e.g. an image. + * + * @param {SyntheticEvent} event Synthetic mousedown or touchstart event. + */ + onPointerDown( event ) { + const { target } = event; + + // If the child element has no text content, it must be an object. + if ( target === this.editableRef || target.textContent ) { + return; + } + + const { parentNode } = target; + const index = Array.from( parentNode.childNodes ).indexOf( target ); + const range = target.ownerDocument.createRange(); + const selection = getSelection(); + + range.setStart( target.parentNode, index ); + range.setEnd( target.parentNode, index + 1 ); + + selection.removeAllRanges(); + selection.addRange( range ); + } + componentDidUpdate( prevProps ) { const { tagName, value, isSelected } = this.props; @@ -994,6 +1021,8 @@ export class RichText extends Component { onKeyDown={ this.onKeyDown } onFocus={ this.onFocus } onBlur={ this.onBlur } + onMouseDown={ this.onPointerDown } + onTouchStart={ this.onPointerDown } multilineTag={ this.multilineTag } multilineWrapperTags={ this.multilineWrapperTags } setRef={ this.setRef } diff --git a/packages/format-library/package.json b/packages/format-library/package.json index c3e3d6b018950..d8fd5b4b7f366 100644 --- a/packages/format-library/package.json +++ b/packages/format-library/package.json @@ -22,7 +22,6 @@ "dependencies": { "@babel/runtime": "^7.3.1", "@wordpress/components": "file:../components", - "@wordpress/dom": "file:../dom", "@wordpress/editor": "file:../editor", "@wordpress/element": "file:../element", "@wordpress/i18n": "file:../i18n", diff --git a/packages/format-library/src/image/index.js b/packages/format-library/src/image/index.js index c26bcca626a5d..b44d6ddc57c62 100644 --- a/packages/format-library/src/image/index.js +++ b/packages/format-library/src/image/index.js @@ -1,16 +1,19 @@ /** * WordPress dependencies */ -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG, TextControl, Popover, IconButton, PositionedAtSelection } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; import { insertObject } from '@wordpress/rich-text'; import { MediaUpload, RichTextInserterItem, MediaUploadCheck } from '@wordpress/editor'; +import { LEFT, RIGHT, UP, DOWN, BACKSPACE, ENTER } from '@wordpress/keycodes'; const ALLOWED_MEDIA_TYPES = [ 'image' ]; const name = 'core/image'; +const stopKeyPropagation = ( event ) => event.stopPropagation(); + export const image = { name, title: __( 'Image' ), @@ -27,6 +30,8 @@ export const image = { edit: class ImageEdit extends Component { constructor() { super( ...arguments ); + this.onChange = this.onChange.bind( this ); + this.onKeyDown = this.onKeyDown.bind( this ); this.openModal = this.openModal.bind( this ); this.closeModal = this.closeModal.bind( this ); this.state = { @@ -34,6 +39,37 @@ export const image = { }; } + static getDerivedStateFromProps( props, state ) { + const { activeAttributes: { style } } = props; + + if ( style === state.previousStyle ) { + return null; + } + + if ( ! style ) { + return { + width: undefined, + previousStyle: style, + }; + } + + return { + width: style.replace( /\D/g, '' ), + previousStyle: style, + }; + } + + onChange( width ) { + this.setState( { width } ); + } + + onKeyDown( event ) { + if ( [ LEFT, DOWN, RIGHT, UP, BACKSPACE, ENTER ].indexOf( event.keyCode ) > -1 ) { + // Stop the key event from propagating up to ObserveTyping.startTypingInTextField. + event.stopPropagation(); + } + } + openModal() { this.setState( { modal: true } ); } @@ -43,7 +79,11 @@ export const image = { } render() { - const { value, onChange } = this.props; + const { value, onChange, isActive, activeAttributes } = this.props; + const { style } = activeAttributes; + // Rerender PositionedAtSelection when the selection changes or when + // the width changes. + const key = value.start + style; return ( <MediaUploadCheck> @@ -73,6 +113,50 @@ export const image = { return null; } } /> } + { isActive && <PositionedAtSelection key={ key }> + <Popover + position="bottom center" + focusOnMount={ false } + > + { // Disable reason: KeyPress must be suppressed so the block doesn't hide the toolbar + /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ } + <form + className="editor-format-toolbar__image-container-content" + onKeyPress={ stopKeyPropagation } + onKeyDown={ this.onKeyDown } + onSubmit={ ( event ) => { + const newFormats = value.formats.slice( 0 ); + + newFormats[ value.start ] = [ { + type: name, + object: true, + attributes: { + ...activeAttributes, + style: `width: ${ this.state.width }px;`, + }, + } ]; + + onChange( { + ...value, + formats: newFormats, + } ); + + event.preventDefault(); + } } + > + <TextControl + className="editor-format-toolbar__image-container-value" + type="number" + label={ __( 'Width' ) } + value={ this.state.width } + min={ 1 } + onChange={ this.onChange } + /> + <IconButton icon="editor-break" label={ __( 'Apply' ) } type="submit" /> + </form> + { /* eslint-enable jsx-a11y/no-noninteractive-element-interactions */ } + </Popover> + </PositionedAtSelection> } </MediaUploadCheck> ); } diff --git a/packages/format-library/src/image/style.scss b/packages/format-library/src/image/style.scss new file mode 100644 index 0000000000000..bfc4599920a21 --- /dev/null +++ b/packages/format-library/src/image/style.scss @@ -0,0 +1,21 @@ +.editor-format-toolbar__image-container-content { + display: flex; + + .components-icon-button { + height: $icon-button-size + $grid-size + $grid-size; + align-self: flex-end; + } +} + +.editor-format-toolbar__image-container-value { + margin: $grid-size - $border-width; + flex-grow: 1; + flex-shrink: 1; + white-space: nowrap; + min-width: 150px; + max-width: 500px; + + &.components-base-control .components-base-control__field { + margin-bottom: 0; + } +} diff --git a/packages/format-library/src/link/inline.js b/packages/format-library/src/link/inline.js index cb2e4ea1025d9..2d79e11735f1c 100644 --- a/packages/format-library/src/link/inline.js +++ b/packages/format-library/src/link/inline.js @@ -13,6 +13,7 @@ import { IconButton, ToggleControl, withSpokenMessages, + PositionedAtSelection, } from '@wordpress/components'; import { LEFT, RIGHT, UP, DOWN, BACKSPACE, ENTER } from '@wordpress/keycodes'; import { prependHTTP, safeDecodeURI, filterURLForDisplay } from '@wordpress/url'; @@ -29,7 +30,6 @@ import { URLInput, URLPopover } from '@wordpress/editor'; /** * Internal dependencies */ -import PositionedAtSelection from './positioned-at-selection'; import { isValidHref } from './utils'; const stopKeyPropagation = ( event ) => event.stopPropagation(); diff --git a/packages/format-library/src/style.scss b/packages/format-library/src/style.scss index f421ddc28f928..a9ab600a7ad7d 100644 --- a/packages/format-library/src/style.scss +++ b/packages/format-library/src/style.scss @@ -1 +1,2 @@ +@import "./image/style.scss"; @import "./link/style.scss"; From b8b26d4f07b6d46f311652fee2d2930c2a07fc78 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Sun, 17 Feb 2019 19:53:11 -0500 Subject: [PATCH 455/691] Plugin: Extract block editor styles replacement as filter (#13625) * Plugin: Extract block editor styles replacement as filter * Plugin: Override default styles by exact file contents match * Plugin: Fix editor styles prepend behavior --- lib/client-assets.php | 63 ++++++++- phpunit/class-extend-styles-test.php | 185 +++++++++++++++++++++++++++ 2 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 phpunit/class-extend-styles-test.php diff --git a/lib/client-assets.php b/lib/client-assets.php index 2302345beaa8d..3030459fbfce8 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -840,6 +840,67 @@ function gutenberg_get_available_image_sizes() { return $all_sizes; } +/** + * Extends block editor settings to include Gutenberg's `editor-styles.css` as + * taking precedent those styles shipped with core. + * + * @param array $settings Default editor settings. + * + * @return array Filtered editor settings. + */ +function gutenberg_extend_block_editor_styles( $settings ) { + $editor_styles_file = gutenberg_dir_path() . 'build/editor/editor-styles.css'; + + /* + * If, for whatever reason, the built editor styles do not exist, avoid + * override and fall back to the default. + */ + if ( ! file_exists( $editor_styles_file ) ) { + return $settings; + } + + if ( empty( $settings['styles'] ) ) { + $settings['styles'] = array(); + } else { + /* + * The styles setting is an array of CSS strings, so there is no direct + * way to find the default styles. To maximize stability, load (again) + * the default styles from disk and find its place in the array. + * + * See: https://github.com/WordPress/wordpress-develop/blob/5.0.3/src/wp-admin/edit-form-blocks.php#L168-L175 + */ + + $default_styles = file_get_contents( + ABSPATH . WPINC . '/css/dist/editor/editor-styles.css' + ); + + /* + * Iterate backwards from the end of the array since the preferred + * insertion point in case not found is prepended as first entry. + */ + for ( $i = count( $settings['styles'] ) - 1; $i >= 0; $i-- ) { + if ( isset( $settings['styles'][ $i ]['css'] ) && + $default_styles === $settings['styles'][ $i ]['css'] ) { + break; + } + } + } + + $editor_styles = array( + 'css' => file_get_contents( $editor_styles_file ), + ); + + // Substitute default styles if found. Otherwise, prepend to setting array. + if ( isset( $i ) && $i >= 0 ) { + $settings['styles'][ $i ] = $editor_styles; + } else { + array_unshift( $settings['styles'], $editor_styles ); + } + + return $settings; +} +add_filter( 'block_editor_settings', 'gutenberg_extend_block_editor_styles' ); + /** * Scripts & Styles. * @@ -1002,7 +1063,7 @@ function gutenberg_editor_scripts_and_styles( $hook ) { $styles = array( array( 'css' => file_get_contents( - gutenberg_dir_path() . 'build/editor/editor-styles.css' + ABSPATH . WPINC . '/css/dist/editor/editor-styles.css' ), ), ); diff --git a/phpunit/class-extend-styles-test.php b/phpunit/class-extend-styles-test.php new file mode 100644 index 0000000000000..6ae12ada22797 --- /dev/null +++ b/phpunit/class-extend-styles-test.php @@ -0,0 +1,185 @@ +<?php +/** + * Test `gutenberg_extend_block_editor_styles`. + * + * @package Gutenberg + */ + +class Extend_Styles_Test extends WP_UnitTestCase { + + /** + * Path of the original `editor-styles.css` file. + * + * @var string|null + */ + protected $orignal_file = null; + + /** + * Contents of the `editor-styles.css` file. + * + * @var string + */ + protected $style_contents = null; + + public function wpTearDown() { + parent::wpTearDown(); + + $this->restore_editor_styles(); + } + + /** + * Restores the existence of `editor-styles.css` to its original state. + */ + protected function restore_editor_styles() { + $path = gutenberg_dir_path() . 'build/editor/editor-styles.css'; + + if ( $this->original_file ) { + if ( $this->original_file !== $path ) { + rename( $this->original_file, $path ); + } + } elseif ( file_exists( $path ) ) { + unlink( $path ); + } + + $this->style_contents = null; + $this->original_file = null; + } + + /** + * Guarantees that an `editor-styles.css` file exists, if and only if it + * should exist. Assigns `style_contents` according to the contents of the + * file if it should exist. Renames the existing file temporarily if it + * exists but should not. + * + * @param bool $should_exist Whether the editor styles file should exist. + */ + protected function ensure_editor_styles( $should_exist = true ) { + $path = gutenberg_dir_path() . 'build/editor/editor-styles.css'; + + if ( file_exists( $path ) ) { + if ( $should_exist ) { + $this->style_contents = file_get_contents( $path ); + $this->original_file = $path; + } else { + rename( $path, $path . '.bak' ); + $this->original_file = $path . '.bak'; + } + } elseif ( $should_exist ) { + $this->style_contents = ''; + file_put_contents( $path, $this->style_contents ); + $this->original_file = null; + } + } + + /** + * Tests a non-existent build `styles`. + */ + function test_without_built_styles() { + $this->ensure_editor_styles( false ); + + $settings = array( + 'styles' => array( + array( 'css' => 'original' ), + array( 'css' => 'someother' ), + ), + ); + + $result = gutenberg_extend_block_editor_styles( $settings ); + + $this->assertEquals( $settings, $result ); + } + + /** + * Tests an unset `styles` setting. + */ + function test_unset_editor_settings_style() { + $this->ensure_editor_styles(); + + $settings = array(); + + $settings = gutenberg_extend_block_editor_styles( $settings ); + + $this->assertEquals( + array( array( 'css' => $this->style_contents ) ), + $settings['styles'] + ); + } + + /** + * Tests replacing the default styles. + */ + function test_replace_default_editor_styles() { + $this->ensure_editor_styles(); + $default_styles = file_get_contents( + ABSPATH . WPINC . '/css/dist/editor/editor-styles.css' + ); + + $settings = array( + 'styles' => array( + array( 'css' => $default_styles ), + array( 'css' => 'someother' ), + ), + ); + + $settings = gutenberg_extend_block_editor_styles( $settings ); + + $this->assertEquals( + array( + array( 'css' => $this->style_contents ), + array( 'css' => 'someother' ), + ), + $settings['styles'] + ); + } + + /** + * Tests replacing the rearranged default styles. + */ + function test_replace_rearranged_default_editor_styles() { + $this->ensure_editor_styles(); + $default_styles = file_get_contents( + ABSPATH . WPINC . '/css/dist/editor/editor-styles.css' + ); + + $settings = array( + 'styles' => array( + array( 'css' => 'someother' ), + array( 'css' => $default_styles ), + ), + ); + + $settings = gutenberg_extend_block_editor_styles( $settings ); + + $this->assertEquals( + array( + array( 'css' => 'someother' ), + array( 'css' => $this->style_contents ), + ), + $settings['styles'] + ); + } + + /** + * Tests when the default styles aren't in the styles setting. + */ + function test_without_default_editor_styles() { + $this->ensure_editor_styles(); + + $settings = array( + 'styles' => array( + array( 'css' => 'someother' ), + ), + ); + + $settings = gutenberg_extend_block_editor_styles( $settings ); + + $this->assertEquals( + array( + array( 'css' => $this->style_contents ), + array( 'css' => 'someother' ), + ), + $settings['styles'] + ); + } + +} From 54bae98f51dd4a12d68022ff1a2806253d18fefa Mon Sep 17 00:00:00 2001 From: Karol Gorski <naerriel@gmail.com> Date: Mon, 18 Feb 2019 01:54:58 +0100 Subject: [PATCH 456/691] Decode permalinks with URL encoding (#13551) * Decode URL encoding slug in sidebar permalink * Decode URL encoding slug in sidebar permalink pt.2. * Decode URL encoding slug in sidebar permalink pt.3. * Decode URL encoding slug in sidebar permalink pt.4. --- .../src/components/sidebar/post-link/index.js | 3 ++- .../src/components/post-permalink/index.js | 4 ++-- .../components/post-publish-panel/postpublish.js | 3 ++- packages/url/src/index.js | 16 ++++++++++++++++ 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/edit-post/src/components/sidebar/post-link/index.js b/packages/edit-post/src/components/sidebar/post-link/index.js index c6305b6311066..6b85cb8739a46 100644 --- a/packages/edit-post/src/components/sidebar/post-link/index.js +++ b/packages/edit-post/src/components/sidebar/post-link/index.js @@ -12,6 +12,7 @@ import { PanelBody, TextControl, ExternalLink } from '@wordpress/components'; import { withSelect, withDispatch } from '@wordpress/data'; import { compose, ifCondition, withState } from '@wordpress/compose'; import { cleanForSlug } from '@wordpress/editor'; +import { safeDecodeURIComponent } from '@wordpress/url'; /** * Module Constants @@ -33,7 +34,7 @@ function PostLink( { } ) { const { prefix, suffix } = permalinkParts; let prefixElement, postNameElement, suffixElement; - const currentSlug = postSlug || cleanForSlug( postTitle ) || postID; + const currentSlug = safeDecodeURIComponent( postSlug ) || cleanForSlug( postTitle ) || postID; if ( isEditable ) { prefixElement = prefix && ( <span className="edit-post-post-link__link-prefix">{ prefix }</span> diff --git a/packages/editor/src/components/post-permalink/index.js b/packages/editor/src/components/post-permalink/index.js index 5205997f76e5e..aa713b4bf8262 100644 --- a/packages/editor/src/components/post-permalink/index.js +++ b/packages/editor/src/components/post-permalink/index.js @@ -12,7 +12,7 @@ import { Component } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { compose } from '@wordpress/compose'; import { ClipboardButton, Button, ExternalLink } from '@wordpress/components'; -import { safeDecodeURI } from '@wordpress/url'; +import { safeDecodeURI, safeDecodeURIComponent } from '@wordpress/url'; /** * Internal dependencies @@ -78,7 +78,7 @@ class PostPermalink extends Component { const ariaLabel = isCopied ? __( 'Permalink copied' ) : __( 'Copy the permalink' ); const { prefix, suffix } = permalinkParts; - const slug = postSlug || cleanForSlug( postTitle ) || postID; + const slug = safeDecodeURIComponent( postSlug ) || cleanForSlug( postTitle ) || postID; const samplePermalink = ( isEditable ) ? prefix + slug + suffix : prefix; return ( diff --git a/packages/editor/src/components/post-publish-panel/postpublish.js b/packages/editor/src/components/post-publish-panel/postpublish.js index 97fb7ff7ca3cd..6527e40560489 100644 --- a/packages/editor/src/components/post-publish-panel/postpublish.js +++ b/packages/editor/src/components/post-publish-panel/postpublish.js @@ -10,6 +10,7 @@ import { PanelBody, Button, ClipboardButton, TextControl } from '@wordpress/comp import { __, sprintf } from '@wordpress/i18n'; import { Component, Fragment, createRef } from '@wordpress/element'; import { withSelect } from '@wordpress/data'; +import { safeDecodeURIComponent } from '@wordpress/url'; /** * Internal dependencies @@ -79,7 +80,7 @@ class PostPublishPanelPostpublish extends Component { /* translators: %s: post type singular name */ __( '%s address' ), postLabel ) } - value={ post.link } + value={ safeDecodeURIComponent( post.link ) } onFocus={ this.onSelectInput } /> <div className="post-publish-panel__postpublish-buttons"> diff --git a/packages/url/src/index.js b/packages/url/src/index.js index 8e9bad1165a0c..d060f3539d76e 100644 --- a/packages/url/src/index.js +++ b/packages/url/src/index.js @@ -287,3 +287,19 @@ export function filterURLForDisplay( url ) { return filteredURL; } + +/** + * Safely decodes a URI component with `decodeURIComponent`. Returns the URI component unmodified if + * `decodeURIComponent` throws an error. + * + * @param {string} uriComponent URI component to decode. + * + * @return {string} Decoded URI component if possible. + */ +export function safeDecodeURIComponent( uriComponent ) { + try { + return decodeURIComponent( uriComponent ); + } catch ( uriComponentError ) { + return uriComponent; + } +} From f51fd27081e514c85c2ed419151dd71559d61526 Mon Sep 17 00:00:00 2001 From: Robert Anderson <robert@noisysocks.com> Date: Mon, 18 Feb 2019 14:21:16 +1100 Subject: [PATCH 457/691] Bump plugin version to 5.1.0-rc.1 (#13908) --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 990395c0b9c6b..f1364f2860b64 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,7 +3,7 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the new block editor in core. - * Version: 5.0.0 + * Version: 5.1.0-rc.1 * Author: Gutenberg Team * * @package gutenberg diff --git a/package-lock.json b/package-lock.json index 581be0214a14f..e7c688511be70 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.0.0", + "version": "5.1.0-rc.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 758213c258bea..a467efd345915 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.0.0", + "version": "5.1.0-rc.1", "private": true, "description": "A new WordPress editor experience", "repository": "git+https://github.com/WordPress/gutenberg.git", From 5dc9ac48ea25b669f30458bc1241d29289da0813 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Sun, 17 Feb 2019 23:36:55 -0800 Subject: [PATCH 458/691] Add link to tutorial on main Handbook index (#13906) --- docs/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/readme.md b/docs/readme.md index eb699e6a2ed08..a95a1b0ea5abf 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -6,7 +6,7 @@ The Gutenberg project provides three sources of documentation: Learn how to build blocks and extend the editor, best practices for designing block interfaces, and how to create themes that make the most of the new features Gutenberg provides. -[Visit the Designer & Developer Handbook](/docs/designers-developers/readme.md) +Start with [the Designer & Developer Handbook](/docs/designers-developers/readme.md) to learn the background and concepts, or jump straight to [Gutenberg Tutorials](/docs/designers-developers/developers/tutorials/readme.md) for detailed examples. ## User Handbook From c583ea29c869ea2a7028c03551b8925f9a9777df Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Mon, 18 Feb 2019 10:49:33 +0000 Subject: [PATCH 459/691] Refactor: Move block fixtures to e2e-tests package (#13658) --- package.json | 2 +- .../e2e-tests/fixtures/blocks}/README.md | 0 .../core__4-invalid-starting-letter.html | 0 .../core__4-invalid-starting-letter.json | 0 ...ore__4-invalid-starting-letter.parsed.json | 0 ..._4-invalid-starting-letter.serialized.html | 0 .../fixtures/blocks}/core__archives.html | 0 .../fixtures/blocks}/core__archives.json | 0 .../blocks}/core__archives.parsed.json | 0 .../blocks}/core__archives.serialized.html | 0 .../core__archives__showPostCounts.html | 0 .../core__archives__showPostCounts.json | 0 ...core__archives__showPostCounts.parsed.json | 0 ...__archives__showPostCounts.serialized.html | 0 .../fixtures/blocks}/core__audio.html | 0 .../fixtures/blocks}/core__audio.json | 0 .../fixtures/blocks}/core__audio.parsed.json | 0 .../blocks}/core__audio.serialized.html | 0 .../fixtures/blocks}/core__block.html | 0 .../fixtures/blocks}/core__block.json | 0 .../fixtures/blocks}/core__block.parsed.json | 0 .../blocks}/core__block.serialized.html | 0 .../blocks}/core__button__center.html | 0 .../blocks}/core__button__center.json | 0 .../blocks}/core__button__center.parsed.json | 0 .../core__button__center.serialized.html | 0 .../fixtures/blocks}/core__calendar.html | 0 .../fixtures/blocks}/core__calendar.json | 0 .../blocks}/core__calendar.parsed.json | 0 .../blocks}/core__calendar.serialized.html | 0 .../fixtures/blocks}/core__categories.html | 0 .../fixtures/blocks}/core__categories.json | 0 .../blocks}/core__categories.parsed.json | 0 .../blocks}/core__categories.serialized.html | 0 .../fixtures/blocks}/core__code.html | 0 .../fixtures/blocks}/core__code.json | 0 .../fixtures/blocks}/core__code.parsed.json | 0 .../blocks}/core__code.serialized.html | 0 .../fixtures/blocks}/core__column.html | 0 .../fixtures/blocks}/core__column.json | 0 .../fixtures/blocks}/core__column.parsed.json | 0 .../blocks}/core__column.serialized.html | 0 .../fixtures/blocks}/core__columns.html | 0 .../fixtures/blocks}/core__columns.json | 0 .../blocks}/core__columns.parsed.json | 0 .../blocks}/core__columns.serialized.html | 0 .../blocks}/core__columns__deprecated.html | 0 .../blocks}/core__columns__deprecated.json | 0 .../core__columns__deprecated.parsed.json | 0 .../core__columns__deprecated.serialized.html | 0 .../fixtures/blocks}/core__cover.html | 0 .../fixtures/blocks}/core__cover.json | 0 .../fixtures/blocks}/core__cover.parsed.json | 0 .../blocks}/core__cover.serialized.html | 0 .../blocks}/core__cover__video-overlay.html | 0 .../blocks}/core__cover__video-overlay.json | 0 .../core__cover__video-overlay.parsed.json | 0 ...core__cover__video-overlay.serialized.html | 0 .../fixtures/blocks}/core__cover__video.html | 0 .../fixtures/blocks}/core__cover__video.json | 0 .../blocks}/core__cover__video.parsed.json | 0 .../core__cover__video.serialized.html | 0 .../fixtures/blocks}/core__embed.html | 0 .../fixtures/blocks}/core__embed.json | 0 .../fixtures/blocks}/core__embed.parsed.json | 0 .../blocks}/core__embed.serialized.html | 0 .../blocks}/core__file__new-window.html | 0 .../blocks}/core__file__new-window.json | 0 .../core__file__new-window.parsed.json | 0 .../core__file__new-window.serialized.html | 0 .../core__file__no-download-button.html | 0 .../core__file__no-download-button.json | 0 ...core__file__no-download-button.parsed.json | 0 ...__file__no-download-button.serialized.html | 0 .../blocks}/core__file__no-text-link.html | 0 .../blocks}/core__file__no-text-link.json | 0 .../core__file__no-text-link.parsed.json | 0 .../core__file__no-text-link.serialized.html | 0 .../fixtures/blocks}/core__freeform.html | 0 .../fixtures/blocks}/core__freeform.json | 0 .../blocks}/core__freeform.parsed.json | 0 .../blocks}/core__freeform.serialized.html | 0 .../blocks}/core__freeform__undelimited.html | 0 .../blocks}/core__freeform__undelimited.json | 0 .../core__freeform__undelimited.parsed.json | 0 ...ore__freeform__undelimited.serialized.html | 0 .../fixtures/blocks}/core__gallery.html | 0 .../fixtures/blocks}/core__gallery.json | 0 .../blocks}/core__gallery.parsed.json | 0 .../blocks}/core__gallery.serialized.html | 0 .../blocks}/core__gallery__columns.html | 0 .../blocks}/core__gallery__columns.json | 0 .../core__gallery__columns.parsed.json | 0 .../core__gallery__columns.serialized.html | 0 .../blocks}/core__heading__h2-em.html | 0 .../blocks}/core__heading__h2-em.json | 0 .../blocks}/core__heading__h2-em.parsed.json | 0 .../core__heading__h2-em.serialized.html | 0 .../fixtures/blocks}/core__heading__h2.html | 0 .../fixtures/blocks}/core__heading__h2.json | 0 .../blocks}/core__heading__h2.parsed.json | 0 .../blocks}/core__heading__h2.serialized.html | 0 .../fixtures/blocks}/core__html.html | 0 .../fixtures/blocks}/core__html.json | 0 .../fixtures/blocks}/core__html.parsed.json | 0 .../blocks}/core__html.serialized.html | 0 .../fixtures/blocks}/core__image.html | 0 .../fixtures/blocks}/core__image.json | 0 .../fixtures/blocks}/core__image.parsed.json | 0 .../blocks}/core__image.serialized.html | 0 .../blocks}/core__image__attachment-link.html | 0 .../blocks}/core__image__attachment-link.json | 0 .../core__image__attachment-link.parsed.json | 0 ...re__image__attachment-link.serialized.html | 0 .../blocks}/core__image__center-caption.html | 0 .../blocks}/core__image__center-caption.json | 0 .../core__image__center-caption.parsed.json | 0 ...ore__image__center-caption.serialized.html | 0 .../core__image__custom-link-class.html | 0 .../core__image__custom-link-class.json | 0 ...core__image__custom-link-class.parsed.json | 0 ...__image__custom-link-class.serialized.html | 0 .../blocks}/core__image__custom-link-rel.html | 0 .../blocks}/core__image__custom-link-rel.json | 0 .../core__image__custom-link-rel.parsed.json | 0 ...re__image__custom-link-rel.serialized.html | 0 .../blocks}/core__image__custom-link.html | 0 .../blocks}/core__image__custom-link.json | 0 .../core__image__custom-link.parsed.json | 0 .../core__image__custom-link.serialized.html | 0 .../blocks}/core__image__media-link.html | 0 .../blocks}/core__image__media-link.json | 0 .../core__image__media-link.parsed.json | 0 .../core__image__media-link.serialized.html | 0 .../blocks}/core__invalid-Capitals.html | 0 .../blocks}/core__invalid-Capitals.json | 0 .../core__invalid-Capitals.parsed.json | 0 .../core__invalid-Capitals.serialized.html | 0 .../blocks}/core__invalid-special.html | 0 .../blocks}/core__invalid-special.json | 0 .../blocks}/core__invalid-special.parsed.json | 0 .../core__invalid-special.serialized.html | 0 .../blocks}/core__latest-comments.html | 0 .../blocks}/core__latest-comments.json | 0 .../blocks}/core__latest-comments.parsed.json | 0 .../core__latest-comments.serialized.html | 0 .../fixtures/blocks}/core__latest-posts.html | 0 .../fixtures/blocks}/core__latest-posts.json | 0 .../blocks}/core__latest-posts.parsed.json | 0 .../core__latest-posts.serialized.html | 0 .../core__latest-posts__displayPostDate.html | 0 .../core__latest-posts__displayPostDate.json | 0 ..._latest-posts__displayPostDate.parsed.json | 0 ...est-posts__displayPostDate.serialized.html | 0 .../fixtures/blocks}/core__list__ul.html | 0 .../fixtures/blocks}/core__list__ul.json | 0 .../blocks}/core__list__ul.parsed.json | 0 .../blocks}/core__list__ul.serialized.html | 0 .../fixtures/blocks}/core__media-text.html | 0 .../fixtures/blocks}/core__media-text.json | 0 .../blocks}/core__media-text.parsed.json | 0 .../blocks}/core__media-text.serialized.html | 0 .../core__media-text__image-alt-no-align.html | 0 .../core__media-text__image-alt-no-align.json | 0 ...media-text__image-alt-no-align.parsed.json | 0 ...a-text__image-alt-no-align.serialized.html | 0 ...ore__media-text__is-stacked-on-mobile.html | 0 ...ore__media-text__is-stacked-on-mobile.json | 0 ...dia-text__is-stacked-on-mobile.parsed.json | 0 ...text__is-stacked-on-mobile.serialized.html | 0 ..._media-text__media-right-custom-width.html | 0 ..._media-text__media-right-custom-width.json | 0 ...text__media-right-custom-width.parsed.json | 0 ...__media-right-custom-width.serialized.html | 0 .../blocks}/core__media-text__video.html | 0 .../blocks}/core__media-text__video.json | 0 .../core__media-text__video.parsed.json | 0 .../core__media-text__video.serialized.html | 0 .../fixtures/blocks}/core__missing.html | 0 .../fixtures/blocks}/core__missing.json | 0 .../blocks}/core__missing.parsed.json | 0 .../blocks}/core__missing.serialized.html | 0 .../fixtures/blocks}/core__more.html | 0 .../fixtures/blocks}/core__more.json | 0 .../fixtures/blocks}/core__more.parsed.json | 0 .../blocks}/core__more.serialized.html | 0 .../core__more__custom-text-teaser.html | 0 .../core__more__custom-text-teaser.json | 0 ...core__more__custom-text-teaser.parsed.json | 0 ...__more__custom-text-teaser.serialized.html | 0 .../fixtures/blocks}/core__nextpage.html | 0 .../fixtures/blocks}/core__nextpage.json | 0 .../blocks}/core__nextpage.parsed.json | 0 .../blocks}/core__nextpage.serialized.html | 0 .../blocks}/core__paragraph__align-right.html | 0 .../blocks}/core__paragraph__align-right.json | 0 .../core__paragraph__align-right.parsed.json | 0 ...re__paragraph__align-right.serialized.html | 0 .../blocks}/core__paragraph__deprecated.html | 0 .../blocks}/core__paragraph__deprecated.json | 0 .../core__paragraph__deprecated.parsed.json | 0 ...ore__paragraph__deprecated.serialized.html | 0 .../fixtures/blocks}/core__preformatted.html | 0 .../fixtures/blocks}/core__preformatted.json | 0 .../blocks}/core__preformatted.parsed.json | 0 .../core__preformatted.serialized.html | 0 .../fixtures/blocks}/core__pullquote.html | 0 .../fixtures/blocks}/core__pullquote.json | 0 .../blocks}/core__pullquote.parsed.json | 0 .../blocks}/core__pullquote.serialized.html | 0 .../core__pullquote__multi-paragraph.html | 0 .../core__pullquote__multi-paragraph.json | 0 ...re__pullquote__multi-paragraph.parsed.json | 0 ...pullquote__multi-paragraph.serialized.html | 0 .../blocks}/core__quote__style-1.html | 0 .../blocks}/core__quote__style-1.json | 0 .../blocks}/core__quote__style-1.parsed.json | 0 .../core__quote__style-1.serialized.html | 0 .../blocks}/core__quote__style-2.html | 0 .../blocks}/core__quote__style-2.json | 0 .../blocks}/core__quote__style-2.parsed.json | 0 .../core__quote__style-2.serialized.html | 0 .../e2e-tests/fixtures/blocks}/core__rss.html | 0 .../e2e-tests/fixtures/blocks}/core__rss.json | 0 .../fixtures/blocks}/core__rss.parsed.json | 0 .../blocks}/core__rss.serialized.html | 0 .../fixtures/blocks}/core__search.html | 0 .../fixtures/blocks}/core__search.json | 0 .../fixtures/blocks}/core__search.parsed.json | 0 .../blocks}/core__search.serialized.html | 0 .../blocks}/core__search__custom-text.html | 0 .../blocks}/core__search__custom-text.json | 0 .../core__search__custom-text.parsed.json | 0 .../core__search__custom-text.serialized.html | 0 .../fixtures/blocks}/core__separator.html | 0 .../fixtures/blocks}/core__separator.json | 0 .../blocks}/core__separator.parsed.json | 0 .../blocks}/core__separator.serialized.html | 0 .../fixtures/blocks}/core__shortcode.html | 0 .../fixtures/blocks}/core__shortcode.json | 0 .../blocks}/core__shortcode.parsed.json | 0 .../blocks}/core__shortcode.serialized.html | 0 .../fixtures/blocks}/core__spacer.html | 0 .../fixtures/blocks}/core__spacer.json | 0 .../fixtures/blocks}/core__spacer.parsed.json | 0 .../blocks}/core__spacer.serialized.html | 0 .../fixtures/blocks}/core__subhead.html | 0 .../fixtures/blocks}/core__subhead.json | 0 .../blocks}/core__subhead.parsed.json | 0 .../blocks}/core__subhead.serialized.html | 0 .../fixtures/blocks}/core__table.html | 0 .../fixtures/blocks}/core__table.json | 0 .../fixtures/blocks}/core__table.parsed.json | 0 .../blocks}/core__table.serialized.html | 0 .../fixtures/blocks}/core__tag-cloud.html | 0 .../fixtures/blocks}/core__tag-cloud.json | 0 .../blocks}/core__tag-cloud.parsed.json | 0 .../blocks}/core__tag-cloud.serialized.html | 0 .../core__tag-cloud__showTagCounts.html | 0 .../core__tag-cloud__showTagCounts.json | 0 ...core__tag-cloud__showTagCounts.parsed.json | 0 ...__tag-cloud__showTagCounts.serialized.html | 0 .../fixtures/blocks}/core__text-columns.html | 0 .../fixtures/blocks}/core__text-columns.json | 0 .../blocks}/core__text-columns.parsed.json | 0 .../core__text-columns.serialized.html | 0 .../core__text__converts-to-paragraph.html | 0 .../core__text__converts-to-paragraph.json | 0 ...e__text__converts-to-paragraph.parsed.json | 0 ...ext__converts-to-paragraph.serialized.html | 0 .../fixtures/blocks}/core__verse.html | 0 .../fixtures/blocks}/core__verse.json | 0 .../fixtures/blocks}/core__verse.parsed.json | 0 .../blocks}/core__verse.serialized.html | 0 .../fixtures/blocks}/core__video.html | 0 .../fixtures/blocks}/core__video.json | 0 .../fixtures/blocks}/core__video.parsed.json | 0 .../blocks}/core__video.serialized.html | 0 packages/e2e-tests/fixtures/index.js | 12 ++ packages/e2e-tests/fixtures/utils.js | 92 +++++++++ .../full-content/full-content.spec.js | 192 +++++++++--------- 281 files changed, 200 insertions(+), 98 deletions(-) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/README.md (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__4-invalid-starting-letter.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__4-invalid-starting-letter.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__4-invalid-starting-letter.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__4-invalid-starting-letter.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__archives.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__archives.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__archives.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__archives.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__archives__showPostCounts.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__archives__showPostCounts.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__archives__showPostCounts.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__archives__showPostCounts.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__audio.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__audio.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__audio.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__audio.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__block.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__block.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__block.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__block.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__button__center.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__button__center.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__button__center.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__button__center.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__calendar.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__calendar.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__calendar.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__calendar.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__categories.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__categories.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__categories.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__categories.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__code.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__code.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__code.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__code.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__column.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__column.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__column.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__column.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__columns.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__columns.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__columns.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__columns.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__columns__deprecated.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__columns__deprecated.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__columns__deprecated.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__columns__deprecated.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__cover.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__cover.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__cover.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__cover.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__cover__video-overlay.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__cover__video-overlay.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__cover__video-overlay.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__cover__video-overlay.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__cover__video.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__cover__video.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__cover__video.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__cover__video.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__embed.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__embed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__embed.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__embed.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__file__new-window.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__file__new-window.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__file__new-window.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__file__new-window.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__file__no-download-button.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__file__no-download-button.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__file__no-download-button.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__file__no-download-button.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__file__no-text-link.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__file__no-text-link.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__file__no-text-link.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__file__no-text-link.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__freeform.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__freeform.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__freeform.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__freeform.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__freeform__undelimited.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__freeform__undelimited.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__freeform__undelimited.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__freeform__undelimited.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__gallery.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__gallery.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__gallery.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__gallery.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__gallery__columns.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__gallery__columns.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__gallery__columns.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__gallery__columns.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__heading__h2-em.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__heading__h2-em.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__heading__h2-em.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__heading__h2-em.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__heading__h2.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__heading__h2.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__heading__h2.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__heading__h2.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__html.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__html.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__html.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__html.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__image.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__image.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__image.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__image.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__image__attachment-link.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__image__attachment-link.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__image__attachment-link.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__image__attachment-link.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__image__center-caption.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__image__center-caption.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__image__center-caption.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__image__center-caption.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__image__custom-link-class.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__image__custom-link-class.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__image__custom-link-class.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__image__custom-link-class.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__image__custom-link-rel.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__image__custom-link-rel.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__image__custom-link-rel.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__image__custom-link-rel.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__image__custom-link.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__image__custom-link.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__image__custom-link.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__image__custom-link.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__image__media-link.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__image__media-link.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__image__media-link.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__image__media-link.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__invalid-Capitals.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__invalid-Capitals.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__invalid-Capitals.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__invalid-Capitals.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__invalid-special.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__invalid-special.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__invalid-special.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__invalid-special.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__latest-comments.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__latest-comments.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__latest-comments.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__latest-comments.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__latest-posts.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__latest-posts.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__latest-posts.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__latest-posts.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__latest-posts__displayPostDate.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__latest-posts__displayPostDate.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__latest-posts__displayPostDate.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__latest-posts__displayPostDate.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__list__ul.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__list__ul.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__list__ul.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__list__ul.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__media-text.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__media-text.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__media-text.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__media-text.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__media-text__image-alt-no-align.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__media-text__image-alt-no-align.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__media-text__image-alt-no-align.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__media-text__image-alt-no-align.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__media-text__is-stacked-on-mobile.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__media-text__is-stacked-on-mobile.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__media-text__is-stacked-on-mobile.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__media-text__is-stacked-on-mobile.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__media-text__media-right-custom-width.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__media-text__media-right-custom-width.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__media-text__media-right-custom-width.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__media-text__media-right-custom-width.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__media-text__video.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__media-text__video.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__media-text__video.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__media-text__video.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__missing.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__missing.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__missing.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__missing.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__more.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__more.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__more.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__more.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__more__custom-text-teaser.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__more__custom-text-teaser.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__more__custom-text-teaser.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__more__custom-text-teaser.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__nextpage.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__nextpage.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__nextpage.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__nextpage.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__paragraph__align-right.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__paragraph__align-right.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__paragraph__align-right.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__paragraph__align-right.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__paragraph__deprecated.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__paragraph__deprecated.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__paragraph__deprecated.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__paragraph__deprecated.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__preformatted.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__preformatted.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__preformatted.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__preformatted.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__pullquote.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__pullquote.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__pullquote.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__pullquote.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__pullquote__multi-paragraph.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__pullquote__multi-paragraph.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__pullquote__multi-paragraph.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__pullquote__multi-paragraph.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__quote__style-1.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__quote__style-1.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__quote__style-1.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__quote__style-1.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__quote__style-2.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__quote__style-2.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__quote__style-2.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__quote__style-2.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__rss.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__rss.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__rss.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__rss.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__search.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__search.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__search.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__search.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__search__custom-text.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__search__custom-text.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__search__custom-text.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__search__custom-text.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__separator.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__separator.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__separator.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__separator.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__shortcode.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__shortcode.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__shortcode.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__shortcode.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__spacer.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__spacer.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__spacer.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__spacer.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__subhead.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__subhead.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__subhead.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__subhead.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__table.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__table.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__table.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__table.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__tag-cloud.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__tag-cloud.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__tag-cloud.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__tag-cloud.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__tag-cloud__showTagCounts.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__tag-cloud__showTagCounts.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__tag-cloud__showTagCounts.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__tag-cloud__showTagCounts.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__text-columns.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__text-columns.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__text-columns.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__text-columns.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__text__converts-to-paragraph.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__text__converts-to-paragraph.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__text__converts-to-paragraph.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__text__converts-to-paragraph.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__verse.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__verse.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__verse.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__verse.serialized.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__video.html (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__video.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__video.parsed.json (100%) rename {test/integration/full-content/fixtures => packages/e2e-tests/fixtures/blocks}/core__video.serialized.html (100%) create mode 100644 packages/e2e-tests/fixtures/index.js create mode 100644 packages/e2e-tests/fixtures/utils.js diff --git a/package.json b/package.json index a467efd345915..119dcd7e3ba26 100644 --- a/package.json +++ b/package.json @@ -162,7 +162,7 @@ "dev": "npm run build:packages && concurrently \"wp-scripts start\" \"npm run dev:packages\"", "dev:packages": "node ./bin/packages/watch.js", "docs:build": "node docs/tool", - "fixtures:clean": "rimraf \"test/integration/full-content/fixtures/*.+(json|serialized.html)\"", + "fixtures:clean": "rimraf \"packages/e2e-tests/fixtures/blocks/*.+(json|serialized.html)\"", "fixtures:server-registered": "docker-compose run -w /var/www/html/wp-content/plugins/gutenberg --rm wordpress ./bin/get-server-blocks.php > test/integration/full-content/server-registered.json", "fixtures:generate": "npm run fixtures:server-registered && cross-env GENERATE_MISSING_FIXTURES=y npm run test-unit", "fixtures:regenerate": "npm run fixtures:clean && npm run fixtures:generate", diff --git a/test/integration/full-content/fixtures/README.md b/packages/e2e-tests/fixtures/blocks/README.md similarity index 100% rename from test/integration/full-content/fixtures/README.md rename to packages/e2e-tests/fixtures/blocks/README.md diff --git a/test/integration/full-content/fixtures/core__4-invalid-starting-letter.html b/packages/e2e-tests/fixtures/blocks/core__4-invalid-starting-letter.html similarity index 100% rename from test/integration/full-content/fixtures/core__4-invalid-starting-letter.html rename to packages/e2e-tests/fixtures/blocks/core__4-invalid-starting-letter.html diff --git a/test/integration/full-content/fixtures/core__4-invalid-starting-letter.json b/packages/e2e-tests/fixtures/blocks/core__4-invalid-starting-letter.json similarity index 100% rename from test/integration/full-content/fixtures/core__4-invalid-starting-letter.json rename to packages/e2e-tests/fixtures/blocks/core__4-invalid-starting-letter.json diff --git a/test/integration/full-content/fixtures/core__4-invalid-starting-letter.parsed.json b/packages/e2e-tests/fixtures/blocks/core__4-invalid-starting-letter.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__4-invalid-starting-letter.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__4-invalid-starting-letter.parsed.json diff --git a/test/integration/full-content/fixtures/core__4-invalid-starting-letter.serialized.html b/packages/e2e-tests/fixtures/blocks/core__4-invalid-starting-letter.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__4-invalid-starting-letter.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__4-invalid-starting-letter.serialized.html diff --git a/test/integration/full-content/fixtures/core__archives.html b/packages/e2e-tests/fixtures/blocks/core__archives.html similarity index 100% rename from test/integration/full-content/fixtures/core__archives.html rename to packages/e2e-tests/fixtures/blocks/core__archives.html diff --git a/test/integration/full-content/fixtures/core__archives.json b/packages/e2e-tests/fixtures/blocks/core__archives.json similarity index 100% rename from test/integration/full-content/fixtures/core__archives.json rename to packages/e2e-tests/fixtures/blocks/core__archives.json diff --git a/test/integration/full-content/fixtures/core__archives.parsed.json b/packages/e2e-tests/fixtures/blocks/core__archives.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__archives.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__archives.parsed.json diff --git a/test/integration/full-content/fixtures/core__archives.serialized.html b/packages/e2e-tests/fixtures/blocks/core__archives.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__archives.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__archives.serialized.html diff --git a/test/integration/full-content/fixtures/core__archives__showPostCounts.html b/packages/e2e-tests/fixtures/blocks/core__archives__showPostCounts.html similarity index 100% rename from test/integration/full-content/fixtures/core__archives__showPostCounts.html rename to packages/e2e-tests/fixtures/blocks/core__archives__showPostCounts.html diff --git a/test/integration/full-content/fixtures/core__archives__showPostCounts.json b/packages/e2e-tests/fixtures/blocks/core__archives__showPostCounts.json similarity index 100% rename from test/integration/full-content/fixtures/core__archives__showPostCounts.json rename to packages/e2e-tests/fixtures/blocks/core__archives__showPostCounts.json diff --git a/test/integration/full-content/fixtures/core__archives__showPostCounts.parsed.json b/packages/e2e-tests/fixtures/blocks/core__archives__showPostCounts.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__archives__showPostCounts.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__archives__showPostCounts.parsed.json diff --git a/test/integration/full-content/fixtures/core__archives__showPostCounts.serialized.html b/packages/e2e-tests/fixtures/blocks/core__archives__showPostCounts.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__archives__showPostCounts.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__archives__showPostCounts.serialized.html diff --git a/test/integration/full-content/fixtures/core__audio.html b/packages/e2e-tests/fixtures/blocks/core__audio.html similarity index 100% rename from test/integration/full-content/fixtures/core__audio.html rename to packages/e2e-tests/fixtures/blocks/core__audio.html diff --git a/test/integration/full-content/fixtures/core__audio.json b/packages/e2e-tests/fixtures/blocks/core__audio.json similarity index 100% rename from test/integration/full-content/fixtures/core__audio.json rename to packages/e2e-tests/fixtures/blocks/core__audio.json diff --git a/test/integration/full-content/fixtures/core__audio.parsed.json b/packages/e2e-tests/fixtures/blocks/core__audio.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__audio.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__audio.parsed.json diff --git a/test/integration/full-content/fixtures/core__audio.serialized.html b/packages/e2e-tests/fixtures/blocks/core__audio.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__audio.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__audio.serialized.html diff --git a/test/integration/full-content/fixtures/core__block.html b/packages/e2e-tests/fixtures/blocks/core__block.html similarity index 100% rename from test/integration/full-content/fixtures/core__block.html rename to packages/e2e-tests/fixtures/blocks/core__block.html diff --git a/test/integration/full-content/fixtures/core__block.json b/packages/e2e-tests/fixtures/blocks/core__block.json similarity index 100% rename from test/integration/full-content/fixtures/core__block.json rename to packages/e2e-tests/fixtures/blocks/core__block.json diff --git a/test/integration/full-content/fixtures/core__block.parsed.json b/packages/e2e-tests/fixtures/blocks/core__block.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__block.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__block.parsed.json diff --git a/test/integration/full-content/fixtures/core__block.serialized.html b/packages/e2e-tests/fixtures/blocks/core__block.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__block.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__block.serialized.html diff --git a/test/integration/full-content/fixtures/core__button__center.html b/packages/e2e-tests/fixtures/blocks/core__button__center.html similarity index 100% rename from test/integration/full-content/fixtures/core__button__center.html rename to packages/e2e-tests/fixtures/blocks/core__button__center.html diff --git a/test/integration/full-content/fixtures/core__button__center.json b/packages/e2e-tests/fixtures/blocks/core__button__center.json similarity index 100% rename from test/integration/full-content/fixtures/core__button__center.json rename to packages/e2e-tests/fixtures/blocks/core__button__center.json diff --git a/test/integration/full-content/fixtures/core__button__center.parsed.json b/packages/e2e-tests/fixtures/blocks/core__button__center.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__button__center.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__button__center.parsed.json diff --git a/test/integration/full-content/fixtures/core__button__center.serialized.html b/packages/e2e-tests/fixtures/blocks/core__button__center.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__button__center.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__button__center.serialized.html diff --git a/test/integration/full-content/fixtures/core__calendar.html b/packages/e2e-tests/fixtures/blocks/core__calendar.html similarity index 100% rename from test/integration/full-content/fixtures/core__calendar.html rename to packages/e2e-tests/fixtures/blocks/core__calendar.html diff --git a/test/integration/full-content/fixtures/core__calendar.json b/packages/e2e-tests/fixtures/blocks/core__calendar.json similarity index 100% rename from test/integration/full-content/fixtures/core__calendar.json rename to packages/e2e-tests/fixtures/blocks/core__calendar.json diff --git a/test/integration/full-content/fixtures/core__calendar.parsed.json b/packages/e2e-tests/fixtures/blocks/core__calendar.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__calendar.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__calendar.parsed.json diff --git a/test/integration/full-content/fixtures/core__calendar.serialized.html b/packages/e2e-tests/fixtures/blocks/core__calendar.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__calendar.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__calendar.serialized.html diff --git a/test/integration/full-content/fixtures/core__categories.html b/packages/e2e-tests/fixtures/blocks/core__categories.html similarity index 100% rename from test/integration/full-content/fixtures/core__categories.html rename to packages/e2e-tests/fixtures/blocks/core__categories.html diff --git a/test/integration/full-content/fixtures/core__categories.json b/packages/e2e-tests/fixtures/blocks/core__categories.json similarity index 100% rename from test/integration/full-content/fixtures/core__categories.json rename to packages/e2e-tests/fixtures/blocks/core__categories.json diff --git a/test/integration/full-content/fixtures/core__categories.parsed.json b/packages/e2e-tests/fixtures/blocks/core__categories.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__categories.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__categories.parsed.json diff --git a/test/integration/full-content/fixtures/core__categories.serialized.html b/packages/e2e-tests/fixtures/blocks/core__categories.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__categories.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__categories.serialized.html diff --git a/test/integration/full-content/fixtures/core__code.html b/packages/e2e-tests/fixtures/blocks/core__code.html similarity index 100% rename from test/integration/full-content/fixtures/core__code.html rename to packages/e2e-tests/fixtures/blocks/core__code.html diff --git a/test/integration/full-content/fixtures/core__code.json b/packages/e2e-tests/fixtures/blocks/core__code.json similarity index 100% rename from test/integration/full-content/fixtures/core__code.json rename to packages/e2e-tests/fixtures/blocks/core__code.json diff --git a/test/integration/full-content/fixtures/core__code.parsed.json b/packages/e2e-tests/fixtures/blocks/core__code.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__code.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__code.parsed.json diff --git a/test/integration/full-content/fixtures/core__code.serialized.html b/packages/e2e-tests/fixtures/blocks/core__code.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__code.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__code.serialized.html diff --git a/test/integration/full-content/fixtures/core__column.html b/packages/e2e-tests/fixtures/blocks/core__column.html similarity index 100% rename from test/integration/full-content/fixtures/core__column.html rename to packages/e2e-tests/fixtures/blocks/core__column.html diff --git a/test/integration/full-content/fixtures/core__column.json b/packages/e2e-tests/fixtures/blocks/core__column.json similarity index 100% rename from test/integration/full-content/fixtures/core__column.json rename to packages/e2e-tests/fixtures/blocks/core__column.json diff --git a/test/integration/full-content/fixtures/core__column.parsed.json b/packages/e2e-tests/fixtures/blocks/core__column.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__column.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__column.parsed.json diff --git a/test/integration/full-content/fixtures/core__column.serialized.html b/packages/e2e-tests/fixtures/blocks/core__column.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__column.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__column.serialized.html diff --git a/test/integration/full-content/fixtures/core__columns.html b/packages/e2e-tests/fixtures/blocks/core__columns.html similarity index 100% rename from test/integration/full-content/fixtures/core__columns.html rename to packages/e2e-tests/fixtures/blocks/core__columns.html diff --git a/test/integration/full-content/fixtures/core__columns.json b/packages/e2e-tests/fixtures/blocks/core__columns.json similarity index 100% rename from test/integration/full-content/fixtures/core__columns.json rename to packages/e2e-tests/fixtures/blocks/core__columns.json diff --git a/test/integration/full-content/fixtures/core__columns.parsed.json b/packages/e2e-tests/fixtures/blocks/core__columns.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__columns.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__columns.parsed.json diff --git a/test/integration/full-content/fixtures/core__columns.serialized.html b/packages/e2e-tests/fixtures/blocks/core__columns.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__columns.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__columns.serialized.html diff --git a/test/integration/full-content/fixtures/core__columns__deprecated.html b/packages/e2e-tests/fixtures/blocks/core__columns__deprecated.html similarity index 100% rename from test/integration/full-content/fixtures/core__columns__deprecated.html rename to packages/e2e-tests/fixtures/blocks/core__columns__deprecated.html diff --git a/test/integration/full-content/fixtures/core__columns__deprecated.json b/packages/e2e-tests/fixtures/blocks/core__columns__deprecated.json similarity index 100% rename from test/integration/full-content/fixtures/core__columns__deprecated.json rename to packages/e2e-tests/fixtures/blocks/core__columns__deprecated.json diff --git a/test/integration/full-content/fixtures/core__columns__deprecated.parsed.json b/packages/e2e-tests/fixtures/blocks/core__columns__deprecated.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__columns__deprecated.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__columns__deprecated.parsed.json diff --git a/test/integration/full-content/fixtures/core__columns__deprecated.serialized.html b/packages/e2e-tests/fixtures/blocks/core__columns__deprecated.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__columns__deprecated.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__columns__deprecated.serialized.html diff --git a/test/integration/full-content/fixtures/core__cover.html b/packages/e2e-tests/fixtures/blocks/core__cover.html similarity index 100% rename from test/integration/full-content/fixtures/core__cover.html rename to packages/e2e-tests/fixtures/blocks/core__cover.html diff --git a/test/integration/full-content/fixtures/core__cover.json b/packages/e2e-tests/fixtures/blocks/core__cover.json similarity index 100% rename from test/integration/full-content/fixtures/core__cover.json rename to packages/e2e-tests/fixtures/blocks/core__cover.json diff --git a/test/integration/full-content/fixtures/core__cover.parsed.json b/packages/e2e-tests/fixtures/blocks/core__cover.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__cover.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__cover.parsed.json diff --git a/test/integration/full-content/fixtures/core__cover.serialized.html b/packages/e2e-tests/fixtures/blocks/core__cover.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__cover.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__cover.serialized.html diff --git a/test/integration/full-content/fixtures/core__cover__video-overlay.html b/packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.html similarity index 100% rename from test/integration/full-content/fixtures/core__cover__video-overlay.html rename to packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.html diff --git a/test/integration/full-content/fixtures/core__cover__video-overlay.json b/packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.json similarity index 100% rename from test/integration/full-content/fixtures/core__cover__video-overlay.json rename to packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.json diff --git a/test/integration/full-content/fixtures/core__cover__video-overlay.parsed.json b/packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__cover__video-overlay.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.parsed.json diff --git a/test/integration/full-content/fixtures/core__cover__video-overlay.serialized.html b/packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__cover__video-overlay.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.serialized.html diff --git a/test/integration/full-content/fixtures/core__cover__video.html b/packages/e2e-tests/fixtures/blocks/core__cover__video.html similarity index 100% rename from test/integration/full-content/fixtures/core__cover__video.html rename to packages/e2e-tests/fixtures/blocks/core__cover__video.html diff --git a/test/integration/full-content/fixtures/core__cover__video.json b/packages/e2e-tests/fixtures/blocks/core__cover__video.json similarity index 100% rename from test/integration/full-content/fixtures/core__cover__video.json rename to packages/e2e-tests/fixtures/blocks/core__cover__video.json diff --git a/test/integration/full-content/fixtures/core__cover__video.parsed.json b/packages/e2e-tests/fixtures/blocks/core__cover__video.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__cover__video.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__cover__video.parsed.json diff --git a/test/integration/full-content/fixtures/core__cover__video.serialized.html b/packages/e2e-tests/fixtures/blocks/core__cover__video.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__cover__video.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__cover__video.serialized.html diff --git a/test/integration/full-content/fixtures/core__embed.html b/packages/e2e-tests/fixtures/blocks/core__embed.html similarity index 100% rename from test/integration/full-content/fixtures/core__embed.html rename to packages/e2e-tests/fixtures/blocks/core__embed.html diff --git a/test/integration/full-content/fixtures/core__embed.json b/packages/e2e-tests/fixtures/blocks/core__embed.json similarity index 100% rename from test/integration/full-content/fixtures/core__embed.json rename to packages/e2e-tests/fixtures/blocks/core__embed.json diff --git a/test/integration/full-content/fixtures/core__embed.parsed.json b/packages/e2e-tests/fixtures/blocks/core__embed.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__embed.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__embed.parsed.json diff --git a/test/integration/full-content/fixtures/core__embed.serialized.html b/packages/e2e-tests/fixtures/blocks/core__embed.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__embed.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__embed.serialized.html diff --git a/test/integration/full-content/fixtures/core__file__new-window.html b/packages/e2e-tests/fixtures/blocks/core__file__new-window.html similarity index 100% rename from test/integration/full-content/fixtures/core__file__new-window.html rename to packages/e2e-tests/fixtures/blocks/core__file__new-window.html diff --git a/test/integration/full-content/fixtures/core__file__new-window.json b/packages/e2e-tests/fixtures/blocks/core__file__new-window.json similarity index 100% rename from test/integration/full-content/fixtures/core__file__new-window.json rename to packages/e2e-tests/fixtures/blocks/core__file__new-window.json diff --git a/test/integration/full-content/fixtures/core__file__new-window.parsed.json b/packages/e2e-tests/fixtures/blocks/core__file__new-window.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__file__new-window.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__file__new-window.parsed.json diff --git a/test/integration/full-content/fixtures/core__file__new-window.serialized.html b/packages/e2e-tests/fixtures/blocks/core__file__new-window.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__file__new-window.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__file__new-window.serialized.html diff --git a/test/integration/full-content/fixtures/core__file__no-download-button.html b/packages/e2e-tests/fixtures/blocks/core__file__no-download-button.html similarity index 100% rename from test/integration/full-content/fixtures/core__file__no-download-button.html rename to packages/e2e-tests/fixtures/blocks/core__file__no-download-button.html diff --git a/test/integration/full-content/fixtures/core__file__no-download-button.json b/packages/e2e-tests/fixtures/blocks/core__file__no-download-button.json similarity index 100% rename from test/integration/full-content/fixtures/core__file__no-download-button.json rename to packages/e2e-tests/fixtures/blocks/core__file__no-download-button.json diff --git a/test/integration/full-content/fixtures/core__file__no-download-button.parsed.json b/packages/e2e-tests/fixtures/blocks/core__file__no-download-button.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__file__no-download-button.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__file__no-download-button.parsed.json diff --git a/test/integration/full-content/fixtures/core__file__no-download-button.serialized.html b/packages/e2e-tests/fixtures/blocks/core__file__no-download-button.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__file__no-download-button.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__file__no-download-button.serialized.html diff --git a/test/integration/full-content/fixtures/core__file__no-text-link.html b/packages/e2e-tests/fixtures/blocks/core__file__no-text-link.html similarity index 100% rename from test/integration/full-content/fixtures/core__file__no-text-link.html rename to packages/e2e-tests/fixtures/blocks/core__file__no-text-link.html diff --git a/test/integration/full-content/fixtures/core__file__no-text-link.json b/packages/e2e-tests/fixtures/blocks/core__file__no-text-link.json similarity index 100% rename from test/integration/full-content/fixtures/core__file__no-text-link.json rename to packages/e2e-tests/fixtures/blocks/core__file__no-text-link.json diff --git a/test/integration/full-content/fixtures/core__file__no-text-link.parsed.json b/packages/e2e-tests/fixtures/blocks/core__file__no-text-link.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__file__no-text-link.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__file__no-text-link.parsed.json diff --git a/test/integration/full-content/fixtures/core__file__no-text-link.serialized.html b/packages/e2e-tests/fixtures/blocks/core__file__no-text-link.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__file__no-text-link.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__file__no-text-link.serialized.html diff --git a/test/integration/full-content/fixtures/core__freeform.html b/packages/e2e-tests/fixtures/blocks/core__freeform.html similarity index 100% rename from test/integration/full-content/fixtures/core__freeform.html rename to packages/e2e-tests/fixtures/blocks/core__freeform.html diff --git a/test/integration/full-content/fixtures/core__freeform.json b/packages/e2e-tests/fixtures/blocks/core__freeform.json similarity index 100% rename from test/integration/full-content/fixtures/core__freeform.json rename to packages/e2e-tests/fixtures/blocks/core__freeform.json diff --git a/test/integration/full-content/fixtures/core__freeform.parsed.json b/packages/e2e-tests/fixtures/blocks/core__freeform.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__freeform.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__freeform.parsed.json diff --git a/test/integration/full-content/fixtures/core__freeform.serialized.html b/packages/e2e-tests/fixtures/blocks/core__freeform.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__freeform.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__freeform.serialized.html diff --git a/test/integration/full-content/fixtures/core__freeform__undelimited.html b/packages/e2e-tests/fixtures/blocks/core__freeform__undelimited.html similarity index 100% rename from test/integration/full-content/fixtures/core__freeform__undelimited.html rename to packages/e2e-tests/fixtures/blocks/core__freeform__undelimited.html diff --git a/test/integration/full-content/fixtures/core__freeform__undelimited.json b/packages/e2e-tests/fixtures/blocks/core__freeform__undelimited.json similarity index 100% rename from test/integration/full-content/fixtures/core__freeform__undelimited.json rename to packages/e2e-tests/fixtures/blocks/core__freeform__undelimited.json diff --git a/test/integration/full-content/fixtures/core__freeform__undelimited.parsed.json b/packages/e2e-tests/fixtures/blocks/core__freeform__undelimited.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__freeform__undelimited.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__freeform__undelimited.parsed.json diff --git a/test/integration/full-content/fixtures/core__freeform__undelimited.serialized.html b/packages/e2e-tests/fixtures/blocks/core__freeform__undelimited.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__freeform__undelimited.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__freeform__undelimited.serialized.html diff --git a/test/integration/full-content/fixtures/core__gallery.html b/packages/e2e-tests/fixtures/blocks/core__gallery.html similarity index 100% rename from test/integration/full-content/fixtures/core__gallery.html rename to packages/e2e-tests/fixtures/blocks/core__gallery.html diff --git a/test/integration/full-content/fixtures/core__gallery.json b/packages/e2e-tests/fixtures/blocks/core__gallery.json similarity index 100% rename from test/integration/full-content/fixtures/core__gallery.json rename to packages/e2e-tests/fixtures/blocks/core__gallery.json diff --git a/test/integration/full-content/fixtures/core__gallery.parsed.json b/packages/e2e-tests/fixtures/blocks/core__gallery.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__gallery.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__gallery.parsed.json diff --git a/test/integration/full-content/fixtures/core__gallery.serialized.html b/packages/e2e-tests/fixtures/blocks/core__gallery.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__gallery.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__gallery.serialized.html diff --git a/test/integration/full-content/fixtures/core__gallery__columns.html b/packages/e2e-tests/fixtures/blocks/core__gallery__columns.html similarity index 100% rename from test/integration/full-content/fixtures/core__gallery__columns.html rename to packages/e2e-tests/fixtures/blocks/core__gallery__columns.html diff --git a/test/integration/full-content/fixtures/core__gallery__columns.json b/packages/e2e-tests/fixtures/blocks/core__gallery__columns.json similarity index 100% rename from test/integration/full-content/fixtures/core__gallery__columns.json rename to packages/e2e-tests/fixtures/blocks/core__gallery__columns.json diff --git a/test/integration/full-content/fixtures/core__gallery__columns.parsed.json b/packages/e2e-tests/fixtures/blocks/core__gallery__columns.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__gallery__columns.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__gallery__columns.parsed.json diff --git a/test/integration/full-content/fixtures/core__gallery__columns.serialized.html b/packages/e2e-tests/fixtures/blocks/core__gallery__columns.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__gallery__columns.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__gallery__columns.serialized.html diff --git a/test/integration/full-content/fixtures/core__heading__h2-em.html b/packages/e2e-tests/fixtures/blocks/core__heading__h2-em.html similarity index 100% rename from test/integration/full-content/fixtures/core__heading__h2-em.html rename to packages/e2e-tests/fixtures/blocks/core__heading__h2-em.html diff --git a/test/integration/full-content/fixtures/core__heading__h2-em.json b/packages/e2e-tests/fixtures/blocks/core__heading__h2-em.json similarity index 100% rename from test/integration/full-content/fixtures/core__heading__h2-em.json rename to packages/e2e-tests/fixtures/blocks/core__heading__h2-em.json diff --git a/test/integration/full-content/fixtures/core__heading__h2-em.parsed.json b/packages/e2e-tests/fixtures/blocks/core__heading__h2-em.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__heading__h2-em.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__heading__h2-em.parsed.json diff --git a/test/integration/full-content/fixtures/core__heading__h2-em.serialized.html b/packages/e2e-tests/fixtures/blocks/core__heading__h2-em.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__heading__h2-em.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__heading__h2-em.serialized.html diff --git a/test/integration/full-content/fixtures/core__heading__h2.html b/packages/e2e-tests/fixtures/blocks/core__heading__h2.html similarity index 100% rename from test/integration/full-content/fixtures/core__heading__h2.html rename to packages/e2e-tests/fixtures/blocks/core__heading__h2.html diff --git a/test/integration/full-content/fixtures/core__heading__h2.json b/packages/e2e-tests/fixtures/blocks/core__heading__h2.json similarity index 100% rename from test/integration/full-content/fixtures/core__heading__h2.json rename to packages/e2e-tests/fixtures/blocks/core__heading__h2.json diff --git a/test/integration/full-content/fixtures/core__heading__h2.parsed.json b/packages/e2e-tests/fixtures/blocks/core__heading__h2.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__heading__h2.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__heading__h2.parsed.json diff --git a/test/integration/full-content/fixtures/core__heading__h2.serialized.html b/packages/e2e-tests/fixtures/blocks/core__heading__h2.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__heading__h2.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__heading__h2.serialized.html diff --git a/test/integration/full-content/fixtures/core__html.html b/packages/e2e-tests/fixtures/blocks/core__html.html similarity index 100% rename from test/integration/full-content/fixtures/core__html.html rename to packages/e2e-tests/fixtures/blocks/core__html.html diff --git a/test/integration/full-content/fixtures/core__html.json b/packages/e2e-tests/fixtures/blocks/core__html.json similarity index 100% rename from test/integration/full-content/fixtures/core__html.json rename to packages/e2e-tests/fixtures/blocks/core__html.json diff --git a/test/integration/full-content/fixtures/core__html.parsed.json b/packages/e2e-tests/fixtures/blocks/core__html.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__html.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__html.parsed.json diff --git a/test/integration/full-content/fixtures/core__html.serialized.html b/packages/e2e-tests/fixtures/blocks/core__html.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__html.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__html.serialized.html diff --git a/test/integration/full-content/fixtures/core__image.html b/packages/e2e-tests/fixtures/blocks/core__image.html similarity index 100% rename from test/integration/full-content/fixtures/core__image.html rename to packages/e2e-tests/fixtures/blocks/core__image.html diff --git a/test/integration/full-content/fixtures/core__image.json b/packages/e2e-tests/fixtures/blocks/core__image.json similarity index 100% rename from test/integration/full-content/fixtures/core__image.json rename to packages/e2e-tests/fixtures/blocks/core__image.json diff --git a/test/integration/full-content/fixtures/core__image.parsed.json b/packages/e2e-tests/fixtures/blocks/core__image.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__image.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__image.parsed.json diff --git a/test/integration/full-content/fixtures/core__image.serialized.html b/packages/e2e-tests/fixtures/blocks/core__image.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__image.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__image.serialized.html diff --git a/test/integration/full-content/fixtures/core__image__attachment-link.html b/packages/e2e-tests/fixtures/blocks/core__image__attachment-link.html similarity index 100% rename from test/integration/full-content/fixtures/core__image__attachment-link.html rename to packages/e2e-tests/fixtures/blocks/core__image__attachment-link.html diff --git a/test/integration/full-content/fixtures/core__image__attachment-link.json b/packages/e2e-tests/fixtures/blocks/core__image__attachment-link.json similarity index 100% rename from test/integration/full-content/fixtures/core__image__attachment-link.json rename to packages/e2e-tests/fixtures/blocks/core__image__attachment-link.json diff --git a/test/integration/full-content/fixtures/core__image__attachment-link.parsed.json b/packages/e2e-tests/fixtures/blocks/core__image__attachment-link.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__image__attachment-link.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__image__attachment-link.parsed.json diff --git a/test/integration/full-content/fixtures/core__image__attachment-link.serialized.html b/packages/e2e-tests/fixtures/blocks/core__image__attachment-link.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__image__attachment-link.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__image__attachment-link.serialized.html diff --git a/test/integration/full-content/fixtures/core__image__center-caption.html b/packages/e2e-tests/fixtures/blocks/core__image__center-caption.html similarity index 100% rename from test/integration/full-content/fixtures/core__image__center-caption.html rename to packages/e2e-tests/fixtures/blocks/core__image__center-caption.html diff --git a/test/integration/full-content/fixtures/core__image__center-caption.json b/packages/e2e-tests/fixtures/blocks/core__image__center-caption.json similarity index 100% rename from test/integration/full-content/fixtures/core__image__center-caption.json rename to packages/e2e-tests/fixtures/blocks/core__image__center-caption.json diff --git a/test/integration/full-content/fixtures/core__image__center-caption.parsed.json b/packages/e2e-tests/fixtures/blocks/core__image__center-caption.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__image__center-caption.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__image__center-caption.parsed.json diff --git a/test/integration/full-content/fixtures/core__image__center-caption.serialized.html b/packages/e2e-tests/fixtures/blocks/core__image__center-caption.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__image__center-caption.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__image__center-caption.serialized.html diff --git a/test/integration/full-content/fixtures/core__image__custom-link-class.html b/packages/e2e-tests/fixtures/blocks/core__image__custom-link-class.html similarity index 100% rename from test/integration/full-content/fixtures/core__image__custom-link-class.html rename to packages/e2e-tests/fixtures/blocks/core__image__custom-link-class.html diff --git a/test/integration/full-content/fixtures/core__image__custom-link-class.json b/packages/e2e-tests/fixtures/blocks/core__image__custom-link-class.json similarity index 100% rename from test/integration/full-content/fixtures/core__image__custom-link-class.json rename to packages/e2e-tests/fixtures/blocks/core__image__custom-link-class.json diff --git a/test/integration/full-content/fixtures/core__image__custom-link-class.parsed.json b/packages/e2e-tests/fixtures/blocks/core__image__custom-link-class.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__image__custom-link-class.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__image__custom-link-class.parsed.json diff --git a/test/integration/full-content/fixtures/core__image__custom-link-class.serialized.html b/packages/e2e-tests/fixtures/blocks/core__image__custom-link-class.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__image__custom-link-class.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__image__custom-link-class.serialized.html diff --git a/test/integration/full-content/fixtures/core__image__custom-link-rel.html b/packages/e2e-tests/fixtures/blocks/core__image__custom-link-rel.html similarity index 100% rename from test/integration/full-content/fixtures/core__image__custom-link-rel.html rename to packages/e2e-tests/fixtures/blocks/core__image__custom-link-rel.html diff --git a/test/integration/full-content/fixtures/core__image__custom-link-rel.json b/packages/e2e-tests/fixtures/blocks/core__image__custom-link-rel.json similarity index 100% rename from test/integration/full-content/fixtures/core__image__custom-link-rel.json rename to packages/e2e-tests/fixtures/blocks/core__image__custom-link-rel.json diff --git a/test/integration/full-content/fixtures/core__image__custom-link-rel.parsed.json b/packages/e2e-tests/fixtures/blocks/core__image__custom-link-rel.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__image__custom-link-rel.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__image__custom-link-rel.parsed.json diff --git a/test/integration/full-content/fixtures/core__image__custom-link-rel.serialized.html b/packages/e2e-tests/fixtures/blocks/core__image__custom-link-rel.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__image__custom-link-rel.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__image__custom-link-rel.serialized.html diff --git a/test/integration/full-content/fixtures/core__image__custom-link.html b/packages/e2e-tests/fixtures/blocks/core__image__custom-link.html similarity index 100% rename from test/integration/full-content/fixtures/core__image__custom-link.html rename to packages/e2e-tests/fixtures/blocks/core__image__custom-link.html diff --git a/test/integration/full-content/fixtures/core__image__custom-link.json b/packages/e2e-tests/fixtures/blocks/core__image__custom-link.json similarity index 100% rename from test/integration/full-content/fixtures/core__image__custom-link.json rename to packages/e2e-tests/fixtures/blocks/core__image__custom-link.json diff --git a/test/integration/full-content/fixtures/core__image__custom-link.parsed.json b/packages/e2e-tests/fixtures/blocks/core__image__custom-link.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__image__custom-link.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__image__custom-link.parsed.json diff --git a/test/integration/full-content/fixtures/core__image__custom-link.serialized.html b/packages/e2e-tests/fixtures/blocks/core__image__custom-link.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__image__custom-link.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__image__custom-link.serialized.html diff --git a/test/integration/full-content/fixtures/core__image__media-link.html b/packages/e2e-tests/fixtures/blocks/core__image__media-link.html similarity index 100% rename from test/integration/full-content/fixtures/core__image__media-link.html rename to packages/e2e-tests/fixtures/blocks/core__image__media-link.html diff --git a/test/integration/full-content/fixtures/core__image__media-link.json b/packages/e2e-tests/fixtures/blocks/core__image__media-link.json similarity index 100% rename from test/integration/full-content/fixtures/core__image__media-link.json rename to packages/e2e-tests/fixtures/blocks/core__image__media-link.json diff --git a/test/integration/full-content/fixtures/core__image__media-link.parsed.json b/packages/e2e-tests/fixtures/blocks/core__image__media-link.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__image__media-link.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__image__media-link.parsed.json diff --git a/test/integration/full-content/fixtures/core__image__media-link.serialized.html b/packages/e2e-tests/fixtures/blocks/core__image__media-link.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__image__media-link.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__image__media-link.serialized.html diff --git a/test/integration/full-content/fixtures/core__invalid-Capitals.html b/packages/e2e-tests/fixtures/blocks/core__invalid-Capitals.html similarity index 100% rename from test/integration/full-content/fixtures/core__invalid-Capitals.html rename to packages/e2e-tests/fixtures/blocks/core__invalid-Capitals.html diff --git a/test/integration/full-content/fixtures/core__invalid-Capitals.json b/packages/e2e-tests/fixtures/blocks/core__invalid-Capitals.json similarity index 100% rename from test/integration/full-content/fixtures/core__invalid-Capitals.json rename to packages/e2e-tests/fixtures/blocks/core__invalid-Capitals.json diff --git a/test/integration/full-content/fixtures/core__invalid-Capitals.parsed.json b/packages/e2e-tests/fixtures/blocks/core__invalid-Capitals.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__invalid-Capitals.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__invalid-Capitals.parsed.json diff --git a/test/integration/full-content/fixtures/core__invalid-Capitals.serialized.html b/packages/e2e-tests/fixtures/blocks/core__invalid-Capitals.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__invalid-Capitals.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__invalid-Capitals.serialized.html diff --git a/test/integration/full-content/fixtures/core__invalid-special.html b/packages/e2e-tests/fixtures/blocks/core__invalid-special.html similarity index 100% rename from test/integration/full-content/fixtures/core__invalid-special.html rename to packages/e2e-tests/fixtures/blocks/core__invalid-special.html diff --git a/test/integration/full-content/fixtures/core__invalid-special.json b/packages/e2e-tests/fixtures/blocks/core__invalid-special.json similarity index 100% rename from test/integration/full-content/fixtures/core__invalid-special.json rename to packages/e2e-tests/fixtures/blocks/core__invalid-special.json diff --git a/test/integration/full-content/fixtures/core__invalid-special.parsed.json b/packages/e2e-tests/fixtures/blocks/core__invalid-special.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__invalid-special.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__invalid-special.parsed.json diff --git a/test/integration/full-content/fixtures/core__invalid-special.serialized.html b/packages/e2e-tests/fixtures/blocks/core__invalid-special.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__invalid-special.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__invalid-special.serialized.html diff --git a/test/integration/full-content/fixtures/core__latest-comments.html b/packages/e2e-tests/fixtures/blocks/core__latest-comments.html similarity index 100% rename from test/integration/full-content/fixtures/core__latest-comments.html rename to packages/e2e-tests/fixtures/blocks/core__latest-comments.html diff --git a/test/integration/full-content/fixtures/core__latest-comments.json b/packages/e2e-tests/fixtures/blocks/core__latest-comments.json similarity index 100% rename from test/integration/full-content/fixtures/core__latest-comments.json rename to packages/e2e-tests/fixtures/blocks/core__latest-comments.json diff --git a/test/integration/full-content/fixtures/core__latest-comments.parsed.json b/packages/e2e-tests/fixtures/blocks/core__latest-comments.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__latest-comments.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__latest-comments.parsed.json diff --git a/test/integration/full-content/fixtures/core__latest-comments.serialized.html b/packages/e2e-tests/fixtures/blocks/core__latest-comments.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__latest-comments.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__latest-comments.serialized.html diff --git a/test/integration/full-content/fixtures/core__latest-posts.html b/packages/e2e-tests/fixtures/blocks/core__latest-posts.html similarity index 100% rename from test/integration/full-content/fixtures/core__latest-posts.html rename to packages/e2e-tests/fixtures/blocks/core__latest-posts.html diff --git a/test/integration/full-content/fixtures/core__latest-posts.json b/packages/e2e-tests/fixtures/blocks/core__latest-posts.json similarity index 100% rename from test/integration/full-content/fixtures/core__latest-posts.json rename to packages/e2e-tests/fixtures/blocks/core__latest-posts.json diff --git a/test/integration/full-content/fixtures/core__latest-posts.parsed.json b/packages/e2e-tests/fixtures/blocks/core__latest-posts.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__latest-posts.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__latest-posts.parsed.json diff --git a/test/integration/full-content/fixtures/core__latest-posts.serialized.html b/packages/e2e-tests/fixtures/blocks/core__latest-posts.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__latest-posts.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__latest-posts.serialized.html diff --git a/test/integration/full-content/fixtures/core__latest-posts__displayPostDate.html b/packages/e2e-tests/fixtures/blocks/core__latest-posts__displayPostDate.html similarity index 100% rename from test/integration/full-content/fixtures/core__latest-posts__displayPostDate.html rename to packages/e2e-tests/fixtures/blocks/core__latest-posts__displayPostDate.html diff --git a/test/integration/full-content/fixtures/core__latest-posts__displayPostDate.json b/packages/e2e-tests/fixtures/blocks/core__latest-posts__displayPostDate.json similarity index 100% rename from test/integration/full-content/fixtures/core__latest-posts__displayPostDate.json rename to packages/e2e-tests/fixtures/blocks/core__latest-posts__displayPostDate.json diff --git a/test/integration/full-content/fixtures/core__latest-posts__displayPostDate.parsed.json b/packages/e2e-tests/fixtures/blocks/core__latest-posts__displayPostDate.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__latest-posts__displayPostDate.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__latest-posts__displayPostDate.parsed.json diff --git a/test/integration/full-content/fixtures/core__latest-posts__displayPostDate.serialized.html b/packages/e2e-tests/fixtures/blocks/core__latest-posts__displayPostDate.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__latest-posts__displayPostDate.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__latest-posts__displayPostDate.serialized.html diff --git a/test/integration/full-content/fixtures/core__list__ul.html b/packages/e2e-tests/fixtures/blocks/core__list__ul.html similarity index 100% rename from test/integration/full-content/fixtures/core__list__ul.html rename to packages/e2e-tests/fixtures/blocks/core__list__ul.html diff --git a/test/integration/full-content/fixtures/core__list__ul.json b/packages/e2e-tests/fixtures/blocks/core__list__ul.json similarity index 100% rename from test/integration/full-content/fixtures/core__list__ul.json rename to packages/e2e-tests/fixtures/blocks/core__list__ul.json diff --git a/test/integration/full-content/fixtures/core__list__ul.parsed.json b/packages/e2e-tests/fixtures/blocks/core__list__ul.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__list__ul.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__list__ul.parsed.json diff --git a/test/integration/full-content/fixtures/core__list__ul.serialized.html b/packages/e2e-tests/fixtures/blocks/core__list__ul.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__list__ul.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__list__ul.serialized.html diff --git a/test/integration/full-content/fixtures/core__media-text.html b/packages/e2e-tests/fixtures/blocks/core__media-text.html similarity index 100% rename from test/integration/full-content/fixtures/core__media-text.html rename to packages/e2e-tests/fixtures/blocks/core__media-text.html diff --git a/test/integration/full-content/fixtures/core__media-text.json b/packages/e2e-tests/fixtures/blocks/core__media-text.json similarity index 100% rename from test/integration/full-content/fixtures/core__media-text.json rename to packages/e2e-tests/fixtures/blocks/core__media-text.json diff --git a/test/integration/full-content/fixtures/core__media-text.parsed.json b/packages/e2e-tests/fixtures/blocks/core__media-text.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__media-text.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__media-text.parsed.json diff --git a/test/integration/full-content/fixtures/core__media-text.serialized.html b/packages/e2e-tests/fixtures/blocks/core__media-text.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__media-text.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__media-text.serialized.html diff --git a/test/integration/full-content/fixtures/core__media-text__image-alt-no-align.html b/packages/e2e-tests/fixtures/blocks/core__media-text__image-alt-no-align.html similarity index 100% rename from test/integration/full-content/fixtures/core__media-text__image-alt-no-align.html rename to packages/e2e-tests/fixtures/blocks/core__media-text__image-alt-no-align.html diff --git a/test/integration/full-content/fixtures/core__media-text__image-alt-no-align.json b/packages/e2e-tests/fixtures/blocks/core__media-text__image-alt-no-align.json similarity index 100% rename from test/integration/full-content/fixtures/core__media-text__image-alt-no-align.json rename to packages/e2e-tests/fixtures/blocks/core__media-text__image-alt-no-align.json diff --git a/test/integration/full-content/fixtures/core__media-text__image-alt-no-align.parsed.json b/packages/e2e-tests/fixtures/blocks/core__media-text__image-alt-no-align.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__media-text__image-alt-no-align.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__media-text__image-alt-no-align.parsed.json diff --git a/test/integration/full-content/fixtures/core__media-text__image-alt-no-align.serialized.html b/packages/e2e-tests/fixtures/blocks/core__media-text__image-alt-no-align.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__media-text__image-alt-no-align.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__media-text__image-alt-no-align.serialized.html diff --git a/test/integration/full-content/fixtures/core__media-text__is-stacked-on-mobile.html b/packages/e2e-tests/fixtures/blocks/core__media-text__is-stacked-on-mobile.html similarity index 100% rename from test/integration/full-content/fixtures/core__media-text__is-stacked-on-mobile.html rename to packages/e2e-tests/fixtures/blocks/core__media-text__is-stacked-on-mobile.html diff --git a/test/integration/full-content/fixtures/core__media-text__is-stacked-on-mobile.json b/packages/e2e-tests/fixtures/blocks/core__media-text__is-stacked-on-mobile.json similarity index 100% rename from test/integration/full-content/fixtures/core__media-text__is-stacked-on-mobile.json rename to packages/e2e-tests/fixtures/blocks/core__media-text__is-stacked-on-mobile.json diff --git a/test/integration/full-content/fixtures/core__media-text__is-stacked-on-mobile.parsed.json b/packages/e2e-tests/fixtures/blocks/core__media-text__is-stacked-on-mobile.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__media-text__is-stacked-on-mobile.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__media-text__is-stacked-on-mobile.parsed.json diff --git a/test/integration/full-content/fixtures/core__media-text__is-stacked-on-mobile.serialized.html b/packages/e2e-tests/fixtures/blocks/core__media-text__is-stacked-on-mobile.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__media-text__is-stacked-on-mobile.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__media-text__is-stacked-on-mobile.serialized.html diff --git a/test/integration/full-content/fixtures/core__media-text__media-right-custom-width.html b/packages/e2e-tests/fixtures/blocks/core__media-text__media-right-custom-width.html similarity index 100% rename from test/integration/full-content/fixtures/core__media-text__media-right-custom-width.html rename to packages/e2e-tests/fixtures/blocks/core__media-text__media-right-custom-width.html diff --git a/test/integration/full-content/fixtures/core__media-text__media-right-custom-width.json b/packages/e2e-tests/fixtures/blocks/core__media-text__media-right-custom-width.json similarity index 100% rename from test/integration/full-content/fixtures/core__media-text__media-right-custom-width.json rename to packages/e2e-tests/fixtures/blocks/core__media-text__media-right-custom-width.json diff --git a/test/integration/full-content/fixtures/core__media-text__media-right-custom-width.parsed.json b/packages/e2e-tests/fixtures/blocks/core__media-text__media-right-custom-width.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__media-text__media-right-custom-width.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__media-text__media-right-custom-width.parsed.json diff --git a/test/integration/full-content/fixtures/core__media-text__media-right-custom-width.serialized.html b/packages/e2e-tests/fixtures/blocks/core__media-text__media-right-custom-width.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__media-text__media-right-custom-width.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__media-text__media-right-custom-width.serialized.html diff --git a/test/integration/full-content/fixtures/core__media-text__video.html b/packages/e2e-tests/fixtures/blocks/core__media-text__video.html similarity index 100% rename from test/integration/full-content/fixtures/core__media-text__video.html rename to packages/e2e-tests/fixtures/blocks/core__media-text__video.html diff --git a/test/integration/full-content/fixtures/core__media-text__video.json b/packages/e2e-tests/fixtures/blocks/core__media-text__video.json similarity index 100% rename from test/integration/full-content/fixtures/core__media-text__video.json rename to packages/e2e-tests/fixtures/blocks/core__media-text__video.json diff --git a/test/integration/full-content/fixtures/core__media-text__video.parsed.json b/packages/e2e-tests/fixtures/blocks/core__media-text__video.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__media-text__video.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__media-text__video.parsed.json diff --git a/test/integration/full-content/fixtures/core__media-text__video.serialized.html b/packages/e2e-tests/fixtures/blocks/core__media-text__video.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__media-text__video.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__media-text__video.serialized.html diff --git a/test/integration/full-content/fixtures/core__missing.html b/packages/e2e-tests/fixtures/blocks/core__missing.html similarity index 100% rename from test/integration/full-content/fixtures/core__missing.html rename to packages/e2e-tests/fixtures/blocks/core__missing.html diff --git a/test/integration/full-content/fixtures/core__missing.json b/packages/e2e-tests/fixtures/blocks/core__missing.json similarity index 100% rename from test/integration/full-content/fixtures/core__missing.json rename to packages/e2e-tests/fixtures/blocks/core__missing.json diff --git a/test/integration/full-content/fixtures/core__missing.parsed.json b/packages/e2e-tests/fixtures/blocks/core__missing.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__missing.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__missing.parsed.json diff --git a/test/integration/full-content/fixtures/core__missing.serialized.html b/packages/e2e-tests/fixtures/blocks/core__missing.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__missing.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__missing.serialized.html diff --git a/test/integration/full-content/fixtures/core__more.html b/packages/e2e-tests/fixtures/blocks/core__more.html similarity index 100% rename from test/integration/full-content/fixtures/core__more.html rename to packages/e2e-tests/fixtures/blocks/core__more.html diff --git a/test/integration/full-content/fixtures/core__more.json b/packages/e2e-tests/fixtures/blocks/core__more.json similarity index 100% rename from test/integration/full-content/fixtures/core__more.json rename to packages/e2e-tests/fixtures/blocks/core__more.json diff --git a/test/integration/full-content/fixtures/core__more.parsed.json b/packages/e2e-tests/fixtures/blocks/core__more.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__more.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__more.parsed.json diff --git a/test/integration/full-content/fixtures/core__more.serialized.html b/packages/e2e-tests/fixtures/blocks/core__more.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__more.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__more.serialized.html diff --git a/test/integration/full-content/fixtures/core__more__custom-text-teaser.html b/packages/e2e-tests/fixtures/blocks/core__more__custom-text-teaser.html similarity index 100% rename from test/integration/full-content/fixtures/core__more__custom-text-teaser.html rename to packages/e2e-tests/fixtures/blocks/core__more__custom-text-teaser.html diff --git a/test/integration/full-content/fixtures/core__more__custom-text-teaser.json b/packages/e2e-tests/fixtures/blocks/core__more__custom-text-teaser.json similarity index 100% rename from test/integration/full-content/fixtures/core__more__custom-text-teaser.json rename to packages/e2e-tests/fixtures/blocks/core__more__custom-text-teaser.json diff --git a/test/integration/full-content/fixtures/core__more__custom-text-teaser.parsed.json b/packages/e2e-tests/fixtures/blocks/core__more__custom-text-teaser.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__more__custom-text-teaser.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__more__custom-text-teaser.parsed.json diff --git a/test/integration/full-content/fixtures/core__more__custom-text-teaser.serialized.html b/packages/e2e-tests/fixtures/blocks/core__more__custom-text-teaser.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__more__custom-text-teaser.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__more__custom-text-teaser.serialized.html diff --git a/test/integration/full-content/fixtures/core__nextpage.html b/packages/e2e-tests/fixtures/blocks/core__nextpage.html similarity index 100% rename from test/integration/full-content/fixtures/core__nextpage.html rename to packages/e2e-tests/fixtures/blocks/core__nextpage.html diff --git a/test/integration/full-content/fixtures/core__nextpage.json b/packages/e2e-tests/fixtures/blocks/core__nextpage.json similarity index 100% rename from test/integration/full-content/fixtures/core__nextpage.json rename to packages/e2e-tests/fixtures/blocks/core__nextpage.json diff --git a/test/integration/full-content/fixtures/core__nextpage.parsed.json b/packages/e2e-tests/fixtures/blocks/core__nextpage.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__nextpage.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__nextpage.parsed.json diff --git a/test/integration/full-content/fixtures/core__nextpage.serialized.html b/packages/e2e-tests/fixtures/blocks/core__nextpage.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__nextpage.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__nextpage.serialized.html diff --git a/test/integration/full-content/fixtures/core__paragraph__align-right.html b/packages/e2e-tests/fixtures/blocks/core__paragraph__align-right.html similarity index 100% rename from test/integration/full-content/fixtures/core__paragraph__align-right.html rename to packages/e2e-tests/fixtures/blocks/core__paragraph__align-right.html diff --git a/test/integration/full-content/fixtures/core__paragraph__align-right.json b/packages/e2e-tests/fixtures/blocks/core__paragraph__align-right.json similarity index 100% rename from test/integration/full-content/fixtures/core__paragraph__align-right.json rename to packages/e2e-tests/fixtures/blocks/core__paragraph__align-right.json diff --git a/test/integration/full-content/fixtures/core__paragraph__align-right.parsed.json b/packages/e2e-tests/fixtures/blocks/core__paragraph__align-right.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__paragraph__align-right.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__paragraph__align-right.parsed.json diff --git a/test/integration/full-content/fixtures/core__paragraph__align-right.serialized.html b/packages/e2e-tests/fixtures/blocks/core__paragraph__align-right.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__paragraph__align-right.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__paragraph__align-right.serialized.html diff --git a/test/integration/full-content/fixtures/core__paragraph__deprecated.html b/packages/e2e-tests/fixtures/blocks/core__paragraph__deprecated.html similarity index 100% rename from test/integration/full-content/fixtures/core__paragraph__deprecated.html rename to packages/e2e-tests/fixtures/blocks/core__paragraph__deprecated.html diff --git a/test/integration/full-content/fixtures/core__paragraph__deprecated.json b/packages/e2e-tests/fixtures/blocks/core__paragraph__deprecated.json similarity index 100% rename from test/integration/full-content/fixtures/core__paragraph__deprecated.json rename to packages/e2e-tests/fixtures/blocks/core__paragraph__deprecated.json diff --git a/test/integration/full-content/fixtures/core__paragraph__deprecated.parsed.json b/packages/e2e-tests/fixtures/blocks/core__paragraph__deprecated.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__paragraph__deprecated.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__paragraph__deprecated.parsed.json diff --git a/test/integration/full-content/fixtures/core__paragraph__deprecated.serialized.html b/packages/e2e-tests/fixtures/blocks/core__paragraph__deprecated.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__paragraph__deprecated.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__paragraph__deprecated.serialized.html diff --git a/test/integration/full-content/fixtures/core__preformatted.html b/packages/e2e-tests/fixtures/blocks/core__preformatted.html similarity index 100% rename from test/integration/full-content/fixtures/core__preformatted.html rename to packages/e2e-tests/fixtures/blocks/core__preformatted.html diff --git a/test/integration/full-content/fixtures/core__preformatted.json b/packages/e2e-tests/fixtures/blocks/core__preformatted.json similarity index 100% rename from test/integration/full-content/fixtures/core__preformatted.json rename to packages/e2e-tests/fixtures/blocks/core__preformatted.json diff --git a/test/integration/full-content/fixtures/core__preformatted.parsed.json b/packages/e2e-tests/fixtures/blocks/core__preformatted.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__preformatted.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__preformatted.parsed.json diff --git a/test/integration/full-content/fixtures/core__preformatted.serialized.html b/packages/e2e-tests/fixtures/blocks/core__preformatted.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__preformatted.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__preformatted.serialized.html diff --git a/test/integration/full-content/fixtures/core__pullquote.html b/packages/e2e-tests/fixtures/blocks/core__pullquote.html similarity index 100% rename from test/integration/full-content/fixtures/core__pullquote.html rename to packages/e2e-tests/fixtures/blocks/core__pullquote.html diff --git a/test/integration/full-content/fixtures/core__pullquote.json b/packages/e2e-tests/fixtures/blocks/core__pullquote.json similarity index 100% rename from test/integration/full-content/fixtures/core__pullquote.json rename to packages/e2e-tests/fixtures/blocks/core__pullquote.json diff --git a/test/integration/full-content/fixtures/core__pullquote.parsed.json b/packages/e2e-tests/fixtures/blocks/core__pullquote.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__pullquote.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__pullquote.parsed.json diff --git a/test/integration/full-content/fixtures/core__pullquote.serialized.html b/packages/e2e-tests/fixtures/blocks/core__pullquote.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__pullquote.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__pullquote.serialized.html diff --git a/test/integration/full-content/fixtures/core__pullquote__multi-paragraph.html b/packages/e2e-tests/fixtures/blocks/core__pullquote__multi-paragraph.html similarity index 100% rename from test/integration/full-content/fixtures/core__pullquote__multi-paragraph.html rename to packages/e2e-tests/fixtures/blocks/core__pullquote__multi-paragraph.html diff --git a/test/integration/full-content/fixtures/core__pullquote__multi-paragraph.json b/packages/e2e-tests/fixtures/blocks/core__pullquote__multi-paragraph.json similarity index 100% rename from test/integration/full-content/fixtures/core__pullquote__multi-paragraph.json rename to packages/e2e-tests/fixtures/blocks/core__pullquote__multi-paragraph.json diff --git a/test/integration/full-content/fixtures/core__pullquote__multi-paragraph.parsed.json b/packages/e2e-tests/fixtures/blocks/core__pullquote__multi-paragraph.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__pullquote__multi-paragraph.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__pullquote__multi-paragraph.parsed.json diff --git a/test/integration/full-content/fixtures/core__pullquote__multi-paragraph.serialized.html b/packages/e2e-tests/fixtures/blocks/core__pullquote__multi-paragraph.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__pullquote__multi-paragraph.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__pullquote__multi-paragraph.serialized.html diff --git a/test/integration/full-content/fixtures/core__quote__style-1.html b/packages/e2e-tests/fixtures/blocks/core__quote__style-1.html similarity index 100% rename from test/integration/full-content/fixtures/core__quote__style-1.html rename to packages/e2e-tests/fixtures/blocks/core__quote__style-1.html diff --git a/test/integration/full-content/fixtures/core__quote__style-1.json b/packages/e2e-tests/fixtures/blocks/core__quote__style-1.json similarity index 100% rename from test/integration/full-content/fixtures/core__quote__style-1.json rename to packages/e2e-tests/fixtures/blocks/core__quote__style-1.json diff --git a/test/integration/full-content/fixtures/core__quote__style-1.parsed.json b/packages/e2e-tests/fixtures/blocks/core__quote__style-1.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__quote__style-1.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__quote__style-1.parsed.json diff --git a/test/integration/full-content/fixtures/core__quote__style-1.serialized.html b/packages/e2e-tests/fixtures/blocks/core__quote__style-1.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__quote__style-1.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__quote__style-1.serialized.html diff --git a/test/integration/full-content/fixtures/core__quote__style-2.html b/packages/e2e-tests/fixtures/blocks/core__quote__style-2.html similarity index 100% rename from test/integration/full-content/fixtures/core__quote__style-2.html rename to packages/e2e-tests/fixtures/blocks/core__quote__style-2.html diff --git a/test/integration/full-content/fixtures/core__quote__style-2.json b/packages/e2e-tests/fixtures/blocks/core__quote__style-2.json similarity index 100% rename from test/integration/full-content/fixtures/core__quote__style-2.json rename to packages/e2e-tests/fixtures/blocks/core__quote__style-2.json diff --git a/test/integration/full-content/fixtures/core__quote__style-2.parsed.json b/packages/e2e-tests/fixtures/blocks/core__quote__style-2.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__quote__style-2.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__quote__style-2.parsed.json diff --git a/test/integration/full-content/fixtures/core__quote__style-2.serialized.html b/packages/e2e-tests/fixtures/blocks/core__quote__style-2.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__quote__style-2.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__quote__style-2.serialized.html diff --git a/test/integration/full-content/fixtures/core__rss.html b/packages/e2e-tests/fixtures/blocks/core__rss.html similarity index 100% rename from test/integration/full-content/fixtures/core__rss.html rename to packages/e2e-tests/fixtures/blocks/core__rss.html diff --git a/test/integration/full-content/fixtures/core__rss.json b/packages/e2e-tests/fixtures/blocks/core__rss.json similarity index 100% rename from test/integration/full-content/fixtures/core__rss.json rename to packages/e2e-tests/fixtures/blocks/core__rss.json diff --git a/test/integration/full-content/fixtures/core__rss.parsed.json b/packages/e2e-tests/fixtures/blocks/core__rss.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__rss.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__rss.parsed.json diff --git a/test/integration/full-content/fixtures/core__rss.serialized.html b/packages/e2e-tests/fixtures/blocks/core__rss.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__rss.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__rss.serialized.html diff --git a/test/integration/full-content/fixtures/core__search.html b/packages/e2e-tests/fixtures/blocks/core__search.html similarity index 100% rename from test/integration/full-content/fixtures/core__search.html rename to packages/e2e-tests/fixtures/blocks/core__search.html diff --git a/test/integration/full-content/fixtures/core__search.json b/packages/e2e-tests/fixtures/blocks/core__search.json similarity index 100% rename from test/integration/full-content/fixtures/core__search.json rename to packages/e2e-tests/fixtures/blocks/core__search.json diff --git a/test/integration/full-content/fixtures/core__search.parsed.json b/packages/e2e-tests/fixtures/blocks/core__search.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__search.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__search.parsed.json diff --git a/test/integration/full-content/fixtures/core__search.serialized.html b/packages/e2e-tests/fixtures/blocks/core__search.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__search.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__search.serialized.html diff --git a/test/integration/full-content/fixtures/core__search__custom-text.html b/packages/e2e-tests/fixtures/blocks/core__search__custom-text.html similarity index 100% rename from test/integration/full-content/fixtures/core__search__custom-text.html rename to packages/e2e-tests/fixtures/blocks/core__search__custom-text.html diff --git a/test/integration/full-content/fixtures/core__search__custom-text.json b/packages/e2e-tests/fixtures/blocks/core__search__custom-text.json similarity index 100% rename from test/integration/full-content/fixtures/core__search__custom-text.json rename to packages/e2e-tests/fixtures/blocks/core__search__custom-text.json diff --git a/test/integration/full-content/fixtures/core__search__custom-text.parsed.json b/packages/e2e-tests/fixtures/blocks/core__search__custom-text.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__search__custom-text.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__search__custom-text.parsed.json diff --git a/test/integration/full-content/fixtures/core__search__custom-text.serialized.html b/packages/e2e-tests/fixtures/blocks/core__search__custom-text.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__search__custom-text.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__search__custom-text.serialized.html diff --git a/test/integration/full-content/fixtures/core__separator.html b/packages/e2e-tests/fixtures/blocks/core__separator.html similarity index 100% rename from test/integration/full-content/fixtures/core__separator.html rename to packages/e2e-tests/fixtures/blocks/core__separator.html diff --git a/test/integration/full-content/fixtures/core__separator.json b/packages/e2e-tests/fixtures/blocks/core__separator.json similarity index 100% rename from test/integration/full-content/fixtures/core__separator.json rename to packages/e2e-tests/fixtures/blocks/core__separator.json diff --git a/test/integration/full-content/fixtures/core__separator.parsed.json b/packages/e2e-tests/fixtures/blocks/core__separator.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__separator.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__separator.parsed.json diff --git a/test/integration/full-content/fixtures/core__separator.serialized.html b/packages/e2e-tests/fixtures/blocks/core__separator.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__separator.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__separator.serialized.html diff --git a/test/integration/full-content/fixtures/core__shortcode.html b/packages/e2e-tests/fixtures/blocks/core__shortcode.html similarity index 100% rename from test/integration/full-content/fixtures/core__shortcode.html rename to packages/e2e-tests/fixtures/blocks/core__shortcode.html diff --git a/test/integration/full-content/fixtures/core__shortcode.json b/packages/e2e-tests/fixtures/blocks/core__shortcode.json similarity index 100% rename from test/integration/full-content/fixtures/core__shortcode.json rename to packages/e2e-tests/fixtures/blocks/core__shortcode.json diff --git a/test/integration/full-content/fixtures/core__shortcode.parsed.json b/packages/e2e-tests/fixtures/blocks/core__shortcode.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__shortcode.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__shortcode.parsed.json diff --git a/test/integration/full-content/fixtures/core__shortcode.serialized.html b/packages/e2e-tests/fixtures/blocks/core__shortcode.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__shortcode.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__shortcode.serialized.html diff --git a/test/integration/full-content/fixtures/core__spacer.html b/packages/e2e-tests/fixtures/blocks/core__spacer.html similarity index 100% rename from test/integration/full-content/fixtures/core__spacer.html rename to packages/e2e-tests/fixtures/blocks/core__spacer.html diff --git a/test/integration/full-content/fixtures/core__spacer.json b/packages/e2e-tests/fixtures/blocks/core__spacer.json similarity index 100% rename from test/integration/full-content/fixtures/core__spacer.json rename to packages/e2e-tests/fixtures/blocks/core__spacer.json diff --git a/test/integration/full-content/fixtures/core__spacer.parsed.json b/packages/e2e-tests/fixtures/blocks/core__spacer.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__spacer.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__spacer.parsed.json diff --git a/test/integration/full-content/fixtures/core__spacer.serialized.html b/packages/e2e-tests/fixtures/blocks/core__spacer.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__spacer.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__spacer.serialized.html diff --git a/test/integration/full-content/fixtures/core__subhead.html b/packages/e2e-tests/fixtures/blocks/core__subhead.html similarity index 100% rename from test/integration/full-content/fixtures/core__subhead.html rename to packages/e2e-tests/fixtures/blocks/core__subhead.html diff --git a/test/integration/full-content/fixtures/core__subhead.json b/packages/e2e-tests/fixtures/blocks/core__subhead.json similarity index 100% rename from test/integration/full-content/fixtures/core__subhead.json rename to packages/e2e-tests/fixtures/blocks/core__subhead.json diff --git a/test/integration/full-content/fixtures/core__subhead.parsed.json b/packages/e2e-tests/fixtures/blocks/core__subhead.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__subhead.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__subhead.parsed.json diff --git a/test/integration/full-content/fixtures/core__subhead.serialized.html b/packages/e2e-tests/fixtures/blocks/core__subhead.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__subhead.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__subhead.serialized.html diff --git a/test/integration/full-content/fixtures/core__table.html b/packages/e2e-tests/fixtures/blocks/core__table.html similarity index 100% rename from test/integration/full-content/fixtures/core__table.html rename to packages/e2e-tests/fixtures/blocks/core__table.html diff --git a/test/integration/full-content/fixtures/core__table.json b/packages/e2e-tests/fixtures/blocks/core__table.json similarity index 100% rename from test/integration/full-content/fixtures/core__table.json rename to packages/e2e-tests/fixtures/blocks/core__table.json diff --git a/test/integration/full-content/fixtures/core__table.parsed.json b/packages/e2e-tests/fixtures/blocks/core__table.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__table.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__table.parsed.json diff --git a/test/integration/full-content/fixtures/core__table.serialized.html b/packages/e2e-tests/fixtures/blocks/core__table.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__table.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__table.serialized.html diff --git a/test/integration/full-content/fixtures/core__tag-cloud.html b/packages/e2e-tests/fixtures/blocks/core__tag-cloud.html similarity index 100% rename from test/integration/full-content/fixtures/core__tag-cloud.html rename to packages/e2e-tests/fixtures/blocks/core__tag-cloud.html diff --git a/test/integration/full-content/fixtures/core__tag-cloud.json b/packages/e2e-tests/fixtures/blocks/core__tag-cloud.json similarity index 100% rename from test/integration/full-content/fixtures/core__tag-cloud.json rename to packages/e2e-tests/fixtures/blocks/core__tag-cloud.json diff --git a/test/integration/full-content/fixtures/core__tag-cloud.parsed.json b/packages/e2e-tests/fixtures/blocks/core__tag-cloud.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__tag-cloud.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__tag-cloud.parsed.json diff --git a/test/integration/full-content/fixtures/core__tag-cloud.serialized.html b/packages/e2e-tests/fixtures/blocks/core__tag-cloud.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__tag-cloud.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__tag-cloud.serialized.html diff --git a/test/integration/full-content/fixtures/core__tag-cloud__showTagCounts.html b/packages/e2e-tests/fixtures/blocks/core__tag-cloud__showTagCounts.html similarity index 100% rename from test/integration/full-content/fixtures/core__tag-cloud__showTagCounts.html rename to packages/e2e-tests/fixtures/blocks/core__tag-cloud__showTagCounts.html diff --git a/test/integration/full-content/fixtures/core__tag-cloud__showTagCounts.json b/packages/e2e-tests/fixtures/blocks/core__tag-cloud__showTagCounts.json similarity index 100% rename from test/integration/full-content/fixtures/core__tag-cloud__showTagCounts.json rename to packages/e2e-tests/fixtures/blocks/core__tag-cloud__showTagCounts.json diff --git a/test/integration/full-content/fixtures/core__tag-cloud__showTagCounts.parsed.json b/packages/e2e-tests/fixtures/blocks/core__tag-cloud__showTagCounts.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__tag-cloud__showTagCounts.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__tag-cloud__showTagCounts.parsed.json diff --git a/test/integration/full-content/fixtures/core__tag-cloud__showTagCounts.serialized.html b/packages/e2e-tests/fixtures/blocks/core__tag-cloud__showTagCounts.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__tag-cloud__showTagCounts.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__tag-cloud__showTagCounts.serialized.html diff --git a/test/integration/full-content/fixtures/core__text-columns.html b/packages/e2e-tests/fixtures/blocks/core__text-columns.html similarity index 100% rename from test/integration/full-content/fixtures/core__text-columns.html rename to packages/e2e-tests/fixtures/blocks/core__text-columns.html diff --git a/test/integration/full-content/fixtures/core__text-columns.json b/packages/e2e-tests/fixtures/blocks/core__text-columns.json similarity index 100% rename from test/integration/full-content/fixtures/core__text-columns.json rename to packages/e2e-tests/fixtures/blocks/core__text-columns.json diff --git a/test/integration/full-content/fixtures/core__text-columns.parsed.json b/packages/e2e-tests/fixtures/blocks/core__text-columns.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__text-columns.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__text-columns.parsed.json diff --git a/test/integration/full-content/fixtures/core__text-columns.serialized.html b/packages/e2e-tests/fixtures/blocks/core__text-columns.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__text-columns.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__text-columns.serialized.html diff --git a/test/integration/full-content/fixtures/core__text__converts-to-paragraph.html b/packages/e2e-tests/fixtures/blocks/core__text__converts-to-paragraph.html similarity index 100% rename from test/integration/full-content/fixtures/core__text__converts-to-paragraph.html rename to packages/e2e-tests/fixtures/blocks/core__text__converts-to-paragraph.html diff --git a/test/integration/full-content/fixtures/core__text__converts-to-paragraph.json b/packages/e2e-tests/fixtures/blocks/core__text__converts-to-paragraph.json similarity index 100% rename from test/integration/full-content/fixtures/core__text__converts-to-paragraph.json rename to packages/e2e-tests/fixtures/blocks/core__text__converts-to-paragraph.json diff --git a/test/integration/full-content/fixtures/core__text__converts-to-paragraph.parsed.json b/packages/e2e-tests/fixtures/blocks/core__text__converts-to-paragraph.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__text__converts-to-paragraph.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__text__converts-to-paragraph.parsed.json diff --git a/test/integration/full-content/fixtures/core__text__converts-to-paragraph.serialized.html b/packages/e2e-tests/fixtures/blocks/core__text__converts-to-paragraph.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__text__converts-to-paragraph.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__text__converts-to-paragraph.serialized.html diff --git a/test/integration/full-content/fixtures/core__verse.html b/packages/e2e-tests/fixtures/blocks/core__verse.html similarity index 100% rename from test/integration/full-content/fixtures/core__verse.html rename to packages/e2e-tests/fixtures/blocks/core__verse.html diff --git a/test/integration/full-content/fixtures/core__verse.json b/packages/e2e-tests/fixtures/blocks/core__verse.json similarity index 100% rename from test/integration/full-content/fixtures/core__verse.json rename to packages/e2e-tests/fixtures/blocks/core__verse.json diff --git a/test/integration/full-content/fixtures/core__verse.parsed.json b/packages/e2e-tests/fixtures/blocks/core__verse.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__verse.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__verse.parsed.json diff --git a/test/integration/full-content/fixtures/core__verse.serialized.html b/packages/e2e-tests/fixtures/blocks/core__verse.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__verse.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__verse.serialized.html diff --git a/test/integration/full-content/fixtures/core__video.html b/packages/e2e-tests/fixtures/blocks/core__video.html similarity index 100% rename from test/integration/full-content/fixtures/core__video.html rename to packages/e2e-tests/fixtures/blocks/core__video.html diff --git a/test/integration/full-content/fixtures/core__video.json b/packages/e2e-tests/fixtures/blocks/core__video.json similarity index 100% rename from test/integration/full-content/fixtures/core__video.json rename to packages/e2e-tests/fixtures/blocks/core__video.json diff --git a/test/integration/full-content/fixtures/core__video.parsed.json b/packages/e2e-tests/fixtures/blocks/core__video.parsed.json similarity index 100% rename from test/integration/full-content/fixtures/core__video.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__video.parsed.json diff --git a/test/integration/full-content/fixtures/core__video.serialized.html b/packages/e2e-tests/fixtures/blocks/core__video.serialized.html similarity index 100% rename from test/integration/full-content/fixtures/core__video.serialized.html rename to packages/e2e-tests/fixtures/blocks/core__video.serialized.html diff --git a/packages/e2e-tests/fixtures/index.js b/packages/e2e-tests/fixtures/index.js new file mode 100644 index 0000000000000..a1ee006bdc473 --- /dev/null +++ b/packages/e2e-tests/fixtures/index.js @@ -0,0 +1,12 @@ +export { + blockNameToFixtureBasename, + getAvailableBlockFixturesBasenames, + getBlockFixtureHTML, + getBlockFixtureJSON, + getBlockFixtureParsedJSON, + getBlockFixtureSerializedHTML, + writeBlockFixtureHTML, + writeBlockFixtureJSON, + writeBlockFixtureParsedJSON, + writeBlockFixtureSerializedHTML, +} from './utils'; diff --git a/packages/e2e-tests/fixtures/utils.js b/packages/e2e-tests/fixtures/utils.js new file mode 100644 index 0000000000000..344e6d4a0504a --- /dev/null +++ b/packages/e2e-tests/fixtures/utils.js @@ -0,0 +1,92 @@ +/** + * External dependencies + */ +import fs from 'fs'; +import path from 'path'; +import { uniq } from 'lodash'; + +const FIXTURES_DIR = path.join( __dirname, 'blocks' ); + +function readFixtureFile( fixturesDir, filename ) { + try { + return fs.readFileSync( + path.join( fixturesDir, filename ), + 'utf8' + ); + } catch ( err ) { + return null; + } +} + +function writeFixtureFile( fixturesDir, filename, content ) { + fs.writeFileSync( + path.join( fixturesDir, filename ), + content + ); +} + +export function blockNameToFixtureBasename( blockName ) { + return blockName.replace( /\//g, '__' ); +} + +export function getAvailableBlockFixturesBasenames() { + // We expect 4 different types of files for each fixture: + // - fixture.html : original content + // - fixture.parsed.json : parser output + // - fixture.json : blocks structure + // - fixture.serialized.html : re-serialized content + // Get the "base" name for each fixture first. + return uniq( + fs.readdirSync( FIXTURES_DIR ) + .filter( ( f ) => /(\.html|\.json)$/.test( f ) ) + .map( ( f ) => f.replace( /\..+$/, '' ) ) + ); +} + +export function getBlockFixtureHTML( basename ) { + const filename = `${ basename }.html`; + return { + filename, + file: readFixtureFile( FIXTURES_DIR, filename ), + }; +} + +export function getBlockFixtureJSON( basename ) { + const filename = `${ basename }.json`; + return { + filename, + file: readFixtureFile( FIXTURES_DIR, filename ), + }; +} + +export function getBlockFixtureParsedJSON( basename ) { + const filename = `${ basename }.parsed.json`; + return { + filename, + file: readFixtureFile( FIXTURES_DIR, filename ), + }; +} + +export function getBlockFixtureSerializedHTML( basename ) { + const filename = `${ basename }.serialized.html`; + return { + filename, + file: readFixtureFile( FIXTURES_DIR, filename ), + }; +} + +export function writeBlockFixtureHTML( basename, fixture ) { + writeFixtureFile( FIXTURES_DIR, `${ basename }.html`, fixture ); +} + +export function writeBlockFixtureJSON( basename, fixture ) { + writeFixtureFile( FIXTURES_DIR, `${ basename }.json`, fixture ); +} + +export function writeBlockFixtureParsedJSON( basename, fixture ) { + writeFixtureFile( FIXTURES_DIR, `${ basename }.parsed.json`, fixture ); +} + +export function writeBlockFixtureSerializedHTML( basename, fixture ) { + writeFixtureFile( FIXTURES_DIR, `${ basename }.serialized.html`, fixture ); +} diff --git a/test/integration/full-content/full-content.spec.js b/test/integration/full-content/full-content.spec.js index 42132dea401bc..cd5308e92b081 100644 --- a/test/integration/full-content/full-content.spec.js +++ b/test/integration/full-content/full-content.spec.js @@ -1,9 +1,7 @@ /** * External dependencies */ -import fs from 'fs'; -import path from 'path'; -import { uniq, startsWith, get } from 'lodash'; +import { startsWith, get } from 'lodash'; import { format } from 'util'; /** @@ -17,38 +15,19 @@ import { } from '@wordpress/blocks'; import { parse as grammarParse } from '@wordpress/block-serialization-default-parser'; import { registerCoreBlocks } from '@wordpress/block-library'; - -const fixturesDir = path.join( __dirname, 'fixtures' ); - -// We expect 4 different types of files for each fixture: -// - fixture.html : original content -// - fixture.parsed.json : parser output -// - fixture.json : blocks structure -// - fixture.serialized.html : re-serialized content -// Get the "base" name for each fixture first. -const fileBasenames = uniq( - fs.readdirSync( fixturesDir ) - .filter( ( f ) => /(\.html|\.json)$/.test( f ) ) - .map( ( f ) => f.replace( /\..+$/, '' ) ) -); - -function readFixtureFile( filename ) { - try { - return fs.readFileSync( - path.join( fixturesDir, filename ), - 'utf8' - ); - } catch ( err ) { - return null; - } -} - -function writeFixtureFile( filename, content ) { - fs.writeFileSync( - path.join( fixturesDir, filename ), - content - ); -} +import { //eslint-disable-line no-restricted-syntax + blockNameToFixtureBasename, + getAvailableBlockFixturesBasenames, + getBlockFixtureHTML, + getBlockFixtureJSON, + getBlockFixtureParsedJSON, + getBlockFixtureSerializedHTML, + writeBlockFixtureParsedJSON, + writeBlockFixtureJSON, + writeBlockFixtureSerializedHTML, +} from '@wordpress/e2e-tests/fixtures'; + +const blockBasenames = getAvailableBlockFixturesBasenames(); function normalizeParsedBlocks( blocks ) { return blocks.map( ( block, index ) => { @@ -75,31 +54,37 @@ describe( 'full post content fixture', () => { registerCoreBlocks(); } ); - fileBasenames.forEach( ( f ) => { - it( f, () => { - const content = readFixtureFile( f + '.html' ); - if ( content === null ) { + blockBasenames.forEach( ( basename ) => { + it( basename, () => { + const { + filename: htmlFixtureFileName, + file: htmlFixtureContent, + } = getBlockFixtureHTML( basename ); + if ( htmlFixtureContent === null ) { throw new Error( - 'Missing fixture file: ' + f + '.html' + `Missing fixture file: ${ htmlFixtureFileName }` ); } - const parserOutputActual = grammarParse( content ); - let parserOutputExpectedString = readFixtureFile( f + '.parsed.json' ); - - if ( ! parserOutputExpectedString ) { - if ( process.env.GENERATE_MISSING_FIXTURES ) { - parserOutputExpectedString = JSON.stringify( - parserOutputActual, - null, - 4 - ) + '\n'; - writeFixtureFile( f + '.parsed.json', parserOutputExpectedString ); - } else { - throw new Error( - 'Missing fixture file: ' + f + '.parsed.json' - ); - } + const { + filename: parsedJSONFixtureFileName, + file: parsedJSONFixtureContent, + } = getBlockFixtureParsedJSON( basename ); + const parserOutputActual = grammarParse( htmlFixtureContent ); + let parserOutputExpectedString; + if ( parsedJSONFixtureContent ) { + parserOutputExpectedString = parsedJSONFixtureContent; + } else if ( process.env.GENERATE_MISSING_FIXTURES ) { + parserOutputExpectedString = JSON.stringify( + parserOutputActual, + null, + 4 + ) + '\n'; + writeBlockFixtureParsedJSON( basename, parserOutputExpectedString ); + } else { + throw new Error( + `Missing fixture file: ${ parsedJSONFixtureFileName }` + ); } const parserOutputExpected = JSON.parse( parserOutputExpectedString ); @@ -109,18 +94,18 @@ describe( 'full post content fixture', () => { ).toEqual( parserOutputExpected ); } catch ( err ) { throw new Error( format( - "File '%s.parsed.json' does not match expected value:\n\n%s", - f, + "File '%s' does not match expected value:\n\n%s", + parsedJSONFixtureFileName, err.message ) ); } - const blocksActual = parse( content ); + const blocksActual = parse( htmlFixtureContent ); // Block validation may log errors during deprecation migration, // unless explicitly handled from a valid block via isEligible. - // Match on filename for deprecated blocks fixtures to allow. - const isDeprecated = /__deprecated([-_]|$)/.test( f ); + // Match on basename for deprecated blocks fixtures to allow. + const isDeprecated = /__deprecated([-_]|$)/.test( basename ); if ( isDeprecated ) { /* eslint-disable no-console */ console.warn.mockReset(); @@ -129,21 +114,26 @@ describe( 'full post content fixture', () => { } const blocksActualNormalized = normalizeParsedBlocks( blocksActual ); - let blocksExpectedString = readFixtureFile( f + '.json' ); - - if ( ! blocksExpectedString ) { - if ( process.env.GENERATE_MISSING_FIXTURES ) { - blocksExpectedString = JSON.stringify( - blocksActualNormalized, - null, - 4 - ) + '\n'; - writeFixtureFile( f + '.json', blocksExpectedString ); - } else { - throw new Error( - 'Missing fixture file: ' + f + '.json' - ); - } + const { + filename: jsonFixtureFileName, + file: jsonFixtureContent, + } = getBlockFixtureJSON( basename ); + + let blocksExpectedString; + + if ( jsonFixtureContent ) { + blocksExpectedString = jsonFixtureContent; + } else if ( process.env.GENERATE_MISSING_FIXTURES ) { + blocksExpectedString = JSON.stringify( + blocksActualNormalized, + null, + 4 + ) + '\n'; + writeBlockFixtureJSON( basename, blocksExpectedString ); + } else { + throw new Error( + `Missing fixture file: ${ jsonFixtureFileName }` + ); } const blocksExpected = JSON.parse( blocksExpectedString ); @@ -153,8 +143,8 @@ describe( 'full post content fixture', () => { ).toEqual( blocksExpected ); } catch ( err ) { throw new Error( format( - "File '%s.json' does not match expected value:\n\n%s", - f, + "File '%s' does not match expected value:\n\n%s", + jsonFixtureFileName, err.message ) ); } @@ -162,25 +152,29 @@ describe( 'full post content fixture', () => { // `serialize` doesn't have a trailing newline, but the fixture // files should. const serializedActual = serialize( blocksActual ) + '\n'; - let serializedExpected = readFixtureFile( f + '.serialized.html' ); - - if ( ! serializedExpected ) { - if ( process.env.GENERATE_MISSING_FIXTURES ) { - serializedExpected = serializedActual; - writeFixtureFile( f + '.serialized.html', serializedExpected ); - } else { - throw new Error( - 'Missing fixture file: ' + f + '.serialized.html' - ); - } + const { + filename: serializedHTMLFileName, + file: serializedHTMLFixtureContent, + } = getBlockFixtureSerializedHTML( basename ); + + let serializedExpected; + if ( serializedHTMLFixtureContent ) { + serializedExpected = serializedHTMLFixtureContent; + } else if ( process.env.GENERATE_MISSING_FIXTURES ) { + serializedExpected = serializedActual; + writeBlockFixtureSerializedHTML( basename, serializedExpected ); + } else { + throw new Error( + `Missing fixture file: ${ serializedHTMLFileName }` + ); } try { expect( serializedActual ).toEqual( serializedExpected ); } catch ( err ) { throw new Error( format( - "File '%s.serialized.html' does not match expected value:\n\n%s", - f, + "File '%s' does not match expected value:\n\n%s", + serializedHTMLFileName, err.message ) ); } @@ -197,25 +191,29 @@ describe( 'full post content fixture', () => { // The `core/template` is not worth testing here because it's never saved, it's covered better in e2e tests. .filter( ( name ) => name.indexOf( 'core-embed' ) !== 0 && name !== 'core/template' ) .forEach( ( name ) => { - const nameToFilename = name.replace( /\//g, '__' ); - const foundFixtures = fileBasenames + const nameToFilename = blockNameToFixtureBasename( name ); + const foundFixtures = blockBasenames .filter( ( basename ) => ( basename === nameToFilename || startsWith( basename, nameToFilename + '__' ) ) ) .map( ( basename ) => { - // The file that contains the input HTML for this test. - const inputFilename = basename + '.html'; + const { + filename: htmlFixtureFileName, + } = getBlockFixtureHTML( basename ); + const { + file: jsonFixtureContent, + } = getBlockFixtureJSON( basename ); // The parser output for this test. For missing files, // JSON.parse( null ) === null. const parserOutput = JSON.parse( - readFixtureFile( basename + '.json' ) + jsonFixtureContent, ); // The name of the first block that this fixture file // contains (if any). const firstBlock = get( parserOutput, [ '0', 'name' ], null ); return { - filename: inputFilename, + filename: htmlFixtureFileName, parserOutput, firstBlock, }; From d80e15b1761a4945009beed47bb1fdc0e729c457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Est=C3=AAv=C3=A3o?= <sergioestevao@gmail.com> Date: Mon, 18 Feb 2019 11:50:31 +0000 Subject: [PATCH 460/691] Have a image placeholder while image size is being calculated. (#13777) * Have a image placeholder while image size is being calculated. * Move placeholder image to image block code. --- packages/block-library/src/image/edit.native.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index 2ba6f707422eb..850569d0bbcff 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -326,6 +326,14 @@ class ImageEdit extends React.Component { imageHeightWithinContainer, } = sizes; + if ( imageWidthWithinContainer === undefined ) { + return ( + <View style={ styles.imageContainer } > + <Dashicon icon={ 'format-image' } size={ 300 } /> + </View> + ); + } + let finalHeight = imageHeightWithinContainer; if ( height > 0 && height < imageHeightWithinContainer ) { finalHeight = height; From 988d9d027bae4a0045df552f07059d93617241bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Est=C3=AAv=C3=A3o?= <sergioestevao@gmail.com> Date: Mon, 18 Feb 2019 12:47:52 +0000 Subject: [PATCH 461/691] Media upload change thumb on progress (#13764) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Allow progress call to update the thumbnail image. * If we already have a subscription don’t go it again. * Fix bottomsheet event removal. --- packages/block-library/src/image/edit.native.js | 15 ++++++++++++++- .../mobile/bottom-sheet/index.native.js | 7 ++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index 850569d0bbcff..7e2d097d2498d 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -67,6 +67,7 @@ class ImageEdit extends React.Component { this.removeMediaUploadListener = this.removeMediaUploadListener.bind( this ); this.finishMediaUploadWithSuccess = this.finishMediaUploadWithSuccess.bind( this ); this.finishMediaUploadWithFailure = this.finishMediaUploadWithFailure.bind( this ); + this.updateMediaProgress = this.updateMediaProgress.bind( this ); this.updateAlt = this.updateAlt.bind( this ); this.updateImageURL = this.updateImageURL.bind( this ); this.onSetLinkDestination = this.onSetLinkDestination.bind( this ); @@ -106,7 +107,7 @@ class ImageEdit extends React.Component { switch ( payload.state ) { case MEDIA_UPLOAD_STATE_UPLOADING: - this.setState( { progress: payload.progress, isUploadInProgress: true, isUploadFailed: false } ); + this.updateMediaProgress( payload ); break; case MEDIA_UPLOAD_STATE_SUCCEEDED: this.finishMediaUploadWithSuccess( payload ); @@ -120,6 +121,14 @@ class ImageEdit extends React.Component { } } + updateMediaProgress( payload ) { + const { setAttributes } = this.props; + this.setState( { progress: payload.progress, isUploadInProgress: true, isUploadFailed: false } ); + if ( payload.mediaUrl !== undefined ) { + setAttributes( { url: payload.mediaUrl } ); + } + } + finishMediaUploadWithSuccess( payload ) { const { setAttributes } = this.props; @@ -144,6 +153,10 @@ class ImageEdit extends React.Component { } addMediaUploadListener() { + //if we already have a subscription not worth doing it again + if ( this.subscriptionParentMediaUpload ) { + return; + } this.subscriptionParentMediaUpload = subscribeMediaUpload( ( payload ) => { this.mediaUpload( payload ); } ); diff --git a/packages/editor/src/components/mobile/bottom-sheet/index.native.js b/packages/editor/src/components/mobile/bottom-sheet/index.native.js index 986e220e41a36..c3cfb24686092 100644 --- a/packages/editor/src/components/mobile/bottom-sheet/index.native.js +++ b/packages/editor/src/components/mobile/bottom-sheet/index.native.js @@ -30,14 +30,19 @@ class BottomSheet extends Component { } componentDidMount() { - SafeArea.addEventListener( 'safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsUpdate ); + this.eventSubscription = SafeArea.addEventListener( 'safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsUpdate ); } componentWillUnmount() { + this.eventSubscription.remove(); + this.eventSubscription = null; SafeArea.removeEventListener( 'safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsUpdate ); } onSafeAreaInsetsUpdate( result ) { + if ( this.eventSubscription === null ) { + return; + } const { safeAreaInsets } = result; if ( this.state.safeAreaBottomInset !== safeAreaInsets.bottom ) { this.setState( { safeAreaBottomInset: safeAreaInsets.bottom } ); From d7aa66e87753b5214c08cc3dbaf74d385747ebe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20Van=C2=A0Durpe?= <iseulde@automattic.com> Date: Mon, 18 Feb 2019 14:50:48 +0100 Subject: [PATCH 462/691] onReplace: select the last block (#13294) * onReplace: select the last block * Update tests * Add second test to cover cases of REPLACE_BLOCKS --- packages/editor/src/store/reducer.js | 9 ++++---- packages/editor/src/store/test/reducer.js | 28 +++++++++++++++++++++-- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/packages/editor/src/store/reducer.js b/packages/editor/src/store/reducer.js index a268e2670fc65..6e305185c96e7 100644 --- a/packages/editor/src/store/reducer.js +++ b/packages/editor/src/store/reducer.js @@ -15,7 +15,6 @@ import { isEqual, isEmpty, overSome, - get, } from 'lodash'; /** @@ -887,9 +886,11 @@ export function blockSelection( state = { return state; } - // If there is replacement block(s), assign first's client ID as - // the next selected block. If empty replacement, reset to null. - const nextSelectedBlockClientId = get( action.blocks, [ 0, 'clientId' ], null ); + // If there are replacement blocks, assign last block as the next + // selected block, otherwise set to null. + const lastBlock = last( action.blocks ); + const nextSelectedBlockClientId = lastBlock ? lastBlock.clientId : null; + if ( nextSelectedBlockClientId === state.start && nextSelectedBlockClientId === state.end ) { return state; } diff --git a/packages/editor/src/store/test/reducer.js b/packages/editor/src/store/test/reducer.js index 2d11a0e319a2d..a8172df208a41 100644 --- a/packages/editor/src/store/test/reducer.js +++ b/packages/editor/src/store/test/reducer.js @@ -1810,7 +1810,26 @@ describe( 'state', () => { } ); } ); - it( 'should not replace the selected block if we keep it when replacing blocks', () => { + it( 'should not replace the selected block if we keep it at the end when replacing blocks', () => { + const original = deepFreeze( { start: 'wings', end: 'wings' } ); + const state = blockSelection( original, { + type: 'REPLACE_BLOCKS', + clientIds: [ 'wings' ], + blocks: [ + { + clientId: 'chicken', + name: 'core/freeform', + }, + { + clientId: 'wings', + name: 'core/freeform', + } ], + } ); + + expect( state ).toBe( original ); + } ); + + it( 'should replace the selected block if we keep it not at the end when replacing blocks', () => { const original = deepFreeze( { start: 'chicken', end: 'chicken' } ); const state = blockSelection( original, { type: 'REPLACE_BLOCKS', @@ -1826,7 +1845,12 @@ describe( 'state', () => { } ], } ); - expect( state ).toBe( original ); + expect( state ).toEqual( { + start: 'wings', + end: 'wings', + initialPosition: null, + isMultiSelecting: false, + } ); } ); it( 'should reset if replacing with empty set', () => { From 99f31cdb4ed035264e0332b72dd3a2287d93ff50 Mon Sep 17 00:00:00 2001 From: Jorge Bernal <jbernal@gmail.com> Date: Mon, 18 Feb 2019 15:28:01 +0100 Subject: [PATCH 463/691] Use format-library in gutenberg-mobile (#12249) * Store current selection in RichText state * Make native RichText use format-library This is a work in progress. Sometimes it works, sometimes it doesn't, I haven't figured out the right way to wire the RichText structure into Aztec * Fix bad merge * Don't use applyRecord for now * Missing newlines * Do not change lastContent on formatChange, let the component rerender and update record. Also minor cleanup * Make sure the component rerenders when isSelected changes so FormatToolbar is removed * Make a simple version of Link that displays on mobile * Rerender on selection change to update record * Make it update onFormatChange when url changes * Cleanup changes in Heading and Paragraph * Remove unnecessary components and styles added while testing * Improve link editing UI * Fix adding a new url without a selection * Fix editing the text part of the url * Fill modal URL to current value * Pressing Remove dismisses the popup. Improve styles * Bring back multilineTag prop * Remove unused param in shouldComponentUpdate * Remove withBlockEditContext for now, it will need a better refactor to properly deal with focus from Aztec * Temporary fix for whitespace in HTML, will need to be addressed later * Make sure end is always greater than start for the text selection * Remove unsupported css property * Disable autoCapitalize and autoCorrect for URLInput * Fix lint errors in mobile and in rnmobile/rich-text-formats * Use a template string to generate html for Aztec in RichText * Preprocess the HTML value before sending it to Aztec, removing whitespaces in the process * Use custom button instead of native provided by react-native * Add some padding to our custom button * Send active formats to RCTAztecView so we can type new words in a format * Handle multiple formats with format placeholder * Cleanup applyFormat * Make applyFormat support applying multiple formats at once * Keep formatting when onSelectionChange is emitted without changes * Keep the same order of formats * Prevent inserting anything other that a formatting element from the link modal * Make sure wrapping tags returned by aztec are removed so it's not added to formats * Improve styling of the modal * Make sure we don't pass undefined values in activeFormats * Update formatPlaceholder when user decides to unselect a format in an existing formatted text * Update props.value on format change * Force update native view onFormatChange by not setting this.lastContent * Handle inserting a link * Fix updating format properties * Expand link selection automatically so we can edit a link without selecting it explicitly * Force an aztec text refresh on format change so we make sure the active formats are in sync * Add comment and make the code more consistent for debugging * Make sure we don't use format ref inside the placeholder formats * Make sure we don't use format ref inside the placeholder formats * Do not try to call valueToFormat on format change without selection * Fix editing link url * Add the ability to remove a link without selection * Do not remove trailing whitespace characters * Unescape spaces coming from Aztec * Fix code styling issues * Pick the formats of the first char when in text start * Fix lint errors * RichText value may be undefined * Update lastEventCount on enter and selection changes * Update ModalLinkUI to avoid the keyboard * Lint fixes --- .../block-library/src/heading/edit.native.js | 11 +- .../src/paragraph/edit.native.js | 9 +- .../editor/src/components/index.native.js | 7 +- .../rich-text/format-toolbar/index.native.js | 18 ++ .../src/components/rich-text/index.native.js | 236 +++++++++--------- .../components/rich-text/shortcut.native.js | 10 + .../src/components/url-input/index.native.js | 35 +++ .../format-library/src/default-formats.js | 18 ++ .../src/default-formats.native.js | 16 ++ packages/format-library/src/index.js | 26 +- .../format-library/src/link/button.native.js | 24 ++ .../format-library/src/link/index.native.js | 134 ++++++++++ packages/format-library/src/link/inline.js | 33 +-- .../format-library/src/link/modal.native.js | 175 +++++++++++++ .../format-library/src/link/modal.native.scss | 80 ++++++ packages/format-library/src/link/utils.js | 30 +++ packages/rich-text/src/apply-format.native.js | 83 ++++++ .../rich-text/src/get-active-format.native.js | 44 ++++ .../rich-text/src/normalise-formats.native.js | 36 +++ .../rich-text/src/remove-format.native.js | 69 +++++ 20 files changed, 910 insertions(+), 184 deletions(-) create mode 100644 packages/editor/src/components/rich-text/format-toolbar/index.native.js create mode 100644 packages/editor/src/components/rich-text/shortcut.native.js create mode 100644 packages/editor/src/components/url-input/index.native.js create mode 100644 packages/format-library/src/default-formats.js create mode 100644 packages/format-library/src/default-formats.native.js create mode 100644 packages/format-library/src/link/button.native.js create mode 100644 packages/format-library/src/link/index.native.js create mode 100644 packages/format-library/src/link/modal.native.js create mode 100644 packages/format-library/src/link/modal.native.scss create mode 100644 packages/rich-text/src/apply-format.native.js create mode 100644 packages/rich-text/src/get-active-format.native.js create mode 100644 packages/rich-text/src/normalise-formats.native.js create mode 100644 packages/rich-text/src/remove-format.native.js diff --git a/packages/block-library/src/heading/edit.native.js b/packages/block-library/src/heading/edit.native.js index 44289a550ce0b..ab578dfd22916 100644 --- a/packages/block-library/src/heading/edit.native.js +++ b/packages/block-library/src/heading/edit.native.js @@ -14,7 +14,7 @@ import { View } from 'react-native'; import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; import { RichText, BlockControls } from '@wordpress/editor'; -import { parse, createBlock } from '@wordpress/blocks'; +import { createBlock } from '@wordpress/blocks'; /** * Internal dependencies @@ -62,14 +62,7 @@ class HeadingEdit extends Component { style={ { minHeight: Math.max( minHeight, this.state.aztecHeight ), } } - onChange={ ( event ) => { - // Create a React Tree from the new HTML - const newParaBlock = parse( `<!-- wp:heading {"level":${ level }} --><${ tagName }>${ event.content }</${ tagName }><!-- /wp:heading -->` )[ 0 ]; - setAttributes( { - ...this.props.attributes, - content: newParaBlock.attributes.content, - } ); - } } + onChange={ ( value ) => setAttributes( { content: value } ) } onMerge={ mergeBlocks } onSplit={ insertBlocksAfter ? diff --git a/packages/block-library/src/paragraph/edit.native.js b/packages/block-library/src/paragraph/edit.native.js index 3ed4225f7b26d..b8205240d48d1 100644 --- a/packages/block-library/src/paragraph/edit.native.js +++ b/packages/block-library/src/paragraph/edit.native.js @@ -8,7 +8,7 @@ import { View } from 'react-native'; */ import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; -import { parse, createBlock } from '@wordpress/blocks'; +import { createBlock } from '@wordpress/blocks'; import { RichText } from '@wordpress/editor'; /** @@ -99,12 +99,9 @@ class ParagraphEdit extends Component { ...style, minHeight: Math.max( minHeight, this.state.aztecHeight ), } } - onChange={ ( event ) => { - // Create a React Tree from the new HTML - const newParaBlock = parse( '<!-- wp:paragraph --><p>' + event.content + '</p><!-- /wp:paragraph -->' )[ 0 ]; + onChange={ ( nextContent ) => { setAttributes( { - ...this.props.attributes, - content: newParaBlock.attributes.content, + content: nextContent, } ); } } onSplit={ this.splitBlock } diff --git a/packages/editor/src/components/index.native.js b/packages/editor/src/components/index.native.js index fc41a93fa6af3..5581015fa1d16 100644 --- a/packages/editor/src/components/index.native.js +++ b/packages/editor/src/components/index.native.js @@ -1,7 +1,11 @@ export * from './colors'; export * from './font-sizes'; export { default as PlainText } from './plain-text'; -export { default as RichText } from './rich-text'; +export { + default as RichText, + RichTextShortcut, + RichTextToolbarButton, +} from './rich-text'; export { default as MediaPlaceholder } from './media-placeholder'; export { default as BlockFormatControls } from './block-format-controls'; export { default as BlockControls } from './block-controls'; @@ -13,3 +17,4 @@ export { default as EditorHistoryUndo } from './editor-history/undo'; export { default as InspectorControls } from './inspector-controls'; export { default as BottomSheet } from './mobile/bottom-sheet'; export { default as Picker } from './mobile/picker'; +export { default as URLInput } from './url-input'; diff --git a/packages/editor/src/components/rich-text/format-toolbar/index.native.js b/packages/editor/src/components/rich-text/format-toolbar/index.native.js new file mode 100644 index 0000000000000..d90860c05f4d6 --- /dev/null +++ b/packages/editor/src/components/rich-text/format-toolbar/index.native.js @@ -0,0 +1,18 @@ +/** + * WordPress dependencies + */ + +import { Toolbar, Slot } from '@wordpress/components'; + +const FormatToolbar = ( { controls } ) => { + return ( + <Toolbar> + { controls.map( ( format ) => + <Slot name={ `RichText.ToolbarControls.${ format }` } key={ format } /> + ) } + <Slot name="RichText.ToolbarControls" /> + </Toolbar> + ); +}; + +export default FormatToolbar; diff --git a/packages/editor/src/components/rich-text/index.native.js b/packages/editor/src/components/rich-text/index.native.js index 72ff2d49c5b18..72e7c70a0a300 100644 --- a/packages/editor/src/components/rich-text/index.native.js +++ b/packages/editor/src/components/rich-text/index.native.js @@ -3,19 +3,16 @@ */ import RCTAztecView from 'react-native-aztec'; import { View, Platform } from 'react-native'; -import { - forEach, - merge, -} from 'lodash'; /** * WordPress dependencies */ import { Component, RawHTML } from '@wordpress/element'; import { withInstanceId, compose } from '@wordpress/compose'; -import { Toolbar } from '@wordpress/components'; import { BlockFormatControls } from '@wordpress/editor'; +import { withSelect } from '@wordpress/data'; import { + getActiveFormat, isEmpty, create, split, @@ -23,47 +20,28 @@ import { } from '@wordpress/rich-text'; import { BACKSPACE } from '@wordpress/keycodes'; import { children } from '@wordpress/blocks'; -import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import styles from './style.scss'; +import FormatEdit from './format-edit'; +import FormatToolbar from './format-toolbar'; -const FORMATTING_CONTROLS = [ - { - icon: 'editor-bold', - title: __( 'Bold' ), - format: 'bold', - }, - { - icon: 'editor-italic', - title: __( 'Italic' ), - format: 'italic', - }, - // TODO: get this back after alpha - // { - // icon: 'admin-links', - // title: __( 'Link' ), - // format: 'link', - // }, - { - icon: 'editor-strikethrough', - title: __( 'Strikethrough' ), - format: 'strikethrough', - }, -]; +import styles from './style.scss'; const isRichTextValueEmpty = ( value ) => { return ! value || ! value.length; }; -export function getFormatValue( formatName ) { - if ( 'link' === formatName ) { - //TODO: Implement link command - } - return { isActive: true }; -} +const unescapeSpaces = ( text ) => { + return text.replace( /&nbsp;|&#160;/gi, ' ' ); +}; + +const gutenbergFormatNamesToAztec = { + 'core/bold': 'bold', + 'core/italic': 'italic', + 'core/strikethrough': 'strikethrough', +}; export class RichText extends Component { constructor() { @@ -73,17 +51,31 @@ export class RichText extends Component { this.onEnter = this.onEnter.bind( this ); this.onBackspace = this.onBackspace.bind( this ); this.onContentSizeChange = this.onContentSizeChange.bind( this ); - this.changeFormats = this.changeFormats.bind( this ); - this.toggleFormat = this.toggleFormat.bind( this ); - this.onActiveFormatsChange = this.onActiveFormatsChange.bind( this ); - this.isEmpty = this.isEmpty.bind( this ); + this.onFormatChange = this.onFormatChange.bind( this ); + // This prevents a bug in Aztec which triggers onSelectionChange twice on format change + this.onSelectionChange = this.onSelectionChange.bind( this ); this.valueToFormat = this.valueToFormat.bind( this ); this.state = { - formats: {}, - selectedNodeId: 0, + start: 0, + end: 0, + formatPlaceholder: null, }; } + /** + * Get the current record (value and selection) from props and state. + * + * @return {Object} The current record (value and selection). + */ + getRecord() { + const { formatPlaceholder, start, end } = this.state; + // Since we get the text selection from Aztec we need to be in sync with the HTML `value` + // Removing leading white spaces using `trim()` should make sure this is the case. + const { formats, text } = this.formatToValue( this.props.value === undefined ? undefined : this.props.value.trimLeft() ); + + return { formats, formatPlaceholder, text, start, end }; + } + /* * Splits the content at the location of the selection. * @@ -149,19 +141,37 @@ export class RichText extends Component { return this.removeRootTagsProduceByAztec( value ); } - onActiveFormatsChange( formats ) { - // force re-render the component skipping shouldComponentUpdate() See: https://reactjs.org/docs/react-component.html#forceupdate - // This is needed because our shouldComponentUpdate impl. doesn't take in consideration props yet. - this.forceUpdate(); - const newFormats = formats.reduce( ( accFormats, activeFormat ) => { - accFormats[ activeFormat ] = getFormatValue( activeFormat ); - return accFormats; - }, {} ); + getActiveFormatNames( record ) { + const { + formatTypes, + } = this.props; + return formatTypes.map( ( { name } ) => name ).filter( ( name ) => { + return getActiveFormat( record, name ) !== undefined; + } ).map( ( name ) => gutenbergFormatNamesToAztec[ name ] ).filter( Boolean ); + } + + onFormatChange( record ) { + let newContent; + // valueToFormat might throw when converting the record to a tree structure + // let's ignore the event for now and force a render update so we're still in sync + try { + newContent = this.valueToFormat( record ); + } catch ( error ) { + // eslint-disable-next-line no-console + console.log( error ); + } this.setState( { - formats: merge( {}, newFormats ), - selectedNodeId: this.state.selectedNodeId + 1, + formatPlaceholder: record.formatPlaceholder, } ); + if ( newContent && newContent !== this.props.value ) { + this.props.onChange( newContent ); + } else { + // make sure the component rerenders without refreshing the text on gutenberg + // (this can trigger other events that might update the active formats on aztec) + this.lastEventCount = 0; + this.forceUpdate(); + } } /* @@ -186,17 +196,14 @@ export class RichText extends Component { return html.replace( openingTagRegexp, '' ).replace( closingTagRegexp, '' ); } - /** + /* * Handles any case where the content of the AztecRN instance has changed */ - onChange( event ) { this.lastEventCount = event.nativeEvent.eventCount; - const contentWithoutRootTag = this.removeRootTagsProduceByAztec( event.nativeEvent.text ); + const contentWithoutRootTag = this.removeRootTagsProduceByAztec( unescapeSpaces( event.nativeEvent.text ) ); this.lastContent = contentWithoutRootTag; - this.props.onChange( { - content: this.lastContent, - } ); + this.props.onChange( this.lastContent ); } /** @@ -207,18 +214,18 @@ export class RichText extends Component { const contentHeight = contentSize.height; this.props.onContentSizeChange( { aztecHeight: contentHeight, - } - ); + } ); } // eslint-disable-next-line no-unused-vars onEnter( event ) { + this.lastEventCount = event.nativeEvent.eventCount; if ( ! this.props.onSplit ) { // TODO: insert the \n char instead? return; } - this.splitContent( event.nativeEvent.text, event.nativeEvent.selectionStart, event.nativeEvent.selectionEnd ); + this.splitContent( unescapeSpaces( event.nativeEvent.text ), event.nativeEvent.selectionStart, event.nativeEvent.selectionEnd ); } // eslint-disable-next-line no-unused-vars @@ -246,6 +253,32 @@ export class RichText extends Component { } } + onSelectionChange( start, end, text, event ) { + // `end` can be less than `start` on iOS + // Let's fix that here so `rich-text/slice` can work properly + const realStart = Math.min( start, end ); + const realEnd = Math.max( start, end ); + const noChange = this.state.start === start && this.state.end === end; + const isTyping = this.state.start + 1 === realStart; + const shouldKeepFormats = noChange || isTyping; + // update format placeholder to continue writing in the current format + // or set it to null if user jumped to another part in the text + const formatPlaceholder = shouldKeepFormats && this.state.formatPlaceholder ? { + ...this.state.formatPlaceholder, + index: realStart, + } : null; + this.setState( { + start: realStart, + end: realEnd, + formatPlaceholder, + } ); + this.lastEventCount = event.nativeEvent.eventCount; + // we don't want to refresh aztec as no content can have changed from this event + // let's update lastContent to prevent that in shouldComponentUpdate + this.lastContent = this.removeRootTagsProduceByAztec( unescapeSpaces( text ) ); + this.props.onChange( this.lastContent ); + } + isEmpty() { return isEmpty( this.formatToValue( this.props.value ) ); } @@ -276,7 +309,7 @@ export class RichText extends Component { } shouldComponentUpdate( nextProps ) { - if ( nextProps.tagName !== this.props.tagName ) { + if ( nextProps.tagName !== this.props.tagName || nextProps.isSelected !== this.props.isSelected ) { this.lastEventCount = undefined; this.lastContent = undefined; return true; @@ -316,61 +349,18 @@ export class RichText extends Component { } } - isFormatActive( format ) { - return this.state.formats[ format ] && this.state.formats[ format ].isActive; - } - - // eslint-disable-next-line no-unused-vars - removeFormat( format ) { - this._editor.applyFormat( format ); - } - - // eslint-disable-next-line no-unused-vars - applyFormat( format, args, node ) { - this._editor.applyFormat( format ); - } - - changeFormats( formats ) { - const newStateFormats = {}; - forEach( formats, ( formatValue, format ) => { - newStateFormats[ format ] = getFormatValue( format ); - const isActive = this.isFormatActive( format ); - if ( isActive && ! formatValue ) { - this.removeFormat( format ); - } else if ( ! isActive && formatValue ) { - this.applyFormat( format ); - } - } ); - - this.setState( ( state ) => ( { - formats: merge( {}, state.formats, newStateFormats ), - } ) ); - } - - toggleFormat( format ) { - return () => this.changeFormats( { - [ format ]: ! this.state.formats[ format ], - } ); - } - render() { const { tagName, style, formattingControls, - value, + isSelected, } = this.props; - const toolbarControls = FORMATTING_CONTROLS - .filter( ( control ) => formattingControls.indexOf( control.format ) !== -1 ) - .map( ( control ) => ( { - ...control, - onClick: this.toggleFormat( control.format ), - isActive: this.isFormatActive( control.format ), - } ) ); - + const record = this.getRecord(); // Save back to HTML from React tree - let html = '<' + tagName + '>' + value + '</' + tagName + '>'; + const value = this.valueToFormat( record ); + let html = `<${ tagName }>${ value }</${ tagName }>`; // We need to check if the value is undefined or empty, and then assign it properly otherwise the placeholder is not visible if ( value === undefined || value === '' ) { html = ''; @@ -379,9 +369,11 @@ export class RichText extends Component { return ( <View> - <BlockFormatControls> - <Toolbar controls={ toolbarControls } /> - </BlockFormatControls> + { isSelected && ( + <BlockFormatControls> + <FormatToolbar controls={ formattingControls } /> + </BlockFormatControls> + ) } <RCTAztecView ref={ ( ref ) => { this._editor = ref; @@ -389,8 +381,7 @@ export class RichText extends Component { if ( this.props.setRef ) { this.props.setRef( ref ); } - } - } + } } text={ { text: html, eventCount: this.lastEventCount } } placeholder={ this.props.placeholder } placeholderTextColor={ this.props.placeholderTextColor || 'lightgrey' } @@ -399,10 +390,11 @@ export class RichText extends Component { onBlur={ this.props.onBlur } onEnter={ this.onEnter } onBackspace={ this.onBackspace } + activeFormats={ this.getActiveFormatNames( record ) } onContentSizeChange={ this.onContentSizeChange } - onActiveFormatsChange={ this.onActiveFormatsChange } onCaretVerticalPositionChange={ this.props.onCaretVerticalPositionChange } - isSelected={ this.props.isSelected } + onSelectionChange={ this.onSelectionChange } + isSelected={ isSelected } blockType={ { tag: tagName } } color={ 'black' } maxImagesWidth={ 200 } @@ -412,18 +404,26 @@ export class RichText extends Component { fontWeight={ this.props.fontWeight } fontStyle={ this.props.fontStyle } /> + { isSelected && <FormatEdit value={ record } onChange={ this.onFormatChange } /> } </View> ); } } RichText.defaultProps = { - formattingControls: FORMATTING_CONTROLS.map( ( { format } ) => format ), + formattingControls: [ 'bold', 'italic', 'link', 'strikethrough' ], format: 'string', }; const RichTextContainer = compose( [ withInstanceId, + withSelect( ( select ) => { + const { getFormatTypes } = select( 'core/rich-text' ); + + return { + formatTypes: getFormatTypes(), + }; + } ), ] )( RichText ); RichTextContainer.Content = ( { value, format, tagName: Tag, ...props } ) => { @@ -448,3 +448,5 @@ RichTextContainer.Content.defaultProps = { }; export default RichTextContainer; +export { RichTextShortcut } from './shortcut'; +export { RichTextToolbarButton } from './toolbar-button'; diff --git a/packages/editor/src/components/rich-text/shortcut.native.js b/packages/editor/src/components/rich-text/shortcut.native.js new file mode 100644 index 0000000000000..61a62170f2768 --- /dev/null +++ b/packages/editor/src/components/rich-text/shortcut.native.js @@ -0,0 +1,10 @@ +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; + +export class RichTextShortcut extends Component { + render() { + return null; + } +} diff --git a/packages/editor/src/components/url-input/index.native.js b/packages/editor/src/components/url-input/index.native.js new file mode 100644 index 0000000000000..a76252cadff39 --- /dev/null +++ b/packages/editor/src/components/url-input/index.native.js @@ -0,0 +1,35 @@ +/** + * External dependencies + */ +import { TextInput } from 'react-native'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Component } from '@wordpress/element'; +import { withInstanceId } from '@wordpress/compose'; + +class URLInput extends Component { + render() { + const { value = '', autoFocus = true, ...extraProps } = this.props; + /* eslint-disable jsx-a11y/no-autofocus */ + return ( + <TextInput + autoFocus={ autoFocus } + editable + selectTextOnFocus + autoCapitalize="none" + autoCorrect={ false } + textContentType="URL" + value={ value } + onChangeText={ this.props.onChange } + placeholder={ __( 'Paste URL' ) } + { ...extraProps } + /> + ); + /* eslint-enable jsx-a11y/no-autofocus */ + } +} + +export default withInstanceId( URLInput ); diff --git a/packages/format-library/src/default-formats.js b/packages/format-library/src/default-formats.js new file mode 100644 index 0000000000000..46d8e4b851e4a --- /dev/null +++ b/packages/format-library/src/default-formats.js @@ -0,0 +1,18 @@ +/** + * Internal dependencies + */ +import { bold } from './bold'; +import { code } from './code'; +import { image } from './image'; +import { italic } from './italic'; +import { link } from './link'; +import { strikethrough } from './strikethrough'; + +export default [ + bold, + code, + image, + italic, + link, + strikethrough, +]; diff --git a/packages/format-library/src/default-formats.native.js b/packages/format-library/src/default-formats.native.js new file mode 100644 index 0000000000000..30af1e7af981c --- /dev/null +++ b/packages/format-library/src/default-formats.native.js @@ -0,0 +1,16 @@ +/** + * Internal dependencies + */ +import { bold } from './bold'; +import { code } from './code'; +import { italic } from './italic'; +import { link } from './link'; +import { strikethrough } from './strikethrough'; + +export default [ + bold, + code, + italic, + link, + strikethrough, +]; diff --git a/packages/format-library/src/index.js b/packages/format-library/src/index.js index 05adec8abf40a..d3341b31315ee 100644 --- a/packages/format-library/src/index.js +++ b/packages/format-library/src/index.js @@ -1,14 +1,3 @@ -/** - * Internal dependencies - */ -import { bold } from './bold'; -import { code } from './code'; -import { image } from './image'; -import { italic } from './italic'; -import { link } from './link'; -import { strikethrough } from './strikethrough'; -import { underline } from './underline'; - /** * WordPress dependencies */ @@ -16,12 +5,9 @@ import { registerFormatType, } from '@wordpress/rich-text'; -[ - bold, - code, - image, - italic, - link, - strikethrough, - underline, -].forEach( ( { name, ...settings } ) => registerFormatType( name, settings ) ); +/** + * Internal dependencies + */ +import formats from './default-formats'; + +formats.forEach( ( { name, ...settings } ) => registerFormatType( name, settings ) ); diff --git a/packages/format-library/src/link/button.native.js b/packages/format-library/src/link/button.native.js new file mode 100644 index 0000000000000..fa8fd004c5a37 --- /dev/null +++ b/packages/format-library/src/link/button.native.js @@ -0,0 +1,24 @@ +/** + * External dependencies + */ +import { TouchableOpacity, View } from 'react-native'; + +export default function Button( props ) { + const { + children, + onClick, + disabled, + } = props; + + return ( + <TouchableOpacity + accessible={ true } + onPress={ onClick } + disabled={ disabled } + > + <View style={ { flexDirection: 'row' } }> + { children } + </View> + </TouchableOpacity> + ); +} diff --git a/packages/format-library/src/link/index.native.js b/packages/format-library/src/link/index.native.js new file mode 100644 index 0000000000000..4a2def1614323 --- /dev/null +++ b/packages/format-library/src/link/index.native.js @@ -0,0 +1,134 @@ +/** + * External dependencies + */ +import { find } from 'lodash'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Component, Fragment } from '@wordpress/element'; +import { withSpokenMessages } from '@wordpress/components'; +import { RichTextToolbarButton } from '@wordpress/editor'; +import { + applyFormat, + getActiveFormat, + getTextContent, + isCollapsed, + removeFormat, + slice, +} from '@wordpress/rich-text'; +import { isURL } from '@wordpress/url'; + +/** + * Internal dependencies + */ +import ModalLinkUI from './modal'; + +const name = 'core/link'; + +export const link = { + name, + title: __( 'Link' ), + tagName: 'a', + className: null, + attributes: { + url: 'href', + target: 'target', + }, + edit: withSpokenMessages( class LinkEdit extends Component { + constructor() { + super( ...arguments ); + + this.addLink = this.addLink.bind( this ); + this.stopAddingLink = this.stopAddingLink.bind( this ); + this.onRemoveFormat = this.onRemoveFormat.bind( this ); + this.state = { + addingLink: false, + }; + } + + addLink() { + const { value, onChange } = this.props; + const text = getTextContent( slice( value ) ); + + if ( text && isURL( text ) ) { + onChange( applyFormat( value, { type: name, attributes: { url: text } } ) ); + } else { + this.setState( { addingLink: true } ); + } + } + + stopAddingLink() { + this.setState( { addingLink: false } ); + } + + getLinkSelection() { + const { value, isActive } = this.props; + const startFormat = getActiveFormat( value, 'core/link' ); + + // if the link isn't selected, get the link manually by looking around the cursor + // TODO: handle partly selected links + if ( startFormat && isCollapsed( value ) && isActive ) { + let startIndex = value.start; + let endIndex = value.end; + + while ( find( value.formats[ startIndex ], startFormat ) ) { + startIndex--; + } + + endIndex++; + + while ( find( value.formats[ endIndex ], startFormat ) ) { + endIndex++; + } + + return { + ...value, + start: startIndex + 1, + end: endIndex, + }; + } + + return value; + } + + onRemoveFormat() { + const { onChange, speak } = this.props; + const linkSelection = this.getLinkSelection(); + + onChange( removeFormat( linkSelection, name ) ); + speak( __( 'Link removed.' ), 'assertive' ); + } + + render() { + const { isActive, activeAttributes, onChange } = this.props; + const linkSelection = this.getLinkSelection(); + + return ( + <Fragment> + { this.state.addingLink && + <ModalLinkUI + isVisible + isActive={ isActive } + activeAttributes={ activeAttributes } + onClose={ this.stopAddingLink } + onChange={ onChange } + onRemove={ this.onRemoveFormat } + value={ linkSelection } + /> + } + <RichTextToolbarButton + name="link" + icon="admin-links" + title={ __( 'Link' ) } + onClick={ this.addLink } + isActive={ isActive } + shortcutType="primary" + shortcutCharacter="k" + /> + </Fragment> + ); + } + } ), +}; diff --git a/packages/format-library/src/link/inline.js b/packages/format-library/src/link/inline.js index 2d79e11735f1c..c700ad2ef3dc5 100644 --- a/packages/format-library/src/link/inline.js +++ b/packages/format-library/src/link/inline.js @@ -6,7 +6,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { sprintf, __ } from '@wordpress/i18n'; +import { __ } from '@wordpress/i18n'; import { Component, createRef } from '@wordpress/element'; import { ExternalLink, @@ -30,39 +30,10 @@ import { URLInput, URLPopover } from '@wordpress/editor'; /** * Internal dependencies */ -import { isValidHref } from './utils'; +import { createLinkFormat, isValidHref } from './utils'; const stopKeyPropagation = ( event ) => event.stopPropagation(); -/** - * Generates the format object that will be applied to the link text. - * - * @param {string} url The href of the link. - * @param {boolean} opensInNewWindow Whether this link will open in a new window. - * @param {Object} text The text that is being hyperlinked. - * - * @return {Object} The final format object. - */ -function createLinkFormat( { url, opensInNewWindow, text } ) { - const format = { - type: 'core/link', - attributes: { - url, - }, - }; - - if ( opensInNewWindow ) { - // translators: accessibility label for external links, where the argument is the link text - const label = sprintf( __( '%s (opens in a new tab)' ), text ); - - format.attributes.target = '_blank'; - format.attributes.rel = 'noreferrer noopener'; - format.attributes[ 'aria-label' ] = label; - } - - return format; -} - function isShowingInput( props, state ) { return props.addingLink || state.editLink; } diff --git a/packages/format-library/src/link/modal.native.js b/packages/format-library/src/link/modal.native.js new file mode 100644 index 0000000000000..6736b4a707945 --- /dev/null +++ b/packages/format-library/src/link/modal.native.js @@ -0,0 +1,175 @@ +/** + * External dependencies + */ +import React from 'react'; +import { Switch, Text, TextInput, View } from 'react-native'; +import Modal from 'react-native-modal'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Component } from '@wordpress/element'; +import { URLInput } from '@wordpress/editor'; +import { prependHTTP } from '@wordpress/url'; +import { + withSpokenMessages, +} from '@wordpress/components'; +import { + create, + insert, + isCollapsed, + applyFormat, + getTextContent, + slice, +} from '@wordpress/rich-text'; + +/** + * Internal dependencies + */ +import { createLinkFormat, isValidHref } from './utils'; +import Button from './button'; + +import styles from './modal.scss'; + +class ModalLinkUI extends Component { + constructor( props ) { + super( ...arguments ); + + this.submitLink = this.submitLink.bind( this ); + this.onChangeInputValue = this.onChangeInputValue.bind( this ); + this.onChangeText = this.onChangeText.bind( this ); + this.onChangeOpensInNewWindow = this.onChangeOpensInNewWindow.bind( this ); + this.removeLink = this.removeLink.bind( this ); + + this.state = { + inputValue: props.activeAttributes.url || '', + text: getTextContent( slice( props.value ) ), + opensInNewWindow: false, + }; + } + + onChangeInputValue( inputValue ) { + this.setState( { inputValue } ); + } + + onChangeText( text ) { + this.setState( { text } ); + } + + onChangeOpensInNewWindow( opensInNewWindow ) { + this.setState( { opensInNewWindow } ); + } + + submitLink() { + const { isActive, onChange, speak, value } = this.props; + const { inputValue, opensInNewWindow, text } = this.state; + const url = prependHTTP( inputValue ); + const linkText = text || inputValue; + const format = createLinkFormat( { + url, + opensInNewWindow, + text: linkText, + } ); + const placeholderFormats = ( value.formatPlaceholder && value.formatPlaceholder.formats ) || []; + + if ( isCollapsed( value ) && ! isActive ) { // insert link + const toInsert = applyFormat( create( { text: linkText } ), [ ...placeholderFormats, format ], 0, text.length ); + onChange( insert( value, toInsert ) ); + } else if ( text !== getTextContent( slice( value ) ) ) { // edit text in selected link + const toInsert = applyFormat( create( { text } ), [ ...placeholderFormats, format ], 0, text.length ); + onChange( insert( value, toInsert, value.start, value.end ) ); + } else { // transform selected text into link + onChange( applyFormat( value, [ ...placeholderFormats, format ] ) ); + } + + if ( ! isValidHref( url ) ) { + speak( __( 'Warning: the link has been inserted but may have errors. Please test it.' ), 'assertive' ); + } else if ( isActive ) { + speak( __( 'Link edited.' ), 'assertive' ); + } else { + speak( __( 'Link inserted' ), 'assertive' ); + } + + this.props.onClose(); + } + + removeLink() { + this.props.onRemove(); + this.props.onClose(); + } + + render() { + const { isVisible } = this.props; + + return ( + <Modal + isVisible={ isVisible } + style={ styles.bottomModal } + animationInTiming={ 500 } + animationOutTiming={ 500 } + backdropTransitionInTiming={ 500 } + backdropTransitionOutTiming={ 500 } + onBackdropPress={ this.props.onClose } + onSwipe={ this.props.onClose } + swipeDirection="down" + avoidKeyboard={ true } + > + <View style={ { ...styles.content, borderColor: 'rgba(0, 0, 0, 0.1)' } }> + <View style={ styles.dragIndicator } /> + <View style={ styles.head }> + <Button onClick={ this.removeLink }> + <Text style={ { ...styles.buttonText, color: 'red' } }> + { __( 'Remove' ) } + </Text> + </Button> + <Text style={ styles.title }> + { __( 'Link Settings' ) } + </Text> + <Button onClick={ this.submitLink }> + <Text style={ { ...styles.buttonText, color: '#0087be' } } > + { __( 'Done' ) } + </Text> + </Button> + </View> + <View style={ styles.separator } /> + <View style={ styles.inlineInput }> + <Text style={ styles.inlineInputLabel }> + { __( 'URL' ) } + </Text> + <URLInput + style={ styles.inlineInputValue } + value={ this.state.inputValue } + onChange={ this.onChangeInputValue } + /> + </View> + <View style={ styles.separator } /> + <View style={ styles.inlineInput }> + <Text style={ styles.inlineInputLabel }> + { __( 'Link Text' ) } + </Text> + <TextInput + style={ styles.inlineInputValue } + value={ this.state.text } + onChangeText={ this.onChangeText } + /> + </View> + <View style={ styles.separator } /> + <View style={ styles.inlineInput }> + <Text style={ styles.inlineInputLabel }> + { __( 'Open in a new window' ) } + </Text> + <View style={ { ...styles.inlineInputValue, ...styles.inlineInputValueSwitch, alignItems: 'flex-end' } }> + <Switch + value={ this.state.opensInNewWindow } + onValueChange={ this.onChangeOpensInNewWindow } + /> + </View> + </View> + </View> + </Modal> + ); + } +} + +export default withSpokenMessages( ModalLinkUI ); diff --git a/packages/format-library/src/link/modal.native.scss b/packages/format-library/src/link/modal.native.scss new file mode 100644 index 0000000000000..72f6a647b2396 --- /dev/null +++ b/packages/format-library/src/link/modal.native.scss @@ -0,0 +1,80 @@ + +.bottomModal { + justify-content: flex-end; + margin: 0; +} + +.dragIndicator { + background-color: $light-gray-400; + height: 4px; + width: 10%; + top: -12px; + margin: auto; + border-radius: 2px; +} + +.separator { + background-color: $light-gray-400; + height: 1px; + width: 95%; + margin: auto; +} + +.content { + background-color: $white; + padding: 18px 10px 5px 10px; + justify-content: center; + border-top-right-radius: 8px; + border-top-left-radius: 8px; +} + +.head { + flex-direction: row; + width: 100%; + margin-bottom: 5px; + justify-content: space-between; + align-items: center; + align-content: center; +} + +.title { + color: $dark-gray-600; + font-size: 18px; + font-weight: 600; + flex: 1; + text-align: center; +} + +.buttonText { + font-size: 18px; + padding: 5px; +} + +.inlineInput { + flex-direction: row; + width: 100%; + justify-content: space-between; + align-items: center; + margin: 5px 0; +} + +.inlineInputLabel { + padding: 10px 10px; + color: $dark-gray-600; + font-size: 14px; + font-weight: bold; +} + +.inlineInputValue { + flex-grow: 1; + font-size: 14px; + text-align: right; + align-items: stretch; + align-self: flex-end; + padding: 10px; +} + +.inlineInputValueSwitch { + padding: 5px; +} + diff --git a/packages/format-library/src/link/utils.js b/packages/format-library/src/link/utils.js index a516917886a95..d9115cbcfe099 100644 --- a/packages/format-library/src/link/utils.js +++ b/packages/format-library/src/link/utils.js @@ -18,6 +18,7 @@ import { getFragment, isValidFragment, } from '@wordpress/url'; +import { __, sprintf } from '@wordpress/i18n'; /** * Check for issues with the provided href. @@ -78,3 +79,32 @@ export function isValidHref( href ) { return true; } + +/** + * Generates the format object that will be applied to the link text. + * + * @param {string} url The href of the link. + * @param {boolean} opensInNewWindow Whether this link will open in a new window. + * @param {Object} text The text that is being hyperlinked. + * + * @return {Object} The final format object. + */ +export function createLinkFormat( { url, opensInNewWindow, text } ) { + const format = { + type: 'core/link', + attributes: { + url, + }, + }; + + if ( opensInNewWindow ) { + // translators: accessibility label for external links, where the argument is the link text + const label = sprintf( __( '%s (opens in a new tab)' ), text ); + + format.attributes.target = '_blank'; + format.attributes.rel = 'noreferrer noopener'; + format.attributes[ 'aria-label' ] = label; + } + + return format; +} diff --git a/packages/rich-text/src/apply-format.native.js b/packages/rich-text/src/apply-format.native.js new file mode 100644 index 0000000000000..9c5a4749ae928 --- /dev/null +++ b/packages/rich-text/src/apply-format.native.js @@ -0,0 +1,83 @@ +/** + * External dependencies + */ + +import { cloneDeep } from 'lodash'; + +/** + * Internal dependencies + */ + +import { normaliseFormats } from './normalise-formats'; + +/** + * Apply a format object to a Rich Text value from the given `startIndex` to the + * given `endIndex`. Indices are retrieved from the selection if none are + * provided. + * + * @param {Object} value Value to modify. + * @param {Object} formats Formats to apply. + * @param {number} startIndex Start index. + * @param {number} endIndex End index. + * + * @return {Object} A new value with the format applied. + */ +export function applyFormat( + { formats: currentFormats, formatPlaceholder, text, start, end }, + formats, + startIndex = start, + endIndex = end +) { + if ( ! Array.isArray( formats ) ) { + formats = [ formats ]; + } + + // The selection is collpased, insert a placeholder with the format so new input appears + // with the format applied. + if ( startIndex === endIndex ) { + const previousFormats = currentFormats[ startIndex - 1 ] || []; + const placeholderFormats = formatPlaceholder && formatPlaceholder.index === start && formatPlaceholder.formats; + // Follow the same logic as in getActiveFormat: placeholderFormats has priority over previousFormats + const activeFormats = ( placeholderFormats ? placeholderFormats : previousFormats ) || []; + return { + formats: currentFormats, + text, + start, + end, + formatPlaceholder: { + index: start, + formats: mergeFormats( activeFormats, formats ), + }, + }; + } + + const newFormats = currentFormats.slice( 0 ); + + for ( let index = startIndex; index < endIndex; index++ ) { + applyFormats( newFormats, index, formats ); + } + + return normaliseFormats( { formats: newFormats, text, start, end } ); +} + +function mergeFormats( formats1, formats2 ) { + const formatsOut = cloneDeep( formats1 ); + formats2.forEach( ( format2 ) => { + const format1In2 = formatsOut.find( ( format1 ) => format1.type === format2.type ); + // update properties while keeping the formats ordered + if ( format1In2 ) { + Object.assign( format1In2, format2 ); + } else { + formatsOut.push( cloneDeep( format2 ) ); + } + } ); + return formatsOut; +} + +function applyFormats( formats, index, newFormats ) { + if ( formats[ index ] ) { + formats[ index ] = mergeFormats( formats[ index ], newFormats ); + } else { + formats[ index ] = cloneDeep( newFormats ); + } +} diff --git a/packages/rich-text/src/get-active-format.native.js b/packages/rich-text/src/get-active-format.native.js new file mode 100644 index 0000000000000..152070fedf278 --- /dev/null +++ b/packages/rich-text/src/get-active-format.native.js @@ -0,0 +1,44 @@ +/** + * External dependencies + */ + +import { find } from 'lodash'; + +/** + * Gets the format object by type at the start of the selection. This can be + * used to get e.g. the URL of a link format at the current selection, but also + * to check if a format is active at the selection. Returns undefined if there + * is no format at the selection. + * + * @param {Object} value Value to inspect. + * @param {string} formatType Format type to look for. + * + * @return {?Object} Active format object of the specified type, or undefined. + */ +export function getActiveFormat( { formats, formatPlaceholder, start, end }, formatType ) { + if ( start === undefined ) { + return; + } + + // if selection is not empty, get the first character format + if ( start !== end ) { + return find( formats[ start ], { type: formatType } ); + } + + // if user picked (or unpicked) formats but didn't write anything in those formats yet return this format + if ( formatPlaceholder && formatPlaceholder.index === start ) { + return find( formatPlaceholder.formats, { type: formatType } ); + } + + // if we're at the start of text, use the first char to pick up the formats + const startPos = start === 0 ? 0 : start - 1; + + // otherwise get the previous character format + const previousLetterFormat = find( formats[ startPos ], { type: formatType } ); + + if ( previousLetterFormat ) { + return previousLetterFormat; + } + + return undefined; +} diff --git a/packages/rich-text/src/normalise-formats.native.js b/packages/rich-text/src/normalise-formats.native.js new file mode 100644 index 0000000000000..2a75e343a2c12 --- /dev/null +++ b/packages/rich-text/src/normalise-formats.native.js @@ -0,0 +1,36 @@ +/** + * Internal dependencies + */ + +import { isFormatEqual } from './is-format-equal'; + +/** + * Normalises formats: ensures subsequent equal formats have the same reference. + * + * @param {Object} value Value to normalise formats of. + * + * @return {Object} New value with normalised formats. + */ +export function normaliseFormats( { formats, formatPlaceholder, text, start, end } ) { + const newFormats = formats.slice( 0 ); + + newFormats.forEach( ( formatsAtIndex, index ) => { + const lastFormatsAtIndex = newFormats[ index - 1 ]; + + if ( lastFormatsAtIndex ) { + const newFormatsAtIndex = formatsAtIndex.slice( 0 ); + + newFormatsAtIndex.forEach( ( format, formatIndex ) => { + const lastFormat = lastFormatsAtIndex[ formatIndex ]; + + if ( isFormatEqual( format, lastFormat ) ) { + newFormatsAtIndex[ formatIndex ] = lastFormat; + } + } ); + + newFormats[ index ] = newFormatsAtIndex; + } + } ); + + return { formats: newFormats, formatPlaceholder, text, start, end }; +} diff --git a/packages/rich-text/src/remove-format.native.js b/packages/rich-text/src/remove-format.native.js new file mode 100644 index 0000000000000..b6213f813bb63 --- /dev/null +++ b/packages/rich-text/src/remove-format.native.js @@ -0,0 +1,69 @@ +/** + * External dependencies + */ + +import { cloneDeep } from 'lodash'; + +/** + * Internal dependencies + */ + +import { normaliseFormats } from './normalise-formats'; + +/** + * Remove any format object from a Rich Text value by type from the given + * `startIndex` to the given `endIndex`. Indices are retrieved from the + * selection if none are provided. + * + * @param {Object} value Value to modify. + * @param {string} formatType Format type to remove. + * @param {number} startIndex Start index. + * @param {number} endIndex End index. + * + * @return {Object} A new value with the format applied. + */ +export function removeFormat( + { formats, formatPlaceholder, text, start, end }, + formatType, + startIndex = start, + endIndex = end +) { + const newFormats = formats.slice( 0 ); + let newFormatPlaceholder = null; + + if ( start === end ) { + if ( formatPlaceholder && formatPlaceholder.index === start ) { + const placeholderFormats = ( formatPlaceholder.formats || [] ).slice( 0 ); + newFormatPlaceholder = { + ...formatPlaceholder, + // make sure we do not reuse the formats reference in our placeholder `formats` array + formats: cloneDeep( placeholderFormats.filter( ( { type } ) => type !== formatType ) ), + }; + } else if ( ! formatPlaceholder ) { + const previousFormat = ( start > 0 ? formats[ start - 1 ] : formats[ 0 ] ) || []; + newFormatPlaceholder = { + index: start, + formats: cloneDeep( previousFormat.filter( ( { type } ) => type !== formatType ) ), + }; + } + } + + // Do not remove format if selection is empty + for ( let i = startIndex; i < endIndex; i++ ) { + if ( newFormats[ i ] ) { + filterFormats( newFormats, i, formatType ); + } + } + + return normaliseFormats( { formats: newFormats, formatPlaceholder: newFormatPlaceholder, text, start, end } ); +} + +function filterFormats( formats, index, formatType ) { + const newFormats = formats[ index ].filter( ( { type } ) => type !== formatType ); + + if ( newFormats.length ) { + formats[ index ] = newFormats; + } else { + delete formats[ index ]; + } +} From f4b65e6f3d7b7557008a18863d571c51b1f3eacd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zoli=20Szab=C3=B3?= <zoliszabo@users.noreply.github.com> Date: Mon, 18 Feb 2019 17:05:36 +0200 Subject: [PATCH 464/691] Fixed typo in theme-support.md (#13920) --- docs/designers-developers/developers/themes/theme-support.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/designers-developers/developers/themes/theme-support.md b/docs/designers-developers/developers/themes/theme-support.md index 765483eadb798..57a2f748f641c 100644 --- a/docs/designers-developers/developers/themes/theme-support.md +++ b/docs/designers-developers/developers/themes/theme-support.md @@ -5,7 +5,7 @@ The new Blocks include baseline support in all themes, enhancements to opt-in to There are a few new concepts to consider when building themes: - **Editor Color Palette** - A default set of colors is provided, but themes can register their own and optionally lock users into picking from the defined palette. -- **Editor Text Size Palette** - A default set of sizes is provided, but themes and register their own and optionally lock users into picking from preselected sizes. +- **Editor Text Size Palette** - A default set of sizes is provided, but themes can register their own and optionally lock users into picking from preselected sizes. - **Responsive Embeds** - Themes must opt-in to responsive embeds. - **Frontend & Editor Styles** - To get the most out of blocks, theme authors will want to make sure Core styles look good and opt-in, or write their own styles to best fit their theme. - **Dark Mode** - If a Theme is a Dark Theme with a dark background containing light text, the theme author can opt-in to the Dark Mode. From e3e3b5327b1288a574f65153d31ed76f001eebeb Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Mon, 18 Feb 2019 17:41:17 +0100 Subject: [PATCH 465/691] Bootstrap the widgets page playground (#13912) --- .eslintrc.js | 4 ++ .github/CODEOWNERS | 3 ++ docs/manifest.json | 6 +++ gutenberg.php | 9 ++++ lib/client-assets.php | 8 ++++ lib/load.php | 1 + lib/packages-dependencies.php | 3 ++ lib/widgets-page.php | 35 +++++++++++++++ package-lock.json | 7 +++ package.json | 1 + packages/edit-widgets/.npmrc | 1 + packages/edit-widgets/CHANGELOG.md | 5 +++ packages/edit-widgets/README.md | 17 ++++++++ packages/edit-widgets/package.json | 29 +++++++++++++ packages/edit-widgets/src/index.js | 9 ++++ packages/edit-widgets/src/style.scss | 64 ++++++++++++++++++++++++++++ 16 files changed, 202 insertions(+) create mode 100644 lib/widgets-page.php create mode 100644 packages/edit-widgets/.npmrc create mode 100644 packages/edit-widgets/CHANGELOG.md create mode 100644 packages/edit-widgets/README.md create mode 100644 packages/edit-widgets/package.json create mode 100644 packages/edit-widgets/src/index.js create mode 100644 packages/edit-widgets/src/style.scss diff --git a/.eslintrc.js b/.eslintrc.js index 235fe1af10b07..5ce3aa251459b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -87,6 +87,10 @@ module.exports = { selector: 'ImportDeclaration[source.value=/^edit-post(\\u002F|$)/]', message: 'Use @wordpress/edit-post as import path instead.', }, + { + selector: 'ImportDeclaration[source.value=/^edit-widgets(\\u002F|$)/]', + message: 'Use @wordpress/edit-widgets as import path instead.', + }, { selector: 'ImportDeclaration[source.value=/^viewport(\\u002F|$)/]', message: 'Use @wordpress/viewport as import path instead.', diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ddc0dfec2b7df..18c7ac39ce1be 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -18,6 +18,9 @@ /packages/list-reusable-blocks @youknowriad @aduth @noisysocks /packages/shortcode @youknowriad @aduth +# Widgets +/packages/edit-widgets @youknowriad + # Tooling /bin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra /packages/babel-plugin-import-jsx-pragma @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra diff --git a/docs/manifest.json b/docs/manifest.json index 038b93e03f25a..026790426f1ec 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -659,6 +659,12 @@ "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/edit-post/README.md", "parent": "packages" }, + { + "title": "@wordpress/edit-widgets", + "slug": "packages-edit-widgets", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/edit-widgets/README.md", + "parent": "packages" + }, { "title": "@wordpress/editor", "slug": "packages-editor", diff --git a/gutenberg.php b/gutenberg.php index f1364f2860b64..216566261a2ab 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -76,6 +76,15 @@ function gutenberg_menu() { 'gutenberg' ); + add_submenu_page( + 'gutenberg', + __( 'Widgets (beta)', 'gutenberg' ), + __( 'Widgets (beta)', 'gutenberg' ), + 'edit_theme_options', + 'gutenberg-widgets', + 'the_gutenberg_widgets' + ); + if ( current_user_can( 'edit_posts' ) ) { $submenu['gutenberg'][] = array( __( 'Support', 'gutenberg' ), diff --git a/lib/client-assets.php b/lib/client-assets.php index 3030459fbfce8..b34e428425e80 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -486,6 +486,14 @@ function gutenberg_register_scripts_and_styles() { ); wp_style_add_data( 'wp-list-reusable-block', 'rtl', 'replace' ); + gutenberg_override_style( + 'wp-edit-widgets', + gutenberg_url( 'build/edit-widgets/style.css' ), + array(), + filemtime( gutenberg_dir_path() . 'build/edit-widgets/style.css' ) + ); + wp_style_add_data( 'wp-edit-widgets', 'rtl', 'replace' ); + if ( defined( 'GUTENBERG_LIVE_RELOAD' ) && GUTENBERG_LIVE_RELOAD ) { $live_reload_url = ( GUTENBERG_LIVE_RELOAD === true ) ? 'http://localhost:35729/livereload.js' : GUTENBERG_LIVE_RELOAD; diff --git a/lib/load.php b/lib/load.php index 2027600792886..580f9852b53a5 100644 --- a/lib/load.php +++ b/lib/load.php @@ -23,6 +23,7 @@ require dirname( __FILE__ ) . '/i18n.php'; require dirname( __FILE__ ) . '/register.php'; require dirname( __FILE__ ) . '/demo.php'; +require dirname( __FILE__ ) . '/widgets-page.php'; // Register server-side code for individual blocks. if ( ! function_exists( 'render_block_core_archives' ) ) { diff --git a/lib/packages-dependencies.php b/lib/packages-dependencies.php index c559fce161807..9da875e8df2cc 100644 --- a/lib/packages-dependencies.php +++ b/lib/packages-dependencies.php @@ -130,6 +130,9 @@ 'wp-url', 'wp-viewport', ), + 'wp-edit-widgets' => array( + 'wp-element', + ), 'wp-editor' => array( 'lodash', 'wp-a11y', diff --git a/lib/widgets-page.php b/lib/widgets-page.php new file mode 100644 index 0000000000000..82eff09001f45 --- /dev/null +++ b/lib/widgets-page.php @@ -0,0 +1,35 @@ +<?php +/** + * Bootstraping the Gutenberg widgets page. + * + * @package gutenberg + */ + +/** + * The main entry point for the Gutenberg widgets page. + * + * @since 5.2.0 + */ +function the_gutenberg_widgets() { + ?> + <div class="blocks-widgets-container"> + </div> + <?php +} + +/** + * Initialize the Gutenberg widgets page. + * + * @since 5.2.0 + * + * @param string $hook Page. + */ +function gutenberg_widgets_init( $hook ) { + if ( 'gutenberg_page_gutenberg-widgets' !== $hook ) { + return; + } + + wp_enqueue_script( 'wp-edit-widgets' ); + wp_enqueue_style( 'wp-edit-widgets' ); +} +add_action( 'admin_enqueue_scripts', 'gutenberg_widgets_init' ); diff --git a/package-lock.json b/package-lock.json index e7c688511be70..b5442e8113a32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2746,6 +2746,13 @@ "refx": "^3.0.0" } }, + "@wordpress/edit-widgets": { + "version": "file:packages/edit-widgets", + "requires": { + "@babel/runtime": "^7.0.0", + "@wordpress/element": "file:packages/element" + } + }, "@wordpress/editor": { "version": "file:packages/editor", "requires": { diff --git a/package.json b/package.json index 119dcd7e3ba26..1b369596bdf06 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "@wordpress/dom": "file:packages/dom", "@wordpress/dom-ready": "file:packages/dom-ready", "@wordpress/edit-post": "file:packages/edit-post", + "@wordpress/edit-widgets": "file:packages/edit-widgets", "@wordpress/editor": "file:packages/editor", "@wordpress/element": "file:packages/element", "@wordpress/escape-html": "file:packages/escape-html", diff --git a/packages/edit-widgets/.npmrc b/packages/edit-widgets/.npmrc new file mode 100644 index 0000000000000..43c97e719a5a8 --- /dev/null +++ b/packages/edit-widgets/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/packages/edit-widgets/CHANGELOG.md b/packages/edit-widgets/CHANGELOG.md new file mode 100644 index 0000000000000..6718bf307cadd --- /dev/null +++ b/packages/edit-widgets/CHANGELOG.md @@ -0,0 +1,5 @@ +## 1.0.0 (Unreleased) + +### New Features + + - Initial version of the module. diff --git a/packages/edit-widgets/README.md b/packages/edit-widgets/README.md new file mode 100644 index 0000000000000..606d52cf89f57 --- /dev/null +++ b/packages/edit-widgets/README.md @@ -0,0 +1,17 @@ +# Edit Widgets + +Widgets Page Module for WordPress. + +> This package is meant to be used only with WordPress core. Feel free to use it in your own project but please keep in mind that it might never get fully documented. + +## Installation + +Install the module + +```bash +npm install @wordpress/edit-widgets +``` + +_This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. Learn more about it in [Babel docs](https://babeljs.io/docs/en/next/caveats)._ + +<br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/edit-widgets/package.json b/packages/edit-widgets/package.json new file mode 100644 index 0000000000000..a308d8cd6035f --- /dev/null +++ b/packages/edit-widgets/package.json @@ -0,0 +1,29 @@ +{ + "name": "@wordpress/edit-widgets", + "version": "0.0.1-alpha.0", + "private": true, + "description": "Widgets Page module for WordPress..", + "author": "The WordPress Contributors", + "license": "GPL-2.0-or-later", + "keywords": [ + "wordpress" + ], + "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/edit-widgets/README.md", + "repository": { + "type": "git", + "url": "https://github.com/WordPress/gutenberg.git" + }, + "bugs": { + "url": "https://github.com/WordPress/gutenberg/issues" + }, + "main": "build/index.js", + "module": "build-module/index.js", + "react-native": "src/index", + "dependencies": { + "@babel/runtime": "^7.0.0", + "@wordpress/element": "file:../element" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/edit-widgets/src/index.js b/packages/edit-widgets/src/index.js new file mode 100644 index 0000000000000..ef8ed74e9da63 --- /dev/null +++ b/packages/edit-widgets/src/index.js @@ -0,0 +1,9 @@ +/** + * WordPress dependencies + */ +import { render } from '@wordpress/element'; + +render( + <h1>Widgets (beta)</h1>, + document.querySelector( '.blocks-widgets-container' ) +); diff --git a/packages/edit-widgets/src/style.scss b/packages/edit-widgets/src/style.scss new file mode 100644 index 0000000000000..2c013c9a2d5ae --- /dev/null +++ b/packages/edit-widgets/src/style.scss @@ -0,0 +1,64 @@ + +// In order to use mix-blend-mode, this element needs to have an explicitly set background-color +// We scope it to .wp-toolbar to be wp-admin only, to prevent bleed into other implementations +html.wp-toolbar { + background: $white; +} + +body.gutenberg_page_gutenberg-widgets { + background: $white; + + #wpcontent { + padding-left: 0; + } + + #wpbody-content { + padding-bottom: 0; + } + + /* We hide legacy notices in Gutenberg, because they were not designed in a way that scaled well. + Plugins can use Gutenberg notices if they need to pass on information to the user when they are editing. */ + #wpbody-content > div:not(.blocks-widgets-container):not(#screen-meta) { + display: none; + } + + #wpfooter { + display: none; + } + + .a11y-speak-region { + left: -1px; + top: -1px; + } + + ul#adminmenu a.wp-has-current-submenu::after, + ul#adminmenu > li.current > a.current::after { + border-right-color: $white; + } + + .media-frame select.attachment-filters:last-of-type { + width: auto; + max-width: 100%; + } +} + +.blocks-widgets-container, +// The modals are shown outside the .blocks-widgets-container wrapper, they need these styles +.components-modal__frame { + box-sizing: border-box; + + *, + *::before, + *::after { + box-sizing: inherit; + } + + select { + font-size: $default-font-size; + color: $dark-gray-500; + } +} + +.blocks-widgets-container { + padding: 0 10px; +} From c17239fb5f5a09eae863880dc452a1b045539300 Mon Sep 17 00:00:00 2001 From: Stefanos Togoulidis <stefanostogoulidis@gmail.com> Date: Mon, 18 Feb 2019 20:45:45 +0200 Subject: [PATCH 466/691] Don't do anything in RichTextInputEvent on native mobile (#13929) --- packages/editor/src/components/index.native.js | 1 + .../editor/src/components/rich-text/index.native.js | 1 + .../src/components/rich-text/input-event.native.js | 10 ++++++++++ 3 files changed, 12 insertions(+) create mode 100644 packages/editor/src/components/rich-text/input-event.native.js diff --git a/packages/editor/src/components/index.native.js b/packages/editor/src/components/index.native.js index 5581015fa1d16..cd974fd2dfea1 100644 --- a/packages/editor/src/components/index.native.js +++ b/packages/editor/src/components/index.native.js @@ -5,6 +5,7 @@ export { default as RichText, RichTextShortcut, RichTextToolbarButton, + RichTextInputEvent, } from './rich-text'; export { default as MediaPlaceholder } from './media-placeholder'; export { default as BlockFormatControls } from './block-format-controls'; diff --git a/packages/editor/src/components/rich-text/index.native.js b/packages/editor/src/components/rich-text/index.native.js index 72e7c70a0a300..a97c37f248e05 100644 --- a/packages/editor/src/components/rich-text/index.native.js +++ b/packages/editor/src/components/rich-text/index.native.js @@ -450,3 +450,4 @@ RichTextContainer.Content.defaultProps = { export default RichTextContainer; export { RichTextShortcut } from './shortcut'; export { RichTextToolbarButton } from './toolbar-button'; +export { RichTextInputEvent } from './input-event'; diff --git a/packages/editor/src/components/rich-text/input-event.native.js b/packages/editor/src/components/rich-text/input-event.native.js new file mode 100644 index 0000000000000..71f2ce4797e24 --- /dev/null +++ b/packages/editor/src/components/rich-text/input-event.native.js @@ -0,0 +1,10 @@ +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; + +export class RichTextInputEvent extends Component { + render() { + return null; + } +} From 0f84a81d479b0ab22e3ed1ed65bc99e8bc57202c Mon Sep 17 00:00:00 2001 From: etoledom <etoledom@icloud.com> Date: Mon, 18 Feb 2019 20:18:11 +0100 Subject: [PATCH 467/691] Mobile: BottomSheet design tweaks v2 (#13855) * Mobile BottomSheet: Added the posibility of selecting the cell separator style. * Mobile BottomSheet: Increased space between top and table * Mobile BottomSheet: Truncating long values at the middle of the string. * Fix lint issues * Fix syntax error to pass CI * Fix lint issue * Mobile Picker: Tweak Android styles. * Mobile BottomSheet: Simplified Android cell styling. * Mobile: Fix swipe to dismiss on BottomSheet * Fix lint issues * Mobile BottomSheet: Fixes sensibility of pan vs tap gestures on swipe to dismiss. * Fix lint issues * Fixed failed tests * Mobile BottomSheet cell: Removed unnecessary variable init. * Fixed set state after component is unmounted * Revert "Fixed set state after component is unmounted" This reverts commit 787df7306ae32acd8b072fd09f475af55f1fea5f. --- .../block-library/src/image/edit.native.js | 7 +- .../mobile/bottom-sheet/cell.native.js | 184 +++++++++++------- .../bottom-sheet/cellStyles.android.scss | 7 + .../mobile/bottom-sheet/cellStyles.ios.scss | 7 + .../mobile/bottom-sheet/index.native.js | 23 ++- .../mobile/bottom-sheet/styles.native.scss | 12 +- .../components/mobile/picker/index.android.js | 4 +- .../mobile/picker/styles.android.scss | 10 - 8 files changed, 165 insertions(+), 89 deletions(-) create mode 100644 packages/editor/src/components/mobile/bottom-sheet/cellStyles.android.scss create mode 100644 packages/editor/src/components/mobile/bottom-sheet/cellStyles.ios.scss delete mode 100644 packages/editor/src/components/mobile/picker/styles.android.scss diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index 7e2d097d2498d..73b3eba1ab5fa 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -193,9 +193,9 @@ class ImageEdit extends React.Component { getMediaOptionsItems() { return [ - { icon: 'wordpress-alt', value: MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_CHOOSE_FROM_DEVICE, label: __( 'Choose from device' ) }, + { icon: 'format-image', value: MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_CHOOSE_FROM_DEVICE, label: __( 'Choose from device' ) }, { icon: 'camera', value: MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_TAKE_PHOTO, label: __( 'Take a Photo' ) }, - { icon: 'format-image', value: MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_WORD_PRESS_LIBRARY, label: __( 'WordPress Media Library' ) }, + { icon: 'wordpress-alt', value: MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_WORD_PRESS_LIBRARY, label: __( 'WordPress Media Library' ) }, ]; } @@ -273,12 +273,13 @@ class ImageEdit extends React.Component { label={ __( 'Alt Text' ) } value={ alt || '' } valuePlaceholder={ __( 'None' ) } + separatorType={ 'fullWidth' } onChangeValue={ this.updateAlt } /> <BottomSheet.Cell label={ __( 'Clear All Settings' ) } labelStyle={ styles.clearSettingsButton } - drawSeparator={ false } + separatorType={ 'none' } onPress={ this.onClearSettings } /> </BottomSheet> diff --git a/packages/editor/src/components/mobile/bottom-sheet/cell.native.js b/packages/editor/src/components/mobile/bottom-sheet/cell.native.js index 1ca7127416e2c..6ac26d2d00ff8 100644 --- a/packages/editor/src/components/mobile/bottom-sheet/cell.native.js +++ b/packages/editor/src/components/mobile/bottom-sheet/cell.native.js @@ -7,85 +7,131 @@ import { TouchableOpacity, Text, View, TextInput, I18nManager } from 'react-nati * WordPress dependencies */ import { Dashicon } from '@wordpress/components'; +import { Component } from '@wordpress/element'; /** * Internal dependencies */ import styles from './styles.scss'; +import platformStyles from './cellStyles.scss'; -export default function Cell( props ) { - const { - onPress, - label, - value, - valuePlaceholder = '', - drawSeparator = true, - icon, - labelStyle = {}, - valueStyle = {}, - onChangeValue, - children, - editable = true, - ...valueProps - } = props; +export default class Cell extends Component { + constructor() { + super( ...arguments ); + this.state = { + isEditingValue: false, + }; + } - const showValue = value !== undefined; - const isValueEditable = editable && onChangeValue !== undefined; - const defaultLabelStyle = showValue ? styles.cellLabel : styles.cellLabelCentered; - const separatorStyle = showValue ? styles.cellSeparator : styles.separator; - let valueTextInput; - - const onCellPress = () => { - if ( isValueEditable ) { - valueTextInput.focus(); - } else if ( onPress !== undefined ) { - onPress(); + componentDidUpdate() { + if ( this.state.isEditingValue ) { + this._valueTextInput.focus(); } - }; + } - const getValueComponent = () => { - const styleRTL = I18nManager.isRTL && styles.cellValueRTL; - const style = { ...styles.cellValue, ...valueStyle, ...styleRTL }; + render() { + const { + onPress, + label, + value, + valuePlaceholder = '', + icon, + labelStyle = {}, + valueStyle = {}, + onChangeValue, + children, + editable = true, + separatorType, + style = {}, + ...valueProps + } = this.props; - return isValueEditable ? ( - <TextInput - ref={ ( c ) => valueTextInput = c } - numberOfLines={ 1 } - style={ style } - value={ value } - placeholder={ valuePlaceholder } - placeholderTextColor={ '#87a6bc' } - onChangeText={ onChangeValue } - editable={ isValueEditable } - { ...valueProps } - /> - ) : ( - <Text style={ { ...styles.cellValue, ...valueStyle } }> - { value } - </Text> - ); - }; + const showValue = value !== undefined; + const isValueEditable = editable && onChangeValue !== undefined; + const defaultLabelStyle = showValue || icon !== undefined ? styles.cellLabel : styles.cellLabelCentered; + const drawSeparator = ( separatorType && separatorType !== 'none' ) || separatorStyle === undefined; + + const onCellPress = () => { + if ( isValueEditable ) { + this.setState( { isEditingValue: true } ); + } else if ( onPress !== undefined ) { + onPress(); + } + }; + + const finishEditing = () => { + this.setState( { isEditingValue: false } ); + }; - return ( - <TouchableOpacity onPress={ onCellPress } > - <View style={ styles.cellContainer }> - <View style={ styles.cellRowContainer }> - { icon && ( - <View style={ styles.cellRowContainer }> - <Dashicon icon={ icon } size={ 24 } /> - <View style={ { width: 12 } } /> - </View> - ) } - <Text numberOfLines={ 1 } style={ { ...defaultLabelStyle, ...labelStyle } }> - { label } - </Text> + const separatorStyle = () => { + const leftMarginStyle = { ...styles.cellSeparator, ...platformStyles.separatorMarginLeft }; + switch ( separatorType ) { + case 'leftMargin': + return leftMarginStyle; + case 'fullWidth': + return styles.separator; + case 'none': + return undefined; + case undefined: + return showValue ? leftMarginStyle : styles.separator; + } + }; + + const getValueComponent = () => { + const styleRTL = I18nManager.isRTL && styles.cellValueRTL; + const finalStyle = { ...styles.cellValue, ...valueStyle, ...styleRTL }; + + // To be able to show the `middle` ellipsizeMode on editable cells + // we show the TextInput just when the user wants to edit the value, + // and the Text component to display it. + // We also show the TextInput to display placeholder. + const shouldShowPlaceholder = isValueEditable && value === ''; + return this.state.isEditingValue || shouldShowPlaceholder ? ( + <TextInput + ref={ ( c ) => this._valueTextInput = c } + numberOfLines={ 1 } + style={ finalStyle } + value={ value } + placeholder={ valuePlaceholder } + placeholderTextColor={ '#87a6bc' } + onChangeText={ onChangeValue } + editable={ isValueEditable } + pointerEvents={ this.state.isEditingValue ? 'auto' : 'none' } + onBlur={ finishEditing } + { ...valueProps } + /> + ) : ( + <Text + style={ { ...styles.cellValue, ...valueStyle } } + numberOfLines={ 1 } + ellipsizeMode={ 'middle' } + > + { value } + </Text> + ); + }; + + return ( + <TouchableOpacity onPress={ onCellPress } style={ { ...styles.clipToBounds, ...style } } > + <View style={ styles.cellContainer }> + <View style={ styles.cellRowContainer }> + { icon && ( + <View style={ styles.cellRowContainer }> + <Dashicon icon={ icon } size={ 24 } /> + <View style={ platformStyles.labelIconSeparator } /> + </View> + ) } + <Text numberOfLines={ 1 } style={ { ...defaultLabelStyle, ...labelStyle } }> + { label } + </Text> + </View> + { showValue && getValueComponent() } + { children } </View> - { showValue && getValueComponent() } - { children } - </View> - { drawSeparator && ( - <View style={ separatorStyle } /> - ) } - </TouchableOpacity> - ); + { drawSeparator && ( + <View style={ separatorStyle() } /> + ) } + </TouchableOpacity> + ); + } } diff --git a/packages/editor/src/components/mobile/bottom-sheet/cellStyles.android.scss b/packages/editor/src/components/mobile/bottom-sheet/cellStyles.android.scss new file mode 100644 index 0000000000000..4feb815ba9bd0 --- /dev/null +++ b/packages/editor/src/components/mobile/bottom-sheet/cellStyles.android.scss @@ -0,0 +1,7 @@ +.labelIconSeparator { + width: 32px; +} + +.separatorMarginLeft { + margin-left: 56px; +} diff --git a/packages/editor/src/components/mobile/bottom-sheet/cellStyles.ios.scss b/packages/editor/src/components/mobile/bottom-sheet/cellStyles.ios.scss new file mode 100644 index 0000000000000..45b97213d1768 --- /dev/null +++ b/packages/editor/src/components/mobile/bottom-sheet/cellStyles.ios.scss @@ -0,0 +1,7 @@ +.labelIconSeparator { + width: 12px; +} + +.separatorMarginLeft { + margin-left: 36px; +} diff --git a/packages/editor/src/components/mobile/bottom-sheet/index.native.js b/packages/editor/src/components/mobile/bottom-sheet/index.native.js index c3cfb24686092..9815463dc19c5 100644 --- a/packages/editor/src/components/mobile/bottom-sheet/index.native.js +++ b/packages/editor/src/components/mobile/bottom-sheet/index.native.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { Text, View, KeyboardAvoidingView, Platform } from 'react-native'; +import { Text, View, KeyboardAvoidingView, Platform, PanResponder } from 'react-native'; import Modal from 'react-native-modal'; import SafeArea from 'react-native-safe-area'; @@ -50,7 +50,18 @@ class BottomSheet extends Component { } render() { - const { title = '', isVisible, leftButton, rightButton, hideHeader } = this.props; + const { title = '', isVisible, leftButton, rightButton, hideHeader, style = {} } = this.props; + + const panResponder = PanResponder.create( { + onMoveShouldSetPanResponder: ( evt, gestureState ) => { + // Activates swipe down over child Touchables if the swipe is long enough. + // With this we can adjust sensibility on the swipe vs tap gestures. + if ( gestureState.dy > 3 ) { + gestureState.dy = 0; + return true; + } + }, + } ); return ( <Modal @@ -65,14 +76,18 @@ class BottomSheet extends Component { onBackButtonPress={ this.props.onClose } onSwipe={ this.props.onClose } swipeDirection="down" + onMoveShouldSetResponder={ panResponder.panHandlers.onMoveShouldSetResponder } + onMoveShouldSetResponderCapture={ panResponder.panHandlers.onMoveShouldSetResponderCapture } > <KeyboardAvoidingView behavior={ Platform.OS === 'ios' && 'padding' } - style={ { ...styles.content, borderColor: 'rgba(0, 0, 0, 0.1)' } } + style={ { ...styles.content, borderColor: 'rgba(0, 0, 0, 0.1)', ...style } } keyboardVerticalOffset={ -this.state.safeAreaBottomInset } > <View style={ styles.dragIndicator } /> - { hideHeader || ( + { hideHeader ? ( + <View style={ styles.emptyHeaderSpace } /> + ) : ( <View> <View style={ styles.head }> <View style={ { flex: 1 } }> diff --git a/packages/editor/src/components/mobile/bottom-sheet/styles.native.scss b/packages/editor/src/components/mobile/bottom-sheet/styles.native.scss index 6255ddfce859c..3f826b35b7dbb 100644 --- a/packages/editor/src/components/mobile/bottom-sheet/styles.native.scss +++ b/packages/editor/src/components/mobile/bottom-sheet/styles.native.scss @@ -8,7 +8,7 @@ .dragIndicator { background-color: $light-gray-400; height: 4px; - width: 10%; + width: 36px; margin: auto; border-radius: 2px; } @@ -19,6 +19,10 @@ width: 100%; } +.emptyHeaderSpace { + height: 14; +} + .content { padding: 6px 16px 0 16px; background-color: $white; @@ -33,6 +37,7 @@ justify-content: space-between; align-items: center; align-content: center; + min-height: 48; } .title { @@ -63,11 +68,14 @@ align-items: center; } +.clipToBounds { + overflow: hidden; +} + .cellSeparator { background-color: $light-gray-400; height: 1px; width: 100%; - margin-left: 36px; } .cellRowContainer { diff --git a/packages/editor/src/components/mobile/picker/index.android.js b/packages/editor/src/components/mobile/picker/index.android.js index 47dd8abb0dc35..a0b85caf96382 100644 --- a/packages/editor/src/components/mobile/picker/index.android.js +++ b/packages/editor/src/components/mobile/picker/index.android.js @@ -40,6 +40,7 @@ export default class Picker extends Component { <BottomSheet isVisible={ this.state.isVisible } onClose={ this.onClose } + style={ { paddingBottom: 20 } } hideHeader > <View> @@ -48,13 +49,14 @@ export default class Picker extends Component { icon={ option.icon } key={ index } label={ option.label } + separatorType={ 'none' } onPress={ () => this.onCellPress( option.value ) } /> ) } { ! this.props.hideCancelButton && <BottomSheet.Cell label={ __( 'Cancel' ) } onPress={ this.onClose } - drawSeparator={ false } + separatorType={ 'none' } /> } </View> </BottomSheet> diff --git a/packages/editor/src/components/mobile/picker/styles.android.scss b/packages/editor/src/components/mobile/picker/styles.android.scss deleted file mode 100644 index 56579b4916535..0000000000000 --- a/packages/editor/src/components/mobile/picker/styles.android.scss +++ /dev/null @@ -1,10 +0,0 @@ -.cellContainer { - min-height: 48; - align-items: flex-start; -} - -.cellLabel { - font-size: 17px; - color: #2e4453; - margin-right: 12px; -} From 910b655018037167c2dcee38095905003f632b51 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Mon, 18 Feb 2019 20:28:22 +0000 Subject: [PATCH 468/691] Fix: RangeControl does not validates the min and max properties (#12952) --- packages/components/CHANGELOG.md | 1 + .../components/src/range-control/README.md | 15 ++ .../components/src/range-control/index.js | 56 ++++++-- .../src/range-control/test/index.js | 134 ++++++++++++++++++ 4 files changed, 198 insertions(+), 8 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index a21b3d305a7e2..f8ad35ad5c426 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -8,6 +8,7 @@ - `withFilters` has been optimized to avoid binding hook handlers for each mounted instance of the component, instead using a single centralized hook delegator. - `withFilters` has been optimized to reuse a single shared component definition for all filtered instances of the component. +- Make `RangeControl` validate min and max properties. ### Bug Fixes diff --git a/packages/components/src/range-control/README.md b/packages/components/src/range-control/README.md index 28fcae0c82c95..1f2024c8b6086 100644 --- a/packages/components/src/range-control/README.md +++ b/packages/components/src/range-control/README.md @@ -169,6 +169,21 @@ If allowReset is true, when onChange is called without any parameter passed it s - Type: `function` - Required: Yes +#### min + +The minimum value accepted. If smaller values are inserted onChange will not be called and the value gets reverted when blur event fires. + +- Type: `Number` +- Required: No + + +#### max + +The maximum value accepted. If higher values are inserted onChange will not be called and the value gets reverted when blur event fires. + +- Type: `Number` +- Required: No + ## Related components - To collect a numerical input in a text field, use the `TextControl` component. diff --git a/packages/components/src/range-control/index.js b/packages/components/src/range-control/index.js index ea341b5fc8b27..1503813def5cb 100644 --- a/packages/components/src/range-control/index.js +++ b/packages/components/src/range-control/index.js @@ -8,7 +8,7 @@ import classnames from 'classnames'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { withInstanceId } from '@wordpress/compose'; +import { compose, withInstanceId, withState } from '@wordpress/compose'; /** * Internal dependencies @@ -17,6 +17,7 @@ import { BaseControl, Button, Dashicon } from '../'; function RangeControl( { className, + currentInput, label, value, instanceId, @@ -26,19 +27,48 @@ function RangeControl( { help, allowReset, initialPosition, + min, + max, + setState, ...props } ) { const id = `inspector-range-control-${ instanceId }`; - const resetValue = () => onChange(); + const currentInputValue = currentInput === null ? value : currentInput; + const resetValue = () => { + resetCurrentInput(); + onChange(); + }; + const resetCurrentInput = () => { + if ( currentInput !== null ) { + setState( { + currentInput: null, + } ); + } + }; + const onChangeValue = ( event ) => { const newValue = event.target.value; - if ( newValue === '' ) { - resetValue(); + const newNumericValue = parseInt( newValue, 10 ); + // If the input value is invalid temporarily save it to the state, + // without calling on change. + if ( + isNaN( newNumericValue ) || + ( min !== undefined && newNumericValue < min ) || + ( max !== undefined && newNumericValue > max ) + ) { + setState( { + currentInput: newValue, + } ); return; } - onChange( Number( newValue ) ); + // The input is valid, reset the local state property used to temporaly save the value, + // and call onChange with the new value as a number. + resetCurrentInput(); + onChange( newNumericValue ); }; - const initialSliderValue = isFinite( value ) ? value : initialPosition || ''; + const initialSliderValue = isFinite( value ) ? + currentInputValue : + initialPosition || ''; return ( <BaseControl @@ -55,6 +85,8 @@ function RangeControl( { value={ initialSliderValue } onChange={ onChangeValue } aria-describedby={ !! help ? id + '__help' : undefined } + min={ min } + max={ max } { ...props } /> { afterIcon && <Dashicon icon={ afterIcon } /> } <input @@ -62,7 +94,10 @@ function RangeControl( { type="number" onChange={ onChangeValue } aria-label={ label } - value={ value } + value={ currentInputValue } + min={ min } + max={ max } + onBlur={ resetCurrentInput } { ...props } /> { allowReset && @@ -74,4 +109,9 @@ function RangeControl( { ); } -export default withInstanceId( RangeControl ); +export default compose( [ + withInstanceId, + withState( { + currentInput: null, + } ), +] )( RangeControl ); diff --git a/packages/components/src/range-control/test/index.js b/packages/components/src/range-control/test/index.js index 4b2912665dd99..5f8e0bccb8aa8 100644 --- a/packages/components/src/range-control/test/index.js +++ b/packages/components/src/range-control/test/index.js @@ -81,4 +81,138 @@ describe( 'RangeControl', () => { expect( icons[ 1 ].props.icon ).toBe( 'format-video' ); } ); } ); + + describe( 'validation', () => { + it( 'does not calls onChange if the new value is lower than minimum', () => { + // Mount: With shallow, cannot find input child of BaseControl + const onChange = jest.fn(); + const wrapper = getWrapper( { onChange, min: 11, value: 12 } ); + + const numberInputElement = () => TestUtils.findRenderedDOMComponentWithClass( + wrapper, + 'components-range-control__number' + ); + + TestUtils.Simulate.change( + numberInputElement(), + { + target: { value: '10' }, + } + ); + + expect( onChange ).not.toHaveBeenCalled(); + } ); + + it( 'does not calls onChange if the new value is greater than maximum', () => { + // Mount: With shallow, cannot find input child of BaseControl + const onChange = jest.fn(); + const wrapper = getWrapper( { onChange, max: 20, value: 12 } ); + + const numberInputElement = () => TestUtils.findRenderedDOMComponentWithClass( + wrapper, + 'components-range-control__number' + ); + + TestUtils.Simulate.change( + numberInputElement(), + { + target: { value: '21' }, + } + ); + + expect( onChange ).not.toHaveBeenCalled(); + } ); + + it( 'calls onChange after invalid inputs if the new input is valid', () => { + // Mount: With shallow, cannot find input child of BaseControl + const onChange = jest.fn(); + const wrapper = getWrapper( { onChange, min: 11, max: 20, value: 12 } ); + + const numberInputElement = () => TestUtils.findRenderedDOMComponentWithClass( + wrapper, + 'components-range-control__number' + ); + + TestUtils.Simulate.change( + numberInputElement(), + { + target: { value: '10' }, + } + ); + + TestUtils.Simulate.change( + numberInputElement(), + { + target: { value: '21' }, + } + ); + + expect( onChange ).not.toHaveBeenCalled(); + + TestUtils.Simulate.change( + numberInputElement(), + { + target: { value: '14' }, + } + ); + + expect( onChange ).toHaveBeenCalledWith( 14 ); + } ); + + it( 'validates when provided a max or min of zero', () => { + const onChange = jest.fn(); + const wrapper = getWrapper( { onChange, min: -100, max: 0, value: 0 } ); + + const numberInputElement = () => TestUtils.findRenderedDOMComponentWithClass( + wrapper, + 'components-range-control__number' + ); + + TestUtils.Simulate.change( + numberInputElement(), + { + target: { value: '1' }, + } + ); + + expect( onChange ).not.toHaveBeenCalled(); + } ); + + it( 'validates when min and max are negative', () => { + const onChange = jest.fn(); + const wrapper = getWrapper( { onChange, min: -100, max: -50, value: -60 } ); + + const numberInputElement = () => TestUtils.findRenderedDOMComponentWithClass( + wrapper, + 'components-range-control__number' + ); + + TestUtils.Simulate.change( + numberInputElement(), + { + target: { value: '-101' }, + } + ); + + expect( onChange ).not.toHaveBeenCalled(); + + TestUtils.Simulate.change( + numberInputElement(), + { + target: { value: '-49' }, + } + ); + + expect( onChange ).not.toHaveBeenCalled(); + + TestUtils.Simulate.change( + numberInputElement(), + { + target: { value: '-50' }, + } + ); + + expect( onChange ).toHaveBeenCalled(); + } ); + } ); } ); From a96bc422c0ba6c6fa328be8b1d66cf54a8521977 Mon Sep 17 00:00:00 2001 From: Hannah Malcolm <KawaiiHannah@users.noreply.github.com> Date: Tue, 19 Feb 2019 16:42:29 +1000 Subject: [PATCH 469/691] Update readme wording (#13940) Remove an unnecessary word from the readme. --- packages/editor/src/components/media-upload/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor/src/components/media-upload/README.md b/packages/editor/src/components/media-upload/README.md index 1fdeed671bd1e..74567c8694205 100644 --- a/packages/editor/src/components/media-upload/README.md +++ b/packages/editor/src/components/media-upload/README.md @@ -1,7 +1,7 @@ MediaUpload =========== -MediaUpload is a React component used to render a button that opens a the WordPress media modal. +MediaUpload is a React component used to render a button that opens the WordPress media modal. ## Setup From 4f0081bf9c278280d04d45573915b9aa36caee37 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Tue, 19 Feb 2019 02:26:38 -0500 Subject: [PATCH 470/691] Testing: Remove custom ESLint rules for import paths (#13937) --- .eslintrc.js | 75 ---------------------------------------------------- 1 file changed, 75 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 5ce3aa251459b..2f4a4f05e3e77 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -32,81 +32,6 @@ module.exports = { selector: 'ImportDeclaration[source.value=/^@wordpress\\u002F.+\\u002F/]', message: 'Path access on WordPress dependencies is not allowed.', }, - { - selector: 'ImportDeclaration[source.value=/^api-fetch(\\u002F|$)/]', - message: 'Use @wordpress/api-fetch as import path instead.', - }, - { - selector: 'ImportDeclaration[source.value=/^blob(\\u002F|$)/]', - message: 'Use @wordpress/blob as import path instead.', - }, - { - selector: 'ImportDeclaration[source.value=/^block-serialization-spec-parser(\\u002F|$)/]', - message: 'Use @wordpress/block-serialization-spec-parser as import path instead.', - }, - { - selector: 'ImportDeclaration[source.value=/^blocks(\\u002F|$)/]', - message: 'Use @wordpress/blocks as import path instead.', - },{ - selector: 'ImportDeclaration[source.value=/^components(\\u002F|$)/]', - message: 'Use @wordpress/components as import path instead.', - }, - { - selector: 'ImportDeclaration[source.value=/^data(\\u002F|$)/]', - message: 'Use @wordpress/data as import path instead.', - }, - { - selector: 'ImportDeclaration[source.value=/^date(\\u002F|$)/]', - message: 'Use @wordpress/date as import path instead.', - }, - { - selector: 'ImportDeclaration[source.value=/^deprecated(\\u002F|$)/]', - message: 'Use @wordpress/deprecated as import path instead.', - }, - { - selector: 'ImportDeclaration[source.value=/^dom(\\u002F|$)/]', - message: 'Use @wordpress/dom as import path instead.', - }, - { - selector: 'ImportDeclaration[source.value=/^editor(\\u002F|$)/]', - message: 'Use @wordpress/editor as import path instead.', - }, - { - selector: 'ImportDeclaration[source.value=/^element(\\u002F|$)/]', - message: 'Use @wordpress/element as import path instead.', - }, - { - selector: 'ImportDeclaration[source.value=/^keycodes(\\u002F|$)/]', - message: 'Use @wordpress/keycodes as import path instead.', - }, - { - selector: 'ImportDeclaration[source.value=/^nux(\\u002F|$)/]', - message: 'Use @wordpress/nux as import path instead.', - }, - { - selector: 'ImportDeclaration[source.value=/^edit-post(\\u002F|$)/]', - message: 'Use @wordpress/edit-post as import path instead.', - }, - { - selector: 'ImportDeclaration[source.value=/^edit-widgets(\\u002F|$)/]', - message: 'Use @wordpress/edit-widgets as import path instead.', - }, - { - selector: 'ImportDeclaration[source.value=/^viewport(\\u002F|$)/]', - message: 'Use @wordpress/viewport as import path instead.', - }, - { - selector: 'ImportDeclaration[source.value=/^plugins(\\u002F|$)/]', - message: 'Use @wordpress/plugins as import path instead.', - }, - { - "selector": "ImportDeclaration[source.value=/^core-data$/]", - "message": "Use @wordpress/core-data as import path instead." - }, - { - "selector": "ImportDeclaration[source.value=/^block-library$/]", - "message": "Use @wordpress/block-library as import path instead." - }, { selector: 'CallExpression[callee.name="deprecated"] Property[key.name="version"][value.value=/' + majorMinorRegExp + '/]', message: 'Deprecated functions must be removed before releasing this version.', From 7804fa06fe833cf96a6b54166b2fd485c2a09e65 Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler <dekervit@gmail.com> Date: Tue, 19 Feb 2019 09:36:17 +0100 Subject: [PATCH 471/691] Update default placeholder for the default block appender component (#13880) --- .../src/components/default-block-appender/index.native.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/editor/src/components/default-block-appender/index.native.js b/packages/editor/src/components/default-block-appender/index.native.js index 22563f75a086f..4414829c2e4b7 100644 --- a/packages/editor/src/components/default-block-appender/index.native.js +++ b/packages/editor/src/components/default-block-appender/index.native.js @@ -6,7 +6,7 @@ import { TextInput, TouchableWithoutFeedback, View } from 'react-native'; /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { compose } from '@wordpress/compose'; import { decodeEntities } from '@wordpress/html-entities'; import { withSelect, withDispatch } from '@wordpress/data'; @@ -26,7 +26,7 @@ export function DefaultBlockAppender( { return null; } - const value = decodeEntities( placeholder ) || __( 'Start writing or press \u2295 to add content' ); + const value = decodeEntities( placeholder ) || sprintf( __( 'Start writing or press %s to add content' ), '\u2295' ); return ( <TouchableWithoutFeedback From 0254aaf11c7f6f727f6940a3bbf0b020a3a27711 Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.p.richards@gmail.com> Date: Tue, 19 Feb 2019 17:02:01 +0800 Subject: [PATCH 472/691] Try: Fix issue where block fails validation when a default attribute is deprecated (#12757) * Use attributes as parsed when determining a block migration, not the attributes from the failed created block * Updated tests with new migratedBlocks signature and add new test to cover deprecated default use case * Fix typo with attribute type in test --- packages/blocks/src/api/parser.js | 16 +++--- packages/blocks/src/api/test/parser.js | 78 ++++++++++++++++++++------ 2 files changed, 71 insertions(+), 23 deletions(-) diff --git a/packages/blocks/src/api/parser.js b/packages/blocks/src/api/parser.js index 597ceffff726c..5b05f0fe93af6 100644 --- a/packages/blocks/src/api/parser.js +++ b/packages/blocks/src/api/parser.js @@ -304,11 +304,13 @@ export function getBlockAttributes( blockTypeOrName, innerHTML, attributes = {} * deprecated migrations applied, or the original block if it was both valid * and no eligible migrations exist. * - * @param {WPBlock} block Original block object. + * @param {WPBlock} block Original block object. + * @param {Object} parsedAttributes Attributes as parsed from the initial + * block markup. * * @return {WPBlock} Migrated block object. */ -export function getMigratedBlock( block ) { +export function getMigratedBlock( block, parsedAttributes ) { const blockType = getBlockType( block.name ); const { deprecated: deprecatedDefinitions } = blockType; @@ -316,14 +318,14 @@ export function getMigratedBlock( block ) { return block; } - const { originalContent, attributes, innerBlocks } = block; + const { originalContent, innerBlocks } = block; for ( let i = 0; i < deprecatedDefinitions.length; i++ ) { // A block can opt into a migration even if the block is valid by // defining isEligible on its deprecation. If the block is both valid // and does not opt to migrate, skip. const { isEligible = stubFalse } = deprecatedDefinitions[ i ]; - if ( block.isValid && ! isEligible( attributes, innerBlocks ) ) { + if ( block.isValid && ! isEligible( parsedAttributes, innerBlocks ) ) { continue; } @@ -338,7 +340,7 @@ export function getMigratedBlock( block ) { let migratedAttributes = getBlockAttributes( deprecatedBlockType, originalContent, - attributes + parsedAttributes ); // Ignore the deprecation if it produces a block which is not valid. @@ -364,7 +366,7 @@ export function getMigratedBlock( block ) { const { migrate } = deprecatedBlockType; if ( migrate ) { ( [ - migratedAttributes = attributes, + migratedAttributes = parsedAttributes, migratedInnerBlocks = innerBlocks, ] = castArray( migrate( migratedAttributes, innerBlocks ) ) ); } @@ -468,7 +470,7 @@ export function createBlockWithFallback( blockNode ) { // invalid, or future serialization attempt results in an error. block.originalContent = innerHTML; - block = getMigratedBlock( block ); + block = getMigratedBlock( block, attributes ); return block; } diff --git a/packages/blocks/src/api/test/parser.js b/packages/blocks/src/api/test/parser.js index bc059542b0142..02469c6e3f569 100644 --- a/packages/blocks/src/api/test/parser.js +++ b/packages/blocks/src/api/test/parser.js @@ -367,23 +367,25 @@ describe( 'block parser', () => { describe( 'getMigratedBlock', () => { it( 'should return the original block if it has no deprecated versions', () => { + const parsedAttributes = {}; const block = deepFreeze( { name: 'core/test-block', - attributes: {}, + attributes: parsedAttributes, originalContent: '<span class="wp-block-test-block">Bananas</span>', isValid: false, } ); registerBlockType( 'core/test-block', defaultBlockSettings ); - const migratedBlock = getMigratedBlock( block ); + const migratedBlock = getMigratedBlock( block, parsedAttributes ); expect( migratedBlock ).toBe( block ); } ); it( 'should return the original block if no valid deprecated version found', () => { + const parsedAttributes = {}; const block = deepFreeze( { name: 'core/test-block', - attributes: {}, + attributes: parsedAttributes, originalContent: '<span class="wp-block-test-block">Bananas</span>', isValid: false, } ); @@ -398,7 +400,7 @@ describe( 'block parser', () => { ], } ); - const migratedBlock = getMigratedBlock( block ); + const migratedBlock = getMigratedBlock( block, parsedAttributes ); expect( migratedBlock ).toBe( block ); expect( console ).toHaveErrored(); @@ -406,12 +408,14 @@ describe( 'block parser', () => { } ); it( 'should return with attributes parsed by the deprecated version', () => { + const parsedAttributes = {}; const block = deepFreeze( { name: 'core/test-block', - attributes: {}, + attributes: parsedAttributes, originalContent: '<span>Bananas</span>', isValid: false, } ); + registerBlockType( 'core/test-block', { ...defaultBlockSettings, save: ( props ) => <div>{ props.attributes.fruit }</div>, @@ -429,15 +433,16 @@ describe( 'block parser', () => { ], } ); - const migratedBlock = getMigratedBlock( block ); + const migratedBlock = getMigratedBlock( block, parsedAttributes ); expect( migratedBlock.attributes ).toEqual( { fruit: 'Bananas' } ); } ); it( 'should be able to migrate attributes and innerBlocks', () => { + const parsedAttributes = {}; const block = deepFreeze( { name: 'core/test-block', - attributes: {}, + attributes: parsedAttributes, originalContent: '<span>Bananas</span>', isValid: false, } ); @@ -467,7 +472,7 @@ describe( 'block parser', () => { ], } ); - const migratedBlock = getMigratedBlock( block ); + const migratedBlock = getMigratedBlock( block, parsedAttributes ); expect( migratedBlock.attributes ).toEqual( { newFruit: 'Bananas' } ); expect( migratedBlock.innerBlocks ).toHaveLength( 1 ); @@ -476,11 +481,10 @@ describe( 'block parser', () => { } ); it( 'should ignore valid uneligible blocks', () => { + const parsedAttributes = { fruit: 'Bananas' }; const block = deepFreeze( { name: 'core/test-block', - attributes: { - fruit: 'Bananas', - }, + attributes: parsedAttributes, originalContent: 'Bananas', isValid: true, } ); @@ -499,17 +503,16 @@ describe( 'block parser', () => { ], } ); - const migratedBlock = getMigratedBlock( block ); + const migratedBlock = getMigratedBlock( block, parsedAttributes ); expect( migratedBlock.attributes ).toEqual( { fruit: 'Bananas' } ); } ); it( 'should allow opt-in eligibility of valid block', () => { + const parsedAttributes = { fruit: 'Bananas' }; const block = deepFreeze( { name: 'core/test-block', - attributes: { - fruit: 'Bananas', - }, + attributes: parsedAttributes, originalContent: 'Bananas', isValid: true, } ); @@ -529,10 +532,53 @@ describe( 'block parser', () => { ], } ); - const migratedBlock = getMigratedBlock( block ); + const migratedBlock = getMigratedBlock( block, parsedAttributes ); expect( migratedBlock.attributes ).toEqual( { fruit: 'Bananas!' } ); } ); + + it( 'allows a default attribute to be deprecated', () => { + // The block's default fruit attribute has been changed from 'Bananas' to 'Oranges'. + registerBlockType( 'core/test-block', { + ...defaultBlockSettings, + attributes: { + fruit: { + type: 'string', + default: 'Oranges', + }, + }, + deprecated: [ + { + attributes: { + fruit: { + type: 'string', + default: 'Bananas', + }, + }, + save: defaultBlockSettings.save, + }, + ], + } ); + + // Because the fruits attribute is not sourced, when the block content was parsed no value for the + // fruit attribute was found. + const parsedAttributes = {}; + + // When the block was created, it was given the new default value for the fruit attribute of 'Oranges'. + // This is because unchanged default values are not saved to the comment delimeter attributes. + // Validation failed because this block was saved when the old default was 'Bananas' as reflected by the originalContent. + const block = deepFreeze( { + name: 'core/test-block', + attributes: { fruit: 'Oranges' }, + originalContent: 'Bananas', + isValid: false, + } ); + + // The migrated block successfully falls back to the old value of 'Bananas', allowing the block to + // continue to be used. + const migratedBlock = getMigratedBlock( block, parsedAttributes ); + expect( migratedBlock.attributes ).toEqual( { fruit: 'Bananas' } ); + } ); } ); describe( 'createBlockWithFallback', () => { From 2df40d8a3cc1e1d4d6e07a2a35e619dca0ab0a50 Mon Sep 17 00:00:00 2001 From: etoledom <etoledom@icloud.com> Date: Tue, 19 Feb 2019 10:46:44 +0100 Subject: [PATCH 473/691] Mobile BottomSheet: Adding max-width and centering. (#13882) * Mobile BottomSheet: Adding max-width and centering. * Mobile BottomSheet: Fixed drag indicator width. * Mobile BottomSheet: Fix max-width on Android. * Mobile BottomSheet: Added children container. * Mobile BottomSheet: Added function to get bottom-sheet width * Fix lint issues --- .../mobile/bottom-sheet/index.native.js | 76 ++++++++++++------- .../mobile/bottom-sheet/styles.native.scss | 11 ++- 2 files changed, 56 insertions(+), 31 deletions(-) diff --git a/packages/editor/src/components/mobile/bottom-sheet/index.native.js b/packages/editor/src/components/mobile/bottom-sheet/index.native.js index 9815463dc19c5..313ad950c7b55 100644 --- a/packages/editor/src/components/mobile/bottom-sheet/index.native.js +++ b/packages/editor/src/components/mobile/bottom-sheet/index.native.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { Text, View, KeyboardAvoidingView, Platform, PanResponder } from 'react-native'; +import { Text, View, KeyboardAvoidingView, Platform, PanResponder, Dimensions } from 'react-native'; import Modal from 'react-native-modal'; import SafeArea from 'react-native-safe-area'; @@ -30,17 +30,20 @@ class BottomSheet extends Component { } componentDidMount() { - this.eventSubscription = SafeArea.addEventListener( 'safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsUpdate ); + this.safeAreaEventSubscription = SafeArea.addEventListener( 'safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsUpdate ); } componentWillUnmount() { - this.eventSubscription.remove(); - this.eventSubscription = null; + if ( this.safeAreaEventSubscription === null ) { + return; + } + this.safeAreaEventSubscription.remove(); + this.safeAreaEventSubscription = null; SafeArea.removeEventListener( 'safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsUpdate ); } onSafeAreaInsetsUpdate( result ) { - if ( this.eventSubscription === null ) { + if ( this.safeAreaEventSubscription === null ) { return; } const { safeAreaInsets } = result; @@ -50,7 +53,15 @@ class BottomSheet extends Component { } render() { - const { title = '', isVisible, leftButton, rightButton, hideHeader, style = {} } = this.props; + const { + title = '', + isVisible, + leftButton, + rightButton, + hideHeader, + style = {}, + contentStyle = {}, + } = this.props; const panResponder = PanResponder.create( { onMoveShouldSetPanResponder: ( evt, gestureState ) => { @@ -63,6 +74,25 @@ class BottomSheet extends Component { }, } ); + const getHeader = () => ( + <View> + <View style={ styles.head }> + <View style={ { flex: 1 } }> + { leftButton } + </View> + <View style={ styles.titleContainer }> + <Text style={ styles.title }> + { title } + </Text> + </View> + <View style={ { flex: 1 } }> + { rightButton } + </View> + </View> + <View style={ styles.separator } /> + </View> + ); + return ( <Modal isVisible={ isVisible } @@ -81,32 +111,15 @@ class BottomSheet extends Component { > <KeyboardAvoidingView behavior={ Platform.OS === 'ios' && 'padding' } - style={ { ...styles.content, borderColor: 'rgba(0, 0, 0, 0.1)', ...style } } + style={ { ...styles.background, borderColor: 'rgba(0, 0, 0, 0.1)', ...style } } keyboardVerticalOffset={ -this.state.safeAreaBottomInset } > <View style={ styles.dragIndicator } /> - { hideHeader ? ( - <View style={ styles.emptyHeaderSpace } /> - ) : ( - <View> - <View style={ styles.head }> - <View style={ { flex: 1 } }> - { leftButton } - </View> - <View style={ styles.titleContainer }> - <Text style={ styles.title }> - { title } - </Text> - </View> - <View style={ { flex: 1 } }> - { rightButton } - </View> - </View> - <View style={ styles.separator } /> - </View> - ) } - { this.props.children } - <View style={ { flexGrow: 1 } }></View> + { hideHeader && ( <View style={ styles.emptyHeaderSpace } /> ) } + { ! hideHeader && getHeader() } + <View style={ [ styles.content, contentStyle ] }> + { this.props.children } + </View> <View style={ { height: this.state.safeAreaBottomInset } } /> </KeyboardAvoidingView> </Modal> @@ -115,6 +128,11 @@ class BottomSheet extends Component { } } +function getWidth() { + return Math.min( Dimensions.get( 'window' ).width, styles.background.maxWidth ); +} + +BottomSheet.getWidth = getWidth; BottomSheet.Button = Button; BottomSheet.Cell = Cell; BottomSheet.PickerCell = PickerCell; diff --git a/packages/editor/src/components/mobile/bottom-sheet/styles.native.scss b/packages/editor/src/components/mobile/bottom-sheet/styles.native.scss index 3f826b35b7dbb..198d526092a85 100644 --- a/packages/editor/src/components/mobile/bottom-sheet/styles.native.scss +++ b/packages/editor/src/components/mobile/bottom-sheet/styles.native.scss @@ -3,6 +3,7 @@ .bottomModal { justify-content: flex-end; margin: 0; + align-items: center; } .dragIndicator { @@ -10,6 +11,7 @@ height: 4px; width: 36px; margin: auto; + margin-top: 6px; border-radius: 2px; } @@ -23,11 +25,16 @@ height: 14; } -.content { - padding: 6px 16px 0 16px; +.background { background-color: $white; border-top-right-radius: 8px; border-top-left-radius: 8px; + width: 100%; + max-width: 512; +} + +.content { + padding: 0 16px 0 16px; } .head { From b2861d588cbe8dc7652cc8415c7e7cb95accca5e Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Tue, 19 Feb 2019 10:04:15 +0000 Subject: [PATCH 474/691] Fix: Display HTML in titles of latest posts block. (#13622) --- .../block-library/src/latest-posts/edit.js | 38 +++++++++++++------ .../block-library/src/latest-posts/index.php | 2 +- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/packages/block-library/src/latest-posts/edit.js b/packages/block-library/src/latest-posts/edit.js index f6bc6953f2955..a275afda148ab 100644 --- a/packages/block-library/src/latest-posts/edit.js +++ b/packages/block-library/src/latest-posts/edit.js @@ -7,7 +7,11 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { Component, Fragment } from '@wordpress/element'; +import { + Component, + Fragment, + RawHTML, +} from '@wordpress/element'; import { PanelBody, Placeholder, @@ -21,7 +25,6 @@ import apiFetch from '@wordpress/api-fetch'; import { addQueryArgs } from '@wordpress/url'; import { __ } from '@wordpress/i18n'; import { dateI18n, format, __experimentalGetSettings } from '@wordpress/date'; -import { decodeEntities } from '@wordpress/html-entities'; import { InspectorControls, BlockAlignmentToolbar, @@ -171,16 +174,27 @@ class LatestPostsEdit extends Component { [ `columns-${ columns }` ]: postLayout === 'grid', } ) } > - { displayPosts.map( ( post, i ) => - <li key={ i }> - <a href={ post.link } target="_blank">{ decodeEntities( post.title.rendered.trim() ) || __( '(Untitled)' ) }</a> - { displayPostDate && post.date_gmt && - <time dateTime={ format( 'c', post.date_gmt ) } className="wp-block-latest-posts__post-date"> - { dateI18n( dateFormat, post.date_gmt ) } - </time> - } - </li> - ) } + { displayPosts.map( ( post, i ) => { + const titleTrimmed = post.title.rendered.trim(); + return ( + <li key={ i }> + <a href={ post.link } target="_blank"> + { titleTrimmed ? ( + <RawHTML> + { titleTrimmed } + </RawHTML> + ) : + __( '(Untitled)' ) + } + </a> + { displayPostDate && post.date_gmt && + <time dateTime={ format( 'c', post.date_gmt ) } className="wp-block-latest-posts__post-date"> + { dateI18n( dateFormat, post.date_gmt ) } + </time> + } + </li> + ); + } ) } </ul> </Fragment> ); diff --git a/packages/block-library/src/latest-posts/index.php b/packages/block-library/src/latest-posts/index.php index 0b0d858bbfe5e..888f99c887992 100644 --- a/packages/block-library/src/latest-posts/index.php +++ b/packages/block-library/src/latest-posts/index.php @@ -37,7 +37,7 @@ function render_block_core_latest_posts( $attributes ) { $list_items_markup .= sprintf( '<li><a href="%1$s">%2$s</a>', esc_url( get_permalink( $post ) ), - esc_html( $title ) + $title ); if ( isset( $attributes['displayPostDate'] ) && $attributes['displayPostDate'] ) { From 23e59d1f3601164788278297e5cbb33d0c1d31e5 Mon Sep 17 00:00:00 2001 From: Matthew Kevins <mkevins@users.noreply.github.com> Date: Tue, 19 Feb 2019 20:28:02 +1000 Subject: [PATCH 475/691] Implement pasting external content with plain and styled text (#13841) * Refactor raw-handler and paste-handler into their own files * Add undefined and null checks for jsdom-jscore node methods/properties * Implement createRecord method for converting native content to RichText * Refactor splitContent and onEnter for common interface with onPaste * Begin implementing onPaste for plain and styled text * Fix some lint errors and some invalid references. * Add undefined check for jsdom-jscore node in phrasing-content-reducer * Add onReplace method to ParagraphEdit * Refactor raw-handler back into index.js * Remove unused parameters from RichText create method --- .../src/paragraph/edit.native.js | 16 ++ packages/blocks/src/api/index.native.js | 3 +- packages/blocks/src/api/raw-handling/index.js | 167 +----------- .../src/api/raw-handling/index.native.js | 1 + .../src/api/raw-handling/paste-handler.js | 240 ++++++++++++++++++ .../raw-handling/phrasing-content-reducer.js | 8 +- packages/blocks/src/api/raw-handling/utils.js | 4 +- packages/dom/package.json | 1 + .../src/components/rich-text/index.native.js | 152 +++++++++-- packages/shortcode/package.json | 1 + 10 files changed, 407 insertions(+), 186 deletions(-) create mode 100644 packages/blocks/src/api/raw-handling/paste-handler.js diff --git a/packages/block-library/src/paragraph/edit.native.js b/packages/block-library/src/paragraph/edit.native.js index b8205240d48d1..c859f5c5216c8 100644 --- a/packages/block-library/src/paragraph/edit.native.js +++ b/packages/block-library/src/paragraph/edit.native.js @@ -22,6 +22,7 @@ class ParagraphEdit extends Component { constructor( props ) { super( props ); this.splitBlock = this.splitBlock.bind( this ); + this.onReplace = this.onReplace.bind( this ); this.state = { aztecHeight: 0, @@ -71,6 +72,20 @@ class ParagraphEdit extends Component { } } + onReplace( blocks ) { + const { attributes, onReplace } = this.props; + onReplace( blocks.map( ( block, index ) => ( + index === 0 && block.name === name ? + { ...block, + attributes: { + ...attributes, + ...block.attributes, + }, + } : + block + ) ) ); + } + render() { const { attributes, @@ -106,6 +121,7 @@ class ParagraphEdit extends Component { } } onSplit={ this.splitBlock } onMerge={ mergeBlocks } + onReplace={ this.onReplace } onContentSizeChange={ ( event ) => { this.setState( { aztecHeight: event.aztecHeight } ); } } diff --git a/packages/blocks/src/api/index.native.js b/packages/blocks/src/api/index.native.js index 7b0bd10eadfd7..2f850d36108cb 100644 --- a/packages/blocks/src/api/index.native.js +++ b/packages/blocks/src/api/index.native.js @@ -4,6 +4,7 @@ export { } from './factory'; export { default as parse, + getBlockAttributes, parseWithAttributeSchema, } from './parser'; export { @@ -28,5 +29,5 @@ export { export { isUnmodifiedDefaultBlock, } from './utils'; -export { getPhrasingContentSchema } from './raw-handling'; +export { pasteHandler, getPhrasingContentSchema } from './raw-handling'; export { default as children } from './children'; diff --git a/packages/blocks/src/api/raw-handling/index.js b/packages/blocks/src/api/raw-handling/index.js index 2de9bff8e2059..58044375d9dc3 100644 --- a/packages/blocks/src/api/raw-handling/index.js +++ b/packages/blocks/src/api/raw-handling/index.js @@ -7,52 +7,20 @@ import { flatMap, filter, compact } from 'lodash'; * Internal dependencies */ import { createBlock, getBlockTransforms, findTransform } from '../factory'; -import { getBlockContent } from '../serializer'; import { getBlockAttributes, parseWithGrammar } from '../parser'; import normaliseBlocks from './normalise-blocks'; import specialCommentConverter from './special-comment-converter'; -import isInlineContent from './is-inline-content'; -import phrasingContentReducer from './phrasing-content-reducer'; -import headRemover from './head-remover'; -import msListConverter from './ms-list-converter'; import listReducer from './list-reducer'; -import imageCorrector from './image-corrector'; import blockquoteNormaliser from './blockquote-normaliser'; import figureContentReducer from './figure-content-reducer'; import shortcodeConverter from './shortcode-converter'; -import markdownConverter from './markdown-converter'; -import iframeRemover from './iframe-remover'; -import { getPhrasingContentSchema } from './phrasing-content'; import { deepFilterHTML, - isPlain, - removeInvalidHTML, getBlockContentSchema, } from './utils'; -/** - * Browser dependencies - */ -const { console } = window; - -export { getPhrasingContentSchema }; - -/** - * Filters HTML to only contain phrasing content. - * - * @param {string} HTML The HTML to filter. - * - * @return {string} HTML only containing phrasing content. - */ -function filterInlineHTML( HTML ) { - HTML = deepFilterHTML( HTML, [ phrasingContentReducer ] ); - HTML = removeInvalidHTML( HTML, getPhrasingContentSchema(), { inline: true } ); - - // Allows us to ask for this information when we get a report. - console.log( 'Processed inline HTML:\n\n', HTML ); - - return HTML; -} +export { getPhrasingContentSchema } from './phrasing-content'; +export { pasteHandler } from './paste-handler'; function getRawTransformations() { return filter( getBlockTransforms( 'from' ), { type: 'raw' } ) @@ -110,137 +78,6 @@ function htmlToBlocks( { html, rawTransforms } ) { } ); } -/** - * Converts an HTML string to known blocks. Strips everything else. - * - * @param {string} [options.HTML] The HTML to convert. - * @param {string} [options.plainText] Plain text version. - * @param {string} [options.mode] Handle content as blocks or inline content. - * * 'AUTO': Decide based on the content passed. - * * 'INLINE': Always handle as inline content, and return string. - * * 'BLOCKS': Always handle as blocks, and return array of blocks. - * @param {Array} [options.tagName] The tag into which content will be inserted. - * @param {boolean} [options.canUserUseUnfilteredHTML] Whether or not the user can use unfiltered HTML. - * - * @return {Array|string} A list of blocks or a string, depending on `handlerMode`. - */ -export function pasteHandler( { HTML = '', plainText = '', mode = 'AUTO', tagName, canUserUseUnfilteredHTML = false } ) { - // First of all, strip any meta tags. - HTML = HTML.replace( /<meta[^>]+>/, '' ); - - // If we detect block delimiters, parse entirely as blocks. - if ( mode !== 'INLINE' && HTML.indexOf( '<!-- wp:' ) !== -1 ) { - return parseWithGrammar( HTML ); - } - - // Normalize unicode to use composed characters. - // This is unsupported in IE 11 but it's a nice-to-have feature, not mandatory. - // Not normalizing the content will only affect older browsers and won't - // entirely break the app. - // See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize - // See: https://core.trac.wordpress.org/ticket/30130 - // See: https://github.com/WordPress/gutenberg/pull/6983#pullrequestreview-125151075 - if ( String.prototype.normalize ) { - HTML = HTML.normalize(); - } - - // Parse Markdown (and encoded HTML) if: - // * There is a plain text version. - // * There is no HTML version, or it has no formatting. - if ( plainText && ( ! HTML || isPlain( HTML ) ) ) { - HTML = markdownConverter( plainText ); - - // Switch to inline mode if: - // * The current mode is AUTO. - // * The original plain text had no line breaks. - // * The original plain text was not an HTML paragraph. - // * The converted text is just a paragraph. - if ( - mode === 'AUTO' && - plainText.indexOf( '\n' ) === -1 && - plainText.indexOf( '<p>' ) !== 0 && - HTML.indexOf( '<p>' ) === 0 - ) { - mode = 'INLINE'; - } - } - - if ( mode === 'INLINE' ) { - return filterInlineHTML( HTML ); - } - - // An array of HTML strings and block objects. The blocks replace matched - // shortcodes. - const pieces = shortcodeConverter( HTML ); - - // The call to shortcodeConverter will always return more than one element - // if shortcodes are matched. The reason is when shortcodes are matched - // empty HTML strings are included. - const hasShortcodes = pieces.length > 1; - - if ( mode === 'AUTO' && ! hasShortcodes && isInlineContent( HTML, tagName ) ) { - return filterInlineHTML( HTML ); - } - - const rawTransforms = getRawTransformations(); - const phrasingContentSchema = getPhrasingContentSchema(); - const blockContentSchema = getBlockContentSchema( rawTransforms ); - - const blocks = compact( flatMap( pieces, ( piece ) => { - // Already a block from shortcode. - if ( typeof piece !== 'string' ) { - return piece; - } - - const filters = [ - msListConverter, - headRemover, - listReducer, - imageCorrector, - phrasingContentReducer, - specialCommentConverter, - figureContentReducer, - blockquoteNormaliser, - ]; - - if ( ! canUserUseUnfilteredHTML ) { - // Should run before `figureContentReducer`. - filters.unshift( iframeRemover ); - } - - const schema = { - ...blockContentSchema, - // Keep top-level phrasing content, normalised by `normaliseBlocks`. - ...phrasingContentSchema, - }; - - piece = deepFilterHTML( piece, filters, blockContentSchema ); - piece = removeInvalidHTML( piece, schema ); - piece = normaliseBlocks( piece ); - - // Allows us to ask for this information when we get a report. - console.log( 'Processed HTML piece:\n\n', piece ); - - return htmlToBlocks( { html: piece, rawTransforms } ); - } ) ); - - // If we're allowed to return inline content and there is only one block - // and the original plain text content does not have any line breaks, then - // treat it as inline paste. - if ( mode === 'AUTO' && blocks.length === 1 ) { - const trimmedPlainText = plainText.trim(); - - if ( trimmedPlainText !== '' && trimmedPlainText.indexOf( '\n' ) === -1 ) { - return removeInvalidHTML( - getBlockContent( blocks[ 0 ] ), - phrasingContentSchema - ); - } - } - - return blocks; -} - /** * Converts an HTML string to known blocks. * diff --git a/packages/blocks/src/api/raw-handling/index.native.js b/packages/blocks/src/api/raw-handling/index.native.js index fb0890bf2aecd..f7e468e3fe363 100644 --- a/packages/blocks/src/api/raw-handling/index.native.js +++ b/packages/blocks/src/api/raw-handling/index.native.js @@ -1 +1,2 @@ export { getPhrasingContentSchema } from './phrasing-content'; +export { pasteHandler } from './paste-handler'; diff --git a/packages/blocks/src/api/raw-handling/paste-handler.js b/packages/blocks/src/api/raw-handling/paste-handler.js new file mode 100644 index 0000000000000..cc14ace649b5a --- /dev/null +++ b/packages/blocks/src/api/raw-handling/paste-handler.js @@ -0,0 +1,240 @@ +/** + * External dependencies + */ +import { flatMap, filter, compact } from 'lodash'; + +/** + * Internal dependencies + */ +import { createBlock, getBlockTransforms, findTransform } from '../factory'; +import { getBlockContent } from '../serializer'; +import { getBlockAttributes, parseWithGrammar } from '../parser'; +import normaliseBlocks from './normalise-blocks'; +import specialCommentConverter from './special-comment-converter'; +import isInlineContent from './is-inline-content'; +import phrasingContentReducer from './phrasing-content-reducer'; +import headRemover from './head-remover'; +import msListConverter from './ms-list-converter'; +import listReducer from './list-reducer'; +import imageCorrector from './image-corrector'; +import blockquoteNormaliser from './blockquote-normaliser'; +import figureContentReducer from './figure-content-reducer'; +import shortcodeConverter from './shortcode-converter'; +import markdownConverter from './markdown-converter'; +import iframeRemover from './iframe-remover'; +import { getPhrasingContentSchema } from './phrasing-content'; +import { + deepFilterHTML, + isPlain, + removeInvalidHTML, + getBlockContentSchema, +} from './utils'; + +/** + * Browser dependencies + */ +const { console } = window; + +/** + * Filters HTML to only contain phrasing content. + * + * @param {string} HTML The HTML to filter. + * + * @return {string} HTML only containing phrasing content. + */ +function filterInlineHTML( HTML ) { + HTML = deepFilterHTML( HTML, [ phrasingContentReducer ] ); + HTML = removeInvalidHTML( HTML, getPhrasingContentSchema(), { inline: true } ); + + // Allows us to ask for this information when we get a report. + console.log( 'Processed inline HTML:\n\n', HTML ); + + return HTML; +} + +function getRawTransformations() { + return filter( getBlockTransforms( 'from' ), { type: 'raw' } ) + .map( ( transform ) => { + return transform.isMatch ? transform : { + ...transform, + isMatch: ( node ) => transform.selector && node.matches( transform.selector ), + }; + } ); +} + +/** + * Converts HTML directly to blocks. Looks for a matching transform for each + * top-level tag. The HTML should be filtered to not have any text between + * top-level tags and formatted in a way that blocks can handle the HTML. + * + * @param {Object} $1 Named parameters. + * @param {string} $1.html HTML to convert. + * @param {Array} $1.rawTransforms Transforms that can be used. + * + * @return {Array} An array of blocks. + */ +function htmlToBlocks( { html, rawTransforms } ) { + const doc = document.implementation.createHTMLDocument( '' ); + + doc.body.innerHTML = html; + + return Array.from( doc.body.children ).map( ( node ) => { + const rawTransform = findTransform( rawTransforms, ( { isMatch } ) => isMatch( node ) ); + + if ( ! rawTransform ) { + return createBlock( + // Should not be hardcoded. + 'core/html', + getBlockAttributes( + 'core/html', + node.outerHTML + ) + ); + } + + const { transform, blockName } = rawTransform; + + if ( transform ) { + return transform( node ); + } + + return createBlock( + blockName, + getBlockAttributes( + blockName, + node.outerHTML + ) + ); + } ); +} + +/** + * Converts an HTML string to known blocks. Strips everything else. + * + * @param {string} [options.HTML] The HTML to convert. + * @param {string} [options.plainText] Plain text version. + * @param {string} [options.mode] Handle content as blocks or inline content. + * * 'AUTO': Decide based on the content passed. + * * 'INLINE': Always handle as inline content, and return string. + * * 'BLOCKS': Always handle as blocks, and return array of blocks. + * @param {Array} [options.tagName] The tag into which content will be inserted. + * @param {boolean} [options.canUserUseUnfilteredHTML] Whether or not the user can use unfiltered HTML. + * + * @return {Array|string} A list of blocks or a string, depending on `handlerMode`. + */ +export function pasteHandler( { HTML = '', plainText = '', mode = 'AUTO', tagName, canUserUseUnfilteredHTML = false } ) { + // First of all, strip any meta tags. + HTML = HTML.replace( /<meta[^>]+>/, '' ); + + // If we detect block delimiters, parse entirely as blocks. + if ( mode !== 'INLINE' && HTML.indexOf( '<!-- wp:' ) !== -1 ) { + return parseWithGrammar( HTML ); + } + + // Normalize unicode to use composed characters. + // This is unsupported in IE 11 but it's a nice-to-have feature, not mandatory. + // Not normalizing the content will only affect older browsers and won't + // entirely break the app. + // See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize + // See: https://core.trac.wordpress.org/ticket/30130 + // See: https://github.com/WordPress/gutenberg/pull/6983#pullrequestreview-125151075 + if ( String.prototype.normalize ) { + HTML = HTML.normalize(); + } + + // Parse Markdown (and encoded HTML) if: + // * There is a plain text version. + // * There is no HTML version, or it has no formatting. + if ( plainText && ( ! HTML || isPlain( HTML ) ) ) { + HTML = markdownConverter( plainText ); + + // Switch to inline mode if: + // * The current mode is AUTO. + // * The original plain text had no line breaks. + // * The original plain text was not an HTML paragraph. + // * The converted text is just a paragraph. + if ( + mode === 'AUTO' && + plainText.indexOf( '\n' ) === -1 && + plainText.indexOf( '<p>' ) !== 0 && + HTML.indexOf( '<p>' ) === 0 + ) { + mode = 'INLINE'; + } + } + + if ( mode === 'INLINE' ) { + return filterInlineHTML( HTML ); + } + + // An array of HTML strings and block objects. The blocks replace matched + // shortcodes. + const pieces = shortcodeConverter( HTML ); + + // The call to shortcodeConverter will always return more than one element + // if shortcodes are matched. The reason is when shortcodes are matched + // empty HTML strings are included. + const hasShortcodes = pieces.length > 1; + + if ( mode === 'AUTO' && ! hasShortcodes && isInlineContent( HTML, tagName ) ) { + return filterInlineHTML( HTML ); + } + + const rawTransforms = getRawTransformations(); + const phrasingContentSchema = getPhrasingContentSchema(); + const blockContentSchema = getBlockContentSchema( rawTransforms ); + + const blocks = compact( flatMap( pieces, ( piece ) => { + // Already a block from shortcode. + if ( typeof piece !== 'string' ) { + return piece; + } + + const filters = [ + msListConverter, + headRemover, + listReducer, + imageCorrector, + phrasingContentReducer, + specialCommentConverter, + figureContentReducer, + blockquoteNormaliser, + ]; + + if ( ! canUserUseUnfilteredHTML ) { + // Should run before `figureContentReducer`. + filters.unshift( iframeRemover ); + } + + const schema = { + ...blockContentSchema, + // Keep top-level phrasing content, normalised by `normaliseBlocks`. + ...phrasingContentSchema, + }; + + piece = deepFilterHTML( piece, filters, blockContentSchema ); + piece = removeInvalidHTML( piece, schema ); + piece = normaliseBlocks( piece ); + + // Allows us to ask for this information when we get a report. + console.log( 'Processed HTML piece:\n\n', piece ); + + return htmlToBlocks( { html: piece, rawTransforms } ); + } ) ); + + // If we're allowed to return inline content and there is only one block + // and the original plain text content does not have any line breaks, then + // treat it as inline paste. + if ( mode === 'AUTO' && blocks.length === 1 ) { + const trimmedPlainText = plainText.trim(); + + if ( trimmedPlainText !== '' && trimmedPlainText.indexOf( '\n' ) === -1 ) { + return removeInvalidHTML( + getBlockContent( blocks[ 0 ] ), + phrasingContentSchema + ); + } + } + + return blocks; +} diff --git a/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js b/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js index 5237c28495ee7..defdbe1eb97dc 100644 --- a/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js +++ b/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js @@ -4,7 +4,9 @@ import { wrap, replaceTag } from '@wordpress/dom'; export default function( node, doc ) { - if ( node.nodeName === 'SPAN' ) { + // In jsdom-jscore, 'node.style' can be null. + // TODO: Explore fixing this by patching jsdom-jscore. + if ( node.nodeName === 'SPAN' && node.style ) { const { fontWeight, fontStyle, @@ -34,7 +36,9 @@ export default function( node, doc ) { } else if ( node.nodeName === 'I' ) { node = replaceTag( node, 'em' ); } else if ( node.nodeName === 'A' ) { - if ( node.target.toLowerCase() === '_blank' ) { + // In jsdom-jscore, 'node.target' can be null. + // TODO: Explore fixing this by patching jsdom-jscore. + if ( node.target && node.target.toLowerCase() === '_blank' ) { node.rel = 'noreferrer noopener'; } else { node.removeAttribute( 'target' ); diff --git a/packages/blocks/src/api/raw-handling/utils.js b/packages/blocks/src/api/raw-handling/utils.js index 90e6f463ad465..4395c82c429dc 100644 --- a/packages/blocks/src/api/raw-handling/utils.js +++ b/packages/blocks/src/api/raw-handling/utils.js @@ -203,7 +203,9 @@ function cleanNodeList( nodeList, doc, schema, inline ) { } ); // Strip invalid classes. - if ( node.classList.length ) { + // In jsdom-jscore, 'node.classList' can be undefined. + // TODO: Explore patching this in jsdom-jscore. + if ( node.classList && node.classList.length ) { const mattchers = classes.map( ( item ) => { if ( typeof item === 'string' ) { return ( className ) => className === item; diff --git a/packages/dom/package.json b/packages/dom/package.json index 5577a2dc31ddc..6e94a8fc0dae5 100644 --- a/packages/dom/package.json +++ b/packages/dom/package.json @@ -19,6 +19,7 @@ }, "main": "build/index.js", "module": "build-module/index.js", + "react-native": "src/index", "dependencies": { "@babel/runtime": "^7.3.1", "lodash": "^4.17.11" diff --git a/packages/editor/src/components/rich-text/index.native.js b/packages/editor/src/components/rich-text/index.native.js index a97c37f248e05..9ada8911699a0 100644 --- a/packages/editor/src/components/rich-text/index.native.js +++ b/packages/editor/src/components/rich-text/index.native.js @@ -12,14 +12,19 @@ import { withInstanceId, compose } from '@wordpress/compose'; import { BlockFormatControls } from '@wordpress/editor'; import { withSelect } from '@wordpress/data'; import { + applyFormat, getActiveFormat, isEmpty, create, split, toHTMLString, + insert, + isCollapsed, } from '@wordpress/rich-text'; +import { decodeEntities } from '@wordpress/html-entities'; import { BACKSPACE } from '@wordpress/keycodes'; -import { children } from '@wordpress/blocks'; +import { pasteHandler, children } from '@wordpress/blocks'; +import { isURL } from '@wordpress/url'; /** * Internal dependencies @@ -50,6 +55,7 @@ export class RichText extends Component { this.onChange = this.onChange.bind( this ); this.onEnter = this.onEnter.bind( this ); this.onBackspace = this.onBackspace.bind( this ); + this.onPaste = this.onPaste.bind( this ); this.onContentSizeChange = this.onContentSizeChange.bind( this ); this.onFormatChange = this.onFormatChange.bind( this ); // This prevents a bug in Aztec which triggers onSelectionChange twice on format change @@ -84,25 +90,15 @@ export class RichText extends Component { * handler. * */ - splitContent( htmlText, start, end ) { + splitContent( currentRecord, blocks = [], isPasted = false ) { const { onSplit } = this.props; if ( ! onSplit ) { return; } - const record = create( { - html: htmlText, - range: null, - multilineTag: false, - removeNode: null, - unwrapNode: null, - removeAttribute: null, - filterString: null, - } ); - // TODO : Fix the index position in AztecNative for Android - let [ before, after ] = split( { start, end, ...record } ); + let [ before, after ] = split( currentRecord ); // In case split occurs at the trailing or leading edge of the field, // assume that the before/after values respectively reflect the current @@ -110,9 +106,16 @@ export class RichText extends Component { // determine whether the before/after value has changed using a trivial // strict equality operation. if ( isEmpty( after ) ) { - before = record; + before = currentRecord; } else if ( isEmpty( before ) ) { - after = record; + after = currentRecord; + } + + // If pasting and the split would result in no content other than the + // pasted blocks, remove the before and after blocks. + if ( isPasted ) { + before = isEmpty( before ) ? null : before; + after = isEmpty( after ) ? null : after; } if ( before ) { @@ -129,7 +132,7 @@ export class RichText extends Component { // always update when provided with new content. this.lastEventCount = undefined; - onSplit( before, after ); + onSplit( before, after, ...blocks ); } valueToFormat( { formats, text } ) { @@ -225,7 +228,12 @@ export class RichText extends Component { return; } - this.splitContent( unescapeSpaces( event.nativeEvent.text ), event.nativeEvent.selectionStart, event.nativeEvent.selectionEnd ); + const currentRecord = this.createRecord( { + ...event.nativeEvent, + currentContent: unescapeSpaces( event.nativeEvent.text ), + } ); + + this.splitContent( currentRecord ); } // eslint-disable-next-line no-unused-vars @@ -253,6 +261,85 @@ export class RichText extends Component { } } + /** + * Handles a paste event from the native Aztec Wrapper. + * + * @param {PasteEvent} event The paste event which wraps `nativeEvent`. + */ + onPaste( event ) { + const isPasted = true; + const { onSplit } = this.props; + + const { pastedText, pastedHtml } = event.nativeEvent; + const currentRecord = this.createRecord( event.nativeEvent ); + + event.preventDefault(); + + // There is a selection, check if a URL is pasted. + if ( ! isCollapsed( currentRecord ) ) { + const trimmedText = ( pastedHtml || pastedText ).replace( /<[^>]+>/g, '' ) + .trim(); + + // A URL was pasted, turn the selection into a link + if ( isURL( trimmedText ) ) { + const linkedRecord = applyFormat( currentRecord, { + type: 'a', + attributes: { + href: decodeEntities( trimmedText ), + }, + } ); + this.lastContent = this.valueToFormat( linkedRecord ); + this.props.onChange( { + content: this.lastContent, + } ); + + // Allows us to ask for this information when we get a report. + window.console.log( 'Created link:\n\n', trimmedText ); + + return; + } + } + + const shouldReplace = this.props.onReplace && this.isEmpty(); + + let mode = 'INLINE'; + + if ( shouldReplace ) { + mode = 'BLOCKS'; + } else if ( onSplit ) { + mode = 'AUTO'; + } + + const pastedContent = pasteHandler( { + HTML: pastedHtml, + plainText: pastedText, + mode, + tagName: this.props.tagName, + canUserUseUnfilteredHTML: this.props.canUserUseUnfilteredHTML, + } ); + + if ( typeof pastedContent === 'string' ) { + const recordToInsert = create( { html: pastedContent } ); + const insertedContent = insert( currentRecord, recordToInsert ); + const newContent = this.valueToFormat( insertedContent ); + this.lastEventCount = undefined; + this.lastContent = newContent; + this.props.onChange( { + content: this.lastContent, + } ); + } else if ( onSplit ) { + if ( ! pastedContent.length ) { + return; + } + + if ( shouldReplace ) { + this.props.onReplace( pastedContent ); + } else { + this.splitContent( currentRecord, pastedContent, isPasted ); + } + } + } + onSelectionChange( start, end, text, event ) { // `end` can be less than `start` on iOS // Let's fix that here so `rich-text/slice` can work properly @@ -283,6 +370,36 @@ export class RichText extends Component { return isEmpty( this.formatToValue( this.props.value ) ); } + /** + * Creates a RichText value "record" from native content and selection + * information + * + * @param {string} currentContent The content (usually an HTML string) from + * the native component. + * @param {number} selectionStart The start of the selection. + * @param {number} selectionEnd The end of the selection (same as start if + * cursor instead of selection). + * + * @return {Object} A RichText value with formats and selection. + */ + createRecord( { currentContent, selectionStart, selectionEnd } ) { + // strip outer <p> tags + const innerContent = this.removeRootTagsProduceByAztec( currentContent ); + + // create record (with selection) from current contents + const currentRecord = { + start: selectionStart, + end: selectionEnd, + ...create( { + html: innerContent, + range: null, + multilineTag: false, + } ), + }; + + return currentRecord; + } + formatToValue( value ) { // Handle deprecated `children` and `node` sources. if ( Array.isArray( value ) ) { @@ -390,6 +507,7 @@ export class RichText extends Component { onBlur={ this.props.onBlur } onEnter={ this.onEnter } onBackspace={ this.onBackspace } + onPaste={ this.onPaste } activeFormats={ this.getActiveFormatNames( record ) } onContentSizeChange={ this.onContentSizeChange } onCaretVerticalPositionChange={ this.props.onCaretVerticalPositionChange } diff --git a/packages/shortcode/package.json b/packages/shortcode/package.json index 128647ed2d35a..f110b0c3542ef 100644 --- a/packages/shortcode/package.json +++ b/packages/shortcode/package.json @@ -18,6 +18,7 @@ }, "main": "build/index.js", "module": "build-module/index.js", + "react-native": "src/index", "dependencies": { "@babel/runtime": "^7.3.1", "lodash": "^4.17.11", From 7858cb2b01ba5985f434af9053a9ce67144bfdbb Mon Sep 17 00:00:00 2001 From: Karol Gorski <naerriel@gmail.com> Date: Tue, 19 Feb 2019 14:01:42 +0100 Subject: [PATCH 476/691] Fix column moved in itself error (#13941) * Fix column moved in itself error * Update packages/editor/src/components/block-drop-zone/index.js Co-Authored-By: Naerriel <naerriel@gmail.com> --- packages/editor/src/components/block-drop-zone/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor/src/components/block-drop-zone/index.js b/packages/editor/src/components/block-drop-zone/index.js index 1a8bb142aae89..f1c95a53242a6 100644 --- a/packages/editor/src/components/block-drop-zone/index.js +++ b/packages/editor/src/components/block-drop-zone/index.js @@ -98,7 +98,7 @@ class BlockDropZone extends Component { if ( ! isBlockDropType( type ) || isSameBlock( srcClientId, dstClientId ) || - isSrcBlockAnAncestorOfDstBlock( srcClientId, dstClientId ) ) { + isSrcBlockAnAncestorOfDstBlock( srcClientId, dstClientId || dstRootClientId ) ) { return; } From ad9bbe5fbe65522490ba4ad8b659b17eaae307de Mon Sep 17 00:00:00 2001 From: Sami Keijonen <sami.keijonen@foxnet.fi> Date: Tue, 19 Feb 2019 15:15:59 +0200 Subject: [PATCH 477/691] Update button wording (#13933) * Update button block description by using wording about link. * Add keyword: link. * Update index.js --- packages/block-library/src/button/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/button/index.js b/packages/block-library/src/button/index.js index 9c92a4cda6828..cbc60718bb716 100644 --- a/packages/block-library/src/button/index.js +++ b/packages/block-library/src/button/index.js @@ -64,12 +64,14 @@ const colorsMigration = ( attributes ) => { export const settings = { title: __( 'Button' ), - description: __( 'Prompt visitors to take action with a custom button.' ), + description: __( 'Prompt visitors to take action with a button-style link.' ), icon: <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><G><Path d="M19 6H5c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 10H5V8h14v8z" /></G></SVG>, category: 'layout', + keywords: [ __( 'link' ) ], + attributes: blockAttributes, supports: { From 52a0f69d1bc016be885a9fd26bf77981903d2b0d Mon Sep 17 00:00:00 2001 From: Grzegorz Ziolkowski <grzegorz@gziolo.pl> Date: Tue, 19 Feb 2019 15:52:09 +0100 Subject: [PATCH 478/691] Remove gziolo from rich text in CODEOWNERS file --- .github/CODEOWNERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 18c7ac39ce1be..dcd01eefb79cc 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -70,8 +70,8 @@ # Rich Text /packages/format-library @youknowriad @gziolo @aduth @iseulde @jorgefilipecosta -/packages/rich-text @youknowriad @gziolo @aduth @iseulde @jorgefilipecosta -/packages/editor/src/components/rich-text @youknowriad @gziolo @aduth @iseulde @jorgefilipecosta +/packages/rich-text @youknowriad @aduth @iseulde @jorgefilipecosta +/packages/editor/src/components/rich-text @youknowriad @aduth @iseulde @jorgefilipecosta # PHP /lib @youknowriad @gziolo @aduth From 8e18cafdf823c274c8f81ae16ca7b1cbf7f062f3 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell@kjellr.com> Date: Tue, 19 Feb 2019 10:30:50 -0500 Subject: [PATCH 479/691] Switch the custom color control to a text link, fix padding for color picker. (#13708) * Change custom color control into a text link. Changes the custom color control into a text link instead of a multi-colored palette icon. Updates styles to properly line it up to the left of the clear button. * Add missing top/bottom padding to color picker controls. * Update snapshot * Remove unnecessary tooltip on the color picker button. * Update snapshot. * Update test + snapshot * Remove unnecessary type declaration for the toggle button. --- .../components/src/color-palette/index.js | 61 +++++++++---------- .../components/src/color-palette/style.scss | 54 +++------------- .../test/__snapshots__/index.js.snap | 60 ++++++++++-------- .../src/color-palette/test/index.js | 2 +- .../components/src/color-picker/style.scss | 2 +- 5 files changed, 75 insertions(+), 104 deletions(-) diff --git a/packages/components/src/color-palette/index.js b/packages/components/src/color-palette/index.js index 99f76c99d64cf..054de52dfea83 100644 --- a/packages/components/src/color-palette/index.js +++ b/packages/components/src/color-palette/index.js @@ -55,42 +55,41 @@ export default function ColorPalette( { colors, disableCustomColors = false, val ); } ) } - { ! disableCustomColors && - <Dropdown - className="components-color-palette__item-wrapper components-color-palette__custom-color" - contentClassName="components-color-palette__picker" - renderToggle={ ( { isOpen, onToggle } ) => ( - <Tooltip text={ customColorPickerLabel }> - <button - type="button" + <div className="components-color-palette__custom-clear-wrapper"> + { ! disableCustomColors && + <Dropdown + className="components-color-palette__custom-color" + contentClassName="components-color-palette__picker" + renderToggle={ ( { isOpen, onToggle } ) => ( + <Button aria-expanded={ isOpen } - className="components-color-palette__item" onClick={ onToggle } aria-label={ customColorPickerLabel } + isLink > - <span className="components-color-palette__custom-color-gradient" /> - </button> - </Tooltip> - ) } - renderContent={ () => ( - <ColorPicker - color={ value } - onChangeComplete={ ( color ) => onChange( color.hex ) } - disableAlpha - /> - ) } - /> - } + { __( 'Custom Color' ) } + </Button> + ) } + renderContent={ () => ( + <ColorPicker + color={ value } + onChangeComplete={ ( color ) => onChange( color.hex ) } + disableAlpha + /> + ) } + /> + } - <Button - className="components-color-palette__clear" - type="button" - onClick={ () => onChange( undefined ) } - isSmall - isDefault - > - { __( 'Clear' ) } - </Button> + <Button + className="components-color-palette__clear" + type="button" + onClick={ () => onChange( undefined ) } + isSmall + isDefault + > + { __( 'Clear' ) } + </Button> + </div> </div> ); } diff --git a/packages/components/src/color-palette/style.scss b/packages/components/src/color-palette/style.scss index b5c5d9686e25f..a2aace0b31b46 100644 --- a/packages/components/src/color-palette/style.scss +++ b/packages/components/src/color-palette/style.scss @@ -3,10 +3,12 @@ $color-palette-circle-spacing: 14px; .components-color-palette { margin-right: -14px; + width: calc(100% + 14px); - .components-color-palette__clear { - float: right; - margin-right: 20px; + .components-color-palette__custom-clear-wrapper { + width: calc(100% - 14px); + display: flex; + justify-content: flex-end; } } @@ -110,48 +112,12 @@ $color-palette-circle-spacing: 14px; } } -.components-color-palette__custom-color .components-color-palette__item { - position: relative; - box-shadow: none; -} +.components-color-palette__custom-color { + margin-right: $grid-size-large; -.components-color-palette__custom-color .components-color-palette__custom-color-gradient { - display: block; - width: 100%; - height: 100%; - position: absolute; - top: 0; - left: 0; - border-radius: 50%; - overflow: hidden; -} - -.components-color-palette__custom-color .components-color-palette__custom-color-gradient::before { - content: ""; - filter: blur(6px) saturate(0.7) brightness(1.1); - display: block; - width: 200%; - height: 200%; - position: absolute; - top: -50%; - left: -50%; - padding-top: 100%; - transform: scale(1); - background-image: - linear-gradient(330deg, transparent 50%, #ff8100 50%), - linear-gradient(300deg, transparent 50%, #ff5800 50%), - linear-gradient(270deg, transparent 50%, #c92323 50%), - linear-gradient(240deg, transparent 50%, #cc42a2 50%), - linear-gradient(210deg, transparent 50%, #9f49ac 50%), - linear-gradient(180deg, transparent 50%, #306cd3 50%), - linear-gradient(150deg, transparent 50%, #179067 50%), - linear-gradient(120deg, transparent 50%, #0eb5d6 50%), - linear-gradient(90deg, transparent 50%, #50b517 50%), - linear-gradient(60deg, transparent 50%, #ede604 50%), - linear-gradient(30deg, transparent 50%, #fc0 50%), - linear-gradient(0deg, transparent 50%, #feac00 50%); - - background-clip: content-box, content-box, content-box, content-box, content-box, content-box, padding-box, padding-box, padding-box, padding-box, padding-box, padding-box; + .components-button { + line-height: 22px; + } } .block-editor__container .components-popover.components-color-palette__picker.is-bottom { diff --git a/packages/components/src/color-palette/test/__snapshots__/index.js.snap b/packages/components/src/color-palette/test/__snapshots__/index.js.snap index cbb6798ae5444..3a49ace1959ad 100644 --- a/packages/components/src/color-palette/test/__snapshots__/index.js.snap +++ b/packages/components/src/color-palette/test/__snapshots__/index.js.snap @@ -90,19 +90,17 @@ exports[`ColorPalette Dropdown .renderToggle should render dropdown content 1`] <button aria-expanded={true} aria-label="Custom color picker" - className="components-color-palette__item" + className="components-button is-link" onClick={[MockFunction]} type="button" > - <span - className="components-color-palette__custom-color-gradient" - /> + Custom Color </button> `; exports[`ColorPalette Dropdown should render it correctly 1`] = ` <Dropdown - className="components-color-palette__item-wrapper components-color-palette__custom-color" + className="components-color-palette__custom-color" contentClassName="components-color-palette__picker" renderContent={[Function]} renderToggle={[Function]} @@ -179,15 +177,19 @@ exports[`ColorPalette should allow disabling custom color picker 1`] = ` /> </Tooltip> </div> - <Button - className="components-color-palette__clear" - isDefault={true} - isSmall={true} - onClick={[Function]} - type="button" + <div + className="components-color-palette__custom-clear-wrapper" > - Clear - </Button> + <Button + className="components-color-palette__clear" + isDefault={true} + isSmall={true} + onClick={[Function]} + type="button" + > + Clear + </Button> + </div> </div> `; @@ -261,20 +263,24 @@ exports[`ColorPalette should render a dynamic toolbar of colors 1`] = ` /> </Tooltip> </div> - <Dropdown - className="components-color-palette__item-wrapper components-color-palette__custom-color" - contentClassName="components-color-palette__picker" - renderContent={[Function]} - renderToggle={[Function]} - /> - <Button - className="components-color-palette__clear" - isDefault={true} - isSmall={true} - onClick={[Function]} - type="button" + <div + className="components-color-palette__custom-clear-wrapper" > - Clear - </Button> + <Dropdown + className="components-color-palette__custom-color" + contentClassName="components-color-palette__picker" + renderContent={[Function]} + renderToggle={[Function]} + /> + <Button + className="components-color-palette__clear" + isDefault={true} + isSmall={true} + onClick={[Function]} + type="button" + > + Clear + </Button> + </div> </div> `; diff --git a/packages/components/src/color-palette/test/index.js b/packages/components/src/color-palette/test/index.js index ef1263535d0f4..8983537ed7dc1 100644 --- a/packages/components/src/color-palette/test/index.js +++ b/packages/components/src/color-palette/test/index.js @@ -71,7 +71,7 @@ describe( 'ColorPalette', () => { const isOpen = true; const onToggle = jest.fn(); - const renderedToggleButton = shallow( dropdown.props().renderToggle( { isOpen, onToggle } ).props.children ); + const renderedToggleButton = shallow( dropdown.props().renderToggle( { isOpen, onToggle } ) ); test( 'should render dropdown content', () => { expect( renderedToggleButton ).toMatchSnapshot(); diff --git a/packages/components/src/color-picker/style.scss b/packages/components/src/color-picker/style.scss index 368c6f8a70321..8abda3ac27589 100644 --- a/packages/components/src/color-picker/style.scss +++ b/packages/components/src/color-picker/style.scss @@ -35,7 +35,7 @@ position: relative; } .components-color-picker__body { - padding: $grid-size-large 0 #{ $grid-size-small * 3 }; + padding: $grid-size-large $grid-size-large #{ $grid-size-small * 3 }; } .components-color-picker__controls { display: flex; From 929afdd88a0e2855048ca2c098cbdb623cf27a4a Mon Sep 17 00:00:00 2001 From: Marty Helmick <info@martyhelmick.com> Date: Tue, 19 Feb 2019 11:36:30 -0500 Subject: [PATCH 480/691] Match height of font range control with the size select. (#11555) * Match height of font range control with the size select. This also aligns the range up/down controls to the right side of the input. * update font range control inline docs * update with master * bring up to date with master branch * wider range number controls to account for OS specific input spinners. * Use the same height as our other number inputs. * wider number inputs to account for OS specific spinners. * Revert "wider range number controls to account for OS specific input spinners." This reverts commit d26c6595dd46ae101cde8c3065a9e438db3b8c25. * make number input styles more consistent across components * refresh with upstream * smaller changes --- packages/components/src/font-size-picker/style.scss | 3 +-- packages/components/src/range-control/style.scss | 3 +-- packages/edit-post/src/style.scss | 5 +++++ 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/components/src/font-size-picker/style.scss b/packages/components/src/font-size-picker/style.scss index 6435f4dfd6dee..1841375ccf397 100644 --- a/packages/components/src/font-size-picker/style.scss +++ b/packages/components/src/font-size-picker/style.scss @@ -6,8 +6,7 @@ // Apply the same height as the isSmall Reset button. .components-range-control__number { - height: 24px; - line-height: 22px; + height: 30px; margin-left: 0; // Show the reset button as disabled until a value is entered. diff --git a/packages/components/src/range-control/style.scss b/packages/components/src/range-control/style.scss index 005ccc40100e6..e3dfca023ca83 100644 --- a/packages/components/src/range-control/style.scss +++ b/packages/components/src/range-control/style.scss @@ -121,6 +121,5 @@ display: inline-block; margin-left: $grid-size; font-weight: 500; - width: 50px; - padding: 3px 5px !important; + width: 54px; } diff --git a/packages/edit-post/src/style.scss b/packages/edit-post/src/style.scss index 83f2a77745458..bec0e8484c580 100644 --- a/packages/edit-post/src/style.scss +++ b/packages/edit-post/src/style.scss @@ -188,6 +188,11 @@ body.block-editor-page { } } + input[type="number"] { + padding-left: 4px; + padding-right: 4px; + } + select { padding: 2px; From a82c257641739fcdec7ed910ad5ddf0cafee9658 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Tue, 19 Feb 2019 14:46:07 -0500 Subject: [PATCH 481/691] Data: Fix persistence initial state merging behavior (#13951) * Data: Fix inaccurate persistence plugin documentation * Data: Leave unpersisted keys intact in initial persisted state * Data: Add initialState option for namespace stores * Data: Use initialState option for persistence restore * Data: Deeply merge into persistence default value * Data: Remove outdated code comment for effecting initialState * Data: Persistence: Defer to default initial state in object-like mismatch * Data: Persistence: Revert to persisted value as preferred Merge if possible when both it and the default initial state are objects --- packages/data/CHANGELOG.md | 11 ++ packages/data/README.md | 4 + packages/data/src/namespace-store.js | 18 +- .../data/src/plugins/persistence/README.md | 2 +- .../data/src/plugins/persistence/index.js | 42 ++--- .../src/plugins/persistence/test/index.js | 155 +++++++++++++++--- packages/data/src/test/registry.js | 3 +- 7 files changed, 186 insertions(+), 49 deletions(-) diff --git a/packages/data/CHANGELOG.md b/packages/data/CHANGELOG.md index f3b1f067ed648..1a05bfd4891ca 100644 --- a/packages/data/CHANGELOG.md +++ b/packages/data/CHANGELOG.md @@ -1,3 +1,14 @@ +## 4.3.0 (Unreleased) + +### Enhancements + +- The `registerStore` function now accepts an optional `initialState` option value. + +### Bug Fix + +- Resolves issue in the persistence plugin where passing `persist` as an array of reducer keys would wrongly replace state values for the unpersisted reducer keys. +- Restores a behavior in the persistence plugin where a default state provided as an object will be deeply merged as a base for the persisted value. This allows for a developer to include additional new keys in a persisted value default in future iterations of their store. + ## 4.2.0 (2019-01-03) ### Enhancements diff --git a/packages/data/README.md b/packages/data/README.md index 8e3ef328516ec..47082c451ec70 100644 --- a/packages/data/README.md +++ b/packages/data/README.md @@ -139,6 +139,10 @@ The `controls` option should be passed as an object where each key is the name o Refer to the [documentation of `@wordpress/redux-routine`](/packages/redux-routine/README.md) for more information. +### `initialState` + +An optional preloaded initial state for the store. You may use this to restore some serialized state value or a state generated server-side. + ## Data Access and Manipulation It is very rare that you should access store methods directly. Instead, the following suite of functions and higher-order components is provided for the most common data access and manipulation needs. diff --git a/packages/data/src/namespace-store.js b/packages/data/src/namespace-store.js index 510e218152e81..97f1e2eae1b38 100644 --- a/packages/data/src/namespace-store.js +++ b/packages/data/src/namespace-store.js @@ -25,7 +25,7 @@ import createResolversCacheMiddleware from './resolvers-cache-middleware'; */ export default function createNamespace( key, options, registry ) { const reducer = options.reducer; - const store = createReduxStore( reducer, key, registry ); + const store = createReduxStore( key, options, registry ); let selectors, actions, resolvers; if ( options.actions ) { @@ -76,13 +76,14 @@ export default function createNamespace( key, options, registry ) { /** * Creates a redux store for a namespace. * - * @param {Function} reducer Root reducer for redux store. - * @param {string} key Part of the state shape to register the - * selectors for. - * @param {Object} registry Registry reference, for resolver enhancer support. - * @return {Object} Newly created redux store. + * @param {string} key Part of the state shape to register the + * selectors for. + * @param {Object} options Registered store options. + * @param {Object} registry Registry reference, for resolver enhancer support. + * + * @return {Object} Newly created redux store. */ -function createReduxStore( reducer, key, registry ) { +function createReduxStore( key, options, registry ) { const enhancers = [ applyMiddleware( createResolversCacheMiddleware( registry, key ), promise ), ]; @@ -90,7 +91,8 @@ function createReduxStore( reducer, key, registry ) { enhancers.push( window.__REDUX_DEVTOOLS_EXTENSION__( { name: key, instanceId: key } ) ); } - return createStore( reducer, flowRight( enhancers ) ); + const { reducer, initialState } = options; + return createStore( reducer, initialState, flowRight( enhancers ) ); } /** diff --git a/packages/data/src/plugins/persistence/README.md b/packages/data/src/plugins/persistence/README.md index ed21472ee595d..f3c5bced35804 100644 --- a/packages/data/src/plugins/persistence/README.md +++ b/packages/data/src/plugins/persistence/README.md @@ -3,7 +3,7 @@ Persistence Plugin The persistence plugin enhances a registry to enable registered stores to opt in to persistent storage. -By default, persistence occurs by [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage). This can be changed using the [`setStorage` function](#api) defined on the plugin. Unless set otherwise, state will be persisted on the `WP_DATA` key in storage. +By default, persistence occurs by [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage). In environments where `localStorage` is not available, it will gracefully fall back to an in-memory object storage which will not persist between sessions. You can provide your own storage implementation by providing the [`storage` option](#options). Unless set otherwise, state will be persisted on the `WP_DATA` key in storage. ## Usage diff --git a/packages/data/src/plugins/persistence/index.js b/packages/data/src/plugins/persistence/index.js index 3ae854b2ca6cd..4a286e65ca71e 100644 --- a/packages/data/src/plugins/persistence/index.js +++ b/packages/data/src/plugins/persistence/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { flow } from 'lodash'; +import { flow, merge, isPlainObject } from 'lodash'; /** * Internal dependencies @@ -34,20 +34,6 @@ const DEFAULT_STORAGE = defaultStorage; */ const DEFAULT_STORAGE_KEY = 'WP_DATA'; -/** - * Higher-order reducer to provides an initial value when state is undefined. - * - * @param {Function} reducer Original reducer. - * @param {*} initialState Value to use as initial state. - * - * @return {Function} Enhanced reducer. - */ -export function withInitialState( reducer, initialState ) { - return ( state = initialState, action ) => { - return reducer( state, action ); - }; -} - /** * Higher-order reducer which invokes the original reducer only if state is * inequal from that of the action's `nextState` property, otherwise returning @@ -178,12 +164,28 @@ export default function( registry, pluginOptions ) { return registry.registerStore( reducerKey, options ); } - const initialState = persistence.get()[ reducerKey ]; + // Load from persistence to use as initial state. + const persistedState = persistence.get()[ reducerKey ]; + if ( persistedState !== undefined ) { + let initialState = options.reducer( undefined, { + type: '@@WP/PERSISTENCE_RESTORE', + } ); + + if ( isPlainObject( initialState ) && isPlainObject( persistedState ) ) { + // If state is an object, ensure that: + // - Other keys are left intact when persisting only a + // subset of keys. + // - New keys in what would otherwise be used as initial + // state are deeply merged as base for persisted value. + initialState = merge( {}, initialState, persistedState ); + } else { + // If there is a mismatch in object-likeness of default + // initial or persisted state, defer to persisted value. + initialState = persistedState; + } - options = { - ...options, - reducer: withInitialState( options.reducer, initialState ), - }; + options = { ...options, initialState }; + } const store = registry.registerStore( reducerKey, options ); diff --git a/packages/data/src/plugins/persistence/test/index.js b/packages/data/src/plugins/persistence/test/index.js index 55a9fa79d459e..b66ebe5f8154d 100644 --- a/packages/data/src/plugins/persistence/test/index.js +++ b/packages/data/src/plugins/persistence/test/index.js @@ -1,9 +1,13 @@ +/** + * External dependencies + */ +import deepFreeze from 'deep-freeze'; + /** * Internal dependencies */ import plugin, { createPersistenceInterface, - withInitialState, withLazySameState, } from '../'; import objectStorage from '../storage/object'; @@ -37,6 +41,137 @@ describe( 'persistence', () => { registry.registerStore( 'test', options ); } ); + it( 'should load a persisted value as initialState', () => { + registry = createRegistry().use( plugin, { + storage: { + getItem: () => JSON.stringify( { test: { a: 1 } } ), + setItem() {}, + }, + } ); + + registry.registerStore( 'test', { + persist: true, + reducer: ( state = {} ) => state, + selectors: { + getState: ( state ) => state, + }, + } ); + + expect( registry.select( 'test' ).getState() ).toEqual( { a: 1 } ); + } ); + + it( 'should load a persisted subset value as initialState', () => { + const DEFAULT_STATE = { a: null, b: null }; + + registry = createRegistry().use( plugin, { + storage: { + getItem: () => JSON.stringify( { test: { a: 1 } } ), + setItem() {}, + }, + } ); + + registry.registerStore( 'test', { + persist: [ 'a' ], + reducer: ( state = DEFAULT_STATE ) => state, + selectors: { + getState: ( state ) => state, + }, + } ); + + expect( registry.select( 'test' ).getState() ).toEqual( { a: 1, b: null } ); + } ); + + it( 'should merge persisted value with default if object-like', () => { + const DEFAULT_STATE = deepFreeze( { preferences: { useFoo: true, useBar: true } } ); + + registry = createRegistry().use( plugin, { + storage: { + getItem: () => JSON.stringify( { + test: { + preferences: { + useFoo: false, + }, + }, + } ), + setItem() {}, + }, + } ); + + registry.registerStore( 'test', { + persist: [ 'preferences' ], + reducer: ( state = DEFAULT_STATE ) => state, + selectors: { + getState: ( state ) => state, + }, + } ); + + expect( registry.select( 'test' ).getState() ).toEqual( { + preferences: { + useFoo: false, + useBar: true, + }, + } ); + } ); + + it( 'should defer to persisted state if mismatch of object-like (persisted object-like)', () => { + registry = createRegistry().use( plugin, { + storage: { + getItem: () => JSON.stringify( { test: { persisted: true } } ), + setItem() {}, + }, + } ); + + registry.registerStore( 'test', { + persist: true, + reducer: ( state = null ) => state, + selectors: { + getState: ( state ) => state, + }, + } ); + + expect( registry.select( 'test' ).getState() ).toEqual( { persisted: true } ); + } ); + + it( 'should defer to persisted state if mismatch of object-like (initial object-like)', () => { + registry = createRegistry().use( plugin, { + storage: { + getItem: () => JSON.stringify( { test: null } ), + setItem() {}, + }, + } ); + + registry.registerStore( 'test', { + persist: true, + reducer: ( state = {} ) => state, + selectors: { + getState: ( state ) => state, + }, + } ); + + expect( registry.select( 'test' ).getState() ).toBe( null ); + } ); + + it( 'should be reasonably tolerant to a non-object persisted state', () => { + registry = createRegistry().use( plugin, { + storage: { + getItem: () => JSON.stringify( { + test: 1, + } ), + setItem() {}, + }, + } ); + + registry.registerStore( 'test', { + persist: true, + reducer: ( state = null ) => state, + selectors: { + getState: ( state ) => state, + }, + } ); + + expect( registry.select( 'test' ).getState() ).toBe( 1 ); + } ); + it( 'override values passed to registerStore', () => { const options = { persist: true, reducer() {} }; @@ -46,8 +181,6 @@ describe( 'persistence', () => { persist: true, reducer: expect.any( Function ), } ); - // Replaced reducer: - expect( originalRegisterStore.mock.calls[ 0 ][ 1 ].reducer ).not.toBe( options.reducer ); } ); it( 'should not persist if option not passed', () => { @@ -208,22 +341,6 @@ describe( 'persistence', () => { } ); } ); - describe( 'withInitialState', () => { - it( 'should return a reducer function', () => { - const reducer = ( state = 1 ) => state; - const enhanced = withInitialState( reducer ); - - expect( enhanced() ).toBe( 1 ); - } ); - - it( 'should assign a default state by argument', () => { - const reducer = ( state = 1 ) => state; - const enhanced = withInitialState( reducer, 2 ); - - expect( enhanced() ).toBe( 2 ); - } ); - } ); - describe( 'withLazySameState', () => { it( 'should call the original reducer if action.nextState differs from state', () => { const reducer = jest.fn().mockImplementation( ( state, action ) => action.nextState ); diff --git a/packages/data/src/test/registry.js b/packages/data/src/test/registry.js index 752d21dedb721..cd18ac2d89285 100644 --- a/packages/data/src/test/registry.js +++ b/packages/data/src/test/registry.js @@ -151,7 +151,7 @@ describe( 'createRegistry', () => { describe( 'registerStore', () => { it( 'should be shorthand for reducer, actions, selectors registration', () => { const store = registry.registerStore( 'butcher', { - reducer( state = { ribs: 6, chicken: 4 }, action ) { + reducer( state = {}, action ) { switch ( action.type ) { case 'sale': return { @@ -162,6 +162,7 @@ describe( 'createRegistry', () => { return state; }, + initialState: { ribs: 6, chicken: 4 }, selectors: { getPrice: ( state, meat ) => state[ meat ], }, From 4e9b6b51caf315f7c71ee345a94793a35d83ac39 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Tue, 19 Feb 2019 14:46:23 -0500 Subject: [PATCH 482/691] Editor: Reimplement select previous/next as independent controls (#13924) * Block Editor: Reimplement removeBlocks previous selection as control * Editor: Reimplement select previous/next as independent controls * Refactor controls to actions * Editor: Pass initialPosition in selectPreviousBlock * Editor: Rename storeKey to storeName --- .../developers/data/data-core-editor.md | 20 ++++++- packages/editor/src/store/actions.js | 54 +++++++++++++++--- packages/editor/src/store/controls.js | 30 ++++++++++ packages/editor/src/store/effects.js | 43 +------------- packages/editor/src/store/index.js | 2 + packages/editor/src/store/test/actions.js | 56 ++++++++++++------- 6 files changed, 135 insertions(+), 70 deletions(-) create mode 100644 packages/editor/src/store/controls.js diff --git a/docs/designers-developers/developers/data/data-core-editor.md b/docs/designers-developers/developers/data/data-core-editor.md index 5f1c0500fe8df..96e65b41659de 100644 --- a/docs/designers-developers/developers/data/data-core-editor.md +++ b/docs/designers-developers/developers/data/data-core-editor.md @@ -1560,6 +1560,24 @@ reflects a reverse selection. * initialPosition: Optional initial position. Pass as -1 to reflect reverse selection. +### selectPreviousBlock + +Yields action objects used in signalling that the block preceding the given +clientId should be selected. + +*Parameters* + + * clientId: Block client ID. + +### selectNextBlock + +Yields action objects used in signalling that the block following the given +clientId should be selected. + +*Parameters* + + * clientId: Block client ID. + ### toggleSelection Returns an action object that enables or disables block selection. @@ -1703,7 +1721,7 @@ be created. ### removeBlocks -Returns an action object used in signalling that the blocks corresponding to +Yields action objects used in signalling that the blocks corresponding to the set of specified client IDs are to be removed. *Parameters* diff --git a/packages/editor/src/store/actions.js b/packages/editor/src/store/actions.js index 2043f21ed9a03..b65774fe4c219 100644 --- a/packages/editor/src/store/actions.js +++ b/packages/editor/src/store/actions.js @@ -8,6 +8,11 @@ import { castArray } from 'lodash'; */ import { getDefaultBlockName, createBlock } from '@wordpress/blocks'; +/** + * Internal dependencies + */ +import { select } from './controls'; + /** * Returns an action object used in signalling that editor has initialized with * the specified post object and editor settings. @@ -172,6 +177,38 @@ export function selectBlock( clientId, initialPosition = null ) { }; } +/** + * Yields action objects used in signalling that the block preceding the given + * clientId should be selected. + * + * @param {string} clientId Block client ID. + */ +export function* selectPreviousBlock( clientId ) { + const previousBlockClientId = yield select( + 'core/editor', + 'getPreviousBlockClientId', + clientId + ); + + yield selectBlock( previousBlockClientId, -1 ); +} + +/** + * Yields action objects used in signalling that the block following the given + * clientId should be selected. + * + * @param {string} clientId Block client ID. + */ +export function* selectNextBlock( clientId ) { + const nextBlockClientId = yield select( + 'core/editor', + 'getNextBlockClientId', + clientId + ); + + yield selectBlock( nextBlockClientId ); +} + export function startMultiSelect() { return { type: 'START_MULTI_SELECT', @@ -477,20 +514,23 @@ export function createUndoLevel() { } /** - * Returns an action object used in signalling that the blocks corresponding to + * Yields action objects used in signalling that the blocks corresponding to * the set of specified client IDs are to be removed. * * @param {string|string[]} clientIds Client IDs of blocks to remove. * @param {boolean} selectPrevious True if the previous block should be * selected when a block is removed. - * - * @return {Object} Action object. */ -export function removeBlocks( clientIds, selectPrevious = true ) { - return { +export function* removeBlocks( clientIds, selectPrevious = true ) { + clientIds = castArray( clientIds ); + + if ( selectPrevious ) { + yield selectPreviousBlock( clientIds[ 0 ] ); + } + + yield { type: 'REMOVE_BLOCKS', - clientIds: castArray( clientIds ), - selectPrevious, + clientIds, }; } diff --git a/packages/editor/src/store/controls.js b/packages/editor/src/store/controls.js new file mode 100644 index 0000000000000..5012ab244c21c --- /dev/null +++ b/packages/editor/src/store/controls.js @@ -0,0 +1,30 @@ +/** + * WordPress dependencies + */ +import { createRegistryControl } from '@wordpress/data'; + +/** + * Calls a selector using the current state. + * + * @param {string} storeName Store name. + * @param {string} selectorName Selector name. + * @param {Array} args Selector arguments. + * + * @return {Object} control descriptor. + */ +export function select( storeName, selectorName, ...args ) { + return { + type: 'SELECT', + storeName, + selectorName, + args, + }; +} + +const controls = { + SELECT: createRegistryControl( ( registry ) => ( { storeName, selectorName, args } ) => { + return registry.select( storeName )[ selectorName ]( ...args ); + } ), +}; + +export default controls; diff --git a/packages/editor/src/store/effects.js b/packages/editor/src/store/effects.js index 18e753d3f70bf..de9552ff5c9b6 100644 --- a/packages/editor/src/store/effects.js +++ b/packages/editor/src/store/effects.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { compact, last, has } from 'lodash'; +import { compact, has } from 'lodash'; /** * WordPress dependencies @@ -29,11 +29,8 @@ import { } from './actions'; import { getBlock, - getBlockRootClientId, getBlocks, getBlockCount, - getPreviousBlockClientId, - getSelectedBlockClientId, getSelectedBlockCount, getTemplate, getTemplateLock, @@ -86,43 +83,6 @@ export function validateBlocksToTemplate( action, store ) { } } -/** - * Effect handler which will return a block select action to select the block - * occurring before the selected block in the previous state, unless it is the - * same block or the action includes a falsey `selectPrevious` option flag. - * - * @param {Object} action Action which had initiated the effect handler. - * @param {Object} store Store instance. - * - * @return {?Object} Block select action to select previous, if applicable. - */ -export function selectPreviousBlock( action, store ) { - // if the action says previous block should not be selected don't do anything. - if ( ! action.selectPrevious ) { - return; - } - - const firstRemovedBlockClientId = action.clientIds[ 0 ]; - const state = store.getState(); - const selectedBlockClientId = getSelectedBlockClientId( state ); - - // recreate the state before the block was removed. - const previousState = { ...state, editor: { present: last( state.editor.past ) } }; - - // rootClientId of the removed block. - const rootClientId = getBlockRootClientId( previousState, firstRemovedBlockClientId ); - - // Client ID of the block that was before the removed block or the - // rootClientId if the removed block was first amongst its siblings. - const blockClientIdToSelect = getPreviousBlockClientId( previousState, firstRemovedBlockClientId ) || rootClientId; - - // Dispatch select block action if the currently selected block - // is not already the block we want to be selected. - if ( blockClientIdToSelect !== selectedBlockClientId ) { - return selectBlock( blockClientIdToSelect, -1 ); - } -} - /** * Effect handler which will return a default block insertion action if there * are no other blocks at the root of the editor. This is expected to be used @@ -258,7 +218,6 @@ export default { CONVERT_BLOCK_TO_STATIC: convertBlockToStatic, CONVERT_BLOCK_TO_REUSABLE: convertBlockToReusable, REMOVE_BLOCKS: [ - selectPreviousBlock, ensureDefaultBlock, ], REPLACE_BLOCKS: [ diff --git a/packages/editor/src/store/index.js b/packages/editor/src/store/index.js index 11fa1c76eaf8c..bc7b51a604fad 100644 --- a/packages/editor/src/store/index.js +++ b/packages/editor/src/store/index.js @@ -10,6 +10,7 @@ import reducer from './reducer'; import applyMiddlewares from './middlewares'; import * as selectors from './selectors'; import * as actions from './actions'; +import controls from './controls'; /** * Module Constants @@ -20,6 +21,7 @@ const store = registerStore( MODULE_KEY, { reducer, selectors, actions, + controls, persist: [ 'preferences' ], } ); applyMiddlewares( store ); diff --git a/packages/editor/src/store/test/actions.js b/packages/editor/src/store/test/actions.js index b071dc78204fb..057e86c121aa5 100644 --- a/packages/editor/src/store/test/actions.js +++ b/packages/editor/src/store/test/actions.js @@ -19,6 +19,7 @@ import { updateBlockAttributes, updateBlock, selectBlock, + selectPreviousBlock, startMultiSelect, stopMultiSelect, multiSelect, @@ -293,32 +294,47 @@ describe( 'actions', () => { describe( 'removeBlocks', () => { it( 'should return REMOVE_BLOCKS action', () => { - const clientIds = [ 'clientId' ]; - expect( removeBlocks( clientIds ) ).toEqual( { - type: 'REMOVE_BLOCKS', - clientIds, - selectPrevious: true, - } ); + const clientId = 'clientId'; + const clientIds = [ clientId ]; + + const actions = Array.from( removeBlocks( clientIds ) ); + + expect( actions ).toEqual( [ + selectPreviousBlock( clientId ), + { + type: 'REMOVE_BLOCKS', + clientIds, + }, + ] ); } ); } ); describe( 'removeBlock', () => { it( 'should return REMOVE_BLOCKS action', () => { const clientId = 'myclientid'; - expect( removeBlock( clientId ) ).toEqual( { - type: 'REMOVE_BLOCKS', - clientIds: [ - clientId, - ], - selectPrevious: true, - } ); - expect( removeBlock( clientId, false ) ).toEqual( { - type: 'REMOVE_BLOCKS', - clientIds: [ - clientId, - ], - selectPrevious: false, - } ); + + const actions = Array.from( removeBlock( clientId ) ); + + expect( actions ).toEqual( [ + selectPreviousBlock( clientId ), + { + type: 'REMOVE_BLOCKS', + clientIds: [ clientId ], + }, + ] ); + } ); + + it( 'should return REMOVE_BLOCKS action, opting out of remove previous', () => { + const clientId = 'myclientid'; + + const actions = Array.from( removeBlock( clientId, false ) ); + + expect( actions ).toEqual( [ + { + type: 'REMOVE_BLOCKS', + clientIds: [ clientId ], + }, + ] ); } ); } ); From bf3940f582bff2b0cc160d5902a2e0bd0b97ca4e Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Tue, 19 Feb 2019 22:19:38 +0000 Subject: [PATCH 483/691] chore: Replace wrong usage of map with forEach (#13953) --- packages/components/src/drop-zone/provider.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/drop-zone/provider.js b/packages/components/src/drop-zone/provider.js index 5e77c74d5b958..43fc3d76c562c 100644 --- a/packages/components/src/drop-zone/provider.js +++ b/packages/components/src/drop-zone/provider.js @@ -172,7 +172,7 @@ class DropZoneProvider extends Component { } // Notifying the dropzones - toUpdate.map( ( dropZone ) => { + toUpdate.forEach( ( dropZone ) => { const index = this.dropZones.indexOf( dropZone ); const isDraggingOverDropZone = index === hoveredDropZoneIndex; dropZone.setState( { From e5a69c58cab6a07a02c8c9e9f845d7b5f209c880 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20Van=C2=A0Durpe?= <iseulde@automattic.com> Date: Wed, 20 Feb 2019 03:35:56 +0100 Subject: [PATCH 484/691] RichText: only ignore input types that insert HTML (#13914) * RichText: only ignore input types that insert HTML * Mark RichTextInputEvent as unstable * Use Set --- packages/editor/src/components/index.js | 2 +- .../editor/src/components/rich-text/index.js | 21 ++++++++++++++++--- .../src/components/rich-text/input-event.js | 2 +- packages/format-library/src/bold/index.js | 4 ++-- packages/format-library/src/italic/index.js | 4 ++-- .../format-library/src/underline/index.js | 4 ++-- 6 files changed, 26 insertions(+), 11 deletions(-) diff --git a/packages/editor/src/components/index.js b/packages/editor/src/components/index.js index 40ce2cc74d559..94cadfc7ddc24 100644 --- a/packages/editor/src/components/index.js +++ b/packages/editor/src/components/index.js @@ -23,7 +23,7 @@ export { RichTextShortcut, RichTextToolbarButton, RichTextInserterItem, - RichTextInputEvent, + UnstableRichTextInputEvent, } from './rich-text'; export { default as ServerSideRender } from './server-side-render'; export { default as MediaPlaceholder } from './media-placeholder'; diff --git a/packages/editor/src/components/rich-text/index.js b/packages/editor/src/components/rich-text/index.js index 8029dfa0a640d..1d6c353448d9e 100644 --- a/packages/editor/src/components/rich-text/index.js +++ b/packages/editor/src/components/rich-text/index.js @@ -69,6 +69,21 @@ import { RemoveBrowserShortcuts } from './remove-browser-shortcuts'; const { getSelection } = window; +/** + * All inserting input types that would insert HTML into the DOM. + * + * @see https://www.w3.org/TR/input-events-2/#interface-InputEvent-Attributes + * + * @type {Set} + */ +const INSERTION_INPUT_TYPES_TO_IGNORE = new Set( [ + 'insertParagraph', + 'insertOrderedList', + 'insertUnorderedList', + 'insertHorizontalRule', + 'insertLink', +] ); + export class RichText extends Component { constructor( { value, onReplace, multiline } ) { super( ...arguments ); @@ -354,12 +369,12 @@ export class RichText extends Component { if ( event ) { const { inputType } = event.nativeEvent; - // The browser formatted something or tried to insert a list. + // The browser formatted something or tried to insert HTML. // Overwrite it. It will be handled later by the format library if // needed. if ( inputType.indexOf( 'format' ) === 0 || - ( inputType.indexOf( 'insert' ) === 0 && inputType !== 'insertText' ) + INSERTION_INPUT_TYPES_TO_IGNORE.has( inputType ) ) { this.applyRecord( this.getRecord() ); return; @@ -1150,4 +1165,4 @@ export default RichTextContainer; export { RichTextShortcut } from './shortcut'; export { RichTextToolbarButton } from './toolbar-button'; export { RichTextInserterItem } from './inserter-list-item'; -export { RichTextInputEvent } from './input-event'; +export { UnstableRichTextInputEvent } from './input-event'; diff --git a/packages/editor/src/components/rich-text/input-event.js b/packages/editor/src/components/rich-text/input-event.js index 3f7e19d45b75d..e77a57cb898e7 100644 --- a/packages/editor/src/components/rich-text/input-event.js +++ b/packages/editor/src/components/rich-text/input-event.js @@ -3,7 +3,7 @@ */ import { Component } from '@wordpress/element'; -export class RichTextInputEvent extends Component { +export class UnstableRichTextInputEvent extends Component { constructor() { super( ...arguments ); diff --git a/packages/format-library/src/bold/index.js b/packages/format-library/src/bold/index.js index 910c59a2085e2..15c64261fbfde 100644 --- a/packages/format-library/src/bold/index.js +++ b/packages/format-library/src/bold/index.js @@ -4,7 +4,7 @@ import { __ } from '@wordpress/i18n'; import { Fragment } from '@wordpress/element'; import { toggleFormat } from '@wordpress/rich-text'; -import { RichTextToolbarButton, RichTextShortcut, RichTextInputEvent } from '@wordpress/editor'; +import { RichTextToolbarButton, RichTextShortcut, UnstableRichTextInputEvent } from '@wordpress/editor'; const name = 'core/bold'; @@ -32,7 +32,7 @@ export const bold = { shortcutType="primary" shortcutCharacter="b" /> - <RichTextInputEvent + <UnstableRichTextInputEvent inputType="formatBold" onInput={ onToggle } /> diff --git a/packages/format-library/src/italic/index.js b/packages/format-library/src/italic/index.js index 30bf72acd5778..1acadaf6f4826 100644 --- a/packages/format-library/src/italic/index.js +++ b/packages/format-library/src/italic/index.js @@ -4,7 +4,7 @@ import { __ } from '@wordpress/i18n'; import { Fragment } from '@wordpress/element'; import { toggleFormat } from '@wordpress/rich-text'; -import { RichTextToolbarButton, RichTextShortcut, RichTextInputEvent } from '@wordpress/editor'; +import { RichTextToolbarButton, RichTextShortcut, UnstableRichTextInputEvent } from '@wordpress/editor'; const name = 'core/italic'; @@ -32,7 +32,7 @@ export const italic = { shortcutType="primary" shortcutCharacter="i" /> - <RichTextInputEvent + <UnstableRichTextInputEvent inputType="formatItalic" onInput={ onToggle } /> diff --git a/packages/format-library/src/underline/index.js b/packages/format-library/src/underline/index.js index bab50a0452eb4..0777e83e1f717 100644 --- a/packages/format-library/src/underline/index.js +++ b/packages/format-library/src/underline/index.js @@ -4,7 +4,7 @@ import { __ } from '@wordpress/i18n'; import { Fragment } from '@wordpress/element'; import { toggleFormat } from '@wordpress/rich-text'; -import { RichTextShortcut, RichTextInputEvent } from '@wordpress/editor'; +import { RichTextShortcut, UnstableRichTextInputEvent } from '@wordpress/editor'; const name = 'core/underline'; @@ -34,7 +34,7 @@ export const underline = { character="u" onUse={ onToggle } /> - <RichTextInputEvent + <UnstableRichTextInputEvent inputType="formatUnderline" onInput={ onToggle } /> From f43572c8e55e692f45062ffaab0bdf8c8040b41b Mon Sep 17 00:00:00 2001 From: Robert Anderson <robert@noisysocks.com> Date: Wed, 20 Feb 2019 14:18:41 +1100 Subject: [PATCH 485/691] Bump plugin version to 5.1.0 (#13960) --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 216566261a2ab..3ade4ca6d01b0 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,7 +3,7 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the new block editor in core. - * Version: 5.1.0-rc.1 + * Version: 5.1.0 * Author: Gutenberg Team * * @package gutenberg diff --git a/package-lock.json b/package-lock.json index b5442e8113a32..55d51e8dce8bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.1.0-rc.1", + "version": "5.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 1b369596bdf06..8dd9929cfa023 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.1.0-rc.1", + "version": "5.1.0", "private": true, "description": "A new WordPress editor experience", "repository": "git+https://github.com/WordPress/gutenberg.git", From 1a0f88decbefed2e31335cee3affbc1465d47f01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20Van=C2=A0Durpe?= <iseulde@automattic.com> Date: Wed, 20 Feb 2019 14:03:58 +0100 Subject: [PATCH 486/691] Webpack: remove TinyMCE external dependency mapping (#13971) --- docs/contributors/coding-guidelines.md | 4 ++-- packages/keycodes/src/index.js | 1 - webpack.config.js | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/contributors/coding-guidelines.md b/docs/contributors/coding-guidelines.md index 1b4524ffcf9ac..5230d561fa98a 100644 --- a/docs/contributors/coding-guidelines.md +++ b/docs/contributors/coding-guidelines.md @@ -57,7 +57,7 @@ Example: /** * External dependencies */ -import TinyMCE from 'tinymce'; +import moment from 'moment'; ``` #### WordPress Dependencies @@ -142,7 +142,7 @@ const name = 'Matt'; // Bad: const pet = 'Matt\'s dog'; -// Also bad (not using an apostrophe): +// Also bad (not using an apostrophe): const pet = "Matt's dog"; // Good: const pet = 'Matt’s dog'; diff --git a/packages/keycodes/src/index.js b/packages/keycodes/src/index.js index 45e5dba707674..5465b9df19184 100644 --- a/packages/keycodes/src/index.js +++ b/packages/keycodes/src/index.js @@ -39,7 +39,6 @@ export const F10 = 121; export const ALT = 'alt'; export const CTRL = 'ctrl'; -// Understood in both Mousetrap and TinyMCE. export const COMMAND = 'meta'; export const SHIFT = 'shift'; diff --git a/webpack.config.js b/webpack.config.js index 998cabd9c9ace..c67b8c95c804d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -48,7 +48,6 @@ const gutenbergPackages = Object.keys( dependencies ) const externals = { react: 'React', 'react-dom': 'ReactDOM', - tinymce: 'tinymce', moment: 'moment', jquery: 'jQuery', lodash: 'lodash', From 2a373339cdce7909a05ec52c53d06d4bd17fe0f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20Van=C2=A0Durpe?= <iseulde@automattic.com> Date: Wed, 20 Feb 2019 14:12:27 +0100 Subject: [PATCH 487/691] Fix character newlines in pre (#13799) * Fix character newlines in pre * Add e2e test --- .../block-library/src/preformatted/index.js | 2 +- .../__snapshots__/preformatted.test.js.snap | 14 ++++++++ .../specs/blocks/preformatted.test.js | 35 +++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 packages/e2e-tests/specs/blocks/__snapshots__/preformatted.test.js.snap create mode 100644 packages/e2e-tests/specs/blocks/preformatted.test.js diff --git a/packages/block-library/src/preformatted/index.js b/packages/block-library/src/preformatted/index.js index f0ee53a942221..25144b04abcfe 100644 --- a/packages/block-library/src/preformatted/index.js +++ b/packages/block-library/src/preformatted/index.js @@ -68,7 +68,7 @@ export const settings = { return ( <RichText tagName="pre" - value={ content } + value={ content.replace( /\n/g, '<br>' ) } onChange={ ( nextContent ) => { setAttributes( { content: nextContent, diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/preformatted.test.js.snap b/packages/e2e-tests/specs/blocks/__snapshots__/preformatted.test.js.snap new file mode 100644 index 0000000000000..4538816e15b41 --- /dev/null +++ b/packages/e2e-tests/specs/blocks/__snapshots__/preformatted.test.js.snap @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Preformatted should preserve character newlines 1`] = ` +"<!-- wp:html --> +<pre>1 +2</pre> +<!-- /wp:html -->" +`; + +exports[`Preformatted should preserve character newlines 2`] = ` +"<!-- wp:preformatted --> +<pre class=\\"wp-block-preformatted\\">0<br>1<br>2</pre> +<!-- /wp:preformatted -->" +`; diff --git a/packages/e2e-tests/specs/blocks/preformatted.test.js b/packages/e2e-tests/specs/blocks/preformatted.test.js new file mode 100644 index 0000000000000..3397c9f75f68d --- /dev/null +++ b/packages/e2e-tests/specs/blocks/preformatted.test.js @@ -0,0 +1,35 @@ +/** + * WordPress dependencies + */ +import { + getEditedPostContent, + createNewPost, + insertBlock, + clickButton, +} from '@wordpress/e2e-test-utils'; + +describe( 'Preformatted', () => { + beforeEach( async () => { + await createNewPost(); + } ); + + it( 'should preserve character newlines', async () => { + await insertBlock( 'Custom HTML' ); + await page.keyboard.type( '<pre>1' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '2</pre>' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + + await page.keyboard.press( 'Escape' ); + await page.click( 'button[aria-label="More options"]' ); + await clickButton( 'Convert to Blocks' ); + // Once it's edited, it should be saved as BR tags. + await page.keyboard.type( '0' ); + await page.keyboard.press( 'Enter' ); + await page.click( 'button[aria-label="More options"]' ); + await clickButton( 'Edit as HTML' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); +} ); From aa6797c916fbe5a3d2220b3efb65b38e59b17a3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20Van=C2=A0Durpe?= <iseulde@automattic.com> Date: Wed, 20 Feb 2019 14:43:53 +0100 Subject: [PATCH 488/691] Fix emoji in demo content (#13969) --- post-content.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/post-content.php b/post-content.php index fd59ab75ad1b8..45684cb6b0d2a 100644 --- a/post-content.php +++ b/post-content.php @@ -180,5 +180,5 @@ <!-- /wp:paragraph --> <!-- wp:paragraph {"align":"center"} --> -<p style="text-align:center"><img draggable="false" class="emoji" alt="👋" src="https://s.w.org/images/core/emoji/2.3/svg/1f44b.svg" /></p> +<p style="text-align:center">👋</p> <!-- /wp:paragraph --> From d97251b1d63ab75299deb042b7e906014e2d8e82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Est=C3=AAv=C3=A3o?= <sergioestevao@gmail.com> Date: Wed, 20 Feb 2019 13:46:54 +0000 Subject: [PATCH 489/691] Mobile Block Placeholder fixes (#13945) * Use correct font and padding for block placeholder. * Match text for placeholder for paragraph to block placeholder. * Make placeholder text simpler. * Make min height smaller to make component entered. * Fix default placeholder color. * Use variable for color. * Change minimum height of text blocks. * Remove top padding on the appender. --- packages/block-library/src/heading/edit.native.js | 2 +- packages/block-library/src/paragraph/edit.native.js | 2 +- .../block-library/src/paragraph/style.native.scss | 2 +- .../components/default-block-appender/index.native.js | 4 ++-- .../default-block-appender/style.native.scss | 11 +++++++++-- .../editor/src/components/post-title/index.native.js | 2 +- .../editor/src/components/rich-text/index.native.js | 2 +- .../editor/src/components/rich-text/style.native.scss | 2 ++ 8 files changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/block-library/src/heading/edit.native.js b/packages/block-library/src/heading/edit.native.js index ab578dfd22916..d28021a0bbd99 100644 --- a/packages/block-library/src/heading/edit.native.js +++ b/packages/block-library/src/heading/edit.native.js @@ -21,7 +21,7 @@ import { createBlock } from '@wordpress/blocks'; */ import './editor.scss'; -const minHeight = 50; +const minHeight = 24; class HeadingEdit extends Component { constructor( props ) { diff --git a/packages/block-library/src/paragraph/edit.native.js b/packages/block-library/src/paragraph/edit.native.js index c859f5c5216c8..4b303d489557d 100644 --- a/packages/block-library/src/paragraph/edit.native.js +++ b/packages/block-library/src/paragraph/edit.native.js @@ -125,7 +125,7 @@ class ParagraphEdit extends Component { onContentSizeChange={ ( event ) => { this.setState( { aztecHeight: event.aztecHeight } ); } } - placeholder={ placeholder || __( 'Add text or type / to add content' ) } + placeholder={ placeholder || __( 'Start writing…' ) } /> </View> ); diff --git a/packages/block-library/src/paragraph/style.native.scss b/packages/block-library/src/paragraph/style.native.scss index 8724a7ca468ca..a732fa2608dd1 100644 --- a/packages/block-library/src/paragraph/style.native.scss +++ b/packages/block-library/src/paragraph/style.native.scss @@ -1,3 +1,3 @@ .blockText { - min-height: 50; + min-height: 24; } diff --git a/packages/editor/src/components/default-block-appender/index.native.js b/packages/editor/src/components/default-block-appender/index.native.js index 4414829c2e4b7..8255afc3cd8fc 100644 --- a/packages/editor/src/components/default-block-appender/index.native.js +++ b/packages/editor/src/components/default-block-appender/index.native.js @@ -6,7 +6,7 @@ import { TextInput, TouchableWithoutFeedback, View } from 'react-native'; /** * WordPress dependencies */ -import { __, sprintf } from '@wordpress/i18n'; +import { __ } from '@wordpress/i18n'; import { compose } from '@wordpress/compose'; import { decodeEntities } from '@wordpress/html-entities'; import { withSelect, withDispatch } from '@wordpress/data'; @@ -26,7 +26,7 @@ export function DefaultBlockAppender( { return null; } - const value = decodeEntities( placeholder ) || sprintf( __( 'Start writing or press %s to add content' ), '\u2295' ); + const value = decodeEntities( placeholder ) || __( 'Start writing…' ); return ( <TouchableWithoutFeedback diff --git a/packages/editor/src/components/default-block-appender/style.native.scss b/packages/editor/src/components/default-block-appender/style.native.scss index cc08f6c820ca9..fd3cd2f9afefb 100644 --- a/packages/editor/src/components/default-block-appender/style.native.scss +++ b/packages/editor/src/components/default-block-appender/style.native.scss @@ -1,3 +1,7 @@ +// @format + +@import "variables.scss"; +@import "colors.scss"; .blockHolder { flex: 1 1 auto; @@ -5,10 +9,13 @@ .blockContainer { background-color: $white; - padding: 8px; + padding-top: 0; + padding-left: 16px; + padding-right: 16px; } .textView { - color: #87a6bc; + color: $gray; font-size: 16px; + font-family: $default-regular-font; } diff --git a/packages/editor/src/components/post-title/index.native.js b/packages/editor/src/components/post-title/index.native.js index 59079073a62c9..8c79b43414a1a 100644 --- a/packages/editor/src/components/post-title/index.native.js +++ b/packages/editor/src/components/post-title/index.native.js @@ -8,7 +8,7 @@ import { withDispatch } from '@wordpress/data'; import { withFocusOutside } from '@wordpress/components'; import { withInstanceId, compose } from '@wordpress/compose'; -const minHeight = 53; +const minHeight = 30; class PostTitle extends Component { constructor() { diff --git a/packages/editor/src/components/rich-text/index.native.js b/packages/editor/src/components/rich-text/index.native.js index 9ada8911699a0..e7deab608e266 100644 --- a/packages/editor/src/components/rich-text/index.native.js +++ b/packages/editor/src/components/rich-text/index.native.js @@ -501,7 +501,7 @@ export class RichText extends Component { } } text={ { text: html, eventCount: this.lastEventCount } } placeholder={ this.props.placeholder } - placeholderTextColor={ this.props.placeholderTextColor || 'lightgrey' } + placeholderTextColor={ this.props.placeholderTextColor || styles[ 'editor-rich-text' ].textDecorationColor } onChange={ this.onChange } onFocus={ this.props.onFocus } onBlur={ this.props.onBlur } diff --git a/packages/editor/src/components/rich-text/style.native.scss b/packages/editor/src/components/rich-text/style.native.scss index 15c1b834a87ef..b1207a4338aa4 100644 --- a/packages/editor/src/components/rich-text/style.native.scss +++ b/packages/editor/src/components/rich-text/style.native.scss @@ -1,5 +1,7 @@ @import "variables.scss"; +@import "colors.scss"; .editor-rich-text { font-family: $default-regular-font; + text-decoration-color: $gray; } From f81d0d7dc41779a95d164098eabb219a95df5970 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Wed, 20 Feb 2019 09:54:57 -0800 Subject: [PATCH 490/691] Add ESNext syntax to meta block tutorial (#13954) * Add ESNext syntax to meta block tutorial * Applied WordPress code styles to the examples * Apply suggestions from code review Co-Authored-By: mkaz <marcus@mkaz.com> --- .../tutorials/metabox/meta-block-3-add.md | 75 +++++++++++++++---- 1 file changed, 60 insertions(+), 15 deletions(-) diff --git a/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md b/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md index 0ad9966b91a12..4f6e54f148ed7 100644 --- a/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md +++ b/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md @@ -10,26 +10,28 @@ By specifying the source of the attributes as `meta`, the Block Editor automatic Add this code to your JavaScript file (this tutorial will call the file `myguten.js`): +{% codetabs %} +{% ES5 %} ```js ( function( wp ) { var el = wp.element.createElement; var registerBlockType = wp.blocks.registerBlockType; - var TextField = wp.components.TextControl; + var TextControl = wp.components.TextControl; - registerBlockType("myguten/meta-block", { - title: "Meta Block", - icon: "smiley", - category: "common", + registerBlockType( 'myguten/meta-block', { + title: 'Meta Block', + icon: 'smiley', + category: 'common', attributes: { blockValue: { - type: "string", - source: "meta", - meta: "myguten_meta_block_field" + type: 'string', + source: 'meta', + meta: 'myguten_meta_block_field' } }, - edit: function(props) { + edit: function( props ) { var className = props.className; var setAttributes = props.setAttributes; @@ -37,11 +39,11 @@ Add this code to your JavaScript file (this tutorial will call the file `myguten setAttributes({ blockValue }); } - return el( - "div", + return el( + 'div', { className: className }, - el( TextField, { - label: "Meta Block Field", + el( TextControl, { + label: 'Meta Block Field', value: props.attributes.blockValue, onChange: updateBlockValue } ) @@ -53,9 +55,52 @@ Add this code to your JavaScript file (this tutorial will call the file `myguten save: function() { return null; } - }); -})( window.wp ); + } ); +} )( window.wp ); ``` +{% ESNext %} +```jsx +import { registerBlockType } from '@wordpress/blocks'; +import { TextControl } from '@wordpress/components'; + +registerBlockType( 'myguten/meta-block', { + title: 'Meta Block', + icon: 'smiley', + category: 'common', + + attributes: { + blockValue: { + type: 'string', + source: 'meta', + meta: 'myguten_meta_block_field', + }, + }, + + edit( { className, setAttributes, attributes } ) { + + function updateBlockValue( blockValue ) { + setAttributes( { blockValue } ); + } + + return ( + <div className={ className }> + <TextControl + label="Meta Block Field" + value={ attributes.blockValue } + onChange={ updateBlockValue } + /> + </div> + ); + }, + + // No information saved to the block + // Data is saved to post meta via attributes + save() { + return null; + } +} ); +``` +{% end %} **Important:** Before you test, you need to enqueue your JavaScript file and its dependencies. Note the WordPress packages used above are `wp.element`, `wp.blocks`, and `wp.components`. Each of these need to be included in the array of dependencies. Update the `myguten-meta-block.php` file adding the enqueue function: From 1020543c18a7e303e652ba5b27c4e434642ec72c Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Wed, 20 Feb 2019 16:00:40 -0500 Subject: [PATCH 491/691] Editor: RichText: Check for presence of inputType (#13986) --- packages/editor/src/components/rich-text/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor/src/components/rich-text/index.js b/packages/editor/src/components/rich-text/index.js index 1d6c353448d9e..724c9f26e30e6 100644 --- a/packages/editor/src/components/rich-text/index.js +++ b/packages/editor/src/components/rich-text/index.js @@ -366,7 +366,7 @@ export class RichText extends Component { return; } - if ( event ) { + if ( event && event.nativeEvent.inputType ) { const { inputType } = event.nativeEvent; // The browser formatted something or tried to insert HTML. From 4be0a36c703a45cdcac67807d30e2e4f26c92343 Mon Sep 17 00:00:00 2001 From: Robert Anderson <robert@noisysocks.com> Date: Thu, 21 Feb 2019 14:29:44 +1100 Subject: [PATCH 492/691] Bump plugin version to 5.1.1 (#13990) --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 3ade4ca6d01b0..40da12a8c4b8c 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,7 +3,7 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the new block editor in core. - * Version: 5.1.0 + * Version: 5.1.1 * Author: Gutenberg Team * * @package gutenberg diff --git a/package-lock.json b/package-lock.json index 55d51e8dce8bb..ed633e96e9bd2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.1.0", + "version": "5.1.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 8dd9929cfa023..ea6cd663dbca0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.1.0", + "version": "5.1.1", "private": true, "description": "A new WordPress editor experience", "repository": "git+https://github.com/WordPress/gutenberg.git", From c60224004300e06c9875fe78511312f4dd812b9d Mon Sep 17 00:00:00 2001 From: andrei draganescu <me@andreidraganescu.info> Date: Thu, 21 Feb 2019 11:08:55 +0200 Subject: [PATCH 493/691] Added a snippet for observing the browser when running e2e tests (#13993) * Added a snippet for observing the browser when running e2e tests --- docs/contributors/testing-overview.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/contributors/testing-overview.md b/docs/contributors/testing-overview.md index 7d0d01921a6a5..6992bd664c3ec 100644 --- a/docs/contributors/testing-overview.md +++ b/docs/contributors/testing-overview.md @@ -356,6 +356,12 @@ or interactively npm run test-e2e:watch ``` +Sometimes it's useful to observe the browser while running tests. To do so you can use these environment variables: + +```bash +PUPPETEER_HEADLESS=false PUPPETEER_SLOWMO=80 npm run test-e2e:watch +``` + If you're using a different setup, you can provide the base URL, username and password like this: ```bash From 72ff590394027945b4adea0cd1d838a6060671c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Thu, 21 Feb 2019 11:00:02 +0100 Subject: [PATCH 494/691] Extract reusable part of Webpack config and put in @wordpress/scripts (#13814) * New build-config package with webpack config. Pull the Gutenberg webpack config into a package so it can be re-used for block/extension development. * Require new build-config package. * Dynamically handle WP externals with a function. Use code from WP Calypso for handling WP externals so we don't have to have the actual list of packages accessible in our webpack configuration. * Use webpack config from build-config package. * Require build-config package. * Adjust file refs for WP packages. * Move main gutenberg entry definition and webpack copy plugin out of build-config. * Add react-dev-utils for formatting webpack compiler messages. * Implement build script using webpack config from build-config. * Adjust output path so build goes to working directory. * Update package name to webpack-config * Apply more tweaks to the way webpack config package is structured * Update the way externals are handled * Add default values for entry and output * Move shared webpack config under @wordpress/scripts package * Improve the way how loaders are handled * Replace GUTENBERG with WP in webpack config env variables Co-Authored-By: gziolo <grzegorz@gziolo.pl> * Bring back feature flag to webpack config accidentally removed during merge * Add missing dev dependencies for the packages used in webpack config * Fix the list of excluded folders for babel-loader --- package-lock.json | 90 ++++--- package.json | 5 +- .../test/index.js | 3 +- .../CHANGELOG.md | 10 +- .../block-serialization-spec-parser/index.js | 2 - .../package.json | 6 +- .../test/index.js | 3 +- packages/scripts/config/webpack.config.js | 109 +++++++++ packages/scripts/package.json | 6 +- packages/scripts/scripts/build.js | 1 - packages/scripts/utils/index.js | 4 + packages/scripts/utils/string.js | 20 ++ packages/scripts/utils/test/string.js | 23 ++ webpack.config.js | 228 ++++++------------ 14 files changed, 305 insertions(+), 205 deletions(-) delete mode 100644 packages/block-serialization-spec-parser/index.js create mode 100644 packages/scripts/config/webpack.config.js create mode 100644 packages/scripts/utils/string.js create mode 100644 packages/scripts/utils/test/string.js diff --git a/package-lock.json b/package-lock.json index ed633e96e9bd2..656ba9ae3cd0e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2562,7 +2562,10 @@ } }, "@wordpress/block-serialization-spec-parser": { - "version": "file:packages/block-serialization-spec-parser" + "version": "file:packages/block-serialization-spec-parser", + "requires": { + "pegjs": "^0.10.0" + } }, "@wordpress/blocks": { "version": "file:packages/blocks", @@ -2997,6 +3000,7 @@ "@wordpress/eslint-plugin": "file:packages/eslint-plugin", "@wordpress/jest-preset-default": "file:packages/jest-preset-default", "@wordpress/npm-package-json-lint-config": "file:packages/npm-package-json-lint-config", + "babel-loader": "^8.0.5", "chalk": "^2.4.1", "check-node-version": "^3.1.1", "cross-spawn": "^5.1.0", @@ -3007,10 +3011,13 @@ "puppeteer": "1.6.1", "read-pkg-up": "^1.0.1", "resolve-bin": "^0.4.0", + "source-map-loader": "^0.2.4", "stylelint": "^9.10.1", "stylelint-config-wordpress": "^13.1.0", "webpack": "4.8.3", - "webpack-cli": "^3.1.2" + "webpack-bundle-analyzer": "^3.0.3", + "webpack-cli": "^3.1.2", + "webpack-livereload-plugin": "^2.2.0" } }, "@wordpress/shortcode": { @@ -15869,8 +15876,7 @@ "pegjs": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/pegjs/-/pegjs-0.10.0.tgz", - "integrity": "sha1-z4uvrm7d/0tafvsYUmnqr0YQ3b0=", - "dev": true + "integrity": "sha1-z4uvrm7d/0tafvsYUmnqr0YQ3b0=" }, "pend": { "version": "1.2.0", @@ -15956,6 +15962,34 @@ "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", "dev": true }, + "portfinder": { + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.20.tgz", + "integrity": "sha512-Yxe4mTyDzTd59PZJY4ojZR8F+E5e97iq2ZOHPz3HDgSvYC5siNad2tLooQ5y5QHyQhc3xVqvyk/eNA3wuoa7Sw==", + "dev": true, + "requires": { + "async": "^1.5.2", + "debug": "^2.2.0", + "mkdirp": "0.5.x" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", @@ -19763,34 +19797,13 @@ "dev": true }, "source-map-loader": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-0.2.3.tgz", - "integrity": "sha512-MYbFX9DYxmTQFfy2v8FC1XZwpwHKYxg3SK8Wb7VPBKuhDjz8gi9re2819MsG4p49HDyiOSUKlmZ+nQBArW5CGw==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-0.2.4.tgz", + "integrity": "sha512-OU6UJUty+i2JDpTItnizPrlpOIBLmQbWMuBg9q5bVtnHACqw1tn9nNwqJLbv0/00JjnJb/Ee5g5WS5vrRv7zIQ==", "dev": true, "requires": { "async": "^2.5.0", - "loader-utils": "~0.2.2", - "source-map": "~0.6.1" - }, - "dependencies": { - "loader-utils": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", - "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", - "dev": true, - "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0", - "object-assign": "^4.0.1" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } + "loader-utils": "^1.1.0" } }, "source-map-resolve": { @@ -21820,9 +21833,9 @@ } }, "webpack-bundle-analyzer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.0.2.tgz", - "integrity": "sha512-cZG4wSQtKrSpk5RJ33dxiaAyo8bP0V+JvycAyIDFEiDIhw4LHhhVKhn40YT1w6TR9E4scHA00LnIoBtTA13Mow==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.0.3.tgz", + "integrity": "sha512-naLWiRfmtH4UJgtUktRTLw6FdoZJ2RvCR9ePbwM9aRMsS/KjFerkPZG9epEvXRAw5d5oPdrs9+3p+afNjxW8Xw==", "dev": true, "requires": { "acorn": "^5.7.3", @@ -21846,9 +21859,9 @@ "dev": true }, "commander": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.18.0.tgz", - "integrity": "sha512-6CYPa+JP2ftfRU2qkDK+UTVeQYosOg/2GbcjIcKPHfinyOLPVGXu/ovN86RP49Re5ndJK1N0kuiidFFuepc4ZQ==", + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", "dev": true } } @@ -22052,11 +22065,12 @@ } }, "webpack-livereload-plugin": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/webpack-livereload-plugin/-/webpack-livereload-plugin-2.1.1.tgz", - "integrity": "sha512-W7Q55QbPvVJotpIZSjjwzmqQ22333ExYxWM3WFlHKkbPStQqVRSmJkjntUqXF9jtpdeXi8r8HLkA1RVnAP0SQA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/webpack-livereload-plugin/-/webpack-livereload-plugin-2.2.0.tgz", + "integrity": "sha512-sx9xA5mHoNOUgLQI0PmXT3KV9ecsVmUaTgr+fsoL69qAOHw/7VzkL1+ZMDQ8n0dPbWounswK6cBRSgMod7Nhgg==", "dev": true, "requires": { + "portfinder": "^1.0.17", "tiny-lr": "^1.1.1" } }, diff --git a/package.json b/package.json index ea6cd663dbca0..2299994e4f385 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,6 @@ "@wordpress/npm-package-json-lint-config": "file:packages/npm-package-json-lint-config", "@wordpress/postcss-themes": "file:packages/postcss-themes", "@wordpress/scripts": "file:packages/scripts", - "babel-loader": "8.0.5", "benchmark": "2.1.4", "browserslist": "4.4.1", "chalk": "2.4.1", @@ -104,6 +103,7 @@ "node-watch": "0.6.0", "pegjs": "0.10.0", "phpegjs": "1.0.0-beta7", + "postcss": "7.0.13", "react-dom": "16.6.3", "react-test-renderer": "16.6.3", "redux": "4.0.0", @@ -113,13 +113,10 @@ "shallow-equal": "1.0.0", "shallow-equals": "1.0.0", "shallowequal": "1.1.0", - "source-map-loader": "0.2.3", "sprintf-js": "1.1.1", "stylelint-config-wordpress": "13.1.0", "uuid": "3.3.2", "webpack": "4.8.3", - "webpack-bundle-analyzer": "3.0.2", - "webpack-livereload-plugin": "2.1.1", "webpack-rtl-plugin": "github:yoavf/webpack-rtl-plugin#develop" }, "npmPackageJsonLintConfig": { diff --git a/packages/block-serialization-default-parser/test/index.js b/packages/block-serialization-default-parser/test/index.js index a3c67e280ef94..72f4121ce1944 100644 --- a/packages/block-serialization-default-parser/test/index.js +++ b/packages/block-serialization-default-parser/test/index.js @@ -6,7 +6,8 @@ import path from 'path'; /** * WordPress dependencies */ -import { jsTester, phpTester } from '@wordpress/block-serialization-spec-parser'; +// eslint-disable-next-line no-restricted-syntax +import { jsTester, phpTester } from '@wordpress/block-serialization-spec-parser/shared-tests'; /** * Internal dependencies diff --git a/packages/block-serialization-spec-parser/CHANGELOG.md b/packages/block-serialization-spec-parser/CHANGELOG.md index e4f403ffd6849..7ba44f0cd9a96 100644 --- a/packages/block-serialization-spec-parser/CHANGELOG.md +++ b/packages/block-serialization-spec-parser/CHANGELOG.md @@ -1,6 +1,12 @@ -## 2.1.0 (Unreleased) +## 3.0.0 (Unreleased) -- A `parser.php` file generated from the PEGJS grammar is now included. +## Breaking Change + +- A `parser.js` file generated from the PEGJS grammar is now outputted in commonjs format. + +## New Feature + +- A `parser.php` file generated from the PEGJS grammar is now added upon installation. ## 2.0.2 (2018-12-12) diff --git a/packages/block-serialization-spec-parser/index.js b/packages/block-serialization-spec-parser/index.js deleted file mode 100644 index b217f8f5c5ad7..0000000000000 --- a/packages/block-serialization-spec-parser/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export { parse } from './parser'; -export { jsTester, phpTester } from './shared-tests'; diff --git a/packages/block-serialization-spec-parser/package.json b/packages/block-serialization-spec-parser/package.json index 918c269169f09..5d40a8cfd03de 100644 --- a/packages/block-serialization-spec-parser/package.json +++ b/packages/block-serialization-spec-parser/package.json @@ -18,12 +18,16 @@ "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" }, + "main": "parser.js", + "dependencies": { + "pegjs": "^0.10.0" + }, "publishConfig": { "access": "public" }, "scripts": { "build": "concurrently \"npm run build:js\" \"npm run build:php\"", - "build:js": "pegjs --format umd -o ./parser.js ./grammar.pegjs", + "build:js": "pegjs --format commonjs -o ./parser.js ./grammar.pegjs", "build:php": "node bin/create-php-parser.js" } } diff --git a/packages/block-serialization-spec-parser/test/index.js b/packages/block-serialization-spec-parser/test/index.js index 7bdbe9f053f16..9d00c5a5434c3 100644 --- a/packages/block-serialization-spec-parser/test/index.js +++ b/packages/block-serialization-spec-parser/test/index.js @@ -6,7 +6,8 @@ import path from 'path'; /** * Internal dependencies */ -import { jsTester, phpTester, parse } from '../'; +import { parse } from '../'; +import { jsTester, phpTester } from '../shared-tests'; describe( 'block-serialization-spec-parser-js', jsTester( parse ) ); diff --git a/packages/scripts/config/webpack.config.js b/packages/scripts/config/webpack.config.js new file mode 100644 index 0000000000000..44d0ff61d9a65 --- /dev/null +++ b/packages/scripts/config/webpack.config.js @@ -0,0 +1,109 @@ +/** + * External dependencies + */ +const { BundleAnalyzerPlugin } = require( 'webpack-bundle-analyzer' ); +const LiveReloadPlugin = require( 'webpack-livereload-plugin' ); +const path = require( 'path' ); + +/** + * Internal dependencies + */ +const { camelCaseDash } = require( '../utils' ); + +/** + * Converts @wordpress/* string request into request object. + * + * Note this isn't the same as camel case because of the + * way that numbers don't trigger the capitalized next letter. + * + * @example + * formatRequest( '@wordpress/api-fetch' ); + * // { this: [ 'wp', 'apiFetch' ] } + * formatRequest( '@wordpress/i18n' ); + * // { this: [ 'wp', 'i18n' ] } + * + * @param {string} request Request name from import statement. + * @return {Object} Request object formatted for further processing. + */ +const formatRequest = ( request ) => { + // '@wordpress/api-fetch' -> [ '@wordpress', 'api-fetch' ] + const [ , name ] = request.split( '/' ); + + // { this: [ 'wp', 'apiFetch' ] } + return { + this: [ 'wp', camelCaseDash( name ) ], + }; +}; + +const wordpressExternals = ( context, request, callback ) => { + if ( /^@wordpress\//.test( request ) ) { + callback( null, formatRequest( request ), 'this' ); + } else { + callback(); + } +}; + +const externals = [ + { + react: 'React', + 'react-dom': 'ReactDOM', + moment: 'moment', + jquery: 'jQuery', + lodash: 'lodash', + 'lodash-es': 'lodash', + }, + wordpressExternals, +]; + +const isProduction = process.env.NODE_ENV === 'production'; +const mode = isProduction ? 'production' : 'development'; + +const config = { + mode, + entry: { + index: path.resolve( process.cwd(), 'src', 'index.js' ), + }, + output: { + filename: '[name].js', + path: path.resolve( process.cwd(), 'build' ), + }, + externals, + resolve: { + alias: { + 'lodash-es': 'lodash', + }, + }, + module: { + rules: [ + { + test: /\.js$/, + use: require.resolve( 'source-map-loader' ), + enforce: 'pre', + }, + { + test: /\.js$/, + exclude: /node_modules/, + use: require.resolve( 'babel-loader' ), + }, + ], + }, + plugins: [ + // WP_BUNDLE_ANALYZER global variable enables utility that represents bundle content + // as convenient interactive zoomable treemap. + process.env.WP_BUNDLE_ANALYZER && new BundleAnalyzerPlugin(), + // WP_LIVE_RELOAD_PORT global variable changes port on which live reload works + // when running watch mode. + ! isProduction && new LiveReloadPlugin( { port: process.env.WP_LIVE_RELOAD_PORT || 35729 } ), + ].filter( Boolean ), + stats: { + children: false, + }, +}; + +if ( ! isProduction ) { + // WP_DEVTOOL global variable controls how source maps are generated. + // See: https://webpack.js.org/configuration/devtool/#devtool. + config.devtool = process.env.WP_DEVTOOL || 'source-map'; +} + +module.exports = config; diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 8aaf6e8ee7ac6..388bc0a26a3b8 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -35,6 +35,7 @@ "@wordpress/eslint-plugin": "file:../eslint-plugin", "@wordpress/jest-preset-default": "file:../jest-preset-default", "@wordpress/npm-package-json-lint-config": "file:../npm-package-json-lint-config", + "babel-loader": "^8.0.5", "chalk": "^2.4.1", "check-node-version": "^3.1.1", "cross-spawn": "^5.1.0", @@ -45,10 +46,13 @@ "puppeteer": "1.6.1", "read-pkg-up": "^1.0.1", "resolve-bin": "^0.4.0", + "source-map-loader": "^0.2.4", "stylelint": "^9.10.1", "stylelint-config-wordpress": "^13.1.0", "webpack": "4.8.3", - "webpack-cli": "^3.1.2" + "webpack-bundle-analyzer": "^3.0.3", + "webpack-cli": "^3.1.2", + "webpack-livereload-plugin": "^2.2.0" }, "publishConfig": { "access": "public" diff --git a/packages/scripts/scripts/build.js b/packages/scripts/scripts/build.js index 9470274f76633..ea2c7d303f2fa 100644 --- a/packages/scripts/scripts/build.js +++ b/packages/scripts/scripts/build.js @@ -32,4 +32,3 @@ if ( hasWebpackConfig ) { console.log( 'Webpack config file is missing.' ); process.exit( 1 ); } - diff --git a/packages/scripts/utils/index.js b/packages/scripts/utils/index.js index 473952fcdfc17..b1a78d0d600c9 100644 --- a/packages/scripts/utils/index.js +++ b/packages/scripts/utils/index.js @@ -18,8 +18,12 @@ const { const { hasPackageProp, } = require( './package' ); +const { + camelCaseDash, +} = require( './string' ); module.exports = { + camelCaseDash, fromConfigRoot, getCliArg, getCliArgs, diff --git a/packages/scripts/utils/string.js b/packages/scripts/utils/string.js new file mode 100644 index 0000000000000..ea6ea3889c1ad --- /dev/null +++ b/packages/scripts/utils/string.js @@ -0,0 +1,20 @@ +/** + * Given a string, returns a new string with dash separators converted to + * camelCase equivalent. This is not as aggressive as `_.camelCase` in + * converting to uppercase, where Lodash will also capitalize letters + * following numbers. + * + * @param {string} string Input dash-delimited string. + * + * @return {string} Camel-cased string. + */ +function camelCaseDash( string ) { + return string.replace( + /-([a-z])/g, + ( match, letter ) => letter.toUpperCase() + ); +} + +module.exports = { + camelCaseDash, +}; diff --git a/packages/scripts/utils/test/string.js b/packages/scripts/utils/test/string.js new file mode 100644 index 0000000000000..ca81bdda5df54 --- /dev/null +++ b/packages/scripts/utils/test/string.js @@ -0,0 +1,23 @@ +/** + * Internal dependencies + */ +import { camelCaseDash } from '../string'; + +describe( 'string', () => { + describe( 'camelCaseDash', () => { + test( 'does not change a single word', () => { + expect( camelCaseDash( 'blocks' ) ).toBe( 'blocks' ); + expect( camelCaseDash( 'dom' ) ).toBe( 'dom' ); + } ); + + test( 'does not capitalize letters following numbers', () => { + expect( camelCaseDash( 'a11y' ) ).toBe( 'a11y' ); + expect( camelCaseDash( 'i18n' ) ).toBe( 'i18n' ); + } ); + + test( 'converts dashes into camel case', () => { + expect( camelCaseDash( 'api-fetch' ) ).toBe( 'apiFetch' ); + expect( camelCaseDash( 'list-reusable-blocks' ) ).toBe( 'listReusableBlocks' ); + } ); + } ); +} ); diff --git a/webpack.config.js b/webpack.config.js index c67b8c95c804d..a5dd63c3bd982 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -3,10 +3,8 @@ */ const { DefinePlugin } = require( 'webpack' ); const WebpackRTLPlugin = require( 'webpack-rtl-plugin' ); -const LiveReloadPlugin = require( 'webpack-livereload-plugin' ); const CopyWebpackPlugin = require( 'copy-webpack-plugin' ); const postcss = require( 'postcss' ); - const { get } = require( 'lodash' ); const { basename } = require( 'path' ); @@ -15,7 +13,8 @@ const { basename } = require( 'path' ); */ const CustomTemplatedPathPlugin = require( '@wordpress/custom-templated-path-webpack-plugin' ); const LibraryExportDefaultPlugin = require( '@wordpress/library-export-default-webpack-plugin' ); -const { BundleAnalyzerPlugin } = require( 'webpack-bundle-analyzer' ); +const config = require( '@wordpress/scripts/config/webpack.config' ); +const { camelCaseDash } = require( '@wordpress/scripts/utils' ); /** * Internal dependencies @@ -24,165 +23,86 @@ const { dependencies } = require( './package' ); const WORDPRESS_NAMESPACE = '@wordpress/'; -/** - * Given a string, returns a new string with dash separators converted to - * camelCase equivalent. This is not as aggressive as `_.camelCase` in - * converting to uppercase, where Lodash will also capitalize letters - * following numbers. - * - * @param {string} string Input dash-delimited string. - * - * @return {string} Camel-cased string. - */ -function camelCaseDash( string ) { - return string.replace( - /-([a-z])/g, - ( match, letter ) => letter.toUpperCase() - ); -} - const gutenbergPackages = Object.keys( dependencies ) .filter( ( packageName ) => packageName.startsWith( WORDPRESS_NAMESPACE ) ) .map( ( packageName ) => packageName.replace( WORDPRESS_NAMESPACE, '' ) ); -const externals = { - react: 'React', - 'react-dom': 'ReactDOM', - moment: 'moment', - jquery: 'jQuery', - lodash: 'lodash', - 'lodash-es': 'lodash', +config.entry = gutenbergPackages.reduce( ( memo, packageName ) => { + const name = camelCaseDash( packageName ); + memo[ name ] = `./packages/${ packageName }`; + return memo; +}, {} ); + +config.output = { + filename: './build/[basename]/index.js', + path: __dirname, + library: [ 'wp', '[name]' ], + libraryTarget: 'this', }; -gutenbergPackages.forEach( ( name ) => { - externals[ WORDPRESS_NAMESPACE + name ] = { - this: [ 'wp', camelCaseDash( name ) ], - }; -} ); - -const isProduction = process.env.NODE_ENV === 'production'; -const mode = isProduction ? 'production' : 'development'; - -const config = { - mode, - entry: gutenbergPackages.reduce( ( memo, packageName ) => { - const name = camelCaseDash( packageName ); - memo[ name ] = `./packages/${ packageName }`; - return memo; - }, {} ), - output: { - filename: './build/[basename]/index.js', - path: __dirname, - library: [ 'wp', '[name]' ], - libraryTarget: 'this', - }, - externals, - resolve: { - modules: [ - __dirname, - 'node_modules', - ], - alias: { - 'lodash-es': 'lodash', +config.plugins.push( + new DefinePlugin( { + // Inject the `GUTENBERG_PHASE` global, used for feature flagging. + // eslint-disable-next-line @wordpress/gutenberg-phase + 'process.env.GUTENBERG_PHASE': JSON.stringify( parseInt( process.env.npm_package_config_GUTENBERG_PHASE, 10 ) || 1 ), + } ), + // Create RTL files with a -rtl suffix + new WebpackRTLPlugin( { + suffix: '-rtl', + minify: config.mode === 'production' ? { safe: true } : false, + } ), + new CustomTemplatedPathPlugin( { + basename( path, data ) { + let rawRequest; + + const entryModule = get( data, [ 'chunk', 'entryModule' ], {} ); + switch ( entryModule.type ) { + case 'javascript/auto': + rawRequest = entryModule.rawRequest; + break; + + case 'javascript/esm': + rawRequest = entryModule.rootModule.rawRequest; + break; + } + + if ( rawRequest ) { + return basename( rawRequest ); + } + + return path; }, - }, - module: { - rules: [ - { - test: /\.js$/, - use: [ 'source-map-loader' ], - enforce: 'pre', - }, - { - test: /\.js$/, - exclude: [ - /block-serialization-spec-parser/, - /is-shallow-equal/, - /node_modules/, - ], - use: 'babel-loader', - }, - ], - }, - plugins: [ - new DefinePlugin( { - // Inject the `GUTENBERG_PHASE` global, used for feature flagging. - // eslint-disable-next-line @wordpress/gutenberg-phase - 'process.env.GUTENBERG_PHASE': JSON.stringify( parseInt( process.env.npm_package_config_GUTENBERG_PHASE, 10 ) || 1 ), - } ), - // Create RTL files with a -rtl suffix - new WebpackRTLPlugin( { - suffix: '-rtl', - minify: process.env.NODE_ENV === 'production' ? { safe: true } : false, - } ), - new CustomTemplatedPathPlugin( { - basename( path, data ) { - let rawRequest; - - const entryModule = get( data, [ 'chunk', 'entryModule' ], {} ); - switch ( entryModule.type ) { - case 'javascript/auto': - rawRequest = entryModule.rawRequest; - break; - - case 'javascript/esm': - rawRequest = entryModule.rootModule.rawRequest; - break; - } - - if ( rawRequest ) { - return basename( rawRequest ); + } ), + new LibraryExportDefaultPlugin( [ + 'api-fetch', + 'deprecated', + 'dom-ready', + 'redux-routine', + 'token-list', + ].map( camelCaseDash ) ), + new CopyWebpackPlugin( + gutenbergPackages.map( ( packageName ) => ( { + from: `./packages/${ packageName }/build-style/*.css`, + to: `./build/${ packageName }/`, + flatten: true, + transform: ( content ) => { + if ( config.mode === 'production' ) { + return postcss( [ + require( 'cssnano' )( { + preset: [ 'default', { + discardComments: { + removeAll: true, + }, + } ], + } ), + ] ) + .process( content, { from: 'src/app.css', to: 'dest/app.css' } ) + .then( ( result ) => result.css ); } - - return path; + return content; }, - } ), - new LibraryExportDefaultPlugin( [ - 'api-fetch', - 'deprecated', - 'dom-ready', - 'redux-routine', - 'token-list', - ].map( camelCaseDash ) ), - new CopyWebpackPlugin( - gutenbergPackages.map( ( packageName ) => ( { - from: `./packages/${ packageName }/build-style/*.css`, - to: `./build/${ packageName }/`, - flatten: true, - transform: ( content ) => { - if ( config.mode === 'production' ) { - return postcss( [ - require( 'cssnano' )( { - preset: [ 'default', { - discardComments: { - removeAll: true, - }, - } ], - } ), - ] ) - .process( content, { from: 'src/app.css', to: 'dest/app.css' } ) - .then( ( result ) => result.css ); - } - return content; - }, - } ) ) - ), - // GUTENBERG_BUNDLE_ANALYZER global variable enables utility that represents bundle content - // as convenient interactive zoomable treemap. - process.env.GUTENBERG_BUNDLE_ANALYZER && new BundleAnalyzerPlugin(), - // GUTENBERG_LIVE_RELOAD_PORT global variable changes port on which live reload works - // when running watch mode. - ! isProduction && new LiveReloadPlugin( { port: process.env.GUTENBERG_LIVE_RELOAD_PORT || 35729 } ), - ].filter( Boolean ), - stats: { - children: false, - }, -}; - -if ( ! isProduction ) { - // GUTENBERG_DEVTOOL global variable controls how source maps are generated. - // See: https://webpack.js.org/configuration/devtool/#devtool. - config.devtool = process.env.GUTENBERG_DEVTOOL || 'source-map'; -} + } ) ) + ) +); module.exports = config; From 011a8b3d92f2bdd8b79853bb53d292999dc4c5dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Thu, 21 Feb 2019 13:38:33 +0100 Subject: [PATCH 495/691] Use globals instead of imports in tutorials (#13995) * Use globals instead of imports * Update docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md Co-Authored-By: nosolosw <nosolosw@users.noreply.github.com> * Update docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md Co-Authored-By: nosolosw <nosolosw@users.noreply.github.com> --- .../developers/tutorials/metabox/meta-block-3-add.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md b/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md index 4f6e54f148ed7..e1e6749c17ff7 100644 --- a/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md +++ b/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md @@ -60,8 +60,9 @@ Add this code to your JavaScript file (this tutorial will call the file `myguten ``` {% ESNext %} ```jsx -import { registerBlockType } from '@wordpress/blocks'; -import { TextControl } from '@wordpress/components'; + +const { registerBlockType } = wp.blocks; +const { TextControl } = wp.components; registerBlockType( 'myguten/meta-block', { title: 'Meta Block', From 10989f01abbb187805443fa9e0e839a55ae07a63 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell@kjellr.com> Date: Thu, 21 Feb 2019 09:46:45 -0500 Subject: [PATCH 496/691] URL input popover visual cleanup (#13973) * Use chevron instead of ellipsis in url input field options. * Mimic toolbar icon styles for the icons in the URL popover. * Add a left divider before the URL settings toggle * Even up the spacing in the settings panel. * Add periods to code comments. * Update snapshot --- .../src/components/url-popover/index.js | 2 +- .../src/components/url-popover/style.scss | 46 ++++++++++++++++--- .../test/__snapshots__/index.js.snap | 4 +- 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/packages/editor/src/components/url-popover/index.js b/packages/editor/src/components/url-popover/index.js index c89f76d8bfd62..5842dd6017b5e 100644 --- a/packages/editor/src/components/url-popover/index.js +++ b/packages/editor/src/components/url-popover/index.js @@ -54,7 +54,7 @@ class URLPopover extends Component { { !! renderSettings && ( <IconButton className="editor-url-popover__settings-toggle" - icon="ellipsis" + icon="arrow-down-alt2" label={ __( 'Link Settings' ) } onClick={ this.toggleSettingsVisibility } aria-expanded={ isSettingsExpanded } diff --git a/packages/editor/src/components/url-popover/style.scss b/packages/editor/src/components/url-popover/style.scss index c27d842084394..af277e9fc4c4a 100644 --- a/packages/editor/src/components/url-popover/style.scss +++ b/packages/editor/src/components/url-popover/style.scss @@ -9,19 +9,53 @@ flex-grow: 1; } + // Mimic toolbar component styles for the icons in this popover. + .components-icon-button { + padding: 3px; + + > svg { + padding: 5px; + border-radius: $radius-round-rectangle; + height: 30px; + width: 30px; + } + + &:not(:disabled):not([aria-disabled="true"]):not(.is-default):hover { + box-shadow: none; + + > svg { + @include formatting-button-style__hover; + } + } + + &:not(:disabled):focus { + box-shadow: none; + + > svg { + @include formatting-button-style__focus; + } + } + } + &__settings-toggle { flex-shrink: 0; - width: $icon-button-size; - height: $icon-button-size; - .dashicon { - transform: rotate(90deg); + // Add a left divider to the toggle button. + border-radius: 0; + border-left: $border-width solid $light-gray-500; + margin-left: 1px; + + &[aria-expanded="true"] .dashicon { + transform: rotate(180deg); } } &__settings { - padding: 7px 8px; + padding: $panel-padding; border-top: $border-width solid $light-gray-500; - padding-top: 7px + $border-width; + + .components-base-control:last-child .components-base-control__field { + margin-bottom: 0; + } } } diff --git a/packages/editor/src/components/url-popover/test/__snapshots__/index.js.snap b/packages/editor/src/components/url-popover/test/__snapshots__/index.js.snap index 4bffa2ecb3747..f8f0aa9027bdc 100644 --- a/packages/editor/src/components/url-popover/test/__snapshots__/index.js.snap +++ b/packages/editor/src/components/url-popover/test/__snapshots__/index.js.snap @@ -16,7 +16,7 @@ exports[`URLPopover matches the snapshot in its default state 1`] = ` <IconButton aria-expanded={false} className="editor-url-popover__settings-toggle" - icon="ellipsis" + icon="arrow-down-alt2" label="Link Settings" onClick={[Function]} /> @@ -40,7 +40,7 @@ exports[`URLPopover matches the snapshot when the settings are toggled open 1`] <IconButton aria-expanded={true} className="editor-url-popover__settings-toggle" - icon="ellipsis" + icon="arrow-down-alt2" label="Link Settings" onClick={[Function]} /> From f5e15ae16fd96149146cb92e085ab75e5ccf4fee Mon Sep 17 00:00:00 2001 From: Stefanos Togoulidis <stefanostogoulidis@gmail.com> Date: Thu, 21 Feb 2019 16:51:23 +0200 Subject: [PATCH 497/691] Reinstate "underline" in default formats list (#14008) --- packages/format-library/src/default-formats.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/format-library/src/default-formats.js b/packages/format-library/src/default-formats.js index 46d8e4b851e4a..f5d57910a7f13 100644 --- a/packages/format-library/src/default-formats.js +++ b/packages/format-library/src/default-formats.js @@ -7,6 +7,7 @@ import { image } from './image'; import { italic } from './italic'; import { link } from './link'; import { strikethrough } from './strikethrough'; +import { underline } from './underline'; export default [ bold, @@ -15,4 +16,5 @@ export default [ italic, link, strikethrough, + underline, ]; From 6f88bec8fbf15291e3e00872a5df59e310494f5b Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Thu, 21 Feb 2019 16:35:39 +0100 Subject: [PATCH 498/691] Upgrade React to 16.8.2: Welcome React Hooks (#13992) * Upgrade React to 16.8.2 * Update package-lock.json file * Expose React Hooks --- lib/client-assets.php | 4 +- package-lock.json | 200 +++--------------- package.json | 4 +- packages/element/package.json | 4 +- packages/element/src/react.js | 26 +++ phpunit/class-vendor-script-filename-test.php | 8 +- 6 files changed, 68 insertions(+), 178 deletions(-) diff --git a/lib/client-assets.php b/lib/client-assets.php index b34e428425e80..e1a491fa9ef4e 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -540,12 +540,12 @@ function gutenberg_register_vendor_scripts() { gutenberg_register_vendor_script( 'react', - 'https://unpkg.com/react@16.6.3/umd/react' . $react_suffix . '.js', + 'https://unpkg.com/react@16.8.2/umd/react' . $react_suffix . '.js', array( 'wp-polyfill' ) ); gutenberg_register_vendor_script( 'react-dom', - 'https://unpkg.com/react-dom@16.6.3/umd/react-dom' . $react_suffix . '.js', + 'https://unpkg.com/react-dom@16.8.2/umd/react-dom' . $react_suffix . '.js', array( 'react' ) ); $moment_script = SCRIPT_DEBUG ? 'moment.js' : 'min/moment.min.js'; diff --git a/package-lock.json b/package-lock.json index 656ba9ae3cd0e..92c991e0c93f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2803,8 +2803,8 @@ "@babel/runtime": "^7.3.1", "@wordpress/escape-html": "file:packages/escape-html", "lodash": "^4.17.11", - "react": "^16.6.3", - "react-dom": "^16.6.3" + "react": "^16.8.2", + "react-dom": "^16.8.2" } }, "@wordpress/escape-html": { @@ -3164,17 +3164,6 @@ "object.entries": "^1.0.4", "prop-types": "^15.6.1", "prop-types-exact": "^1.1.2" - }, - "dependencies": { - "prop-types": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", - "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", - "requires": { - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" - } - } } }, "ajv": { @@ -7452,24 +7441,6 @@ "prop-types": "^15.6.2", "react-is": "^16.5.2", "react-test-renderer": "^16.0.0-0" - }, - "dependencies": { - "prop-types": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", - "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", - "dev": true, - "requires": { - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" - } - }, - "react-is": { - "version": "16.6.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.6.0.tgz", - "integrity": "sha512-q8U7k0Fi7oxF1HvQgyBjPwDXeMplEsArnKt2iYhuIF86+GBbgLHdAmokL3XUFjTd7Q363OSNG55FOGUdONVn1g==", - "dev": true - } } }, "enzyme-adapter-utils": { @@ -7481,18 +7452,6 @@ "function.prototype.name": "^1.1.0", "object.assign": "^4.1.0", "prop-types": "^15.6.2" - }, - "dependencies": { - "prop-types": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", - "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", - "dev": true, - "requires": { - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" - } - } } }, "enzyme-to-json": { @@ -7816,18 +7775,6 @@ "has": "^1.0.1", "jsx-ast-utils": "^2.0.1", "prop-types": "^15.6.0" - }, - "dependencies": { - "prop-types": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", - "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", - "dev": true, - "requires": { - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" - } - } } }, "eslint-scope": { @@ -10244,6 +10191,11 @@ "minimalistic-crypto-utils": "^1.0.1" } }, + "hoist-non-react-statics": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", + "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" + }, "home-or-tmp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", @@ -17785,12 +17737,13 @@ } }, "prop-types": { - "version": "15.5.10", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.5.10.tgz", - "integrity": "sha1-J5ffwxJhguOpXj37suiT3ddFYVQ=", + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", "requires": { - "fbjs": "^0.8.9", - "loose-envify": "^1.3.1" + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" } }, "prop-types-exact": { @@ -18055,25 +18008,14 @@ "integrity": "sha512-pLJkPbZCe+3ml+9Q15z+R69qYZDsluj0KwrdFb8kSNaqDzYAveDUblf7voHH9hNTdKIiIvP8iIdGFFKSgffVaQ==" }, "react": { - "version": "16.6.3", - "resolved": "https://registry.npmjs.org/react/-/react-16.6.3.tgz", - "integrity": "sha512-zCvmH2vbEolgKxtqXL2wmGCUxUyNheYn/C+PD1YAjfxHC54+MhdruyhO7QieQrYsYeTxrn93PM2y0jRH1zEExw==", + "version": "16.8.2", + "resolved": "https://registry.npmjs.org/react/-/react-16.8.2.tgz", + "integrity": "sha512-aB2ctx9uQ9vo09HVknqv3DGRpI7OIGJhCx3Bt0QqoRluEjHSaObJl+nG12GDdYH6sTgE7YiPJ6ZUyMx9kICdXw==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", "prop-types": "^15.6.2", - "scheduler": "^0.11.2" - }, - "dependencies": { - "prop-types": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", - "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", - "requires": { - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" - } - } + "scheduler": "^0.13.2" } }, "react-addons-shallow-compare": { @@ -18128,46 +18070,23 @@ "react-portal": "^4.1.5", "react-with-styles": "^3.2.0", "react-with-styles-interface-css": "^4.0.2" - }, - "dependencies": { - "prop-types": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", - "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", - "requires": { - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" - } - } } }, "react-dom": { - "version": "16.6.3", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.6.3.tgz", - "integrity": "sha512-8ugJWRCWLGXy+7PmNh8WJz3g1TaTUt1XyoIcFN+x0Zbkoz+KKdUyx1AQLYJdbFXjuF41Nmjn5+j//rxvhFjgSQ==", + "version": "16.8.2", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.2.tgz", + "integrity": "sha512-cPGfgFfwi+VCZjk73buu14pYkYBR1b/SRMSYqkLDdhSEHnSwcuYTPu6/Bh6ZphJFIk80XLvbSe2azfcRzNF+Xg==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", "prop-types": "^15.6.2", - "scheduler": "^0.11.2" - }, - "dependencies": { - "prop-types": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", - "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", - "requires": { - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" - } - } + "scheduler": "^0.13.2" } }, "react-is": { - "version": "16.6.3", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.6.3.tgz", - "integrity": "sha512-u7FDWtthB4rWibG/+mFbVd5FvdI20yde86qKGx4lVUTWmPlSWQ4QxbBIrrs+HnXGbxOUlUzTAP/VDmvCwaP2yA==", - "dev": true + "version": "16.8.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.2.tgz", + "integrity": "sha512-D+NxhSR2HUCjYky1q1DwpNUD44cDpUXzSmmFyC3ug1bClcU/iDNy0YNn1iwme28fn+NFhpA13IndOd42CrFb+Q==" }, "react-moment-proptypes": { "version": "1.6.0", @@ -18186,17 +18105,6 @@ "consolidated-events": "^1.1.1 || ^2.0.0", "object.values": "^1.0.4", "prop-types": "^15.6.1" - }, - "dependencies": { - "prop-types": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", - "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", - "requires": { - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" - } - } } }, "react-portal": { @@ -18208,27 +18116,15 @@ } }, "react-test-renderer": { - "version": "16.6.3", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.6.3.tgz", - "integrity": "sha512-B5bCer+qymrQz/wN03lT0LppbZUDRq6AMfzMKrovzkGzfO81a9T+PWQW6MzkWknbwODQH/qpJno/yFQLX5IWrQ==", + "version": "16.8.2", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.8.2.tgz", + "integrity": "sha512-gsd4NoOaYrZD2R8zi+CBV9wTGMsGhE2bRe4wvenGy0WcLJgdPscRZDDz+kmLjY+/5XpYC8yRR/v4CScgYfGyoQ==", "dev": true, "requires": { "object-assign": "^4.1.1", "prop-types": "^15.6.2", - "react-is": "^16.6.3", - "scheduler": "^0.11.2" - }, - "dependencies": { - "prop-types": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", - "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", - "dev": true, - "requires": { - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" - } - } + "react-is": "^16.8.2", + "scheduler": "^0.13.2" } }, "react-with-direction": { @@ -18244,22 +18140,6 @@ "object.assign": "^4.1.0", "object.values": "^1.0.4", "prop-types": "^15.6.0" - }, - "dependencies": { - "hoist-non-react-statics": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", - "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" - }, - "prop-types": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", - "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", - "requires": { - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" - } - } } }, "react-with-styles": { @@ -18271,22 +18151,6 @@ "hoist-non-react-statics": "^2.5.0", "prop-types": "^15.6.1", "react-with-direction": "^1.3.0" - }, - "dependencies": { - "hoist-non-react-statics": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", - "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" - }, - "prop-types": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", - "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", - "requires": { - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" - } - } } }, "react-with-styles-interface-css": { @@ -19337,9 +19201,9 @@ "dev": true }, "scheduler": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.11.2.tgz", - "integrity": "sha512-+WCP3s3wOaW4S7C1tl3TEXp4l9lJn0ZK8G3W3WKRWmw77Z2cIFUW2MiNTMHn5sCjxN+t7N43HAOOgMjyAg5hlg==", + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.2.tgz", + "integrity": "sha512-qK5P8tHS7vdEMCW5IPyt8v9MJOHqTrOUgPXib7tqm9vh834ibBX5BNhwkplX/0iOzHW5sXyluehYfS9yrkz9+w==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" diff --git a/package.json b/package.json index 2299994e4f385..59316766071af 100644 --- a/package.json +++ b/package.json @@ -104,8 +104,8 @@ "pegjs": "0.10.0", "phpegjs": "1.0.0-beta7", "postcss": "7.0.13", - "react-dom": "16.6.3", - "react-test-renderer": "16.6.3", + "react-dom": "16.8.2", + "react-test-renderer": "16.8.2", "redux": "4.0.0", "rimraf": "2.6.2", "rtlcss": "2.4.0", diff --git a/packages/element/package.json b/packages/element/package.json index 24df06e6702af..0aa0e7141202f 100644 --- a/packages/element/package.json +++ b/packages/element/package.json @@ -24,8 +24,8 @@ "@babel/runtime": "^7.3.1", "@wordpress/escape-html": "file:../escape-html", "lodash": "^4.17.11", - "react": "^16.6.3", - "react-dom": "^16.6.3" + "react": "^16.8.2", + "react-dom": "^16.8.2" }, "publishConfig": { "access": "public" diff --git a/packages/element/src/react.js b/packages/element/src/react.js index 4d42fae21c1f7..bb8b98d79d1d5 100644 --- a/packages/element/src/react.js +++ b/packages/element/src/react.js @@ -12,6 +12,16 @@ import { Fragment, isValidElement, StrictMode, + useState, + useEffect, + useContext, + useReducer, + useCallback, + useMemo, + useRef, + useImperativeHandle, + useLayoutEffect, + useDebugValue, } from 'react'; import { isString } from 'lodash'; @@ -93,6 +103,22 @@ export { isValidElement }; export { StrictMode }; +/** + * Make React Hooks available + */ +export { + useCallback, + useContext, + useDebugValue, + useEffect, + useImperativeHandle, + useLayoutEffect, + useMemo, + useReducer, + useRef, + useState, +}; + /** * Concatenate two or more React children objects. * diff --git a/phpunit/class-vendor-script-filename-test.php b/phpunit/class-vendor-script-filename-test.php index 0ee7a7b78b83d..ef9917e476f86 100644 --- a/phpunit/class-vendor-script-filename-test.php +++ b/phpunit/class-vendor-script-filename-test.php @@ -11,23 +11,23 @@ function vendor_script_filename_cases() { // Development mode scripts. array( 'react-handle', - 'https://unpkg.com/react@16.6.3/umd/react.development.js', + 'https://unpkg.com/react@16.8.2/umd/react.development.js', 'react-handle.HASH.js', ), array( 'react-dom-handle', - 'https://unpkg.com/react-dom@16.6.3/umd/react-dom.development.js', + 'https://unpkg.com/react-dom@16.8.2/umd/react-dom.development.js', 'react-dom-handle.HASH.js', ), // Production mode scripts. array( 'react-handle', - 'https://unpkg.com/react@16.6.3/umd/react.production.min.js', + 'https://unpkg.com/react@16.8.2/umd/react.production.min.js', 'react-handle.min.HASH.js', ), array( 'react-dom-handle', - 'https://unpkg.com/react-dom@16.6.3/umd/react-dom.production.min.js', + 'https://unpkg.com/react-dom@16.8.2/umd/react-dom.production.min.js', 'react-dom-handle.min.HASH.js', ), // Other cases. From bc065626fe83efee65a876364a6da51619042bed Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Thu, 21 Feb 2019 17:00:01 +0100 Subject: [PATCH 499/691] Revert "Upgrade React to 16.8.2: Welcome React Hooks (#13992)" (#14017) This reverts commit 6f88bec8fbf15291e3e00872a5df59e310494f5b. --- lib/client-assets.php | 4 +- package-lock.json | 200 +++++++++++++++--- package.json | 4 +- packages/element/package.json | 4 +- packages/element/src/react.js | 26 --- phpunit/class-vendor-script-filename-test.php | 8 +- 6 files changed, 178 insertions(+), 68 deletions(-) diff --git a/lib/client-assets.php b/lib/client-assets.php index e1a491fa9ef4e..b34e428425e80 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -540,12 +540,12 @@ function gutenberg_register_vendor_scripts() { gutenberg_register_vendor_script( 'react', - 'https://unpkg.com/react@16.8.2/umd/react' . $react_suffix . '.js', + 'https://unpkg.com/react@16.6.3/umd/react' . $react_suffix . '.js', array( 'wp-polyfill' ) ); gutenberg_register_vendor_script( 'react-dom', - 'https://unpkg.com/react-dom@16.8.2/umd/react-dom' . $react_suffix . '.js', + 'https://unpkg.com/react-dom@16.6.3/umd/react-dom' . $react_suffix . '.js', array( 'react' ) ); $moment_script = SCRIPT_DEBUG ? 'moment.js' : 'min/moment.min.js'; diff --git a/package-lock.json b/package-lock.json index 92c991e0c93f8..656ba9ae3cd0e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2803,8 +2803,8 @@ "@babel/runtime": "^7.3.1", "@wordpress/escape-html": "file:packages/escape-html", "lodash": "^4.17.11", - "react": "^16.8.2", - "react-dom": "^16.8.2" + "react": "^16.6.3", + "react-dom": "^16.6.3" } }, "@wordpress/escape-html": { @@ -3164,6 +3164,17 @@ "object.entries": "^1.0.4", "prop-types": "^15.6.1", "prop-types-exact": "^1.1.2" + }, + "dependencies": { + "prop-types": { + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", + "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", + "requires": { + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" + } + } } }, "ajv": { @@ -7441,6 +7452,24 @@ "prop-types": "^15.6.2", "react-is": "^16.5.2", "react-test-renderer": "^16.0.0-0" + }, + "dependencies": { + "prop-types": { + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", + "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", + "dev": true, + "requires": { + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" + } + }, + "react-is": { + "version": "16.6.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.6.0.tgz", + "integrity": "sha512-q8U7k0Fi7oxF1HvQgyBjPwDXeMplEsArnKt2iYhuIF86+GBbgLHdAmokL3XUFjTd7Q363OSNG55FOGUdONVn1g==", + "dev": true + } } }, "enzyme-adapter-utils": { @@ -7452,6 +7481,18 @@ "function.prototype.name": "^1.1.0", "object.assign": "^4.1.0", "prop-types": "^15.6.2" + }, + "dependencies": { + "prop-types": { + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", + "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", + "dev": true, + "requires": { + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" + } + } } }, "enzyme-to-json": { @@ -7775,6 +7816,18 @@ "has": "^1.0.1", "jsx-ast-utils": "^2.0.1", "prop-types": "^15.6.0" + }, + "dependencies": { + "prop-types": { + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", + "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", + "dev": true, + "requires": { + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" + } + } } }, "eslint-scope": { @@ -10191,11 +10244,6 @@ "minimalistic-crypto-utils": "^1.0.1" } }, - "hoist-non-react-statics": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", - "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" - }, "home-or-tmp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", @@ -17737,13 +17785,12 @@ } }, "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "version": "15.5.10", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.5.10.tgz", + "integrity": "sha1-J5ffwxJhguOpXj37suiT3ddFYVQ=", "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" + "fbjs": "^0.8.9", + "loose-envify": "^1.3.1" } }, "prop-types-exact": { @@ -18008,14 +18055,25 @@ "integrity": "sha512-pLJkPbZCe+3ml+9Q15z+R69qYZDsluj0KwrdFb8kSNaqDzYAveDUblf7voHH9hNTdKIiIvP8iIdGFFKSgffVaQ==" }, "react": { - "version": "16.8.2", - "resolved": "https://registry.npmjs.org/react/-/react-16.8.2.tgz", - "integrity": "sha512-aB2ctx9uQ9vo09HVknqv3DGRpI7OIGJhCx3Bt0QqoRluEjHSaObJl+nG12GDdYH6sTgE7YiPJ6ZUyMx9kICdXw==", + "version": "16.6.3", + "resolved": "https://registry.npmjs.org/react/-/react-16.6.3.tgz", + "integrity": "sha512-zCvmH2vbEolgKxtqXL2wmGCUxUyNheYn/C+PD1YAjfxHC54+MhdruyhO7QieQrYsYeTxrn93PM2y0jRH1zEExw==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", "prop-types": "^15.6.2", - "scheduler": "^0.13.2" + "scheduler": "^0.11.2" + }, + "dependencies": { + "prop-types": { + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", + "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", + "requires": { + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" + } + } } }, "react-addons-shallow-compare": { @@ -18070,23 +18128,46 @@ "react-portal": "^4.1.5", "react-with-styles": "^3.2.0", "react-with-styles-interface-css": "^4.0.2" + }, + "dependencies": { + "prop-types": { + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", + "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", + "requires": { + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" + } + } } }, "react-dom": { - "version": "16.8.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.2.tgz", - "integrity": "sha512-cPGfgFfwi+VCZjk73buu14pYkYBR1b/SRMSYqkLDdhSEHnSwcuYTPu6/Bh6ZphJFIk80XLvbSe2azfcRzNF+Xg==", + "version": "16.6.3", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.6.3.tgz", + "integrity": "sha512-8ugJWRCWLGXy+7PmNh8WJz3g1TaTUt1XyoIcFN+x0Zbkoz+KKdUyx1AQLYJdbFXjuF41Nmjn5+j//rxvhFjgSQ==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", "prop-types": "^15.6.2", - "scheduler": "^0.13.2" + "scheduler": "^0.11.2" + }, + "dependencies": { + "prop-types": { + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", + "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", + "requires": { + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" + } + } } }, "react-is": { - "version": "16.8.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.2.tgz", - "integrity": "sha512-D+NxhSR2HUCjYky1q1DwpNUD44cDpUXzSmmFyC3ug1bClcU/iDNy0YNn1iwme28fn+NFhpA13IndOd42CrFb+Q==" + "version": "16.6.3", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.6.3.tgz", + "integrity": "sha512-u7FDWtthB4rWibG/+mFbVd5FvdI20yde86qKGx4lVUTWmPlSWQ4QxbBIrrs+HnXGbxOUlUzTAP/VDmvCwaP2yA==", + "dev": true }, "react-moment-proptypes": { "version": "1.6.0", @@ -18105,6 +18186,17 @@ "consolidated-events": "^1.1.1 || ^2.0.0", "object.values": "^1.0.4", "prop-types": "^15.6.1" + }, + "dependencies": { + "prop-types": { + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", + "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", + "requires": { + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" + } + } } }, "react-portal": { @@ -18116,15 +18208,27 @@ } }, "react-test-renderer": { - "version": "16.8.2", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.8.2.tgz", - "integrity": "sha512-gsd4NoOaYrZD2R8zi+CBV9wTGMsGhE2bRe4wvenGy0WcLJgdPscRZDDz+kmLjY+/5XpYC8yRR/v4CScgYfGyoQ==", + "version": "16.6.3", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.6.3.tgz", + "integrity": "sha512-B5bCer+qymrQz/wN03lT0LppbZUDRq6AMfzMKrovzkGzfO81a9T+PWQW6MzkWknbwODQH/qpJno/yFQLX5IWrQ==", "dev": true, "requires": { "object-assign": "^4.1.1", "prop-types": "^15.6.2", - "react-is": "^16.8.2", - "scheduler": "^0.13.2" + "react-is": "^16.6.3", + "scheduler": "^0.11.2" + }, + "dependencies": { + "prop-types": { + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", + "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", + "dev": true, + "requires": { + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" + } + } } }, "react-with-direction": { @@ -18140,6 +18244,22 @@ "object.assign": "^4.1.0", "object.values": "^1.0.4", "prop-types": "^15.6.0" + }, + "dependencies": { + "hoist-non-react-statics": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", + "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" + }, + "prop-types": { + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", + "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", + "requires": { + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" + } + } } }, "react-with-styles": { @@ -18151,6 +18271,22 @@ "hoist-non-react-statics": "^2.5.0", "prop-types": "^15.6.1", "react-with-direction": "^1.3.0" + }, + "dependencies": { + "hoist-non-react-statics": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", + "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" + }, + "prop-types": { + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", + "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", + "requires": { + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" + } + } } }, "react-with-styles-interface-css": { @@ -19201,9 +19337,9 @@ "dev": true }, "scheduler": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.2.tgz", - "integrity": "sha512-qK5P8tHS7vdEMCW5IPyt8v9MJOHqTrOUgPXib7tqm9vh834ibBX5BNhwkplX/0iOzHW5sXyluehYfS9yrkz9+w==", + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.11.2.tgz", + "integrity": "sha512-+WCP3s3wOaW4S7C1tl3TEXp4l9lJn0ZK8G3W3WKRWmw77Z2cIFUW2MiNTMHn5sCjxN+t7N43HAOOgMjyAg5hlg==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" diff --git a/package.json b/package.json index 59316766071af..2299994e4f385 100644 --- a/package.json +++ b/package.json @@ -104,8 +104,8 @@ "pegjs": "0.10.0", "phpegjs": "1.0.0-beta7", "postcss": "7.0.13", - "react-dom": "16.8.2", - "react-test-renderer": "16.8.2", + "react-dom": "16.6.3", + "react-test-renderer": "16.6.3", "redux": "4.0.0", "rimraf": "2.6.2", "rtlcss": "2.4.0", diff --git a/packages/element/package.json b/packages/element/package.json index 0aa0e7141202f..24df06e6702af 100644 --- a/packages/element/package.json +++ b/packages/element/package.json @@ -24,8 +24,8 @@ "@babel/runtime": "^7.3.1", "@wordpress/escape-html": "file:../escape-html", "lodash": "^4.17.11", - "react": "^16.8.2", - "react-dom": "^16.8.2" + "react": "^16.6.3", + "react-dom": "^16.6.3" }, "publishConfig": { "access": "public" diff --git a/packages/element/src/react.js b/packages/element/src/react.js index bb8b98d79d1d5..4d42fae21c1f7 100644 --- a/packages/element/src/react.js +++ b/packages/element/src/react.js @@ -12,16 +12,6 @@ import { Fragment, isValidElement, StrictMode, - useState, - useEffect, - useContext, - useReducer, - useCallback, - useMemo, - useRef, - useImperativeHandle, - useLayoutEffect, - useDebugValue, } from 'react'; import { isString } from 'lodash'; @@ -103,22 +93,6 @@ export { isValidElement }; export { StrictMode }; -/** - * Make React Hooks available - */ -export { - useCallback, - useContext, - useDebugValue, - useEffect, - useImperativeHandle, - useLayoutEffect, - useMemo, - useReducer, - useRef, - useState, -}; - /** * Concatenate two or more React children objects. * diff --git a/phpunit/class-vendor-script-filename-test.php b/phpunit/class-vendor-script-filename-test.php index ef9917e476f86..0ee7a7b78b83d 100644 --- a/phpunit/class-vendor-script-filename-test.php +++ b/phpunit/class-vendor-script-filename-test.php @@ -11,23 +11,23 @@ function vendor_script_filename_cases() { // Development mode scripts. array( 'react-handle', - 'https://unpkg.com/react@16.8.2/umd/react.development.js', + 'https://unpkg.com/react@16.6.3/umd/react.development.js', 'react-handle.HASH.js', ), array( 'react-dom-handle', - 'https://unpkg.com/react-dom@16.8.2/umd/react-dom.development.js', + 'https://unpkg.com/react-dom@16.6.3/umd/react-dom.development.js', 'react-dom-handle.HASH.js', ), // Production mode scripts. array( 'react-handle', - 'https://unpkg.com/react@16.8.2/umd/react.production.min.js', + 'https://unpkg.com/react@16.6.3/umd/react.production.min.js', 'react-handle.min.HASH.js', ), array( 'react-dom-handle', - 'https://unpkg.com/react-dom@16.8.2/umd/react-dom.production.min.js', + 'https://unpkg.com/react-dom@16.6.3/umd/react-dom.production.min.js', 'react-dom-handle.min.HASH.js', ), // Other cases. From 211f65dd854fc1f7d7e54782863ad0629abeff41 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell@kjellr.com> Date: Thu, 21 Feb 2019 11:40:58 -0500 Subject: [PATCH 500/691] Cleanup URL Popover stylesheet. (#14015) As noted in #13973, this stylesheet uses a relatively non-standard SCSS method of nesting some classnames. For instance: `.editor-url-popover { &__settings-toggle { ... } }`. ... instead of: `.editor-url-popover__settings-toggle { ... }` This is different from the conventions used elsewhere in Gutenberg, and is a bit more difficult to follow for that reason. This commit un-nests those styles, and should have no effect on the compiled CSS. --- .../src/components/url-popover/style.scss | 84 +++++++++---------- 1 file changed, 41 insertions(+), 43 deletions(-) diff --git a/packages/editor/src/components/url-popover/style.scss b/packages/editor/src/components/url-popover/style.scss index af277e9fc4c4a..51f16cadfb24c 100644 --- a/packages/editor/src/components/url-popover/style.scss +++ b/packages/editor/src/components/url-popover/style.scss @@ -1,61 +1,59 @@ -.editor-url-popover { - &__row { - display: flex; - } +.editor-url-popover__row { + display: flex; +} - // Any children of the popover-row that are not the settings-toggle - // should take up as much space as possible. - &__row > :not(.editor-url-popover__settings-toggle) { - flex-grow: 1; - } +// Any children of the popover-row that are not the settings-toggle +// should take up as much space as possible. +.editor-url-popover__row > :not(.editor-url-popover__settings-toggle) { + flex-grow: 1; +} - // Mimic toolbar component styles for the icons in this popover. - .components-icon-button { - padding: 3px; +// Mimic toolbar component styles for the icons in this popover. +.editor-url-popover .components-icon-button { + padding: 3px; - > svg { - padding: 5px; - border-radius: $radius-round-rectangle; - height: 30px; - width: 30px; - } + > svg { + padding: 5px; + border-radius: $radius-round-rectangle; + height: 30px; + width: 30px; + } - &:not(:disabled):not([aria-disabled="true"]):not(.is-default):hover { - box-shadow: none; + &:not(:disabled):not([aria-disabled="true"]):not(.is-default):hover { + box-shadow: none; - > svg { - @include formatting-button-style__hover; - } + > svg { + @include formatting-button-style__hover; } + } - &:not(:disabled):focus { - box-shadow: none; + &:not(:disabled):focus { + box-shadow: none; - > svg { - @include formatting-button-style__focus; - } + > svg { + @include formatting-button-style__focus; } } +} - &__settings-toggle { - flex-shrink: 0; +.editor-url-popover__settings-toggle { + flex-shrink: 0; - // Add a left divider to the toggle button. - border-radius: 0; - border-left: $border-width solid $light-gray-500; - margin-left: 1px; + // Add a left divider to the toggle button. + border-radius: 0; + border-left: $border-width solid $light-gray-500; + margin-left: 1px; - &[aria-expanded="true"] .dashicon { - transform: rotate(180deg); - } + &[aria-expanded="true"] .dashicon { + transform: rotate(180deg); } +} - &__settings { - padding: $panel-padding; - border-top: $border-width solid $light-gray-500; +.editor-url-popover__settings { + padding: $panel-padding; + border-top: $border-width solid $light-gray-500; - .components-base-control:last-child .components-base-control__field { - margin-bottom: 0; - } + .components-base-control:last-child .components-base-control__field { + margin-bottom: 0; } } From 0c4f457b0bf2dd3b97a20edda216288c11b05466 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Fri, 22 Feb 2019 09:33:04 +0100 Subject: [PATCH 501/691] Add a generic block editor module (#13088) --- .../developers/data/README.md | 3 +- .../developers/data/data-core-block-editor.md | 1050 +++++ .../developers/data/data-core-editor.md | 1053 +---- docs/manifest.json | 14 +- docs/tool/config.js | 7 +- lib/client-assets.php | 1 + lib/packages-dependencies.php | 10 + package-lock.json | 16 + package.json | 1 + packages/block-editor/.npmrc | 1 + packages/block-editor/CHANGELOG.md | 5 + packages/block-editor/README.md | 13 + packages/block-editor/package.json | 38 + packages/block-editor/src/components/index.js | 1 + .../src/components/provider/index.js | 152 + packages/block-editor/src/index.js | 11 + packages/block-editor/src/store/actions.js | 544 +++ .../src/store/array.js | 0 packages/block-editor/src/store/controls.js | 30 + packages/block-editor/src/store/defaults.js | 133 + packages/block-editor/src/store/effects.js | 150 + packages/block-editor/src/store/index.js | 29 + .../block-editor/src/store/middlewares.js | 45 + packages/block-editor/src/store/reducer.js | 901 +++++ packages/block-editor/src/store/selectors.js | 1395 +++++++ .../block-editor/src/store/test/actions.js | 336 ++ .../src/store/test/array.js | 0 .../block-editor/src/store/test/effects.js | 280 ++ .../block-editor/src/store/test/reducer.js | 1721 ++++++++ .../block-editor/src/store/test/selectors.js | 2321 +++++++++++ .../data/src/plugins/persistence/index.js | 33 +- .../specs/blocks/preformatted.test.js | 3 + .../specs/plugins/container-blocks.test.js | 1 + packages/edit-post/src/editor.js | 93 +- packages/editor/package.json | 1 + .../editor/src/components/block-list/block.js | 7 +- .../src/components/post-text-editor/index.js | 6 +- .../editor/src/components/provider/index.js | 135 +- .../editor/src/components/rich-text/index.js | 10 +- packages/editor/src/index.js | 1 + packages/editor/src/store/actions.js | 554 +-- packages/editor/src/store/controls.js | 20 +- packages/editor/src/store/defaults.js | 130 - packages/editor/src/store/effects.js | 159 +- .../src/store/effects/reusable-blocks.js | 41 +- .../src/store/effects/test/reusable-blocks.js | 157 +- packages/editor/src/store/reducer.js | 618 +-- packages/editor/src/store/selectors.js | 1844 ++------- packages/editor/src/store/test/actions.js | 328 -- packages/editor/src/store/test/effects.js | 306 +- packages/editor/src/store/test/reducer.js | 2143 ++-------- packages/editor/src/store/test/selectors.js | 3447 ++--------------- 52 files changed, 10511 insertions(+), 9787 deletions(-) create mode 100644 docs/designers-developers/developers/data/data-core-block-editor.md create mode 100644 packages/block-editor/.npmrc create mode 100644 packages/block-editor/CHANGELOG.md create mode 100644 packages/block-editor/README.md create mode 100644 packages/block-editor/package.json create mode 100644 packages/block-editor/src/components/index.js create mode 100644 packages/block-editor/src/components/provider/index.js create mode 100644 packages/block-editor/src/index.js create mode 100644 packages/block-editor/src/store/actions.js rename packages/{editor => block-editor}/src/store/array.js (100%) create mode 100644 packages/block-editor/src/store/controls.js create mode 100644 packages/block-editor/src/store/defaults.js create mode 100644 packages/block-editor/src/store/effects.js create mode 100644 packages/block-editor/src/store/index.js create mode 100644 packages/block-editor/src/store/middlewares.js create mode 100644 packages/block-editor/src/store/reducer.js create mode 100644 packages/block-editor/src/store/selectors.js create mode 100644 packages/block-editor/src/store/test/actions.js rename packages/{editor => block-editor}/src/store/test/array.js (100%) create mode 100644 packages/block-editor/src/store/test/effects.js create mode 100644 packages/block-editor/src/store/test/reducer.js create mode 100644 packages/block-editor/src/store/test/selectors.js diff --git a/docs/designers-developers/developers/data/README.md b/docs/designers-developers/developers/data/README.md index 7ba7f9264e8bc..7408d171144cf 100644 --- a/docs/designers-developers/developers/data/README.md +++ b/docs/designers-developers/developers/data/README.md @@ -3,7 +3,8 @@ - [**core**: WordPress Core Data](/docs/designers-developers/developers/data/data-core.md) - [**core/annotations**: Annotations](/docs/designers-developers/developers/data/data-core-annotations.md) - [**core/blocks**: Block Types Data](/docs/designers-developers/developers/data/data-core-blocks.md) - - [**core/editor**: The Editor’s Data](/docs/designers-developers/developers/data/data-core-editor.md) + - [**core/block-editor**: The Block Editor’s Data](/docs/designers-developers/developers/data/data-core-block-editor.md) + - [**core/editor**: The Post Editor’s Data](/docs/designers-developers/developers/data/data-core-editor.md) - [**core/edit-post**: The Editor’s UI Data](/docs/designers-developers/developers/data/data-core-edit-post.md) - [**core/notices**: Notices Data](/docs/designers-developers/developers/data/data-core-notices.md) - [**core/nux**: The NUX (New User Experience) Data](/docs/designers-developers/developers/data/data-core-nux.md) diff --git a/docs/designers-developers/developers/data/data-core-block-editor.md b/docs/designers-developers/developers/data/data-core-block-editor.md new file mode 100644 index 0000000000000..3166400bd3fbf --- /dev/null +++ b/docs/designers-developers/developers/data/data-core-block-editor.md @@ -0,0 +1,1050 @@ +# **core/block-editor**: The Block Editor’s Data + +## Selectors + +### getBlockDependantsCacheBust + +Returns a new reference when the inner blocks of a given block client ID +change. This is used exclusively as a memoized selector dependant, relying +on this selector's shared return value and recursively those of its inner +blocks defined as dependencies. This abuses mechanics of the selector +memoization to return from the original selector function only when +dependants change. + +*Parameters* + + * state: Editor state. + * clientId: Block client ID. + +### getBlockName + +Returns a block's name given its client ID, or null if no block exists with +the client ID. + +*Parameters* + + * state: Editor state. + * clientId: Block client ID. + +*Returns* + +Block name. + +### isBlockValid + +Returns whether a block is valid or not. + +*Parameters* + + * state: Editor state. + * clientId: Block client ID. + +*Returns* + +Is Valid. + +### getBlockAttributes + +Returns a block's attributes given its client ID, or null if no block exists with +the client ID. + +*Parameters* + + * state: Editor state. + * clientId: Block client ID. + +*Returns* + +Block attributes. + +### getBlock + +Returns a block given its client ID. This is a parsed copy of the block, +containing its `blockName`, `clientId`, and current `attributes` state. This +is not the block's registration settings, which must be retrieved from the +blocks module registration store. + +*Parameters* + + * state: Editor state. + * clientId: Block client ID. + +*Returns* + +Parsed block object. + +### getBlocks + +Returns all block objects for the current post being edited as an array in +the order they appear in the post. + +Note: It's important to memoize this selector to avoid return a new instance +on each call + +*Parameters* + + * state: Editor state. + * rootClientId: Optional root client ID of block list. + +*Returns* + +Post blocks. + +### getClientIdsOfDescendants + +Returns an array containing the clientIds of all descendants +of the blocks given. + +*Parameters* + + * state: Global application state. + * clientIds: Array of blocks to inspect. + +*Returns* + +ids of descendants. + +### getClientIdsWithDescendants + +Returns an array containing the clientIds of the top-level blocks +and their descendants of any depth (for nested blocks). + +*Parameters* + + * state: Global application state. + +*Returns* + +ids of top-level and descendant blocks. + +### getGlobalBlockCount + +Returns the total number of blocks, or the total number of blocks with a specific name in a post. +The number returned includes nested blocks. + +*Parameters* + + * state: Global application state. + * blockName: Optional block name, if specified only blocks of that type will be counted. + +*Returns* + +Number of blocks in the post, or number of blocks with name equal to blockName. + +### getBlocksByClientId + +Given an array of block client IDs, returns the corresponding array of block +objects. + +*Parameters* + + * state: Editor state. + * clientIds: Client IDs for which blocks are to be returned. + +*Returns* + +Block objects. + +### getBlockCount + +Returns the number of blocks currently present in the post. + +*Parameters* + + * state: Editor state. + * rootClientId: Optional root client ID of block list. + +*Returns* + +Number of blocks in the post. + +### getBlockSelectionStart + +Returns the current block selection start. This value may be null, and it +may represent either a singular block selection or multi-selection start. +A selection is singular if its start and end match. + +*Parameters* + + * state: Global application state. + +*Returns* + +Client ID of block selection start. + +### getBlockSelectionEnd + +Returns the current block selection end. This value may be null, and it +may represent either a singular block selection or multi-selection end. +A selection is singular if its start and end match. + +*Parameters* + + * state: Global application state. + +*Returns* + +Client ID of block selection end. + +### getSelectedBlockCount + +Returns the number of blocks currently selected in the post. + +*Parameters* + + * state: Global application state. + +*Returns* + +Number of blocks selected in the post. + +### hasSelectedBlock + +Returns true if there is a single selected block, or false otherwise. + +*Parameters* + + * state: Editor state. + +*Returns* + +Whether a single block is selected. + +### getSelectedBlockClientId + +Returns the currently selected block client ID, or null if there is no +selected block. + +*Parameters* + + * state: Editor state. + +*Returns* + +Selected block client ID. + +### getSelectedBlock + +Returns the currently selected block, or null if there is no selected block. + +*Parameters* + + * state: Global application state. + +*Returns* + +Selected block. + +### getBlockRootClientId + +Given a block client ID, returns the root block from which the block is +nested, an empty string for top-level blocks, or null if the block does not +exist. + +*Parameters* + + * state: Editor state. + * clientId: Block from which to find root client ID. + +*Returns* + +Root client ID, if exists + +### getBlockHierarchyRootClientId + +Given a block client ID, returns the root of the hierarchy from which the block is nested, return the block itself for root level blocks. + +*Parameters* + + * state: Editor state. + * clientId: Block from which to find root client ID. + +*Returns* + +Root client ID + +### getAdjacentBlockClientId + +Returns the client ID of the block adjacent one at the given reference +startClientId and modifier directionality. Defaults start startClientId to +the selected block, and direction as next block. Returns null if there is no +adjacent block. + +*Parameters* + + * state: Editor state. + * startClientId: Optional client ID of block from which to + search. + * modifier: Directionality multiplier (1 next, -1 + previous). + +*Returns* + +Return the client ID of the block, or null if none exists. + +### getPreviousBlockClientId + +Returns the previous block's client ID from the given reference start ID. +Defaults start to the selected block. Returns null if there is no previous +block. + +*Parameters* + + * state: Editor state. + * startClientId: Optional client ID of block from which to + search. + +*Returns* + +Adjacent block's client ID, or null if none exists. + +### getNextBlockClientId + +Returns the next block's client ID from the given reference start ID. +Defaults start to the selected block. Returns null if there is no next +block. + +*Parameters* + + * state: Editor state. + * startClientId: Optional client ID of block from which to + search. + +*Returns* + +Adjacent block's client ID, or null if none exists. + +### getSelectedBlocksInitialCaretPosition + +Returns the initial caret position for the selected block. +This position is to used to position the caret properly when the selected block changes. + +*Parameters* + + * state: Global application state. + +*Returns* + +Selected block. + +### getMultiSelectedBlockClientIds + +Returns the current multi-selection set of block client IDs, or an empty +array if there is no multi-selection. + +*Parameters* + + * state: Editor state. + +*Returns* + +Multi-selected block client IDs. + +### getMultiSelectedBlocks + +Returns the current multi-selection set of blocks, or an empty array if +there is no multi-selection. + +*Parameters* + + * state: Editor state. + +*Returns* + +Multi-selected block objects. + +### getFirstMultiSelectedBlockClientId + +Returns the client ID of the first block in the multi-selection set, or null +if there is no multi-selection. + +*Parameters* + + * state: Editor state. + +*Returns* + +First block client ID in the multi-selection set. + +### getLastMultiSelectedBlockClientId + +Returns the client ID of the last block in the multi-selection set, or null +if there is no multi-selection. + +*Parameters* + + * state: Editor state. + +*Returns* + +Last block client ID in the multi-selection set. + +### isFirstMultiSelectedBlock + +Returns true if a multi-selection exists, and the block corresponding to the +specified client ID is the first block of the multi-selection set, or false +otherwise. + +*Parameters* + + * state: Editor state. + * clientId: Block client ID. + +*Returns* + +Whether block is first in multi-selection. + +### isBlockMultiSelected + +Returns true if the client ID occurs within the block multi-selection, or +false otherwise. + +*Parameters* + + * state: Editor state. + * clientId: Block client ID. + +*Returns* + +Whether block is in multi-selection set. + +### isAncestorMultiSelected + +Returns true if an ancestor of the block is multi-selected, or false +otherwise. + +*Parameters* + + * state: Editor state. + * clientId: Block client ID. + +*Returns* + +Whether an ancestor of the block is in multi-selection + set. + +### getMultiSelectedBlocksStartClientId + +Returns the client ID of the block which begins the multi-selection set, or +null if there is no multi-selection. + +This is not necessarily the first client ID in the selection. + +*Parameters* + + * state: Editor state. + +*Returns* + +Client ID of block beginning multi-selection. + +### getMultiSelectedBlocksEndClientId + +Returns the client ID of the block which ends the multi-selection set, or +null if there is no multi-selection. + +This is not necessarily the last client ID in the selection. + +*Parameters* + + * state: Editor state. + +*Returns* + +Client ID of block ending multi-selection. + +### getBlockOrder + +Returns an array containing all block client IDs in the editor in the order +they appear. Optionally accepts a root client ID of the block list for which +the order should be returned, defaulting to the top-level block order. + +*Parameters* + + * state: Editor state. + * rootClientId: Optional root client ID of block list. + +*Returns* + +Ordered client IDs of editor blocks. + +### getBlockIndex + +Returns the index at which the block corresponding to the specified client +ID occurs within the block order, or `-1` if the block does not exist. + +*Parameters* + + * state: Editor state. + * clientId: Block client ID. + * rootClientId: Optional root client ID of block list. + +*Returns* + +Index at which block exists in order. + +### isBlockSelected + +Returns true if the block corresponding to the specified client ID is +currently selected and no multi-selection exists, or false otherwise. + +*Parameters* + + * state: Editor state. + * clientId: Block client ID. + +*Returns* + +Whether block is selected and multi-selection exists. + +### hasSelectedInnerBlock + +Returns true if one of the block's inner blocks is selected. + +*Parameters* + + * state: Editor state. + * clientId: Block client ID. + * deep: Perform a deep check. + +*Returns* + +Whether the block as an inner block selected + +### isBlockWithinSelection + +Returns true if the block corresponding to the specified client ID is +currently selected but isn't the last of the selected blocks. Here "last" +refers to the block sequence in the document, _not_ the sequence of +multi-selection, which is why `state.blockSelection.end` isn't used. + +*Parameters* + + * state: Editor state. + * clientId: Block client ID. + +*Returns* + +Whether block is selected and not the last in the + selection. + +### hasMultiSelection + +Returns true if a multi-selection has been made, or false otherwise. + +*Parameters* + + * state: Editor state. + +*Returns* + +Whether multi-selection has been made. + +### isMultiSelecting + +Whether in the process of multi-selecting or not. This flag is only true +while the multi-selection is being selected (by mouse move), and is false +once the multi-selection has been settled. + +*Parameters* + + * state: Global application state. + +*Returns* + +True if multi-selecting, false if not. + +### isSelectionEnabled + +Selector that returns if multi-selection is enabled or not. + +*Parameters* + + * state: Global application state. + +*Returns* + +True if it should be possible to multi-select blocks, false if multi-selection is disabled. + +### getBlockMode + +Returns the block's editing mode, defaulting to "visual" if not explicitly +assigned. + +*Parameters* + + * state: Editor state. + * clientId: Block client ID. + +*Returns* + +Block editing mode. + +### isTyping + +Returns true if the user is typing, or false otherwise. + +*Parameters* + + * state: Global application state. + +*Returns* + +Whether user is typing. + +### isCaretWithinFormattedText + +Returns true if the caret is within formatted text, or false otherwise. + +*Parameters* + + * state: Global application state. + +*Returns* + +Whether the caret is within formatted text. + +### getBlockInsertionPoint + +Returns the insertion point, the index at which the new inserted block would +be placed. Defaults to the last index. + +*Parameters* + + * state: Editor state. + +*Returns* + +Insertion point object with `rootClientId`, `index`. + +### isBlockInsertionPointVisible + +Returns true if we should show the block insertion point. + +*Parameters* + + * state: Global application state. + +*Returns* + +Whether the insertion point is visible or not. + +### isValidTemplate + +Returns whether the blocks matches the template or not. + +*Parameters* + + * state: null + +*Returns* + +Whether the template is valid or not. + +### getTemplate + +Returns the defined block template + +*Parameters* + + * state: null + +*Returns* + +Block Template + +### getTemplateLock + +Returns the defined block template lock. Optionally accepts a root block +client ID as context, otherwise defaulting to the global context. + +*Parameters* + + * state: Editor state. + * rootClientId: Optional block root client ID. + +*Returns* + +Block Template Lock + +### canInsertBlockType + +Determines if the given block type is allowed to be inserted into the block list. + +*Parameters* + + * state: Editor state. + * blockName: The name of the block type, e.g.' core/paragraph'. + * rootClientId: Optional root client ID of block list. + +*Returns* + +Whether the given block type is allowed to be inserted. + +### getInserterItems + +Determines the items that appear in the inserter. Includes both static +items (e.g. a regular block type) and dynamic items (e.g. a reusable block). + +Each item object contains what's necessary to display a button in the +inserter and handle its selection. + +The 'utility' property indicates how useful we think an item will be to the +user. There are 4 levels of utility: + +1. Blocks that are contextually useful (utility = 3) +2. Blocks that have been previously inserted (utility = 2) +3. Blocks that are in the common category (utility = 1) +4. All other blocks (utility = 0) + +The 'frecency' property is a heuristic (https://en.wikipedia.org/wiki/Frecency) +that combines block usage frequenty and recency. + +Items are returned ordered descendingly by their 'utility' and 'frecency'. + +*Parameters* + + * state: Editor state. + * rootClientId: Optional root client ID of block list. + +*Returns* + +Items that appear in inserter. + +### hasInserterItems + +Determines whether there are items to show in the inserter. + +*Parameters* + + * state: Editor state. + * rootClientId: Optional root client ID of block list. + +*Returns* + +Items that appear in inserter. + +### getBlockListSettings + +Returns the Block List settings of a block, if any exist. + +*Parameters* + + * state: Editor state. + * clientId: Block client ID. + +*Returns* + +Block settings of the block if set. + +### getEditorSettings + +Returns the editor settings. + +*Parameters* + + * state: Editor state. + +*Returns* + +The editor settings object. + +### isLastBlockChangePersistent + +Returns true if the most recent block change is be considered persistent, or +false otherwise. A persistent change is one committed by BlockEditorProvider +via its `onChange` callback, in addition to `onInput`. + +*Parameters* + + * state: Block editor state. + +*Returns* + +Whether the most recent block change was persistent. + +## Actions + +### resetBlocks + +Returns an action object used in signalling that blocks state should be +reset to the specified array of blocks, taking precedence over any other +content reflected as an edit in state. + +*Parameters* + + * blocks: Array of blocks. + +### receiveBlocks + +Returns an action object used in signalling that blocks have been received. +Unlike resetBlocks, these should be appended to the existing known set, not +replacing. + +*Parameters* + + * blocks: Array of block objects. + +### updateBlockAttributes + +Returns an action object used in signalling that the block attributes with +the specified client ID has been updated. + +*Parameters* + + * clientId: Block client ID. + * attributes: Block attributes to be merged. + +### updateBlock + +Returns an action object used in signalling that the block with the +specified client ID has been updated. + +*Parameters* + + * clientId: Block client ID. + * updates: Block attributes to be merged. + +### selectBlock + +Returns an action object used in signalling that the block with the +specified client ID has been selected, optionally accepting a position +value reflecting its selection directionality. An initialPosition of -1 +reflects a reverse selection. + +*Parameters* + + * clientId: Block client ID. + * initialPosition: Optional initial position. Pass as -1 to + reflect reverse selection. + +### selectPreviousBlock + +Yields action objects used in signalling that the block preceding the given +clientId should be selected. + +*Parameters* + + * clientId: Block client ID. + +### selectNextBlock + +Yields action objects used in signalling that the block following the given +clientId should be selected. + +*Parameters* + + * clientId: Block client ID. + +### startMultiSelect + +Returns an action object used in signalling that a block multi-selection has started. + +### stopMultiSelect + +Returns an action object used in signalling that block multi-selection stopped. + +### multiSelect + +Returns an action object used in signalling that block multi-selection changed. + +*Parameters* + + * start: First block of the multi selection. + * end: Last block of the multiselection. + +### clearSelectedBlock + +Returns an action object used in signalling that the block selection is cleared. + +### toggleSelection + +Returns an action object that enables or disables block selection. + +*Parameters* + + * boolean: [isSelectionEnabled=true] Whether block selection should + be enabled. + +### replaceBlocks + +Returns an action object signalling that a blocks should be replaced with +one or more replacement blocks. + +*Parameters* + + * clientIds: Block client ID(s) to replace. + * blocks: Replacement block(s). + +### replaceBlock + +Returns an action object signalling that a single block should be replaced +with one or more replacement blocks. + +*Parameters* + + * clientId: Block client ID to replace. + * block: Replacement block(s). + +### moveBlockToPosition + +Returns an action object signalling that an indexed block should be moved +to a new index. + +*Parameters* + + * clientId: The client ID of the block. + * fromRootClientId: Root client ID source. + * toRootClientId: Root client ID destination. + * index: The index to move the block into. + +### insertBlock + +Returns an action object used in signalling that a single block should be +inserted, optionally at a specific index respective a root block list. + +*Parameters* + + * block: Block object to insert. + * index: Index at which block should be inserted. + * rootClientId: Optional root client ID of block list on which to insert. + * updateSelection: If true block selection will be updated. If false, block selection will not change. Defaults to true. + +### insertBlocks + +Returns an action object used in signalling that an array of blocks should +be inserted, optionally at a specific index respective a root block list. + +*Parameters* + + * blocks: Block objects to insert. + * index: Index at which block should be inserted. + * rootClientId: Optional root client ID of block list on which to insert. + * updateSelection: If true block selection will be updated. If false, block selection will not change. Defaults to true. + +### showInsertionPoint + +Returns an action object used in signalling that the insertion point should +be shown. + +*Parameters* + + * rootClientId: Optional root client ID of block list on + which to insert. + * index: Index at which block should be inserted. + +### hideInsertionPoint + +Returns an action object hiding the insertion point. + +### setTemplateValidity + +Returns an action object resetting the template validity. + +*Parameters* + + * isValid: template validity flag. + +### synchronizeTemplate + +Returns an action object synchronize the template with the list of blocks + +### mergeBlocks + +Returns an action object used in signalling that two blocks should be merged + +*Parameters* + + * firstBlockClientId: Client ID of the first block to merge. + * secondBlockClientId: Client ID of the second block to merge. + +### removeBlocks + +Yields action objects used in signalling that the blocks corresponding to +the set of specified client IDs are to be removed. + +*Parameters* + + * clientIds: Client IDs of blocks to remove. + * selectPrevious: True if the previous block should be + selected when a block is removed. + +### removeBlock + +Returns an action object used in signalling that the block with the +specified client ID is to be removed. + +*Parameters* + + * clientId: Client ID of block to remove. + * selectPrevious: True if the previous block should be + selected when a block is removed. + +### toggleBlockMode + +Returns an action object used to toggle the block editing mode between +visual and HTML modes. + +*Parameters* + + * clientId: Block client ID. + +### startTyping + +Returns an action object used in signalling that the user has begun to type. + +### stopTyping + +Returns an action object used in signalling that the user has stopped typing. + +### enterFormattedText + +Returns an action object used in signalling that the caret has entered formatted text. + +### exitFormattedText + +Returns an action object used in signalling that the user caret has exited formatted text. + +### insertDefaultBlock + +Returns an action object used in signalling that a new block of the default +type should be added to the block list. + +*Parameters* + + * attributes: Optional attributes of the block to assign. + * rootClientId: Optional root client ID of block list on which + to append. + * index: Optional index where to insert the default block + +### updateBlockListSettings + +Returns an action object that changes the nested settings of a given block. + +*Parameters* + + * clientId: Client ID of the block whose nested setting are + being received. + * settings: Object with the new settings for the nested block. + +### updateEditorSettings + +Returns an action object used in signalling that the editor settings have been updated. + +*Parameters* + + * settings: Updated settings + +### __unstableSaveReusableBlock + +Returns an action object used in signalling that a temporary reusable blocks have been saved +in order to switch its temporary id with the real id. + +*Parameters* + + * id: Reusable block's id. + * updatedId: Updated block's id. + +### __unstableMarkLastChangeAsPersistent + +Returns an action object used in signalling that the last block change should be marked explicitely as persistent. \ No newline at end of file diff --git a/docs/designers-developers/developers/data/data-core-editor.md b/docs/designers-developers/developers/data/data-core-editor.md index 96e65b41659de..7e95832dc5bdb 100644 --- a/docs/designers-developers/developers/data/data-core-editor.md +++ b/docs/designers-developers/developers/data/data-core-editor.md @@ -1,4 +1,4 @@ -# **core/editor**: The Editor’s Data +# **core/editor**: The Post Editor’s Data ## Selectors @@ -365,676 +365,6 @@ and modified date are the same. Whether the edited post has a floating date value. -### getBlockDependantsCacheBust - -Returns a new reference when the inner blocks of a given block client ID -change. This is used exclusively as a memoized selector dependant, relying -on this selector's shared return value and recursively those of its inner -blocks defined as dependencies. This abuses mechanics of the selector -memoization to return from the original selector function only when -dependants change. - -*Parameters* - - * state: Editor state. - * clientId: Block client ID. - -*Returns* - -A value whose reference will change only when inner blocks of - the given block client ID change. - -### getBlockName - -Returns a block's name given its client ID, or null if no block exists with -the client ID. - -*Parameters* - - * state: Editor state. - * clientId: Block client ID. - -*Returns* - -Block name. - -### isBlockValid - -Returns whether a block is valid or not. - -*Parameters* - - * state: Editor state. - * clientId: Block client ID. - -*Returns* - -Is Valid. - -### getBlockAttributes - -Returns a block's attributes given its client ID, or null if no block exists with -the client ID. - -*Parameters* - - * state: Editor state. - * clientId: Block client ID. - -*Returns* - -Block attributes. - -### getBlock - -Returns a block given its client ID. This is a parsed copy of the block, -containing its `blockName`, `clientId`, and current `attributes` state. This -is not the block's registration settings, which must be retrieved from the -blocks module registration store. - -*Parameters* - - * state: Editor state. - * clientId: Block client ID. - -*Returns* - -Parsed block object. - -### getBlocks - -Returns all block objects for the current post being edited as an array in -the order they appear in the post. - -Note: It's important to memoize this selector to avoid return a new instance -on each call - -*Parameters* - - * state: Editor state. - * rootClientId: Optional root client ID of block list. - -*Returns* - -Post blocks. - -### getClientIdsOfDescendants - -Returns an array containing the clientIds of all descendants -of the blocks given. - -*Parameters* - - * state: Global application state. - * clientIds: Array of blocks to inspect. - -*Returns* - -ids of descendants. - -### getClientIdsWithDescendants - -Returns an array containing the clientIds of the top-level blocks -and their descendants of any depth (for nested blocks). - -*Parameters* - - * state: Global application state. - -*Returns* - -ids of top-level and descendant blocks. - -### getGlobalBlockCount - -Returns the total number of blocks, or the total number of blocks with a specific name in a post. -The number returned includes nested blocks. - -*Parameters* - - * state: Global application state. - * blockName: Optional block name, if specified only blocks of that type will be counted. - -*Returns* - -Number of blocks in the post, or number of blocks with name equal to blockName. - -### getBlocksByClientId - -Given an array of block client IDs, returns the corresponding array of block -objects. - -*Parameters* - - * state: Editor state. - * clientIds: Client IDs for which blocks are to be returned. - -*Returns* - -Block objects. - -### getBlockCount - -Returns the number of blocks currently present in the post. - -*Parameters* - - * state: Editor state. - * rootClientId: Optional root client ID of block list. - -*Returns* - -Number of blocks in the post. - -### getBlockSelectionStart - -Returns the current block selection start. This value may be null, and it -may represent either a singular block selection or multi-selection start. -A selection is singular if its start and end match. - -*Parameters* - - * state: Global application state. - -*Returns* - -Client ID of block selection start. - -### getBlockSelectionEnd - -Returns the current block selection end. This value may be null, and it -may represent either a singular block selection or multi-selection end. -A selection is singular if its start and end match. - -*Parameters* - - * state: Global application state. - -*Returns* - -Client ID of block selection end. - -### getSelectedBlockCount - -Returns the number of blocks currently selected in the post. - -*Parameters* - - * state: Global application state. - -*Returns* - -Number of blocks selected in the post. - -### hasSelectedBlock - -Returns true if there is a single selected block, or false otherwise. - -*Parameters* - - * state: Editor state. - -*Returns* - -Whether a single block is selected. - -### getSelectedBlockClientId - -Returns the currently selected block client ID, or null if there is no -selected block. - -*Parameters* - - * state: Editor state. - -*Returns* - -Selected block client ID. - -### getSelectedBlock - -Returns the currently selected block, or null if there is no selected block. - -*Parameters* - - * state: Global application state. - -*Returns* - -Selected block. - -### getBlockRootClientId - -Given a block client ID, returns the root block from which the block is -nested, an empty string for top-level blocks, or null if the block does not -exist. - -*Parameters* - - * state: Editor state. - * clientId: Block from which to find root client ID. - -*Returns* - -Root client ID, if exists - -### getBlockHierarchyRootClientId - -Given a block client ID, returns the root of the hierarchy from which the block is nested, return the block itself for root level blocks. - -*Parameters* - - * state: Editor state. - * clientId: Block from which to find root client ID. - -*Returns* - -Root client ID - -### getAdjacentBlockClientId - -Returns the client ID of the block adjacent one at the given reference -startClientId and modifier directionality. Defaults start startClientId to -the selected block, and direction as next block. Returns null if there is no -adjacent block. - -*Parameters* - - * state: Editor state. - * startClientId: Optional client ID of block from which to - search. - * modifier: Directionality multiplier (1 next, -1 - previous). - -*Returns* - -Return the client ID of the block, or null if none exists. - -### getPreviousBlockClientId - -Returns the previous block's client ID from the given reference start ID. -Defaults start to the selected block. Returns null if there is no previous -block. - -*Parameters* - - * state: Editor state. - * startClientId: Optional client ID of block from which to - search. - -*Returns* - -Adjacent block's client ID, or null if none exists. - -### getNextBlockClientId - -Returns the next block's client ID from the given reference start ID. -Defaults start to the selected block. Returns null if there is no next -block. - -*Parameters* - - * state: Editor state. - * startClientId: Optional client ID of block from which to - search. - -*Returns* - -Adjacent block's client ID, or null if none exists. - -### getSelectedBlocksInitialCaretPosition - -Returns the initial caret position for the selected block. -This position is to used to position the caret properly when the selected block changes. - -*Parameters* - - * state: Global application state. - -*Returns* - -Selected block. - -### getMultiSelectedBlockClientIds - -Returns the current multi-selection set of block client IDs, or an empty -array if there is no multi-selection. - -*Parameters* - - * state: Editor state. - -*Returns* - -Multi-selected block client IDs. - -### getMultiSelectedBlocks - -Returns the current multi-selection set of blocks, or an empty array if -there is no multi-selection. - -*Parameters* - - * state: Editor state. - -*Returns* - -Multi-selected block objects. - -### getFirstMultiSelectedBlockClientId - -Returns the client ID of the first block in the multi-selection set, or null -if there is no multi-selection. - -*Parameters* - - * state: Editor state. - -*Returns* - -First block client ID in the multi-selection set. - -### getLastMultiSelectedBlockClientId - -Returns the client ID of the last block in the multi-selection set, or null -if there is no multi-selection. - -*Parameters* - - * state: Editor state. - -*Returns* - -Last block client ID in the multi-selection set. - -### isFirstMultiSelectedBlock - -Returns true if a multi-selection exists, and the block corresponding to the -specified client ID is the first block of the multi-selection set, or false -otherwise. - -*Parameters* - - * state: Editor state. - * clientId: Block client ID. - -*Returns* - -Whether block is first in multi-selection. - -### isBlockMultiSelected - -Returns true if the client ID occurs within the block multi-selection, or -false otherwise. - -*Parameters* - - * state: Editor state. - * clientId: Block client ID. - -*Returns* - -Whether block is in multi-selection set. - -### isAncestorMultiSelected - -Returns true if an ancestor of the block is multi-selected, or false -otherwise. - -*Parameters* - - * state: Editor state. - * clientId: Block client ID. - -*Returns* - -Whether an ancestor of the block is in multi-selection - set. - -### getMultiSelectedBlocksStartClientId - -Returns the client ID of the block which begins the multi-selection set, or -null if there is no multi-selection. - -This is not necessarily the first client ID in the selection. - -*Parameters* - - * state: Editor state. - -*Returns* - -Client ID of block beginning multi-selection. - -### getMultiSelectedBlocksEndClientId - -Returns the client ID of the block which ends the multi-selection set, or -null if there is no multi-selection. - -This is not necessarily the last client ID in the selection. - -*Parameters* - - * state: Editor state. - -*Returns* - -Client ID of block ending multi-selection. - -### getBlockOrder - -Returns an array containing all block client IDs in the editor in the order -they appear. Optionally accepts a root client ID of the block list for which -the order should be returned, defaulting to the top-level block order. - -*Parameters* - - * state: Editor state. - * rootClientId: Optional root client ID of block list. - -*Returns* - -Ordered client IDs of editor blocks. - -### getBlockIndex - -Returns the index at which the block corresponding to the specified client -ID occurs within the block order, or `-1` if the block does not exist. - -*Parameters* - - * state: Editor state. - * clientId: Block client ID. - * rootClientId: Optional root client ID of block list. - -*Returns* - -Index at which block exists in order. - -### isBlockSelected - -Returns true if the block corresponding to the specified client ID is -currently selected and no multi-selection exists, or false otherwise. - -*Parameters* - - * state: Editor state. - * clientId: Block client ID. - -*Returns* - -Whether block is selected and multi-selection exists. - -### hasSelectedInnerBlock - -Returns true if one of the block's inner blocks is selected. - -*Parameters* - - * state: Editor state. - * clientId: Block client ID. - * deep: Perform a deep check. - -*Returns* - -Whether the block as an inner block selected - -### isBlockWithinSelection - -Returns true if the block corresponding to the specified client ID is -currently selected but isn't the last of the selected blocks. Here "last" -refers to the block sequence in the document, _not_ the sequence of -multi-selection, which is why `state.blockSelection.end` isn't used. - -*Parameters* - - * state: Editor state. - * clientId: Block client ID. - -*Returns* - -Whether block is selected and not the last in the - selection. - -### hasMultiSelection - -Returns true if a multi-selection has been made, or false otherwise. - -*Parameters* - - * state: Editor state. - -*Returns* - -Whether multi-selection has been made. - -### isMultiSelecting - -Whether in the process of multi-selecting or not. This flag is only true -while the multi-selection is being selected (by mouse move), and is false -once the multi-selection has been settled. - -*Parameters* - - * state: Global application state. - -*Returns* - -True if multi-selecting, false if not. - -### isSelectionEnabled - -Selector that returns if multi-selection is enabled or not. - -*Parameters* - - * state: Global application state. - -*Returns* - -True if it should be possible to multi-select blocks, false if multi-selection is disabled. - -### getBlockMode - -Returns the block's editing mode, defaulting to "visual" if not explicitly -assigned. - -*Parameters* - - * state: Editor state. - * clientId: Block client ID. - -*Returns* - -Block editing mode. - -### isTyping - -Returns true if the user is typing, or false otherwise. - -*Parameters* - - * state: Global application state. - -*Returns* - -Whether user is typing. - -### isCaretWithinFormattedText - -Returns true if the caret is within formatted text, or false otherwise. - -*Parameters* - - * state: Global application state. - -*Returns* - -Whether the caret is within formatted text. - -### getBlockInsertionPoint - -Returns the insertion point, the index at which the new inserted block would -be placed. Defaults to the last index. - -*Parameters* - - * state: Editor state. - -*Returns* - -Insertion point object with `rootClientId`, `index`. - -### isBlockInsertionPointVisible - -Returns true if we should show the block insertion point. - -*Parameters* - - * state: Global application state. - -*Returns* - -Whether the insertion point is visible or not. - -### isValidTemplate - -Returns whether the blocks matches the template or not. - -*Parameters* - - * state: null - -*Returns* - -Whether the template is valid or not. - -### getTemplate - -Returns the defined block template - -*Parameters* - - * state: null - -*Returns* - -Block Template - -### getTemplateLock - -Returns the defined block template lock. Optionally accepts a root block -client ID as context, otherwise defaulting to the global context. - -*Parameters* - - * state: Editor state. - * rootClientId: Optional block root client ID. - -*Returns* - -Block Template Lock - ### isSavingPost Returns true if the post is currently being saved, or false otherwise. @@ -1149,63 +479,6 @@ before falling back to serialization of block state. Post content. -### canInsertBlockType - -Determines if the given block type is allowed to be inserted into the block list. - -*Parameters* - - * state: Editor state. - * blockName: The name of the block type, e.g.' core/paragraph'. - * rootClientId: Optional root client ID of block list. - -*Returns* - -Whether the given block type is allowed to be inserted. - -### getInserterItems - -Determines the items that appear in the inserter. Includes both static -items (e.g. a regular block type) and dynamic items (e.g. a reusable block). - -Each item object contains what's necessary to display a button in the -inserter and handle its selection. - -The 'utility' property indicates how useful we think an item will be to the -user. There are 4 levels of utility: - -1. Blocks that are contextually useful (utility = 3) -2. Blocks that have been previously inserted (utility = 2) -3. Blocks that are in the common category (utility = 1) -4. All other blocks (utility = 0) - -The 'frecency' property is a heuristic (https://en.wikipedia.org/wiki/Frecency) -that combines block usage frequenty and recency. - -Items are returned ordered descendingly by their 'utility' and 'frecency'. - -*Parameters* - - * state: Editor state. - * rootClientId: Optional root client ID of block list. - -*Returns* - -Items that appear in inserter. - -### hasInserterItems - -Determines whether there are items to show in the inserter. - -*Parameters* - - * state: Editor state. - * rootClientId: Optional root client ID of block list. - -*Returns* - -Items that appear in inserter. - ### __experimentalGetReusableBlock Returns the reusable block with the given ID. @@ -1336,44 +609,6 @@ before state satisfies the given predicate function. Whether predicate matches for some history. -### getBlockListSettings - -Returns the Block List settings of a block, if any exist. - -*Parameters* - - * state: Editor state. - * clientId: Block client ID. - -*Returns* - -Block settings of the block if set. - -### getEditorSettings - -Returns the editor settings. - -*Parameters* - - * state: Editor state. - -*Returns* - -The editor settings object. - -### getTokenSettings - -Returns the token settings. - -*Parameters* - - * state: Editor state. - * name: Token name. - -*Returns* - -Token settings object, or the named token settings object if set. - ### isPostLocked Returns whether the post is locked. @@ -1459,6 +694,30 @@ or skipped when the user clicks the "publish" button. Whether the pre-publish panel should be shown or not. +### getEditorBlocks + +Return the current block list. + +*Parameters* + + * state: null + +*Returns* + +Block list. + +### __unstableIsEditorReady + +Is the editor ready + +*Parameters* + + * state: null + +*Returns* + +is Ready. + ## Actions ### setupEditor @@ -1470,6 +729,7 @@ the specified post object and editor settings. * post: Post object. * edits: Initial edited attributes object. + * template: Block Template. ### resetPost @@ -1505,170 +765,6 @@ Returns an action object used to setup the editor state when first opening an ed *Parameters* * post: Post object. - * blocks: Array of blocks. - -### resetBlocks - -Returns an action object used in signalling that blocks state should be -reset to the specified array of blocks, taking precedence over any other -content reflected as an edit in state. - -*Parameters* - - * blocks: Array of blocks. - -### receiveBlocks - -Returns an action object used in signalling that blocks have been received. -Unlike resetBlocks, these should be appended to the existing known set, not -replacing. - -*Parameters* - - * blocks: Array of block objects. - -### updateBlockAttributes - -Returns an action object used in signalling that the block attributes with -the specified client ID has been updated. - -*Parameters* - - * clientId: Block client ID. - * attributes: Block attributes to be merged. - -### updateBlock - -Returns an action object used in signalling that the block with the -specified client ID has been updated. - -*Parameters* - - * clientId: Block client ID. - * updates: Block attributes to be merged. - -### selectBlock - -Returns an action object used in signalling that the block with the -specified client ID has been selected, optionally accepting a position -value reflecting its selection directionality. An initialPosition of -1 -reflects a reverse selection. - -*Parameters* - - * clientId: Block client ID. - * initialPosition: Optional initial position. Pass as -1 to - reflect reverse selection. - -### selectPreviousBlock - -Yields action objects used in signalling that the block preceding the given -clientId should be selected. - -*Parameters* - - * clientId: Block client ID. - -### selectNextBlock - -Yields action objects used in signalling that the block following the given -clientId should be selected. - -*Parameters* - - * clientId: Block client ID. - -### toggleSelection - -Returns an action object that enables or disables block selection. - -*Parameters* - - * boolean: [isSelectionEnabled=true] Whether block selection should - be enabled. - -### replaceBlocks - -Returns an action object signalling that a blocks should be replaced with -one or more replacement blocks. - -*Parameters* - - * clientIds: Block client ID(s) to replace. - * blocks: Replacement block(s). - -### replaceBlock - -Returns an action object signalling that a single block should be replaced -with one or more replacement blocks. - -*Parameters* - - * clientId: Block client ID to replace. - * block: Replacement block(s). - -### moveBlockToPosition - -Returns an action object signalling that an indexed block should be moved -to a new index. - -*Parameters* - - * clientId: The client ID of the block. - * fromRootClientId: Root client ID source. - * toRootClientId: Root client ID destination. - * index: The index to move the block into. - -### insertBlock - -Returns an action object used in signalling that a single block should be -inserted, optionally at a specific index respective a root block list. - -*Parameters* - - * block: Block object to insert. - * index: Index at which block should be inserted. - * rootClientId: Optional root client ID of block list on which to insert. - * updateSelection: If true block selection will be updated. If false, block selection will not change. Defaults to true. - -### insertBlocks - -Returns an action object used in signalling that an array of blocks should -be inserted, optionally at a specific index respective a root block list. - -*Parameters* - - * blocks: Block objects to insert. - * index: Index at which block should be inserted. - * rootClientId: Optional root client ID of block list on which to insert. - * updateSelection: If true block selection will be updated. If false, block selection will not change. Defaults to true. - -### showInsertionPoint - -Returns an action object used in signalling that the insertion point should -be shown. - -*Parameters* - - * rootClientId: Optional root client ID of block list on - which to insert. - * index: Index at which block should be inserted. - -### hideInsertionPoint - -Returns an action object hiding the insertion point. - -### setTemplateValidity - -Returns an action object resetting the template validity. - -*Parameters* - - * isValid: template validity flag. - -### synchronizeTemplate - -Returns an action object synchronize the template with the list of blocks ### editPost @@ -1688,15 +784,6 @@ Returns an action object to save the post. * options: Options for the save. * options.isAutosave: Perform an autosave if true. -### mergeBlocks - -Returns an action object used in signalling that two blocks should be merged - -*Parameters* - - * firstBlockClientId: Client ID of the first block to merge. - * secondBlockClientId: Client ID of the second block to merge. - ### autosave Returns an action object used in signalling that the post should autosave. @@ -1719,53 +806,6 @@ Returns an action object used in signalling that undo history should pop. Returns an action object used in signalling that undo history record should be created. -### removeBlocks - -Yields action objects used in signalling that the blocks corresponding to -the set of specified client IDs are to be removed. - -*Parameters* - - * clientIds: Client IDs of blocks to remove. - * selectPrevious: True if the previous block should be - selected when a block is removed. - -### removeBlock - -Returns an action object used in signalling that the block with the -specified client ID is to be removed. - -*Parameters* - - * clientId: Client ID of block to remove. - * selectPrevious: True if the previous block should be - selected when a block is removed. - -### toggleBlockMode - -Returns an action object used to toggle the block editing mode between -visual and HTML modes. - -*Parameters* - - * clientId: Block client ID. - -### startTyping - -Returns an action object used in signalling that the user has begun to type. - -### stopTyping - -Returns an action object used in signalling that the user has stopped typing. - -### enterFormattedText - -Returns an action object used in signalling that the caret has entered formatted text. - -### exitFormattedText - -Returns an action object used in signalling that the user caret has exited formatted text. - ### updatePostLock Returns an action object used to lock the editor. @@ -1838,36 +878,6 @@ Returns an action object used to convert a static block into a reusable block. * clientIds: The client IDs of the block to detach. -### insertDefaultBlock - -Returns an action object used in signalling that a new block of the default -type should be added to the block list. - -*Parameters* - - * attributes: Optional attributes of the block to assign. - * rootClientId: Optional root client ID of block list on which - to append. - * index: Optional index where to insert the default block - -### updateBlockListSettings - -Returns an action object that changes the nested settings of a given block. - -*Parameters* - - * clientId: Client ID of the block whose nested setting are - being received. - * settings: Object with the new settings for the nested block. - -### updateEditorSettings - -Returns an action object used in signalling that the editor settings have been updated. - -*Parameters* - - * settings: Updated settings - ### enablePublishSidebar Returns an action object used in signalling that the user has enabled the publish sidebar. @@ -1890,4 +900,13 @@ Returns an action object used to signal that post saving is unlocked. *Parameters* - * lockName: The lock name. \ No newline at end of file + * lockName: The lock name. + +### resetEditorBlocks + +Returns an action object used to signal that the blocks have been updated. + +*Parameters* + + * blocks: Block Array. + * options: Optional options. \ No newline at end of file diff --git a/docs/manifest.json b/docs/manifest.json index 026790426f1ec..c6eccbce1d73d 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -557,6 +557,12 @@ "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/blob/README.md", "parent": "packages" }, + { + "title": "@wordpress/block-editor", + "slug": "packages-block-editor", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/block-editor/README.md", + "parent": "packages" + }, { "title": "@wordpress/block-library", "slug": "packages-block-library", @@ -1248,7 +1254,13 @@ "parent": "data" }, { - "title": "The Editor’s Data", + "title": "The Block Editor’s Data", + "slug": "data-core-block-editor", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core-block-editor.md", + "parent": "data" + }, + { + "title": "The Post Editor’s Data", "slug": "data-core-editor", "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core-editor.md", "parent": "data" diff --git a/docs/tool/config.js b/docs/tool/config.js index 87900c4c624fa..6dd2341e3d3c8 100644 --- a/docs/tool/config.js +++ b/docs/tool/config.js @@ -25,8 +25,13 @@ module.exports = { selectors: [ path.resolve( root, 'packages/blocks/src/store/selectors.js' ) ], actions: [ path.resolve( root, 'packages/blocks/src/store/actions.js' ) ], }, + 'core/block-editor': { + title: 'The Block Editor’s Data', + selectors: [ path.resolve( root, 'packages/block-editor/src/store/selectors.js' ) ], + actions: [ path.resolve( root, 'packages/block-editor/src/store/actions.js' ) ], + }, 'core/editor': { - title: 'The Editor’s Data', + title: 'The Post Editor’s Data', selectors: [ path.resolve( root, 'packages/editor/src/store/selectors.js' ) ], actions: [ path.resolve( root, 'packages/editor/src/store/actions.js' ) ], }, diff --git a/lib/client-assets.php b/lib/client-assets.php index b34e428425e80..72d3522075a01 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -205,6 +205,7 @@ function gutenberg_register_scripts_and_styles() { ' wp.data', ' .use( wp.data.plugins.persistence, { storageKey: storageKey } )', ' .use( wp.data.plugins.controls );', + ' wp.data.plugins.persistence.__unstableMigrate( { storageKey: storageKey } );', '} )()', ) ) diff --git a/lib/packages-dependencies.php b/lib/packages-dependencies.php index 9da875e8df2cc..e2f135bda24d9 100644 --- a/lib/packages-dependencies.php +++ b/lib/packages-dependencies.php @@ -44,6 +44,15 @@ ), 'wp-block-serialization-default-parser' => array(), 'wp-block-serialization-spec-parser' => array(), + 'wp-block-editor' => array( + 'lodash', + 'wp-blocks', + 'wp-compose', + 'wp-components', + 'wp-data', + 'wp-element', + 'wp-i18n', + ), 'wp-blocks' => array( 'lodash', 'wp-autop', @@ -138,6 +147,7 @@ 'wp-a11y', 'wp-api-fetch', 'wp-blob', + 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', diff --git a/package-lock.json b/package-lock.json index 656ba9ae3cd0e..a5737be8fefbc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2531,6 +2531,21 @@ "@babel/runtime": "^7.3.1" } }, + "@wordpress/block-editor": { + "version": "file:packages/block-editor", + "requires": { + "@babel/runtime": "^7.0.0", + "@wordpress/blocks": "file:packages/blocks", + "@wordpress/components": "file:packages/components", + "@wordpress/compose": "file:packages/compose", + "@wordpress/data": "file:packages/data", + "@wordpress/element": "file:packages/element", + "@wordpress/i18n": "file:packages/i18n", + "lodash": "^4.17.10", + "refx": "^3.0.0", + "rememo": "^3.0.0" + } + }, "@wordpress/block-library": { "version": "file:packages/block-library", "requires": { @@ -2763,6 +2778,7 @@ "@wordpress/a11y": "file:packages/a11y", "@wordpress/api-fetch": "file:packages/api-fetch", "@wordpress/blob": "file:packages/blob", + "@wordpress/block-editor": "file:packages/block-editor", "@wordpress/blocks": "file:packages/blocks", "@wordpress/components": "file:packages/components", "@wordpress/compose": "file:packages/compose", diff --git a/package.json b/package.json index 2299994e4f385..88a28a598a7f7 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@wordpress/api-fetch": "file:packages/api-fetch", "@wordpress/autop": "file:packages/autop", "@wordpress/blob": "file:packages/blob", + "@wordpress/block-editor": "file:packages/block-editor", "@wordpress/block-library": "file:packages/block-library", "@wordpress/block-serialization-default-parser": "file:packages/block-serialization-default-parser", "@wordpress/block-serialization-spec-parser": "file:packages/block-serialization-spec-parser", diff --git a/packages/block-editor/.npmrc b/packages/block-editor/.npmrc new file mode 100644 index 0000000000000..43c97e719a5a8 --- /dev/null +++ b/packages/block-editor/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/packages/block-editor/CHANGELOG.md b/packages/block-editor/CHANGELOG.md new file mode 100644 index 0000000000000..b5527e39e4e7c --- /dev/null +++ b/packages/block-editor/CHANGELOG.md @@ -0,0 +1,5 @@ +## 1.0.0 (Unreleased) + +### New Features + +- Initial version. diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md new file mode 100644 index 0000000000000..9e07cf10eb2ce --- /dev/null +++ b/packages/block-editor/README.md @@ -0,0 +1,13 @@ +# Block Editor + +Generic Block Editor Module. + +## Installation + +Install the module + +```bash +npm install @wordpress/block-editor --save +``` + +_This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. Learn more about it in [Babel docs](https://babeljs.io/docs/en/next/caveats)._ diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json new file mode 100644 index 0000000000000..d4f3cf7f757c7 --- /dev/null +++ b/packages/block-editor/package.json @@ -0,0 +1,38 @@ +{ + "name": "@wordpress/block-editor", + "version": "1.0.0-alpha.0", + "description": "Generic Block Editor.", + "author": "The WordPress Contributors", + "license": "GPL-2.0-or-later", + "keywords": [ + "wordpress", + "editor", + "blocks" + ], + "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/block-editor/README.md", + "repository": { + "type": "git", + "url": "https://github.com/WordPress/gutenberg.git" + }, + "bugs": { + "url": "https://github.com/WordPress/gutenberg/issues" + }, + "main": "build/index.js", + "module": "build-module/index.js", + "react-native": "src/index", + "dependencies": { + "@babel/runtime": "^7.0.0", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "lodash": "^4.17.10", + "refx": "^3.0.0", + "rememo": "^3.0.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/block-editor/src/components/index.js b/packages/block-editor/src/components/index.js new file mode 100644 index 0000000000000..cb2cca8f110ba --- /dev/null +++ b/packages/block-editor/src/components/index.js @@ -0,0 +1 @@ +export { default as BlockEditorProvider } from './provider'; diff --git a/packages/block-editor/src/components/provider/index.js b/packages/block-editor/src/components/provider/index.js new file mode 100644 index 0000000000000..a1d3063962b5d --- /dev/null +++ b/packages/block-editor/src/components/provider/index.js @@ -0,0 +1,152 @@ +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; +import { DropZoneProvider, SlotFillProvider } from '@wordpress/components'; +import { withDispatch, RegistryConsumer } from '@wordpress/data'; +import { createHigherOrderComponent, compose } from '@wordpress/compose'; + +/** + * Higher-order component which renders the original component with the current + * registry context passed as its `registry` prop. + * + * @param {WPComponent} OriginalComponent Original component. + * + * @return {WPComponent} Enhanced component. + */ +const withRegistry = createHigherOrderComponent( + ( OriginalComponent ) => ( props ) => ( + <RegistryConsumer> + { ( registry ) => ( + <OriginalComponent + { ...props } + registry={ registry } + /> + ) } + </RegistryConsumer> + ), + 'withRegistry' +); + +class BlockEditorProvider extends Component { + componentDidMount() { + this.props.updateEditorSettings( this.props.settings ); + this.props.resetBlocks( this.props.value ); + this.attachChangeObserver( this.props.registry ); + } + + componentDidUpdate( prevProps ) { + const { + settings, + updateEditorSettings, + value, + resetBlocks, + registry, + } = this.props; + + if ( settings !== prevProps.settings ) { + updateEditorSettings( settings ); + } + + if ( registry !== prevProps.registry ) { + this.attachChangeObserver( registry ); + } + + if ( this.isSyncingOutcomingValue ) { + this.isSyncingOutcomingValue = false; + } else if ( value !== prevProps.value ) { + this.isSyncingIncomingValue = true; + resetBlocks( value ); + } + } + + componentWillUnmount() { + if ( this.unsubscribe ) { + this.unsubscribe(); + } + } + + /** + * Given a registry object, overrides the default dispatch behavior for the + * `core/block-editor` store to interpret a state change and decide whether + * we should call `onChange` or `onInput` depending on whether the change + * is persistent or not. + * + * This needs to be done synchronously after state changes (instead of using + * `componentDidUpdate`) in order to avoid batching these changes. + * + * @param {WPDataRegistry} registry Registry from which block editor + * dispatch is to be overriden. + */ + attachChangeObserver( registry ) { + if ( this.unsubscribe ) { + this.unsubscribe(); + } + + const { + getBlocks, + isLastBlockChangePersistent, + } = registry.select( 'core/block-editor' ); + + let blocks = getBlocks(); + let isPersistent = isLastBlockChangePersistent(); + + this.unsubscribe = registry.subscribe( () => { + const { + onChange, + onInput, + } = this.props; + const newBlocks = getBlocks(); + const newIsPersistent = isLastBlockChangePersistent(); + if ( newBlocks !== blocks && this.isSyncingIncomingValue ) { + this.isSyncingIncomingValue = false; + blocks = newBlocks; + isPersistent = newIsPersistent; + return; + } + + if ( + newBlocks !== blocks || + // This happens when a previous input is explicitely marked as persistent. + ( newIsPersistent && ! isPersistent ) + ) { + blocks = newBlocks; + isPersistent = newIsPersistent; + + this.isSyncingOutcomingValue = true; + if ( isPersistent ) { + onChange( blocks ); + } else { + onInput( blocks ); + } + } + } ); + } + + render() { + const { children } = this.props; + + return ( + <SlotFillProvider> + <DropZoneProvider> + { children } + </DropZoneProvider> + </SlotFillProvider> + ); + } +} + +export default compose( [ + withDispatch( ( dispatch ) => { + const { + updateEditorSettings, + resetBlocks, + } = dispatch( 'core/block-editor' ); + + return { + updateEditorSettings, + resetBlocks, + }; + } ), + withRegistry, +] )( BlockEditorProvider ); diff --git a/packages/block-editor/src/index.js b/packages/block-editor/src/index.js new file mode 100644 index 0000000000000..9421db61f16e9 --- /dev/null +++ b/packages/block-editor/src/index.js @@ -0,0 +1,11 @@ +/** + * WordPress dependencies + */ +import '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import './store'; + +export * from './components'; diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js new file mode 100644 index 0000000000000..382a25f438d34 --- /dev/null +++ b/packages/block-editor/src/store/actions.js @@ -0,0 +1,544 @@ +/** + * External dependencies + */ +import { castArray } from 'lodash'; + +/** + * WordPress dependencies + */ +import { getDefaultBlockName, createBlock } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import { select } from './controls'; + +/** + * Returns an action object used in signalling that blocks state should be + * reset to the specified array of blocks, taking precedence over any other + * content reflected as an edit in state. + * + * @param {Array} blocks Array of blocks. + * + * @return {Object} Action object. + */ +export function resetBlocks( blocks ) { + return { + type: 'RESET_BLOCKS', + blocks, + }; +} + +/** + * Returns an action object used in signalling that blocks have been received. + * Unlike resetBlocks, these should be appended to the existing known set, not + * replacing. + * + * @param {Object[]} blocks Array of block objects. + * + * @return {Object} Action object. + */ +export function receiveBlocks( blocks ) { + return { + type: 'RECEIVE_BLOCKS', + blocks, + }; +} + +/** + * Returns an action object used in signalling that the block attributes with + * the specified client ID has been updated. + * + * @param {string} clientId Block client ID. + * @param {Object} attributes Block attributes to be merged. + * + * @return {Object} Action object. + */ +export function updateBlockAttributes( clientId, attributes ) { + return { + type: 'UPDATE_BLOCK_ATTRIBUTES', + clientId, + attributes, + }; +} + +/** + * Returns an action object used in signalling that the block with the + * specified client ID has been updated. + * + * @param {string} clientId Block client ID. + * @param {Object} updates Block attributes to be merged. + * + * @return {Object} Action object. + */ +export function updateBlock( clientId, updates ) { + return { + type: 'UPDATE_BLOCK', + clientId, + updates, + }; +} + +/** + * Returns an action object used in signalling that the block with the + * specified client ID has been selected, optionally accepting a position + * value reflecting its selection directionality. An initialPosition of -1 + * reflects a reverse selection. + * + * @param {string} clientId Block client ID. + * @param {?number} initialPosition Optional initial position. Pass as -1 to + * reflect reverse selection. + * + * @return {Object} Action object. + */ +export function selectBlock( clientId, initialPosition = null ) { + return { + type: 'SELECT_BLOCK', + initialPosition, + clientId, + }; +} + +/** + * Yields action objects used in signalling that the block preceding the given + * clientId should be selected. + * + * @param {string} clientId Block client ID. + */ +export function* selectPreviousBlock( clientId ) { + const previousBlockClientId = yield select( + 'core/editor', + 'getPreviousBlockClientId', + clientId + ); + + yield selectBlock( previousBlockClientId, -1 ); +} + +/** + * Yields action objects used in signalling that the block following the given + * clientId should be selected. + * + * @param {string} clientId Block client ID. + */ +export function* selectNextBlock( clientId ) { + const nextBlockClientId = yield select( + 'core/editor', + 'getNextBlockClientId', + clientId + ); + + yield selectBlock( nextBlockClientId ); +} + +/** + * Returns an action object used in signalling that a block multi-selection has started. + * + * @return {Object} Action object. + */ +export function startMultiSelect() { + return { + type: 'START_MULTI_SELECT', + }; +} + +/** + * Returns an action object used in signalling that block multi-selection stopped. + * + * @return {Object} Action object. + */ +export function stopMultiSelect() { + return { + type: 'STOP_MULTI_SELECT', + }; +} + +/** + * Returns an action object used in signalling that block multi-selection changed. + * + * @param {string} start First block of the multi selection. + * @param {string} end Last block of the multiselection. + * + * @return {Object} Action object. + */ +export function multiSelect( start, end ) { + return { + type: 'MULTI_SELECT', + start, + end, + }; +} + +/** + * Returns an action object used in signalling that the block selection is cleared. + * + * @return {Object} Action object. + */ +export function clearSelectedBlock() { + return { + type: 'CLEAR_SELECTED_BLOCK', + }; +} + +/** + * Returns an action object that enables or disables block selection. + * + * @param {boolean} [isSelectionEnabled=true] Whether block selection should + * be enabled. + + * @return {Object} Action object. + */ +export function toggleSelection( isSelectionEnabled = true ) { + return { + type: 'TOGGLE_SELECTION', + isSelectionEnabled, + }; +} + +/** + * Returns an action object signalling that a blocks should be replaced with + * one or more replacement blocks. + * + * @param {(string|string[])} clientIds Block client ID(s) to replace. + * @param {(Object|Object[])} blocks Replacement block(s). + * + * @return {Object} Action object. + */ +export function replaceBlocks( clientIds, blocks ) { + return { + type: 'REPLACE_BLOCKS', + clientIds: castArray( clientIds ), + blocks: castArray( blocks ), + time: Date.now(), + }; +} + +/** + * Returns an action object signalling that a single block should be replaced + * with one or more replacement blocks. + * + * @param {(string|string[])} clientId Block client ID to replace. + * @param {(Object|Object[])} block Replacement block(s). + * + * @return {Object} Action object. + */ +export function replaceBlock( clientId, block ) { + return replaceBlocks( clientId, block ); +} + +/** + * Higher-order action creator which, given the action type to dispatch creates + * an action creator for managing block movement. + * + * @param {string} type Action type to dispatch. + * + * @return {Function} Action creator. + */ +function createOnMove( type ) { + return ( clientIds, rootClientId ) => { + return { + clientIds: castArray( clientIds ), + type, + rootClientId, + }; + }; +} + +export const moveBlocksDown = createOnMove( 'MOVE_BLOCKS_DOWN' ); +export const moveBlocksUp = createOnMove( 'MOVE_BLOCKS_UP' ); + +/** + * Returns an action object signalling that an indexed block should be moved + * to a new index. + * + * @param {?string} clientId The client ID of the block. + * @param {?string} fromRootClientId Root client ID source. + * @param {?string} toRootClientId Root client ID destination. + * @param {number} index The index to move the block into. + * + * @return {Object} Action object. + */ +export function moveBlockToPosition( clientId, fromRootClientId, toRootClientId, index ) { + return { + type: 'MOVE_BLOCK_TO_POSITION', + fromRootClientId, + toRootClientId, + clientId, + index, + }; +} + +/** + * Returns an action object used in signalling that a single block should be + * inserted, optionally at a specific index respective a root block list. + * + * @param {Object} block Block object to insert. + * @param {?number} index Index at which block should be inserted. + * @param {?string} rootClientId Optional root client ID of block list on which to insert. + * @param {?boolean} updateSelection If true block selection will be updated. If false, block selection will not change. Defaults to true. + * + * @return {Object} Action object. + */ +export function insertBlock( block, index, rootClientId, updateSelection = true ) { + return insertBlocks( [ block ], index, rootClientId, updateSelection ); +} + +/** + * Returns an action object used in signalling that an array of blocks should + * be inserted, optionally at a specific index respective a root block list. + * + * @param {Object[]} blocks Block objects to insert. + * @param {?number} index Index at which block should be inserted. + * @param {?string} rootClientId Optional root client ID of block list on which to insert. + * @param {?boolean} updateSelection If true block selection will be updated. If false, block selection will not change. Defaults to true. + * + * @return {Object} Action object. + */ +export function insertBlocks( blocks, index, rootClientId, updateSelection = true ) { + return { + type: 'INSERT_BLOCKS', + blocks: castArray( blocks ), + index, + rootClientId, + time: Date.now(), + updateSelection, + }; +} + +/** + * Returns an action object used in signalling that the insertion point should + * be shown. + * + * @param {?string} rootClientId Optional root client ID of block list on + * which to insert. + * @param {?number} index Index at which block should be inserted. + * + * @return {Object} Action object. + */ +export function showInsertionPoint( rootClientId, index ) { + return { + type: 'SHOW_INSERTION_POINT', + rootClientId, + index, + }; +} + +/** + * Returns an action object hiding the insertion point. + * + * @return {Object} Action object. + */ +export function hideInsertionPoint() { + return { + type: 'HIDE_INSERTION_POINT', + }; +} + +/** + * Returns an action object resetting the template validity. + * + * @param {boolean} isValid template validity flag. + * + * @return {Object} Action object. + */ +export function setTemplateValidity( isValid ) { + return { + type: 'SET_TEMPLATE_VALIDITY', + isValid, + }; +} + +/** + * Returns an action object synchronize the template with the list of blocks + * + * @return {Object} Action object. + */ +export function synchronizeTemplate() { + return { + type: 'SYNCHRONIZE_TEMPLATE', + }; +} + +/** + * Returns an action object used in signalling that two blocks should be merged + * + * @param {string} firstBlockClientId Client ID of the first block to merge. + * @param {string} secondBlockClientId Client ID of the second block to merge. + * + * @return {Object} Action object. + */ +export function mergeBlocks( firstBlockClientId, secondBlockClientId ) { + return { + type: 'MERGE_BLOCKS', + blocks: [ firstBlockClientId, secondBlockClientId ], + }; +} + +/** + * Yields action objects used in signalling that the blocks corresponding to + * the set of specified client IDs are to be removed. + * + * @param {string|string[]} clientIds Client IDs of blocks to remove. + * @param {boolean} selectPrevious True if the previous block should be + * selected when a block is removed. + */ +export function* removeBlocks( clientIds, selectPrevious = true ) { + clientIds = castArray( clientIds ); + + if ( selectPrevious ) { + yield selectPreviousBlock( clientIds[ 0 ] ); + } + + yield { + type: 'REMOVE_BLOCKS', + clientIds, + }; +} + +/** + * Returns an action object used in signalling that the block with the + * specified client ID is to be removed. + * + * @param {string} clientId Client ID of block to remove. + * @param {boolean} selectPrevious True if the previous block should be + * selected when a block is removed. + * + * @return {Object} Action object. + */ +export function removeBlock( clientId, selectPrevious ) { + return removeBlocks( [ clientId ], selectPrevious ); +} + +/** + * Returns an action object used to toggle the block editing mode between + * visual and HTML modes. + * + * @param {string} clientId Block client ID. + * + * @return {Object} Action object. + */ +export function toggleBlockMode( clientId ) { + return { + type: 'TOGGLE_BLOCK_MODE', + clientId, + }; +} + +/** + * Returns an action object used in signalling that the user has begun to type. + * + * @return {Object} Action object. + */ +export function startTyping() { + return { + type: 'START_TYPING', + }; +} + +/** + * Returns an action object used in signalling that the user has stopped typing. + * + * @return {Object} Action object. + */ +export function stopTyping() { + return { + type: 'STOP_TYPING', + }; +} + +/** + * Returns an action object used in signalling that the caret has entered formatted text. + * + * @return {Object} Action object. + */ +export function enterFormattedText() { + return { + type: 'ENTER_FORMATTED_TEXT', + }; +} + +/** + * Returns an action object used in signalling that the user caret has exited formatted text. + * + * @return {Object} Action object. + */ +export function exitFormattedText() { + return { + type: 'EXIT_FORMATTED_TEXT', + }; +} + +/** + * Returns an action object used in signalling that a new block of the default + * type should be added to the block list. + * + * @param {?Object} attributes Optional attributes of the block to assign. + * @param {?string} rootClientId Optional root client ID of block list on which + * to append. + * @param {?number} index Optional index where to insert the default block + * + * @return {Object} Action object + */ +export function insertDefaultBlock( attributes, rootClientId, index ) { + const block = createBlock( getDefaultBlockName(), attributes ); + + return insertBlock( block, index, rootClientId ); +} + +/** + * Returns an action object that changes the nested settings of a given block. + * + * @param {string} clientId Client ID of the block whose nested setting are + * being received. + * @param {Object} settings Object with the new settings for the nested block. + * + * @return {Object} Action object + */ +export function updateBlockListSettings( clientId, settings ) { + return { + type: 'UPDATE_BLOCK_LIST_SETTINGS', + clientId, + settings, + }; +} + +/* + * Returns an action object used in signalling that the editor settings have been updated. + * + * @param {Object} settings Updated settings + * + * @return {Object} Action object + */ +export function updateEditorSettings( settings ) { + return { + type: 'UPDATE_EDITOR_SETTINGS', + settings, + }; +} + +/** + * Returns an action object used in signalling that a temporary reusable blocks have been saved + * in order to switch its temporary id with the real id. + * + * @param {string} id Reusable block's id. + * @param {string} updatedId Updated block's id. + * + * @return {Object} Action object. + */ +export function __unstableSaveReusableBlock( id, updatedId ) { + return { + type: 'SAVE_REUSABLE_BLOCK_SUCCESS', + id, + updatedId, + }; +} + +/** + * Returns an action object used in signalling that the last block change should be marked explicitely as persistent. + * + * @return {Object} Action object. + */ +export function __unstableMarkLastChangeAsPersistent() { + return { type: 'MARK_LAST_CHANGE_AS_PERSISTENT' }; +} + diff --git a/packages/editor/src/store/array.js b/packages/block-editor/src/store/array.js similarity index 100% rename from packages/editor/src/store/array.js rename to packages/block-editor/src/store/array.js diff --git a/packages/block-editor/src/store/controls.js b/packages/block-editor/src/store/controls.js new file mode 100644 index 0000000000000..5012ab244c21c --- /dev/null +++ b/packages/block-editor/src/store/controls.js @@ -0,0 +1,30 @@ +/** + * WordPress dependencies + */ +import { createRegistryControl } from '@wordpress/data'; + +/** + * Calls a selector using the current state. + * + * @param {string} storeName Store name. + * @param {string} selectorName Selector name. + * @param {Array} args Selector arguments. + * + * @return {Object} control descriptor. + */ +export function select( storeName, selectorName, ...args ) { + return { + type: 'SELECT', + storeName, + selectorName, + args, + }; +} + +const controls = { + SELECT: createRegistryControl( ( registry ) => ( { storeName, selectorName, args } ) => { + return registry.select( storeName )[ selectorName ]( ...args ); + } ), +}; + +export default controls; diff --git a/packages/block-editor/src/store/defaults.js b/packages/block-editor/src/store/defaults.js new file mode 100644 index 0000000000000..31d1574d6283a --- /dev/null +++ b/packages/block-editor/src/store/defaults.js @@ -0,0 +1,133 @@ +/** + * WordPress dependencies + */ +import { __, _x } from '@wordpress/i18n'; + +export const PREFERENCES_DEFAULTS = { + insertUsage: {}, +}; + +/** + * The default editor settings + * + * alignWide boolean Enable/Disable Wide/Full Alignments + * colors Array Palette colors + * fontSizes Array Available font sizes + * imageSizes Array Available image sizes + * maxWidth number Max width to constraint resizing + * blockTypes boolean|Array Allowed block types + * hasFixedToolbar boolean Whether or not the editor toolbar is fixed + * focusMode boolean Whether the focus mode is enabled or not + * richEditingEnabled boolean Whether rich editing is enabled or not + */ +export const EDITOR_SETTINGS_DEFAULTS = { + alignWide: false, + colors: [ + { + name: __( 'Pale pink' ), + slug: 'pale-pink', + color: '#f78da7', + }, + { name: __( 'Vivid red' ), + slug: 'vivid-red', + color: '#cf2e2e', + }, + { + name: __( 'Luminous vivid orange' ), + slug: 'luminous-vivid-orange', + color: '#ff6900', + }, + { + name: __( 'Luminous vivid amber' ), + slug: 'luminous-vivid-amber', + color: '#fcb900', + }, + { + name: __( 'Light green cyan' ), + slug: 'light-green-cyan', + color: '#7bdcb5', + }, + { + name: __( 'Vivid green cyan' ), + slug: 'vivid-green-cyan', + color: '#00d084', + }, + { + name: __( 'Pale cyan blue' ), + slug: 'pale-cyan-blue', + color: '#8ed1fc', + }, + { + name: __( 'Vivid cyan blue' ), + slug: 'vivid-cyan-blue', + color: '#0693e3', + }, + { + name: __( 'Very light gray' ), + slug: 'very-light-gray', + color: '#eeeeee', + }, + { + name: __( 'Cyan bluish gray' ), + slug: 'cyan-bluish-gray', + color: '#abb8c3', + }, + { + name: __( 'Very dark gray' ), + slug: 'very-dark-gray', + color: '#313131', + }, + ], + + fontSizes: [ + { + name: _x( 'Small', 'font size name' ), + size: 13, + slug: 'small', + }, + { + name: _x( 'Normal', 'font size name' ), + size: 16, + slug: 'normal', + }, + { + name: _x( 'Medium', 'font size name' ), + size: 20, + slug: 'medium', + }, + { + name: _x( 'Large', 'font size name' ), + size: 36, + slug: 'large', + }, + { + name: _x( 'Huge', 'font size name' ), + size: 48, + slug: 'huge', + }, + ], + + imageSizes: [ + { slug: 'thumbnail', label: __( 'Thumbnail' ) }, + { slug: 'medium', label: __( 'Medium' ) }, + { slug: 'large', label: __( 'Large' ) }, + { slug: 'full', label: __( 'Full Size' ) }, + ], + + // This is current max width of the block inner area + // It's used to constraint image resizing and this value could be overridden later by themes + maxWidth: 580, + + // Allowed block types for the editor, defaulting to true (all supported). + allowedBlockTypes: true, + + // Maximum upload size in bytes allowed for the site. + maxUploadFileSize: 0, + + // List of allowed mime types and file extensions. + allowedMimeTypes: null, + + // Whether richs editing is enabled or not. + richEditingEnabled: true, +}; + diff --git a/packages/block-editor/src/store/effects.js b/packages/block-editor/src/store/effects.js new file mode 100644 index 0000000000000..f02ef6fbd7231 --- /dev/null +++ b/packages/block-editor/src/store/effects.js @@ -0,0 +1,150 @@ +/** + * WordPress dependencies + */ +import { speak } from '@wordpress/a11y'; +import { + getBlockType, + doBlocksMatchTemplate, + switchToBlockType, + synchronizeBlocksWithTemplate, +} from '@wordpress/blocks'; +import { _n, sprintf } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { + replaceBlocks, + selectBlock, + setTemplateValidity, + insertDefaultBlock, + resetBlocks, +} from './actions'; +import { + getBlock, + getBlocks, + getSelectedBlockCount, + getBlockCount, + getTemplateLock, + getTemplate, + isValidTemplate, +} from './selectors'; + +/** + * Block validity is a function of blocks state (at the point of a + * reset) and the template setting. As a compromise to its placement + * across distinct parts of state, it is implemented here as a side- + * effect of the block reset action. + * + * @param {Object} action RESET_BLOCKS action. + * @param {Object} store Store instance. + * + * @return {?Object} New validity set action if validity has changed. + */ +export function validateBlocksToTemplate( action, store ) { + const state = store.getState(); + const template = getTemplate( state ); + const templateLock = getTemplateLock( state ); + + // Unlocked templates are considered always valid because they act + // as default values only. + const isBlocksValidToTemplate = ( + ! template || + templateLock !== 'all' || + doBlocksMatchTemplate( action.blocks, template ) + ); + + // Update if validity has changed. + if ( isBlocksValidToTemplate !== isValidTemplate( state ) ) { + return setTemplateValidity( isBlocksValidToTemplate ); + } +} + +/** + * Effect handler which will return a default block insertion action if there + * are no other blocks at the root of the editor. This is expected to be used + * in actions which may result in no blocks remaining in the editor (removal, + * replacement, etc). + * + * @param {Object} action Action which had initiated the effect handler. + * @param {Object} store Store instance. + * + * @return {?Object} Default block insert action, if no other blocks exist. + */ +export function ensureDefaultBlock( action, store ) { + if ( ! getBlockCount( store.getState() ) ) { + return insertDefaultBlock(); + } +} + +export default { + MERGE_BLOCKS( action, store ) { + const { dispatch } = store; + const state = store.getState(); + const [ firstBlockClientId, secondBlockClientId ] = action.blocks; + const blockA = getBlock( state, firstBlockClientId ); + const blockType = getBlockType( blockA.name ); + + // Only focus the previous block if it's not mergeable + if ( ! blockType.merge ) { + dispatch( selectBlock( blockA.clientId ) ); + return; + } + + // We can only merge blocks with similar types + // thus, we transform the block to merge first + const blockB = getBlock( state, secondBlockClientId ); + const blocksWithTheSameType = blockA.name === blockB.name ? + [ blockB ] : + switchToBlockType( blockB, blockA.name ); + + // If the block types can not match, do nothing + if ( ! blocksWithTheSameType || ! blocksWithTheSameType.length ) { + return; + } + + // Calling the merge to update the attributes and remove the block to be merged + const updatedAttributes = blockType.merge( + blockA.attributes, + blocksWithTheSameType[ 0 ].attributes + ); + + dispatch( selectBlock( blockA.clientId, -1 ) ); + dispatch( replaceBlocks( + [ blockA.clientId, blockB.clientId ], + [ + { + ...blockA, + attributes: { + ...blockA.attributes, + ...updatedAttributes, + }, + }, + ...blocksWithTheSameType.slice( 1 ), + ] + ) ); + }, + RESET_BLOCKS: [ + validateBlocksToTemplate, + ], + REMOVE_BLOCKS: [ + ensureDefaultBlock, + ], + REPLACE_BLOCKS: [ + ensureDefaultBlock, + ], + MULTI_SELECT: ( action, { getState } ) => { + const blockCount = getSelectedBlockCount( getState() ); + + /* translators: %s: number of selected blocks */ + speak( sprintf( _n( '%s block selected.', '%s blocks selected.', blockCount ), blockCount ), 'assertive' ); + }, + SYNCHRONIZE_TEMPLATE( action, { getState } ) { + const state = getState(); + const blocks = getBlocks( state ); + const template = getTemplate( state ); + const updatedBlockList = synchronizeBlocksWithTemplate( blocks, template ); + + return resetBlocks( updatedBlockList ); + }, +}; diff --git a/packages/block-editor/src/store/index.js b/packages/block-editor/src/store/index.js new file mode 100644 index 0000000000000..0119e63d7a3d1 --- /dev/null +++ b/packages/block-editor/src/store/index.js @@ -0,0 +1,29 @@ +/** + * WordPress dependencies + */ +import { registerStore } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import reducer from './reducer'; +import applyMiddlewares from './middlewares'; +import * as selectors from './selectors'; +import * as actions from './actions'; +import controls from './controls'; + +/** + * Module Constants + */ +const MODULE_KEY = 'core/block-editor'; + +const store = registerStore( MODULE_KEY, { + reducer, + selectors, + actions, + controls, + persist: [ 'preferences' ], +} ); +applyMiddlewares( store ); + +export default store; diff --git a/packages/block-editor/src/store/middlewares.js b/packages/block-editor/src/store/middlewares.js new file mode 100644 index 0000000000000..6381132bb81e0 --- /dev/null +++ b/packages/block-editor/src/store/middlewares.js @@ -0,0 +1,45 @@ +/** + * External dependencies + */ +import refx from 'refx'; +import multi from 'redux-multi'; +import { flowRight } from 'lodash'; + +/** + * Internal dependencies + */ +import effects from './effects'; + +/** + * Applies the custom middlewares used specifically in the editor module. + * + * @param {Object} store Store Object. + * + * @return {Object} Update Store Object. + */ +function applyMiddlewares( store ) { + const middlewares = [ + refx( effects ), + multi, + ]; + + let enhancedDispatch = () => { + throw new Error( + 'Dispatching while constructing your middleware is not allowed. ' + + 'Other middleware would not be applied to this dispatch.' + ); + }; + let chain = []; + + const middlewareAPI = { + getState: store.getState, + dispatch: ( ...args ) => enhancedDispatch( ...args ), + }; + chain = middlewares.map( ( middleware ) => middleware( middlewareAPI ) ); + enhancedDispatch = flowRight( ...chain )( store.dispatch ); + + store.dispatch = enhancedDispatch; + return store; +} + +export default applyMiddlewares; diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js new file mode 100644 index 0000000000000..d6cc2444af050 --- /dev/null +++ b/packages/block-editor/src/store/reducer.js @@ -0,0 +1,901 @@ +/** + * External dependencies + */ +import { + flow, + reduce, + first, + last, + omit, + without, + mapValues, + keys, + isEqual, + isEmpty, +} from 'lodash'; + +/** + * WordPress dependencies + */ +import { combineReducers } from '@wordpress/data'; +import { isReusableBlock } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import { + PREFERENCES_DEFAULTS, + EDITOR_SETTINGS_DEFAULTS, +} from './defaults'; +import { insertAt, moveTo } from './array'; + +/** + * Given an array of blocks, returns an object where each key is a nesting + * context, the value of which is an array of block client IDs existing within + * that nesting context. + * + * @param {Array} blocks Blocks to map. + * @param {?string} rootClientId Assumed root client ID. + * + * @return {Object} Block order map object. + */ +function mapBlockOrder( blocks, rootClientId = '' ) { + const result = { [ rootClientId ]: [] }; + + blocks.forEach( ( block ) => { + const { clientId, innerBlocks } = block; + + result[ rootClientId ].push( clientId ); + + Object.assign( result, mapBlockOrder( innerBlocks, clientId ) ); + } ); + + return result; +} + +/** + * Helper method to iterate through all blocks, recursing into inner blocks, + * applying a transformation function to each one. + * Returns a flattened object with the transformed blocks. + * + * @param {Array} blocks Blocks to flatten. + * @param {Function} transform Transforming function to be applied to each block. + * + * @return {Object} Flattened object. + */ +function flattenBlocks( blocks, transform ) { + const result = {}; + + const stack = [ ...blocks ]; + while ( stack.length ) { + const { innerBlocks, ...block } = stack.shift(); + stack.push( ...innerBlocks ); + result[ block.clientId ] = transform( block ); + } + + return result; +} + +/** + * Given an array of blocks, returns an object containing all blocks, without + * attributes, recursing into inner blocks. Keys correspond to the block client + * ID, the value of which is the attributes object. + * + * @param {Array} blocks Blocks to flatten. + * + * @return {Object} Flattened block attributes object. + */ +function getFlattenedBlocksWithoutAttributes( blocks ) { + return flattenBlocks( blocks, ( block ) => omit( block, 'attributes' ) ); +} + +/** + * Given an array of blocks, returns an object containing all block attributes, + * recursing into inner blocks. Keys correspond to the block client ID, the + * value of which is the attributes object. + * + * @param {Array} blocks Blocks to flatten. + * + * @return {Object} Flattened block attributes object. + */ +function getFlattenedBlockAttributes( blocks ) { + return flattenBlocks( blocks, ( block ) => block.attributes ); +} + +/** + * Given a block order map object, returns *all* of the block client IDs that are + * a descendant of the given root client ID. + * + * Calling this with `rootClientId` set to `''` results in a list of client IDs + * that are in the post. That is, it excludes blocks like fetched reusable + * blocks which are stored into state but not visible. + * + * @param {Object} blocksOrder Object that maps block client IDs to a list of + * nested block client IDs. + * @param {?string} rootClientId The root client ID to search. Defaults to ''. + * + * @return {Array} List of descendant client IDs. + */ +function getNestedBlockClientIds( blocksOrder, rootClientId = '' ) { + return reduce( blocksOrder[ rootClientId ], ( result, clientId ) => [ + ...result, + clientId, + ...getNestedBlockClientIds( blocksOrder, clientId ), + ], [] ); +} + +/** + * Returns an object against which it is safe to perform mutating operations, + * given the original object and its current working copy. + * + * @param {Object} original Original object. + * @param {Object} working Working object. + * + * @return {Object} Mutation-safe object. + */ +function getMutateSafeObject( original, working ) { + if ( original === working ) { + return { ...original }; + } + + return working; +} + +/** + * Returns true if the two object arguments have the same keys, or false + * otherwise. + * + * @param {Object} a First object. + * @param {Object} b Second object. + * + * @return {boolean} Whether the two objects have the same keys. + */ +export function hasSameKeys( a, b ) { + return isEqual( keys( a ), keys( b ) ); +} + +/** + * Returns true if, given the currently dispatching action and the previously + * dispatched action, the two actions are updating the same block attribute, or + * false otherwise. + * + * @param {Object} action Currently dispatching action. + * @param {Object} lastAction Previously dispatched action. + * + * @return {boolean} Whether actions are updating the same block attribute. + */ +export function isUpdatingSameBlockAttribute( action, lastAction ) { + return ( + action.type === 'UPDATE_BLOCK_ATTRIBUTES' && + lastAction !== undefined && + lastAction.type === 'UPDATE_BLOCK_ATTRIBUTES' && + action.clientId === lastAction.clientId && + hasSameKeys( action.attributes, lastAction.attributes ) + ); +} + +/** + * Higher-order reducer intended to augment the blocks reducer, assigning an + * `isPersistentChange` property value corresponding to whether a change in + * state can be considered as persistent. All changes are considered persistent + * except when updating the same block attribute as in the previous action. + * + * @param {Function} reducer Original reducer function. + * + * @return {Function} Enhanced reducer function. + */ +function withPersistentBlockChange( reducer ) { + let lastAction; + + return ( state, action ) => { + let nextState = reducer( state, action ); + const isExplicitPersistentChange = action.type === 'MARK_LAST_CHANGE_AS_PERSISTENT'; + + if ( state !== nextState || isExplicitPersistentChange ) { + nextState = { + ...nextState, + isPersistentChange: ( + isExplicitPersistentChange || + ! isUpdatingSameBlockAttribute( action, lastAction ) + ), + }; + } + + lastAction = action; + + return nextState; + }; +} + +/** + * Higher-order reducer targeting the combined blocks reducer, augmenting + * block client IDs in remove action to include cascade of inner blocks. + * + * @param {Function} reducer Original reducer function. + * + * @return {Function} Enhanced reducer function. + */ +const withInnerBlocksRemoveCascade = ( reducer ) => ( state, action ) => { + if ( state && action.type === 'REMOVE_BLOCKS' ) { + const clientIds = [ ...action.clientIds ]; + + // For each removed client ID, include its inner blocks to remove, + // recursing into those so long as inner blocks exist. + for ( let i = 0; i < clientIds.length; i++ ) { + clientIds.push( ...state.order[ clientIds[ i ] ] ); + } + + action = { ...action, clientIds }; + } + + return reducer( state, action ); +}; + +/** + * Higher-order reducer which targets the combined blocks reducer and handles + * the `RESET_BLOCKS` action. When dispatched, this action will replace all + * blocks that exist in the post, leaving blocks that exist only in state (e.g. + * reusable blocks) alone. + * + * @param {Function} reducer Original reducer function. + * + * @return {Function} Enhanced reducer function. + */ +const withBlockReset = ( reducer ) => ( state, action ) => { + if ( state && action.type === 'RESET_BLOCKS' ) { + const visibleClientIds = getNestedBlockClientIds( state.order ); + return { + ...state, + byClientId: { + ...omit( state.byClientId, visibleClientIds ), + ...getFlattenedBlocksWithoutAttributes( action.blocks ), + }, + attributes: { + ...omit( state.attributes, visibleClientIds ), + ...getFlattenedBlockAttributes( action.blocks ), + }, + order: { + ...omit( state.order, visibleClientIds ), + ...mapBlockOrder( action.blocks ), + }, + }; + } + + return reducer( state, action ); +}; + +/** + * Higher-order reducer which targets the combined blocks reducer and handles + * the `SAVE_REUSABLE_BLOCK_SUCCESS` action. This action can't be handled by + * regular reducers and needs a higher-order reducer since it needs access to + * both `byClientId` and `attributes` simultaneously. + * + * @param {Function} reducer Original reducer function. + * + * @return {Function} Enhanced reducer function. + */ +const withSaveReusableBlock = ( reducer ) => ( state, action ) => { + if ( state && action.type === 'SAVE_REUSABLE_BLOCK_SUCCESS' ) { + const { id, updatedId } = action; + + // If a temporary reusable block is saved, we swap the temporary id with the final one + if ( id === updatedId ) { + return state; + } + + state = { ...state }; + + state.attributes = mapValues( state.attributes, ( attributes, clientId ) => { + const { name } = state.byClientId[ clientId ]; + if ( name === 'core/block' && attributes.ref === id ) { + return { + ...attributes, + ref: updatedId, + }; + } + + return attributes; + } ); + } + + return reducer( state, action ); +}; + +/** + * Reducer returning the blocks state. + * + * @param {Object} state Current state. + * @param {Object} action Dispatched action. + * + * @returns {Object} Updated state. + */ +export const blocks = flow( + combineReducers, + withInnerBlocksRemoveCascade, + withBlockReset, + withSaveReusableBlock, + withPersistentBlockChange, +)( { + byClientId( state = {}, action ) { + switch ( action.type ) { + case 'RESET_BLOCKS': + return getFlattenedBlocksWithoutAttributes( action.blocks ); + + case 'RECEIVE_BLOCKS': + return { + ...state, + ...getFlattenedBlocksWithoutAttributes( action.blocks ), + }; + + case 'UPDATE_BLOCK': + // Ignore updates if block isn't known + if ( ! state[ action.clientId ] ) { + return state; + } + + // Do nothing if only attributes change. + const changes = omit( action.updates, 'attributes' ); + if ( isEmpty( changes ) ) { + return state; + } + + return { + ...state, + [ action.clientId ]: { + ...state[ action.clientId ], + ...changes, + }, + }; + + case 'INSERT_BLOCKS': + return { + ...state, + ...getFlattenedBlocksWithoutAttributes( action.blocks ), + }; + + case 'REPLACE_BLOCKS': + if ( ! action.blocks ) { + return state; + } + + return { + ...omit( state, action.clientIds ), + ...getFlattenedBlocksWithoutAttributes( action.blocks ), + }; + + case 'REMOVE_BLOCKS': + return omit( state, action.clientIds ); + } + + return state; + }, + + attributes( state = {}, action ) { + switch ( action.type ) { + case 'RESET_BLOCKS': + return getFlattenedBlockAttributes( action.blocks ); + + case 'RECEIVE_BLOCKS': + return { + ...state, + ...getFlattenedBlockAttributes( action.blocks ), + }; + + case 'UPDATE_BLOCK': + // Ignore updates if block isn't known or there are no attribute changes. + if ( ! state[ action.clientId ] || ! action.updates.attributes ) { + return state; + } + + return { + ...state, + [ action.clientId ]: { + ...state[ action.clientId ], + ...action.updates.attributes, + }, + }; + + case 'UPDATE_BLOCK_ATTRIBUTES': + // Ignore updates if block isn't known + if ( ! state[ action.clientId ] ) { + return state; + } + + // Consider as updates only changed values + const nextAttributes = reduce( action.attributes, ( result, value, key ) => { + if ( value !== result[ key ] ) { + result = getMutateSafeObject( state[ action.clientId ], result ); + result[ key ] = value; + } + + return result; + }, state[ action.clientId ] ); + + // Skip update if nothing has been changed. The reference will + // match the original block if `reduce` had no changed values. + if ( nextAttributes === state[ action.clientId ] ) { + return state; + } + + // Otherwise replace attributes in state + return { + ...state, + [ action.clientId ]: nextAttributes, + }; + + case 'INSERT_BLOCKS': + return { + ...state, + ...getFlattenedBlockAttributes( action.blocks ), + }; + + case 'REPLACE_BLOCKS': + if ( ! action.blocks ) { + return state; + } + + return { + ...omit( state, action.clientIds ), + ...getFlattenedBlockAttributes( action.blocks ), + }; + + case 'REMOVE_BLOCKS': + return omit( state, action.clientIds ); + } + + return state; + }, + + order( state = {}, action ) { + switch ( action.type ) { + case 'RESET_BLOCKS': + return mapBlockOrder( action.blocks ); + + case 'RECEIVE_BLOCKS': + return { + ...state, + ...omit( mapBlockOrder( action.blocks ), '' ), + }; + + case 'INSERT_BLOCKS': { + const { rootClientId = '' } = action; + const subState = state[ rootClientId ] || []; + const mappedBlocks = mapBlockOrder( action.blocks, rootClientId ); + const { index = subState.length } = action; + + return { + ...state, + ...mappedBlocks, + [ rootClientId ]: insertAt( subState, mappedBlocks[ rootClientId ], index ), + }; + } + + case 'MOVE_BLOCK_TO_POSITION': { + const { fromRootClientId = '', toRootClientId = '', clientId } = action; + const { index = state[ toRootClientId ].length } = action; + + // Moving inside the same parent block + if ( fromRootClientId === toRootClientId ) { + const subState = state[ toRootClientId ]; + const fromIndex = subState.indexOf( clientId ); + return { + ...state, + [ toRootClientId ]: moveTo( state[ toRootClientId ], fromIndex, index ), + }; + } + + // Moving from a parent block to another + return { + ...state, + [ fromRootClientId ]: without( state[ fromRootClientId ], clientId ), + [ toRootClientId ]: insertAt( state[ toRootClientId ], clientId, index ), + }; + } + + case 'MOVE_BLOCKS_UP': { + const { clientIds, rootClientId = '' } = action; + const firstClientId = first( clientIds ); + const subState = state[ rootClientId ]; + + if ( ! subState.length || firstClientId === first( subState ) ) { + return state; + } + + const firstIndex = subState.indexOf( firstClientId ); + + return { + ...state, + [ rootClientId ]: moveTo( subState, firstIndex, firstIndex - 1, clientIds.length ), + }; + } + + case 'MOVE_BLOCKS_DOWN': { + const { clientIds, rootClientId = '' } = action; + const firstClientId = first( clientIds ); + const lastClientId = last( clientIds ); + const subState = state[ rootClientId ]; + + if ( ! subState.length || lastClientId === last( subState ) ) { + return state; + } + + const firstIndex = subState.indexOf( firstClientId ); + + return { + ...state, + [ rootClientId ]: moveTo( subState, firstIndex, firstIndex + 1, clientIds.length ), + }; + } + + case 'REPLACE_BLOCKS': { + const { clientIds } = action; + if ( ! action.blocks ) { + return state; + } + + const mappedBlocks = mapBlockOrder( action.blocks ); + + return flow( [ + ( nextState ) => omit( nextState, clientIds ), + ( nextState ) => ( { + ...nextState, + ...omit( mappedBlocks, '' ), + } ), + ( nextState ) => mapValues( nextState, ( subState ) => ( + reduce( subState, ( result, clientId ) => { + if ( clientId === clientIds[ 0 ] ) { + return [ + ...result, + ...mappedBlocks[ '' ], + ]; + } + + if ( clientIds.indexOf( clientId ) === -1 ) { + result.push( clientId ); + } + + return result; + }, [] ) + ) ), + ] )( state ); + } + + case 'REMOVE_BLOCKS': + return flow( [ + // Remove inner block ordering for removed blocks + ( nextState ) => omit( nextState, action.clientIds ), + + // Remove deleted blocks from other blocks' orderings + ( nextState ) => mapValues( nextState, ( subState ) => ( + without( subState, ...action.clientIds ) + ) ), + ] )( state ); + } + + return state; + }, +} ); + +/** + * Reducer returning typing state. + * + * @param {boolean} state Current state. + * @param {Object} action Dispatched action. + * + * @return {boolean} Updated state. + */ +export function isTyping( state = false, action ) { + switch ( action.type ) { + case 'START_TYPING': + return true; + + case 'STOP_TYPING': + return false; + } + + return state; +} + +/** + * Reducer returning whether the caret is within formatted text. + * + * @param {boolean} state Current state. + * @param {Object} action Dispatched action. + * + * @return {boolean} Updated state. + */ +export function isCaretWithinFormattedText( state = false, action ) { + switch ( action.type ) { + case 'ENTER_FORMATTED_TEXT': + return true; + + case 'EXIT_FORMATTED_TEXT': + return false; + } + + return state; +} + +/** + * Reducer returning the block selection's state. + * + * @param {Object} state Current state. + * @param {Object} action Dispatched action. + * + * @return {Object} Updated state. + */ +export function blockSelection( state = { + start: null, + end: null, + isMultiSelecting: false, + isEnabled: true, + initialPosition: null, +}, action ) { + switch ( action.type ) { + case 'CLEAR_SELECTED_BLOCK': + if ( state.start === null && state.end === null && ! state.isMultiSelecting ) { + return state; + } + + return { + ...state, + start: null, + end: null, + isMultiSelecting: false, + initialPosition: null, + }; + case 'START_MULTI_SELECT': + if ( state.isMultiSelecting ) { + return state; + } + + return { + ...state, + isMultiSelecting: true, + initialPosition: null, + }; + case 'STOP_MULTI_SELECT': + if ( ! state.isMultiSelecting ) { + return state; + } + + return { + ...state, + isMultiSelecting: false, + initialPosition: null, + }; + case 'MULTI_SELECT': + return { + ...state, + start: action.start, + end: action.end, + initialPosition: null, + }; + case 'SELECT_BLOCK': + if ( action.clientId === state.start && action.clientId === state.end ) { + return state; + } + return { + ...state, + start: action.clientId, + end: action.clientId, + initialPosition: action.initialPosition, + }; + case 'INSERT_BLOCKS': { + if ( action.updateSelection ) { + return { + ...state, + start: action.blocks[ 0 ].clientId, + end: action.blocks[ 0 ].clientId, + initialPosition: null, + isMultiSelecting: false, + }; + } + return state; + } + case 'REMOVE_BLOCKS': + if ( ! action.clientIds || ! action.clientIds.length || action.clientIds.indexOf( state.start ) === -1 ) { + return state; + } + return { + ...state, + start: null, + end: null, + initialPosition: null, + isMultiSelecting: false, + }; + case 'REPLACE_BLOCKS': + if ( action.clientIds.indexOf( state.start ) === -1 ) { + return state; + } + + // If there are replacement blocks, assign last block as the next + // selected block, otherwise set to null. + const lastBlock = last( action.blocks ); + const nextSelectedBlockClientId = lastBlock ? lastBlock.clientId : null; + + if ( nextSelectedBlockClientId === state.start && nextSelectedBlockClientId === state.end ) { + return state; + } + + return { + ...state, + start: nextSelectedBlockClientId, + end: nextSelectedBlockClientId, + initialPosition: null, + isMultiSelecting: false, + }; + case 'TOGGLE_SELECTION': + return { + ...state, + isEnabled: action.isSelectionEnabled, + }; + } + + return state; +} + +export function blocksMode( state = {}, action ) { + if ( action.type === 'TOGGLE_BLOCK_MODE' ) { + const { clientId } = action; + return { + ...state, + [ clientId ]: state[ clientId ] && state[ clientId ] === 'html' ? 'visual' : 'html', + }; + } + + return state; +} + +/** + * Reducer returning the block insertion point visibility, either null if there + * is not an explicit insertion point assigned, or an object of its `index` and + * `rootClientId`. + * + * @param {Object} state Current state. + * @param {Object} action Dispatched action. + * + * @return {Object} Updated state. + */ +export function insertionPoint( state = null, action ) { + switch ( action.type ) { + case 'SHOW_INSERTION_POINT': + const { rootClientId, index } = action; + return { rootClientId, index }; + + case 'HIDE_INSERTION_POINT': + return null; + } + + return state; +} + +/** + * Reducer returning whether the post blocks match the defined template or not. + * + * @param {Object} state Current state. + * @param {Object} action Dispatched action. + * + * @return {boolean} Updated state. + */ +export function template( state = { isValid: true }, action ) { + switch ( action.type ) { + case 'SET_TEMPLATE_VALIDITY': + return { + ...state, + isValid: action.isValid, + }; + } + + return state; +} + +/** + * Reducer returning the editor setting. + * + * @param {Object} state Current state. + * @param {Object} action Dispatched action. + * + * @return {Object} Updated state. + */ +export function settings( state = EDITOR_SETTINGS_DEFAULTS, action ) { + switch ( action.type ) { + case 'UPDATE_EDITOR_SETTINGS': + return { + ...state, + ...action.settings, + }; + } + + return state; +} + +/** + * Reducer returning the user preferences. + * + * @param {Object} state Current state. + * @param {Object} action Dispatched action. + * + * @return {string} Updated state. + */ +export function preferences( state = PREFERENCES_DEFAULTS, action ) { + switch ( action.type ) { + case 'INSERT_BLOCKS': + case 'REPLACE_BLOCKS': + return action.blocks.reduce( ( prevState, block ) => { + let id = block.name; + const insert = { name: block.name }; + if ( isReusableBlock( block ) ) { + insert.ref = block.attributes.ref; + id += '/' + block.attributes.ref; + } + + return { + ...prevState, + insertUsage: { + ...prevState.insertUsage, + [ id ]: { + time: action.time, + count: prevState.insertUsage[ id ] ? prevState.insertUsage[ id ].count + 1 : 1, + insert, + }, + }, + }; + }, state ); + } + + return state; +} + +/** + * Reducer returning an object where each key is a block client ID, its value + * representing the settings for its nested blocks. + * + * @param {Object} state Current state. + * @param {Object} action Dispatched action. + * + * @return {Object} Updated state. + */ +export const blockListSettings = ( state = {}, action ) => { + switch ( action.type ) { + // Even if the replaced blocks have the same client ID, our logic + // should correct the state. + case 'REPLACE_BLOCKS' : + case 'REMOVE_BLOCKS': { + return omit( state, action.clientIds ); + } + case 'UPDATE_BLOCK_LIST_SETTINGS': { + const { clientId } = action; + if ( ! action.settings ) { + if ( state.hasOwnProperty( clientId ) ) { + return omit( state, clientId ); + } + + return state; + } + + if ( isEqual( state[ clientId ], action.settings ) ) { + return state; + } + + return { + ...state, + [ clientId ]: action.settings, + }; + } + } + return state; +}; + +export default combineReducers( { + blocks, + isTyping, + isCaretWithinFormattedText, + blockSelection, + blocksMode, + blockListSettings, + insertionPoint, + template, + settings, + preferences, +} ); diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js new file mode 100644 index 0000000000000..efadb31bf0ff2 --- /dev/null +++ b/packages/block-editor/src/store/selectors.js @@ -0,0 +1,1395 @@ +/** + * External dependencies + */ +import { + castArray, + flatMap, + first, + get, + includes, + isArray, + isBoolean, + last, + map, + orderBy, + reduce, + some, +} from 'lodash'; +import createSelector from 'rememo'; + +/** + * WordPress dependencies + */ +import { + getBlockType, + getBlockTypes, + hasBlockSupport, + hasChildBlocksWithInserterSupport, +} from '@wordpress/blocks'; + +/*** + * Module constants + */ +export const INSERTER_UTILITY_HIGH = 3; +export const INSERTER_UTILITY_MEDIUM = 2; +export const INSERTER_UTILITY_LOW = 1; +export const INSERTER_UTILITY_NONE = 0; +const MILLISECONDS_PER_HOUR = 3600 * 1000; +const MILLISECONDS_PER_DAY = 24 * 3600 * 1000; +const MILLISECONDS_PER_WEEK = 7 * 24 * 3600 * 1000; + +/** + * Shared reference to an empty array for cases where it is important to avoid + * returning a new array reference on every invocation, as in a connected or + * other pure component which performs `shouldComponentUpdate` check on props. + * This should be used as a last resort, since the normalized data should be + * maintained by the reducer result in state. + * + * @type {Array} + */ +const EMPTY_ARRAY = []; + +/** + * Shared reference to an empty object for cases where it is important to avoid + * returning a new object reference on every invocation. + * + * @type {Object} + */ +const EMPTY_OBJECT = {}; + +/** + * Returns a new reference when the inner blocks of a given block client ID + * change. This is used exclusively as a memoized selector dependant, relying + * on this selector's shared return value and recursively those of its inner + * blocks defined as dependencies. This abuses mechanics of the selector + * memoization to return from the original selector function only when + * dependants change. + * + * @param {Object} state Editor state. + * @param {string} clientId Block client ID. + * + * @return {*} A value whose reference will change only when inner blocks of + * the given block client ID change. + */ +export const getBlockDependantsCacheBust = createSelector( + () => [], + ( state, clientId ) => map( + getBlockOrder( state, clientId ), + ( innerBlockClientId ) => getBlock( state, innerBlockClientId ), + ), +); + +/** + * Returns a block's name given its client ID, or null if no block exists with + * the client ID. + * + * @param {Object} state Editor state. + * @param {string} clientId Block client ID. + * + * @return {string} Block name. + */ +export function getBlockName( state, clientId ) { + const block = state.blocks.byClientId[ clientId ]; + return block ? block.name : null; +} + +/** + * Returns whether a block is valid or not. + * + * @param {Object} state Editor state. + * @param {string} clientId Block client ID. + * + * @return {boolean} Is Valid. + */ +export function isBlockValid( state, clientId ) { + const block = state.blocks.byClientId[ clientId ]; + return !! block && block.isValid; +} + +/** + * Returns a block's attributes given its client ID, or null if no block exists with + * the client ID. + * + * @param {Object} state Editor state. + * @param {string} clientId Block client ID. + * + * @return {Object?} Block attributes. + */ +export const getBlockAttributes = createSelector( + ( state, clientId ) => { + const block = state.blocks.byClientId[ clientId ]; + if ( ! block ) { + return null; + } + + let attributes = state.blocks.attributes[ clientId ]; + + // Inject custom source attribute values. + // + // TODO: Create generic external sourcing pattern, not explicitly + // targeting meta attributes. + const type = getBlockType( block.name ); + if ( type ) { + attributes = reduce( type.attributes, ( result, value, key ) => { + if ( value.source === 'meta' ) { + if ( result === attributes ) { + result = { ...result }; + } + + result[ key ] = getPostMeta( state, value.meta ); + } + + return result; + }, attributes ); + } + + return attributes; + }, + ( state, clientId ) => [ + state.blocks.byClientId[ clientId ], + state.blocks.attributes[ clientId ], + getPostMeta( state ), + ] +); + +/** + * Returns a block given its client ID. This is a parsed copy of the block, + * containing its `blockName`, `clientId`, and current `attributes` state. This + * is not the block's registration settings, which must be retrieved from the + * blocks module registration store. + * + * @param {Object} state Editor state. + * @param {string} clientId Block client ID. + * + * @return {Object} Parsed block object. + */ +export const getBlock = createSelector( + ( state, clientId ) => { + const block = state.blocks.byClientId[ clientId ]; + if ( ! block ) { + return null; + } + + return { + ...block, + attributes: getBlockAttributes( state, clientId ), + innerBlocks: getBlocks( state, clientId ), + }; + }, + ( state, clientId ) => [ + ...getBlockAttributes.getDependants( state, clientId ), + getBlockDependantsCacheBust( state, clientId ), + ] +); + +export const __unstableGetBlockWithoutInnerBlocks = createSelector( + ( state, clientId ) => { + const block = state.blocks.byClientId[ clientId ]; + if ( ! block ) { + return null; + } + + return { + ...block, + attributes: getBlockAttributes( state, clientId ), + }; + }, + ( state, clientId ) => [ + state.blocks.byClientId[ clientId ], + ...getBlockAttributes.getDependants( state, clientId ), + ] +); + +/** + * Returns all block objects for the current post being edited as an array in + * the order they appear in the post. + * + * Note: It's important to memoize this selector to avoid return a new instance + * on each call + * + * @param {Object} state Editor state. + * @param {?String} rootClientId Optional root client ID of block list. + * + * @return {Object[]} Post blocks. + */ +export const getBlocks = createSelector( + ( state, rootClientId ) => { + return map( + getBlockOrder( state, rootClientId ), + ( clientId ) => getBlock( state, clientId ) + ); + }, + ( state ) => [ + state.blocks.byClientId, + state.blocks.order, + state.blocks.attributes, + ] +); + +/** + * Returns an array containing the clientIds of all descendants + * of the blocks given. + * + * @param {Object} state Global application state. + * @param {Array} clientIds Array of blocks to inspect. + * + * @return {Array} ids of descendants. + */ +export const getClientIdsOfDescendants = ( state, clientIds ) => flatMap( clientIds, ( clientId ) => { + const descendants = getBlockOrder( state, clientId ); + return [ ...descendants, ...getClientIdsOfDescendants( state, descendants ) ]; +} ); + +/** + * Returns an array containing the clientIds of the top-level blocks + * and their descendants of any depth (for nested blocks). + * + * @param {Object} state Global application state. + * + * @return {Array} ids of top-level and descendant blocks. + */ +export const getClientIdsWithDescendants = createSelector( + ( state ) => { + const topLevelIds = getBlockOrder( state ); + return [ ...topLevelIds, ...getClientIdsOfDescendants( state, topLevelIds ) ]; + }, + ( state ) => [ + state.blocks.order, + ] +); + +/** + * Returns the total number of blocks, or the total number of blocks with a specific name in a post. + * The number returned includes nested blocks. + * + * @param {Object} state Global application state. + * @param {?String} blockName Optional block name, if specified only blocks of that type will be counted. + * + * @return {number} Number of blocks in the post, or number of blocks with name equal to blockName. + */ +export const getGlobalBlockCount = createSelector( + ( state, blockName ) => { + const clientIds = getClientIdsWithDescendants( state ); + if ( ! blockName ) { + return clientIds.length; + } + return reduce( clientIds, ( count, clientId ) => { + const block = state.blocks.byClientId[ clientId ]; + return block.name === blockName ? count + 1 : count; + }, 0 ); + }, + ( state ) => [ + state.blocks.order, + state.blocks.byClientId, + ] +); + +/** + * Given an array of block client IDs, returns the corresponding array of block + * objects. + * + * @param {Object} state Editor state. + * @param {string[]} clientIds Client IDs for which blocks are to be returned. + * + * @return {WPBlock[]} Block objects. + */ +export const getBlocksByClientId = createSelector( + ( state, clientIds ) => map( + castArray( clientIds ), + ( clientId ) => getBlock( state, clientId ) + ), + ( state ) => [ + getPostMeta( state ), + state.blocks.byClientId, + state.blocks.order, + state.blocks.attributes, + ] +); + +/** + * Returns the number of blocks currently present in the post. + * + * @param {Object} state Editor state. + * @param {?string} rootClientId Optional root client ID of block list. + * + * @return {number} Number of blocks in the post. + */ +export function getBlockCount( state, rootClientId ) { + return getBlockOrder( state, rootClientId ).length; +} + +/** + * Returns the current block selection start. This value may be null, and it + * may represent either a singular block selection or multi-selection start. + * A selection is singular if its start and end match. + * + * @param {Object} state Global application state. + * + * @return {?string} Client ID of block selection start. + */ +export function getBlockSelectionStart( state ) { + return state.blockSelection.start; +} + +/** + * Returns the current block selection end. This value may be null, and it + * may represent either a singular block selection or multi-selection end. + * A selection is singular if its start and end match. + * + * @param {Object} state Global application state. + * + * @return {?string} Client ID of block selection end. + */ +export function getBlockSelectionEnd( state ) { + return state.blockSelection.end; +} + +/** + * Returns the number of blocks currently selected in the post. + * + * @param {Object} state Global application state. + * + * @return {number} Number of blocks selected in the post. + */ +export function getSelectedBlockCount( state ) { + const multiSelectedBlockCount = getMultiSelectedBlockClientIds( state ).length; + + if ( multiSelectedBlockCount ) { + return multiSelectedBlockCount; + } + + return state.blockSelection.start ? 1 : 0; +} + +/** + * Returns true if there is a single selected block, or false otherwise. + * + * @param {Object} state Editor state. + * + * @return {boolean} Whether a single block is selected. + */ +export function hasSelectedBlock( state ) { + const { start, end } = state.blockSelection; + return !! start && start === end; +} + +/** + * Returns the currently selected block client ID, or null if there is no + * selected block. + * + * @param {Object} state Editor state. + * + * @return {?string} Selected block client ID. + */ +export function getSelectedBlockClientId( state ) { + const { start, end } = state.blockSelection; + // We need to check the block exists because the current blockSelection + // reducer doesn't take into account when blocks are reset via undo. To be + // removed when that's fixed. + return start && start === end && !! state.blocks.byClientId[ start ] ? start : null; +} + +/** + * Returns the currently selected block, or null if there is no selected block. + * + * @param {Object} state Global application state. + * + * @return {?Object} Selected block. + */ +export function getSelectedBlock( state ) { + const clientId = getSelectedBlockClientId( state ); + return clientId ? getBlock( state, clientId ) : null; +} + +/** + * Given a block client ID, returns the root block from which the block is + * nested, an empty string for top-level blocks, or null if the block does not + * exist. + * + * @param {Object} state Editor state. + * @param {string} clientId Block from which to find root client ID. + * + * @return {?string} Root client ID, if exists + */ +export const getBlockRootClientId = createSelector( + ( state, clientId ) => { + const { order } = state.blocks; + + for ( const rootClientId in order ) { + if ( includes( order[ rootClientId ], clientId ) ) { + return rootClientId; + } + } + + return null; + }, + ( state ) => [ + state.blocks.order, + ] +); + +/** + * Given a block client ID, returns the root of the hierarchy from which the block is nested, return the block itself for root level blocks. + * + * @param {Object} state Editor state. + * @param {string} clientId Block from which to find root client ID. + * + * @return {string} Root client ID + */ +export const getBlockHierarchyRootClientId = createSelector( + ( state, clientId ) => { + let rootClientId = clientId; + let current = clientId; + while ( rootClientId ) { + current = rootClientId; + rootClientId = getBlockRootClientId( state, current ); + } + + return current; + }, + ( state ) => [ + state.blocks.order, + ] +); + +/** + * Returns the client ID of the block adjacent one at the given reference + * startClientId and modifier directionality. Defaults start startClientId to + * the selected block, and direction as next block. Returns null if there is no + * adjacent block. + * + * @param {Object} state Editor state. + * @param {?string} startClientId Optional client ID of block from which to + * search. + * @param {?number} modifier Directionality multiplier (1 next, -1 + * previous). + * + * @return {?string} Return the client ID of the block, or null if none exists. + */ +export function getAdjacentBlockClientId( state, startClientId, modifier = 1 ) { + // Default to selected block. + if ( startClientId === undefined ) { + startClientId = getSelectedBlockClientId( state ); + } + + // Try multi-selection starting at extent based on modifier. + if ( startClientId === undefined ) { + if ( modifier < 0 ) { + startClientId = getFirstMultiSelectedBlockClientId( state ); + } else { + startClientId = getLastMultiSelectedBlockClientId( state ); + } + } + + // Validate working start client ID. + if ( ! startClientId ) { + return null; + } + + // Retrieve start block root client ID, being careful to allow the falsey + // empty string top-level root by explicitly testing against null. + const rootClientId = getBlockRootClientId( state, startClientId ); + if ( rootClientId === null ) { + return null; + } + + const { order } = state.blocks; + const orderSet = order[ rootClientId ]; + const index = orderSet.indexOf( startClientId ); + const nextIndex = ( index + ( 1 * modifier ) ); + + // Block was first in set and we're attempting to get previous. + if ( nextIndex < 0 ) { + return null; + } + + // Block was last in set and we're attempting to get next. + if ( nextIndex === orderSet.length ) { + return null; + } + + // Assume incremented index is within the set. + return orderSet[ nextIndex ]; +} + +/** + * Returns the previous block's client ID from the given reference start ID. + * Defaults start to the selected block. Returns null if there is no previous + * block. + * + * @param {Object} state Editor state. + * @param {?string} startClientId Optional client ID of block from which to + * search. + * + * @return {?string} Adjacent block's client ID, or null if none exists. + */ +export function getPreviousBlockClientId( state, startClientId ) { + return getAdjacentBlockClientId( state, startClientId, -1 ); +} + +/** + * Returns the next block's client ID from the given reference start ID. + * Defaults start to the selected block. Returns null if there is no next + * block. + * + * @param {Object} state Editor state. + * @param {?string} startClientId Optional client ID of block from which to + * search. + * + * @return {?string} Adjacent block's client ID, or null if none exists. + */ +export function getNextBlockClientId( state, startClientId ) { + return getAdjacentBlockClientId( state, startClientId, 1 ); +} + +/** + * Returns the initial caret position for the selected block. + * This position is to used to position the caret properly when the selected block changes. + * + * @param {Object} state Global application state. + * + * @return {?Object} Selected block. + */ +export function getSelectedBlocksInitialCaretPosition( state ) { + const { start, end } = state.blockSelection; + if ( start !== end || ! start ) { + return null; + } + + return state.blockSelection.initialPosition; +} + +/** + * Returns the current multi-selection set of block client IDs, or an empty + * array if there is no multi-selection. + * + * @param {Object} state Editor state. + * + * @return {Array} Multi-selected block client IDs. + */ +export const getMultiSelectedBlockClientIds = createSelector( + ( state ) => { + const { start, end } = state.blockSelection; + if ( start === end ) { + return []; + } + + // Retrieve root client ID to aid in retrieving relevant nested block + // order, being careful to allow the falsey empty string top-level root + // by explicitly testing against null. + const rootClientId = getBlockRootClientId( state, start ); + if ( rootClientId === null ) { + return []; + } + + const blockOrder = getBlockOrder( state, rootClientId ); + const startIndex = blockOrder.indexOf( start ); + const endIndex = blockOrder.indexOf( end ); + + if ( startIndex > endIndex ) { + return blockOrder.slice( endIndex, startIndex + 1 ); + } + + return blockOrder.slice( startIndex, endIndex + 1 ); + }, + ( state ) => [ + state.blocks.order, + state.blockSelection.start, + state.blockSelection.end, + ], +); + +/** + * Returns the current multi-selection set of blocks, or an empty array if + * there is no multi-selection. + * + * @param {Object} state Editor state. + * + * @return {Array} Multi-selected block objects. + */ +export const getMultiSelectedBlocks = createSelector( + ( state ) => { + const multiSelectedBlockClientIds = getMultiSelectedBlockClientIds( state ); + if ( ! multiSelectedBlockClientIds.length ) { + return EMPTY_ARRAY; + } + + return multiSelectedBlockClientIds.map( ( clientId ) => getBlock( state, clientId ) ); + }, + ( state ) => [ + ...getMultiSelectedBlockClientIds.getDependants( state ), + state.blocks.byClientId, + state.blocks.order, + state.blocks.attributes, + getPostMeta( state ), + ] +); + +/** + * Returns the client ID of the first block in the multi-selection set, or null + * if there is no multi-selection. + * + * @param {Object} state Editor state. + * + * @return {?string} First block client ID in the multi-selection set. + */ +export function getFirstMultiSelectedBlockClientId( state ) { + return first( getMultiSelectedBlockClientIds( state ) ) || null; +} + +/** + * Returns the client ID of the last block in the multi-selection set, or null + * if there is no multi-selection. + * + * @param {Object} state Editor state. + * + * @return {?string} Last block client ID in the multi-selection set. + */ +export function getLastMultiSelectedBlockClientId( state ) { + return last( getMultiSelectedBlockClientIds( state ) ) || null; +} + +/** + * Checks if possibleAncestorId is an ancestor of possibleDescendentId. + * + * @param {Object} state Editor state. + * @param {string} possibleAncestorId Possible ancestor client ID. + * @param {string} possibleDescendentId Possible descent client ID. + * + * @return {boolean} True if possibleAncestorId is an ancestor + * of possibleDescendentId, and false otherwise. + */ +const isAncestorOf = createSelector( + ( state, possibleAncestorId, possibleDescendentId ) => { + let idToCheck = possibleDescendentId; + while ( possibleAncestorId !== idToCheck && idToCheck ) { + idToCheck = getBlockRootClientId( state, idToCheck ); + } + return possibleAncestorId === idToCheck; + }, + ( state ) => [ + state.blocks.order, + ], +); + +/** + * Returns true if a multi-selection exists, and the block corresponding to the + * specified client ID is the first block of the multi-selection set, or false + * otherwise. + * + * @param {Object} state Editor state. + * @param {string} clientId Block client ID. + * + * @return {boolean} Whether block is first in multi-selection. + */ +export function isFirstMultiSelectedBlock( state, clientId ) { + return getFirstMultiSelectedBlockClientId( state ) === clientId; +} + +/** + * Returns true if the client ID occurs within the block multi-selection, or + * false otherwise. + * + * @param {Object} state Editor state. + * @param {string} clientId Block client ID. + * + * @return {boolean} Whether block is in multi-selection set. + */ +export function isBlockMultiSelected( state, clientId ) { + return getMultiSelectedBlockClientIds( state ).indexOf( clientId ) !== -1; +} + +/** + * Returns true if an ancestor of the block is multi-selected, or false + * otherwise. + * + * @param {Object} state Editor state. + * @param {string} clientId Block client ID. + * + * @return {boolean} Whether an ancestor of the block is in multi-selection + * set. + */ +export const isAncestorMultiSelected = createSelector( + ( state, clientId ) => { + let ancestorClientId = clientId; + let isMultiSelected = false; + while ( ancestorClientId && ! isMultiSelected ) { + ancestorClientId = getBlockRootClientId( state, ancestorClientId ); + isMultiSelected = isBlockMultiSelected( state, ancestorClientId ); + } + return isMultiSelected; + }, + ( state ) => [ + state.blocks.order, + state.blockSelection.start, + state.blockSelection.end, + ], +); +/** + * Returns the client ID of the block which begins the multi-selection set, or + * null if there is no multi-selection. + * + * This is not necessarily the first client ID in the selection. + * + * @see getFirstMultiSelectedBlockClientId + * + * @param {Object} state Editor state. + * + * @return {?string} Client ID of block beginning multi-selection. + */ +export function getMultiSelectedBlocksStartClientId( state ) { + const { start, end } = state.blockSelection; + if ( start === end ) { + return null; + } + return start || null; +} + +/** + * Returns the client ID of the block which ends the multi-selection set, or + * null if there is no multi-selection. + * + * This is not necessarily the last client ID in the selection. + * + * @see getLastMultiSelectedBlockClientId + * + * @param {Object} state Editor state. + * + * @return {?string} Client ID of block ending multi-selection. + */ +export function getMultiSelectedBlocksEndClientId( state ) { + const { start, end } = state.blockSelection; + if ( start === end ) { + return null; + } + return end || null; +} + +/** + * Returns an array containing all block client IDs in the editor in the order + * they appear. Optionally accepts a root client ID of the block list for which + * the order should be returned, defaulting to the top-level block order. + * + * @param {Object} state Editor state. + * @param {?string} rootClientId Optional root client ID of block list. + * + * @return {Array} Ordered client IDs of editor blocks. + */ +export function getBlockOrder( state, rootClientId ) { + return state.blocks.order[ rootClientId || '' ] || EMPTY_ARRAY; +} + +/** + * Returns the index at which the block corresponding to the specified client + * ID occurs within the block order, or `-1` if the block does not exist. + * + * @param {Object} state Editor state. + * @param {string} clientId Block client ID. + * @param {?string} rootClientId Optional root client ID of block list. + * + * @return {number} Index at which block exists in order. + */ +export function getBlockIndex( state, clientId, rootClientId ) { + return getBlockOrder( state, rootClientId ).indexOf( clientId ); +} + +/** + * Returns true if the block corresponding to the specified client ID is + * currently selected and no multi-selection exists, or false otherwise. + * + * @param {Object} state Editor state. + * @param {string} clientId Block client ID. + * + * @return {boolean} Whether block is selected and multi-selection exists. + */ +export function isBlockSelected( state, clientId ) { + const { start, end } = state.blockSelection; + + if ( start !== end ) { + return false; + } + + return start === clientId; +} + +/** + * Returns true if one of the block's inner blocks is selected. + * + * @param {Object} state Editor state. + * @param {string} clientId Block client ID. + * @param {boolean} deep Perform a deep check. + * + * @return {boolean} Whether the block as an inner block selected + */ +export function hasSelectedInnerBlock( state, clientId, deep = false ) { + return some( + getBlockOrder( state, clientId ), + ( innerClientId ) => ( + isBlockSelected( state, innerClientId ) || + isBlockMultiSelected( state, innerClientId ) || + ( deep && hasSelectedInnerBlock( state, innerClientId, deep ) ) + ) + ); +} + +/** + * Returns true if the block corresponding to the specified client ID is + * currently selected but isn't the last of the selected blocks. Here "last" + * refers to the block sequence in the document, _not_ the sequence of + * multi-selection, which is why `state.blockSelection.end` isn't used. + * + * @param {Object} state Editor state. + * @param {string} clientId Block client ID. + * + * @return {boolean} Whether block is selected and not the last in the + * selection. + */ +export function isBlockWithinSelection( state, clientId ) { + if ( ! clientId ) { + return false; + } + + const clientIds = getMultiSelectedBlockClientIds( state ); + const index = clientIds.indexOf( clientId ); + return index > -1 && index < clientIds.length - 1; +} + +/** + * Returns true if a multi-selection has been made, or false otherwise. + * + * @param {Object} state Editor state. + * + * @return {boolean} Whether multi-selection has been made. + */ +export function hasMultiSelection( state ) { + const { start, end } = state.blockSelection; + return start !== end; +} + +/** + * Whether in the process of multi-selecting or not. This flag is only true + * while the multi-selection is being selected (by mouse move), and is false + * once the multi-selection has been settled. + * + * @see hasMultiSelection + * + * @param {Object} state Global application state. + * + * @return {boolean} True if multi-selecting, false if not. + */ +export function isMultiSelecting( state ) { + return state.blockSelection.isMultiSelecting; +} + +/** + * Selector that returns if multi-selection is enabled or not. + * + * @param {Object} state Global application state. + * + * @return {boolean} True if it should be possible to multi-select blocks, false if multi-selection is disabled. + */ +export function isSelectionEnabled( state ) { + return state.blockSelection.isEnabled; +} + +/** + * Returns the block's editing mode, defaulting to "visual" if not explicitly + * assigned. + * + * @param {Object} state Editor state. + * @param {string} clientId Block client ID. + * + * @return {Object} Block editing mode. + */ +export function getBlockMode( state, clientId ) { + return state.blocksMode[ clientId ] || 'visual'; +} + +/** + * Returns true if the user is typing, or false otherwise. + * + * @param {Object} state Global application state. + * + * @return {boolean} Whether user is typing. + */ +export function isTyping( state ) { + return state.isTyping; +} + +/** + * Returns true if the caret is within formatted text, or false otherwise. + * + * @param {Object} state Global application state. + * + * @return {boolean} Whether the caret is within formatted text. + */ +export function isCaretWithinFormattedText( state ) { + return state.isCaretWithinFormattedText; +} + +/** + * Returns the insertion point, the index at which the new inserted block would + * be placed. Defaults to the last index. + * + * @param {Object} state Editor state. + * + * @return {Object} Insertion point object with `rootClientId`, `index`. + */ +export function getBlockInsertionPoint( state ) { + let rootClientId, index; + + const { insertionPoint, blockSelection } = state; + if ( insertionPoint !== null ) { + return insertionPoint; + } + + const { end } = blockSelection; + if ( end ) { + rootClientId = getBlockRootClientId( state, end ) || undefined; + index = getBlockIndex( state, end, rootClientId ) + 1; + } else { + index = getBlockOrder( state ).length; + } + + return { rootClientId, index }; +} + +/** + * Returns true if we should show the block insertion point. + * + * @param {Object} state Global application state. + * + * @return {?boolean} Whether the insertion point is visible or not. + */ +export function isBlockInsertionPointVisible( state ) { + return state.insertionPoint !== null; +} + +/** + * Returns whether the blocks matches the template or not. + * + * @param {boolean} state + * @return {?boolean} Whether the template is valid or not. + */ +export function isValidTemplate( state ) { + return state.template.isValid; +} + +/** + * Returns the defined block template + * + * @param {boolean} state + * @return {?Array} Block Template + */ +export function getTemplate( state ) { + return state.settings.template; +} + +/** + * Returns the defined block template lock. Optionally accepts a root block + * client ID as context, otherwise defaulting to the global context. + * + * @param {Object} state Editor state. + * @param {?string} rootClientId Optional block root client ID. + * + * @return {?string} Block Template Lock + */ +export function getTemplateLock( state, rootClientId ) { + if ( ! rootClientId ) { + return state.settings.templateLock; + } + + const blockListSettings = getBlockListSettings( state, rootClientId ); + if ( ! blockListSettings ) { + return null; + } + + return blockListSettings.templateLock; +} + +/** + * Determines if the given block type is allowed to be inserted into the block list. + * This function is not exported and not memoized because using a memoized selector + * inside another memoized selector is just a waste of time. + * + * @param {Object} state Editor state. + * @param {string} blockName The name of the block type, e.g.' core/paragraph'. + * @param {?string} rootClientId Optional root client ID of block list. + * + * @return {boolean} Whether the given block type is allowed to be inserted. + */ +const canInsertBlockTypeUnmemoized = ( state, blockName, rootClientId = null ) => { + const checkAllowList = ( list, item, defaultResult = null ) => { + if ( isBoolean( list ) ) { + return list; + } + if ( isArray( list ) ) { + return includes( list, item ); + } + return defaultResult; + }; + + const blockType = getBlockType( blockName ); + if ( ! blockType ) { + return false; + } + + const { allowedBlockTypes } = getEditorSettings( state ); + + const isBlockAllowedInEditor = checkAllowList( allowedBlockTypes, blockName, true ); + if ( ! isBlockAllowedInEditor ) { + return false; + } + + const isLocked = !! getTemplateLock( state, rootClientId ); + if ( isLocked ) { + return false; + } + + const parentBlockListSettings = getBlockListSettings( state, rootClientId ); + const parentAllowedBlocks = get( parentBlockListSettings, [ 'allowedBlocks' ] ); + const hasParentAllowedBlock = checkAllowList( parentAllowedBlocks, blockName ); + + const blockAllowedParentBlocks = blockType.parent; + const parentName = getBlockName( state, rootClientId ); + const hasBlockAllowedParent = checkAllowList( blockAllowedParentBlocks, parentName ); + + if ( hasParentAllowedBlock !== null && hasBlockAllowedParent !== null ) { + return hasParentAllowedBlock || hasBlockAllowedParent; + } else if ( hasParentAllowedBlock !== null ) { + return hasParentAllowedBlock; + } else if ( hasBlockAllowedParent !== null ) { + return hasBlockAllowedParent; + } + + return true; +}; + +/** + * Determines if the given block type is allowed to be inserted into the block list. + * + * @param {Object} state Editor state. + * @param {string} blockName The name of the block type, e.g.' core/paragraph'. + * @param {?string} rootClientId Optional root client ID of block list. + * + * @return {boolean} Whether the given block type is allowed to be inserted. + */ +export const canInsertBlockType = createSelector( + canInsertBlockTypeUnmemoized, + ( state, blockName, rootClientId ) => [ + state.blockListSettings[ rootClientId ], + state.blocks.byClientId[ rootClientId ], + state.settings.allowedBlockTypes, + state.settings.templateLock, + ], +); + +/** + * Returns information about how recently and frequently a block has been inserted. + * + * @param {Object} state Global application state. + * @param {string} id A string which identifies the insert, e.g. 'core/block/12' + * + * @return {?{ time: number, count: number }} An object containing `time` which is when the last + * insert occurred as a UNIX epoch, and `count` which is + * the number of inserts that have occurred. + */ +function getInsertUsage( state, id ) { + return state.preferences.insertUsage[ id ] || null; +} + +/** + * Returns whether we can show a block type in the inserter + * + * @param {Object} state Global State + * @param {Object} blockType BlockType + * @param {?string} rootClientId Optional root client ID of block list. + * + * @return {boolean} Whether the given block type is allowed to be shown in the inserter. + */ +const canIncludeBlockTypeInInserter = ( state, blockType, rootClientId ) => { + if ( ! hasBlockSupport( blockType, 'inserter', true ) ) { + return false; + } + + return canInsertBlockTypeUnmemoized( state, blockType.name, rootClientId ); +}; + +/** + * Returns whether we can show a reusable block in the inserter + * + * @param {Object} state Global State + * @param {Object} reusableBlock Reusable block object + * @param {?string} rootClientId Optional root client ID of block list. + * + * @return {boolean} Whether the given block type is allowed to be shown in the inserter. + */ +const canIncludeReusableBlockInInserter = ( state, reusableBlock, rootClientId ) => { + if ( ! canInsertBlockTypeUnmemoized( state, 'core/block', rootClientId ) ) { + return false; + } + + const referencedBlockName = getBlockName( state, reusableBlock.clientId ); + if ( ! referencedBlockName ) { + return false; + } + + const referencedBlockType = getBlockType( referencedBlockName ); + if ( ! referencedBlockType ) { + return false; + } + + if ( ! canInsertBlockTypeUnmemoized( state, referencedBlockName, rootClientId ) ) { + return false; + } + + if ( isAncestorOf( state, reusableBlock.clientId, rootClientId ) ) { + return false; + } + + return true; +}; + +/** + * Determines the items that appear in the inserter. Includes both static + * items (e.g. a regular block type) and dynamic items (e.g. a reusable block). + * + * Each item object contains what's necessary to display a button in the + * inserter and handle its selection. + * + * The 'utility' property indicates how useful we think an item will be to the + * user. There are 4 levels of utility: + * + * 1. Blocks that are contextually useful (utility = 3) + * 2. Blocks that have been previously inserted (utility = 2) + * 3. Blocks that are in the common category (utility = 1) + * 4. All other blocks (utility = 0) + * + * The 'frecency' property is a heuristic (https://en.wikipedia.org/wiki/Frecency) + * that combines block usage frequenty and recency. + * + * Items are returned ordered descendingly by their 'utility' and 'frecency'. + * + * @param {Object} state Editor state. + * @param {?string} rootClientId Optional root client ID of block list. + * + * @return {Editor.InserterItem[]} Items that appear in inserter. + * + * @typedef {Object} Editor.InserterItem + * @property {string} id Unique identifier for the item. + * @property {string} name The type of block to create. + * @property {Object} initialAttributes Attributes to pass to the newly created block. + * @property {string} title Title of the item, as it appears in the inserter. + * @property {string} icon Dashicon for the item, as it appears in the inserter. + * @property {string} category Block category that the item is associated with. + * @property {string[]} keywords Keywords that can be searched to find this item. + * @property {boolean} isDisabled Whether or not the user should be prevented from inserting + * this item. + * @property {number} utility How useful we think this item is, between 0 and 3. + * @property {number} frecency Hueristic that combines frequency and recency. + */ +export const getInserterItems = createSelector( + ( state, rootClientId = null ) => { + const calculateUtility = ( category, count, isContextual ) => { + if ( isContextual ) { + return INSERTER_UTILITY_HIGH; + } else if ( count > 0 ) { + return INSERTER_UTILITY_MEDIUM; + } else if ( category === 'common' ) { + return INSERTER_UTILITY_LOW; + } + return INSERTER_UTILITY_NONE; + }; + + const calculateFrecency = ( time, count ) => { + if ( ! time ) { + return count; + } + + // The selector is cached, which means Date.now() is the last time that the + // relevant state changed. This suits our needs. + const duration = Date.now() - time; + + switch ( true ) { + case duration < MILLISECONDS_PER_HOUR: + return count * 4; + case duration < MILLISECONDS_PER_DAY: + return count * 2; + case duration < MILLISECONDS_PER_WEEK: + return count / 2; + default: + return count / 4; + } + }; + + const buildBlockTypeInserterItem = ( blockType ) => { + const id = blockType.name; + + let isDisabled = false; + if ( ! hasBlockSupport( blockType.name, 'multiple', true ) ) { + isDisabled = some( getBlocksByClientId( state, getClientIdsWithDescendants( state ) ), { name: blockType.name } ); + } + + const isContextual = isArray( blockType.parent ); + const { time, count = 0 } = getInsertUsage( state, id ) || {}; + + return { + id, + name: blockType.name, + initialAttributes: {}, + title: blockType.title, + icon: blockType.icon, + category: blockType.category, + keywords: blockType.keywords, + isDisabled, + utility: calculateUtility( blockType.category, count, isContextual ), + frecency: calculateFrecency( time, count ), + hasChildBlocksWithInserterSupport: hasChildBlocksWithInserterSupport( blockType.name ), + }; + }; + + const buildReusableBlockInserterItem = ( reusableBlock ) => { + const id = `core/block/${ reusableBlock.id }`; + + const referencedBlockName = getBlockName( state, reusableBlock.clientId ); + const referencedBlockType = getBlockType( referencedBlockName ); + + const { time, count = 0 } = getInsertUsage( state, id ) || {}; + const utility = calculateUtility( 'reusable', count, false ); + const frecency = calculateFrecency( time, count ); + + return { + id, + name: 'core/block', + initialAttributes: { ref: reusableBlock.id }, + title: reusableBlock.title, + icon: referencedBlockType.icon, + category: 'reusable', + keywords: [], + isDisabled: false, + utility, + frecency, + }; + }; + + const blockTypeInserterItems = getBlockTypes() + .filter( ( blockType ) => canIncludeBlockTypeInInserter( state, blockType, rootClientId ) ) + .map( buildBlockTypeInserterItem ); + + const reusableBlockInserterItems = getReusableBlocks( state ) + .filter( ( block ) => canIncludeReusableBlockInInserter( state, block, rootClientId ) ) + .map( buildReusableBlockInserterItem ); + + return orderBy( + [ ...blockTypeInserterItems, ...reusableBlockInserterItems ], + [ 'utility', 'frecency' ], + [ 'desc', 'desc' ] + ); + }, + ( state, rootClientId ) => [ + state.blockListSettings[ rootClientId ], + state.blocks.byClientId, + state.blocks.order, + state.preferences.insertUsage, + state.settings.allowedBlockTypes, + state.settings.templateLock, + getReusableBlocks( state ), + getBlockTypes(), + ], +); + +/** + * Determines whether there are items to show in the inserter. + * @param {Object} state Editor state. + * @param {?string} rootClientId Optional root client ID of block list. + * + * @return {boolean} Items that appear in inserter. + */ +export const hasInserterItems = createSelector( + ( state, rootClientId = null ) => { + const hasBlockType = some( + getBlockTypes(), + ( blockType ) => canIncludeBlockTypeInInserter( state, blockType, rootClientId ) + ); + if ( hasBlockType ) { + return true; + } + const hasReusableBlock = some( + getReusableBlocks( state ), + ( block ) => canIncludeReusableBlockInInserter( state, block, rootClientId ) + ); + + return hasReusableBlock; + }, + ( state, rootClientId ) => [ + state.blockListSettings[ rootClientId ], + state.blocks.byClientId, + state.settings.allowedBlockTypes, + state.settings.templateLock, + getReusableBlocks( state ), + getBlockTypes(), + ], +); + +/** + * Returns the Block List settings of a block, if any exist. + * + * @param {Object} state Editor state. + * @param {?string} clientId Block client ID. + * + * @return {?Object} Block settings of the block if set. + */ +export function getBlockListSettings( state, clientId ) { + return state.blockListSettings[ clientId ]; +} + +/** + * Returns the editor settings. + * + * @param {Object} state Editor state. + * + * @return {Object} The editor settings object. + */ +export function getEditorSettings( state ) { + return state.settings; +} + +/** + * Returns true if the most recent block change is be considered persistent, or + * false otherwise. A persistent change is one committed by BlockEditorProvider + * via its `onChange` callback, in addition to `onInput`. + * + * @param {Object} state Block editor state. + * + * @return {boolean} Whether the most recent block change was persistent. + */ +export function isLastBlockChangePersistent( state ) { + return state.blocks.isPersistentChange; +} + +/** + * Returns the value of a post meta from the editor settings. + * + * @param {Object} state Global application state. + * @param {string} key Meta Key to retrieve + * + * @return {*} Meta value + */ +function getPostMeta( state, key ) { + if ( key === undefined ) { + return get( state, [ 'settings', '__experimentalMetaSource', 'value' ], EMPTY_OBJECT ); + } + + return get( state, [ 'settings', '__experimentalMetaSource', 'value', key ] ); +} + +/** + * Returns the available reusable blocks + * + * @param {Object} state Global application state. + * + * @return {Array} Reusable blocks + */ +function getReusableBlocks( state ) { + return get( state, [ 'settings', '__experimentalReusableBlocks' ], EMPTY_ARRAY ); +} diff --git a/packages/block-editor/src/store/test/actions.js b/packages/block-editor/src/store/test/actions.js new file mode 100644 index 0000000000000..3ae9039505356 --- /dev/null +++ b/packages/block-editor/src/store/test/actions.js @@ -0,0 +1,336 @@ +/** + * Internal dependencies + */ +import { + replaceBlocks, + startTyping, + stopTyping, + enterFormattedText, + exitFormattedText, + toggleSelection, + resetBlocks, + updateBlockAttributes, + updateBlock, + selectBlock, + selectPreviousBlock, + startMultiSelect, + stopMultiSelect, + multiSelect, + clearSelectedBlock, + replaceBlock, + insertBlock, + insertBlocks, + showInsertionPoint, + hideInsertionPoint, + mergeBlocks, + removeBlocks, + removeBlock, + toggleBlockMode, + updateBlockListSettings, +} from '../actions'; + +describe( 'actions', () => { + describe( 'resetBlocks', () => { + it( 'should return the RESET_BLOCKS actions', () => { + const blocks = []; + const result = resetBlocks( blocks ); + expect( result ).toEqual( { + type: 'RESET_BLOCKS', + blocks, + } ); + } ); + } ); + + describe( 'updateBlockAttributes', () => { + it( 'should return the UPDATE_BLOCK_ATTRIBUTES action', () => { + const clientId = 'myclientid'; + const attributes = {}; + const result = updateBlockAttributes( clientId, attributes ); + expect( result ).toEqual( { + type: 'UPDATE_BLOCK_ATTRIBUTES', + clientId, + attributes, + } ); + } ); + } ); + + describe( 'updateBlock', () => { + it( 'should return the UPDATE_BLOCK action', () => { + const clientId = 'myclientid'; + const updates = {}; + const result = updateBlock( clientId, updates ); + expect( result ).toEqual( { + type: 'UPDATE_BLOCK', + clientId, + updates, + } ); + } ); + } ); + + describe( 'selectBlock', () => { + it( 'should return the SELECT_BLOCK action', () => { + const clientId = 'myclientid'; + const result = selectBlock( clientId, -1 ); + expect( result ).toEqual( { + type: 'SELECT_BLOCK', + initialPosition: -1, + clientId, + } ); + } ); + } ); + + describe( 'startMultiSelect', () => { + it( 'should return the START_MULTI_SELECT', () => { + expect( startMultiSelect() ).toEqual( { + type: 'START_MULTI_SELECT', + } ); + } ); + } ); + + describe( 'stopMultiSelect', () => { + it( 'should return the Stop_MULTI_SELECT', () => { + expect( stopMultiSelect() ).toEqual( { + type: 'STOP_MULTI_SELECT', + } ); + } ); + } ); + describe( 'multiSelect', () => { + it( 'should return MULTI_SELECT action', () => { + const start = 'start'; + const end = 'end'; + expect( multiSelect( start, end ) ).toEqual( { + type: 'MULTI_SELECT', + start, + end, + } ); + } ); + } ); + + describe( 'clearSelectedBlock', () => { + it( 'should return CLEAR_SELECTED_BLOCK action', () => { + expect( clearSelectedBlock() ).toEqual( { + type: 'CLEAR_SELECTED_BLOCK', + } ); + } ); + } ); + + describe( 'replaceBlock', () => { + it( 'should return the REPLACE_BLOCKS action', () => { + const block = { + clientId: 'ribs', + }; + + expect( replaceBlock( [ 'chicken' ], block ) ).toEqual( { + type: 'REPLACE_BLOCKS', + clientIds: [ 'chicken' ], + blocks: [ block ], + time: expect.any( Number ), + } ); + } ); + } ); + + describe( 'replaceBlocks', () => { + it( 'should return the REPLACE_BLOCKS action', () => { + const blocks = [ { + clientId: 'ribs', + } ]; + + expect( replaceBlocks( [ 'chicken' ], blocks ) ).toEqual( { + type: 'REPLACE_BLOCKS', + clientIds: [ 'chicken' ], + blocks, + time: expect.any( Number ), + } ); + } ); + } ); + + describe( 'insertBlock', () => { + it( 'should return the INSERT_BLOCKS action', () => { + const block = { + clientId: 'ribs', + }; + const index = 5; + expect( insertBlock( block, index, 'testclientid' ) ).toEqual( { + type: 'INSERT_BLOCKS', + blocks: [ block ], + index, + rootClientId: 'testclientid', + time: expect.any( Number ), + updateSelection: true, + } ); + } ); + } ); + + describe( 'insertBlocks', () => { + it( 'should return the INSERT_BLOCKS action', () => { + const blocks = [ { + clientId: 'ribs', + } ]; + const index = 3; + expect( insertBlocks( blocks, index, 'testclientid' ) ).toEqual( { + type: 'INSERT_BLOCKS', + blocks, + index, + rootClientId: 'testclientid', + time: expect.any( Number ), + updateSelection: true, + } ); + } ); + } ); + + describe( 'showInsertionPoint', () => { + it( 'should return the SHOW_INSERTION_POINT action', () => { + expect( showInsertionPoint() ).toEqual( { + type: 'SHOW_INSERTION_POINT', + } ); + } ); + } ); + + describe( 'hideInsertionPoint', () => { + it( 'should return the HIDE_INSERTION_POINT action', () => { + expect( hideInsertionPoint() ).toEqual( { + type: 'HIDE_INSERTION_POINT', + } ); + } ); + } ); + + describe( 'mergeBlocks', () => { + it( 'should return MERGE_BLOCKS action', () => { + const firstBlockClientId = 'blockA'; + const secondBlockClientId = 'blockB'; + expect( mergeBlocks( firstBlockClientId, secondBlockClientId ) ).toEqual( { + type: 'MERGE_BLOCKS', + blocks: [ firstBlockClientId, secondBlockClientId ], + } ); + } ); + } ); + + describe( 'removeBlocks', () => { + it( 'should return REMOVE_BLOCKS action', () => { + const clientId = 'clientId'; + const clientIds = [ clientId ]; + + const actions = Array.from( removeBlocks( clientIds ) ); + + expect( actions ).toEqual( [ + selectPreviousBlock( clientId ), + { + type: 'REMOVE_BLOCKS', + clientIds, + }, + ] ); + } ); + } ); + + describe( 'removeBlock', () => { + it( 'should return REMOVE_BLOCKS action', () => { + const clientId = 'myclientid'; + + const actions = Array.from( removeBlock( clientId ) ); + + expect( actions ).toEqual( [ + selectPreviousBlock( clientId ), + { + type: 'REMOVE_BLOCKS', + clientIds: [ clientId ], + }, + ] ); + } ); + + it( 'should return REMOVE_BLOCKS action, opting out of remove previous', () => { + const clientId = 'myclientid'; + + const actions = Array.from( removeBlock( clientId, false ) ); + + expect( actions ).toEqual( [ + { + type: 'REMOVE_BLOCKS', + clientIds: [ clientId ], + }, + ] ); + } ); + } ); + + describe( 'toggleBlockMode', () => { + it( 'should return TOGGLE_BLOCK_MODE action', () => { + const clientId = 'myclientid'; + expect( toggleBlockMode( clientId ) ).toEqual( { + type: 'TOGGLE_BLOCK_MODE', + clientId, + } ); + } ); + } ); + + describe( 'startTyping', () => { + it( 'should return the START_TYPING action', () => { + expect( startTyping() ).toEqual( { + type: 'START_TYPING', + } ); + } ); + } ); + + describe( 'stopTyping', () => { + it( 'should return the STOP_TYPING action', () => { + expect( stopTyping() ).toEqual( { + type: 'STOP_TYPING', + } ); + } ); + } ); + + describe( 'enterFormattedText', () => { + it( 'should return the ENTER_FORMATTED_TEXT action', () => { + expect( enterFormattedText() ).toEqual( { + type: 'ENTER_FORMATTED_TEXT', + } ); + } ); + } ); + + describe( 'exitFormattedText', () => { + it( 'should return the EXIT_FORMATTED_TEXT action', () => { + expect( exitFormattedText() ).toEqual( { + type: 'EXIT_FORMATTED_TEXT', + } ); + } ); + } ); + + describe( 'toggleSelection', () => { + it( 'should return the TOGGLE_SELECTION action with default value for isSelectionEnabled = true', () => { + expect( toggleSelection() ).toEqual( { + type: 'TOGGLE_SELECTION', + isSelectionEnabled: true, + } ); + } ); + + it( 'should return the TOGGLE_SELECTION action with isSelectionEnabled = true as passed in the argument', () => { + expect( toggleSelection( true ) ).toEqual( { + type: 'TOGGLE_SELECTION', + isSelectionEnabled: true, + } ); + } ); + + it( 'should return the TOGGLE_SELECTION action with isSelectionEnabled = false as passed in the argument', () => { + expect( toggleSelection( false ) ).toEqual( { + type: 'TOGGLE_SELECTION', + isSelectionEnabled: false, + } ); + } ); + } ); + + describe( 'updateBlockListSettings', () => { + it( 'should return the UPDATE_BLOCK_LIST_SETTINGS with undefined settings', () => { + expect( updateBlockListSettings( 'chicken' ) ).toEqual( { + type: 'UPDATE_BLOCK_LIST_SETTINGS', + clientId: 'chicken', + settings: undefined, + } ); + } ); + + it( 'should return the UPDATE_BLOCK_LIST_SETTINGS action with the passed settings', () => { + expect( updateBlockListSettings( 'chicken', { chicken: 'ribs' } ) ).toEqual( { + type: 'UPDATE_BLOCK_LIST_SETTINGS', + clientId: 'chicken', + settings: { chicken: 'ribs' }, + } ); + } ); + } ); +} ); diff --git a/packages/editor/src/store/test/array.js b/packages/block-editor/src/store/test/array.js similarity index 100% rename from packages/editor/src/store/test/array.js rename to packages/block-editor/src/store/test/array.js diff --git a/packages/block-editor/src/store/test/effects.js b/packages/block-editor/src/store/test/effects.js new file mode 100644 index 0000000000000..090779cbad0d9 --- /dev/null +++ b/packages/block-editor/src/store/test/effects.js @@ -0,0 +1,280 @@ +/** + * External dependencies + */ +import { noop } from 'lodash'; + +/** + * WordPress dependencies + */ +import { + getBlockTypes, + unregisterBlockType, + registerBlockType, + createBlock, +} from '@wordpress/blocks'; +import { createRegistry } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import actions, { + updateEditorSettings, + mergeBlocks, + replaceBlocks, + resetBlocks, + selectBlock, + setTemplateValidity, +} from '../actions'; +import effects, { validateBlocksToTemplate } from '../effects'; +import * as selectors from '../selectors'; +import reducer from '../reducer'; +import applyMiddlewares from '../middlewares'; +import '../../'; + +describe( 'effects', () => { + const defaultBlockSettings = { save: () => 'Saved', category: 'common', title: 'block title' }; + + describe( '.MERGE_BLOCKS', () => { + const handler = effects.MERGE_BLOCKS; + const defaultGetBlock = selectors.getBlock; + + afterEach( () => { + getBlockTypes().forEach( ( block ) => { + unregisterBlockType( block.name ); + } ); + selectors.getBlock = defaultGetBlock; + } ); + + it( 'should only focus the blockA if the blockA has no merge function', () => { + registerBlockType( 'core/test-block', defaultBlockSettings ); + const blockA = { + clientId: 'chicken', + name: 'core/test-block', + }; + const blockB = { + clientId: 'ribs', + name: 'core/test-block', + }; + selectors.getBlock = ( state, clientId ) => { + return blockA.clientId === clientId ? blockA : blockB; + }; + + const dispatch = jest.fn(); + const getState = () => ( {} ); + handler( mergeBlocks( blockA.clientId, blockB.clientId ), { dispatch, getState } ); + + expect( dispatch ).toHaveBeenCalledTimes( 1 ); + expect( dispatch ).toHaveBeenCalledWith( selectBlock( 'chicken' ) ); + } ); + + it( 'should merge the blocks if blocks of the same type', () => { + registerBlockType( 'core/test-block', { + merge( attributes, attributesToMerge ) { + return { + content: attributes.content + ' ' + attributesToMerge.content, + }; + }, + save: noop, + category: 'common', + title: 'test block', + } ); + const blockA = { + clientId: 'chicken', + name: 'core/test-block', + attributes: { content: 'chicken' }, + }; + const blockB = { + clientId: 'ribs', + name: 'core/test-block', + attributes: { content: 'ribs' }, + }; + selectors.getBlock = ( state, clientId ) => { + return blockA.clientId === clientId ? blockA : blockB; + }; + const dispatch = jest.fn(); + const getState = () => ( {} ); + handler( mergeBlocks( blockA.clientId, blockB.clientId ), { dispatch, getState } ); + + expect( dispatch ).toHaveBeenCalledTimes( 2 ); + expect( dispatch ).toHaveBeenCalledWith( selectBlock( 'chicken', -1 ) ); + expect( dispatch ).toHaveBeenCalledWith( { + ...replaceBlocks( [ 'chicken', 'ribs' ], [ { + clientId: 'chicken', + name: 'core/test-block', + attributes: { content: 'chicken ribs' }, + } ] ), + time: expect.any( Number ), + } ); + } ); + + it( 'should not merge the blocks have different types without transformation', () => { + registerBlockType( 'core/test-block', { + merge( attributes, attributesToMerge ) { + return { + content: attributes.content + ' ' + attributesToMerge.content, + }; + }, + save: noop, + category: 'common', + title: 'test block', + } ); + registerBlockType( 'core/test-block-2', defaultBlockSettings ); + const blockA = { + clientId: 'chicken', + name: 'core/test-block', + attributes: { content: 'chicken' }, + }; + const blockB = { + clientId: 'ribs', + name: 'core/test-block2', + attributes: { content: 'ribs' }, + }; + selectors.getBlock = ( state, clientId ) => { + return blockA.clientId === clientId ? blockA : blockB; + }; + const dispatch = jest.fn(); + const getState = () => ( {} ); + handler( mergeBlocks( blockA.clientId, blockB.clientId ), { dispatch, getState } ); + + expect( dispatch ).not.toHaveBeenCalled(); + } ); + + it( 'should transform and merge the blocks', () => { + registerBlockType( 'core/test-block', { + attributes: { + content: { + type: 'string', + }, + }, + merge( attributes, attributesToMerge ) { + return { + content: attributes.content + ' ' + attributesToMerge.content, + }; + }, + save: noop, + category: 'common', + title: 'test block', + } ); + registerBlockType( 'core/test-block-2', { + attributes: { + content: { + type: 'string', + }, + }, + transforms: { + to: [ { + type: 'block', + blocks: [ 'core/test-block' ], + transform: ( { content2 } ) => { + return createBlock( 'core/test-block', { + content: content2, + } ); + }, + } ], + }, + save: noop, + category: 'common', + title: 'test block 2', + } ); + const blockA = { + clientId: 'chicken', + name: 'core/test-block', + attributes: { content: 'chicken' }, + }; + const blockB = { + clientId: 'ribs', + name: 'core/test-block-2', + attributes: { content2: 'ribs' }, + }; + selectors.getBlock = ( state, clientId ) => { + return blockA.clientId === clientId ? blockA : blockB; + }; + const dispatch = jest.fn(); + const getState = () => ( {} ); + handler( mergeBlocks( blockA.clientId, blockB.clientId ), { dispatch, getState } ); + + expect( dispatch ).toHaveBeenCalledTimes( 2 ); + // expect( dispatch ).toHaveBeenCalledWith( focusBlock( 'chicken', { offset: -1 } ) ); + expect( dispatch ).toHaveBeenCalledWith( { + ...replaceBlocks( [ 'chicken', 'ribs' ], [ { + clientId: 'chicken', + name: 'core/test-block', + attributes: { content: 'chicken ribs' }, + } ] ), + time: expect.any( Number ), + } ); + } ); + } ); + + describe( 'validateBlocksToTemplate', () => { + let store; + beforeEach( () => { + store = createRegistry().registerStore( 'test', { + actions, + selectors, + reducer, + } ); + applyMiddlewares( store ); + + registerBlockType( 'core/test-block', defaultBlockSettings ); + } ); + + afterEach( () => { + getBlockTypes().forEach( ( block ) => { + unregisterBlockType( block.name ); + } ); + } ); + + it( 'should return undefined if no template assigned', () => { + const result = validateBlocksToTemplate( resetBlocks( [ + createBlock( 'core/test-block' ), + ] ), store ); + + expect( result ).toBe( undefined ); + } ); + + it( 'should return undefined if invalid but unlocked', () => { + store.dispatch( updateEditorSettings( { + template: [ + [ 'core/foo', {} ], + ], + } ) ); + + const result = validateBlocksToTemplate( resetBlocks( [ + createBlock( 'core/test-block' ), + ] ), store ); + + expect( result ).toBe( undefined ); + } ); + + it( 'should return undefined if locked and valid', () => { + store.dispatch( updateEditorSettings( { + template: [ + [ 'core/test-block' ], + ], + templateLock: 'all', + } ) ); + + const result = validateBlocksToTemplate( resetBlocks( [ + createBlock( 'core/test-block' ), + ] ), store ); + + expect( result ).toBe( undefined ); + } ); + + it( 'should return validity set action if invalid on default state', () => { + store.dispatch( updateEditorSettings( { + template: [ + [ 'core/foo' ], + ], + templateLock: 'all', + } ) ); + + const result = validateBlocksToTemplate( resetBlocks( [ + createBlock( 'core/test-block' ), + ] ), store ); + + expect( result ).toEqual( setTemplateValidity( false ) ); + } ); + } ); +} ); diff --git a/packages/block-editor/src/store/test/reducer.js b/packages/block-editor/src/store/test/reducer.js new file mode 100644 index 0000000000000..6f1fae9dc0f03 --- /dev/null +++ b/packages/block-editor/src/store/test/reducer.js @@ -0,0 +1,1721 @@ +/** + * External dependencies + */ +import { values, noop } from 'lodash'; +import deepFreeze from 'deep-freeze'; + +/** + * WordPress dependencies + */ +import { + registerBlockType, + unregisterBlockType, + createBlock, +} from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import { + hasSameKeys, + isUpdatingSameBlockAttribute, + blocks, + isTyping, + isCaretWithinFormattedText, + blockSelection, + preferences, + blocksMode, + insertionPoint, + template, + blockListSettings, +} from '../reducer'; + +describe( 'state', () => { + describe( 'hasSameKeys()', () => { + it( 'returns false if two objects do not have the same keys', () => { + const a = { foo: 10 }; + const b = { bar: 10 }; + + expect( hasSameKeys( a, b ) ).toBe( false ); + } ); + + it( 'returns false if two objects have the same keys', () => { + const a = { foo: 10 }; + const b = { foo: 20 }; + + expect( hasSameKeys( a, b ) ).toBe( true ); + } ); + } ); + + describe( 'isUpdatingSameBlockAttribute()', () => { + it( 'should return false if not updating block attributes', () => { + const action = { + type: 'SELECT_BLOCK', + clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', + }; + const previousAction = { + type: 'SELECT_BLOCK', + clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', + }; + + expect( isUpdatingSameBlockAttribute( action, previousAction ) ).toBe( false ); + } ); + + it( 'should return false if last action was not updating block attributes', () => { + const action = { + type: 'UPDATE_BLOCK_ATTRIBUTES', + clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', + attributes: { + foo: 10, + }, + }; + const previousAction = { + type: 'SELECT_BLOCK', + clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', + }; + + expect( isUpdatingSameBlockAttribute( action, previousAction ) ).toBe( false ); + } ); + + it( 'should return false if not updating the same block', () => { + const action = { + type: 'UPDATE_BLOCK_ATTRIBUTES', + clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', + attributes: { + foo: 10, + }, + }; + const previousAction = { + type: 'UPDATE_BLOCK_ATTRIBUTES', + clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1', + attributes: { + foo: 20, + }, + }; + + expect( isUpdatingSameBlockAttribute( action, previousAction ) ).toBe( false ); + } ); + + it( 'should return false if not updating the same block attributes', () => { + const action = { + type: 'UPDATE_BLOCK_ATTRIBUTES', + clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', + attributes: { + foo: 10, + }, + }; + const previousAction = { + type: 'UPDATE_BLOCK_ATTRIBUTES', + clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', + attributes: { + bar: 20, + }, + }; + + expect( isUpdatingSameBlockAttribute( action, previousAction ) ).toBe( false ); + } ); + + it( 'should return false if no previous action', () => { + const action = { + type: 'UPDATE_BLOCK_ATTRIBUTES', + clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', + attributes: { + foo: 10, + }, + }; + const previousAction = undefined; + + expect( isUpdatingSameBlockAttribute( action, previousAction ) ).toBe( false ); + } ); + + it( 'should return true if updating the same block attributes', () => { + const action = { + type: 'UPDATE_BLOCK_ATTRIBUTES', + clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', + attributes: { + foo: 10, + }, + }; + const previousAction = { + type: 'UPDATE_BLOCK_ATTRIBUTES', + clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', + attributes: { + foo: 20, + }, + }; + + expect( isUpdatingSameBlockAttribute( action, previousAction ) ).toBe( true ); + } ); + } ); + + describe( 'blocks()', () => { + beforeAll( () => { + registerBlockType( 'core/test-block', { + save: noop, + edit: noop, + category: 'common', + title: 'test block', + } ); + } ); + + afterAll( () => { + unregisterBlockType( 'core/test-block' ); + } ); + + it( 'should return empty byClientId, attributes, order by default', () => { + const state = blocks( undefined, {} ); + + expect( state ).toEqual( { + byClientId: {}, + attributes: {}, + order: {}, + isPersistentChange: true, + } ); + } ); + + it( 'should key by reset blocks clientId', () => { + [ + undefined, + blocks( undefined, {} ), + ].forEach( ( original ) => { + const state = blocks( original, { + type: 'RESET_BLOCKS', + blocks: [ { clientId: 'bananas', innerBlocks: [] } ], + } ); + + expect( Object.keys( state.byClientId ) ).toHaveLength( 1 ); + expect( values( state.byClientId )[ 0 ].clientId ).toBe( 'bananas' ); + expect( state.order ).toEqual( { + '': [ 'bananas' ], + bananas: [], + } ); + } ); + } ); + + it( 'should key by reset blocks clientId, including inner blocks', () => { + const original = blocks( undefined, {} ); + const state = blocks( original, { + type: 'RESET_BLOCKS', + blocks: [ { + clientId: 'bananas', + innerBlocks: [ { clientId: 'apples', innerBlocks: [] } ], + } ], + } ); + + expect( Object.keys( state.byClientId ) ).toHaveLength( 2 ); + expect( state.order ).toEqual( { + '': [ 'bananas' ], + apples: [], + bananas: [ 'apples' ], + } ); + } ); + + it( 'should insert block', () => { + const original = blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ { + clientId: 'chicken', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + } ], + } ); + const state = blocks( original, { + type: 'INSERT_BLOCKS', + blocks: [ { + clientId: 'ribs', + name: 'core/freeform', + innerBlocks: [], + } ], + } ); + + expect( Object.keys( state.byClientId ) ).toHaveLength( 2 ); + expect( values( state.byClientId )[ 1 ].clientId ).toBe( 'ribs' ); + expect( state.order ).toEqual( { + '': [ 'chicken', 'ribs' ], + chicken: [], + ribs: [], + } ); + } ); + + it( 'should replace the block', () => { + const original = blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ { + clientId: 'chicken', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + } ], + } ); + const state = blocks( original, { + type: 'REPLACE_BLOCKS', + clientIds: [ 'chicken' ], + blocks: [ { + clientId: 'wings', + name: 'core/freeform', + innerBlocks: [], + } ], + } ); + + expect( Object.keys( state.byClientId ) ).toHaveLength( 1 ); + expect( values( state.byClientId )[ 0 ].name ).toBe( 'core/freeform' ); + expect( values( state.byClientId )[ 0 ].clientId ).toBe( 'wings' ); + expect( state.order ).toEqual( { + '': [ 'wings' ], + wings: [], + } ); + } ); + + it( 'should replace the nested block', () => { + const nestedBlock = createBlock( 'core/test-block' ); + const wrapperBlock = createBlock( 'core/test-block', {}, [ nestedBlock ] ); + const replacementBlock = createBlock( 'core/test-block' ); + const original = blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ wrapperBlock ], + } ); + + const state = blocks( original, { + type: 'REPLACE_BLOCKS', + clientIds: [ nestedBlock.clientId ], + blocks: [ replacementBlock ], + } ); + + expect( state.order ).toEqual( { + '': [ wrapperBlock.clientId ], + [ wrapperBlock.clientId ]: [ replacementBlock.clientId ], + [ replacementBlock.clientId ]: [], + } ); + } ); + + it( 'should replace the block even if the new block clientId is the same', () => { + const originalState = blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ { + clientId: 'chicken', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + } ], + } ); + const replacedState = blocks( originalState, { + type: 'REPLACE_BLOCKS', + clientIds: [ 'chicken' ], + blocks: [ { + clientId: 'chicken', + name: 'core/freeform', + innerBlocks: [], + } ], + } ); + + expect( Object.keys( replacedState.byClientId ) ).toHaveLength( 1 ); + expect( values( originalState.byClientId )[ 0 ].name ).toBe( 'core/test-block' ); + expect( values( replacedState.byClientId )[ 0 ].name ).toBe( 'core/freeform' ); + expect( values( replacedState.byClientId )[ 0 ].clientId ).toBe( 'chicken' ); + expect( replacedState.order ).toEqual( { + '': [ 'chicken' ], + chicken: [], + } ); + + const nestedBlock = { + clientId: 'chicken', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }; + const wrapperBlock = createBlock( 'core/test-block', {}, [ nestedBlock ] ); + const replacementNestedBlock = { + clientId: 'chicken', + name: 'core/freeform', + attributes: {}, + innerBlocks: [], + }; + + const originalNestedState = blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ wrapperBlock ], + } ); + + const replacedNestedState = blocks( originalNestedState, { + type: 'REPLACE_BLOCKS', + clientIds: [ nestedBlock.clientId ], + blocks: [ replacementNestedBlock ], + } ); + + expect( replacedNestedState.order ).toEqual( { + '': [ wrapperBlock.clientId ], + [ wrapperBlock.clientId ]: [ replacementNestedBlock.clientId ], + [ replacementNestedBlock.clientId ]: [], + } ); + + expect( originalNestedState.byClientId.chicken.name ).toBe( 'core/test-block' ); + expect( replacedNestedState.byClientId.chicken.name ).toBe( 'core/freeform' ); + } ); + + it( 'should update the block', () => { + const original = blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ { + clientId: 'chicken', + name: 'core/test-block', + attributes: {}, + isValid: false, + innerBlocks: [], + } ], + } ); + const state = blocks( deepFreeze( original ), { + type: 'UPDATE_BLOCK', + clientId: 'chicken', + updates: { + attributes: { content: 'ribs' }, + isValid: true, + }, + } ); + + expect( state.byClientId.chicken ).toEqual( { + clientId: 'chicken', + name: 'core/test-block', + isValid: true, + } ); + + expect( state.attributes.chicken ).toEqual( { + content: 'ribs', + } ); + } ); + + it( 'should update the reusable block reference if the temporary id is swapped', () => { + const original = blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ { + clientId: 'chicken', + name: 'core/block', + attributes: { + ref: 'random-clientId', + }, + isValid: false, + innerBlocks: [], + } ], + } ); + + const state = blocks( deepFreeze( original ), { + type: 'SAVE_REUSABLE_BLOCK_SUCCESS', + id: 'random-clientId', + updatedId: 3, + } ); + + expect( state.byClientId.chicken ).toEqual( { + clientId: 'chicken', + name: 'core/block', + isValid: false, + } ); + + expect( state.attributes.chicken ).toEqual( { + ref: 3, + } ); + } ); + + it( 'should move the block up', () => { + const original = blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ { + clientId: 'chicken', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, { + clientId: 'ribs', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + } ], + } ); + const state = blocks( original, { + type: 'MOVE_BLOCKS_UP', + clientIds: [ 'ribs' ], + } ); + + expect( state.order[ '' ] ).toEqual( [ 'ribs', 'chicken' ] ); + } ); + + it( 'should move the nested block up', () => { + const movedBlock = createBlock( 'core/test-block' ); + const siblingBlock = createBlock( 'core/test-block' ); + const wrapperBlock = createBlock( 'core/test-block', {}, [ siblingBlock, movedBlock ] ); + const original = blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ wrapperBlock ], + } ); + const state = blocks( original, { + type: 'MOVE_BLOCKS_UP', + clientIds: [ movedBlock.clientId ], + rootClientId: wrapperBlock.clientId, + } ); + + expect( state.order ).toEqual( { + '': [ wrapperBlock.clientId ], + [ wrapperBlock.clientId ]: [ movedBlock.clientId, siblingBlock.clientId ], + [ movedBlock.clientId ]: [], + [ siblingBlock.clientId ]: [], + } ); + } ); + + it( 'should move multiple blocks up', () => { + const original = blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ { + clientId: 'chicken', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, { + clientId: 'ribs', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, { + clientId: 'veggies', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + } ], + } ); + const state = blocks( original, { + type: 'MOVE_BLOCKS_UP', + clientIds: [ 'ribs', 'veggies' ], + } ); + + expect( state.order[ '' ] ).toEqual( [ 'ribs', 'veggies', 'chicken' ] ); + } ); + + it( 'should move multiple nested blocks up', () => { + const movedBlockA = createBlock( 'core/test-block' ); + const movedBlockB = createBlock( 'core/test-block' ); + const siblingBlock = createBlock( 'core/test-block' ); + const wrapperBlock = createBlock( 'core/test-block', {}, [ siblingBlock, movedBlockA, movedBlockB ] ); + const original = blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ wrapperBlock ], + } ); + const state = blocks( original, { + type: 'MOVE_BLOCKS_UP', + clientIds: [ movedBlockA.clientId, movedBlockB.clientId ], + rootClientId: wrapperBlock.clientId, + } ); + + expect( state.order ).toEqual( { + '': [ wrapperBlock.clientId ], + [ wrapperBlock.clientId ]: [ movedBlockA.clientId, movedBlockB.clientId, siblingBlock.clientId ], + [ movedBlockA.clientId ]: [], + [ movedBlockB.clientId ]: [], + [ siblingBlock.clientId ]: [], + } ); + } ); + + it( 'should not move the first block up', () => { + const original = blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ { + clientId: 'chicken', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, { + clientId: 'ribs', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + } ], + } ); + const state = blocks( original, { + type: 'MOVE_BLOCKS_UP', + clientIds: [ 'chicken' ], + } ); + + expect( state.order ).toBe( original.order ); + } ); + + it( 'should move the block down', () => { + const original = blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ { + clientId: 'chicken', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, { + clientId: 'ribs', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + } ], + } ); + const state = blocks( original, { + type: 'MOVE_BLOCKS_DOWN', + clientIds: [ 'chicken' ], + } ); + + expect( state.order[ '' ] ).toEqual( [ 'ribs', 'chicken' ] ); + } ); + + it( 'should move the nested block down', () => { + const movedBlock = createBlock( 'core/test-block' ); + const siblingBlock = createBlock( 'core/test-block' ); + const wrapperBlock = createBlock( 'core/test-block', {}, [ movedBlock, siblingBlock ] ); + const original = blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ wrapperBlock ], + } ); + const state = blocks( original, { + type: 'MOVE_BLOCKS_DOWN', + clientIds: [ movedBlock.clientId ], + rootClientId: wrapperBlock.clientId, + } ); + + expect( state.order ).toEqual( { + '': [ wrapperBlock.clientId ], + [ wrapperBlock.clientId ]: [ siblingBlock.clientId, movedBlock.clientId ], + [ movedBlock.clientId ]: [], + [ siblingBlock.clientId ]: [], + } ); + } ); + + it( 'should move multiple blocks down', () => { + const original = blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ { + clientId: 'chicken', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, { + clientId: 'ribs', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, { + clientId: 'veggies', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + } ], + } ); + const state = blocks( original, { + type: 'MOVE_BLOCKS_DOWN', + clientIds: [ 'chicken', 'ribs' ], + } ); + + expect( state.order[ '' ] ).toEqual( [ 'veggies', 'chicken', 'ribs' ] ); + } ); + + it( 'should move multiple nested blocks down', () => { + const movedBlockA = createBlock( 'core/test-block' ); + const movedBlockB = createBlock( 'core/test-block' ); + const siblingBlock = createBlock( 'core/test-block' ); + const wrapperBlock = createBlock( 'core/test-block', {}, [ movedBlockA, movedBlockB, siblingBlock ] ); + const original = blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ wrapperBlock ], + } ); + const state = blocks( original, { + type: 'MOVE_BLOCKS_DOWN', + clientIds: [ movedBlockA.clientId, movedBlockB.clientId ], + rootClientId: wrapperBlock.clientId, + } ); + + expect( state.order ).toEqual( { + '': [ wrapperBlock.clientId ], + [ wrapperBlock.clientId ]: [ siblingBlock.clientId, movedBlockA.clientId, movedBlockB.clientId ], + [ movedBlockA.clientId ]: [], + [ movedBlockB.clientId ]: [], + [ siblingBlock.clientId ]: [], + } ); + } ); + + it( 'should not move the last block down', () => { + const original = blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ { + clientId: 'chicken', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, { + clientId: 'ribs', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + } ], + } ); + const state = blocks( original, { + type: 'MOVE_BLOCKS_DOWN', + clientIds: [ 'ribs' ], + } ); + + expect( state.order ).toBe( original.order ); + } ); + + it( 'should remove the block', () => { + const original = blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ { + clientId: 'chicken', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, { + clientId: 'ribs', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + } ], + } ); + const state = blocks( original, { + type: 'REMOVE_BLOCKS', + clientIds: [ 'chicken' ], + } ); + + expect( state.order[ '' ] ).toEqual( [ 'ribs' ] ); + expect( state.order ).not.toHaveProperty( 'chicken' ); + expect( state.byClientId ).toEqual( { + ribs: { + clientId: 'ribs', + name: 'core/test-block', + }, + } ); + expect( state.attributes ).toEqual( { + ribs: {}, + } ); + } ); + + it( 'should remove multiple blocks', () => { + const original = blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ { + clientId: 'chicken', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, { + clientId: 'ribs', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, { + clientId: 'veggies', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + } ], + } ); + const state = blocks( original, { + type: 'REMOVE_BLOCKS', + clientIds: [ 'chicken', 'veggies' ], + } ); + + expect( state.order[ '' ] ).toEqual( [ 'ribs' ] ); + expect( state.order ).not.toHaveProperty( 'chicken' ); + expect( state.order ).not.toHaveProperty( 'veggies' ); + expect( state.byClientId ).toEqual( { + ribs: { + clientId: 'ribs', + name: 'core/test-block', + }, + } ); + expect( state.attributes ).toEqual( { + ribs: {}, + } ); + } ); + + it( 'should cascade remove to include inner blocks', () => { + const block = createBlock( 'core/test-block', {}, [ + createBlock( 'core/test-block', {}, [ + createBlock( 'core/test-block' ), + ] ), + ] ); + + const original = blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ block ], + } ); + + const state = blocks( original, { + type: 'REMOVE_BLOCKS', + clientIds: [ block.clientId ], + } ); + + expect( state.byClientId ).toEqual( {} ); + expect( state.order ).toEqual( { + '': [], + } ); + } ); + + it( 'should insert at the specified index', () => { + const original = blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ { + clientId: 'kumquat', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, { + clientId: 'loquat', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + } ], + } ); + + const state = blocks( original, { + type: 'INSERT_BLOCKS', + index: 1, + blocks: [ { + clientId: 'persimmon', + name: 'core/freeform', + innerBlocks: [], + } ], + } ); + + expect( Object.keys( state.byClientId ) ).toHaveLength( 3 ); + expect( state.order[ '' ] ).toEqual( [ 'kumquat', 'persimmon', 'loquat' ] ); + } ); + + it( 'should move block to lower index', () => { + const original = blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ { + clientId: 'chicken', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, { + clientId: 'ribs', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, { + clientId: 'veggies', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + } ], + } ); + const state = blocks( original, { + type: 'MOVE_BLOCK_TO_POSITION', + clientId: 'ribs', + index: 0, + } ); + + expect( state.order[ '' ] ).toEqual( [ 'ribs', 'chicken', 'veggies' ] ); + } ); + + it( 'should move block to higher index', () => { + const original = blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ { + clientId: 'chicken', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, { + clientId: 'ribs', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, { + clientId: 'veggies', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + } ], + } ); + const state = blocks( original, { + type: 'MOVE_BLOCK_TO_POSITION', + clientId: 'ribs', + index: 2, + } ); + + expect( state.order[ '' ] ).toEqual( [ 'chicken', 'veggies', 'ribs' ] ); + } ); + + it( 'should not move block if passed same index', () => { + const original = blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ { + clientId: 'chicken', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, { + clientId: 'ribs', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, { + clientId: 'veggies', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + } ], + } ); + const state = blocks( original, { + type: 'MOVE_BLOCK_TO_POSITION', + clientId: 'ribs', + index: 1, + } ); + + expect( state.order[ '' ] ).toEqual( [ 'chicken', 'ribs', 'veggies' ] ); + } ); + + describe( 'blocks', () => { + it( 'should not reset any blocks that are not in the post', () => { + const actions = [ + { + type: 'RESET_BLOCKS', + blocks: [ + { + clientId: 'block1', + innerBlocks: [ + { clientId: 'block11', innerBlocks: [] }, + { clientId: 'block12', innerBlocks: [] }, + ], + }, + ], + }, + { + type: 'RECEIVE_BLOCKS', + blocks: [ + { + clientId: 'block2', + innerBlocks: [ + { clientId: 'block21', innerBlocks: [] }, + { clientId: 'block22', innerBlocks: [] }, + ], + }, + ], + }, + ]; + const original = deepFreeze( actions.reduce( blocks, undefined ) ); + + const state = blocks( original, { + type: 'RESET_BLOCKS', + blocks: [ + { + clientId: 'block3', + innerBlocks: [ + { clientId: 'block31', innerBlocks: [] }, + { clientId: 'block32', innerBlocks: [] }, + ], + }, + ], + } ); + + expect( state.byClientId ).toEqual( { + block2: { clientId: 'block2' }, + block21: { clientId: 'block21' }, + block22: { clientId: 'block22' }, + block3: { clientId: 'block3' }, + block31: { clientId: 'block31' }, + block32: { clientId: 'block32' }, + } ); + } ); + + describe( 'byClientId', () => { + it( 'should ignore updates to non-existent block', () => { + const original = deepFreeze( blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [], + } ) ); + const state = blocks( original, { + type: 'UPDATE_BLOCK_ATTRIBUTES', + clientId: 'kumquat', + attributes: { + updated: true, + }, + } ); + + expect( state.byClientId ).toBe( original.byClientId ); + } ); + + it( 'should return with same reference if no changes in updates', () => { + const original = deepFreeze( blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ { + clientId: 'kumquat', + attributes: { + updated: true, + }, + innerBlocks: [], + } ], + } ) ); + const state = blocks( original, { + type: 'UPDATE_BLOCK_ATTRIBUTES', + clientId: 'kumquat', + attributes: { + updated: true, + }, + } ); + + expect( state.byClientId ).toBe( state.byClientId ); + } ); + } ); + + describe( 'attributes', () => { + it( 'should return with attribute block updates', () => { + const original = deepFreeze( blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ { + clientId: 'kumquat', + attributes: {}, + innerBlocks: [], + } ], + } ) ); + const state = blocks( original, { + type: 'UPDATE_BLOCK_ATTRIBUTES', + clientId: 'kumquat', + attributes: { + updated: true, + }, + } ); + + expect( state.attributes.kumquat.updated ).toBe( true ); + } ); + + it( 'should accumulate attribute block updates', () => { + const original = deepFreeze( blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ { + clientId: 'kumquat', + attributes: { + updated: true, + }, + innerBlocks: [], + } ], + } ) ); + const state = blocks( original, { + type: 'UPDATE_BLOCK_ATTRIBUTES', + clientId: 'kumquat', + attributes: { + moreUpdated: true, + }, + } ); + + expect( state.attributes.kumquat ).toEqual( { + updated: true, + moreUpdated: true, + } ); + } ); + + it( 'should ignore updates to non-existent block', () => { + const original = deepFreeze( blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [], + } ) ); + const state = blocks( original, { + type: 'UPDATE_BLOCK_ATTRIBUTES', + clientId: 'kumquat', + attributes: { + updated: true, + }, + } ); + + expect( state.attributes ).toBe( original.attributes ); + } ); + + it( 'should return with same reference if no changes in updates', () => { + const original = deepFreeze( blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ { + clientId: 'kumquat', + attributes: { + updated: true, + }, + innerBlocks: [], + } ], + } ) ); + const state = blocks( original, { + type: 'UPDATE_BLOCK_ATTRIBUTES', + clientId: 'kumquat', + attributes: { + updated: true, + }, + } ); + + expect( state.attributes ).toBe( state.attributes ); + } ); + } ); + + describe( 'isPersistentChange', () => { + it( 'should consider any non-exempt block change as persistent', () => { + const original = deepFreeze( blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [], + } ) ); + + const state = blocks( original, { + type: 'UPDATE_BLOCK_ATTRIBUTES', + clientId: 'kumquat', + attributes: { + updated: true, + }, + } ); + + expect( state.isPersistentChange ).toBe( true ); + } ); + + it( 'should consider same block attribute update as exempt', () => { + const original = deepFreeze( blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ { + clientId: 'kumquat', + attributes: {}, + innerBlocks: [], + } ], + } ) ); + let state = blocks( original, { + type: 'UPDATE_BLOCK_ATTRIBUTES', + clientId: 'kumquat', + attributes: { + updated: false, + }, + } ); + + state = blocks( state, { + type: 'UPDATE_BLOCK_ATTRIBUTES', + clientId: 'kumquat', + attributes: { + updated: true, + }, + } ); + + expect( state.isPersistentChange ).toBe( false ); + } ); + } ); + } ); + } ); + + describe( 'insertionPoint', () => { + it( 'should default to null', () => { + const state = insertionPoint( undefined, {} ); + + expect( state ).toBe( null ); + } ); + + it( 'should set insertion point', () => { + const state = insertionPoint( null, { + type: 'SHOW_INSERTION_POINT', + rootClientId: 'clientId1', + index: 0, + } ); + + expect( state ).toEqual( { + rootClientId: 'clientId1', + index: 0, + } ); + } ); + + it( 'should clear the insertion point', () => { + const original = deepFreeze( { + rootClientId: 'clientId1', + index: 0, + } ); + const state = insertionPoint( original, { + type: 'HIDE_INSERTION_POINT', + } ); + + expect( state ).toBe( null ); + } ); + } ); + + describe( 'isTyping()', () => { + it( 'should set the typing flag to true', () => { + const state = isTyping( false, { + type: 'START_TYPING', + } ); + + expect( state ).toBe( true ); + } ); + + it( 'should set the typing flag to false', () => { + const state = isTyping( false, { + type: 'STOP_TYPING', + } ); + + expect( state ).toBe( false ); + } ); + } ); + + describe( 'isCaretWithinFormattedText()', () => { + it( 'should set the flag to true', () => { + const state = isCaretWithinFormattedText( false, { + type: 'ENTER_FORMATTED_TEXT', + } ); + + expect( state ).toBe( true ); + } ); + + it( 'should set the flag to false', () => { + const state = isCaretWithinFormattedText( true, { + type: 'EXIT_FORMATTED_TEXT', + } ); + + expect( state ).toBe( false ); + } ); + } ); + + describe( 'blockSelection()', () => { + it( 'should return with block clientId as selected', () => { + const state = blockSelection( undefined, { + type: 'SELECT_BLOCK', + clientId: 'kumquat', + initialPosition: -1, + } ); + + expect( state ).toEqual( { + start: 'kumquat', + end: 'kumquat', + initialPosition: -1, + isMultiSelecting: false, + isEnabled: true, + } ); + } ); + + it( 'should set multi selection', () => { + const original = deepFreeze( { isMultiSelecting: false } ); + const state = blockSelection( original, { + type: 'MULTI_SELECT', + start: 'ribs', + end: 'chicken', + } ); + + expect( state ).toEqual( { + start: 'ribs', + end: 'chicken', + initialPosition: null, + isMultiSelecting: false, + } ); + } ); + + it( 'should set continuous multi selection', () => { + const original = deepFreeze( { isMultiSelecting: true } ); + const state = blockSelection( original, { + type: 'MULTI_SELECT', + start: 'ribs', + end: 'chicken', + } ); + + expect( state ).toEqual( { + start: 'ribs', + end: 'chicken', + initialPosition: null, + isMultiSelecting: true, + } ); + } ); + + it( 'should start multi selection', () => { + const original = deepFreeze( { start: 'ribs', end: 'ribs', isMultiSelecting: false } ); + const state = blockSelection( original, { + type: 'START_MULTI_SELECT', + } ); + + expect( state ).toEqual( { + start: 'ribs', + end: 'ribs', + initialPosition: null, + isMultiSelecting: true, + } ); + } ); + + it( 'should return same reference if already multi-selecting', () => { + const original = deepFreeze( { start: 'ribs', end: 'ribs', isMultiSelecting: true } ); + const state = blockSelection( original, { + type: 'START_MULTI_SELECT', + } ); + + expect( state ).toBe( original ); + } ); + + it( 'should end multi selection with selection', () => { + const original = deepFreeze( { start: 'ribs', end: 'chicken', isMultiSelecting: true } ); + const state = blockSelection( original, { + type: 'STOP_MULTI_SELECT', + } ); + + expect( state ).toEqual( { + start: 'ribs', + end: 'chicken', + initialPosition: null, + isMultiSelecting: false, + } ); + } ); + + it( 'should return same reference if already ended multi-selecting', () => { + const original = deepFreeze( { start: 'ribs', end: 'chicken', isMultiSelecting: false } ); + const state = blockSelection( original, { + type: 'STOP_MULTI_SELECT', + } ); + + expect( state ).toBe( original ); + } ); + + it( 'should end multi selection without selection', () => { + const original = deepFreeze( { start: 'ribs', end: 'ribs', isMultiSelecting: true } ); + const state = blockSelection( original, { + type: 'STOP_MULTI_SELECT', + } ); + + expect( state ).toEqual( { + start: 'ribs', + end: 'ribs', + initialPosition: null, + isMultiSelecting: false, + } ); + } ); + + it( 'should not update the state if the block is already selected', () => { + const original = deepFreeze( { start: 'ribs', end: 'ribs' } ); + + const state1 = blockSelection( original, { + type: 'SELECT_BLOCK', + clientId: 'ribs', + } ); + + expect( state1 ).toBe( original ); + } ); + + it( 'should unset multi selection', () => { + const original = deepFreeze( { start: 'ribs', end: 'chicken' } ); + + const state1 = blockSelection( original, { + type: 'CLEAR_SELECTED_BLOCK', + } ); + + expect( state1 ).toEqual( { + start: null, + end: null, + initialPosition: null, + isMultiSelecting: false, + } ); + } ); + + it( 'should return same reference if clearing selection but no selection', () => { + const original = deepFreeze( { start: null, end: null, isMultiSelecting: false } ); + + const state1 = blockSelection( original, { + type: 'CLEAR_SELECTED_BLOCK', + } ); + + expect( state1 ).toBe( original ); + } ); + + it( 'should select inserted block', () => { + const original = deepFreeze( { start: 'ribs', end: 'chicken' } ); + + const state3 = blockSelection( original, { + type: 'INSERT_BLOCKS', + blocks: [ { + clientId: 'ribs', + name: 'core/freeform', + } ], + updateSelection: true, + } ); + + expect( state3 ).toEqual( { + start: 'ribs', + end: 'ribs', + initialPosition: null, + isMultiSelecting: false, + } ); + } ); + + it( 'should not select inserted block if updateSelection flag is false', () => { + const original = deepFreeze( { start: 'a', end: 'b' } ); + + const state3 = blockSelection( original, { + type: 'INSERT_BLOCKS', + blocks: [ { + clientId: 'ribs', + name: 'core/freeform', + } ], + updateSelection: false, + } ); + + expect( state3 ).toEqual( { + start: 'a', + end: 'b', + } ); + } ); + + it( 'should not update the state if the block moved is already selected', () => { + const original = deepFreeze( { start: 'ribs', end: 'ribs' } ); + const state = blockSelection( original, { + type: 'MOVE_BLOCKS_UP', + clientIds: [ 'ribs' ], + } ); + + expect( state ).toBe( original ); + } ); + + it( 'should replace the selected block', () => { + const original = deepFreeze( { start: 'chicken', end: 'chicken' } ); + const state = blockSelection( original, { + type: 'REPLACE_BLOCKS', + clientIds: [ 'chicken' ], + blocks: [ { + clientId: 'wings', + name: 'core/freeform', + } ], + } ); + + expect( state ).toEqual( { + start: 'wings', + end: 'wings', + initialPosition: null, + isMultiSelecting: false, + } ); + } ); + + it( 'should not replace the selected block if we keep it at the end when replacing blocks', () => { + const original = deepFreeze( { start: 'wings', end: 'wings' } ); + const state = blockSelection( original, { + type: 'REPLACE_BLOCKS', + clientIds: [ 'wings' ], + blocks: [ + { + clientId: 'chicken', + name: 'core/freeform', + }, + { + clientId: 'wings', + name: 'core/freeform', + } ], + } ); + + expect( state ).toBe( original ); + } ); + + it( 'should replace the selected block if we keep it not at the end when replacing blocks', () => { + const original = deepFreeze( { start: 'chicken', end: 'chicken' } ); + const state = blockSelection( original, { + type: 'REPLACE_BLOCKS', + clientIds: [ 'chicken' ], + blocks: [ + { + clientId: 'chicken', + name: 'core/freeform', + }, + { + clientId: 'wings', + name: 'core/freeform', + } ], + } ); + + expect( state ).toEqual( { + start: 'wings', + end: 'wings', + initialPosition: null, + isMultiSelecting: false, + } ); + } ); + + it( 'should reset if replacing with empty set', () => { + const original = deepFreeze( { start: 'chicken', end: 'chicken' } ); + const state = blockSelection( original, { + type: 'REPLACE_BLOCKS', + clientIds: [ 'chicken' ], + blocks: [], + } ); + + expect( state ).toEqual( { + start: null, + end: null, + initialPosition: null, + isMultiSelecting: false, + } ); + } ); + + it( 'should keep the selected block', () => { + const original = deepFreeze( { start: 'chicken', end: 'chicken' } ); + const state = blockSelection( original, { + type: 'REPLACE_BLOCKS', + clientIds: [ 'ribs' ], + blocks: [ { + clientId: 'wings', + name: 'core/freeform', + } ], + } ); + + expect( state ).toBe( original ); + } ); + + it( 'should remove the selection if we are removing the selected block', () => { + const original = deepFreeze( { + start: 'chicken', + end: 'chicken', + initialPosition: null, + isMultiSelecting: false, + } ); + const state = blockSelection( original, { + type: 'REMOVE_BLOCKS', + clientIds: [ 'chicken' ], + } ); + + expect( state ).toEqual( { + start: null, + end: null, + initialPosition: null, + isMultiSelecting: false, + } ); + } ); + + it( 'should keep the selection if we are not removing the selected block', () => { + const original = deepFreeze( { + start: 'chicken', + end: 'chicken', + initialPosition: null, + isMultiSelecting: false, + } ); + const state = blockSelection( original, { + type: 'REMOVE_BLOCKS', + clientIds: [ 'ribs' ], + } ); + + expect( state ).toBe( original ); + } ); + } ); + + describe( 'preferences()', () => { + it( 'should apply all defaults', () => { + const state = preferences( undefined, {} ); + + expect( state ).toEqual( { + insertUsage: {}, + } ); + } ); + it( 'should record recently used blocks', () => { + const state = preferences( deepFreeze( { insertUsage: {} } ), { + type: 'INSERT_BLOCKS', + blocks: [ { + clientId: 'bacon', + name: 'core-embed/twitter', + } ], + time: 123456, + } ); + + expect( state ).toEqual( { + insertUsage: { + 'core-embed/twitter': { + time: 123456, + count: 1, + insert: { name: 'core-embed/twitter' }, + }, + }, + } ); + + const twoRecentBlocks = preferences( deepFreeze( { + insertUsage: { + 'core-embed/twitter': { + time: 123456, + count: 1, + insert: { name: 'core-embed/twitter' }, + }, + }, + } ), { + type: 'INSERT_BLOCKS', + blocks: [ { + clientId: 'eggs', + name: 'core-embed/twitter', + }, { + clientId: 'bacon', + name: 'core/block', + attributes: { ref: 123 }, + } ], + time: 123457, + } ); + + expect( twoRecentBlocks ).toEqual( { + insertUsage: { + 'core-embed/twitter': { + time: 123457, + count: 2, + insert: { name: 'core-embed/twitter' }, + }, + 'core/block/123': { + time: 123457, + count: 1, + insert: { name: 'core/block', ref: 123 }, + }, + }, + } ); + } ); + } ); + + describe( 'blocksMode', () => { + it( 'should set mode to html if not set', () => { + const action = { + type: 'TOGGLE_BLOCK_MODE', + clientId: 'chicken', + }; + const value = blocksMode( deepFreeze( {} ), action ); + + expect( value ).toEqual( { chicken: 'html' } ); + } ); + + it( 'should toggle mode to visual if set as html', () => { + const action = { + type: 'TOGGLE_BLOCK_MODE', + clientId: 'chicken', + }; + const value = blocksMode( deepFreeze( { chicken: 'html' } ), action ); + + expect( value ).toEqual( { chicken: 'visual' } ); + } ); + } ); + + describe( 'template', () => { + it( 'should default to visible', () => { + const state = template( undefined, {} ); + + expect( state ).toEqual( { isValid: true } ); + } ); + + it( 'should reset the validity flag', () => { + const original = deepFreeze( { isValid: false, template: [] } ); + const state = template( original, { + type: 'SET_TEMPLATE_VALIDITY', + isValid: true, + } ); + + expect( state ).toEqual( { isValid: true, template: [] } ); + } ); + } ); + + describe( 'blockListSettings', () => { + it( 'should add new settings', () => { + const original = deepFreeze( {} ); + + const state = blockListSettings( original, { + type: 'UPDATE_BLOCK_LIST_SETTINGS', + clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', + settings: { + allowedBlocks: [ 'core/paragraph' ], + }, + } ); + + expect( state ).toEqual( { + '9db792c6-a25a-495d-adbd-97d56a4c4189': { + allowedBlocks: [ 'core/paragraph' ], + }, + } ); + } ); + + it( 'should return same reference if updated as the same', () => { + const original = deepFreeze( { + '9db792c6-a25a-495d-adbd-97d56a4c4189': { + allowedBlocks: [ 'core/paragraph' ], + }, + } ); + + const state = blockListSettings( original, { + type: 'UPDATE_BLOCK_LIST_SETTINGS', + clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', + settings: { + allowedBlocks: [ 'core/paragraph' ], + }, + } ); + + expect( state ).toBe( original ); + } ); + + it( 'should return same reference if updated settings not assigned and id not exists', () => { + const original = deepFreeze( {} ); + + const state = blockListSettings( original, { + type: 'UPDATE_BLOCK_LIST_SETTINGS', + clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', + } ); + + expect( state ).toBe( original ); + } ); + + it( 'should update the settings of a block', () => { + const original = deepFreeze( { + '9db792c6-a25a-495d-adbd-97d56a4c4189': { + allowedBlocks: [ 'core/paragraph' ], + }, + 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': { + allowedBlocks: true, + }, + } ); + + const state = blockListSettings( original, { + type: 'UPDATE_BLOCK_LIST_SETTINGS', + clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', + settings: { + allowedBlocks: [ 'core/list' ], + }, + } ); + + expect( state ).toEqual( { + '9db792c6-a25a-495d-adbd-97d56a4c4189': { + allowedBlocks: [ 'core/list' ], + }, + 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': { + allowedBlocks: true, + }, + } ); + } ); + + it( 'should remove existing settings if updated settings not assigned', () => { + const original = deepFreeze( { + '9db792c6-a25a-495d-adbd-97d56a4c4189': { + allowedBlocks: [ 'core/paragraph' ], + }, + } ); + + const state = blockListSettings( original, { + type: 'UPDATE_BLOCK_LIST_SETTINGS', + clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', + } ); + + expect( state ).toEqual( {} ); + } ); + + it( 'should remove the settings of a block when it is replaced', () => { + const original = deepFreeze( { + '9db792c6-a25a-495d-adbd-97d56a4c4189': { + allowedBlocks: [ 'core/paragraph' ], + }, + 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': { + allowedBlocks: true, + }, + } ); + + const state = blockListSettings( original, { + type: 'REPLACE_BLOCKS', + clientIds: [ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1' ], + } ); + + expect( state ).toEqual( { + '9db792c6-a25a-495d-adbd-97d56a4c4189': { + allowedBlocks: [ 'core/paragraph' ], + }, + } ); + } ); + + it( 'should remove the settings of a block when it is removed', () => { + const original = deepFreeze( { + 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': { + allowedBlocks: true, + }, + } ); + + const state = blockListSettings( original, { + type: 'REMOVE_BLOCKS', + clientIds: [ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1' ], + } ); + + expect( state ).toEqual( {} ); + } ); + } ); +} ); diff --git a/packages/block-editor/src/store/test/selectors.js b/packages/block-editor/src/store/test/selectors.js new file mode 100644 index 0000000000000..bcda13ad755ff --- /dev/null +++ b/packages/block-editor/src/store/test/selectors.js @@ -0,0 +1,2321 @@ +/** + * External dependencies + */ +import { filter } from 'lodash'; + +/** + * WordPress dependencies + */ +import { + registerBlockType, + unregisterBlockType, + setFreeformContentHandlerName, +} from '@wordpress/blocks'; +import { RawHTML } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import * as selectors from '../selectors'; + +const { + getBlockDependantsCacheBust, + getBlockName, + getBlock, + getBlocks, + getBlockCount, + getClientIdsWithDescendants, + getClientIdsOfDescendants, + hasSelectedBlock, + getSelectedBlock, + getSelectedBlockClientId, + getBlockRootClientId, + getBlockHierarchyRootClientId, + getGlobalBlockCount, + getMultiSelectedBlockClientIds, + getMultiSelectedBlocks, + getMultiSelectedBlocksStartClientId, + getMultiSelectedBlocksEndClientId, + getBlockOrder, + getBlockIndex, + getPreviousBlockClientId, + getNextBlockClientId, + isBlockSelected, + hasSelectedInnerBlock, + isBlockWithinSelection, + hasMultiSelection, + isBlockMultiSelected, + isFirstMultiSelectedBlock, + getBlockMode, + isTyping, + isCaretWithinFormattedText, + getBlockInsertionPoint, + isBlockInsertionPointVisible, + isSelectionEnabled, + canInsertBlockType, + getInserterItems, + isValidTemplate, + getTemplate, + getTemplateLock, + getBlockListSettings, + INSERTER_UTILITY_HIGH, + INSERTER_UTILITY_MEDIUM, + INSERTER_UTILITY_LOW, +} = selectors; + +describe( 'selectors', () => { + let cachedSelectors; + + beforeAll( () => { + cachedSelectors = filter( selectors, ( selector ) => selector.clear ); + } ); + + beforeEach( () => { + registerBlockType( 'core/block', { + save: () => null, + category: 'reusable', + title: 'Reusable Block Stub', + supports: { + inserter: false, + }, + } ); + + registerBlockType( 'core/test-block-a', { + save: ( props ) => props.attributes.text, + category: 'formatting', + title: 'Test Block A', + icon: 'test', + keywords: [ 'testing' ], + } ); + + registerBlockType( 'core/test-block-b', { + save: ( props ) => props.attributes.text, + category: 'common', + title: 'Test Block B', + icon: 'test', + keywords: [ 'testing' ], + supports: { + multiple: false, + }, + } ); + + registerBlockType( 'core/test-block-c', { + save: ( props ) => props.attributes.text, + category: 'common', + title: 'Test Block C', + icon: 'test', + keywords: [ 'testing' ], + parent: [ 'core/test-block-b' ], + } ); + + registerBlockType( 'core/test-freeform', { + save: ( props ) => <RawHTML>{ props.attributes.content }</RawHTML>, + category: 'common', + title: 'Test Freeform Content Handler', + icon: 'test', + attributes: { + content: { + type: 'string', + }, + }, + } ); + + setFreeformContentHandlerName( 'core/test-freeform' ); + + cachedSelectors.forEach( ( { clear } ) => clear() ); + } ); + + afterEach( () => { + unregisterBlockType( 'core/block' ); + unregisterBlockType( 'core/test-block-a' ); + unregisterBlockType( 'core/test-block-b' ); + unregisterBlockType( 'core/test-block-c' ); + unregisterBlockType( 'core/test-freeform' ); + + setFreeformContentHandlerName( undefined ); + } ); + + describe( 'getBlockDependantsCacheBust', () => { + const rootBlock = { clientId: 123, name: 'core/paragraph' }; + const rootBlockAttributes = {}; + const rootOrder = [ 123 ]; + + it( 'returns an unchanging reference', () => { + const rootBlockOrder = []; + + const state = { + blocks: { + byClientId: { + 123: rootBlock, + }, + attributes: { + 123: rootBlockAttributes, + }, + order: { + '': rootOrder, + 123: rootBlockOrder, + }, + }, + }; + + const nextState = { + blocks: { + byClientId: { + 123: rootBlock, + }, + attributes: { + 123: rootBlockAttributes, + }, + order: { + '': rootOrder, + 123: rootBlockOrder, + }, + }, + }; + + expect( + getBlockDependantsCacheBust( state, 123 ) + ).toBe( getBlockDependantsCacheBust( nextState, 123 ) ); + } ); + + it( 'returns a new reference on added inner block', () => { + const state = { + blocks: { + byClientId: { + 123: rootBlock, + }, + attributes: { + 123: rootBlockAttributes, + }, + order: { + '': rootOrder, + 123: [], + }, + }, + }; + + const nextState = { + blocks: { + byClientId: { + 123: rootBlock, + 456: { clientId: 456, name: 'core/paragraph' }, + }, + attributes: { + 123: rootBlockAttributes, + 456: {}, + }, + order: { + '': rootOrder, + 123: [ 456 ], + 456: [], + }, + }, + }; + + expect( + getBlockDependantsCacheBust( state, 123 ) + ).not.toBe( getBlockDependantsCacheBust( nextState, 123 ) ); + } ); + + it( 'returns an unchanging reference on unchanging inner block', () => { + const rootBlockOrder = [ 456 ]; + const childBlock = { clientId: 456, name: 'core/paragraph' }; + const childBlockAttributes = {}; + const childBlockOrder = []; + + const state = { + blocks: { + byClientId: { + 123: rootBlock, + 456: childBlock, + }, + attributes: { + 123: rootBlockAttributes, + 456: childBlockAttributes, + }, + order: { + '': rootOrder, + 123: rootBlockOrder, + 456: childBlockOrder, + }, + }, + }; + + const nextState = { + blocks: { + byClientId: { + 123: rootBlock, + 456: childBlock, + }, + attributes: { + 123: rootBlockAttributes, + 456: childBlockAttributes, + }, + order: { + '': rootOrder, + 123: rootBlockOrder, + 456: childBlockOrder, + }, + }, + }; + + expect( + getBlockDependantsCacheBust( state, 123 ) + ).toBe( getBlockDependantsCacheBust( nextState, 123 ) ); + } ); + + it( 'returns a new reference on updated inner block', () => { + const rootBlockOrder = [ 456 ]; + const childBlockOrder = []; + + const state = { + blocks: { + byClientId: { + 123: rootBlock, + 456: { clientId: 456, name: 'core/paragraph' }, + }, + attributes: { + 123: rootBlockAttributes, + 456: {}, + }, + order: { + '': rootOrder, + 123: rootBlockOrder, + 456: childBlockOrder, + }, + }, + }; + + const nextState = { + blocks: { + byClientId: { + 123: rootBlock, + 456: { clientId: 456, name: 'core/paragraph' }, + }, + attributes: { + 123: rootBlockAttributes, + 456: { content: [ 'foo' ] }, + }, + order: { + '': rootOrder, + 123: rootBlockOrder, + 456: childBlockOrder, + }, + }, + }; + + expect( + getBlockDependantsCacheBust( state, 123 ) + ).not.toBe( getBlockDependantsCacheBust( nextState, 123 ) ); + } ); + + it( 'returns a new reference on updated grandchild inner block', () => { + const rootBlockOrder = [ 456 ]; + const childBlock = { clientId: 456, name: 'core/paragraph' }; + const childBlockAttributes = {}; + const childBlockOrder = [ 789 ]; + const grandChildBlockOrder = []; + + const state = { + blocks: { + byClientId: { + 123: rootBlock, + 456: childBlock, + 789: { clientId: 789, name: 'core/paragraph' }, + }, + attributes: { + 123: rootBlockAttributes, + 456: childBlockAttributes, + 789: {}, + }, + order: { + '': rootOrder, + 123: rootBlockOrder, + 456: childBlockOrder, + 789: grandChildBlockOrder, + }, + }, + }; + + const nextState = { + blocks: { + byClientId: { + 123: rootBlock, + 456: childBlock, + 789: { clientId: 789, name: 'core/paragraph' }, + }, + attributes: { + 123: rootBlockAttributes, + 456: childBlockAttributes, + 789: { content: [ 'foo' ] }, + }, + order: { + '': rootOrder, + 123: rootBlockOrder, + 456: childBlockOrder, + 789: grandChildBlockOrder, + }, + }, + }; + + expect( + getBlockDependantsCacheBust( state, 123 ) + ).not.toBe( getBlockDependantsCacheBust( nextState, 123 ) ); + } ); + } ); + + describe( 'getBlockName', () => { + it( 'returns null if no block by clientId', () => { + const state = { + blocks: { + byClientId: {}, + attributes: {}, + order: {}, + }, + }; + + const name = getBlockName( state, 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1' ); + + expect( name ).toBe( null ); + } ); + + it( 'returns block name', () => { + const state = { + blocks: { + byClientId: { + 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': { + clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1', + name: 'core/paragraph', + }, + }, + attributes: { + 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': {}, + }, + order: { + '': [ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1' ], + 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': [], + }, + }, + }; + + const name = getBlockName( state, 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1' ); + + expect( name ).toBe( 'core/paragraph' ); + } ); + } ); + + describe( 'getBlock', () => { + it( 'should return the block', () => { + const state = { + blocks: { + byClientId: { + 123: { clientId: 123, name: 'core/paragraph' }, + }, + attributes: { + 123: {}, + }, + order: { + '': [ 123 ], + 123: [], + }, + }, + }; + + expect( getBlock( state, 123 ) ).toEqual( { + clientId: 123, + name: 'core/paragraph', + attributes: {}, + innerBlocks: [], + } ); + } ); + + it( 'should return null if the block is not present in state', () => { + const state = { + blocks: { + byClientId: {}, + attributes: {}, + order: {}, + }, + }; + + expect( getBlock( state, 123 ) ).toBe( null ); + } ); + + it( 'should include inner blocks', () => { + const state = { + blocks: { + byClientId: { + 123: { clientId: 123, name: 'core/paragraph' }, + 456: { clientId: 456, name: 'core/paragraph' }, + }, + attributes: { + 123: {}, + 456: {}, + }, + order: { + '': [ 123 ], + 123: [ 456 ], + 456: [], + }, + }, + }; + + expect( getBlock( state, 123 ) ).toEqual( { + clientId: 123, + name: 'core/paragraph', + attributes: {}, + innerBlocks: [ { + clientId: 456, + name: 'core/paragraph', + attributes: {}, + innerBlocks: [], + } ], + } ); + } ); + + it( 'should merge meta attributes for the block', () => { + registerBlockType( 'core/meta-block', { + save: ( props ) => props.attributes.text, + category: 'common', + title: 'test block', + attributes: { + foo: { + type: 'string', + source: 'meta', + meta: 'foo', + }, + }, + } ); + + const state = { + settings: { + __experimentalMetaSource: { + value: { + foo: 'bar', + }, + }, + }, + blocks: { + byClientId: { + 123: { clientId: 123, name: 'core/meta-block' }, + }, + attributes: { + 123: {}, + }, + order: { + '': [ 123 ], + 123: [], + }, + }, + }; + + expect( getBlock( state, 123 ) ).toEqual( { + clientId: 123, + name: 'core/meta-block', + attributes: { + foo: 'bar', + }, + innerBlocks: [], + } ); + + unregisterBlockType( 'core/meta-block' ); + } ); + } ); + + describe( 'getBlocks', () => { + it( 'should return the ordered blocks', () => { + const state = { + blocks: { + byClientId: { + 23: { clientId: 23, name: 'core/heading' }, + 123: { clientId: 123, name: 'core/paragraph' }, + }, + attributes: { + 23: {}, + 123: {}, + }, + order: { + '': [ 123, 23 ], + }, + }, + }; + + expect( getBlocks( state ) ).toEqual( [ + { clientId: 123, name: 'core/paragraph', attributes: {}, innerBlocks: [] }, + { clientId: 23, name: 'core/heading', attributes: {}, innerBlocks: [] }, + ] ); + } ); + } ); + + describe( 'getClientIdsOfDescendants', () => { + it( 'should return the ids of any descendants, given an array of clientIds', () => { + const state = { + blocks: { + byClientId: { + 'uuid-2': { clientId: 'uuid-2', name: 'core/image' }, + 'uuid-4': { clientId: 'uuid-4', name: 'core/paragraph' }, + 'uuid-6': { clientId: 'uuid-6', name: 'core/paragraph' }, + 'uuid-8': { clientId: 'uuid-8', name: 'core/block' }, + 'uuid-10': { clientId: 'uuid-10', name: 'core/columns' }, + 'uuid-12': { clientId: 'uuid-12', name: 'core/column' }, + 'uuid-14': { clientId: 'uuid-14', name: 'core/column' }, + 'uuid-16': { clientId: 'uuid-16', name: 'core/quote' }, + 'uuid-18': { clientId: 'uuid-18', name: 'core/block' }, + 'uuid-20': { clientId: 'uuid-20', name: 'core/gallery' }, + 'uuid-22': { clientId: 'uuid-22', name: 'core/block' }, + 'uuid-24': { clientId: 'uuid-24', name: 'core/columns' }, + 'uuid-26': { clientId: 'uuid-26', name: 'core/column' }, + 'uuid-28': { clientId: 'uuid-28', name: 'core/column' }, + 'uuid-30': { clientId: 'uuid-30', name: 'core/paragraph' }, + }, + attributes: { + 'uuid-2': {}, + 'uuid-4': {}, + 'uuid-6': {}, + 'uuid-8': {}, + 'uuid-10': {}, + 'uuid-12': {}, + 'uuid-14': {}, + 'uuid-16': {}, + 'uuid-18': {}, + 'uuid-20': {}, + 'uuid-22': {}, + 'uuid-24': {}, + 'uuid-26': {}, + 'uuid-28': {}, + 'uuid-30': {}, + }, + order: { + '': [ 'uuid-6', 'uuid-8', 'uuid-10', 'uuid-22' ], + 'uuid-2': [ ], + 'uuid-4': [ ], + 'uuid-6': [ ], + 'uuid-8': [ ], + 'uuid-10': [ 'uuid-12', 'uuid-14' ], + 'uuid-12': [ 'uuid-16' ], + 'uuid-14': [ 'uuid-18' ], + 'uuid-16': [ ], + 'uuid-18': [ 'uuid-24' ], + 'uuid-20': [ ], + 'uuid-22': [ ], + 'uuid-24': [ 'uuid-26', 'uuid-28' ], + 'uuid-26': [ ], + 'uuid-28': [ 'uuid-30' ], + }, + }, + }; + expect( getClientIdsOfDescendants( state, [ 'uuid-10' ] ) ).toEqual( [ + 'uuid-12', + 'uuid-14', + 'uuid-16', + 'uuid-18', + 'uuid-24', + 'uuid-26', + 'uuid-28', + 'uuid-30', + ] ); + } ); + } ); + + describe( 'getClientIdsWithDescendants', () => { + it( 'should return the ids for top-level blocks and their descendants of any depth (for nested blocks).', () => { + const state = { + blocks: { + byClientId: { + 'uuid-2': { clientId: 'uuid-2', name: 'core/image' }, + 'uuid-4': { clientId: 'uuid-4', name: 'core/paragraph' }, + 'uuid-6': { clientId: 'uuid-6', name: 'core/paragraph' }, + 'uuid-8': { clientId: 'uuid-8', name: 'core/block' }, + 'uuid-10': { clientId: 'uuid-10', name: 'core/columns' }, + 'uuid-12': { clientId: 'uuid-12', name: 'core/column' }, + 'uuid-14': { clientId: 'uuid-14', name: 'core/column' }, + 'uuid-16': { clientId: 'uuid-16', name: 'core/quote' }, + 'uuid-18': { clientId: 'uuid-18', name: 'core/block' }, + 'uuid-20': { clientId: 'uuid-20', name: 'core/gallery' }, + 'uuid-22': { clientId: 'uuid-22', name: 'core/block' }, + 'uuid-24': { clientId: 'uuid-24', name: 'core/columns' }, + 'uuid-26': { clientId: 'uuid-26', name: 'core/column' }, + 'uuid-28': { clientId: 'uuid-28', name: 'core/column' }, + 'uuid-30': { clientId: 'uuid-30', name: 'core/paragraph' }, + }, + attributes: { + 'uuid-2': {}, + 'uuid-4': {}, + 'uuid-6': {}, + 'uuid-8': {}, + 'uuid-10': {}, + 'uuid-12': {}, + 'uuid-14': {}, + 'uuid-16': {}, + 'uuid-18': {}, + 'uuid-20': {}, + 'uuid-22': {}, + 'uuid-24': {}, + 'uuid-26': {}, + 'uuid-28': {}, + 'uuid-30': {}, + }, + order: { + '': [ 'uuid-6', 'uuid-8', 'uuid-10', 'uuid-22' ], + 'uuid-2': [ ], + 'uuid-4': [ ], + 'uuid-6': [ ], + 'uuid-8': [ ], + 'uuid-10': [ 'uuid-12', 'uuid-14' ], + 'uuid-12': [ 'uuid-16' ], + 'uuid-14': [ 'uuid-18' ], + 'uuid-16': [ ], + 'uuid-18': [ 'uuid-24' ], + 'uuid-20': [ ], + 'uuid-22': [ ], + 'uuid-24': [ 'uuid-26', 'uuid-28' ], + 'uuid-26': [ ], + 'uuid-28': [ 'uuid-30' ], + }, + }, + }; + expect( getClientIdsWithDescendants( state ) ).toEqual( [ + 'uuid-6', + 'uuid-8', + 'uuid-10', + 'uuid-22', + 'uuid-12', + 'uuid-14', + 'uuid-16', + 'uuid-18', + 'uuid-24', + 'uuid-26', + 'uuid-28', + 'uuid-30', + ] ); + } ); + } ); + + describe( 'getBlockCount', () => { + it( 'should return the number of top-level blocks in the post', () => { + const state = { + blocks: { + byClientId: { + 23: { clientId: 23, name: 'core/heading' }, + 123: { clientId: 123, name: 'core/paragraph' }, + }, + attributes: { + 23: {}, + 123: {}, + }, + order: { + '': [ 123, 23 ], + }, + }, + }; + + expect( getBlockCount( state ) ).toBe( 2 ); + } ); + + it( 'should return the number of blocks in a nested context', () => { + const state = { + blocks: { + byClientId: { + 123: { clientId: 123, name: 'core/columns' }, + 456: { clientId: 456, name: 'core/paragraph' }, + 789: { clientId: 789, name: 'core/paragraph' }, + }, + attributes: { + 123: {}, + 456: {}, + 789: {}, + }, + order: { + '': [ 123 ], + 123: [ 456, 789 ], + }, + }, + }; + + expect( getBlockCount( state, '123' ) ).toBe( 2 ); + } ); + } ); + + describe( 'hasSelectedBlock', () => { + it( 'should return false if no selection', () => { + const state = { + blockSelection: { + start: null, + end: null, + }, + }; + + expect( hasSelectedBlock( state ) ).toBe( false ); + } ); + + it( 'should return false if multi-selection', () => { + const state = { + blockSelection: { + start: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1', + end: '9db792c6-a25a-495d-adbd-97d56a4c4189', + }, + }; + + expect( hasSelectedBlock( state ) ).toBe( false ); + } ); + + it( 'should return true if singular selection', () => { + const state = { + blockSelection: { + start: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1', + end: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1', + }, + }; + + expect( hasSelectedBlock( state ) ).toBe( true ); + } ); + } ); + + describe( 'getGlobalBlockCount', () => { + const state = { + blocks: { + byClientId: { + 123: { clientId: 123, name: 'core/heading' }, + 456: { clientId: 456, name: 'core/paragraph' }, + 789: { clientId: 789, name: 'core/paragraph' }, + }, + attributes: { + 123: {}, + 456: {}, + 789: {}, + }, + order: { + '': [ 123, 456 ], + }, + }, + }; + + it( 'should return the global number of blocks in the post', () => { + expect( getGlobalBlockCount( state ) ).toBe( 2 ); + } ); + + it( 'should return the global number of blocks in the post of a given type', () => { + expect( getGlobalBlockCount( state, 'core/paragraph' ) ).toBe( 1 ); + } ); + + it( 'should return 0 if no blocks exist', () => { + const emptyState = { + blocks: { + byClientId: {}, + attributes: {}, + order: {}, + }, + }; + expect( getGlobalBlockCount( emptyState ) ).toBe( 0 ); + expect( getGlobalBlockCount( emptyState, 'core/heading' ) ).toBe( 0 ); + } ); + } ); + + describe( 'getSelectedBlockClientId', () => { + it( 'should return null if no block is selected', () => { + const state = { + blockSelection: { start: null, end: null }, + }; + + expect( getSelectedBlockClientId( state ) ).toBe( null ); + } ); + + it( 'should return null if there is multi selection', () => { + const state = { + blockSelection: { start: 23, end: 123 }, + }; + + expect( getSelectedBlockClientId( state ) ).toBe( null ); + } ); + + it( 'should return the selected block ClientId', () => { + const state = { + blocks: { + byClientId: { + 23: { + name: 'fake block', + }, + }, + }, + blockSelection: { start: 23, end: 23 }, + }; + + expect( getSelectedBlockClientId( state ) ).toEqual( 23 ); + } ); + } ); + + describe( 'getSelectedBlock', () => { + it( 'should return null if no block is selected', () => { + const state = { + blocks: { + byClientId: { + 23: { clientId: 23, name: 'core/heading' }, + 123: { clientId: 123, name: 'core/paragraph' }, + }, + attributes: { + 23: {}, + 123: {}, + }, + order: { + '': [ 23, 123 ], + 23: [], + 123: [], + }, + }, + blockSelection: { start: null, end: null }, + }; + + expect( getSelectedBlock( state ) ).toBe( null ); + } ); + + it( 'should return null if there is multi selection', () => { + const state = { + blocks: { + byClientId: { + 23: { clientId: 23, name: 'core/heading' }, + 123: { clientId: 123, name: 'core/paragraph' }, + }, + attributes: { + 23: {}, + 123: {}, + }, + order: { + '': [ 23, 123 ], + 23: [], + 123: [], + }, + }, + blockSelection: { start: 23, end: 123 }, + }; + + expect( getSelectedBlock( state ) ).toBe( null ); + } ); + + it( 'should return the selected block', () => { + const state = { + blocks: { + byClientId: { + 23: { clientId: 23, name: 'core/heading' }, + 123: { clientId: 123, name: 'core/paragraph' }, + }, + attributes: { + 23: {}, + 123: {}, + }, + order: { + '': [ 23, 123 ], + 23: [], + 123: [], + }, + }, + blockSelection: { start: 23, end: 23 }, + }; + + expect( getSelectedBlock( state ) ).toEqual( { + clientId: 23, + name: 'core/heading', + attributes: {}, + innerBlocks: [], + } ); + } ); + } ); + + describe( 'getBlockRootClientId', () => { + it( 'should return null if the block does not exist', () => { + const state = { + blocks: { + order: {}, + }, + }; + + expect( getBlockRootClientId( state, 56 ) ).toBeNull(); + } ); + + it( 'should return root ClientId relative the block ClientId', () => { + const state = { + blocks: { + order: { + '': [ 123, 23 ], + 123: [ 456, 56 ], + }, + }, + }; + + expect( getBlockRootClientId( state, 56 ) ).toBe( '123' ); + } ); + } ); + + describe( 'getBlockHierarchyRootClientId', () => { + it( 'should return the given block if the block has no parents', () => { + const state = { + blocks: { + order: {}, + }, + }; + + expect( getBlockHierarchyRootClientId( state, 56 ) ).toBe( 56 ); + } ); + + it( 'should return root ClientId relative the block ClientId', () => { + const state = { + blocks: { + order: { + '': [ 123, 23 ], + 123: [ 456, 56 ], + }, + }, + }; + + expect( getBlockHierarchyRootClientId( state, 56 ) ).toBe( '123' ); + } ); + + it( 'should return the top level root ClientId relative the block ClientId', () => { + const state = { + blocks: { + order: { + '': [ '123', '23' ], + 123: [ '456', '56' ], + 56: [ '12' ], + }, + }, + }; + + expect( getBlockHierarchyRootClientId( state, '12' ) ).toBe( '123' ); + } ); + } ); + + describe( 'getMultiSelectedBlockClientIds', () => { + it( 'should return empty if there is no multi selection', () => { + const state = { + blocks: { + order: { + '': [ 123, 23 ], + }, + }, + blockSelection: { start: null, end: null }, + }; + + expect( getMultiSelectedBlockClientIds( state ) ).toEqual( [] ); + } ); + + it( 'should return selected block clientIds if there is multi selection', () => { + const state = { + blocks: { + order: { + '': [ 5, 4, 3, 2, 1 ], + }, + }, + blockSelection: { start: 2, end: 4 }, + }; + + expect( getMultiSelectedBlockClientIds( state ) ).toEqual( [ 4, 3, 2 ] ); + } ); + + it( 'should return selected block clientIds if there is multi selection (nested context)', () => { + const state = { + blocks: { + order: { + '': [ 5, 4, 3, 2, 1 ], + 4: [ 9, 8, 7, 6 ], + }, + }, + blockSelection: { start: 7, end: 9 }, + }; + + expect( getMultiSelectedBlockClientIds( state ) ).toEqual( [ 9, 8, 7 ] ); + } ); + } ); + + describe( 'getMultiSelectedBlocks', () => { + it( 'should return the same reference on subsequent invocations of empty selection', () => { + const state = { + blocks: { + byClientId: {}, + attributes: {}, + order: {}, + }, + blockSelection: { start: null, end: null }, + }; + + expect( + getMultiSelectedBlocks( state ) + ).toBe( getMultiSelectedBlocks( state ) ); + } ); + } ); + + describe( 'getMultiSelectedBlocksStartClientId', () => { + it( 'returns null if there is no multi selection', () => { + const state = { + blockSelection: { start: null, end: null }, + }; + + expect( getMultiSelectedBlocksStartClientId( state ) ).toBeNull(); + } ); + + it( 'returns multi selection start', () => { + const state = { + blockSelection: { start: 2, end: 4 }, + }; + + expect( getMultiSelectedBlocksStartClientId( state ) ).toBe( 2 ); + } ); + } ); + + describe( 'getMultiSelectedBlocksEndClientId', () => { + it( 'returns null if there is no multi selection', () => { + const state = { + blockSelection: { start: null, end: null }, + }; + + expect( getMultiSelectedBlocksEndClientId( state ) ).toBeNull(); + } ); + + it( 'returns multi selection end', () => { + const state = { + blockSelection: { start: 2, end: 4 }, + }; + + expect( getMultiSelectedBlocksEndClientId( state ) ).toBe( 4 ); + } ); + } ); + + describe( 'getBlockOrder', () => { + it( 'should return the ordered block ClientIds of top-level blocks by default', () => { + const state = { + blocks: { + order: { + '': [ 123, 23 ], + }, + }, + }; + + expect( getBlockOrder( state ) ).toEqual( [ 123, 23 ] ); + } ); + + it( 'should return the ordered block ClientIds at a specified rootClientId', () => { + const state = { + blocks: { + order: { + '': [ 123, 23 ], + 123: [ 456 ], + }, + }, + }; + + expect( getBlockOrder( state, '123' ) ).toEqual( [ 456 ] ); + } ); + } ); + + describe( 'getBlockIndex', () => { + it( 'should return the block order', () => { + const state = { + blocks: { + order: { + '': [ 123, 23 ], + }, + }, + }; + + expect( getBlockIndex( state, 23 ) ).toBe( 1 ); + } ); + + it( 'should return the block order (nested context)', () => { + const state = { + blocks: { + order: { + '': [ 123, 23 ], + 123: [ 456, 56 ], + }, + }, + }; + + expect( getBlockIndex( state, 56, '123' ) ).toBe( 1 ); + } ); + } ); + + describe( 'getPreviousBlockClientId', () => { + it( 'should return the previous block', () => { + const state = { + blocks: { + order: { + '': [ 123, 23 ], + }, + }, + }; + + expect( getPreviousBlockClientId( state, 23 ) ).toEqual( 123 ); + } ); + + it( 'should return the previous block (nested context)', () => { + const state = { + blocks: { + order: { + '': [ 123, 23 ], + 123: [ 456, 56 ], + }, + }, + }; + + expect( getPreviousBlockClientId( state, 56, '123' ) ).toEqual( 456 ); + } ); + + it( 'should return null for the first block', () => { + const state = { + blocks: { + order: { + '': [ 123, 23 ], + }, + }, + }; + + expect( getPreviousBlockClientId( state, 123 ) ).toBeNull(); + } ); + + it( 'should return null for the first block (nested context)', () => { + const state = { + blocks: { + order: { + '': [ 123, 23 ], + 123: [ 456, 56 ], + }, + }, + }; + + expect( getPreviousBlockClientId( state, 456, '123' ) ).toBeNull(); + } ); + } ); + + describe( 'getNextBlockClientId', () => { + it( 'should return the following block', () => { + const state = { + blocks: { + order: { + '': [ 123, 23 ], + }, + }, + }; + + expect( getNextBlockClientId( state, 123 ) ).toEqual( 23 ); + } ); + + it( 'should return the following block (nested context)', () => { + const state = { + blocks: { + order: { + '': [ 123, 23 ], + 123: [ 456, 56 ], + }, + }, + }; + + expect( getNextBlockClientId( state, 456, '123' ) ).toEqual( 56 ); + } ); + + it( 'should return null for the last block', () => { + const state = { + blocks: { + order: { + '': [ 123, 23 ], + }, + }, + }; + + expect( getNextBlockClientId( state, 23 ) ).toBeNull(); + } ); + + it( 'should return null for the last block (nested context)', () => { + const state = { + blocks: { + order: { + '': [ 123, 23 ], + 123: [ 456, 56 ], + }, + }, + }; + + expect( getNextBlockClientId( state, 56, '123' ) ).toBeNull(); + } ); + } ); + + describe( 'isBlockSelected', () => { + it( 'should return true if the block is selected', () => { + const state = { + blockSelection: { start: 123, end: 123 }, + }; + + expect( isBlockSelected( state, 123 ) ).toBe( true ); + } ); + + it( 'should return false if a multi-selection range exists', () => { + const state = { + blockSelection: { start: 123, end: 124 }, + }; + + expect( isBlockSelected( state, 123 ) ).toBe( false ); + } ); + + it( 'should return false if the block is not selected', () => { + const state = { + blockSelection: { start: null, end: null }, + }; + + expect( isBlockSelected( state, 23 ) ).toBe( false ); + } ); + } ); + + describe( 'hasSelectedInnerBlock', () => { + it( 'should return false if the selected block is a child of the given ClientId', () => { + const state = { + blockSelection: { start: 5, end: 5 }, + blocks: { + order: { + 4: [ 3, 2, 1 ], + }, + }, + }; + + expect( hasSelectedInnerBlock( state, 4 ) ).toBe( false ); + } ); + + it( 'should return true if the selected block is a child of the given ClientId', () => { + const state = { + blockSelection: { start: 3, end: 3 }, + blocks: { + order: { + 4: [ 3, 2, 1 ], + }, + }, + }; + + expect( hasSelectedInnerBlock( state, 4 ) ).toBe( true ); + } ); + + it( 'should return true if a multi selection exists that contains children of the block with the given ClientId', () => { + const state = { + blocks: { + order: { + 6: [ 5, 4, 3, 2, 1 ], + }, + }, + blockSelection: { start: 2, end: 4 }, + }; + expect( hasSelectedInnerBlock( state, 6 ) ).toBe( true ); + } ); + + it( 'should return false if a multi selection exists bot does not contains children of the block with the given ClientId', () => { + const state = { + blocks: { + order: { + 3: [ 2, 1 ], + 6: [ 5, 4 ], + }, + }, + blockSelection: { start: 5, end: 4 }, + }; + expect( hasSelectedInnerBlock( state, 3 ) ).toBe( false ); + } ); + } ); + + describe( 'isBlockWithinSelection', () => { + it( 'should return true if the block is selected but not the last', () => { + const state = { + blockSelection: { start: 5, end: 3 }, + blocks: { + order: { + '': [ 5, 4, 3, 2, 1 ], + }, + }, + }; + + expect( isBlockWithinSelection( state, 4 ) ).toBe( true ); + } ); + + it( 'should return false if the block is the last selected', () => { + const state = { + blockSelection: { start: 5, end: 3 }, + blocks: { + order: { + '': [ 5, 4, 3, 2, 1 ], + }, + }, + }; + + expect( isBlockWithinSelection( state, 3 ) ).toBe( false ); + } ); + + it( 'should return false if the block is not selected', () => { + const state = { + blockSelection: { start: 5, end: 3 }, + blocks: { + order: { + '': [ 5, 4, 3, 2, 1 ], + }, + }, + }; + + expect( isBlockWithinSelection( state, 2 ) ).toBe( false ); + } ); + + it( 'should return false if there is no selection', () => { + const state = { + blockSelection: {}, + blocks: { + order: { + '': [ 5, 4, 3, 2, 1 ], + }, + }, + }; + + expect( isBlockWithinSelection( state, 4 ) ).toBe( false ); + } ); + } ); + + describe( 'hasMultiSelection', () => { + it( 'should return false if no selection', () => { + const state = { + blockSelection: { + start: null, + end: null, + }, + }; + + expect( hasMultiSelection( state ) ).toBe( false ); + } ); + + it( 'should return false if singular selection', () => { + const state = { + blockSelection: { + start: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1', + end: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1', + }, + }; + + expect( hasMultiSelection( state ) ).toBe( false ); + } ); + + it( 'should return true if multi-selection', () => { + const state = { + blockSelection: { + start: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1', + end: '9db792c6-a25a-495d-adbd-97d56a4c4189', + }, + }; + + expect( hasMultiSelection( state ) ).toBe( true ); + } ); + } ); + + describe( 'isBlockMultiSelected', () => { + const state = { + blocks: { + order: { + '': [ 5, 4, 3, 2, 1 ], + }, + }, + blockSelection: { start: 2, end: 4 }, + }; + + it( 'should return true if the block is multi selected', () => { + expect( isBlockMultiSelected( state, 3 ) ).toBe( true ); + } ); + + it( 'should return false if the block is not multi selected', () => { + expect( isBlockMultiSelected( state, 5 ) ).toBe( false ); + } ); + } ); + + describe( 'isFirstMultiSelectedBlock', () => { + const state = { + blocks: { + order: { + '': [ 5, 4, 3, 2, 1 ], + }, + }, + blockSelection: { start: 2, end: 4 }, + }; + + it( 'should return true if the block is first in multi selection', () => { + expect( isFirstMultiSelectedBlock( state, 4 ) ).toBe( true ); + } ); + + it( 'should return false if the block is not first in multi selection', () => { + expect( isFirstMultiSelectedBlock( state, 3 ) ).toBe( false ); + } ); + } ); + + describe( 'getBlockMode', () => { + it( 'should return "visual" if unset', () => { + const state = { + blocksMode: {}, + }; + + expect( getBlockMode( state, 123 ) ).toEqual( 'visual' ); + } ); + + it( 'should return the block mode', () => { + const state = { + blocksMode: { + 123: 'html', + }, + }; + + expect( getBlockMode( state, 123 ) ).toEqual( 'html' ); + } ); + } ); + + describe( 'isTyping', () => { + it( 'should return the isTyping flag if the block is selected', () => { + const state = { + isTyping: true, + }; + + expect( isTyping( state ) ).toBe( true ); + } ); + + it( 'should return false if the block is not selected', () => { + const state = { + isTyping: false, + }; + + expect( isTyping( state ) ).toBe( false ); + } ); + } ); + + describe( 'isCaretWithinFormattedText', () => { + it( 'returns true if the isCaretWithinFormattedText state is also true', () => { + const state = { + isCaretWithinFormattedText: true, + }; + + expect( isCaretWithinFormattedText( state ) ).toBe( true ); + } ); + + it( 'returns false if the isCaretWithinFormattedText state is also false', () => { + const state = { + isCaretWithinFormattedText: false, + }; + + expect( isCaretWithinFormattedText( state ) ).toBe( false ); + } ); + } ); + + describe( 'isSelectionEnabled', () => { + it( 'should return true if selection is enable', () => { + const state = { + blockSelection: { + isEnabled: true, + }, + }; + + expect( isSelectionEnabled( state ) ).toBe( true ); + } ); + + it( 'should return false if selection is disabled', () => { + const state = { + blockSelection: { + isEnabled: false, + }, + }; + + expect( isSelectionEnabled( state ) ).toBe( false ); + } ); + } ); + + describe( 'getBlockInsertionPoint', () => { + it( 'should return the explicitly assigned insertion point', () => { + const state = { + blockSelection: { + start: 'clientId2', + end: 'clientId2', + }, + blocks: { + byClientId: { + clientId1: { clientId: 'clientId1' }, + clientId2: { clientId: 'clientId2' }, + }, + attributes: { + clientId1: {}, + clientId2: {}, + }, + order: { + '': [ 'clientId1' ], + clientId1: [ 'clientId2' ], + clientId2: [], + }, + }, + insertionPoint: { + rootClientId: undefined, + index: 0, + }, + }; + + expect( getBlockInsertionPoint( state ) ).toEqual( { + rootClientId: undefined, + index: 0, + } ); + } ); + + it( 'should return an object for the selected block', () => { + const state = { + blockSelection: { + start: 'clientId1', + end: 'clientId1', + }, + blocks: { + byClientId: { + clientId1: { clientId: 'clientId1' }, + }, + attributes: { + clientId1: {}, + }, + order: { + '': [ 'clientId1' ], + clientId1: [], + }, + }, + insertionPoint: null, + }; + + expect( getBlockInsertionPoint( state ) ).toEqual( { + rootClientId: undefined, + index: 1, + } ); + } ); + + it( 'should return an object for the nested selected block', () => { + const state = { + blockSelection: { + start: 'clientId2', + end: 'clientId2', + }, + blocks: { + byClientId: { + clientId1: { clientId: 'clientId1' }, + clientId2: { clientId: 'clientId2' }, + }, + attributes: { + clientId1: {}, + clientId2: {}, + }, + order: { + '': [ 'clientId1' ], + clientId1: [ 'clientId2' ], + clientId2: [], + }, + }, + insertionPoint: null, + }; + + expect( getBlockInsertionPoint( state ) ).toEqual( { + rootClientId: 'clientId1', + index: 1, + } ); + } ); + + it( 'should return an object for the last multi selected clientId', () => { + const state = { + blockSelection: { + start: 'clientId1', + end: 'clientId2', + }, + blocks: { + byClientId: { + clientId1: { clientId: 'clientId1' }, + clientId2: { clientId: 'clientId2' }, + }, + attributes: { + clientId1: {}, + clientId2: {}, + }, + order: { + '': [ 'clientId1', 'clientId2' ], + clientId1: [], + clientId2: [], + }, + }, + insertionPoint: null, + }; + + expect( getBlockInsertionPoint( state ) ).toEqual( { + rootClientId: undefined, + index: 2, + } ); + } ); + + it( 'should return an object for the last block if no selection', () => { + const state = { + blockSelection: { + start: null, + end: null, + }, + blocks: { + byClientId: { + clientId1: { clientId: 'clientId1' }, + clientId2: { clientId: 'clientId2' }, + }, + attributes: { + clientId1: {}, + clientId2: {}, + }, + order: { + '': [ 'clientId1', 'clientId2' ], + clientId1: [], + clientId2: [], + }, + }, + insertionPoint: null, + }; + + expect( getBlockInsertionPoint( state ) ).toEqual( { + rootClientId: undefined, + index: 2, + } ); + } ); + } ); + + describe( 'isBlockInsertionPointVisible', () => { + it( 'should return false if no assigned insertion point', () => { + const state = { + insertionPoint: null, + }; + + expect( isBlockInsertionPointVisible( state ) ).toBe( false ); + } ); + + it( 'should return true if assigned insertion point', () => { + const state = { + insertionPoint: { + rootClientId: undefined, + index: 5, + }, + }; + + expect( isBlockInsertionPointVisible( state ) ).toBe( true ); + } ); + } ); + + describe( 'canInsertBlockType', () => { + it( 'should deny blocks that are not registered', () => { + const state = { + blocks: { + byClientId: {}, + attributes: {}, + }, + blockListSettings: {}, + settings: {}, + }; + expect( canInsertBlockType( state, 'core/invalid' ) ).toBe( false ); + } ); + + it( 'should deny blocks that are not allowed by the editor', () => { + const state = { + blocks: { + byClientId: {}, + attributes: {}, + }, + blockListSettings: {}, + settings: { + allowedBlockTypes: [], + }, + }; + expect( canInsertBlockType( state, 'core/test-block-a' ) ).toBe( false ); + } ); + + it( 'should allow blocks that are allowed by the editor', () => { + const state = { + blocks: { + byClientId: {}, + attributes: {}, + }, + blockListSettings: {}, + settings: { + allowedBlockTypes: [ 'core/test-block-a' ], + }, + }; + expect( canInsertBlockType( state, 'core/test-block-a' ) ).toBe( true ); + } ); + + it( 'should deny blocks when the editor has a template lock', () => { + const state = { + blocks: { + byClientId: {}, + attributes: {}, + }, + blockListSettings: {}, + settings: { + templateLock: 'all', + }, + }; + expect( canInsertBlockType( state, 'core/test-block-a' ) ).toBe( false ); + } ); + + it( 'should deny blocks that restrict parent from being inserted into the root', () => { + const state = { + blocks: { + byClientId: {}, + attributes: {}, + }, + blockListSettings: {}, + settings: {}, + }; + expect( canInsertBlockType( state, 'core/test-block-c' ) ).toBe( false ); + } ); + + it( 'should deny blocks that restrict parent from being inserted into a restricted parent', () => { + const state = { + blocks: { + byClientId: { + block1: { name: 'core/test-block-a' }, + }, + attributes: { + block1: {}, + }, + }, + blockListSettings: {}, + settings: {}, + }; + expect( canInsertBlockType( state, 'core/test-block-c', 'block1' ) ).toBe( false ); + } ); + + it( 'should allow blocks that restrict parent to be inserted into an allowed parent', () => { + const state = { + blocks: { + byClientId: { + block1: { name: 'core/test-block-b' }, + }, + attributes: { + block1: {}, + }, + }, + blockListSettings: {}, + settings: {}, + }; + expect( canInsertBlockType( state, 'core/test-block-c', 'block1' ) ).toBe( true ); + } ); + + it( 'should deny restricted blocks from being inserted into a block that restricts allowedBlocks', () => { + const state = { + blocks: { + byClientId: { + block1: { name: 'core/test-block-a' }, + }, + attributes: { + block1: {}, + }, + }, + blockListSettings: { + block1: { + allowedBlocks: [ 'core/test-block-c' ], + }, + }, + settings: {}, + }; + expect( canInsertBlockType( state, 'core/test-block-b', 'block1' ) ).toBe( false ); + } ); + + it( 'should allow allowed blocks to be inserted into a block that restricts allowedBlocks', () => { + const state = { + blocks: { + byClientId: { + block1: { name: 'core/test-block-a' }, + }, + attributes: { + block1: {}, + }, + }, + blockListSettings: { + block1: { + allowedBlocks: [ 'core/test-block-b' ], + }, + }, + settings: {}, + }; + expect( canInsertBlockType( state, 'core/test-block-b', 'block1' ) ).toBe( true ); + } ); + + it( 'should prioritise parent over allowedBlocks', () => { + const state = { + blocks: { + byClientId: { + block1: { name: 'core/test-block-b' }, + }, + attributes: { + block1: {}, + }, + }, + blockListSettings: { + block1: { + allowedBlocks: [], + }, + }, + settings: {}, + }; + expect( canInsertBlockType( state, 'core/test-block-c', 'block1' ) ).toBe( true ); + } ); + } ); + + describe( 'getInserterItems', () => { + it( 'should properly list block type and reusable block items', () => { + const state = { + blocks: { + byClientId: { + block1: { name: 'core/test-block-a' }, + }, + attributes: { + block1: {}, + }, + order: {}, + }, + settings: { + __experimentalReusableBlocks: [ + { id: 1, isTemporary: false, clientId: 'block1', title: 'Reusable Block 1' }, + ], + }, + preferences: { + insertUsage: {}, + }, + blockListSettings: {}, + }; + const items = getInserterItems( state ); + const testBlockAItem = items.find( ( item ) => item.id === 'core/test-block-a' ); + expect( testBlockAItem ).toEqual( { + id: 'core/test-block-a', + name: 'core/test-block-a', + initialAttributes: {}, + title: 'Test Block A', + icon: { + src: 'test', + }, + category: 'formatting', + keywords: [ 'testing' ], + isDisabled: false, + utility: 0, + frecency: 0, + hasChildBlocksWithInserterSupport: false, + } ); + const reusableBlockItem = items.find( ( item ) => item.id === 'core/block/1' ); + expect( reusableBlockItem ).toEqual( { + id: 'core/block/1', + name: 'core/block', + initialAttributes: { ref: 1 }, + title: 'Reusable Block 1', + icon: { + src: 'test', + }, + category: 'reusable', + keywords: [], + isDisabled: false, + utility: 0, + frecency: 0, + } ); + } ); + + it( 'should not list a reusable block item if it is being inserted inside it self', () => { + const state = { + blocks: { + byClientId: { + block1ref: { + name: 'core/block', + clientId: 'block1ref', + }, + itselfBlock1: { name: 'core/test-block-a' }, + itselfBlock2: { name: 'core/test-block-b' }, + }, + attributes: { + block1ref: { + attributes: { + ref: 1, + }, + }, + itselfBlock1: {}, + itselfBlock2: {}, + }, + order: { + '': [ 'block1ref' ], + }, + }, + settings: { + __experimentalReusableBlocks: [ + { id: 1, isTemporary: false, clientId: 'itselfBlock1', title: 'Reusable Block 1' }, + { id: 2, isTemporary: false, clientId: 'itselfBlock2', title: 'Reusable Block 2' }, + ], + }, + preferences: { + insertUsage: {}, + }, + blockListSettings: {}, + }; + const items = getInserterItems( state, 'itselfBlock1' ); + const reusableBlockItems = filter( items, [ 'name', 'core/block' ] ); + expect( reusableBlockItems ).toHaveLength( 1 ); + expect( reusableBlockItems[ 0 ] ).toEqual( { + id: 'core/block/2', + name: 'core/block', + initialAttributes: { ref: 2 }, + title: 'Reusable Block 2', + icon: { + src: 'test', + }, + category: 'reusable', + keywords: [], + isDisabled: false, + utility: 0, + frecency: 0, + } ); + } ); + + it( 'should not list a reusable block item if it is being inserted inside a descendent', () => { + const state = { + blocks: { + byClientId: { + block2ref: { + name: 'core/block', + clientId: 'block1ref', + }, + referredBlock1: { name: 'core/test-block-a' }, + referredBlock2: { name: 'core/test-block-b' }, + childReferredBlock2: { name: 'core/test-block-a' }, + grandchildReferredBlock2: { name: 'core/test-block-b' }, + }, + attributes: { + block2ref: { + attributes: { + ref: 2, + }, + }, + referredBlock1: {}, + referredBlock2: {}, + childReferredBlock2: {}, + grandchildReferredBlock2: {}, + }, + order: { + '': [ 'block2ref' ], + referredBlock2: [ 'childReferredBlock2' ], + childReferredBlock2: [ 'grandchildReferredBlock2' ], + }, + }, + + settings: { + __experimentalReusableBlocks: [ + { id: 1, isTemporary: false, clientId: 'referredBlock1', title: 'Reusable Block 1' }, + { id: 2, isTemporary: false, clientId: 'referredBlock2', title: 'Reusable Block 2' }, + ], + }, + preferences: { + insertUsage: {}, + }, + blockListSettings: {}, + }; + const items = getInserterItems( state, 'grandchildReferredBlock2' ); + const reusableBlockItems = filter( items, [ 'name', 'core/block' ] ); + expect( reusableBlockItems ).toHaveLength( 1 ); + expect( reusableBlockItems[ 0 ] ).toEqual( { + id: 'core/block/1', + name: 'core/block', + initialAttributes: { ref: 1 }, + title: 'Reusable Block 1', + icon: { + src: 'test', + }, + category: 'reusable', + keywords: [], + isDisabled: false, + utility: 0, + frecency: 0, + } ); + } ); + it( 'should order items by descending utility and frecency', () => { + const state = { + blocks: { + byClientId: { + block1: { name: 'core/test-block-a' }, + block2: { name: 'core/test-block-a' }, + }, + attributes: { + block1: {}, + block2: {}, + }, + order: {}, + }, + settings: { + __experimentalReusableBlocks: [ + { id: 1, isTemporary: false, clientId: 'block1', title: 'Reusable Block 1' }, + { id: 2, isTemporary: false, clientId: 'block2', title: 'Reusable Block 2' }, + ], + }, + preferences: { + insertUsage: { + 'core/block/1': { count: 10, time: 1000 }, + 'core/block/2': { count: 20, time: 1000 }, + }, + }, + blockListSettings: {}, + }; + const itemIDs = getInserterItems( state ).map( ( item ) => item.id ); + expect( itemIDs ).toEqual( [ + 'core/block/2', + 'core/block/1', + 'core/test-block-b', + 'core/test-freeform', + 'core/test-block-a', + ] ); + } ); + + it( 'should correctly cache the return values', () => { + const state = { + blocks: { + byClientId: { + block1: { name: 'core/test-block-a' }, + block2: { name: 'core/test-block-a' }, + block3: { name: 'core/test-block-a' }, + block4: { name: 'core/test-block-a' }, + }, + attributes: { + block1: {}, + block2: {}, + block3: {}, + block4: {}, + }, + order: { + '': [ 'block3', 'block4' ], + }, + }, + settings: { + __experimentalReusableBlocks: [ + { id: 1, isTemporary: false, clientId: 'block1', title: 'Reusable Block 1' }, + { id: 2, isTemporary: false, clientId: 'block2', title: 'Reusable Block 2' }, + ], + }, + preferences: { + insertUsage: {}, + }, + blockListSettings: {}, + }; + + const stateSecondBlockRestricted = { + ...state, + blockListSettings: { + block4: { + allowedBlocks: [ 'core/test-block-b' ], + }, + }, + }; + + const firstBlockFirstCall = getInserterItems( state, 'block3' ); + const firstBlockSecondCall = getInserterItems( stateSecondBlockRestricted, 'block3' ); + expect( firstBlockFirstCall ).toBe( firstBlockSecondCall ); + expect( firstBlockFirstCall.map( ( item ) => item.id ) ).toEqual( [ + 'core/test-block-b', + 'core/test-freeform', + 'core/test-block-a', + 'core/block/1', + 'core/block/2', + ] ); + + const secondBlockFirstCall = getInserterItems( state, 'block4' ); + const secondBlockSecondCall = getInserterItems( stateSecondBlockRestricted, 'block4' ); + expect( secondBlockFirstCall.map( ( item ) => item.id ) ).toEqual( [ + 'core/test-block-b', + 'core/test-freeform', + 'core/test-block-a', + 'core/block/1', + 'core/block/2', + ] ); + expect( secondBlockSecondCall.map( ( item ) => item.id ) ).toEqual( [ + 'core/test-block-b', + ] ); + } ); + + it( 'should set isDisabled when a block with `multiple: false` has been used', () => { + const state = { + blocks: { + byClientId: { + block1: { clientId: 'block1', name: 'core/test-block-b' }, + }, + attributes: { + block1: { attribute: {} }, + }, + order: { + '': [ 'block1' ], + }, + }, + preferences: { + insertUsage: {}, + }, + blockListSettings: {}, + settings: {}, + }; + const items = getInserterItems( state ); + const testBlockBItem = items.find( ( item ) => item.id === 'core/test-block-b' ); + expect( testBlockBItem.isDisabled ).toBe( true ); + } ); + + it( 'should give common blocks a low utility', () => { + const state = { + blocks: { + byClientId: {}, + attributes: {}, + order: {}, + }, + preferences: { + insertUsage: {}, + }, + blockListSettings: {}, + settings: {}, + }; + const items = getInserterItems( state ); + const testBlockBItem = items.find( ( item ) => item.id === 'core/test-block-b' ); + expect( testBlockBItem.utility ).toBe( INSERTER_UTILITY_LOW ); + } ); + + it( 'should give used blocks a medium utility and set a frecency', () => { + const state = { + blocks: { + byClientId: {}, + attributes: {}, + order: {}, + }, + preferences: { + insertUsage: { + 'core/test-block-b': { count: 10, time: 1000 }, + }, + }, + blockListSettings: {}, + settings: {}, + }; + const items = getInserterItems( state ); + const reusableBlock2Item = items.find( ( item ) => item.id === 'core/test-block-b' ); + expect( reusableBlock2Item.utility ).toBe( INSERTER_UTILITY_MEDIUM ); + expect( reusableBlock2Item.frecency ).toBe( 2.5 ); + } ); + + it( 'should give contextual blocks a high utility', () => { + const state = { + blocks: { + byClientId: { + block1: { name: 'core/test-block-b' }, + }, + attributes: { + block1: { attribute: {} }, + }, + order: { + '': [ 'block1' ], + }, + }, + preferences: { + insertUsage: {}, + }, + blockListSettings: {}, + settings: {}, + }; + const items = getInserterItems( state, 'block1' ); + const testBlockCItem = items.find( ( item ) => item.id === 'core/test-block-c' ); + expect( testBlockCItem.utility ).toBe( INSERTER_UTILITY_HIGH ); + } ); + } ); + + describe( 'isValidTemplate', () => { + it( 'should return true if template is valid', () => { + const state = { + template: { isValid: true }, + }; + + expect( isValidTemplate( state ) ).toBe( true ); + } ); + + it( 'should return false if template is not valid', () => { + const state = { + template: { isValid: false }, + }; + + expect( isValidTemplate( state ) ).toBe( false ); + } ); + } ); + + describe( 'getTemplate', () => { + it( 'should return the template object', () => { + const template = []; + const state = { + settings: { template }, + }; + + expect( getTemplate( state ) ).toBe( template ); + } ); + } ); + + describe( 'getTemplateLock', () => { + it( 'should return the general template lock if no clientId was set', () => { + const state = { + settings: { templateLock: 'all' }, + }; + + expect( getTemplateLock( state ) ).toBe( 'all' ); + } ); + + it( 'should return null if the specified clientId was not found ', () => { + const state = { + settings: { templateLock: 'all' }, + blockListSettings: { + chicken: { + templateLock: 'insert', + }, + }, + }; + + expect( getTemplateLock( state, 'ribs' ) ).toBe( null ); + } ); + + it( 'should return null if template lock was not set on the specified block', () => { + const state = { + settings: { templateLock: 'all' }, + blockListSettings: { + chicken: { + test: 'tes1t', + }, + }, + }; + + expect( getTemplateLock( state, 'ribs' ) ).toBe( null ); + } ); + + it( 'should return the template lock for the specified clientId', () => { + const state = { + settings: { templateLock: 'all' }, + blockListSettings: { + chicken: { + templateLock: 'insert', + }, + }, + }; + + expect( getTemplateLock( state, 'chicken' ) ).toBe( 'insert' ); + } ); + } ); + + describe( 'getBlockListSettings', () => { + it( 'should return the settings of a block', () => { + const state = { + blockListSettings: { + chicken: { + setting1: false, + }, + ribs: { + setting2: true, + }, + }, + }; + + expect( getBlockListSettings( state, 'chicken' ) ).toEqual( { + setting1: false, + } ); + } ); + + it( 'should return undefined if settings for the block don’t exist', () => { + const state = { + blockListSettings: {}, + }; + + expect( getBlockListSettings( state, 'chicken' ) ).toBe( undefined ); + } ); + } ); +} ); diff --git a/packages/data/src/plugins/persistence/index.js b/packages/data/src/plugins/persistence/index.js index 4a286e65ca71e..5ce7f41a37c2f 100644 --- a/packages/data/src/plugins/persistence/index.js +++ b/packages/data/src/plugins/persistence/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { flow, merge, isPlainObject } from 'lodash'; +import { flow, merge, isPlainObject, omit } from 'lodash'; /** * Internal dependencies @@ -115,7 +115,7 @@ export function createPersistenceInterface( options ) { * * @return {WPDataPlugin} Data plugin. */ -export default function( registry, pluginOptions ) { +const persistencePlugin = function( registry, pluginOptions ) { const persistence = createPersistenceInterface( pluginOptions ); /** @@ -201,4 +201,31 @@ export default function( registry, pluginOptions ) { return store; }, }; -} +}; + +/** + * Deprecated: Remove this function once WordPress 5.3 is released. + */ + +persistencePlugin.__unstableMigrate = ( pluginOptions ) => { + const persistence = createPersistenceInterface( pluginOptions ); + + // Preferences migration to introduce the block editor module + const persistedState = persistence.get(); + const coreEditorState = persistedState[ 'core/editor' ]; + if ( coreEditorState && coreEditorState.preferences && coreEditorState.preferences.insertUsage ) { + const blockEditorState = { + preferences: { + insertUsage: coreEditorState.preferences.insertUsage, + }, + }; + + persistence.set( 'core/editor', { + ...coreEditorState, + preferences: omit( coreEditorState.preferences, [ 'insertUsage' ] ), + } ); + persistence.set( 'core/block-editor', blockEditorState ); + } +}; + +export default persistencePlugin; diff --git a/packages/e2e-tests/specs/blocks/preformatted.test.js b/packages/e2e-tests/specs/blocks/preformatted.test.js index 3397c9f75f68d..a5f9965af3c51 100644 --- a/packages/e2e-tests/specs/blocks/preformatted.test.js +++ b/packages/e2e-tests/specs/blocks/preformatted.test.js @@ -22,11 +22,14 @@ describe( 'Preformatted', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); await page.keyboard.press( 'Escape' ); + await page.waitForSelector( 'button[aria-label="More options"]' ); await page.click( 'button[aria-label="More options"]' ); await clickButton( 'Convert to Blocks' ); // Once it's edited, it should be saved as BR tags. await page.keyboard.type( '0' ); await page.keyboard.press( 'Enter' ); + await page.keyboard.press( 'Escape' ); + await page.waitForSelector( 'button[aria-label="More options"]' ); await page.click( 'button[aria-label="More options"]' ); await clickButton( 'Edit as HTML' ); diff --git a/packages/e2e-tests/specs/plugins/container-blocks.test.js b/packages/e2e-tests/specs/plugins/container-blocks.test.js index 2d32224bb3503..50c4d80854e74 100644 --- a/packages/e2e-tests/specs/plugins/container-blocks.test.js +++ b/packages/e2e-tests/specs/plugins/container-blocks.test.js @@ -31,6 +31,7 @@ describe( 'InnerBlocks Template Sync', () => { `; await insertBlock( blockName ); await switchEditorModeTo( 'Code' ); + await page.waitForSelector( '.editor-post-text-editor' ); await page.$eval( '.editor-post-text-editor', ( element, _paragraph, _blockSlug ) => { const blockDelimiter = `<!-- /wp:${ _blockSlug } -->`; element.value = element.value.replace( blockDelimiter, `${ _paragraph }${ blockDelimiter }` ); diff --git a/packages/edit-post/src/editor.js b/packages/edit-post/src/editor.js index 74e035fb2fd52..3e51956a0e074 100644 --- a/packages/edit-post/src/editor.js +++ b/packages/edit-post/src/editor.js @@ -1,9 +1,14 @@ +/** + * External dependencies + */ +import memize from 'memize'; + /** * WordPress dependencies */ import { withSelect } from '@wordpress/data'; import { EditorProvider, ErrorBoundary, PostLockedModal } from '@wordpress/editor'; -import { StrictMode } from '@wordpress/element'; +import { StrictMode, Component } from '@wordpress/element'; import { KeyboardShortcuts } from '@wordpress/components'; /** @@ -12,41 +17,61 @@ import { KeyboardShortcuts } from '@wordpress/components'; import preventEventDiscovery from './prevent-event-discovery'; import Layout from './components/layout'; -function Editor( { - settings, - hasFixedToolbar, - focusMode, - post, - initialEdits, - onError, - ...props -} ) { - if ( ! post ) { - return null; +class Editor extends Component { + constructor() { + super( ...arguments ); + + this.getEditorSettings = memize( this.getEditorSettings, { + maxSize: 1, + } ); + } + + getEditorSettings( settings, hasFixedToolbar, focusMode ) { + return { + ...settings, + hasFixedToolbar, + focusMode, + }; } - const editorSettings = { - ...settings, - hasFixedToolbar, - focusMode, - }; - - return ( - <StrictMode> - <EditorProvider - settings={ editorSettings } - post={ post } - initialEdits={ initialEdits } - { ...props } - > - <ErrorBoundary onError={ onError }> - <Layout /> - <KeyboardShortcuts shortcuts={ preventEventDiscovery } /> - </ErrorBoundary> - <PostLockedModal /> - </EditorProvider> - </StrictMode> - ); + render() { + const { + settings, + hasFixedToolbar, + focusMode, + post, + initialEdits, + onError, + ...props + } = this.props; + + if ( ! post ) { + return null; + } + + const editorSettings = { + ...settings, + hasFixedToolbar, + focusMode, + }; + + return ( + <StrictMode> + <EditorProvider + settings={ editorSettings } + post={ post } + initialEdits={ initialEdits } + { ...props } + > + <ErrorBoundary onError={ onError }> + <Layout /> + <KeyboardShortcuts shortcuts={ preventEventDiscovery } /> + </ErrorBoundary> + <PostLockedModal /> + </EditorProvider> + </StrictMode> + ); + } } export default withSelect( ( select, { postId, postType } ) => ( { diff --git a/packages/editor/package.json b/packages/editor/package.json index c82d754733f61..acbb00bec5532 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -24,6 +24,7 @@ "@wordpress/a11y": "file:../a11y", "@wordpress/api-fetch": "file:../api-fetch", "@wordpress/blob": "file:../blob", + "@wordpress/block-editor": "file:../block-editor", "@wordpress/blocks": "file:../blocks", "@wordpress/components": "file:../components", "@wordpress/compose": "file:../compose", diff --git a/packages/editor/src/components/block-list/block.js b/packages/editor/src/components/block-list/block.js index fc79575258011..22b037c7e2e52 100644 --- a/packages/editor/src/components/block-list/block.js +++ b/packages/editor/src/components/block-list/block.js @@ -693,7 +693,6 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, { select } ) => { removeBlock, mergeBlocks, replaceBlocks, - editPost, toggleSelection, } = dispatch( 'core/editor' ); @@ -749,8 +748,10 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, { select } ) => { onReplace( blocks ) { replaceBlocks( [ ownProps.clientId ], blocks ); }, - onMetaChange( meta ) { - editPost( { meta } ); + onMetaChange( updatedMeta ) { + const { getEditorSettings } = select( 'core/editor' ); + const onChangeMeta = getEditorSettings().__experimentalMetaSource.onChange; + onChangeMeta( updatedMeta ); }, onShiftSelection() { if ( ! ownProps.isSelectionEnabled ) { diff --git a/packages/editor/src/components/post-text-editor/index.js b/packages/editor/src/components/post-text-editor/index.js index c08e59f35a400..13926f4b79e85 100644 --- a/packages/editor/src/components/post-text-editor/index.js +++ b/packages/editor/src/components/post-text-editor/index.js @@ -65,7 +65,6 @@ export class PostTextEditor extends Component { render() { const { value } = this.state; const { instanceId } = this.props; - return ( <Fragment> <label htmlFor={ `post-content-${ instanceId }` } className="screen-reader-text"> @@ -94,13 +93,14 @@ export default compose( [ }; } ), withDispatch( ( dispatch ) => { - const { editPost, resetBlocks } = dispatch( 'core/editor' ); + const { editPost, resetEditorBlocks } = dispatch( 'core/editor' ); return { onChange( content ) { editPost( { content } ); }, onPersist( content ) { - resetBlocks( parse( content ) ); + const blocks = parse( content ); + resetEditorBlocks( blocks ); }, }; } ), diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index f22f129cc8acf..62bf481698b0a 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -1,33 +1,37 @@ /** * External dependencies */ -import { flow, map } from 'lodash'; +import { map } from 'lodash'; +import memize from 'memize'; /** * WordPress dependencies */ -import { createElement, Component } from '@wordpress/element'; -import { DropZoneProvider, SlotFillProvider } from '@wordpress/components'; -import { withDispatch } from '@wordpress/data'; +import { compose } from '@wordpress/compose'; +import { Component } from '@wordpress/element'; +import { withDispatch, withSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; +import { BlockEditorProvider } from '@wordpress/block-editor'; /** * Internal dependencies */ import transformStyles from '../../editor-styles'; - class EditorProvider extends Component { constructor( props ) { super( ...arguments ); + this.getBlockEditorSettings = memize( this.getBlockEditorSettings, { + maxSize: 1, + } ); + // Assume that we don't need to initialize in the case of an error recovery. if ( props.recovery ) { return; } - props.updateEditorSettings( props.settings ); props.updatePostLock( props.settings.postLock ); - props.setupEditor( props.post, props.initialEdits ); + props.setupEditor( props.post, props.initialEdits, props.settings.template ); if ( props.settings.autosave ) { props.createWarningNotice( @@ -45,6 +49,17 @@ class EditorProvider extends Component { } } + getBlockEditorSettings( settings, meta, onMetaChange, reusableBlocks ) { + return { + ...settings, + __experimentalMetaSource: { + value: meta, + onChange: onMetaChange, + }, + __experimentalReusableBlocks: reusableBlocks, + }; + } + componentDidMount() { if ( ! this.props.settings.styles ) { return; @@ -61,55 +76,77 @@ class EditorProvider extends Component { } ); } - componentDidUpdate( prevProps ) { - if ( this.props.settings !== prevProps.settings ) { - this.props.updateEditorSettings( this.props.settings ); - } - } - render() { const { children, + blocks, + resetEditorBlocks, + isReady, + settings, + meta, + onMetaChange, + reusableBlocks, + resetEditorBlocksWithoutUndoLevel, } = this.props; - const providers = [ - // Slot / Fill provider: - // - // - context.getSlot - // - context.registerSlot - // - context.unregisterSlot - [ - SlotFillProvider, - ], - - // DropZone provider: - [ - DropZoneProvider, - ], - ]; - - const createEditorElement = flow( - providers.map( ( [ Provider, props ] ) => ( - ( arg ) => createElement( Provider, props, arg ) - ) ) + if ( ! isReady ) { + return null; + } + + const editorSettings = this.getBlockEditorSettings( + settings, meta, onMetaChange, reusableBlocks ); - return createEditorElement( children ); + return ( + <BlockEditorProvider + value={ blocks } + onInput={ resetEditorBlocksWithoutUndoLevel } + onChange={ resetEditorBlocks } + settings={ editorSettings } + > + { children } + </BlockEditorProvider> + ); } } -export default withDispatch( ( dispatch ) => { - const { - setupEditor, - updateEditorSettings, - updatePostLock, - } = dispatch( 'core/editor' ); - const { createWarningNotice } = dispatch( 'core/notices' ); - - return { - setupEditor, - updateEditorSettings, - updatePostLock, - createWarningNotice, - }; -} )( EditorProvider ); +export default compose( [ + withSelect( ( select ) => { + const { + __unstableIsEditorReady: isEditorReady, + getEditorBlocks, + getEditedPostAttribute, + __experimentalGetReusableBlocks, + } = select( 'core/editor' ); + return { + isReady: isEditorReady(), + blocks: getEditorBlocks(), + meta: getEditedPostAttribute( 'meta' ), + reusableBlocks: __experimentalGetReusableBlocks(), + }; + } ), + withDispatch( ( dispatch ) => { + const { + setupEditor, + updatePostLock, + resetEditorBlocks, + editPost, + } = dispatch( 'core/editor' ); + const { createWarningNotice } = dispatch( 'core/notices' ); + + return { + setupEditor, + updatePostLock, + createWarningNotice, + resetEditorBlocks, + resetEditorBlocksWithoutUndoLevel( blocks ) { + resetEditorBlocks( blocks, { + __unstableShouldCreateUndoLevel: false, + } ); + }, + onMetaChange( meta ) { + editPost( { meta } ); + }, + }; + } ), +] )( EditorProvider ); diff --git a/packages/editor/src/components/rich-text/index.js b/packages/editor/src/components/rich-text/index.js index 724c9f26e30e6..30cc785d63448 100644 --- a/packages/editor/src/components/rich-text/index.js +++ b/packages/editor/src/components/rich-text/index.js @@ -1102,17 +1102,13 @@ const RichTextContainer = compose( [ } ), withDispatch( ( dispatch ) => { const { - createUndoLevel, - redo, - undo, + __unstableMarkLastChangeAsPersistent, enterFormattedText, exitFormattedText, - } = dispatch( 'core/editor' ); + } = dispatch( 'core/block-editor' ); return { - onCreateUndoLevel: createUndoLevel, - onRedo: redo, - onUndo: undo, + onCreateUndoLevel: __unstableMarkLastChangeAsPersistent, onEnterFormattedText: enterFormattedText, onExitFormattedText: exitFormattedText, }; diff --git a/packages/editor/src/index.js b/packages/editor/src/index.js index 7a88f75dbccb5..1f5baad7285f2 100644 --- a/packages/editor/src/index.js +++ b/packages/editor/src/index.js @@ -1,6 +1,7 @@ /** * WordPress dependencies */ +import '@wordpress/block-editor'; import '@wordpress/blocks'; import '@wordpress/core-data'; import '@wordpress/notices'; diff --git a/packages/editor/src/store/actions.js b/packages/editor/src/store/actions.js index b65774fe4c219..0d508375c37c8 100644 --- a/packages/editor/src/store/actions.js +++ b/packages/editor/src/store/actions.js @@ -3,30 +3,27 @@ */ import { castArray } from 'lodash'; -/** - * WordPress dependencies - */ -import { getDefaultBlockName, createBlock } from '@wordpress/blocks'; - /** * Internal dependencies */ -import { select } from './controls'; +import { dispatch } from './controls'; /** * Returns an action object used in signalling that editor has initialized with * the specified post object and editor settings. * - * @param {Object} post Post object. - * @param {Object} edits Initial edited attributes object. + * @param {Object} post Post object. + * @param {Object} edits Initial edited attributes object. + * @param {Array?} template Block Template. * * @return {Object} Action object. */ -export function setupEditor( post, edits ) { +export function setupEditor( post, edits, template ) { return { type: 'SETUP_EDITOR', post, edits, + template, }; } @@ -79,338 +76,13 @@ export function updatePost( edits ) { * Returns an action object used to setup the editor state when first opening an editor. * * @param {Object} post Post object. - * @param {Array} blocks Array of blocks. * * @return {Object} Action object. */ -export function setupEditorState( post, blocks ) { +export function setupEditorState( post ) { return { type: 'SETUP_EDITOR_STATE', post, - blocks, - }; -} - -/** - * Returns an action object used in signalling that blocks state should be - * reset to the specified array of blocks, taking precedence over any other - * content reflected as an edit in state. - * - * @param {Array} blocks Array of blocks. - * - * @return {Object} Action object. - */ -export function resetBlocks( blocks ) { - return { - type: 'RESET_BLOCKS', - blocks, - }; -} - -/** - * Returns an action object used in signalling that blocks have been received. - * Unlike resetBlocks, these should be appended to the existing known set, not - * replacing. - * - * @param {Object[]} blocks Array of block objects. - * - * @return {Object} Action object. - */ -export function receiveBlocks( blocks ) { - return { - type: 'RECEIVE_BLOCKS', - blocks, - }; -} - -/** - * Returns an action object used in signalling that the block attributes with - * the specified client ID has been updated. - * - * @param {string} clientId Block client ID. - * @param {Object} attributes Block attributes to be merged. - * - * @return {Object} Action object. - */ -export function updateBlockAttributes( clientId, attributes ) { - return { - type: 'UPDATE_BLOCK_ATTRIBUTES', - clientId, - attributes, - }; -} - -/** - * Returns an action object used in signalling that the block with the - * specified client ID has been updated. - * - * @param {string} clientId Block client ID. - * @param {Object} updates Block attributes to be merged. - * - * @return {Object} Action object. - */ -export function updateBlock( clientId, updates ) { - return { - type: 'UPDATE_BLOCK', - clientId, - updates, - }; -} - -/** - * Returns an action object used in signalling that the block with the - * specified client ID has been selected, optionally accepting a position - * value reflecting its selection directionality. An initialPosition of -1 - * reflects a reverse selection. - * - * @param {string} clientId Block client ID. - * @param {?number} initialPosition Optional initial position. Pass as -1 to - * reflect reverse selection. - * - * @return {Object} Action object. - */ -export function selectBlock( clientId, initialPosition = null ) { - return { - type: 'SELECT_BLOCK', - initialPosition, - clientId, - }; -} - -/** - * Yields action objects used in signalling that the block preceding the given - * clientId should be selected. - * - * @param {string} clientId Block client ID. - */ -export function* selectPreviousBlock( clientId ) { - const previousBlockClientId = yield select( - 'core/editor', - 'getPreviousBlockClientId', - clientId - ); - - yield selectBlock( previousBlockClientId, -1 ); -} - -/** - * Yields action objects used in signalling that the block following the given - * clientId should be selected. - * - * @param {string} clientId Block client ID. - */ -export function* selectNextBlock( clientId ) { - const nextBlockClientId = yield select( - 'core/editor', - 'getNextBlockClientId', - clientId - ); - - yield selectBlock( nextBlockClientId ); -} - -export function startMultiSelect() { - return { - type: 'START_MULTI_SELECT', - }; -} - -export function stopMultiSelect() { - return { - type: 'STOP_MULTI_SELECT', - }; -} - -export function multiSelect( start, end ) { - return { - type: 'MULTI_SELECT', - start, - end, - }; -} - -export function clearSelectedBlock() { - return { - type: 'CLEAR_SELECTED_BLOCK', - }; -} - -/** - * Returns an action object that enables or disables block selection. - * - * @param {boolean} [isSelectionEnabled=true] Whether block selection should - * be enabled. - - * @return {Object} Action object. - */ -export function toggleSelection( isSelectionEnabled = true ) { - return { - type: 'TOGGLE_SELECTION', - isSelectionEnabled, - }; -} - -/** - * Returns an action object signalling that a blocks should be replaced with - * one or more replacement blocks. - * - * @param {(string|string[])} clientIds Block client ID(s) to replace. - * @param {(Object|Object[])} blocks Replacement block(s). - * - * @return {Object} Action object. - */ -export function replaceBlocks( clientIds, blocks ) { - return { - type: 'REPLACE_BLOCKS', - clientIds: castArray( clientIds ), - blocks: castArray( blocks ), - time: Date.now(), - }; -} - -/** - * Returns an action object signalling that a single block should be replaced - * with one or more replacement blocks. - * - * @param {(string|string[])} clientId Block client ID to replace. - * @param {(Object|Object[])} block Replacement block(s). - * - * @return {Object} Action object. - */ -export function replaceBlock( clientId, block ) { - return replaceBlocks( clientId, block ); -} - -/** - * Higher-order action creator which, given the action type to dispatch creates - * an action creator for managing block movement. - * - * @param {string} type Action type to dispatch. - * - * @return {Function} Action creator. - */ -function createOnMove( type ) { - return ( clientIds, rootClientId ) => { - return { - clientIds: castArray( clientIds ), - type, - rootClientId, - }; - }; -} - -export const moveBlocksDown = createOnMove( 'MOVE_BLOCKS_DOWN' ); -export const moveBlocksUp = createOnMove( 'MOVE_BLOCKS_UP' ); - -/** - * Returns an action object signalling that an indexed block should be moved - * to a new index. - * - * @param {?string} clientId The client ID of the block. - * @param {?string} fromRootClientId Root client ID source. - * @param {?string} toRootClientId Root client ID destination. - * @param {number} index The index to move the block into. - * - * @return {Object} Action object. - */ -export function moveBlockToPosition( clientId, fromRootClientId, toRootClientId, index ) { - return { - type: 'MOVE_BLOCK_TO_POSITION', - fromRootClientId, - toRootClientId, - clientId, - index, - }; -} - -/** - * Returns an action object used in signalling that a single block should be - * inserted, optionally at a specific index respective a root block list. - * - * @param {Object} block Block object to insert. - * @param {?number} index Index at which block should be inserted. - * @param {?string} rootClientId Optional root client ID of block list on which to insert. - * @param {?boolean} updateSelection If true block selection will be updated. If false, block selection will not change. Defaults to true. - * - * @return {Object} Action object. - */ -export function insertBlock( block, index, rootClientId, updateSelection = true ) { - return insertBlocks( [ block ], index, rootClientId, updateSelection ); -} - -/** - * Returns an action object used in signalling that an array of blocks should - * be inserted, optionally at a specific index respective a root block list. - * - * @param {Object[]} blocks Block objects to insert. - * @param {?number} index Index at which block should be inserted. - * @param {?string} rootClientId Optional root client ID of block list on which to insert. - * @param {?boolean} updateSelection If true block selection will be updated. If false, block selection will not change. Defaults to true. - * - * @return {Object} Action object. - */ -export function insertBlocks( blocks, index, rootClientId, updateSelection = true ) { - return { - type: 'INSERT_BLOCKS', - blocks: castArray( blocks ), - index, - rootClientId, - time: Date.now(), - updateSelection, - }; -} - -/** - * Returns an action object used in signalling that the insertion point should - * be shown. - * - * @param {?string} rootClientId Optional root client ID of block list on - * which to insert. - * @param {?number} index Index at which block should be inserted. - * - * @return {Object} Action object. - */ -export function showInsertionPoint( rootClientId, index ) { - return { - type: 'SHOW_INSERTION_POINT', - rootClientId, - index, - }; -} - -/** - * Returns an action object hiding the insertion point. - * - * @return {Object} Action object. - */ -export function hideInsertionPoint() { - return { - type: 'HIDE_INSERTION_POINT', - }; -} - -/** - * Returns an action object resetting the template validity. - * - * @param {boolean} isValid template validity flag. - * - * @return {Object} Action object. - */ -export function setTemplateValidity( isValid ) { - return { - type: 'SET_TEMPLATE_VALIDITY', - isValid, - }; -} - -/** - * Returns an action object synchronize the template with the list of blocks - * - * @return {Object} Action object. - */ -export function synchronizeTemplate() { - return { - type: 'SYNCHRONIZE_TEMPLATE', }; } @@ -458,21 +130,6 @@ export function trashPost( postId, postType ) { }; } -/** - * Returns an action object used in signalling that two blocks should be merged - * - * @param {string} firstBlockClientId Client ID of the first block to merge. - * @param {string} secondBlockClientId Client ID of the second block to merge. - * - * @return {Object} Action object. - */ -export function mergeBlocks( firstBlockClientId, secondBlockClientId ) { - return { - type: 'MERGE_BLOCKS', - blocks: [ firstBlockClientId, secondBlockClientId ], - }; -} - /** * Returns an action object used in signalling that the post should autosave. * @@ -513,100 +170,6 @@ export function createUndoLevel() { return { type: 'CREATE_UNDO_LEVEL' }; } -/** - * Yields action objects used in signalling that the blocks corresponding to - * the set of specified client IDs are to be removed. - * - * @param {string|string[]} clientIds Client IDs of blocks to remove. - * @param {boolean} selectPrevious True if the previous block should be - * selected when a block is removed. - */ -export function* removeBlocks( clientIds, selectPrevious = true ) { - clientIds = castArray( clientIds ); - - if ( selectPrevious ) { - yield selectPreviousBlock( clientIds[ 0 ] ); - } - - yield { - type: 'REMOVE_BLOCKS', - clientIds, - }; -} - -/** - * Returns an action object used in signalling that the block with the - * specified client ID is to be removed. - * - * @param {string} clientId Client ID of block to remove. - * @param {boolean} selectPrevious True if the previous block should be - * selected when a block is removed. - * - * @return {Object} Action object. - */ -export function removeBlock( clientId, selectPrevious ) { - return removeBlocks( [ clientId ], selectPrevious ); -} - -/** - * Returns an action object used to toggle the block editing mode between - * visual and HTML modes. - * - * @param {string} clientId Block client ID. - * - * @return {Object} Action object. - */ -export function toggleBlockMode( clientId ) { - return { - type: 'TOGGLE_BLOCK_MODE', - clientId, - }; -} - -/** - * Returns an action object used in signalling that the user has begun to type. - * - * @return {Object} Action object. - */ -export function startTyping() { - return { - type: 'START_TYPING', - }; -} - -/** - * Returns an action object used in signalling that the user has stopped typing. - * - * @return {Object} Action object. - */ -export function stopTyping() { - return { - type: 'STOP_TYPING', - }; -} - -/** - * Returns an action object used in signalling that the caret has entered formatted text. - * - * @return {Object} Action object. - */ -export function enterFormattedText() { - return { - type: 'ENTER_FORMATTED_TEXT', - }; -} - -/** - * Returns an action object used in signalling that the user caret has exited formatted text. - * - * @return {Object} Action object. - */ -export function exitFormattedText() { - return { - type: 'EXIT_FORMATTED_TEXT', - }; -} - /** * Returns an action object used to lock the editor. * @@ -727,53 +290,6 @@ export function __experimentalConvertBlockToReusable( clientIds ) { clientIds: castArray( clientIds ), }; } -/** - * Returns an action object used in signalling that a new block of the default - * type should be added to the block list. - * - * @param {?Object} attributes Optional attributes of the block to assign. - * @param {?string} rootClientId Optional root client ID of block list on which - * to append. - * @param {?number} index Optional index where to insert the default block - * - * @return {Object} Action object - */ -export function insertDefaultBlock( attributes, rootClientId, index ) { - const block = createBlock( getDefaultBlockName(), attributes ); - - return insertBlock( block, index, rootClientId ); -} - -/** - * Returns an action object that changes the nested settings of a given block. - * - * @param {string} clientId Client ID of the block whose nested setting are - * being received. - * @param {Object} settings Object with the new settings for the nested block. - * - * @return {Object} Action object - */ -export function updateBlockListSettings( clientId, settings ) { - return { - type: 'UPDATE_BLOCK_LIST_SETTINGS', - clientId, - settings, - }; -} - -/* - * Returns an action object used in signalling that the editor settings have been updated. - * - * @param {Object} settings Updated settings - * - * @return {Object} Action object - */ -export function updateEditorSettings( settings ) { - return { - type: 'UPDATE_EDITOR_SETTINGS', - settings, - }; -} /** * Returns an action object used in signalling that the user has enabled the publish sidebar. @@ -824,3 +340,59 @@ export function unlockPostSaving( lockName ) { lockName, }; } + +/** + * Returns an action object used to signal that the blocks have been updated. + * + * @param {Array} blocks Block Array. + * @param {?Object} options Optional options. + * + * @return {Object} Action object + */ +export function resetEditorBlocks( blocks, options = {} ) { + return { + type: 'RESET_EDITOR_BLOCKS', + blocks, + shouldCreateUndoLevel: options.__unstableShouldCreateUndoLevel !== false, + }; +} + +/** + * Backward compatibility + */ + +const getBlockEditorAction = ( name ) => function* ( ...args ) { + yield dispatch( 'core/block-editor', name, ...args ); +}; + +export const resetBlocks = getBlockEditorAction( 'resetBlocks' ); +export const receiveBlocks = getBlockEditorAction( 'receiveBlocks' ); +export const updateBlock = getBlockEditorAction( 'updateBlock' ); +export const updateBlockAttributes = getBlockEditorAction( 'updateBlockAttributes' ); +export const selectBlock = getBlockEditorAction( 'selectBlock' ); +export const startMultiSelect = getBlockEditorAction( 'startMultiSelect' ); +export const stopMultiSelect = getBlockEditorAction( 'stopMultiSelect' ); +export const multiSelect = getBlockEditorAction( 'multiSelect' ); +export const clearSelectedBlock = getBlockEditorAction( 'clearSelectedBlock' ); +export const toggleSelection = getBlockEditorAction( 'toggleSelection' ); +export const replaceBlocks = getBlockEditorAction( 'replaceBlocks' ); +export const moveBlocksDown = getBlockEditorAction( 'moveBlocksDown' ); +export const moveBlocksUp = getBlockEditorAction( 'moveBlocksUp' ); +export const moveBlockToPosition = getBlockEditorAction( 'moveBlockToPosition' ); +export const insertBlock = getBlockEditorAction( 'insertBlock' ); +export const insertBlocks = getBlockEditorAction( 'insertBlocks' ); +export const showInsertionPoint = getBlockEditorAction( 'showInsertionPoint' ); +export const hideInsertionPoint = getBlockEditorAction( 'hideInsertionPoint' ); +export const setTemplateValidity = getBlockEditorAction( 'setTemplateValidity' ); +export const synchronizeTemplate = getBlockEditorAction( 'synchronizeTemplate' ); +export const mergeBlocks = getBlockEditorAction( 'mergeBlocks' ); +export const removeBlocks = getBlockEditorAction( 'removeBlocks' ); +export const removeBlock = getBlockEditorAction( 'removeBlock' ); +export const toggleBlockMode = getBlockEditorAction( 'toggleBlockMode' ); +export const startTyping = getBlockEditorAction( 'startTyping' ); +export const stopTyping = getBlockEditorAction( 'stopTyping' ); +export const enterFormattedText = getBlockEditorAction( 'enterFormattedText' ); +export const exitFormattedText = getBlockEditorAction( 'exitFormattedText' ); +export const insertDefaultBlock = getBlockEditorAction( 'insertDefaultBlock' ); +export const updateBlockListSettings = getBlockEditorAction( 'updateBlockListSettings' ); +export const updateEditorSettings = getBlockEditorAction( 'updateEditorSettings' ); diff --git a/packages/editor/src/store/controls.js b/packages/editor/src/store/controls.js index 5012ab244c21c..fc873ad43aa39 100644 --- a/packages/editor/src/store/controls.js +++ b/packages/editor/src/store/controls.js @@ -4,26 +4,26 @@ import { createRegistryControl } from '@wordpress/data'; /** - * Calls a selector using the current state. + * Dispatches an action. * - * @param {string} storeName Store name. - * @param {string} selectorName Selector name. - * @param {Array} args Selector arguments. + * @param {string} storeKey Store key. + * @param {string} actionName Action name. + * @param {Array} args Action arguments. * * @return {Object} control descriptor. */ -export function select( storeName, selectorName, ...args ) { +export function dispatch( storeKey, actionName, ...args ) { return { - type: 'SELECT', - storeName, - selectorName, + type: 'DISPATCH', + storeKey, + actionName, args, }; } const controls = { - SELECT: createRegistryControl( ( registry ) => ( { storeName, selectorName, args } ) => { - return registry.select( storeName )[ selectorName ]( ...args ); + DISPATCH: createRegistryControl( ( registry ) => ( { storeKey, actionName, args } ) => { + return registry.dispatch( storeKey )[ actionName ]( ...args ); } ), }; diff --git a/packages/editor/src/store/defaults.js b/packages/editor/src/store/defaults.js index dc4111ef641d4..32fd438302c69 100644 --- a/packages/editor/src/store/defaults.js +++ b/packages/editor/src/store/defaults.js @@ -1,137 +1,7 @@ -/** - * WordPress dependencies - */ -import { __, _x } from '@wordpress/i18n'; - export const PREFERENCES_DEFAULTS = { - insertUsage: {}, isPublishSidebarEnabled: true, }; -/** - * The default editor settings - * - * alignWide boolean Enable/Disable Wide/Full Alignments - * colors Array Palette colors - * fontSizes Array Available font sizes - * imageSizes Array Available image sizes - * maxWidth number Max width to constraint resizing - * blockTypes boolean|Array Allowed block types - * hasFixedToolbar boolean Whether or not the editor toolbar is fixed - * focusMode boolean Whether the focus mode is enabled or not - * richEditingEnabled boolean Whether rich editing is enabled or not - */ -export const EDITOR_SETTINGS_DEFAULTS = { - alignWide: false, - colors: [ - { - name: __( 'Pale pink' ), - slug: 'pale-pink', - color: '#f78da7', - }, - { name: __( 'Vivid red' ), - slug: 'vivid-red', - color: '#cf2e2e', - }, - { - name: __( 'Luminous vivid orange' ), - slug: 'luminous-vivid-orange', - color: '#ff6900', - }, - { - name: __( 'Luminous vivid amber' ), - slug: 'luminous-vivid-amber', - color: '#fcb900', - }, - { - name: __( 'Light green cyan' ), - slug: 'light-green-cyan', - color: '#7bdcb5', - }, - { - name: __( 'Vivid green cyan' ), - slug: 'vivid-green-cyan', - color: '#00d084', - }, - { - name: __( 'Pale cyan blue' ), - slug: 'pale-cyan-blue', - color: '#8ed1fc', - }, - { - name: __( 'Vivid cyan blue' ), - slug: 'vivid-cyan-blue', - color: '#0693e3', - }, - { - name: __( 'Very light gray' ), - slug: 'very-light-gray', - color: '#eeeeee', - }, - { - name: __( 'Cyan bluish gray' ), - slug: 'cyan-bluish-gray', - color: '#abb8c3', - }, - { - name: __( 'Very dark gray' ), - slug: 'very-dark-gray', - color: '#313131', - }, - ], - - fontSizes: [ - { - name: _x( 'Small', 'font size name' ), - size: 13, - slug: 'small', - }, - { - name: _x( 'Normal', 'font size name' ), - size: 16, - slug: 'normal', - }, - { - name: _x( 'Medium', 'font size name' ), - size: 20, - slug: 'medium', - }, - { - name: _x( 'Large', 'font size name' ), - size: 36, - slug: 'large', - }, - { - name: _x( 'Huge', 'font size name' ), - size: 48, - slug: 'huge', - }, - ], - - imageSizes: [ - { slug: 'thumbnail', label: __( 'Thumbnail' ) }, - { slug: 'medium', label: __( 'Medium' ) }, - { slug: 'large', label: __( 'Large' ) }, - { slug: 'full', label: __( 'Full Size' ) }, - ], - - // This is current max width of the block inner area - // It's used to constraint image resizing and this value could be overridden later by themes - maxWidth: 580, - - // Allowed block types for the editor, defaulting to true (all supported). - allowedBlockTypes: true, - - // Maximum upload size in bytes allowed for the site. - maxUploadFileSize: 0, - - // List of allowed mime types and file extensions. - allowedMimeTypes: null, - - // Whether richs editing is enabled or not. - richEditingEnabled: true, -}; - /** * Default initial edits state. * diff --git a/packages/editor/src/store/effects.js b/packages/editor/src/store/effects.js index de9552ff5c9b6..de77fbc45aab5 100644 --- a/packages/editor/src/store/effects.js +++ b/packages/editor/src/store/effects.js @@ -1,41 +1,23 @@ /** * External dependencies */ -import { compact, has } from 'lodash'; +import { has } from 'lodash'; /** * WordPress dependencies */ -import { speak } from '@wordpress/a11y'; import { parse, - getBlockType, - switchToBlockType, - doBlocksMatchTemplate, synchronizeBlocksWithTemplate, } from '@wordpress/blocks'; -import { _n, sprintf } from '@wordpress/i18n'; /** * Internal dependencies */ import { setupEditorState, - replaceBlocks, - selectBlock, - resetBlocks, - setTemplateValidity, - insertDefaultBlock, + resetEditorBlocks, } from './actions'; -import { - getBlock, - getBlocks, - getBlockCount, - getSelectedBlockCount, - getTemplate, - getTemplateLock, - isValidTemplate, -} from './selectors'; import { fetchReusableBlocks, saveReusableBlocks, @@ -53,53 +35,6 @@ import { refreshPost, } from './effects/posts'; -/** - * Block validity is a function of blocks state (at the point of a - * reset) and the template setting. As a compromise to its placement - * across distinct parts of state, it is implemented here as a side- - * effect of the block reset action. - * - * @param {Object} action RESET_BLOCKS action. - * @param {Object} store Store instance. - * - * @return {?Object} New validity set action if validity has changed. - */ -export function validateBlocksToTemplate( action, store ) { - const state = store.getState(); - const template = getTemplate( state ); - const templateLock = getTemplateLock( state ); - - // Unlocked templates are considered always valid because they act - // as default values only. - const isBlocksValidToTemplate = ( - ! template || - templateLock !== 'all' || - doBlocksMatchTemplate( action.blocks, template ) - ); - - // Update if validity has changed. - if ( isBlocksValidToTemplate !== isValidTemplate( state ) ) { - return setTemplateValidity( isBlocksValidToTemplate ); - } -} - -/** - * Effect handler which will return a default block insertion action if there - * are no other blocks at the root of the editor. This is expected to be used - * in actions which may result in no blocks remaining in the editor (removal, - * replacement, etc). - * - * @param {Object} action Action which had initiated the effect handler. - * @param {Object} store Store instance. - * - * @return {?Object} Default block insert action, if no other blocks exist. - */ -export function ensureDefaultBlock( action, store ) { - if ( ! getBlockCount( store.getState() ) ) { - return insertDefaultBlock(); - } -} - export default { REQUEST_POST_UPDATE: ( action, store ) => { requestPostUpdate( action, store ); @@ -113,55 +48,8 @@ export default { REFRESH_POST: ( action, store ) => { refreshPost( action, store ); }, - MERGE_BLOCKS( action, store ) { - const { dispatch } = store; - const state = store.getState(); - const [ firstBlockClientId, secondBlockClientId ] = action.blocks; - const blockA = getBlock( state, firstBlockClientId ); - const blockType = getBlockType( blockA.name ); - - // Only focus the previous block if it's not mergeable - if ( ! blockType.merge ) { - dispatch( selectBlock( blockA.clientId ) ); - return; - } - - // We can only merge blocks with similar types - // thus, we transform the block to merge first - const blockB = getBlock( state, secondBlockClientId ); - const blocksWithTheSameType = blockA.name === blockB.name ? - [ blockB ] : - switchToBlockType( blockB, blockA.name ); - - // If the block types can not match, do nothing - if ( ! blocksWithTheSameType || ! blocksWithTheSameType.length ) { - return; - } - - // Calling the merge to update the attributes and remove the block to be merged - const updatedAttributes = blockType.merge( - blockA.attributes, - blocksWithTheSameType[ 0 ].attributes - ); - - dispatch( selectBlock( blockA.clientId, -1 ) ); - dispatch( replaceBlocks( - [ blockA.clientId, blockB.clientId ], - [ - { - ...blockA, - attributes: { - ...blockA.attributes, - ...updatedAttributes, - }, - }, - ...blocksWithTheSameType.slice( 1 ), - ] - ) ); - }, - SETUP_EDITOR( action, store ) { - const { post, edits } = action; - const state = store.getState(); + SETUP_EDITOR( action ) { + const { post, edits, template } = action; // In order to ensure maximum of a single parse during setup, edits are // included as part of editor setup action. Assume edited content as @@ -177,33 +65,14 @@ export default { // Apply a template for new posts only, if exists. const isNewPost = post.status === 'auto-draft'; - const template = getTemplate( state ); if ( isNewPost && template ) { blocks = synchronizeBlocksWithTemplate( blocks, template ); } - const setupAction = setupEditorState( post, blocks ); - - return compact( [ - setupAction, - - // TODO: This is temporary, necessary only so long as editor setup - // is a separate action from block resetting. - // - // See: https://github.com/WordPress/gutenberg/pull/9403 - validateBlocksToTemplate( setupAction, store ), - ] ); - }, - RESET_BLOCKS: [ - validateBlocksToTemplate, - ], - SYNCHRONIZE_TEMPLATE( action, { getState } ) { - const state = getState(); - const blocks = getBlocks( state ); - const template = getTemplate( state ); - const updatedBlockList = synchronizeBlocksWithTemplate( blocks, template ); - - return resetBlocks( updatedBlockList ); + return [ + resetEditorBlocks( blocks ), + setupEditorState( post ), + ]; }, FETCH_REUSABLE_BLOCKS: ( action, store ) => { fetchReusableBlocks( action, store ); @@ -217,16 +86,4 @@ export default { RECEIVE_REUSABLE_BLOCKS: receiveReusableBlocks, CONVERT_BLOCK_TO_STATIC: convertBlockToStatic, CONVERT_BLOCK_TO_REUSABLE: convertBlockToReusable, - REMOVE_BLOCKS: [ - ensureDefaultBlock, - ], - REPLACE_BLOCKS: [ - ensureDefaultBlock, - ], - MULTI_SELECT: ( action, { getState } ) => { - const blockCount = getSelectedBlockCount( getState() ); - - /* translators: %s: number of selected blocks */ - speak( sprintf( _n( '%s block selected.', '%s blocks selected.', blockCount ), blockCount ), 'assertive' ); - }, }; diff --git a/packages/editor/src/store/effects/reusable-blocks.js b/packages/editor/src/store/effects/reusable-blocks.js index acfa3d0aaaefb..63bc00169c543 100644 --- a/packages/editor/src/store/effects/reusable-blocks.js +++ b/packages/editor/src/store/effects/reusable-blocks.js @@ -19,23 +19,17 @@ import { __ } from '@wordpress/i18n'; // TODO: Ideally this would be the only dispatch in scope. This requires either // refactoring editor actions to yielded controls, or replacing direct dispatch // on the editor store with action creators (e.g. `REMOVE_REUSABLE_BLOCK`). -import { dispatch as dataDispatch } from '@wordpress/data'; +import { dispatch as dataDispatch, select } from '@wordpress/data'; /** * Internal dependencies */ import { __experimentalReceiveReusableBlocks as receiveReusableBlocksAction, - removeBlocks, - replaceBlocks, - receiveBlocks, __experimentalSaveReusableBlock as saveReusableBlock, } from '../actions'; import { __experimentalGetReusableBlock as getReusableBlock, - getBlock, - getBlocks, - getBlocksByClientId, } from '../selectors'; import { getPostRawValue } from '../reducer'; @@ -122,7 +116,7 @@ export const saveReusableBlocks = async ( action, store ) => { const { dispatch } = store; const state = store.getState(); const { clientId, title, isTemporary } = getReusableBlock( state, id ); - const reusableBlock = getBlock( state, clientId ); + const reusableBlock = select( 'core/block-editor' ).getBlock( clientId ); const content = serialize( reusableBlock.name === 'core/template' ? reusableBlock.innerBlocks : reusableBlock ); const data = isTemporary ? { title, content, status: 'publish' } : { id, title, content, status: 'publish' }; @@ -140,6 +134,8 @@ export const saveReusableBlocks = async ( action, store ) => { dataDispatch( 'core/notices' ).createSuccessNotice( message, { id: REUSABLE_BLOCK_NOTICE_ID, } ); + + dataDispatch( 'core/block-editor' ).__unstableSaveReusableBlock( id, updatedReusableBlock.id ); } catch ( error ) { dispatch( { type: 'SAVE_REUSABLE_BLOCK_FAILURE', id } ); dataDispatch( 'core/notices' ).createErrorNotice( error.message, { @@ -172,7 +168,7 @@ export const deleteReusableBlocks = async ( action, store ) => { } // Remove any other blocks that reference this reusable block - const allBlocks = getBlocks( getState() ); + const allBlocks = select( 'core/block-editor' ).getBlocks(); const associatedBlocks = allBlocks.filter( ( block ) => isReusableBlock( block ) && block.attributes.ref === id ); const associatedBlockClientIds = associatedBlocks.map( ( block ) => block.clientId ); @@ -185,10 +181,10 @@ export const deleteReusableBlocks = async ( action, store ) => { } ); // Remove the parsed block. - dispatch( removeBlocks( [ + dataDispatch( 'core/block-editor' ).removeBlocks( [ ...associatedBlockClientIds, reusableBlock.clientId, - ] ) ); + ] ); try { await apiFetch( { @@ -220,10 +216,9 @@ export const deleteReusableBlocks = async ( action, store ) => { * Receive Reusable Blocks Effect Handler. * * @param {Object} action action object. - * @return {Object} receive blocks action */ export const receiveReusableBlocks = ( action ) => { - return receiveBlocks( map( action.results, 'parsedBlock' ) ); + dataDispatch( 'core/block-editor' ).receiveBlocks( map( action.results, 'parsedBlock' ) ); }; /** @@ -234,16 +229,16 @@ export const receiveReusableBlocks = ( action ) => { */ export const convertBlockToStatic = ( action, store ) => { const state = store.getState(); - const oldBlock = getBlock( state, action.clientId ); + const oldBlock = select( 'core/block-editor' ).getBlock( action.clientId ); const reusableBlock = getReusableBlock( state, oldBlock.attributes.ref ); - const referencedBlock = getBlock( state, reusableBlock.clientId ); + const referencedBlock = select( 'core/block-editor' ).getBlock( reusableBlock.clientId ); let newBlocks; if ( referencedBlock.name === 'core/template' ) { newBlocks = referencedBlock.innerBlocks.map( ( innerBlock ) => cloneBlock( innerBlock ) ); } else { newBlocks = [ cloneBlock( referencedBlock ) ]; } - store.dispatch( replaceBlocks( oldBlock.clientId, newBlocks ) ); + dataDispatch( 'core/block-editor' ).replaceBlocks( oldBlock.clientId, newBlocks ); }; /** @@ -253,20 +248,20 @@ export const convertBlockToStatic = ( action, store ) => { * @param {Object} store Redux Store. */ export const convertBlockToReusable = ( action, store ) => { - const { getState, dispatch } = store; + const { dispatch } = store; let parsedBlock; if ( action.clientIds.length === 1 ) { - parsedBlock = getBlock( getState(), action.clientIds[ 0 ] ); + parsedBlock = select( 'core/block-editor' ).getBlock( action.clientIds[ 0 ] ); } else { parsedBlock = createBlock( 'core/template', {}, - getBlocksByClientId( getState(), action.clientIds ) + select( 'core/block-editor' ).getBlocksByClientId( action.clientIds ) ); // This shouldn't be necessary but at the moment // we expect the content of the shared blocks to live in the blocks state. - dispatch( receiveBlocks( [ parsedBlock ] ) ); + dataDispatch( 'core/block-editor' ).receiveBlocks( [ parsedBlock ] ); } const reusableBlock = { @@ -282,13 +277,13 @@ export const convertBlockToReusable = ( action, store ) => { dispatch( saveReusableBlock( reusableBlock.id ) ); - dispatch( replaceBlocks( + dataDispatch( 'core/block-editor' ).replaceBlocks( action.clientIds, createBlock( 'core/block', { ref: reusableBlock.id, } ) - ) ); + ); // Re-add the original block to the store, since replaceBlock() will have removed it - dispatch( receiveBlocks( [ parsedBlock ] ) ); + dataDispatch( 'core/block-editor' ).receiveBlocks( [ parsedBlock ] ); }; diff --git a/packages/editor/src/store/effects/test/reusable-blocks.js b/packages/editor/src/store/effects/test/reusable-blocks.js index 071e4f07f4f6f..1d783b1dd9fe1 100644 --- a/packages/editor/src/store/effects/test/reusable-blocks.js +++ b/packages/editor/src/store/effects/test/reusable-blocks.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { noop, reduce } from 'lodash'; +import { noop } from 'lodash'; /** * WordPress dependencies @@ -12,6 +12,7 @@ import { unregisterBlockType, createBlock, } from '@wordpress/blocks'; +import { dispatch as dataDispatch, select as dataSelect } from '@wordpress/data'; /** * Internal dependencies @@ -25,11 +26,8 @@ import { convertBlockToReusable, } from '../reusable-blocks'; import { - resetBlocks, - receiveBlocks, __experimentalSaveReusableBlock as saveReusableBlock, __experimentalDeleteReusableBlock as deleteReusableBlock, - removeBlocks, __experimentalConvertBlockToReusable as convertBlockToReusableAction, __experimentalConvertBlockToStatic as convertBlockToStaticAction, __experimentalReceiveReusableBlocks as receiveReusableBlocksAction, @@ -50,6 +48,7 @@ describe( 'reusable blocks effects', () => { name: { type: 'string' }, }, } ); + registerBlockType( 'core/block', { title: 'Reusable Block', category: 'common', @@ -252,10 +251,8 @@ describe( 'reusable blocks effects', () => { const reusableBlock = { id: 123, title: 'My cool block' }; const parsedBlock = createBlock( 'core/test-block', { name: 'Big Bird' } ); - const state = reduce( [ - receiveReusableBlocksAction( [ { reusableBlock, parsedBlock } ] ), - receiveBlocks( [ parsedBlock ] ), - ], reducer, undefined ); + const state = reducer( undefined, receiveReusableBlocksAction( [ { reusableBlock, parsedBlock } ] ) ); + jest.spyOn( dataSelect( 'core/block-editor' ), 'getBlock' ).mockImplementation( () => parsedBlock ); const dispatch = jest.fn(); const store = { getState: () => state, dispatch }; @@ -267,6 +264,8 @@ describe( 'reusable blocks effects', () => { id: 123, updatedId: 456, } ); + + dataSelect( 'core/block-editor' ).getBlock.mockReset(); } ); it( 'should handle an API error', async () => { @@ -286,10 +285,8 @@ describe( 'reusable blocks effects', () => { const reusableBlock = { id: 123, title: 'My cool block' }; const parsedBlock = createBlock( 'core/test-block', { name: 'Big Bird' } ); - const state = reduce( [ - receiveReusableBlocksAction( [ { reusableBlock, parsedBlock } ] ), - receiveBlocks( [ parsedBlock ] ), - ], reducer, undefined ); + const state = reducer( undefined, receiveReusableBlocksAction( [ { reusableBlock, parsedBlock } ] ) ); + jest.spyOn( dataSelect( 'core/block-editor' ), 'getBlock' ).mockImplementation( () => parsedBlock ); const dispatch = jest.fn(); const store = { getState: () => state, dispatch }; @@ -299,6 +296,8 @@ describe( 'reusable blocks effects', () => { type: 'SAVE_REUSABLE_BLOCK_FAILURE', id: 123, } ); + + dataSelect( 'core/block-editor' ).getBlock.mockReset(); } ); } ); @@ -310,9 +309,13 @@ describe( 'reusable blocks effects', () => { }, ] ); - expect( receiveReusableBlocks( action ) ).toEqual( receiveBlocks( [ + jest.spyOn( dataDispatch( 'core/block-editor' ), 'receiveBlocks' ).mockImplementation( () => {} ); + receiveReusableBlocks( action ); + expect( dataDispatch( 'core/block-editor' ).receiveBlocks ).toHaveBeenCalledWith( [ { clientId: 'broccoli' }, - ] ) ); + ] ); + + dataDispatch( 'core/block-editor' ).receiveBlocks.mockReset(); } ); } ); @@ -335,11 +338,12 @@ describe( 'reusable blocks effects', () => { const reusableBlock = { id: 123, title: 'My cool block' }; const parsedBlock = createBlock( 'core/test-block', { name: 'Big Bird' } ); - const state = reduce( [ - resetBlocks( [ associatedBlock ] ), - receiveReusableBlocksAction( [ { reusableBlock, parsedBlock } ] ), - receiveBlocks( [ parsedBlock ] ), - ], reducer, undefined ); + const state = reducer( undefined, receiveReusableBlocksAction( [ { reusableBlock, parsedBlock } ] ) ); + jest.spyOn( dataSelect( 'core/block-editor' ), 'getBlocks' ).mockImplementation( () => [ + associatedBlock, + parsedBlock, + ] ); + jest.spyOn( dataDispatch( 'core/block-editor' ), 'removeBlocks' ).mockImplementation( () => {} ); const dispatch = jest.fn(); const store = { getState: () => state, dispatch }; @@ -352,8 +356,8 @@ describe( 'reusable blocks effects', () => { optimist: expect.any( Object ), } ); - expect( dispatch ).toHaveBeenCalledWith( - removeBlocks( [ associatedBlock.clientId, parsedBlock.clientId ] ) + expect( dataDispatch( 'core/block-editor' ).removeBlocks ).toHaveBeenCalledWith( + [ associatedBlock.clientId, parsedBlock.clientId ] ); expect( dispatch ).toHaveBeenCalledWith( { @@ -361,6 +365,9 @@ describe( 'reusable blocks effects', () => { id: 123, optimist: expect.any( Object ), } ); + + dataDispatch( 'core/block-editor' ).removeBlocks.mockReset(); + dataSelect( 'core/block-editor' ).getBlocks.mockReset(); } ); it( 'should handle an API error', async () => { @@ -379,11 +386,11 @@ describe( 'reusable blocks effects', () => { const reusableBlock = { id: 123, title: 'My cool block' }; const parsedBlock = createBlock( 'core/test-block', { name: 'Big Bird' } ); - - const state = reduce( [ - receiveReusableBlocksAction( [ { reusableBlock, parsedBlock } ] ), - receiveBlocks( [ parsedBlock ] ), - ], reducer, undefined ); + const state = reducer( undefined, receiveReusableBlocksAction( [ { reusableBlock, parsedBlock } ] ) ); + jest.spyOn( dataSelect( 'core/block-editor' ), 'getBlocks' ).mockImplementation( () => [ + parsedBlock, + ] ); + jest.spyOn( dataDispatch( 'core/block-editor' ), 'removeBlocks' ).mockImplementation( () => {} ); const dispatch = jest.fn(); const store = { getState: () => state, dispatch }; @@ -395,16 +402,19 @@ describe( 'reusable blocks effects', () => { id: 123, optimist: expect.any( Object ), } ); + dataDispatch( 'core/block-editor' ).removeBlocks.mockReset(); + dataSelect( 'core/block-editor' ).getBlocks.mockReset(); } ); it( 'should not save reusable blocks with temporary IDs', async () => { const reusableBlock = { id: 'reusable1', title: 'My cool block' }; const parsedBlock = createBlock( 'core/test-block', { name: 'Big Bird' } ); - const state = reduce( [ - receiveReusableBlocksAction( [ { reusableBlock, parsedBlock } ] ), - receiveBlocks( [ parsedBlock ] ), - ], reducer, undefined ); + const state = reducer( undefined, receiveReusableBlocksAction( [ { reusableBlock, parsedBlock } ] ) ); + jest.spyOn( dataSelect( 'core/block-editor' ), 'getBlocks' ).mockImplementation( () => [ + parsedBlock, + ] ); + jest.spyOn( dataDispatch( 'core/block-editor' ), 'removeBlocks' ).mockImplementation( () => {} ); const dispatch = jest.fn(); const store = { getState: () => state, dispatch }; @@ -412,6 +422,8 @@ describe( 'reusable blocks effects', () => { await deleteReusableBlocks( deleteReusableBlock( 'reusable1' ), store ); expect( dispatch ).not.toHaveBeenCalled(); + dataDispatch( 'core/block-editor' ).removeBlocks.mockReset(); + dataSelect( 'core/block-editor' ).getBlocks.mockReset(); } ); } ); @@ -421,28 +433,29 @@ describe( 'reusable blocks effects', () => { const reusableBlock = { id: 123, title: 'My cool block' }; const parsedBlock = createBlock( 'core/test-block', { name: 'Big Bird' } ); - const state = reduce( [ - resetBlocks( [ associatedBlock ] ), - receiveReusableBlocksAction( [ { reusableBlock, parsedBlock } ] ), - receiveBlocks( [ parsedBlock ] ), - ], reducer, undefined ); + const state = reducer( undefined, receiveReusableBlocksAction( [ { reusableBlock, parsedBlock } ] ) ); + jest.spyOn( dataSelect( 'core/block-editor' ), 'getBlock' ).mockImplementation( ( id ) => + associatedBlock.clientId === id ? associatedBlock : parsedBlock + ); + jest.spyOn( dataDispatch( 'core/block-editor' ), 'replaceBlocks' ).mockImplementation( () => {} ); const dispatch = jest.fn(); const store = { getState: () => state, dispatch }; convertBlockToStatic( convertBlockToStaticAction( associatedBlock.clientId ), store ); - expect( dispatch ).toHaveBeenCalledWith( { - type: 'REPLACE_BLOCKS', - clientIds: [ associatedBlock.clientId ], - blocks: [ + expect( dataDispatch( 'core/block-editor' ).replaceBlocks ).toHaveBeenCalledWith( + associatedBlock.clientId, + [ expect.objectContaining( { name: 'core/test-block', attributes: { name: 'Big Bird' }, } ), - ], - time: expect.any( Number ), - } ); + ] + ); + + dataDispatch( 'core/block-editor' ).replaceBlocks.mockReset(); + dataSelect( 'core/block-editor' ).getBlock.mockReset(); } ); it( 'should convert a reusable block with nested blocks into a static block', () => { @@ -452,22 +465,20 @@ describe( 'reusable blocks effects', () => { createBlock( 'core/test-block', { name: 'Oscar the Grouch' } ), createBlock( 'core/test-block', { name: 'Cookie Monster' } ), ] ); - - const state = reduce( [ - resetBlocks( [ associatedBlock ] ), - receiveReusableBlocksAction( [ { reusableBlock, parsedBlock } ] ), - receiveBlocks( [ parsedBlock ] ), - ], reducer, undefined ); + const state = reducer( undefined, receiveReusableBlocksAction( [ { reusableBlock, parsedBlock } ] ) ); + jest.spyOn( dataSelect( 'core/block-editor' ), 'getBlock' ).mockImplementation( ( id ) => + associatedBlock.clientId === id ? associatedBlock : parsedBlock + ); + jest.spyOn( dataDispatch( 'core/block-editor' ), 'replaceBlocks' ).mockImplementation( () => {} ); const dispatch = jest.fn(); const store = { getState: () => state, dispatch }; convertBlockToStatic( convertBlockToStaticAction( associatedBlock.clientId ), store ); - expect( dispatch ).toHaveBeenCalledWith( { - type: 'REPLACE_BLOCKS', - clientIds: [ associatedBlock.clientId ], - blocks: [ + expect( dataDispatch( 'core/block-editor' ).replaceBlocks ).toHaveBeenCalledWith( + associatedBlock.clientId, + [ expect.objectContaining( { name: 'core/test-block', attributes: { name: 'Big Bird' }, @@ -480,19 +491,25 @@ describe( 'reusable blocks effects', () => { } ), ], } ), - ], - time: expect.any( Number ), - } ); + ] + ); + + dataDispatch( 'core/block-editor' ).replaceBlocks.mockReset(); + dataSelect( 'core/block-editor' ).getBlock.mockReset(); } ); } ); describe( 'convertBlockToReusable', () => { it( 'should convert a static block into a reusable block', () => { const staticBlock = createBlock( 'core/block', { ref: 123 } ); - const state = reducer( undefined, resetBlocks( [ staticBlock ] ) ); + jest.spyOn( dataSelect( 'core/block-editor' ), 'getBlock' ).mockImplementation( ( ) => + staticBlock + ); + jest.spyOn( dataDispatch( 'core/block-editor' ), 'replaceBlocks' ).mockImplementation( () => {} ); + jest.spyOn( dataDispatch( 'core/block-editor' ), 'receiveBlocks' ).mockImplementation( () => {} ); const dispatch = jest.fn(); - const store = { getState: () => state, dispatch }; + const store = { getState: () => {}, dispatch }; convertBlockToReusable( convertBlockToReusableAction( staticBlock.clientId ), store ); @@ -511,21 +528,21 @@ describe( 'reusable blocks effects', () => { saveReusableBlock( expect.stringMatching( /^reusable/ ) ), ); - expect( dispatch ).toHaveBeenCalledWith( { - type: 'REPLACE_BLOCKS', - clientIds: [ staticBlock.clientId ], - blocks: [ - expect.objectContaining( { - name: 'core/block', - attributes: { ref: expect.stringMatching( /^reusable/ ) }, - } ), - ], - time: expect.any( Number ), - } ); + expect( dataDispatch( 'core/block-editor' ).replaceBlocks ).toHaveBeenCalledWith( + [ staticBlock.clientId ], + expect.objectContaining( { + name: 'core/block', + attributes: { ref: expect.stringMatching( /^reusable/ ) }, + } ), + ); - expect( dispatch ).toHaveBeenCalledWith( - receiveBlocks( [ staticBlock ] ), + expect( dataDispatch( 'core/block-editor' ).receiveBlocks ).toHaveBeenCalledWith( + [ staticBlock ] ); + + dataDispatch( 'core/block-editor' ).replaceBlocks.mockReset(); + dataDispatch( 'core/block-editor' ).receiveBlocks.mockReset(); + dataSelect( 'core/block-editor' ).getBlock.mockReset(); } ); } ); } ); diff --git a/packages/editor/src/store/reducer.js b/packages/editor/src/store/reducer.js index 6e305185c96e7..2cc1791a0a4a0 100644 --- a/packages/editor/src/store/reducer.js +++ b/packages/editor/src/store/reducer.js @@ -5,37 +5,29 @@ import optimist from 'redux-optimist'; import { flow, reduce, - first, - last, omit, - without, mapValues, - omitBy, keys, isEqual, - isEmpty, - overSome, + last, } from 'lodash'; /** * WordPress dependencies */ -import { isReusableBlock } from '@wordpress/blocks'; import { combineReducers } from '@wordpress/data'; import { addQueryArgs } from '@wordpress/url'; /** * Internal dependencies */ -import withHistory from '../utils/with-history'; -import withChangeDetection from '../utils/with-change-detection'; import { PREFERENCES_DEFAULTS, - EDITOR_SETTINGS_DEFAULTS, INITIAL_EDITS_DEFAULTS, } from './defaults'; -import { insertAt, moveTo } from './array'; import { EDIT_MERGE_PROPERTIES } from './constants'; +import withChangeDetection from '../utils/with-change-detection'; +import withHistory from '../utils/with-history'; /** * Returns a post attribute value, flattening nested rendered content using its @@ -53,101 +45,6 @@ export function getPostRawValue( value ) { return value; } -/** - * Given an array of blocks, returns an object where each key is a nesting - * context, the value of which is an array of block client IDs existing within - * that nesting context. - * - * @param {Array} blocks Blocks to map. - * @param {?string} rootClientId Assumed root client ID. - * - * @return {Object} Block order map object. - */ -function mapBlockOrder( blocks, rootClientId = '' ) { - const result = { [ rootClientId ]: [] }; - - blocks.forEach( ( block ) => { - const { clientId, innerBlocks } = block; - - result[ rootClientId ].push( clientId ); - - Object.assign( result, mapBlockOrder( innerBlocks, clientId ) ); - } ); - - return result; -} - -/** - * Helper method to iterate through all blocks, recursing into inner blocks, - * applying a transformation function to each one. - * Returns a flattened object with the transformed blocks. - * - * @param {Array} blocks Blocks to flatten. - * @param {Function} transform Transforming function to be applied to each block. - * - * @return {Object} Flattened object. - */ -function flattenBlocks( blocks, transform ) { - const result = {}; - - const stack = [ ...blocks ]; - while ( stack.length ) { - const { innerBlocks, ...block } = stack.shift(); - stack.push( ...innerBlocks ); - result[ block.clientId ] = transform( block ); - } - - return result; -} - -/** - * Given an array of blocks, returns an object containing all blocks, without - * attributes, recursing into inner blocks. Keys correspond to the block client - * ID, the value of which is the attributes object. - * - * @param {Array} blocks Blocks to flatten. - * - * @return {Object} Flattened block attributes object. - */ -function getFlattenedBlocksWithoutAttributes( blocks ) { - return flattenBlocks( blocks, ( block ) => omit( block, 'attributes' ) ); -} - -/** - * Given an array of blocks, returns an object containing all block attributes, - * recursing into inner blocks. Keys correspond to the block client ID, the - * value of which is the attributes object. - * - * @param {Array} blocks Blocks to flatten. - * - * @return {Object} Flattened block attributes object. - */ -function getFlattenedBlockAttributes( blocks ) { - return flattenBlocks( blocks, ( block ) => block.attributes ); -} - -/** - * Given a block order map object, returns *all* of the block client IDs that are - * a descendant of the given root client ID. - * - * Calling this with `rootClientId` set to `''` results in a list of client IDs - * that are in the post. That is, it excludes blocks like fetched reusable - * blocks which are stored into state but not visible. - * - * @param {Object} blocksOrder Object that maps block client IDs to a list of - * nested block client IDs. - * @param {?string} rootClientId The root client ID to search. Defaults to ''. - * - * @return {Array} List of descendant client IDs. - */ -function getNestedBlockClientIds( blocksOrder, rootClientId = '' ) { - return reduce( blocksOrder[ rootClientId ], ( result, clientId ) => [ - ...result, - clientId, - ...getNestedBlockClientIds( blocksOrder, clientId ), - ], [] ); -} - /** * Returns an object against which it is safe to perform mutating operations, * given the original object and its current working copy. @@ -178,24 +75,6 @@ export function hasSameKeys( a, b ) { return isEqual( keys( a ), keys( b ) ); } -/** - * Returns true if, given the currently dispatching action and the previously - * dispatched action, the two actions are updating the same block attribute, or - * false otherwise. - * - * @param {Object} action Currently dispatching action. - * @param {Object} previousAction Previously dispatched action. - * - * @return {boolean} Whether actions are updating the same block attribute. - */ -export function isUpdatingSameBlockAttribute( action, previousAction ) { - return ( - action.type === 'UPDATE_BLOCK_ATTRIBUTES' && - action.clientId === previousAction.clientId && - hasSameKeys( action.attributes, previousAction.attributes ) - ); -} - /** * Returns true if, given the currently dispatching action and the previously * dispatched action, the two actions are editing the same post property, or @@ -224,110 +103,17 @@ export function isUpdatingSamePostProperty( action, previousAction ) { * @return {boolean} Whether to overwrite present state. */ export function shouldOverwriteState( action, previousAction ) { + if ( action.type === 'RESET_EDITOR_BLOCKS' ) { + return ! action.shouldCreateUndoLevel; + } + if ( ! previousAction || action.type !== previousAction.type ) { return false; } - return overSome( [ - isUpdatingSameBlockAttribute, - isUpdatingSamePostProperty, - ] )( action, previousAction ); + return isUpdatingSamePostProperty( action, previousAction ); } -/** - * Higher-order reducer targeting the combined editor reducer, augmenting - * block client IDs in remove action to include cascade of inner blocks. - * - * @param {Function} reducer Original reducer function. - * - * @return {Function} Enhanced reducer function. - */ -const withInnerBlocksRemoveCascade = ( reducer ) => ( state, action ) => { - if ( state && action.type === 'REMOVE_BLOCKS' ) { - const clientIds = [ ...action.clientIds ]; - - // For each removed client ID, include its inner blocks to remove, - // recursing into those so long as inner blocks exist. - for ( let i = 0; i < clientIds.length; i++ ) { - clientIds.push( ...state.blocks.order[ clientIds[ i ] ] ); - } - - action = { ...action, clientIds }; - } - - return reducer( state, action ); -}; - -/** - * Higher-order reducer which targets the combined blocks reducer and handles - * the `RESET_BLOCKS` action. When dispatched, this action will replace all - * blocks that exist in the post, leaving blocks that exist only in state (e.g. - * reusable blocks) alone. - * - * @param {Function} reducer Original reducer function. - * - * @return {Function} Enhanced reducer function. - */ -const withBlockReset = ( reducer ) => ( state, action ) => { - if ( state && action.type === 'RESET_BLOCKS' ) { - const visibleClientIds = getNestedBlockClientIds( state.order ); - return { - ...state, - byClientId: { - ...omit( state.byClientId, visibleClientIds ), - ...getFlattenedBlocksWithoutAttributes( action.blocks ), - }, - attributes: { - ...omit( state.attributes, visibleClientIds ), - ...getFlattenedBlockAttributes( action.blocks ), - }, - order: { - ...omit( state.order, visibleClientIds ), - ...mapBlockOrder( action.blocks ), - }, - }; - } - - return reducer( state, action ); -}; - -/** - * Higher-order reducer which targets the combined blocks reducer and handles - * the `SAVE_REUSABLE_BLOCK_SUCCESS` action. This action can't be handled by - * regular reducers and needs a higher-order reducer since it needs access to - * both `byClientId` and `attributes` simultaneously. - * - * @param {Function} reducer Original reducer function. - * - * @return {Function} Enhanced reducer function. - */ -const withSaveReusableBlock = ( reducer ) => ( state, action ) => { - if ( state && action.type === 'SAVE_REUSABLE_BLOCK_SUCCESS' ) { - const { id, updatedId } = action; - - // If a temporary reusable block is saved, we swap the temporary id with the final one - if ( id === updatedId ) { - return state; - } - - state = { ...state }; - - state.attributes = mapValues( state.attributes, ( attributes, clientId ) => { - const { name } = state.byClientId[ clientId ]; - if ( name === 'core/block' && attributes.ref === id ) { - return { - ...attributes, - ref: updatedId, - }; - } - - return attributes; - } ); - } - - return reducer( state, action ); -}; - /** * Undoable reducer returning the editor post state, including blocks parsed * from current HTML markup. @@ -345,15 +131,31 @@ const withSaveReusableBlock = ( reducer ) => ( state, action ) => { export const editor = flow( [ combineReducers, - withInnerBlocksRemoveCascade, - - // Track undo history, starting at editor initialization. withHistory( { resetTypes: [ 'SETUP_EDITOR_STATE' ], - ignoreTypes: [ 'RECEIVE_BLOCKS', 'RESET_POST', 'UPDATE_POST' ], + ignoreTypes: [ + 'RECEIVE_BLOCKS', + 'RESET_POST', + 'UPDATE_POST', + ], shouldOverwriteState, } ), ] )( { + // Track whether changes exist, resetting at each post save. Relies on + // editor initialization firing post reset as an effect. + blocks: withChangeDetection( { + resetTypes: [ 'SETUP_EDITOR_STATE', 'REQUEST_POST_UPDATE_START' ], + } )( ( state = { value: [] }, action ) => { + switch ( action.type ) { + case 'RESET_EDITOR_BLOCKS': + if ( action.blocks === state.value ) { + return state; + } + return { value: action.blocks }; + } + + return state; + } ), edits( state = {}, action ) { switch ( action.type ) { case 'EDIT_POST': @@ -373,14 +175,6 @@ export const editor = flow( [ return result; }, state ); - - case 'RESET_BLOCKS': - if ( 'content' in state ) { - return omit( state, 'content' ); - } - - return state; - case 'UPDATE_POST': case 'RESET_POST': const getCanonicalValue = action.type === 'UPDATE_POST' ? @@ -396,284 +190,16 @@ export const editor = flow( [ delete result[ key ]; return result; }, state ); + case 'RESET_EDITOR_BLOCKS': + if ( 'content' in state ) { + return omit( state, 'content' ); + } + + return state; } return state; }, - - blocks: flow( [ - combineReducers, - - withBlockReset, - - withSaveReusableBlock, - - // Track whether changes exist, resetting at each post save. Relies on - // editor initialization firing post reset as an effect. - withChangeDetection( { - resetTypes: [ 'SETUP_EDITOR_STATE', 'REQUEST_POST_UPDATE_START' ], - ignoreTypes: [ 'RECEIVE_BLOCKS', 'RESET_POST', 'UPDATE_POST' ], - } ), - ] )( { - byClientId( state = {}, action ) { - switch ( action.type ) { - case 'SETUP_EDITOR_STATE': - return getFlattenedBlocksWithoutAttributes( action.blocks ); - - case 'RECEIVE_BLOCKS': - return { - ...state, - ...getFlattenedBlocksWithoutAttributes( action.blocks ), - }; - - case 'UPDATE_BLOCK': - // Ignore updates if block isn't known - if ( ! state[ action.clientId ] ) { - return state; - } - - // Do nothing if only attributes change. - const changes = omit( action.updates, 'attributes' ); - if ( isEmpty( changes ) ) { - return state; - } - - return { - ...state, - [ action.clientId ]: { - ...state[ action.clientId ], - ...changes, - }, - }; - - case 'INSERT_BLOCKS': - return { - ...state, - ...getFlattenedBlocksWithoutAttributes( action.blocks ), - }; - - case 'REPLACE_BLOCKS': - if ( ! action.blocks ) { - return state; - } - - return { - ...omit( state, action.clientIds ), - ...getFlattenedBlocksWithoutAttributes( action.blocks ), - }; - - case 'REMOVE_BLOCKS': - return omit( state, action.clientIds ); - } - - return state; - }, - - attributes( state = {}, action ) { - switch ( action.type ) { - case 'SETUP_EDITOR_STATE': - return getFlattenedBlockAttributes( action.blocks ); - - case 'RECEIVE_BLOCKS': - return { - ...state, - ...getFlattenedBlockAttributes( action.blocks ), - }; - - case 'UPDATE_BLOCK': - // Ignore updates if block isn't known or there are no attribute changes. - if ( ! state[ action.clientId ] || ! action.updates.attributes ) { - return state; - } - - return { - ...state, - [ action.clientId ]: { - ...state[ action.clientId ], - ...action.updates.attributes, - }, - }; - - case 'UPDATE_BLOCK_ATTRIBUTES': - // Ignore updates if block isn't known - if ( ! state[ action.clientId ] ) { - return state; - } - - // Consider as updates only changed values - const nextAttributes = reduce( action.attributes, ( result, value, key ) => { - if ( value !== result[ key ] ) { - result = getMutateSafeObject( state[ action.clientId ], result ); - result[ key ] = value; - } - - return result; - }, state[ action.clientId ] ); - - // Skip update if nothing has been changed. The reference will - // match the original block if `reduce` had no changed values. - if ( nextAttributes === state[ action.clientId ] ) { - return state; - } - - // Otherwise replace attributes in state - return { - ...state, - [ action.clientId ]: nextAttributes, - }; - - case 'INSERT_BLOCKS': - return { - ...state, - ...getFlattenedBlockAttributes( action.blocks ), - }; - - case 'REPLACE_BLOCKS': - if ( ! action.blocks ) { - return state; - } - - return { - ...omit( state, action.clientIds ), - ...getFlattenedBlockAttributes( action.blocks ), - }; - - case 'REMOVE_BLOCKS': - return omit( state, action.clientIds ); - } - - return state; - }, - - order( state = {}, action ) { - switch ( action.type ) { - case 'SETUP_EDITOR_STATE': - return mapBlockOrder( action.blocks ); - - case 'RECEIVE_BLOCKS': - return { - ...state, - ...omit( mapBlockOrder( action.blocks ), '' ), - }; - - case 'INSERT_BLOCKS': { - const { rootClientId = '', blocks } = action; - const subState = state[ rootClientId ] || []; - const mappedBlocks = mapBlockOrder( blocks, rootClientId ); - const { index = subState.length } = action; - - return { - ...state, - ...mappedBlocks, - [ rootClientId ]: insertAt( subState, mappedBlocks[ rootClientId ], index ), - }; - } - - case 'MOVE_BLOCK_TO_POSITION': { - const { fromRootClientId = '', toRootClientId = '', clientId } = action; - const { index = state[ toRootClientId ].length } = action; - - // Moving inside the same parent block - if ( fromRootClientId === toRootClientId ) { - const subState = state[ toRootClientId ]; - const fromIndex = subState.indexOf( clientId ); - return { - ...state, - [ toRootClientId ]: moveTo( state[ toRootClientId ], fromIndex, index ), - }; - } - - // Moving from a parent block to another - return { - ...state, - [ fromRootClientId ]: without( state[ fromRootClientId ], clientId ), - [ toRootClientId ]: insertAt( state[ toRootClientId ], clientId, index ), - }; - } - - case 'MOVE_BLOCKS_UP': { - const { clientIds, rootClientId = '' } = action; - const firstClientId = first( clientIds ); - const subState = state[ rootClientId ]; - - if ( ! subState.length || firstClientId === first( subState ) ) { - return state; - } - - const firstIndex = subState.indexOf( firstClientId ); - - return { - ...state, - [ rootClientId ]: moveTo( subState, firstIndex, firstIndex - 1, clientIds.length ), - }; - } - - case 'MOVE_BLOCKS_DOWN': { - const { clientIds, rootClientId = '' } = action; - const firstClientId = first( clientIds ); - const lastClientId = last( clientIds ); - const subState = state[ rootClientId ]; - - if ( ! subState.length || lastClientId === last( subState ) ) { - return state; - } - - const firstIndex = subState.indexOf( firstClientId ); - - return { - ...state, - [ rootClientId ]: moveTo( subState, firstIndex, firstIndex + 1, clientIds.length ), - }; - } - - case 'REPLACE_BLOCKS': { - const { blocks, clientIds } = action; - if ( ! blocks ) { - return state; - } - - const mappedBlocks = mapBlockOrder( blocks ); - - return flow( [ - ( nextState ) => omit( nextState, clientIds ), - ( nextState ) => ( { - ...nextState, - ...omit( mappedBlocks, '' ), - } ), - ( nextState ) => mapValues( nextState, ( subState ) => ( - reduce( subState, ( result, clientId ) => { - if ( clientId === clientIds[ 0 ] ) { - return [ - ...result, - ...mappedBlocks[ '' ], - ]; - } - - if ( clientIds.indexOf( clientId ) === -1 ) { - result.push( clientId ); - } - - return result; - }, [] ) - ) ), - ] )( state ); - } - - case 'REMOVE_BLOCKS': - return flow( [ - // Remove inner block ordering for removed blocks - ( nextState ) => omit( nextState, action.clientIds ), - - // Remove deleted blocks from other blocks' orderings - ( nextState ) => mapValues( nextState, ( subState ) => ( - without( subState, ...action.clientIds ) - ) ), - ] )( state ); - } - - return state; - }, - } ), } ); /** @@ -967,26 +493,6 @@ export function template( state = { isValid: true }, action ) { return state; } -/** - * Reducer returning the editor setting. - * - * @param {Object} state Current state. - * @param {Object} action Dispatched action. - * - * @return {Object} Updated state. - */ -export function settings( state = EDITOR_SETTINGS_DEFAULTS, action ) { - switch ( action.type ) { - case 'UPDATE_EDITOR_SETTINGS': - return { - ...state, - ...action.settings, - }; - } - - return state; -} - /** * Reducer returning the user preferences. * @@ -997,35 +503,6 @@ export function settings( state = EDITOR_SETTINGS_DEFAULTS, action ) { */ export function preferences( state = PREFERENCES_DEFAULTS, action ) { switch ( action.type ) { - case 'INSERT_BLOCKS': - case 'REPLACE_BLOCKS': - return action.blocks.reduce( ( prevState, block ) => { - let id = block.name; - const insert = { name: block.name }; - if ( isReusableBlock( block ) ) { - insert.ref = block.attributes.ref; - id += '/' + block.attributes.ref; - } - - return { - ...prevState, - insertUsage: { - ...prevState.insertUsage, - [ id ]: { - time: action.time, - count: prevState.insertUsage[ id ] ? prevState.insertUsage[ id ].count + 1 : 1, - insert, - }, - }, - }; - }, state ); - - case 'REMOVE_REUSABLE_BLOCK': - return { - ...state, - insertUsage: omitBy( state.insertUsage, ( { insert } ) => insert.ref === action.id ), - }; - case 'ENABLE_PUBLISH_SIDEBAR': return { ...state, @@ -1330,16 +807,29 @@ export function previewLink( state = null, action ) { return state; } +/** + * Reducer returning whether the editor is ready to be rendered. + * The editor is considered ready to be rendered once + * the post object is loaded properly and the initial blocks parsed. + * + * @param {boolean} state + * @param {Object} action + * + * @return {boolean} Updated state. + */ +export function isReady( state = false, action ) { + switch ( action.type ) { + case 'SETUP_EDITOR_STATE': + return true; + } + + return state; +} + export default optimist( combineReducers( { editor, initialEdits, currentPost, - isTyping, - isCaretWithinFormattedText, - blockSelection, - blocksMode, - blockListSettings, - insertionPoint, preferences, saving, postLock, @@ -1347,6 +837,6 @@ export default optimist( combineReducers( { template, autosave, previewLink, - settings, postSavingLock, + isReady, } ) ); diff --git a/packages/editor/src/store/selectors.js b/packages/editor/src/store/selectors.js index 7a1ab754c45a5..c3147abda7063 100644 --- a/packages/editor/src/store/selectors.js +++ b/packages/editor/src/store/selectors.js @@ -2,20 +2,10 @@ * External dependencies */ import { - castArray, - flatMap, find, - first, get, has, - includes, - isArray, - isBoolean, - last, map, - orderBy, - reduce, - some, } from 'lodash'; import createSelector from 'rememo'; @@ -24,17 +14,14 @@ import createSelector from 'rememo'; */ import { serialize, - getBlockType, - getBlockTypes, - hasBlockSupport, - hasChildBlocksWithInserterSupport, - getDefaultBlockName, getFreeformContentHandlerName, + getDefaultBlockName, isUnmodifiedDefaultBlock, } from '@wordpress/blocks'; import { isInTheFuture, getDate } from '@wordpress/date'; import { removep } from '@wordpress/autop'; import { addQueryArgs } from '@wordpress/url'; +import { createRegistrySelector } from '@wordpress/data'; /** * Internal dependencies @@ -51,21 +38,16 @@ export const INSERTER_UTILITY_HIGH = 3; export const INSERTER_UTILITY_MEDIUM = 2; export const INSERTER_UTILITY_LOW = 1; export const INSERTER_UTILITY_NONE = 0; -const MILLISECONDS_PER_HOUR = 3600 * 1000; -const MILLISECONDS_PER_DAY = 24 * 3600 * 1000; -const MILLISECONDS_PER_WEEK = 7 * 24 * 3600 * 1000; const ONE_MINUTE_IN_MS = 60 * 1000; /** - * Shared reference to an empty array for cases where it is important to avoid - * returning a new array reference on every invocation, as in a connected or + * Shared reference to an empty object for cases where it is important to avoid + * returning a new object reference on every invocation, as in a connected or * other pure component which performs `shouldComponentUpdate` check on props. * This should be used as a last resort, since the normalized data should be * maintained by the reducer result in state. - * - * @type {Array} */ -const EMPTY_ARRAY = []; +const EMPTY_OBJECT = {}; /** * Returns true if any past editor history snapshots exist, or false otherwise. @@ -280,6 +262,34 @@ export function getCurrentPostAttribute( state, attributeName ) { } } +/** + * Returns a single attribute of the post being edited, preferring the unsaved + * edit if one exists, but mergiging with the attribute value for the last known + * saved state of the post (this is needed for some nested attributes like meta). + * + * @param {Object} state Global application state. + * @param {string} attributeName Post attribute name. + * + * @return {*} Post attribute value. + */ +const getNestedEditedPostProperty = createSelector( + ( state, attributeName ) => { + const edits = getPostEdits( state ); + if ( ! edits.hasOwnProperty( attributeName ) ) { + return getCurrentPostAttribute( state, attributeName ); + } + + return { + ...getCurrentPostAttribute( state, attributeName ), + ...edits[ attributeName ], + }; + }, + ( state, attributeName ) => [ + get( state.editor.present.edits, [ attributeName ], EMPTY_OBJECT ), + get( state.currentPost, [ attributeName ], EMPTY_OBJECT ), + ] +); + /** * Returns a single attribute of the post being edited, preferring the unsaved * edit if one exists, but falling back to the attribute for the last known @@ -306,13 +316,7 @@ export function getEditedPostAttribute( state, attributeName ) { // Merge properties are objects which contain only the patch edit in state, // and thus must be merged with the current post attribute. if ( EDIT_MERGE_PROPERTIES.has( attributeName ) ) { - // [TODO]: Since this will return a new reference on each invocation, - // consider caching in a way which would not impact non-merged property - // derivation. Alternatively, introduce a new selector for meta lookup. - return { - ...getCurrentPostAttribute( state, attributeName ), - ...edits[ attributeName ], - }; + return getNestedEditedPostProperty( state, attributeName ); } return edits[ attributeName ]; @@ -461,12 +465,13 @@ export function isEditedPostEmpty( state ) { // condition of the mere existence of blocks. Note that the value of edited // content takes precedent over block content, and must fall through to the // default logic. - const rootClientIds = getBlockOrder( state ); - if ( rootClientIds.length && ! ( 'content' in getPostEdits( state ) ) ) { + const blocks = state.editor.present.blocks.value; + + if ( blocks.length && ! ( 'content' in getPostEdits( state ) ) ) { // Pierce the abstraction of the serializer in knowing that blocks are // joined with with newlines such that even if every individual block // produces an empty save result, the serialized content is non-empty. - if ( rootClientIds.length > 1 ) { + if ( blocks.length > 1 ) { return false; } @@ -481,7 +486,7 @@ export function isEditedPostEmpty( state ) { // // For all other content, the single block is assumed to make a post // non-empty, if only by virtue of its own comment delimiters. - const blockName = getBlockName( state, rootClientIds[ 0 ] ); + const blockName = blocks[ 0 ].name; if ( blockName !== getDefaultBlockName() && blockName !== getFreeformContentHandlerName() @@ -590,1541 +595,268 @@ export function isEditedPostDateFloating( state ) { } /** - * Returns a new reference when the inner blocks of a given block client ID - * change. This is used exclusively as a memoized selector dependant, relying - * on this selector's shared return value and recursively those of its inner - * blocks defined as dependencies. This abuses mechanics of the selector - * memoization to return from the original selector function only when - * dependants change. - * - * @param {Object} state Editor state. - * @param {string} clientId Block client ID. - * - * @return {*} A value whose reference will change only when inner blocks of - * the given block client ID change. + * Returns true if the post is currently being saved, or false otherwise. + * + * @param {Object} state Global application state. + * + * @return {boolean} Whether post is being saved. */ -export const getBlockDependantsCacheBust = createSelector( - () => [], - ( state, clientId ) => map( - getBlockOrder( state, clientId ), - ( innerBlockClientId ) => getBlock( state, innerBlockClientId ), - ), -); +export function isSavingPost( state ) { + return state.saving.requesting; +} /** - * Returns a block's name given its client ID, or null if no block exists with - * the client ID. + * Returns true if a previous post save was attempted successfully, or false + * otherwise. * - * @param {Object} state Editor state. - * @param {string} clientId Block client ID. + * @param {Object} state Global application state. * - * @return {string} Block name. + * @return {boolean} Whether the post was saved successfully. */ -export function getBlockName( state, clientId ) { - const block = state.editor.present.blocks.byClientId[ clientId ]; - return block ? block.name : null; +export function didPostSaveRequestSucceed( state ) { + return state.saving.successful; } /** - * Returns whether a block is valid or not. + * Returns true if a previous post save was attempted but failed, or false + * otherwise. * - * @param {Object} state Editor state. - * @param {string} clientId Block client ID. + * @param {Object} state Global application state. * - * @return {boolean} Is Valid. + * @return {boolean} Whether the post save failed. */ -export function isBlockValid( state, clientId ) { - const block = state.editor.present.blocks.byClientId[ clientId ]; - return !! block && block.isValid; +export function didPostSaveRequestFail( state ) { + return !! state.saving.error; } /** - * Returns a block's attributes given its client ID, or null if no block exists with - * the client ID. + * Returns true if the post is autosaving, or false otherwise. * - * @param {Object} state Editor state. - * @param {string} clientId Block client ID. + * @param {Object} state Global application state. * - * @return {Object?} Block attributes. + * @return {boolean} Whether the post is autosaving. */ -export const getBlockAttributes = createSelector( - ( state, clientId ) => { - const block = state.editor.present.blocks.byClientId[ clientId ]; - if ( ! block ) { - return null; - } +export function isAutosavingPost( state ) { + return isSavingPost( state ) && !! state.saving.options.isAutosave; +} - let attributes = state.editor.present.blocks.attributes[ clientId ]; +/** + * Returns true if the post is being previewed, or false otherwise. + * + * @param {Object} state Global application state. + * + * @return {boolean} Whether the post is being previewed. + */ +export function isPreviewingPost( state ) { + return isSavingPost( state ) && !! state.saving.options.isPreview; +} - // Inject custom source attribute values. - // - // TODO: Create generic external sourcing pattern, not explicitly - // targeting meta attributes. - const type = getBlockType( block.name ); - if ( type ) { - attributes = reduce( type.attributes, ( result, value, key ) => { - if ( value.source === 'meta' ) { - if ( result === attributes ) { - result = { ...result }; - } - - result[ key ] = getPostMeta( state, value.meta ); - } - - return result; - }, attributes ); - } +/** + * Returns the post preview link + * + * @param {Object} state Global application state. + * + * @return {string?} Preview Link. + */ +export function getEditedPostPreviewLink( state ) { + const featuredImageId = getEditedPostAttribute( state, 'featured_media' ); + const previewLink = state.previewLink; + if ( previewLink && featuredImageId ) { + return addQueryArgs( previewLink, { _thumbnail_id: featuredImageId } ); + } - return attributes; - }, - ( state, clientId ) => [ - state.editor.present.blocks.byClientId[ clientId ], - state.editor.present.blocks.attributes[ clientId ], - state.editor.present.edits.meta, - state.initialEdits.meta, - state.currentPost.meta, - ] -); + return previewLink; +} /** - * Returns a block given its client ID. This is a parsed copy of the block, - * containing its `blockName`, `clientId`, and current `attributes` state. This - * is not the block's registration settings, which must be retrieved from the - * blocks module registration store. + * Returns a suggested post format for the current post, inferred only if there + * is a single block within the post and it is of a type known to match a + * default post format. Returns null if the format cannot be determined. * - * @param {Object} state Editor state. - * @param {string} clientId Block client ID. + * @param {Object} state Global application state. * - * @return {Object} Parsed block object. + * @return {?string} Suggested post format. */ -export const getBlock = createSelector( - ( state, clientId ) => { - const block = state.editor.present.blocks.byClientId[ clientId ]; - if ( ! block ) { - return null; - } +export function getSuggestedPostFormat( state ) { + const blocks = state.editor.present.blocks.value; - return { - ...block, - attributes: getBlockAttributes( state, clientId ), - innerBlocks: getBlocks( state, clientId ), - }; - }, - ( state, clientId ) => [ - ...getBlockAttributes.getDependants( state, clientId ), - getBlockDependantsCacheBust( state, clientId ), - ] -); + let name; + // If there is only one block in the content of the post grab its name + // so we can derive a suitable post format from it. + if ( blocks.length === 1 ) { + name = blocks[ 0 ].name; + } -export const __unstableGetBlockWithoutInnerBlocks = createSelector( - ( state, clientId ) => { - const block = state.editor.present.blocks.byClientId[ clientId ]; - if ( ! block ) { - return null; + // If there are two blocks in the content and the last one is a text blocks + // grab the name of the first one to also suggest a post format from it. + if ( blocks.length === 2 ) { + if ( blocks[ 1 ].name === 'core/paragraph' ) { + name = blocks[ 0 ].name; } + } - return { - ...block, - attributes: getBlockAttributes( state, clientId ), - }; - }, - ( state, clientId ) => [ - state.editor.present.blocks.byClientId[ clientId ], - ...getBlockAttributes.getDependants( state, clientId ), - ] -); + // We only convert to default post formats in core. + switch ( name ) { + case 'core/image': + return 'image'; + case 'core/quote': + case 'core/pullquote': + return 'quote'; + case 'core/gallery': + return 'gallery'; + case 'core/video': + case 'core-embed/youtube': + case 'core-embed/vimeo': + return 'video'; + case 'core/audio': + case 'core-embed/spotify': + case 'core-embed/soundcloud': + return 'audio'; + } -function getPostMeta( state, key ) { - return has( state, [ 'editor', 'present', 'edits', 'meta', key ] ) ? - get( state, [ 'editor', 'present', 'edits', 'meta', key ] ) : - get( state, [ 'currentPost', 'meta', key ] ); + return null; } /** - * Returns all block objects for the current post being edited as an array in - * the order they appear in the post. - * - * Note: It's important to memoize this selector to avoid return a new instance - * on each call + * Returns a set of blocks which are to be used in consideration of the post's + * generated save content. * - * @param {Object} state Editor state. - * @param {?String} rootClientId Optional root client ID of block list. + * @param {Object} state Editor state. * - * @return {Object[]} Post blocks. + * @return {WPBlock[]} Filtered set of blocks for save. */ -export const getBlocks = createSelector( - ( state, rootClientId ) => { - return map( - getBlockOrder( state, rootClientId ), - ( clientId ) => getBlock( state, clientId ) - ); - }, - ( state ) => [ state.editor.present.blocks ] -); +export function getBlocksForSerialization( state ) { + const blocks = state.editor.present.blocks.value; -/** - * Returns an array containing the clientIds of all descendants - * of the blocks given. - * - * @param {Object} state Global application state. - * @param {Array} clientIds Array of blocks to inspect. - * - * @return {Array} ids of descendants. - */ -export const getClientIdsOfDescendants = ( state, clientIds ) => flatMap( clientIds, ( clientId ) => { - const descendants = getBlockOrder( state, clientId ); - return [ ...descendants, ...getClientIdsOfDescendants( state, descendants ) ]; -} ); + // WARNING: Any changes to the logic of this function should be verified + // against the implementation of isEditedPostEmpty, which bypasses this + // function for performance' sake, in an assumption of this current logic + // being irrelevant to the optimized condition of emptiness. + + // A single unmodified default block is assumed to be equivalent to an + // empty post. + const isSingleUnmodifiedDefaultBlock = ( + blocks.length === 1 && + isUnmodifiedDefaultBlock( blocks[ 0 ] ) + ); + + if ( isSingleUnmodifiedDefaultBlock ) { + return []; + } + + return blocks; +} /** - * Returns an array containing the clientIds of the top-level blocks - * and their descendants of any depth (for nested blocks). + * Returns the content of the post being edited, preferring raw string edit + * before falling back to serialization of block state. * * @param {Object} state Global application state. * - * @return {Array} ids of top-level and descendant blocks. + * @return {string} Post content. */ -export const getClientIdsWithDescendants = createSelector( +export const getEditedPostContent = createSelector( ( state ) => { - const topLevelIds = getBlockOrder( state ); - return [ ...topLevelIds, ...getClientIdsOfDescendants( state, topLevelIds ) ]; - }, - ( state ) => [ - state.editor.present.blocks.order, - ] -); + const edits = getPostEdits( state ); + if ( 'content' in edits ) { + return edits.content; + } -/** - * Returns the total number of blocks, or the total number of blocks with a specific name in a post. - * The number returned includes nested blocks. - * - * @param {Object} state Global application state. - * @param {?String} blockName Optional block name, if specified only blocks of that type will be counted. - * - * @return {number} Number of blocks in the post, or number of blocks with name equal to blockName. - */ -export const getGlobalBlockCount = createSelector( - ( state, blockName ) => { - const clientIds = getClientIdsWithDescendants( state ); - if ( ! blockName ) { - return clientIds.length; + const blocks = getBlocksForSerialization( state ); + const content = serialize( blocks ); + + // For compatibility purposes, treat a post consisting of a single + // freeform block as legacy content and downgrade to a pre-block-editor + // removep'd content format. + const isSingleFreeformBlock = ( + blocks.length === 1 && + blocks[ 0 ].name === getFreeformContentHandlerName() + ); + + if ( isSingleFreeformBlock ) { + return removep( content ); } - return reduce( clientIds, ( count, clientId ) => { - const block = state.editor.present.blocks.byClientId[ clientId ]; - return block.name === blockName ? count + 1 : count; - }, 0 ); + + return content; }, ( state ) => [ - state.editor.present.blocks.order, - state.editor.present.blocks.byClientId, - ] + state.editor.present.blocks.value, + state.editor.present.edits.content, + state.initialEdits.content, + ], ); /** - * Given an array of block client IDs, returns the corresponding array of block - * objects. + * Returns the reusable block with the given ID. * - * @param {Object} state Editor state. - * @param {string[]} clientIds Client IDs for which blocks are to be returned. + * @param {Object} state Global application state. + * @param {number|string} ref The reusable block's ID. * - * @return {WPBlock[]} Block objects. + * @return {Object} The reusable block, or null if none exists. */ -export const getBlocksByClientId = createSelector( - ( state, clientIds ) => map( - castArray( clientIds ), - ( clientId ) => getBlock( state, clientId ) - ), - ( state ) => [ - state.editor.present.edits.meta, - state.initialEdits.meta, - state.currentPost.meta, - state.editor.present.blocks, - ] +export const __experimentalGetReusableBlock = createSelector( + ( state, ref ) => { + const block = state.reusableBlocks.data[ ref ]; + if ( ! block ) { + return null; + } + + const isTemporary = isNaN( parseInt( ref ) ); + + return { + ...block, + id: isTemporary ? ref : +ref, + isTemporary, + }; + }, + ( state, ref ) => [ + state.reusableBlocks.data[ ref ], + ], ); /** - * Returns the number of blocks currently present in the post. + * Returns whether or not the reusable block with the given ID is being saved. * - * @param {Object} state Editor state. - * @param {?string} rootClientId Optional root client ID of block list. + * @param {Object} state Global application state. + * @param {string} ref The reusable block's ID. * - * @return {number} Number of blocks in the post. + * @return {boolean} Whether or not the reusable block is being saved. */ -export function getBlockCount( state, rootClientId ) { - return getBlockOrder( state, rootClientId ).length; +export function __experimentalIsSavingReusableBlock( state, ref ) { + return state.reusableBlocks.isSaving[ ref ] || false; } /** - * Returns the current block selection start. This value may be null, and it - * may represent either a singular block selection or multi-selection start. - * A selection is singular if its start and end match. + * Returns true if the reusable block with the given ID is being fetched, or + * false otherwise. * * @param {Object} state Global application state. + * @param {string} ref The reusable block's ID. * - * @return {?string} Client ID of block selection start. + * @return {boolean} Whether the reusable block is being fetched. */ -export function getBlockSelectionStart( state ) { - return state.blockSelection.start; +export function __experimentalIsFetchingReusableBlock( state, ref ) { + return !! state.reusableBlocks.isFetching[ ref ]; } /** - * Returns the current block selection end. This value may be null, and it - * may represent either a singular block selection or multi-selection end. - * A selection is singular if its start and end match. + * Returns an array of all reusable blocks. * * @param {Object} state Global application state. * - * @return {?string} Client ID of block selection end. + * @return {Array} An array of all reusable blocks. */ -export function getBlockSelectionEnd( state ) { - return state.blockSelection.end; -} - -/** - * Returns the number of blocks currently selected in the post. - * - * @param {Object} state Global application state. - * - * @return {number} Number of blocks selected in the post. - */ -export function getSelectedBlockCount( state ) { - const multiSelectedBlockCount = getMultiSelectedBlockClientIds( state ).length; - - if ( multiSelectedBlockCount ) { - return multiSelectedBlockCount; - } - - return state.blockSelection.start ? 1 : 0; -} - -/** - * Returns true if there is a single selected block, or false otherwise. - * - * @param {Object} state Editor state. - * - * @return {boolean} Whether a single block is selected. - */ -export function hasSelectedBlock( state ) { - const { start, end } = state.blockSelection; - return !! start && start === end; -} - -/** - * Returns the currently selected block client ID, or null if there is no - * selected block. - * - * @param {Object} state Editor state. - * - * @return {?string} Selected block client ID. - */ -export function getSelectedBlockClientId( state ) { - const { start, end } = state.blockSelection; - // We need to check the block exists because the current state.blockSelection reducer - // doesn't take into account the UNDO / REDO actions to update selection. - // To be removed when that's fixed. - return start && start === end && !! state.editor.present.blocks.byClientId[ start ] ? start : null; -} - -/** - * Returns the currently selected block, or null if there is no selected block. - * - * @param {Object} state Global application state. - * - * @return {?Object} Selected block. - */ -export function getSelectedBlock( state ) { - const clientId = getSelectedBlockClientId( state ); - return clientId ? getBlock( state, clientId ) : null; -} - -/** - * Given a block client ID, returns the root block from which the block is - * nested, an empty string for top-level blocks, or null if the block does not - * exist. - * - * @param {Object} state Editor state. - * @param {string} clientId Block from which to find root client ID. - * - * @return {?string} Root client ID, if exists - */ -export const getBlockRootClientId = createSelector( - ( state, clientId ) => { - const { order } = state.editor.present.blocks; - - for ( const rootClientId in order ) { - if ( includes( order[ rootClientId ], clientId ) ) { - return rootClientId; - } - } - - return null; - }, - ( state ) => [ - state.editor.present.blocks.order, - ] -); - -/** - * Given a block client ID, returns the root of the hierarchy from which the block is nested, return the block itself for root level blocks. - * - * @param {Object} state Editor state. - * @param {string} clientId Block from which to find root client ID. - * - * @return {string} Root client ID - */ -export const getBlockHierarchyRootClientId = createSelector( - ( state, clientId ) => { - let rootClientId = clientId; - let current = clientId; - while ( rootClientId ) { - current = rootClientId; - rootClientId = getBlockRootClientId( state, current ); - } - - return current; - }, - ( state ) => [ - state.editor.present.blocks.order, - ] -); - -/** - * Returns the client ID of the block adjacent one at the given reference - * startClientId and modifier directionality. Defaults start startClientId to - * the selected block, and direction as next block. Returns null if there is no - * adjacent block. - * - * @param {Object} state Editor state. - * @param {?string} startClientId Optional client ID of block from which to - * search. - * @param {?number} modifier Directionality multiplier (1 next, -1 - * previous). - * - * @return {?string} Return the client ID of the block, or null if none exists. - */ -export function getAdjacentBlockClientId( state, startClientId, modifier = 1 ) { - // Default to selected block. - if ( startClientId === undefined ) { - startClientId = getSelectedBlockClientId( state ); - } - - // Try multi-selection starting at extent based on modifier. - if ( startClientId === undefined ) { - if ( modifier < 0 ) { - startClientId = getFirstMultiSelectedBlockClientId( state ); - } else { - startClientId = getLastMultiSelectedBlockClientId( state ); - } - } - - // Validate working start client ID. - if ( ! startClientId ) { - return null; - } - - // Retrieve start block root client ID, being careful to allow the falsey - // empty string top-level root by explicitly testing against null. - const rootClientId = getBlockRootClientId( state, startClientId ); - if ( rootClientId === null ) { - return null; - } - - const { order } = state.editor.present.blocks; - const orderSet = order[ rootClientId ]; - const index = orderSet.indexOf( startClientId ); - const nextIndex = ( index + ( 1 * modifier ) ); - - // Block was first in set and we're attempting to get previous. - if ( nextIndex < 0 ) { - return null; - } - - // Block was last in set and we're attempting to get next. - if ( nextIndex === orderSet.length ) { - return null; - } - - // Assume incremented index is within the set. - return orderSet[ nextIndex ]; -} - -/** - * Returns the previous block's client ID from the given reference start ID. - * Defaults start to the selected block. Returns null if there is no previous - * block. - * - * @param {Object} state Editor state. - * @param {?string} startClientId Optional client ID of block from which to - * search. - * - * @return {?string} Adjacent block's client ID, or null if none exists. - */ -export function getPreviousBlockClientId( state, startClientId ) { - return getAdjacentBlockClientId( state, startClientId, -1 ); -} - -/** - * Returns the next block's client ID from the given reference start ID. - * Defaults start to the selected block. Returns null if there is no next - * block. - * - * @param {Object} state Editor state. - * @param {?string} startClientId Optional client ID of block from which to - * search. - * - * @return {?string} Adjacent block's client ID, or null if none exists. - */ -export function getNextBlockClientId( state, startClientId ) { - return getAdjacentBlockClientId( state, startClientId, 1 ); -} - -/** - * Returns the initial caret position for the selected block. - * This position is to used to position the caret properly when the selected block changes. - * - * @param {Object} state Global application state. - * - * @return {?Object} Selected block. - */ -export function getSelectedBlocksInitialCaretPosition( state ) { - const { start, end } = state.blockSelection; - if ( start !== end || ! start ) { - return null; - } - - return state.blockSelection.initialPosition; -} - -/** - * Returns the current multi-selection set of block client IDs, or an empty - * array if there is no multi-selection. - * - * @param {Object} state Editor state. - * - * @return {Array} Multi-selected block client IDs. - */ -export const getMultiSelectedBlockClientIds = createSelector( - ( state ) => { - const { start, end } = state.blockSelection; - if ( start === end ) { - return []; - } - - // Retrieve root client ID to aid in retrieving relevant nested block - // order, being careful to allow the falsey empty string top-level root - // by explicitly testing against null. - const rootClientId = getBlockRootClientId( state, start ); - if ( rootClientId === null ) { - return []; - } - - const blockOrder = getBlockOrder( state, rootClientId ); - const startIndex = blockOrder.indexOf( start ); - const endIndex = blockOrder.indexOf( end ); - - if ( startIndex > endIndex ) { - return blockOrder.slice( endIndex, startIndex + 1 ); - } - - return blockOrder.slice( startIndex, endIndex + 1 ); - }, - ( state ) => [ - state.editor.present.blocks.order, - state.blockSelection.start, - state.blockSelection.end, - ], -); - -/** - * Returns the current multi-selection set of blocks, or an empty array if - * there is no multi-selection. - * - * @param {Object} state Editor state. - * - * @return {Array} Multi-selected block objects. - */ -export const getMultiSelectedBlocks = createSelector( - ( state ) => { - const multiSelectedBlockClientIds = getMultiSelectedBlockClientIds( state ); - if ( ! multiSelectedBlockClientIds.length ) { - return EMPTY_ARRAY; - } - - return multiSelectedBlockClientIds.map( ( clientId ) => getBlock( state, clientId ) ); - }, - ( state ) => [ - ...getMultiSelectedBlockClientIds.getDependants( state ), - state.editor.present.blocks, - state.editor.present.edits.meta, - state.initialEdits.meta, - state.currentPost.meta, - ] -); - -/** - * Returns the client ID of the first block in the multi-selection set, or null - * if there is no multi-selection. - * - * @param {Object} state Editor state. - * - * @return {?string} First block client ID in the multi-selection set. - */ -export function getFirstMultiSelectedBlockClientId( state ) { - return first( getMultiSelectedBlockClientIds( state ) ) || null; -} - -/** - * Returns the client ID of the last block in the multi-selection set, or null - * if there is no multi-selection. - * - * @param {Object} state Editor state. - * - * @return {?string} Last block client ID in the multi-selection set. - */ -export function getLastMultiSelectedBlockClientId( state ) { - return last( getMultiSelectedBlockClientIds( state ) ) || null; -} - -/** - * Checks if possibleAncestorId is an ancestor of possibleDescendentId. - * - * @param {Object} state Editor state. - * @param {string} possibleAncestorId Possible ancestor client ID. - * @param {string} possibleDescendentId Possible descent client ID. - * - * @return {boolean} True if possibleAncestorId is an ancestor - * of possibleDescendentId, and false otherwise. - */ -const isAncestorOf = createSelector( - ( state, possibleAncestorId, possibleDescendentId ) => { - let idToCheck = possibleDescendentId; - while ( possibleAncestorId !== idToCheck && idToCheck ) { - idToCheck = getBlockRootClientId( state, idToCheck ); - } - return possibleAncestorId === idToCheck; - }, - ( state ) => [ - state.editor.present.blocks.order, - ], -); - -/** - * Returns true if a multi-selection exists, and the block corresponding to the - * specified client ID is the first block of the multi-selection set, or false - * otherwise. - * - * @param {Object} state Editor state. - * @param {string} clientId Block client ID. - * - * @return {boolean} Whether block is first in multi-selection. - */ -export function isFirstMultiSelectedBlock( state, clientId ) { - return getFirstMultiSelectedBlockClientId( state ) === clientId; -} - -/** - * Returns true if the client ID occurs within the block multi-selection, or - * false otherwise. - * - * @param {Object} state Editor state. - * @param {string} clientId Block client ID. - * - * @return {boolean} Whether block is in multi-selection set. - */ -export function isBlockMultiSelected( state, clientId ) { - return getMultiSelectedBlockClientIds( state ).indexOf( clientId ) !== -1; -} - -/** - * Returns true if an ancestor of the block is multi-selected, or false - * otherwise. - * - * @param {Object} state Editor state. - * @param {string} clientId Block client ID. - * - * @return {boolean} Whether an ancestor of the block is in multi-selection - * set. - */ -export const isAncestorMultiSelected = createSelector( - ( state, clientId ) => { - let ancestorClientId = clientId; - let isMultiSelected = false; - while ( ancestorClientId && ! isMultiSelected ) { - ancestorClientId = getBlockRootClientId( state, ancestorClientId ); - isMultiSelected = isBlockMultiSelected( state, ancestorClientId ); - } - return isMultiSelected; - }, - ( state ) => [ - state.editor.present.blocks.order, - state.blockSelection.start, - state.blockSelection.end, - ], -); -/** - * Returns the client ID of the block which begins the multi-selection set, or - * null if there is no multi-selection. - * - * This is not necessarily the first client ID in the selection. - * - * @see getFirstMultiSelectedBlockClientId - * - * @param {Object} state Editor state. - * - * @return {?string} Client ID of block beginning multi-selection. - */ -export function getMultiSelectedBlocksStartClientId( state ) { - const { start, end } = state.blockSelection; - if ( start === end ) { - return null; - } - return start || null; -} - -/** - * Returns the client ID of the block which ends the multi-selection set, or - * null if there is no multi-selection. - * - * This is not necessarily the last client ID in the selection. - * - * @see getLastMultiSelectedBlockClientId - * - * @param {Object} state Editor state. - * - * @return {?string} Client ID of block ending multi-selection. - */ -export function getMultiSelectedBlocksEndClientId( state ) { - const { start, end } = state.blockSelection; - if ( start === end ) { - return null; - } - return end || null; -} - -/** - * Returns an array containing all block client IDs in the editor in the order - * they appear. Optionally accepts a root client ID of the block list for which - * the order should be returned, defaulting to the top-level block order. - * - * @param {Object} state Editor state. - * @param {?string} rootClientId Optional root client ID of block list. - * - * @return {Array} Ordered client IDs of editor blocks. - */ -export function getBlockOrder( state, rootClientId ) { - return state.editor.present.blocks.order[ rootClientId || '' ] || EMPTY_ARRAY; -} - -/** - * Returns the index at which the block corresponding to the specified client - * ID occurs within the block order, or `-1` if the block does not exist. - * - * @param {Object} state Editor state. - * @param {string} clientId Block client ID. - * @param {?string} rootClientId Optional root client ID of block list. - * - * @return {number} Index at which block exists in order. - */ -export function getBlockIndex( state, clientId, rootClientId ) { - return getBlockOrder( state, rootClientId ).indexOf( clientId ); -} - -/** - * Returns true if the block corresponding to the specified client ID is - * currently selected and no multi-selection exists, or false otherwise. - * - * @param {Object} state Editor state. - * @param {string} clientId Block client ID. - * - * @return {boolean} Whether block is selected and multi-selection exists. - */ -export function isBlockSelected( state, clientId ) { - const { start, end } = state.blockSelection; - - if ( start !== end ) { - return false; - } - - return start === clientId; -} - -/** - * Returns true if one of the block's inner blocks is selected. - * - * @param {Object} state Editor state. - * @param {string} clientId Block client ID. - * @param {boolean} deep Perform a deep check. - * - * @return {boolean} Whether the block as an inner block selected - */ -export function hasSelectedInnerBlock( state, clientId, deep = false ) { - return some( - getBlockOrder( state, clientId ), - ( innerClientId ) => ( - isBlockSelected( state, innerClientId ) || - isBlockMultiSelected( state, innerClientId ) || - ( deep && hasSelectedInnerBlock( state, innerClientId, deep ) ) - ) - ); -} - -/** - * Returns true if the block corresponding to the specified client ID is - * currently selected but isn't the last of the selected blocks. Here "last" - * refers to the block sequence in the document, _not_ the sequence of - * multi-selection, which is why `state.blockSelection.end` isn't used. - * - * @param {Object} state Editor state. - * @param {string} clientId Block client ID. - * - * @return {boolean} Whether block is selected and not the last in the - * selection. - */ -export function isBlockWithinSelection( state, clientId ) { - if ( ! clientId ) { - return false; - } - - const clientIds = getMultiSelectedBlockClientIds( state ); - const index = clientIds.indexOf( clientId ); - return index > -1 && index < clientIds.length - 1; -} - -/** - * Returns true if a multi-selection has been made, or false otherwise. - * - * @param {Object} state Editor state. - * - * @return {boolean} Whether multi-selection has been made. - */ -export function hasMultiSelection( state ) { - const { start, end } = state.blockSelection; - return start !== end; -} - -/** - * Whether in the process of multi-selecting or not. This flag is only true - * while the multi-selection is being selected (by mouse move), and is false - * once the multi-selection has been settled. - * - * @see hasMultiSelection - * - * @param {Object} state Global application state. - * - * @return {boolean} True if multi-selecting, false if not. - */ -export function isMultiSelecting( state ) { - return state.blockSelection.isMultiSelecting; -} - -/** - * Selector that returns if multi-selection is enabled or not. - * - * @param {Object} state Global application state. - * - * @return {boolean} True if it should be possible to multi-select blocks, false if multi-selection is disabled. - */ -export function isSelectionEnabled( state ) { - return state.blockSelection.isEnabled; -} - -/** - * Returns the block's editing mode, defaulting to "visual" if not explicitly - * assigned. - * - * @param {Object} state Editor state. - * @param {string} clientId Block client ID. - * - * @return {Object} Block editing mode. - */ -export function getBlockMode( state, clientId ) { - return state.blocksMode[ clientId ] || 'visual'; -} - -/** - * Returns true if the user is typing, or false otherwise. - * - * @param {Object} state Global application state. - * - * @return {boolean} Whether user is typing. - */ -export function isTyping( state ) { - return state.isTyping; -} - -/** - * Returns true if the caret is within formatted text, or false otherwise. - * - * @param {Object} state Global application state. - * - * @return {boolean} Whether the caret is within formatted text. - */ -export function isCaretWithinFormattedText( state ) { - return state.isCaretWithinFormattedText; -} - -/** - * Returns the insertion point, the index at which the new inserted block would - * be placed. Defaults to the last index. - * - * @param {Object} state Editor state. - * - * @return {Object} Insertion point object with `rootClientId`, `index`. - */ -export function getBlockInsertionPoint( state ) { - let rootClientId, index; - - const { insertionPoint, blockSelection } = state; - if ( insertionPoint !== null ) { - return insertionPoint; - } - - const { end } = blockSelection; - if ( end ) { - rootClientId = getBlockRootClientId( state, end ) || undefined; - index = getBlockIndex( state, end, rootClientId ) + 1; - } else { - index = getBlockOrder( state ).length; - } - - return { rootClientId, index }; -} - -/** - * Returns true if we should show the block insertion point. - * - * @param {Object} state Global application state. - * - * @return {?boolean} Whether the insertion point is visible or not. - */ -export function isBlockInsertionPointVisible( state ) { - return state.insertionPoint !== null; -} - -/** - * Returns whether the blocks matches the template or not. - * - * @param {boolean} state - * @return {?boolean} Whether the template is valid or not. - */ -export function isValidTemplate( state ) { - return state.template.isValid; -} - -/** - * Returns the defined block template - * - * @param {boolean} state - * @return {?Array} Block Template - */ -export function getTemplate( state ) { - return state.settings.template; -} - -/** - * Returns the defined block template lock. Optionally accepts a root block - * client ID as context, otherwise defaulting to the global context. - * - * @param {Object} state Editor state. - * @param {?string} rootClientId Optional block root client ID. - * - * @return {?string} Block Template Lock - */ -export function getTemplateLock( state, rootClientId ) { - if ( ! rootClientId ) { - return state.settings.templateLock; - } - - const blockListSettings = getBlockListSettings( state, rootClientId ); - if ( ! blockListSettings ) { - return null; - } - - return blockListSettings.templateLock; -} - -/** - * Returns true if the post is currently being saved, or false otherwise. - * - * @param {Object} state Global application state. - * - * @return {boolean} Whether post is being saved. - */ -export function isSavingPost( state ) { - return state.saving.requesting; -} - -/** - * Returns true if a previous post save was attempted successfully, or false - * otherwise. - * - * @param {Object} state Global application state. - * - * @return {boolean} Whether the post was saved successfully. - */ -export function didPostSaveRequestSucceed( state ) { - return state.saving.successful; -} - -/** - * Returns true if a previous post save was attempted but failed, or false - * otherwise. - * - * @param {Object} state Global application state. - * - * @return {boolean} Whether the post save failed. - */ -export function didPostSaveRequestFail( state ) { - return !! state.saving.error; -} - -/** - * Returns true if the post is autosaving, or false otherwise. - * - * @param {Object} state Global application state. - * - * @return {boolean} Whether the post is autosaving. - */ -export function isAutosavingPost( state ) { - return isSavingPost( state ) && !! state.saving.options.isAutosave; -} - -/** - * Returns true if the post is being previewed, or false otherwise. - * - * @param {Object} state Global application state. - * - * @return {boolean} Whether the post is being previewed. - */ -export function isPreviewingPost( state ) { - return isSavingPost( state ) && !! state.saving.options.isPreview; -} - -/** - * Returns the post preview link - * - * @param {Object} state Global application state. - * - * @return {string?} Preview Link. - */ -export function getEditedPostPreviewLink( state ) { - const featuredImageId = getEditedPostAttribute( state, 'featured_media' ); - const previewLink = state.previewLink; - if ( previewLink && featuredImageId ) { - return addQueryArgs( previewLink, { _thumbnail_id: featuredImageId } ); - } - - return previewLink; -} - -/** - * Returns a suggested post format for the current post, inferred only if there - * is a single block within the post and it is of a type known to match a - * default post format. Returns null if the format cannot be determined. - * - * @param {Object} state Global application state. - * - * @return {?string} Suggested post format. - */ -export function getSuggestedPostFormat( state ) { - const blocks = getBlockOrder( state ); - - let name; - // If there is only one block in the content of the post grab its name - // so we can derive a suitable post format from it. - if ( blocks.length === 1 ) { - name = getBlockName( state, blocks[ 0 ] ); - } - - // If there are two blocks in the content and the last one is a text blocks - // grab the name of the first one to also suggest a post format from it. - if ( blocks.length === 2 ) { - if ( getBlockName( state, blocks[ 1 ] ) === 'core/paragraph' ) { - name = getBlockName( state, blocks[ 0 ] ); - } - } - - // We only convert to default post formats in core. - switch ( name ) { - case 'core/image': - return 'image'; - case 'core/quote': - case 'core/pullquote': - return 'quote'; - case 'core/gallery': - return 'gallery'; - case 'core/video': - case 'core-embed/youtube': - case 'core-embed/vimeo': - return 'video'; - case 'core/audio': - case 'core-embed/spotify': - case 'core-embed/soundcloud': - return 'audio'; - } - - return null; -} - -/** - * Returns a set of blocks which are to be used in consideration of the post's - * generated save content. - * - * @param {Object} state Editor state. - * - * @return {WPBlock[]} Filtered set of blocks for save. - */ -export function getBlocksForSerialization( state ) { - const blocks = getBlocks( state ); - - // WARNING: Any changes to the logic of this function should be verified - // against the implementation of isEditedPostEmpty, which bypasses this - // function for performance' sake, in an assumption of this current logic - // being irrelevant to the optimized condition of emptiness. - - // A single unmodified default block is assumed to be equivalent to an - // empty post. - const isSingleUnmodifiedDefaultBlock = ( - blocks.length === 1 && - isUnmodifiedDefaultBlock( blocks[ 0 ] ) - ); - - if ( isSingleUnmodifiedDefaultBlock ) { - return []; - } - - return blocks; -} - -/** - * Returns the content of the post being edited, preferring raw string edit - * before falling back to serialization of block state. - * - * @param {Object} state Global application state. - * - * @return {string} Post content. - */ -export const getEditedPostContent = createSelector( - ( state ) => { - const edits = getPostEdits( state ); - if ( 'content' in edits ) { - return edits.content; - } - - const blocks = getBlocksForSerialization( state ); - const content = serialize( blocks ); - - // For compatibility purposes, treat a post consisting of a single - // freeform block as legacy content and downgrade to a pre-block-editor - // removep'd content format. - const isSingleFreeformBlock = ( - blocks.length === 1 && - blocks[ 0 ].name === getFreeformContentHandlerName() - ); - - if ( isSingleFreeformBlock ) { - return removep( content ); - } - - return content; - }, - ( state ) => [ - state.editor.present.blocks, - state.editor.present.edits.content, - state.initialEdits.content, - ], -); - -/** - * Determines if the given block type is allowed to be inserted into the block list. - * This function is not exported and not memoized because using a memoized selector - * inside another memoized selector is just a waste of time. - * - * @param {Object} state Editor state. - * @param {string} blockName The name of the block type, e.g.' core/paragraph'. - * @param {?string} rootClientId Optional root client ID of block list. - * - * @return {boolean} Whether the given block type is allowed to be inserted. - */ -const canInsertBlockTypeUnmemoized = ( state, blockName, rootClientId = null ) => { - const checkAllowList = ( list, item, defaultResult = null ) => { - if ( isBoolean( list ) ) { - return list; - } - if ( isArray( list ) ) { - return includes( list, item ); - } - return defaultResult; - }; - - const blockType = getBlockType( blockName ); - if ( ! blockType ) { - return false; - } - - const { allowedBlockTypes } = getEditorSettings( state ); - - const isBlockAllowedInEditor = checkAllowList( allowedBlockTypes, blockName, true ); - if ( ! isBlockAllowedInEditor ) { - return false; - } - - const isLocked = !! getTemplateLock( state, rootClientId ); - if ( isLocked ) { - return false; - } - - const parentBlockListSettings = getBlockListSettings( state, rootClientId ); - const parentAllowedBlocks = get( parentBlockListSettings, [ 'allowedBlocks' ] ); - const hasParentAllowedBlock = checkAllowList( parentAllowedBlocks, blockName ); - - const blockAllowedParentBlocks = blockType.parent; - const parentName = getBlockName( state, rootClientId ); - const hasBlockAllowedParent = checkAllowList( blockAllowedParentBlocks, parentName ); - - if ( hasParentAllowedBlock !== null && hasBlockAllowedParent !== null ) { - return hasParentAllowedBlock || hasBlockAllowedParent; - } else if ( hasParentAllowedBlock !== null ) { - return hasParentAllowedBlock; - } else if ( hasBlockAllowedParent !== null ) { - return hasBlockAllowedParent; - } - - return true; -}; - -/** - * Determines if the given block type is allowed to be inserted into the block list. - * - * @param {Object} state Editor state. - * @param {string} blockName The name of the block type, e.g.' core/paragraph'. - * @param {?string} rootClientId Optional root client ID of block list. - * - * @return {boolean} Whether the given block type is allowed to be inserted. - */ -export const canInsertBlockType = createSelector( - canInsertBlockTypeUnmemoized, - ( state, blockName, rootClientId ) => [ - state.blockListSettings[ rootClientId ], - state.editor.present.blocks.byClientId[ rootClientId ], - state.settings.allowedBlockTypes, - state.settings.templateLock, - ], -); - -/** - * Returns information about how recently and frequently a block has been inserted. - * - * @param {Object} state Global application state. - * @param {string} id A string which identifies the insert, e.g. 'core/block/12' - * - * @return {?{ time: number, count: number }} An object containing `time` which is when the last - * insert occurred as a UNIX epoch, and `count` which is - * the number of inserts that have occurred. - */ -function getInsertUsage( state, id ) { - return state.preferences.insertUsage[ id ] || null; -} - -/** - * Returns whether we can show a block type in the inserter - * - * @param {Object} state Global State - * @param {Object} blockType BlockType - * @param {?string} rootClientId Optional root client ID of block list. - * - * @return {boolean} Whether the given block type is allowed to be shown in the inserter. - */ -const canIncludeBlockTypeInInserter = ( state, blockType, rootClientId ) => { - if ( ! hasBlockSupport( blockType, 'inserter', true ) ) { - return false; - } - - return canInsertBlockTypeUnmemoized( state, blockType.name, rootClientId ); -}; - -/** - * Returns whether we can show a reusable block in the inserter - * - * @param {Object} state Global State - * @param {Object} reusableBlock Reusable block object - * @param {?string} rootClientId Optional root client ID of block list. - * - * @return {boolean} Whether the given block type is allowed to be shown in the inserter. - */ -const canIncludeReusableBlockInInserter = ( state, reusableBlock, rootClientId ) => { - if ( ! canInsertBlockTypeUnmemoized( state, 'core/block', rootClientId ) ) { - return false; - } - - const referencedBlockName = getBlockName( state, reusableBlock.clientId ); - if ( ! referencedBlockName ) { - return false; - } - - const referencedBlockType = getBlockType( referencedBlockName ); - if ( ! referencedBlockType ) { - return false; - } - - if ( ! canInsertBlockTypeUnmemoized( state, referencedBlockName, rootClientId ) ) { - return false; - } - - if ( isAncestorOf( state, reusableBlock.clientId, rootClientId ) ) { - return false; - } - - return true; -}; - -/** - * Determines the items that appear in the inserter. Includes both static - * items (e.g. a regular block type) and dynamic items (e.g. a reusable block). - * - * Each item object contains what's necessary to display a button in the - * inserter and handle its selection. - * - * The 'utility' property indicates how useful we think an item will be to the - * user. There are 4 levels of utility: - * - * 1. Blocks that are contextually useful (utility = 3) - * 2. Blocks that have been previously inserted (utility = 2) - * 3. Blocks that are in the common category (utility = 1) - * 4. All other blocks (utility = 0) - * - * The 'frecency' property is a heuristic (https://en.wikipedia.org/wiki/Frecency) - * that combines block usage frequenty and recency. - * - * Items are returned ordered descendingly by their 'utility' and 'frecency'. - * - * @param {Object} state Editor state. - * @param {?string} rootClientId Optional root client ID of block list. - * - * @return {Editor.InserterItem[]} Items that appear in inserter. - * - * @typedef {Object} Editor.InserterItem - * @property {string} id Unique identifier for the item. - * @property {string} name The type of block to create. - * @property {Object} initialAttributes Attributes to pass to the newly created block. - * @property {string} title Title of the item, as it appears in the inserter. - * @property {string} icon Dashicon for the item, as it appears in the inserter. - * @property {string} category Block category that the item is associated with. - * @property {string[]} keywords Keywords that can be searched to find this item. - * @property {boolean} isDisabled Whether or not the user should be prevented from inserting - * this item. - * @property {number} utility How useful we think this item is, between 0 and 3. - * @property {number} frecency Hueristic that combines frequency and recency. - */ -export const getInserterItems = createSelector( - ( state, rootClientId = null ) => { - const calculateUtility = ( category, count, isContextual ) => { - if ( isContextual ) { - return INSERTER_UTILITY_HIGH; - } else if ( count > 0 ) { - return INSERTER_UTILITY_MEDIUM; - } else if ( category === 'common' ) { - return INSERTER_UTILITY_LOW; - } - return INSERTER_UTILITY_NONE; - }; - - const calculateFrecency = ( time, count ) => { - if ( ! time ) { - return count; - } - - // The selector is cached, which means Date.now() is the last time that the - // relevant state changed. This suits our needs. - const duration = Date.now() - time; - - switch ( true ) { - case duration < MILLISECONDS_PER_HOUR: - return count * 4; - case duration < MILLISECONDS_PER_DAY: - return count * 2; - case duration < MILLISECONDS_PER_WEEK: - return count / 2; - default: - return count / 4; - } - }; - - const buildBlockTypeInserterItem = ( blockType ) => { - const id = blockType.name; - - let isDisabled = false; - if ( ! hasBlockSupport( blockType.name, 'multiple', true ) ) { - isDisabled = some( getBlocksByClientId( state, getClientIdsWithDescendants( state ) ), { name: blockType.name } ); - } - - const isContextual = isArray( blockType.parent ); - const { time, count = 0 } = getInsertUsage( state, id ) || {}; - - return { - id, - name: blockType.name, - initialAttributes: {}, - title: blockType.title, - icon: blockType.icon, - category: blockType.category, - keywords: blockType.keywords, - isDisabled, - utility: calculateUtility( blockType.category, count, isContextual ), - frecency: calculateFrecency( time, count ), - hasChildBlocksWithInserterSupport: hasChildBlocksWithInserterSupport( blockType.name ), - }; - }; - - const buildReusableBlockInserterItem = ( reusableBlock ) => { - const id = `core/block/${ reusableBlock.id }`; - - const referencedBlockName = getBlockName( state, reusableBlock.clientId ); - const referencedBlockType = getBlockType( referencedBlockName ); - - const { time, count = 0 } = getInsertUsage( state, id ) || {}; - const utility = calculateUtility( 'reusable', count, false ); - const frecency = calculateFrecency( time, count ); - - return { - id, - name: 'core/block', - initialAttributes: { ref: reusableBlock.id }, - title: reusableBlock.title, - icon: referencedBlockType.icon, - category: 'reusable', - keywords: [], - isDisabled: false, - utility, - frecency, - }; - }; - - const blockTypeInserterItems = getBlockTypes() - .filter( ( blockType ) => canIncludeBlockTypeInInserter( state, blockType, rootClientId ) ) - .map( buildBlockTypeInserterItem ); - - const reusableBlockInserterItems = __experimentalGetReusableBlocks( state ) - .filter( ( block ) => canIncludeReusableBlockInInserter( state, block, rootClientId ) ) - .map( buildReusableBlockInserterItem ); - - return orderBy( - [ ...blockTypeInserterItems, ...reusableBlockInserterItems ], - [ 'utility', 'frecency' ], - [ 'desc', 'desc' ] - ); - }, - ( state, rootClientId ) => [ - state.blockListSettings[ rootClientId ], - state.editor.present.blocks.byClientId, - state.editor.present.blocks.order, - state.preferences.insertUsage, - state.settings.allowedBlockTypes, - state.settings.templateLock, - state.reusableBlocks.data, - getBlockTypes(), - ], -); - -/** - * Determines whether there are items to show in the inserter. - * @param {Object} state Editor state. - * @param {?string} rootClientId Optional root client ID of block list. - * - * @return {boolean} Items that appear in inserter. - */ -export const hasInserterItems = createSelector( - ( state, rootClientId = null ) => { - const hasBlockType = some( - getBlockTypes(), - ( blockType ) => canIncludeBlockTypeInInserter( state, blockType, rootClientId ) - ); - if ( hasBlockType ) { - return true; - } - const hasReusableBlock = some( - __experimentalGetReusableBlocks( state ), - ( block ) => canIncludeReusableBlockInInserter( state, block, rootClientId ) - ); - - return hasReusableBlock; - }, - ( state, rootClientId ) => [ - state.blockListSettings[ rootClientId ], - state.editor.present.blocks.byClientId, - state.settings.allowedBlockTypes, - state.settings.templateLock, - state.reusableBlocks.data, - getBlockTypes(), - ], -); - -/** - * Returns the reusable block with the given ID. - * - * @param {Object} state Global application state. - * @param {number|string} ref The reusable block's ID. - * - * @return {Object} The reusable block, or null if none exists. - */ -export const __experimentalGetReusableBlock = createSelector( - ( state, ref ) => { - const block = state.reusableBlocks.data[ ref ]; - if ( ! block ) { - return null; - } - - const isTemporary = isNaN( parseInt( ref ) ); - - return { - ...block, - id: isTemporary ? ref : +ref, - isTemporary, - }; - }, - ( state, ref ) => [ - state.reusableBlocks.data[ ref ], - ], -); - -/** - * Returns whether or not the reusable block with the given ID is being saved. - * - * @param {Object} state Global application state. - * @param {string} ref The reusable block's ID. - * - * @return {boolean} Whether or not the reusable block is being saved. - */ -export function __experimentalIsSavingReusableBlock( state, ref ) { - return state.reusableBlocks.isSaving[ ref ] || false; -} - -/** - * Returns true if the reusable block with the given ID is being fetched, or - * false otherwise. - * - * @param {Object} state Global application state. - * @param {string} ref The reusable block's ID. - * - * @return {boolean} Whether the reusable block is being fetched. - */ -export function __experimentalIsFetchingReusableBlock( state, ref ) { - return !! state.reusableBlocks.isFetching[ ref ]; -} - -/** - * Returns an array of all reusable blocks. - * - * @param {Object} state Global application state. - * - * @return {Array} An array of all reusable blocks. - */ -export function __experimentalGetReusableBlocks( state ) { - return map( - state.reusableBlocks.data, - ( value, ref ) => __experimentalGetReusableBlock( state, ref ) - ); -} +export const __experimentalGetReusableBlocks = createSelector( + ( state ) => { + return map( + state.reusableBlocks.data, + ( value, ref ) => __experimentalGetReusableBlock( state, ref ) + ); + }, + ( state ) => [ + state.reusableBlocks.data, + ] +); /** * Returns state object prior to a specified optimist transaction ID, or `null` @@ -2257,45 +989,6 @@ export function inSomeHistory( state, predicate ) { ) ); } -/** - * Returns the Block List settings of a block, if any exist. - * - * @param {Object} state Editor state. - * @param {?string} clientId Block client ID. - * - * @return {?Object} Block settings of the block if set. - */ -export function getBlockListSettings( state, clientId ) { - return state.blockListSettings[ clientId ]; -} - -/** - * Returns the editor settings. - * - * @param {Object} state Editor state. - * - * @return {Object} The editor settings object. - */ -export function getEditorSettings( state ) { - return state.settings; -} - -/** - * Returns the token settings. - * - * @param {Object} state Editor state. - * @param {?string} name Token name. - * - * @return {Object} Token settings object, or the named token settings object if set. - */ -export function getTokenSettings( state, name ) { - if ( ! name ) { - return state.tokens; - } - - return state.tokens[ name ]; -} - /** * Returns whether the post is locked. * @@ -2376,3 +1069,88 @@ export function isPublishSidebarEnabled( state ) { } return PREFERENCES_DEFAULTS.isPublishSidebarEnabled; } + +/** + * Return the current block list. + * + * @param {Object} state + * @return {Array} Block list. + */ +export function getEditorBlocks( state ) { + return state.editor.present.blocks.value; +} + +/** + * Is the editor ready + * + * @param {Object} state + * @return {boolean} is Ready. + */ +export function __unstableIsEditorReady( state ) { + return state.isReady; +} + +/* + * Backward compatibility + */ + +function getBlockEditorSelector( name ) { + return createRegistrySelector( ( select ) => ( state, ...args ) => { + return select( 'core/block-editor' )[ name ]( ...args ); + } ); +} + +export const getBlockDependantsCacheBust = getBlockEditorSelector( 'getBlockDependantsCacheBust' ); +export const getBlockName = getBlockEditorSelector( 'getBlockName' ); +export const isBlockValid = getBlockEditorSelector( 'isBlockValid' ); +export const getBlockAttributes = getBlockEditorSelector( 'getBlockAttributes' ); +export const getBlock = getBlockEditorSelector( 'getBlock' ); +export const getBlocks = getBlockEditorSelector( 'getBlocks' ); +export const __unstableGetBlockWithoutInnerBlocks = getBlockEditorSelector( '__unstableGetBlockWithoutInnerBlocks' ); +export const getClientIdsOfDescendants = getBlockEditorSelector( 'getClientIdsOfDescendants' ); +export const getClientIdsWithDescendants = getBlockEditorSelector( 'getClientIdsWithDescendants' ); +export const getGlobalBlockCount = getBlockEditorSelector( 'getGlobalBlockCount' ); +export const getBlocksByClientId = getBlockEditorSelector( 'getBlocksByClientId' ); +export const getBlockCount = getBlockEditorSelector( 'getBlockCount' ); +export const getBlockSelectionStart = getBlockEditorSelector( 'getBlockSelectionStart' ); +export const getBlockSelectionEnd = getBlockEditorSelector( 'getBlockSelectionEnd' ); +export const getSelectedBlockCount = getBlockEditorSelector( 'getSelectedBlockCount' ); +export const hasSelectedBlock = getBlockEditorSelector( 'hasSelectedBlock' ); +export const getSelectedBlockClientId = getBlockEditorSelector( 'getSelectedBlockClientId' ); +export const getSelectedBlock = getBlockEditorSelector( 'getSelectedBlock' ); +export const getBlockRootClientId = getBlockEditorSelector( 'getBlockRootClientId' ); +export const getBlockHierarchyRootClientId = getBlockEditorSelector( 'getBlockHierarchyRootClientId' ); +export const getAdjacentBlockClientId = getBlockEditorSelector( 'getAdjacentBlockClientId' ); +export const getPreviousBlockClientId = getBlockEditorSelector( 'getPreviousBlockClientId' ); +export const getNextBlockClientId = getBlockEditorSelector( 'getNextBlockClientId' ); +export const getSelectedBlocksInitialCaretPosition = getBlockEditorSelector( 'getSelectedBlocksInitialCaretPosition' ); +export const getMultiSelectedBlockClientIds = getBlockEditorSelector( 'getMultiSelectedBlockClientIds' ); +export const getMultiSelectedBlocks = getBlockEditorSelector( 'getMultiSelectedBlocks' ); +export const getFirstMultiSelectedBlockClientId = getBlockEditorSelector( 'getFirstMultiSelectedBlockClientId' ); +export const getLastMultiSelectedBlockClientId = getBlockEditorSelector( 'getLastMultiSelectedBlockClientId' ); +export const isFirstMultiSelectedBlock = getBlockEditorSelector( 'isFirstMultiSelectedBlock' ); +export const isBlockMultiSelected = getBlockEditorSelector( 'isBlockMultiSelected' ); +export const isAncestorMultiSelected = getBlockEditorSelector( 'isAncestorMultiSelected' ); +export const getMultiSelectedBlocksStartClientId = getBlockEditorSelector( 'getMultiSelectedBlocksStartClientId' ); +export const getMultiSelectedBlocksEndClientId = getBlockEditorSelector( 'getMultiSelectedBlocksEndClientId' ); +export const getBlockOrder = getBlockEditorSelector( 'getBlockOrder' ); +export const getBlockIndex = getBlockEditorSelector( 'getBlockIndex' ); +export const isBlockSelected = getBlockEditorSelector( 'isBlockSelected' ); +export const hasSelectedInnerBlock = getBlockEditorSelector( 'hasSelectedInnerBlock' ); +export const isBlockWithinSelection = getBlockEditorSelector( 'isBlockWithinSelection' ); +export const hasMultiSelection = getBlockEditorSelector( 'hasMultiSelection' ); +export const isMultiSelecting = getBlockEditorSelector( 'isMultiSelecting' ); +export const isSelectionEnabled = getBlockEditorSelector( 'isSelectionEnabled' ); +export const getBlockMode = getBlockEditorSelector( 'getBlockMode' ); +export const isTyping = getBlockEditorSelector( 'isTyping' ); +export const isCaretWithinFormattedText = getBlockEditorSelector( 'isCaretWithinFormattedText' ); +export const getBlockInsertionPoint = getBlockEditorSelector( 'getBlockInsertionPoint' ); +export const isBlockInsertionPointVisible = getBlockEditorSelector( 'isBlockInsertionPointVisible' ); +export const isValidTemplate = getBlockEditorSelector( 'isValidTemplate' ); +export const getTemplate = getBlockEditorSelector( 'getTemplate' ); +export const getTemplateLock = getBlockEditorSelector( 'getTemplateLock' ); +export const canInsertBlockType = getBlockEditorSelector( 'canInsertBlockType' ); +export const getInserterItems = getBlockEditorSelector( 'getInserterItems' ); +export const hasInserterItems = getBlockEditorSelector( 'hasInserterItems' ); +export const getEditorSettings = getBlockEditorSelector( 'getEditorSettings' ); +export const getBlockListSettings = getBlockEditorSelector( 'getBlockListSettings' ); diff --git a/packages/editor/src/store/test/actions.js b/packages/editor/src/store/test/actions.js index 057e86c121aa5..ec46b287c394e 100644 --- a/packages/editor/src/store/test/actions.js +++ b/packages/editor/src/store/test/actions.js @@ -2,43 +2,18 @@ * Internal dependencies */ import { - replaceBlocks, - startTyping, - stopTyping, - enterFormattedText, - exitFormattedText, __experimentalFetchReusableBlocks as fetchReusableBlocks, __experimentalSaveReusableBlock as saveReusableBlock, __experimentalDeleteReusableBlock as deleteReusableBlock, __experimentalConvertBlockToStatic as convertBlockToStatic, __experimentalConvertBlockToReusable as convertBlockToReusable, - toggleSelection, setupEditor, resetPost, - resetBlocks, - updateBlockAttributes, - updateBlock, - selectBlock, - selectPreviousBlock, - startMultiSelect, - stopMultiSelect, - multiSelect, - clearSelectedBlock, - replaceBlock, - insertBlock, - insertBlocks, - showInsertionPoint, - hideInsertionPoint, editPost, savePost, trashPost, - mergeBlocks, redo, undo, - removeBlocks, - removeBlock, - toggleBlockMode, - updateBlockListSettings, } from '../actions'; describe( 'actions', () => { @@ -63,169 +38,6 @@ describe( 'actions', () => { } ); } ); } ); - describe( 'resetBlocks', () => { - it( 'should return the RESET_BLOCKS actions', () => { - const blocks = []; - const result = resetBlocks( blocks ); - expect( result ).toEqual( { - type: 'RESET_BLOCKS', - blocks, - } ); - } ); - } ); - - describe( 'updateBlockAttributes', () => { - it( 'should return the UPDATE_BLOCK_ATTRIBUTES action', () => { - const clientId = 'myclientid'; - const attributes = {}; - const result = updateBlockAttributes( clientId, attributes ); - expect( result ).toEqual( { - type: 'UPDATE_BLOCK_ATTRIBUTES', - clientId, - attributes, - } ); - } ); - } ); - - describe( 'updateBlock', () => { - it( 'should return the UPDATE_BLOCK action', () => { - const clientId = 'myclientid'; - const updates = {}; - const result = updateBlock( clientId, updates ); - expect( result ).toEqual( { - type: 'UPDATE_BLOCK', - clientId, - updates, - } ); - } ); - } ); - - describe( 'selectBlock', () => { - it( 'should return the SELECT_BLOCK action', () => { - const clientId = 'myclientid'; - const result = selectBlock( clientId, -1 ); - expect( result ).toEqual( { - type: 'SELECT_BLOCK', - initialPosition: -1, - clientId, - } ); - } ); - } ); - - describe( 'startMultiSelect', () => { - it( 'should return the START_MULTI_SELECT', () => { - expect( startMultiSelect() ).toEqual( { - type: 'START_MULTI_SELECT', - } ); - } ); - } ); - - describe( 'stopMultiSelect', () => { - it( 'should return the Stop_MULTI_SELECT', () => { - expect( stopMultiSelect() ).toEqual( { - type: 'STOP_MULTI_SELECT', - } ); - } ); - } ); - describe( 'multiSelect', () => { - it( 'should return MULTI_SELECT action', () => { - const start = 'start'; - const end = 'end'; - expect( multiSelect( start, end ) ).toEqual( { - type: 'MULTI_SELECT', - start, - end, - } ); - } ); - } ); - - describe( 'clearSelectedBlock', () => { - it( 'should return CLEAR_SELECTED_BLOCK action', () => { - expect( clearSelectedBlock() ).toEqual( { - type: 'CLEAR_SELECTED_BLOCK', - } ); - } ); - } ); - - describe( 'replaceBlock', () => { - it( 'should return the REPLACE_BLOCKS action', () => { - const block = { - clientId: 'ribs', - }; - - expect( replaceBlock( [ 'chicken' ], block ) ).toEqual( { - type: 'REPLACE_BLOCKS', - clientIds: [ 'chicken' ], - blocks: [ block ], - time: expect.any( Number ), - } ); - } ); - } ); - - describe( 'replaceBlocks', () => { - it( 'should return the REPLACE_BLOCKS action', () => { - const blocks = [ { - clientId: 'ribs', - } ]; - - expect( replaceBlocks( [ 'chicken' ], blocks ) ).toEqual( { - type: 'REPLACE_BLOCKS', - clientIds: [ 'chicken' ], - blocks, - time: expect.any( Number ), - } ); - } ); - } ); - - describe( 'insertBlock', () => { - it( 'should return the INSERT_BLOCKS action', () => { - const block = { - clientId: 'ribs', - }; - const index = 5; - expect( insertBlock( block, index, 'testclientid' ) ).toEqual( { - type: 'INSERT_BLOCKS', - blocks: [ block ], - index, - rootClientId: 'testclientid', - time: expect.any( Number ), - updateSelection: true, - } ); - } ); - } ); - - describe( 'insertBlocks', () => { - it( 'should return the INSERT_BLOCKS action', () => { - const blocks = [ { - clientId: 'ribs', - } ]; - const index = 3; - expect( insertBlocks( blocks, index, 'testclientid' ) ).toEqual( { - type: 'INSERT_BLOCKS', - blocks, - index, - rootClientId: 'testclientid', - time: expect.any( Number ), - updateSelection: true, - } ); - } ); - } ); - - describe( 'showInsertionPoint', () => { - it( 'should return the SHOW_INSERTION_POINT action', () => { - expect( showInsertionPoint() ).toEqual( { - type: 'SHOW_INSERTION_POINT', - } ); - } ); - } ); - - describe( 'hideInsertionPoint', () => { - it( 'should return the HIDE_INSERTION_POINT action', () => { - expect( hideInsertionPoint() ).toEqual( { - type: 'HIDE_INSERTION_POINT', - } ); - } ); - } ); describe( 'editPost', () => { it( 'should return EDIT_POST action', () => { @@ -265,17 +77,6 @@ describe( 'actions', () => { } ); } ); - describe( 'mergeBlocks', () => { - it( 'should return MERGE_BLOCKS action', () => { - const firstBlockClientId = 'blockA'; - const secondBlockClientId = 'blockB'; - expect( mergeBlocks( firstBlockClientId, secondBlockClientId ) ).toEqual( { - type: 'MERGE_BLOCKS', - blocks: [ firstBlockClientId, secondBlockClientId ], - } ); - } ); - } ); - describe( 'redo', () => { it( 'should return REDO action', () => { expect( redo() ).toEqual( { @@ -292,94 +93,6 @@ describe( 'actions', () => { } ); } ); - describe( 'removeBlocks', () => { - it( 'should return REMOVE_BLOCKS action', () => { - const clientId = 'clientId'; - const clientIds = [ clientId ]; - - const actions = Array.from( removeBlocks( clientIds ) ); - - expect( actions ).toEqual( [ - selectPreviousBlock( clientId ), - { - type: 'REMOVE_BLOCKS', - clientIds, - }, - ] ); - } ); - } ); - - describe( 'removeBlock', () => { - it( 'should return REMOVE_BLOCKS action', () => { - const clientId = 'myclientid'; - - const actions = Array.from( removeBlock( clientId ) ); - - expect( actions ).toEqual( [ - selectPreviousBlock( clientId ), - { - type: 'REMOVE_BLOCKS', - clientIds: [ clientId ], - }, - ] ); - } ); - - it( 'should return REMOVE_BLOCKS action, opting out of remove previous', () => { - const clientId = 'myclientid'; - - const actions = Array.from( removeBlock( clientId, false ) ); - - expect( actions ).toEqual( [ - { - type: 'REMOVE_BLOCKS', - clientIds: [ clientId ], - }, - ] ); - } ); - } ); - - describe( 'toggleBlockMode', () => { - it( 'should return TOGGLE_BLOCK_MODE action', () => { - const clientId = 'myclientid'; - expect( toggleBlockMode( clientId ) ).toEqual( { - type: 'TOGGLE_BLOCK_MODE', - clientId, - } ); - } ); - } ); - - describe( 'startTyping', () => { - it( 'should return the START_TYPING action', () => { - expect( startTyping() ).toEqual( { - type: 'START_TYPING', - } ); - } ); - } ); - - describe( 'stopTyping', () => { - it( 'should return the STOP_TYPING action', () => { - expect( stopTyping() ).toEqual( { - type: 'STOP_TYPING', - } ); - } ); - } ); - - describe( 'enterFormattedText', () => { - it( 'should return the ENTER_FORMATTED_TEXT action', () => { - expect( enterFormattedText() ).toEqual( { - type: 'ENTER_FORMATTED_TEXT', - } ); - } ); - } ); - - describe( 'exitFormattedText', () => { - it( 'should return the EXIT_FORMATTED_TEXT action', () => { - expect( exitFormattedText() ).toEqual( { - type: 'EXIT_FORMATTED_TEXT', - } ); - } ); - } ); - describe( 'fetchReusableBlocks', () => { it( 'should return the FETCH_REUSABLE_BLOCKS action', () => { expect( fetchReusableBlocks() ).toEqual( { @@ -432,45 +145,4 @@ describe( 'actions', () => { } ); } ); } ); - - describe( 'toggleSelection', () => { - it( 'should return the TOGGLE_SELECTION action with default value for isSelectionEnabled = true', () => { - expect( toggleSelection() ).toEqual( { - type: 'TOGGLE_SELECTION', - isSelectionEnabled: true, - } ); - } ); - - it( 'should return the TOGGLE_SELECTION action with isSelectionEnabled = true as passed in the argument', () => { - expect( toggleSelection( true ) ).toEqual( { - type: 'TOGGLE_SELECTION', - isSelectionEnabled: true, - } ); - } ); - - it( 'should return the TOGGLE_SELECTION action with isSelectionEnabled = false as passed in the argument', () => { - expect( toggleSelection( false ) ).toEqual( { - type: 'TOGGLE_SELECTION', - isSelectionEnabled: false, - } ); - } ); - } ); - - describe( 'updateBlockListSettings', () => { - it( 'should return the UPDATE_BLOCK_LIST_SETTINGS with undefined settings', () => { - expect( updateBlockListSettings( 'chicken' ) ).toEqual( { - type: 'UPDATE_BLOCK_LIST_SETTINGS', - clientId: 'chicken', - settings: undefined, - } ); - } ); - - it( 'should return the UPDATE_BLOCK_LIST_SETTINGS action with the passed settings', () => { - expect( updateBlockListSettings( 'chicken', { chicken: 'ribs' } ) ).toEqual( { - type: 'UPDATE_BLOCK_LIST_SETTINGS', - clientId: 'chicken', - settings: { chicken: 'ribs' }, - } ); - } ); - } ); } ); diff --git a/packages/editor/src/store/test/effects.js b/packages/editor/src/store/test/effects.js index a6ce470d86e54..2e9170d2175a0 100644 --- a/packages/editor/src/store/test/effects.js +++ b/packages/editor/src/store/test/effects.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { noop } from 'lodash'; - /** * WordPress dependencies */ @@ -10,27 +5,15 @@ import { getBlockTypes, unregisterBlockType, registerBlockType, - createBlock, } from '@wordpress/blocks'; -import { dispatch as dataDispatch, createRegistry } from '@wordpress/data'; +import { dispatch as dataDispatch } from '@wordpress/data'; /** * Internal dependencies */ -import actions, { - updateEditorSettings, - setupEditorState, - mergeBlocks, - replaceBlocks, - resetBlocks, - selectBlock, - setTemplateValidity, -} from '../actions'; -import effects, { validateBlocksToTemplate } from '../effects'; +import { setupEditorState, resetEditorBlocks } from '../actions'; +import effects from '../effects'; import { SAVE_POST_NOTICE_ID } from '../effects/posts'; -import * as selectors from '../selectors'; -import reducer from '../reducer'; -import applyMiddlewares from '../middlewares'; import '../../'; describe( 'effects', () => { @@ -46,178 +29,6 @@ describe( 'effects', () => { const defaultBlockSettings = { save: () => 'Saved', category: 'common', title: 'block title' }; - describe( '.MERGE_BLOCKS', () => { - const handler = effects.MERGE_BLOCKS; - const defaultGetBlock = selectors.getBlock; - - afterEach( () => { - getBlockTypes().forEach( ( block ) => { - unregisterBlockType( block.name ); - } ); - selectors.getBlock = defaultGetBlock; - } ); - - it( 'should only focus the blockA if the blockA has no merge function', () => { - registerBlockType( 'core/test-block', defaultBlockSettings ); - const blockA = { - clientId: 'chicken', - name: 'core/test-block', - }; - const blockB = { - clientId: 'ribs', - name: 'core/test-block', - }; - selectors.getBlock = ( state, clientId ) => { - return blockA.clientId === clientId ? blockA : blockB; - }; - - const dispatch = jest.fn(); - const getState = () => ( {} ); - handler( mergeBlocks( blockA.clientId, blockB.clientId ), { dispatch, getState } ); - - expect( dispatch ).toHaveBeenCalledTimes( 1 ); - expect( dispatch ).toHaveBeenCalledWith( selectBlock( 'chicken' ) ); - } ); - - it( 'should merge the blocks if blocks of the same type', () => { - registerBlockType( 'core/test-block', { - merge( attributes, attributesToMerge ) { - return { - content: attributes.content + ' ' + attributesToMerge.content, - }; - }, - save: noop, - category: 'common', - title: 'test block', - } ); - const blockA = { - clientId: 'chicken', - name: 'core/test-block', - attributes: { content: 'chicken' }, - }; - const blockB = { - clientId: 'ribs', - name: 'core/test-block', - attributes: { content: 'ribs' }, - }; - selectors.getBlock = ( state, clientId ) => { - return blockA.clientId === clientId ? blockA : blockB; - }; - const dispatch = jest.fn(); - const getState = () => ( {} ); - handler( mergeBlocks( blockA.clientId, blockB.clientId ), { dispatch, getState } ); - - expect( dispatch ).toHaveBeenCalledTimes( 2 ); - expect( dispatch ).toHaveBeenCalledWith( selectBlock( 'chicken', -1 ) ); - expect( dispatch ).toHaveBeenCalledWith( { - ...replaceBlocks( [ 'chicken', 'ribs' ], [ { - clientId: 'chicken', - name: 'core/test-block', - attributes: { content: 'chicken ribs' }, - } ] ), - time: expect.any( Number ), - } ); - } ); - - it( 'should not merge the blocks have different types without transformation', () => { - registerBlockType( 'core/test-block', { - merge( attributes, attributesToMerge ) { - return { - content: attributes.content + ' ' + attributesToMerge.content, - }; - }, - save: noop, - category: 'common', - title: 'test block', - } ); - registerBlockType( 'core/test-block-2', defaultBlockSettings ); - const blockA = { - clientId: 'chicken', - name: 'core/test-block', - attributes: { content: 'chicken' }, - }; - const blockB = { - clientId: 'ribs', - name: 'core/test-block2', - attributes: { content: 'ribs' }, - }; - selectors.getBlock = ( state, clientId ) => { - return blockA.clientId === clientId ? blockA : blockB; - }; - const dispatch = jest.fn(); - const getState = () => ( {} ); - handler( mergeBlocks( blockA.clientId, blockB.clientId ), { dispatch, getState } ); - - expect( dispatch ).not.toHaveBeenCalled(); - } ); - - it( 'should transform and merge the blocks', () => { - registerBlockType( 'core/test-block', { - attributes: { - content: { - type: 'string', - }, - }, - merge( attributes, attributesToMerge ) { - return { - content: attributes.content + ' ' + attributesToMerge.content, - }; - }, - save: noop, - category: 'common', - title: 'test block', - } ); - registerBlockType( 'core/test-block-2', { - attributes: { - content: { - type: 'string', - }, - }, - transforms: { - to: [ { - type: 'block', - blocks: [ 'core/test-block' ], - transform: ( { content2 } ) => { - return createBlock( 'core/test-block', { - content: content2, - } ); - }, - } ], - }, - save: noop, - category: 'common', - title: 'test block 2', - } ); - const blockA = { - clientId: 'chicken', - name: 'core/test-block', - attributes: { content: 'chicken' }, - }; - const blockB = { - clientId: 'ribs', - name: 'core/test-block-2', - attributes: { content2: 'ribs' }, - }; - selectors.getBlock = ( state, clientId ) => { - return blockA.clientId === clientId ? blockA : blockB; - }; - const dispatch = jest.fn(); - const getState = () => ( {} ); - handler( mergeBlocks( blockA.clientId, blockB.clientId ), { dispatch, getState } ); - - expect( dispatch ).toHaveBeenCalledTimes( 2 ); - // expect( dispatch ).toHaveBeenCalledWith( focusBlock( 'chicken', { offset: -1 } ) ); - expect( dispatch ).toHaveBeenCalledWith( { - ...replaceBlocks( [ 'chicken', 'ribs' ], [ { - clientId: 'chicken', - name: 'core/test-block', - attributes: { content: 'chicken ribs' }, - } ] ), - time: expect.any( Number ), - } ); - } ); - } ); - describe( '.REQUEST_POST_UPDATE_SUCCESS', () => { const handler = effects.REQUEST_POST_UPDATE_SUCCESS; @@ -423,19 +234,11 @@ describe( 'effects', () => { }, status: 'draft', }; - const getState = () => ( { - settings: { - template: null, - templateLock: false, - }, - template: { - isValid: true, - }, - } ); - const result = handler( { post, settings: {} }, { getState } ); + const result = handler( { post, settings: {} } ); expect( result ).toEqual( [ + resetEditorBlocks( [] ), setupEditorState( post, [], {} ), ] ); } ); @@ -452,22 +255,11 @@ describe( 'effects', () => { }, status: 'draft', }; - const getState = () => ( { - settings: { - template: null, - templateLock: false, - }, - template: { - isValid: true, - }, - } ); - const result = handler( { post }, { getState } ); + const result = handler( { post } ); expect( result[ 0 ].blocks ).toHaveLength( 1 ); - expect( result ).toEqual( [ - setupEditorState( post, result[ 0 ].blocks, {} ), - ] ); + expect( result[ 1 ] ).toEqual( setupEditorState( post, result[ 0 ].blocks, {} ) ); } ); it( 'should return post setup action only if auto-draft', () => { @@ -481,93 +273,13 @@ describe( 'effects', () => { }, status: 'auto-draft', }; - const getState = () => ( { - settings: { - template: null, - templateLock: false, - }, - template: { - isValid: true, - }, - } ); - const result = handler( { post }, { getState } ); + const result = handler( { post } ); expect( result ).toEqual( [ + resetEditorBlocks( [] ), setupEditorState( post, [], { title: 'A History of Pork' } ), ] ); } ); } ); - - describe( 'validateBlocksToTemplate', () => { - let store; - beforeEach( () => { - store = createRegistry().registerStore( 'test', { - actions, - selectors, - reducer, - } ); - applyMiddlewares( store ); - - registerBlockType( 'core/test-block', defaultBlockSettings ); - } ); - - afterEach( () => { - getBlockTypes().forEach( ( block ) => { - unregisterBlockType( block.name ); - } ); - } ); - - it( 'should return undefined if no template assigned', () => { - const result = validateBlocksToTemplate( resetBlocks( [ - createBlock( 'core/test-block' ), - ] ), store ); - - expect( result ).toBe( undefined ); - } ); - - it( 'should return undefined if invalid but unlocked', () => { - store.dispatch( updateEditorSettings( { - template: [ - [ 'core/foo', {} ], - ], - } ) ); - - const result = validateBlocksToTemplate( resetBlocks( [ - createBlock( 'core/test-block' ), - ] ), store ); - - expect( result ).toBe( undefined ); - } ); - - it( 'should return undefined if locked and valid', () => { - store.dispatch( updateEditorSettings( { - template: [ - [ 'core/test-block' ], - ], - templateLock: 'all', - } ) ); - - const result = validateBlocksToTemplate( resetBlocks( [ - createBlock( 'core/test-block' ), - ] ), store ); - - expect( result ).toBe( undefined ); - } ); - - it( 'should return validity set action if invalid on default state', () => { - store.dispatch( updateEditorSettings( { - template: [ - [ 'core/foo' ], - ], - templateLock: 'all', - } ) ); - - const result = validateBlocksToTemplate( resetBlocks( [ - createBlock( 'core/test-block' ), - ] ), store ); - - expect( result ).toEqual( setTemplateValidity( false ) ); - } ); - } ); } ); diff --git a/packages/editor/src/store/test/reducer.js b/packages/editor/src/store/test/reducer.js index a8172df208a41..f7be763e12170 100644 --- a/packages/editor/src/store/test/reducer.js +++ b/packages/editor/src/store/test/reducer.js @@ -1,40 +1,22 @@ /** * External dependencies */ -import { values, noop } from 'lodash'; import deepFreeze from 'deep-freeze'; -/** - * WordPress dependencies - */ -import { - registerBlockType, - unregisterBlockType, - createBlock, -} from '@wordpress/blocks'; - /** * Internal dependencies */ import { hasSameKeys, - isUpdatingSameBlockAttribute, isUpdatingSamePostProperty, shouldOverwriteState, getPostRawValue, - editor, initialEdits, + editor, currentPost, - isTyping, - isCaretWithinFormattedText, - blockSelection, preferences, saving, - blocksMode, - insertionPoint, reusableBlocks, - template, - blockListSettings, autosave, postSavingLock, previewLink, @@ -58,78 +40,6 @@ describe( 'state', () => { } ); } ); - describe( 'isUpdatingSameBlockAttribute()', () => { - it( 'should return false if not updating block attributes', () => { - const action = { - type: 'EDIT_POST', - edits: {}, - }; - const previousAction = { - type: 'EDIT_POST', - edits: {}, - }; - - expect( isUpdatingSameBlockAttribute( action, previousAction ) ).toBe( false ); - } ); - - it( 'should return false if not updating the same block', () => { - const action = { - type: 'UPDATE_BLOCK_ATTRIBUTES', - clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', - attributes: { - foo: 10, - }, - }; - const previousAction = { - type: 'UPDATE_BLOCK_ATTRIBUTES', - clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1', - attributes: { - foo: 20, - }, - }; - - expect( isUpdatingSameBlockAttribute( action, previousAction ) ).toBe( false ); - } ); - - it( 'should return false if not updating the same block attributes', () => { - const action = { - type: 'UPDATE_BLOCK_ATTRIBUTES', - clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', - attributes: { - foo: 10, - }, - }; - const previousAction = { - type: 'UPDATE_BLOCK_ATTRIBUTES', - clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', - attributes: { - bar: 20, - }, - }; - - expect( isUpdatingSameBlockAttribute( action, previousAction ) ).toBe( false ); - } ); - - it( 'should return true if updating the same block attributes', () => { - const action = { - type: 'UPDATE_BLOCK_ATTRIBUTES', - clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', - attributes: { - foo: 10, - }, - }; - const previousAction = { - type: 'UPDATE_BLOCK_ATTRIBUTES', - clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', - attributes: { - foo: 20, - }, - }; - - expect( isUpdatingSameBlockAttribute( action, previousAction ) ).toBe( true ); - } ); - } ); - describe( 'isUpdatingSamePostProperty()', () => { it( 'should return false if not editing post', () => { const action = { @@ -215,25 +125,6 @@ describe( 'state', () => { expect( shouldOverwriteState( action, previousAction ) ).toBe( false ); } ); - it( 'should return true if updating same block attribute', () => { - const action = { - type: 'UPDATE_BLOCK_ATTRIBUTES', - clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', - attributes: { - foo: 10, - }, - }; - const previousAction = { - type: 'UPDATE_BLOCK_ATTRIBUTES', - clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', - attributes: { - foo: 20, - }, - }; - - expect( shouldOverwriteState( action, previousAction ) ).toBe( true ); - } ); - it( 'should return true if updating same post property', () => { const action = { type: 'EDIT_POST', @@ -267,844 +158,148 @@ describe( 'state', () => { } ); describe( 'editor()', () => { - beforeAll( () => { - registerBlockType( 'core/test-block', { - save: noop, - edit: noop, - category: 'common', - title: 'test block', - } ); - } ); - - afterAll( () => { - unregisterBlockType( 'core/test-block' ); - } ); - - it( 'should return history (empty edits, blocks) by default', () => { - const state = editor( undefined, {} ); - - expect( state.past ).toEqual( [] ); - expect( state.future ).toEqual( [] ); - expect( state.present.edits ).toEqual( {} ); - expect( state.present.blocks.byClientId ).toEqual( {} ); - expect( state.present.blocks.order ).toEqual( {} ); - expect( state.present.blocks.isDirty ).toBe( false ); - } ); - - it( 'should key by reset blocks clientId', () => { - const original = editor( undefined, {} ); - const state = editor( original, { - type: 'RESET_BLOCKS', - blocks: [ { clientId: 'bananas', innerBlocks: [] } ], - } ); - - expect( Object.keys( state.present.blocks.byClientId ) ).toHaveLength( 1 ); - expect( values( state.present.blocks.byClientId )[ 0 ].clientId ).toBe( 'bananas' ); - expect( state.present.blocks.order ).toEqual( { - '': [ 'bananas' ], - bananas: [], - } ); - } ); - - it( 'should key by reset blocks clientId, including inner blocks', () => { - const original = editor( undefined, {} ); - const state = editor( original, { - type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'bananas', - innerBlocks: [ { clientId: 'apples', innerBlocks: [] } ], - } ], - } ); - - expect( Object.keys( state.present.blocks.byClientId ) ).toHaveLength( 2 ); - expect( state.present.blocks.order ).toEqual( { - '': [ 'bananas' ], - apples: [], - bananas: [ 'apples' ], - } ); - } ); - - it( 'should insert block', () => { - const original = editor( undefined, { - type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'chicken', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - } ], - } ); - const state = editor( original, { - type: 'INSERT_BLOCKS', - blocks: [ { - clientId: 'ribs', - name: 'core/freeform', - innerBlocks: [], - } ], - } ); - - expect( Object.keys( state.present.blocks.byClientId ) ).toHaveLength( 2 ); - expect( values( state.present.blocks.byClientId )[ 1 ].clientId ).toBe( 'ribs' ); - expect( state.present.blocks.order ).toEqual( { - '': [ 'chicken', 'ribs' ], - chicken: [], - ribs: [], - } ); - } ); - - it( 'should replace the block', () => { - const original = editor( undefined, { - type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'chicken', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - } ], - } ); - const state = editor( original, { - type: 'REPLACE_BLOCKS', - clientIds: [ 'chicken' ], - blocks: [ { - clientId: 'wings', - name: 'core/freeform', - innerBlocks: [], - } ], - } ); - - expect( Object.keys( state.present.blocks.byClientId ) ).toHaveLength( 1 ); - expect( values( state.present.blocks.byClientId )[ 0 ].name ).toBe( 'core/freeform' ); - expect( values( state.present.blocks.byClientId )[ 0 ].clientId ).toBe( 'wings' ); - expect( state.present.blocks.order ).toEqual( { - '': [ 'wings' ], - wings: [], - } ); - } ); - - it( 'should replace the nested block', () => { - const nestedBlock = createBlock( 'core/test-block' ); - const wrapperBlock = createBlock( 'core/test-block', {}, [ nestedBlock ] ); - const replacementBlock = createBlock( 'core/test-block' ); - const original = editor( undefined, { - type: 'RESET_BLOCKS', - blocks: [ wrapperBlock ], - } ); - - const state = editor( original, { - type: 'REPLACE_BLOCKS', - clientIds: [ nestedBlock.clientId ], - blocks: [ replacementBlock ], - } ); + describe( 'blocks()', () => { + it( 'should set its value by RESET_EDITOR_BLOCKS', () => { + const blocks = [ { + clientId: 'block3', + innerBlocks: [ + { clientId: 'block31', innerBlocks: [] }, + { clientId: 'block32', innerBlocks: [] }, + ], + } ]; + const state = editor( undefined, { + type: 'RESET_EDITOR_BLOCKS', + blocks, + } ); - expect( state.present.blocks.order ).toEqual( { - '': [ wrapperBlock.clientId ], - [ wrapperBlock.clientId ]: [ replacementBlock.clientId ], - [ replacementBlock.clientId ]: [], + expect( state.present.blocks.value ).toBe( blocks ); } ); } ); - it( 'should replace the block even if the new block clientId is the same', () => { - const originalState = editor( undefined, { - type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'chicken', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - } ], - } ); - const replacedState = editor( originalState, { - type: 'REPLACE_BLOCKS', - clientIds: [ 'chicken' ], - blocks: [ { - clientId: 'chicken', - name: 'core/freeform', - innerBlocks: [], - } ], - } ); - - expect( Object.keys( replacedState.present.blocks.byClientId ) ).toHaveLength( 1 ); - expect( values( originalState.present.blocks.byClientId )[ 0 ].name ).toBe( 'core/test-block' ); - expect( values( replacedState.present.blocks.byClientId )[ 0 ].name ).toBe( 'core/freeform' ); - expect( values( replacedState.present.blocks.byClientId )[ 0 ].clientId ).toBe( 'chicken' ); - expect( replacedState.present.blocks.order ).toEqual( { - '': [ 'chicken' ], - chicken: [], - } ); - - const nestedBlock = { - clientId: 'chicken', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }; - const wrapperBlock = createBlock( 'core/test-block', {}, [ nestedBlock ] ); - const replacementNestedBlock = { - clientId: 'chicken', - name: 'core/freeform', - attributes: {}, - innerBlocks: [], - }; - - const originalNestedState = editor( undefined, { - type: 'RESET_BLOCKS', - blocks: [ wrapperBlock ], - } ); + describe( 'edits()', () => { + it( 'should save newly edited properties', () => { + const original = editor( undefined, { + type: 'EDIT_POST', + edits: { + status: 'draft', + title: 'post title', + }, + } ); - const replacedNestedState = editor( originalNestedState, { - type: 'REPLACE_BLOCKS', - clientIds: [ nestedBlock.clientId ], - blocks: [ replacementNestedBlock ], - } ); + const state = editor( original, { + type: 'EDIT_POST', + edits: { + tags: [ 1 ], + }, + } ); - expect( replacedNestedState.present.blocks.order ).toEqual( { - '': [ wrapperBlock.clientId ], - [ wrapperBlock.clientId ]: [ replacementNestedBlock.clientId ], - [ replacementNestedBlock.clientId ]: [], + expect( state.present.edits ).toEqual( { + status: 'draft', + title: 'post title', + tags: [ 1 ], + } ); } ); - expect( originalNestedState.present.blocks.byClientId.chicken.name ).toBe( 'core/test-block' ); - expect( replacedNestedState.present.blocks.byClientId.chicken.name ).toBe( 'core/freeform' ); - } ); + it( 'should return same reference if no changed properties', () => { + const original = editor( undefined, { + type: 'EDIT_POST', + edits: { + status: 'draft', + title: 'post title', + }, + } ); - it( 'should update the block', () => { - const original = editor( undefined, { - type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'chicken', - name: 'core/test-block', - attributes: {}, - isValid: false, - innerBlocks: [], - } ], - } ); - const state = editor( deepFreeze( original ), { - type: 'UPDATE_BLOCK', - clientId: 'chicken', - updates: { - attributes: { content: 'ribs' }, - isValid: true, - }, - } ); + const state = editor( original, { + type: 'EDIT_POST', + edits: { + status: 'draft', + }, + } ); - expect( state.present.blocks.byClientId.chicken ).toEqual( { - clientId: 'chicken', - name: 'core/test-block', - isValid: true, + expect( state.present.edits ).toBe( original.present.edits ); } ); - expect( state.present.blocks.attributes.chicken ).toEqual( { - content: 'ribs', - } ); - } ); + it( 'should save modified properties', () => { + const original = editor( undefined, { + type: 'EDIT_POST', + edits: { + status: 'draft', + title: 'post title', + tags: [ 1 ], + }, + } ); - it( 'should update the reusable block reference if the temporary id is swapped', () => { - const original = editor( undefined, { - type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'chicken', - name: 'core/block', - attributes: { - ref: 'random-clientId', + const state = editor( original, { + type: 'EDIT_POST', + edits: { + title: 'modified title', + tags: [ 2 ], }, - isValid: false, - innerBlocks: [], - } ], - } ); + } ); - const state = editor( deepFreeze( original ), { - type: 'SAVE_REUSABLE_BLOCK_SUCCESS', - id: 'random-clientId', - updatedId: 3, + expect( state.present.edits ).toEqual( { + status: 'draft', + title: 'modified title', + tags: [ 2 ], + } ); } ); - expect( state.present.blocks.byClientId.chicken ).toEqual( { - clientId: 'chicken', - name: 'core/block', - isValid: false, - } ); + it( 'should merge object values', () => { + const original = editor( undefined, { + type: 'EDIT_POST', + edits: { + meta: { + a: 1, + }, + }, + } ); - expect( state.present.blocks.attributes.chicken ).toEqual( { - ref: 3, - } ); - } ); + const state = editor( original, { + type: 'EDIT_POST', + edits: { + meta: { + b: 2, + }, + }, + } ); - it( 'should move the block up', () => { - const original = editor( undefined, { - type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'chicken', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'ribs', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - } ], - } ); - const state = editor( original, { - type: 'MOVE_BLOCKS_UP', - clientIds: [ 'ribs' ], + expect( state.present.edits ).toEqual( { + meta: { + a: 1, + b: 2, + }, + } ); } ); - expect( state.present.blocks.order[ '' ] ).toEqual( [ 'ribs', 'chicken' ] ); - } ); + it( 'return state by reference on unchanging update', () => { + const original = editor( undefined, {} ); - it( 'should move the nested block up', () => { - const movedBlock = createBlock( 'core/test-block' ); - const siblingBlock = createBlock( 'core/test-block' ); - const wrapperBlock = createBlock( 'core/test-block', {}, [ siblingBlock, movedBlock ] ); - const original = editor( undefined, { - type: 'RESET_BLOCKS', - blocks: [ wrapperBlock ], - } ); - const state = editor( original, { - type: 'MOVE_BLOCKS_UP', - clientIds: [ movedBlock.clientId ], - rootClientId: wrapperBlock.clientId, - } ); + const state = editor( original, { + type: 'UPDATE_POST', + edits: {}, + } ); - expect( state.present.blocks.order ).toEqual( { - '': [ wrapperBlock.clientId ], - [ wrapperBlock.clientId ]: [ movedBlock.clientId, siblingBlock.clientId ], - [ movedBlock.clientId ]: [], - [ siblingBlock.clientId ]: [], + expect( state.present.edits ).toBe( original.present.edits ); } ); - } ); - it( 'should move multiple blocks up', () => { - const original = editor( undefined, { - type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'chicken', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'ribs', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'veggies', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - } ], - } ); - const state = editor( original, { - type: 'MOVE_BLOCKS_UP', - clientIds: [ 'ribs', 'veggies' ], - } ); + it( 'unset reset post values which match by canonical value', () => { + const original = editor( undefined, { + type: 'EDIT_POST', + edits: { + title: 'modified title', + }, + } ); - expect( state.present.blocks.order[ '' ] ).toEqual( [ 'ribs', 'veggies', 'chicken' ] ); - } ); + const state = editor( original, { + type: 'RESET_POST', + post: { + title: { + raw: 'modified title', + }, + }, + } ); - it( 'should move multiple nested blocks up', () => { - const movedBlockA = createBlock( 'core/test-block' ); - const movedBlockB = createBlock( 'core/test-block' ); - const siblingBlock = createBlock( 'core/test-block' ); - const wrapperBlock = createBlock( 'core/test-block', {}, [ siblingBlock, movedBlockA, movedBlockB ] ); - const original = editor( undefined, { - type: 'RESET_BLOCKS', - blocks: [ wrapperBlock ], - } ); - const state = editor( original, { - type: 'MOVE_BLOCKS_UP', - clientIds: [ movedBlockA.clientId, movedBlockB.clientId ], - rootClientId: wrapperBlock.clientId, - } ); - - expect( state.present.blocks.order ).toEqual( { - '': [ wrapperBlock.clientId ], - [ wrapperBlock.clientId ]: [ movedBlockA.clientId, movedBlockB.clientId, siblingBlock.clientId ], - [ movedBlockA.clientId ]: [], - [ movedBlockB.clientId ]: [], - [ siblingBlock.clientId ]: [], - } ); - } ); - - it( 'should not move the first block up', () => { - const original = editor( undefined, { - type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'chicken', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'ribs', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - } ], - } ); - const state = editor( original, { - type: 'MOVE_BLOCKS_UP', - clientIds: [ 'chicken' ], - } ); - - expect( state.present.blocks.order ).toBe( original.present.blocks.order ); - } ); - - it( 'should move the block down', () => { - const original = editor( undefined, { - type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'chicken', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'ribs', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - } ], - } ); - const state = editor( original, { - type: 'MOVE_BLOCKS_DOWN', - clientIds: [ 'chicken' ], - } ); - - expect( state.present.blocks.order[ '' ] ).toEqual( [ 'ribs', 'chicken' ] ); - } ); - - it( 'should move the nested block down', () => { - const movedBlock = createBlock( 'core/test-block' ); - const siblingBlock = createBlock( 'core/test-block' ); - const wrapperBlock = createBlock( 'core/test-block', {}, [ movedBlock, siblingBlock ] ); - const original = editor( undefined, { - type: 'RESET_BLOCKS', - blocks: [ wrapperBlock ], - } ); - const state = editor( original, { - type: 'MOVE_BLOCKS_DOWN', - clientIds: [ movedBlock.clientId ], - rootClientId: wrapperBlock.clientId, - } ); - - expect( state.present.blocks.order ).toEqual( { - '': [ wrapperBlock.clientId ], - [ wrapperBlock.clientId ]: [ siblingBlock.clientId, movedBlock.clientId ], - [ movedBlock.clientId ]: [], - [ siblingBlock.clientId ]: [], - } ); - } ); - - it( 'should move multiple blocks down', () => { - const original = editor( undefined, { - type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'chicken', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'ribs', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'veggies', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - } ], - } ); - const state = editor( original, { - type: 'MOVE_BLOCKS_DOWN', - clientIds: [ 'chicken', 'ribs' ], - } ); - - expect( state.present.blocks.order[ '' ] ).toEqual( [ 'veggies', 'chicken', 'ribs' ] ); - } ); - - it( 'should move multiple nested blocks down', () => { - const movedBlockA = createBlock( 'core/test-block' ); - const movedBlockB = createBlock( 'core/test-block' ); - const siblingBlock = createBlock( 'core/test-block' ); - const wrapperBlock = createBlock( 'core/test-block', {}, [ movedBlockA, movedBlockB, siblingBlock ] ); - const original = editor( undefined, { - type: 'RESET_BLOCKS', - blocks: [ wrapperBlock ], - } ); - const state = editor( original, { - type: 'MOVE_BLOCKS_DOWN', - clientIds: [ movedBlockA.clientId, movedBlockB.clientId ], - rootClientId: wrapperBlock.clientId, - } ); - - expect( state.present.blocks.order ).toEqual( { - '': [ wrapperBlock.clientId ], - [ wrapperBlock.clientId ]: [ siblingBlock.clientId, movedBlockA.clientId, movedBlockB.clientId ], - [ movedBlockA.clientId ]: [], - [ movedBlockB.clientId ]: [], - [ siblingBlock.clientId ]: [], - } ); - } ); - - it( 'should not move the last block down', () => { - const original = editor( undefined, { - type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'chicken', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'ribs', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - } ], - } ); - const state = editor( original, { - type: 'MOVE_BLOCKS_DOWN', - clientIds: [ 'ribs' ], - } ); - - expect( state.present.blocks.order ).toBe( original.present.blocks.order ); - } ); - - it( 'should remove the block', () => { - const original = editor( undefined, { - type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'chicken', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'ribs', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - } ], - } ); - const state = editor( original, { - type: 'REMOVE_BLOCKS', - clientIds: [ 'chicken' ], - } ); - - expect( state.present.blocks.order[ '' ] ).toEqual( [ 'ribs' ] ); - expect( state.present.blocks.order ).not.toHaveProperty( 'chicken' ); - expect( state.present.blocks.byClientId ).toEqual( { - ribs: { - clientId: 'ribs', - name: 'core/test-block', - }, - } ); - expect( state.present.blocks.attributes ).toEqual( { - ribs: {}, - } ); - } ); - - it( 'should remove multiple blocks', () => { - const original = editor( undefined, { - type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'chicken', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'ribs', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'veggies', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - } ], - } ); - const state = editor( original, { - type: 'REMOVE_BLOCKS', - clientIds: [ 'chicken', 'veggies' ], - } ); - - expect( state.present.blocks.order[ '' ] ).toEqual( [ 'ribs' ] ); - expect( state.present.blocks.order ).not.toHaveProperty( 'chicken' ); - expect( state.present.blocks.order ).not.toHaveProperty( 'veggies' ); - expect( state.present.blocks.byClientId ).toEqual( { - ribs: { - clientId: 'ribs', - name: 'core/test-block', - }, - } ); - expect( state.present.blocks.attributes ).toEqual( { - ribs: {}, - } ); - } ); - - it( 'should cascade remove to include inner blocks', () => { - const block = createBlock( 'core/test-block', {}, [ - createBlock( 'core/test-block', {}, [ - createBlock( 'core/test-block' ), - ] ), - ] ); - - const original = editor( undefined, { - type: 'RESET_BLOCKS', - blocks: [ block ], - } ); - - const state = editor( original, { - type: 'REMOVE_BLOCKS', - clientIds: [ block.clientId ], - } ); - - expect( state.present.blocks.byClientId ).toEqual( {} ); - expect( state.present.blocks.order ).toEqual( { - '': [], - } ); - } ); - - it( 'should insert at the specified index', () => { - const original = editor( undefined, { - type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'kumquat', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'loquat', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - } ], - } ); - - const state = editor( original, { - type: 'INSERT_BLOCKS', - index: 1, - blocks: [ { - clientId: 'persimmon', - name: 'core/freeform', - innerBlocks: [], - } ], - } ); - - expect( Object.keys( state.present.blocks.byClientId ) ).toHaveLength( 3 ); - expect( state.present.blocks.order[ '' ] ).toEqual( [ 'kumquat', 'persimmon', 'loquat' ] ); - } ); - - it( 'should move block to lower index', () => { - const original = editor( undefined, { - type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'chicken', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'ribs', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'veggies', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - } ], - } ); - const state = editor( original, { - type: 'MOVE_BLOCK_TO_POSITION', - clientId: 'ribs', - index: 0, - } ); - - expect( state.present.blocks.order[ '' ] ).toEqual( [ 'ribs', 'chicken', 'veggies' ] ); - } ); - - it( 'should move block to higher index', () => { - const original = editor( undefined, { - type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'chicken', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'ribs', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'veggies', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - } ], - } ); - const state = editor( original, { - type: 'MOVE_BLOCK_TO_POSITION', - clientId: 'ribs', - index: 2, - } ); - - expect( state.present.blocks.order[ '' ] ).toEqual( [ 'chicken', 'veggies', 'ribs' ] ); - } ); - - it( 'should not move block if passed same index', () => { - const original = editor( undefined, { - type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'chicken', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'ribs', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'veggies', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - } ], - } ); - const state = editor( original, { - type: 'MOVE_BLOCK_TO_POSITION', - clientId: 'ribs', - index: 1, - } ); - - expect( state.present.blocks.order[ '' ] ).toEqual( [ 'chicken', 'ribs', 'veggies' ] ); - } ); - - describe( 'edits()', () => { - it( 'should save newly edited properties', () => { - const original = editor( undefined, { - type: 'EDIT_POST', - edits: { - status: 'draft', - title: 'post title', - }, - } ); - - const state = editor( original, { - type: 'EDIT_POST', - edits: { - tags: [ 1 ], - }, - } ); - - expect( state.present.edits ).toEqual( { - status: 'draft', - title: 'post title', - tags: [ 1 ], - } ); - } ); - - it( 'should return same reference if no changed properties', () => { - const original = editor( undefined, { - type: 'EDIT_POST', - edits: { - status: 'draft', - title: 'post title', - }, - } ); - - const state = editor( original, { - type: 'EDIT_POST', - edits: { - status: 'draft', - }, - } ); - - expect( state.present.edits ).toBe( original.present.edits ); - } ); - - it( 'should save modified properties', () => { - const original = editor( undefined, { - type: 'EDIT_POST', - edits: { - status: 'draft', - title: 'post title', - tags: [ 1 ], - }, - } ); - - const state = editor( original, { - type: 'EDIT_POST', - edits: { - title: 'modified title', - tags: [ 2 ], - }, - } ); - - expect( state.present.edits ).toEqual( { - status: 'draft', - title: 'modified title', - tags: [ 2 ], - } ); - } ); - - it( 'should merge object values', () => { - const original = editor( undefined, { - type: 'EDIT_POST', - edits: { - meta: { - a: 1, - }, - }, - } ); - - const state = editor( original, { - type: 'EDIT_POST', - edits: { - meta: { - b: 2, - }, - }, - } ); - - expect( state.present.edits ).toEqual( { - meta: { - a: 1, - b: 2, - }, - } ); - } ); - - it( 'return state by reference on unchanging update', () => { - const original = editor( undefined, {} ); - - const state = editor( original, { - type: 'UPDATE_POST', - edits: {}, - } ); - - expect( state.present.edits ).toBe( original.present.edits ); - } ); - - it( 'unset reset post values which match by canonical value', () => { - const original = editor( undefined, { - type: 'EDIT_POST', - edits: { - title: 'modified title', - }, - } ); - - const state = editor( original, { - type: 'RESET_POST', - post: { - title: { - raw: 'modified title', - }, - }, - } ); - - expect( state.present.edits ).toEqual( {} ); + expect( state.present.edits ).toEqual( {} ); } ); it( 'unset reset post values by deep match', () => { @@ -1119,812 +314,173 @@ describe( 'state', () => { }, } ); - const state = editor( original, { - type: 'UPDATE_POST', - edits: { - title: 'modified title', - meta: { - a: 1, - b: 2, - }, - }, - } ); - - expect( state.present.edits ).toEqual( {} ); - } ); - - it( 'should omit content when resetting', () => { - // Use case: When editing in Text mode, we defer to content on - // the property, but we reset blocks by parse when switching - // back to Visual mode. - const original = deepFreeze( editor( undefined, {} ) ); - let state = editor( original, { - type: 'EDIT_POST', - edits: { - content: 'bananas', - }, - } ); - - expect( state.present.edits ).toHaveProperty( 'content' ); - - state = editor( original, { - type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'kumquat', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'loquat', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - } ], - } ); - - expect( state.present.edits ).not.toHaveProperty( 'content' ); - } ); - } ); - - describe( 'blocks', () => { - it( 'should not reset any blocks that are not in the post', () => { - const actions = [ - { - type: 'RESET_BLOCKS', - blocks: [ - { - clientId: 'block1', - innerBlocks: [ - { clientId: 'block11', innerBlocks: [] }, - { clientId: 'block12', innerBlocks: [] }, - ], - }, - ], - }, - { - type: 'RECEIVE_BLOCKS', - blocks: [ - { - clientId: 'block2', - innerBlocks: [ - { clientId: 'block21', innerBlocks: [] }, - { clientId: 'block22', innerBlocks: [] }, - ], - }, - ], - }, - ]; - const original = deepFreeze( actions.reduce( editor, undefined ) ); - - const state = editor( original, { - type: 'RESET_BLOCKS', - blocks: [ - { - clientId: 'block3', - innerBlocks: [ - { clientId: 'block31', innerBlocks: [] }, - { clientId: 'block32', innerBlocks: [] }, - ], - }, - ], - } ); - - expect( state.present.blocks.byClientId ).toEqual( { - block2: { clientId: 'block2' }, - block21: { clientId: 'block21' }, - block22: { clientId: 'block22' }, - block3: { clientId: 'block3' }, - block31: { clientId: 'block31' }, - block32: { clientId: 'block32' }, - } ); - } ); - - describe( 'byClientId', () => { - it( 'should ignore updates to non-existent block', () => { - const original = deepFreeze( editor( undefined, { - type: 'RESET_BLOCKS', - blocks: [], - } ) ); - const state = editor( original, { - type: 'UPDATE_BLOCK_ATTRIBUTES', - clientId: 'kumquat', - attributes: { - updated: true, - }, - } ); - - expect( state.present.blocks.byClientId ).toBe( original.present.blocks.byClientId ); - } ); - - it( 'should return with same reference if no changes in updates', () => { - const original = deepFreeze( editor( undefined, { - type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'kumquat', - attributes: { - updated: true, - }, - innerBlocks: [], - } ], - } ) ); - const state = editor( original, { - type: 'UPDATE_BLOCK_ATTRIBUTES', - clientId: 'kumquat', - attributes: { - updated: true, - }, - } ); - - expect( state.present.blocks.byClientId ).toBe( state.present.blocks.byClientId ); - } ); - } ); - - describe( 'attributes', () => { - it( 'should return with attribute block updates', () => { - const original = deepFreeze( editor( undefined, { - type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'kumquat', - attributes: {}, - innerBlocks: [], - } ], - } ) ); - const state = editor( original, { - type: 'UPDATE_BLOCK_ATTRIBUTES', - clientId: 'kumquat', - attributes: { - updated: true, - }, - } ); - - expect( state.present.blocks.attributes.kumquat.updated ).toBe( true ); - } ); - - it( 'should accumulate attribute block updates', () => { - const original = deepFreeze( editor( undefined, { - type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'kumquat', - attributes: { - updated: true, - }, - innerBlocks: [], - } ], - } ) ); - const state = editor( original, { - type: 'UPDATE_BLOCK_ATTRIBUTES', - clientId: 'kumquat', - attributes: { - moreUpdated: true, - }, - } ); - - expect( state.present.blocks.attributes.kumquat ).toEqual( { - updated: true, - moreUpdated: true, - } ); - } ); - - it( 'should ignore updates to non-existent block', () => { - const original = deepFreeze( editor( undefined, { - type: 'RESET_BLOCKS', - blocks: [], - } ) ); - const state = editor( original, { - type: 'UPDATE_BLOCK_ATTRIBUTES', - clientId: 'kumquat', - attributes: { - updated: true, - }, - } ); - - expect( state.present.blocks.attributes ).toBe( original.present.blocks.attributes ); - } ); - - it( 'should return with same reference if no changes in updates', () => { - const original = deepFreeze( editor( undefined, { - type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'kumquat', - attributes: { - updated: true, - }, - innerBlocks: [], - } ], - } ) ); - const state = editor( original, { - type: 'UPDATE_BLOCK_ATTRIBUTES', - clientId: 'kumquat', - attributes: { - updated: true, - }, - } ); - - expect( state.present.blocks.attributes ).toBe( state.present.blocks.attributes ); - } ); - } ); - } ); - - describe( 'withHistory', () => { - it( 'should overwrite present history if updating same attributes', () => { - let state; - - state = editor( state, { - type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'kumquat', - attributes: {}, - innerBlocks: [], - } ], - } ); - - expect( state.past ).toHaveLength( 1 ); - - state = editor( state, { - type: 'UPDATE_BLOCK_ATTRIBUTES', - clientId: 'kumquat', - attributes: { - test: 1, - }, - } ); - - state = editor( state, { - type: 'UPDATE_BLOCK_ATTRIBUTES', - clientId: 'kumquat', - attributes: { - test: 2, - }, - } ); - - expect( state.past ).toHaveLength( 2 ); - } ); - - it( 'should not overwrite present history if updating different attributes', () => { - let state; - - state = editor( state, { - type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'kumquat', - attributes: {}, - innerBlocks: [], - } ], - } ); - - expect( state.past ).toHaveLength( 1 ); - - state = editor( state, { - type: 'UPDATE_BLOCK_ATTRIBUTES', - clientId: 'kumquat', - attributes: { - test: 1, - }, - } ); - - state = editor( state, { - type: 'UPDATE_BLOCK_ATTRIBUTES', - clientId: 'kumquat', - attributes: { - other: 1, - }, - } ); - - expect( state.past ).toHaveLength( 3 ); - } ); - } ); - } ); - - describe( 'initialEdits', () => { - it( 'should default to initial edits', () => { - const state = initialEdits( undefined, {} ); - - expect( state ).toBe( INITIAL_EDITS_DEFAULTS ); - } ); - - it( 'should return initial edits on post reset', () => { - const state = initialEdits( undefined, { - type: 'RESET_POST', - } ); - - expect( state ).toBe( INITIAL_EDITS_DEFAULTS ); - } ); - - it( 'should return referentially equal state if setup includes no edits', () => { - const original = initialEdits( undefined, {} ); - const state = initialEdits( deepFreeze( original ), { - type: 'SETUP_EDITOR', - } ); - - expect( state ).toBe( original ); - } ); - - it( 'should return referentially equal state if reset while having made no edits', () => { - const original = initialEdits( undefined, {} ); - const state = initialEdits( deepFreeze( original ), { - type: 'RESET_POST', - } ); - - expect( state ).toBe( original ); - } ); - - it( 'should return setup edits', () => { - const original = initialEdits( undefined, {} ); - const state = initialEdits( deepFreeze( original ), { - type: 'SETUP_EDITOR', - edits: { - title: '', - content: '', - }, - } ); - - expect( state ).toEqual( { - title: '', - content: '', - } ); - } ); - - it( 'should unset content on editor setup', () => { - const original = initialEdits( undefined, { - type: 'SETUP_EDITOR', - edits: { - title: '', - content: '', - }, - } ); - const state = initialEdits( deepFreeze( original ), { - type: 'SETUP_EDITOR_STATE', - } ); - - expect( state ).toEqual( { title: '' } ); - } ); - - it( 'should unset values on post update', () => { - const original = initialEdits( undefined, { - type: 'SETUP_EDITOR', - edits: { - title: '', - }, - } ); - const state = initialEdits( deepFreeze( original ), { - type: 'UPDATE_POST', - edits: { - title: '', - }, - } ); - - expect( state ).toEqual( {} ); - } ); - } ); - - describe( 'currentPost()', () => { - it( 'should reset a post object', () => { - const original = deepFreeze( { title: 'unmodified' } ); - - const state = currentPost( original, { - type: 'RESET_POST', - post: { - title: 'new post', - }, - } ); - - expect( state ).toEqual( { - title: 'new post', - } ); - } ); - - it( 'should update the post object with UPDATE_POST', () => { - const original = deepFreeze( { title: 'unmodified', status: 'publish' } ); - - const state = currentPost( original, { - type: 'UPDATE_POST', - edits: { - title: 'updated post object from server', - }, - } ); - - expect( state ).toEqual( { - title: 'updated post object from server', - status: 'publish', - } ); - } ); - } ); - - describe( 'insertionPoint', () => { - it( 'should default to null', () => { - const state = insertionPoint( undefined, {} ); - - expect( state ).toBe( null ); - } ); - - it( 'should set insertion point', () => { - const state = insertionPoint( null, { - type: 'SHOW_INSERTION_POINT', - rootClientId: 'clientId1', - index: 0, - } ); - - expect( state ).toEqual( { - rootClientId: 'clientId1', - index: 0, - } ); - } ); - - it( 'should clear the insertion point', () => { - const original = deepFreeze( { - rootClientId: 'clientId1', - index: 0, - } ); - const state = insertionPoint( original, { - type: 'HIDE_INSERTION_POINT', - } ); - - expect( state ).toBe( null ); - } ); - } ); - - describe( 'isTyping()', () => { - it( 'should set the typing flag to true', () => { - const state = isTyping( false, { - type: 'START_TYPING', - } ); - - expect( state ).toBe( true ); - } ); - - it( 'should set the typing flag to false', () => { - const state = isTyping( false, { - type: 'STOP_TYPING', - } ); - - expect( state ).toBe( false ); - } ); - } ); - - describe( 'isCaretWithinFormattedText()', () => { - it( 'should set the flag to true', () => { - const state = isCaretWithinFormattedText( false, { - type: 'ENTER_FORMATTED_TEXT', - } ); - - expect( state ).toBe( true ); - } ); - - it( 'should set the flag to false', () => { - const state = isCaretWithinFormattedText( true, { - type: 'EXIT_FORMATTED_TEXT', - } ); - - expect( state ).toBe( false ); - } ); - } ); - - describe( 'blockSelection()', () => { - it( 'should return with block clientId as selected', () => { - const state = blockSelection( undefined, { - type: 'SELECT_BLOCK', - clientId: 'kumquat', - initialPosition: -1, - } ); - - expect( state ).toEqual( { - start: 'kumquat', - end: 'kumquat', - initialPosition: -1, - isMultiSelecting: false, - isEnabled: true, - } ); - } ); - - it( 'should set multi selection', () => { - const original = deepFreeze( { isMultiSelecting: false } ); - const state = blockSelection( original, { - type: 'MULTI_SELECT', - start: 'ribs', - end: 'chicken', - } ); - - expect( state ).toEqual( { - start: 'ribs', - end: 'chicken', - initialPosition: null, - isMultiSelecting: false, - } ); - } ); - - it( 'should set continuous multi selection', () => { - const original = deepFreeze( { isMultiSelecting: true } ); - const state = blockSelection( original, { - type: 'MULTI_SELECT', - start: 'ribs', - end: 'chicken', - } ); - - expect( state ).toEqual( { - start: 'ribs', - end: 'chicken', - initialPosition: null, - isMultiSelecting: true, - } ); - } ); - - it( 'should start multi selection', () => { - const original = deepFreeze( { start: 'ribs', end: 'ribs', isMultiSelecting: false } ); - const state = blockSelection( original, { - type: 'START_MULTI_SELECT', - } ); - - expect( state ).toEqual( { - start: 'ribs', - end: 'ribs', - initialPosition: null, - isMultiSelecting: true, - } ); - } ); - - it( 'should return same reference if already multi-selecting', () => { - const original = deepFreeze( { start: 'ribs', end: 'ribs', isMultiSelecting: true } ); - const state = blockSelection( original, { - type: 'START_MULTI_SELECT', - } ); - - expect( state ).toBe( original ); - } ); - - it( 'should end multi selection with selection', () => { - const original = deepFreeze( { start: 'ribs', end: 'chicken', isMultiSelecting: true } ); - const state = blockSelection( original, { - type: 'STOP_MULTI_SELECT', - } ); - - expect( state ).toEqual( { - start: 'ribs', - end: 'chicken', - initialPosition: null, - isMultiSelecting: false, - } ); - } ); - - it( 'should return same reference if already ended multi-selecting', () => { - const original = deepFreeze( { start: 'ribs', end: 'chicken', isMultiSelecting: false } ); - const state = blockSelection( original, { - type: 'STOP_MULTI_SELECT', - } ); - - expect( state ).toBe( original ); - } ); - - it( 'should end multi selection without selection', () => { - const original = deepFreeze( { start: 'ribs', end: 'ribs', isMultiSelecting: true } ); - const state = blockSelection( original, { - type: 'STOP_MULTI_SELECT', - } ); - - expect( state ).toEqual( { - start: 'ribs', - end: 'ribs', - initialPosition: null, - isMultiSelecting: false, - } ); - } ); - - it( 'should not update the state if the block is already selected', () => { - const original = deepFreeze( { start: 'ribs', end: 'ribs' } ); + const state = editor( original, { + type: 'UPDATE_POST', + edits: { + title: 'modified title', + meta: { + a: 1, + b: 2, + }, + }, + } ); - const state1 = blockSelection( original, { - type: 'SELECT_BLOCK', - clientId: 'ribs', + expect( state.present.edits ).toEqual( {} ); } ); - expect( state1 ).toBe( original ); - } ); + it( 'should omit content when resetting', () => { + // Use case: When editing in Text mode, we defer to content on + // the property, but we reset blocks by parse when switching + // back to Visual mode. + const original = deepFreeze( editor( undefined, {} ) ); + let state = editor( original, { + type: 'EDIT_POST', + edits: { + content: 'bananas', + }, + } ); - it( 'should unset multi selection', () => { - const original = deepFreeze( { start: 'ribs', end: 'chicken' } ); + expect( state.present.edits ).toHaveProperty( 'content' ); - const state1 = blockSelection( original, { - type: 'CLEAR_SELECTED_BLOCK', - } ); + state = editor( original, { + type: 'RESET_EDITOR_BLOCKS', + blocks: [ { + clientId: 'kumquat', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, { + clientId: 'loquat', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + } ], + } ); - expect( state1 ).toEqual( { - start: null, - end: null, - initialPosition: null, - isMultiSelecting: false, + expect( state.present.edits ).not.toHaveProperty( 'content' ); } ); } ); + } ); - it( 'should return same reference if clearing selection but no selection', () => { - const original = deepFreeze( { start: null, end: null, isMultiSelecting: false } ); - - const state1 = blockSelection( original, { - type: 'CLEAR_SELECTED_BLOCK', - } ); + describe( 'initialEdits', () => { + it( 'should default to initial edits', () => { + const state = initialEdits( undefined, {} ); - expect( state1 ).toBe( original ); + expect( state ).toBe( INITIAL_EDITS_DEFAULTS ); } ); - it( 'should select inserted block', () => { - const original = deepFreeze( { start: 'ribs', end: 'chicken' } ); - - const state3 = blockSelection( original, { - type: 'INSERT_BLOCKS', - blocks: [ { - clientId: 'ribs', - name: 'core/freeform', - } ], - updateSelection: true, + it( 'should return initial edits on post reset', () => { + const state = initialEdits( undefined, { + type: 'RESET_POST', } ); - expect( state3 ).toEqual( { - start: 'ribs', - end: 'ribs', - initialPosition: null, - isMultiSelecting: false, - } ); + expect( state ).toBe( INITIAL_EDITS_DEFAULTS ); } ); - it( 'should not select inserted block if updateSelection flag is false', () => { - const original = deepFreeze( { start: 'a', end: 'b' } ); - - const state3 = blockSelection( original, { - type: 'INSERT_BLOCKS', - blocks: [ { - clientId: 'ribs', - name: 'core/freeform', - } ], - updateSelection: false, + it( 'should return referentially equal state if setup includes no edits', () => { + const original = initialEdits( undefined, {} ); + const state = initialEdits( deepFreeze( original ), { + type: 'SETUP_EDITOR', } ); - expect( state3 ).toEqual( { - start: 'a', - end: 'b', - } ); + expect( state ).toBe( original ); } ); - it( 'should not update the state if the block moved is already selected', () => { - const original = deepFreeze( { start: 'ribs', end: 'ribs' } ); - const state = blockSelection( original, { - type: 'MOVE_BLOCKS_UP', - clientIds: [ 'ribs' ], + it( 'should return referentially equal state if reset while having made no edits', () => { + const original = initialEdits( undefined, {} ); + const state = initialEdits( deepFreeze( original ), { + type: 'RESET_POST', } ); expect( state ).toBe( original ); } ); - it( 'should replace the selected block', () => { - const original = deepFreeze( { start: 'chicken', end: 'chicken' } ); - const state = blockSelection( original, { - type: 'REPLACE_BLOCKS', - clientIds: [ 'chicken' ], - blocks: [ { - clientId: 'wings', - name: 'core/freeform', - } ], + it( 'should return setup edits', () => { + const original = initialEdits( undefined, {} ); + const state = initialEdits( deepFreeze( original ), { + type: 'SETUP_EDITOR', + edits: { + title: '', + content: '', + }, } ); expect( state ).toEqual( { - start: 'wings', - end: 'wings', - initialPosition: null, - isMultiSelecting: false, + title: '', + content: '', } ); } ); - it( 'should not replace the selected block if we keep it at the end when replacing blocks', () => { - const original = deepFreeze( { start: 'wings', end: 'wings' } ); - const state = blockSelection( original, { - type: 'REPLACE_BLOCKS', - clientIds: [ 'wings' ], - blocks: [ - { - clientId: 'chicken', - name: 'core/freeform', - }, - { - clientId: 'wings', - name: 'core/freeform', - } ], + it( 'should unset content on editor setup', () => { + const original = initialEdits( undefined, { + type: 'SETUP_EDITOR', + edits: { + title: '', + content: '', + }, + } ); + const state = initialEdits( deepFreeze( original ), { + type: 'SETUP_EDITOR_STATE', } ); - expect( state ).toBe( original ); + expect( state ).toEqual( { title: '' } ); } ); - it( 'should replace the selected block if we keep it not at the end when replacing blocks', () => { - const original = deepFreeze( { start: 'chicken', end: 'chicken' } ); - const state = blockSelection( original, { - type: 'REPLACE_BLOCKS', - clientIds: [ 'chicken' ], - blocks: [ - { - clientId: 'chicken', - name: 'core/freeform', - }, - { - clientId: 'wings', - name: 'core/freeform', - } ], + it( 'should unset values on post update', () => { + const original = initialEdits( undefined, { + type: 'SETUP_EDITOR', + edits: { + title: '', + }, } ); - - expect( state ).toEqual( { - start: 'wings', - end: 'wings', - initialPosition: null, - isMultiSelecting: false, + const state = initialEdits( deepFreeze( original ), { + type: 'UPDATE_POST', + edits: { + title: '', + }, } ); + + expect( state ).toEqual( {} ); } ); + } ); + + describe( 'currentPost()', () => { + it( 'should reset a post object', () => { + const original = deepFreeze( { title: 'unmodified' } ); - it( 'should reset if replacing with empty set', () => { - const original = deepFreeze( { start: 'chicken', end: 'chicken' } ); - const state = blockSelection( original, { - type: 'REPLACE_BLOCKS', - clientIds: [ 'chicken' ], - blocks: [], + const state = currentPost( original, { + type: 'RESET_POST', + post: { + title: 'new post', + }, } ); expect( state ).toEqual( { - start: null, - end: null, - initialPosition: null, - isMultiSelecting: false, + title: 'new post', } ); } ); - it( 'should keep the selected block', () => { - const original = deepFreeze( { start: 'chicken', end: 'chicken' } ); - const state = blockSelection( original, { - type: 'REPLACE_BLOCKS', - clientIds: [ 'ribs' ], - blocks: [ { - clientId: 'wings', - name: 'core/freeform', - } ], - } ); - - expect( state ).toBe( original ); - } ); + it( 'should update the post object with UPDATE_POST', () => { + const original = deepFreeze( { title: 'unmodified', status: 'publish' } ); - it( 'should remove the selection if we are removing the selected block', () => { - const original = deepFreeze( { - start: 'chicken', - end: 'chicken', - initialPosition: null, - isMultiSelecting: false, - } ); - const state = blockSelection( original, { - type: 'REMOVE_BLOCKS', - clientIds: [ 'chicken' ], + const state = currentPost( original, { + type: 'UPDATE_POST', + edits: { + title: 'updated post object from server', + }, } ); expect( state ).toEqual( { - start: null, - end: null, - initialPosition: null, - isMultiSelecting: false, - } ); - } ); - - it( 'should keep the selection if we are not removing the selected block', () => { - const original = deepFreeze( { - start: 'chicken', - end: 'chicken', - initialPosition: null, - isMultiSelecting: false, - } ); - const state = blockSelection( original, { - type: 'REMOVE_BLOCKS', - clientIds: [ 'ribs' ], + title: 'updated post object from server', + status: 'publish', } ); - - expect( state ).toBe( original ); } ); } ); describe( 'preferences()', () => { it( 'should apply all defaults', () => { const state = preferences( undefined, {} ); - expect( state ).toEqual( { - insertUsage: {}, isPublishSidebarEnabled: true, } ); } ); @@ -1946,84 +502,6 @@ describe( 'state', () => { expect( state.isPublishSidebarEnabled ).toBe( true ); } ); - - it( 'should record recently used blocks', () => { - const state = preferences( deepFreeze( { insertUsage: {} } ), { - type: 'INSERT_BLOCKS', - blocks: [ { - clientId: 'bacon', - name: 'core-embed/twitter', - } ], - time: 123456, - } ); - - expect( state ).toEqual( { - insertUsage: { - 'core-embed/twitter': { - time: 123456, - count: 1, - insert: { name: 'core-embed/twitter' }, - }, - }, - } ); - - const twoRecentBlocks = preferences( deepFreeze( { - insertUsage: { - 'core-embed/twitter': { - time: 123456, - count: 1, - insert: { name: 'core-embed/twitter' }, - }, - }, - } ), { - type: 'INSERT_BLOCKS', - blocks: [ { - clientId: 'eggs', - name: 'core-embed/twitter', - }, { - clientId: 'bacon', - name: 'core/block', - attributes: { ref: 123 }, - } ], - time: 123457, - } ); - - expect( twoRecentBlocks ).toEqual( { - insertUsage: { - 'core-embed/twitter': { - time: 123457, - count: 2, - insert: { name: 'core-embed/twitter' }, - }, - 'core/block/123': { - time: 123457, - count: 1, - insert: { name: 'core/block', ref: 123 }, - }, - }, - } ); - } ); - - it( 'should remove recorded reusable blocks that are deleted', () => { - const initialState = { - insertUsage: { - 'core/block/123': { - time: 1000, - count: 1, - insert: { name: 'core/block', ref: 123 }, - }, - }, - }; - - const state = preferences( deepFreeze( initialState ), { - type: 'REMOVE_REUSABLE_BLOCK', - id: 123, - } ); - - expect( state ).toEqual( { - insertUsage: {}, - } ); - } ); } ); describe( 'saving()', () => { @@ -2071,28 +549,6 @@ describe( 'state', () => { } ); } ); - describe( 'blocksMode', () => { - it( 'should set mode to html if not set', () => { - const action = { - type: 'TOGGLE_BLOCK_MODE', - clientId: 'chicken', - }; - const value = blocksMode( deepFreeze( {} ), action ); - - expect( value ).toEqual( { chicken: 'html' } ); - } ); - - it( 'should toggle mode to visual if set as html', () => { - const action = { - type: 'TOGGLE_BLOCK_MODE', - clientId: 'chicken', - }; - const value = blocksMode( deepFreeze( { chicken: 'html' } ), action ); - - expect( value ).toEqual( { chicken: 'visual' } ); - } ); - } ); - describe( 'reusableBlocks()', () => { it( 'should start out empty', () => { const state = reusableBlocks( undefined, {} ); @@ -2346,153 +802,6 @@ describe( 'state', () => { } ); } ); - describe( 'template', () => { - it( 'should default to visible', () => { - const state = template( undefined, {} ); - - expect( state ).toEqual( { isValid: true } ); - } ); - - it( 'should reset the validity flag', () => { - const original = deepFreeze( { isValid: false, template: [] } ); - const state = template( original, { - type: 'SET_TEMPLATE_VALIDITY', - isValid: true, - } ); - - expect( state ).toEqual( { isValid: true, template: [] } ); - } ); - } ); - - describe( 'blockListSettings', () => { - it( 'should add new settings', () => { - const original = deepFreeze( {} ); - - const state = blockListSettings( original, { - type: 'UPDATE_BLOCK_LIST_SETTINGS', - clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', - settings: { - allowedBlocks: [ 'core/paragraph' ], - }, - } ); - - expect( state ).toEqual( { - '9db792c6-a25a-495d-adbd-97d56a4c4189': { - allowedBlocks: [ 'core/paragraph' ], - }, - } ); - } ); - - it( 'should return same reference if updated as the same', () => { - const original = deepFreeze( { - '9db792c6-a25a-495d-adbd-97d56a4c4189': { - allowedBlocks: [ 'core/paragraph' ], - }, - } ); - - const state = blockListSettings( original, { - type: 'UPDATE_BLOCK_LIST_SETTINGS', - clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', - settings: { - allowedBlocks: [ 'core/paragraph' ], - }, - } ); - - expect( state ).toBe( original ); - } ); - - it( 'should return same reference if updated settings not assigned and id not exists', () => { - const original = deepFreeze( {} ); - - const state = blockListSettings( original, { - type: 'UPDATE_BLOCK_LIST_SETTINGS', - clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', - } ); - - expect( state ).toBe( original ); - } ); - - it( 'should update the settings of a block', () => { - const original = deepFreeze( { - '9db792c6-a25a-495d-adbd-97d56a4c4189': { - allowedBlocks: [ 'core/paragraph' ], - }, - 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': { - allowedBlocks: true, - }, - } ); - - const state = blockListSettings( original, { - type: 'UPDATE_BLOCK_LIST_SETTINGS', - clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', - settings: { - allowedBlocks: [ 'core/list' ], - }, - } ); - - expect( state ).toEqual( { - '9db792c6-a25a-495d-adbd-97d56a4c4189': { - allowedBlocks: [ 'core/list' ], - }, - 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': { - allowedBlocks: true, - }, - } ); - } ); - - it( 'should remove existing settings if updated settings not assigned', () => { - const original = deepFreeze( { - '9db792c6-a25a-495d-adbd-97d56a4c4189': { - allowedBlocks: [ 'core/paragraph' ], - }, - } ); - - const state = blockListSettings( original, { - type: 'UPDATE_BLOCK_LIST_SETTINGS', - clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', - } ); - - expect( state ).toEqual( {} ); - } ); - - it( 'should remove the settings of a block when it is replaced', () => { - const original = deepFreeze( { - '9db792c6-a25a-495d-adbd-97d56a4c4189': { - allowedBlocks: [ 'core/paragraph' ], - }, - 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': { - allowedBlocks: true, - }, - } ); - - const state = blockListSettings( original, { - type: 'REPLACE_BLOCKS', - clientIds: [ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1' ], - } ); - - expect( state ).toEqual( { - '9db792c6-a25a-495d-adbd-97d56a4c4189': { - allowedBlocks: [ 'core/paragraph' ], - }, - } ); - } ); - - it( 'should remove the settings of a block when it is removed', () => { - const original = deepFreeze( { - 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': { - allowedBlocks: true, - }, - } ); - - const state = blockListSettings( original, { - type: 'REMOVE_BLOCKS', - clientIds: [ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1' ], - } ); - - expect( state ).toEqual( {} ); - } ); - } ); - describe( 'autosave', () => { it( 'returns null by default', () => { const state = autosave( undefined, {} ); diff --git a/packages/editor/src/store/test/selectors.js b/packages/editor/src/store/test/selectors.js index d75c81edbb909..459161ee03801 100644 --- a/packages/editor/src/store/test/selectors.js +++ b/packages/editor/src/store/test/selectors.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { filter, without, omit } from 'lodash'; +import { filter, without } from 'lodash'; /** * WordPress dependencies @@ -13,6 +13,7 @@ import { getDefaultBlockName, setDefaultBlockName, setFreeformContentHandlerName, + getBlockTypes, } from '@wordpress/blocks'; import { RawHTML } from '@wordpress/element'; @@ -23,7 +24,6 @@ import * as selectors from '../selectors'; import { PREFERENCES_DEFAULTS } from '../defaults'; const { - canUserUseUnfilteredHTML, hasEditorUndo, hasEditorRedo, isEditedPostNew, @@ -49,69 +49,27 @@ const { isEditedPostEmpty, isEditedPostBeingScheduled, isEditedPostDateFloating, - getBlockDependantsCacheBust, - getBlockName, - getBlock, - getBlocks, - getBlockCount, - getClientIdsWithDescendants, - getClientIdsOfDescendants, - hasSelectedBlock, - getSelectedBlock, - getSelectedBlockClientId, - getBlockRootClientId, - getBlockHierarchyRootClientId, getCurrentPostAttribute, getEditedPostAttribute, getAutosaveAttribute, - getGlobalBlockCount, - getMultiSelectedBlockClientIds, - getMultiSelectedBlocks, - getMultiSelectedBlocksStartClientId, - getMultiSelectedBlocksEndClientId, - getBlockOrder, - getBlockIndex, - getPreviousBlockClientId, - getNextBlockClientId, - isBlockSelected, - hasSelectedInnerBlock, - isBlockWithinSelection, - hasMultiSelection, - isBlockMultiSelected, - isFirstMultiSelectedBlock, - getBlockMode, - isTyping, - isCaretWithinFormattedText, - getBlockInsertionPoint, - isBlockInsertionPointVisible, isSavingPost, didPostSaveRequestSucceed, didPostSaveRequestFail, getSuggestedPostFormat, - getBlocksForSerialization, getEditedPostContent, __experimentalGetReusableBlock: getReusableBlock, __experimentalIsSavingReusableBlock: isSavingReusableBlock, __experimentalIsFetchingReusableBlock: isFetchingReusableBlock, - isSelectionEnabled, __experimentalGetReusableBlocks: getReusableBlocks, getStateBeforeOptimisticTransaction, isPublishingPost, isPublishSidebarEnabled, - canInsertBlockType, - getInserterItems, - isValidTemplate, - getTemplate, - getTemplateLock, - getBlockListSettings, POST_UPDATE_TRANSACTION_ID, isPermalinkEditable, getPermalink, getPermalinkParts, - INSERTER_UTILITY_HIGH, - INSERTER_UTILITY_MEDIUM, - INSERTER_UTILITY_LOW, isPostSavingLocked, + canUserUseUnfilteredHTML, } = selectors; describe( 'selectors', () => { @@ -309,6 +267,7 @@ describe( 'selectors', () => { present: { blocks: { isDirty: true, + value: [], }, edits: {}, }, @@ -324,6 +283,7 @@ describe( 'selectors', () => { present: { blocks: { isDirty: false, + value: [], }, edits: { content: 'text mode edited', @@ -344,6 +304,7 @@ describe( 'selectors', () => { present: { blocks: { isDirty: false, + value: [], }, edits: {}, }, @@ -360,6 +321,7 @@ describe( 'selectors', () => { present: { blocks: { isDirty: true, + value: [], }, edits: {}, }, @@ -376,6 +338,7 @@ describe( 'selectors', () => { present: { blocks: { isDirty: false, + value: [], }, edits: { excerpt: 'hello world', @@ -396,6 +359,7 @@ describe( 'selectors', () => { present: { blocks: { isDirty: true, + value: [], }, edits: {}, }, @@ -407,6 +371,7 @@ describe( 'selectors', () => { present: { blocks: { isDirty: false, + value: [], }, edits: {}, }, @@ -424,6 +389,7 @@ describe( 'selectors', () => { present: { blocks: { isDirty: false, + value: [], }, edits: {}, }, @@ -446,6 +412,7 @@ describe( 'selectors', () => { present: { blocks: { isDirty: false, + value: [], }, edits: {}, }, @@ -468,6 +435,7 @@ describe( 'selectors', () => { present: { blocks: { isDirty: true, + value: [], }, edits: {}, }, @@ -994,6 +962,7 @@ describe( 'selectors', () => { present: { blocks: { isDirty: false, + value: [], }, edits: {}, }, @@ -1015,6 +984,7 @@ describe( 'selectors', () => { present: { blocks: { isDirty: false, + value: [], }, edits: {}, }, @@ -1036,6 +1006,7 @@ describe( 'selectors', () => { present: { blocks: { isDirty: false, + value: [], }, edits: {}, }, @@ -1057,6 +1028,7 @@ describe( 'selectors', () => { present: { blocks: { isDirty: true, + value: [], }, edits: {}, }, @@ -1078,6 +1050,7 @@ describe( 'selectors', () => { present: { blocks: { isDirty: false, + value: [], }, edits: {}, }, @@ -1099,6 +1072,7 @@ describe( 'selectors', () => { present: { blocks: { isDirty: false, + value: [], }, edits: {}, }, @@ -1123,6 +1097,7 @@ describe( 'selectors', () => { present: { blocks: { isDirty: true, + value: [], }, edits: {}, }, @@ -1164,9 +1139,7 @@ describe( 'selectors', () => { editor: { present: { blocks: { - byClientId: {}, - attributes: {}, - order: {}, + value: [], }, edits: {}, }, @@ -1184,9 +1157,7 @@ describe( 'selectors', () => { editor: { present: { blocks: { - byClientId: {}, - attributes: {}, - order: {}, + value: [], }, edits: {}, }, @@ -1208,9 +1179,7 @@ describe( 'selectors', () => { editor: { present: { blocks: { - byClientId: {}, - attributes: {}, - order: {}, + value: [], }, edits: {}, }, @@ -1233,6 +1202,7 @@ describe( 'selectors', () => { byClientId: {}, attributes: {}, order: {}, + value: [], }, edits: {}, }, @@ -1252,21 +1222,16 @@ describe( 'selectors', () => { editor: { present: { blocks: { - byClientId: { - 123: { + value: [ + { clientId: 123, name: 'core/test-block-a', isValid: true, + attributes: { + text: '', + }, }, - }, - attributes: { - 123: { - text: '', - }, - }, - order: { - '': [ 123 ], - }, + ], }, edits: {}, }, @@ -1284,20 +1249,16 @@ describe( 'selectors', () => { editor: { present: { blocks: { - byClientId: { - 123: { + value: [ + { clientId: 123, name: 'core/test-freeform', + isValid: true, + attributes: { + content: '', + }, }, - }, - attributes: { - 123: { - content: '', - }, - }, - order: { - '': [ 123 ], - }, + ], }, edits: {}, }, @@ -1315,21 +1276,16 @@ describe( 'selectors', () => { editor: { present: { blocks: { - byClientId: { - 123: { + value: [ + { clientId: 123, name: 'core/test-freeform', isValid: true, + attributes: { + content: '', + }, }, - }, - attributes: { - 123: { - content: '', - }, - }, - order: { - '': [ 123 ], - }, + ], }, edits: {}, }, @@ -1351,9 +1307,7 @@ describe( 'selectors', () => { editor: { present: { blocks: { - byClientId: {}, - attributes: {}, - order: {}, + value: [], }, edits: {}, }, @@ -1378,9 +1332,7 @@ describe( 'selectors', () => { editor: { present: { blocks: { - byClientId: {}, - attributes: {}, - order: {}, + value: [], }, edits: {}, }, @@ -1401,6 +1353,7 @@ describe( 'selectors', () => { editor: { present: { blocks: { + value: [], isDirty: false, }, edits: {}, @@ -1426,6 +1379,7 @@ describe( 'selectors', () => { editor: { present: { blocks: { + value: [], isDirty: true, }, edits: {}, @@ -1453,6 +1407,7 @@ describe( 'selectors', () => { present: { blocks: { isDirty: false, + value: [], }, edits: {}, }, @@ -1524,9 +1479,7 @@ describe( 'selectors', () => { editor: { present: { blocks: { - byClientId: {}, - attributes: {}, - order: {}, + value: [], }, edits: {}, }, @@ -1543,21 +1496,14 @@ describe( 'selectors', () => { editor: { present: { blocks: { - byClientId: { - 123: { - clientId: 123, - name: 'core/test-block-a', - isValid: true, - }, - }, - attributes: { - 123: { + value: [ { + clientId: 123, + name: 'core/test-block-a', + isValid: true, + attributes: { text: '', }, - }, - order: { - '': [ 123 ], - }, + } ], }, edits: {}, }, @@ -1579,20 +1525,13 @@ describe( 'selectors', () => { editor: { present: { blocks: { - byClientId: { - block1: { - clientId: 'block1', - name: 'core/test-default', - }, - }, - attributes: { - block1: { + value: [ { + clientId: 'block1', + name: 'core/test-default', + attributes: { modified: false, }, - }, - order: { - '': [ 'block1' ], - }, + } ], }, edits: {}, }, @@ -1609,21 +1548,14 @@ describe( 'selectors', () => { editor: { present: { blocks: { - byClientId: { - 123: { - clientId: 123, - name: 'core/test-block-a', - isValid: true, - }, - }, - attributes: { - 123: { + value: [ { + clientId: 123, + name: 'core/test-block-a', + isValid: true, + attributes: { text: '', }, - }, - order: { - '': [ 123 ], - }, + } ], }, edits: { content: '', @@ -1644,9 +1576,7 @@ describe( 'selectors', () => { editor: { present: { blocks: { - byClientId: {}, - attributes: {}, - order: {}, + value: [], }, edits: {}, }, @@ -1665,9 +1595,7 @@ describe( 'selectors', () => { editor: { present: { blocks: { - byClientId: {}, - attributes: {}, - order: {}, + value: [], }, edits: { content: 'sassel', @@ -1686,21 +1614,14 @@ describe( 'selectors', () => { editor: { present: { blocks: { - byClientId: { - 123: { - clientId: 123, - name: 'core/test-freeform', - isValid: true, - }, - }, - attributes: { - 123: { + value: [ { + clientId: 123, + name: 'core/test-freeform', + isValid: true, + attributes: { content: '', }, - }, - order: { - '': [ 123 ], - }, + } ], }, edits: {}, }, @@ -1717,21 +1638,14 @@ describe( 'selectors', () => { editor: { present: { blocks: { - byClientId: { - 123: { - clientId: 123, - name: 'core/test-freeform', - isValid: true, - }, - }, - attributes: { - 123: { + value: [ { + clientId: 123, + name: 'core/test-freeform', + isValid: true, + attributes: { content: '', }, - }, - order: { - '': [ 123 ], - }, + } ], }, edits: {}, }, @@ -1750,21 +1664,14 @@ describe( 'selectors', () => { editor: { present: { blocks: { - byClientId: { - 123: { - clientId: 123, - name: 'core/test-freeform', - isValid: true, - }, - }, - attributes: { - 123: { + value: [ { + clientId: 123, + name: 'core/test-freeform', + isValid: true, + attributes: { content: 'Test Data', }, - }, - order: { - '': [ 123 ], - }, + } ], }, edits: {}, }, @@ -1783,29 +1690,24 @@ describe( 'selectors', () => { editor: { present: { blocks: { - byClientId: { - 123: { + value: [ + { clientId: 123, name: 'core/test-freeform', isValid: true, + attributes: { + content: '', + }, }, - 456: { + { clientId: 456, name: 'core/test-freeform', isValid: true, + attributes: { + content: '', + }, }, - }, - attributes: { - 123: { - content: '', - }, - 456: { - content: '', - }, - }, - order: { - '': [ 123, 456 ], - }, + ], }, edits: {}, }, @@ -1960,3038 +1862,378 @@ describe( 'selectors', () => { } ); } ); - describe( 'getBlockDependantsCacheBust', () => { - const rootBlock = { clientId: 123, name: 'core/paragraph' }; - const rootBlockAttributes = {}; - const rootOrder = [ 123 ]; + describe( 'isSavingPost', () => { + it( 'should return true if the post is currently being saved', () => { + const state = { + saving: { + requesting: true, + }, + }; - it( 'returns an unchanging reference', () => { - const rootBlockOrder = []; + expect( isSavingPost( state ) ).toBe( true ); + } ); + it( 'should return false if the post is not currently being saved', () => { const state = { - currentPost: {}, - editor: { - present: { - blocks: { - byClientId: { - 123: rootBlock, - }, - attributes: { - 123: rootBlockAttributes, - }, - order: { - '': rootOrder, - 123: rootBlockOrder, - }, - }, - edits: {}, - }, + saving: { + requesting: false, }, - initialEdits: {}, }; - const nextState = { - currentPost: {}, - editor: { - present: { - blocks: { - byClientId: { - 123: rootBlock, - }, - attributes: { - 123: rootBlockAttributes, - }, - order: { - '': rootOrder, - 123: rootBlockOrder, - }, - }, - edits: {}, - }, + expect( isSavingPost( state ) ).toBe( false ); + } ); + } ); + + describe( 'didPostSaveRequestSucceed', () => { + it( 'should return true if the post save request is successful', () => { + const state = { + saving: { + successful: true, }, - initialEdits: {}, }; - expect( - getBlockDependantsCacheBust( state, 123 ) - ).toBe( getBlockDependantsCacheBust( nextState, 123 ) ); + expect( didPostSaveRequestSucceed( state ) ).toBe( true ); } ); - it( 'returns a new reference on added inner block', () => { + it( 'should return true if the post save request has failed', () => { const state = { - currentPost: {}, - editor: { - present: { - blocks: { - byClientId: { - 123: rootBlock, - }, - attributes: { - 123: rootBlockAttributes, - }, - order: { - '': rootOrder, - 123: [], - }, - }, - edits: {}, - }, + saving: { + successful: false, }, - initialEdits: {}, }; - const nextState = { - currentPost: {}, - editor: { - present: { - blocks: { - byClientId: { - 123: rootBlock, - 456: { clientId: 456, name: 'core/paragraph' }, - }, - attributes: { - 123: rootBlockAttributes, - 456: {}, - }, - order: { - '': rootOrder, - 123: [ 456 ], - 456: [], - }, - }, - edits: {}, - }, + expect( didPostSaveRequestSucceed( state ) ).toBe( false ); + } ); + } ); + + describe( 'didPostSaveRequestFail', () => { + it( 'should return true if the post save request has failed', () => { + const state = { + saving: { + error: 'error', }, - initialEdits: {}, }; - expect( - getBlockDependantsCacheBust( state, 123 ) - ).not.toBe( getBlockDependantsCacheBust( nextState, 123 ) ); + expect( didPostSaveRequestFail( state ) ).toBe( true ); } ); - it( 'returns an unchanging reference on unchanging inner block', () => { - const rootBlockOrder = [ 456 ]; - const childBlock = { clientId: 456, name: 'core/paragraph' }; - const childBlockAttributes = {}; - const childBlockOrder = []; + it( 'should return true if the post save request is successful', () => { + const state = { + saving: { + error: false, + }, + }; + + expect( didPostSaveRequestFail( state ) ).toBe( false ); + } ); + } ); + describe( 'getSuggestedPostFormat', () => { + it( 'returns null if cannot be determined', () => { const state = { - currentPost: {}, editor: { present: { blocks: { - byClientId: { - 123: rootBlock, - 456: childBlock, - }, - attributes: { - 123: rootBlockAttributes, - 456: childBlockAttributes, - }, - order: { - '': rootOrder, - 123: rootBlockOrder, - 456: childBlockOrder, - }, + value: [], }, edits: {}, }, }, initialEdits: {}, + currentPost: {}, }; - const nextState = { - currentPost: {}, + expect( getSuggestedPostFormat( state ) ).toBeNull(); + } ); + + it( 'returns null if there is more than one block in the post', () => { + const state = { editor: { present: { blocks: { - byClientId: { - 123: rootBlock, - 456: childBlock, - }, - attributes: { - 123: rootBlockAttributes, - 456: childBlockAttributes, - }, - order: { - '': rootOrder, - 123: rootBlockOrder, - 456: childBlockOrder, - }, + value: [ + { + clientId: 123, + name: 'core/image', + attributes: {}, + }, + { + clientId: 456, + name: 'core/quote', + attributes: {}, + }, + ], }, edits: {}, }, }, initialEdits: {}, + currentPost: {}, }; - expect( - getBlockDependantsCacheBust( state, 123 ) - ).toBe( getBlockDependantsCacheBust( nextState, 123 ) ); + expect( getSuggestedPostFormat( state ) ).toBeNull(); } ); - it( 'returns a new reference on updated inner block', () => { - const rootBlockOrder = [ 456 ]; - const childBlockOrder = []; - + it( 'returns Image if the first block is of type `core/image`', () => { const state = { - currentPost: {}, editor: { present: { blocks: { - byClientId: { - 123: rootBlock, - 456: { clientId: 456, name: 'core/paragraph' }, - }, - attributes: { - 123: rootBlockAttributes, - 456: {}, - }, - order: { - '': rootOrder, - 123: rootBlockOrder, - 456: childBlockOrder, - }, + value: [ + { + clientId: 123, + name: 'core/image', + attributes: {}, + }, + ], }, edits: {}, }, }, initialEdits: {}, + currentPost: {}, }; - const nextState = { - currentPost: {}, + expect( getSuggestedPostFormat( state ) ).toBe( 'image' ); + } ); + + it( 'returns Quote if the first block is of type `core/quote`', () => { + const state = { editor: { present: { blocks: { - byClientId: { - 123: rootBlock, - 456: { clientId: 456, name: 'core/paragraph' }, - }, - attributes: { - 123: rootBlockAttributes, - 456: { content: [ 'foo' ] }, - }, - order: { - '': rootOrder, - 123: rootBlockOrder, - 456: childBlockOrder, - }, + value: [ + { + clientId: 456, + name: 'core/quote', + attributes: {}, + }, + ], }, edits: {}, }, }, initialEdits: {}, + currentPost: {}, }; - expect( - getBlockDependantsCacheBust( state, 123 ) - ).not.toBe( getBlockDependantsCacheBust( nextState, 123 ) ); + expect( getSuggestedPostFormat( state ) ).toBe( 'quote' ); } ); - it( 'returns a new reference on updated grandchild inner block', () => { - const rootBlockOrder = [ 456 ]; - const childBlock = { clientId: 456, name: 'core/paragraph' }; - const childBlockAttributes = {}; - const childBlockOrder = [ 789 ]; - const grandChildBlockOrder = []; - + it( 'returns Video if the first block is of type `core-embed/youtube`', () => { const state = { - currentPost: {}, editor: { present: { blocks: { - byClientId: { - 123: rootBlock, - 456: childBlock, - 789: { clientId: 789, name: 'core/paragraph' }, - }, - attributes: { - 123: rootBlockAttributes, - 456: childBlockAttributes, - 789: {}, - }, - order: { - '': rootOrder, - 123: rootBlockOrder, - 456: childBlockOrder, - 789: grandChildBlockOrder, - }, + value: [ + { + clientId: 567, + name: 'core-embed/youtube', + attributes: {}, + }, + ], }, edits: {}, }, }, initialEdits: {}, + currentPost: {}, }; - const nextState = { - currentPost: {}, + expect( getSuggestedPostFormat( state ) ).toBe( 'video' ); + } ); + + it( 'returns Quote if the first block is of type `core/quote` and second is of type `core/paragraph`', () => { + const state = { editor: { present: { blocks: { - byClientId: { - 123: rootBlock, - 456: childBlock, - 789: { clientId: 789, name: 'core/paragraph' }, - }, - attributes: { - 123: rootBlockAttributes, - 456: childBlockAttributes, - 789: { content: [ 'foo' ] }, - }, - order: { - '': rootOrder, - 123: rootBlockOrder, - 456: childBlockOrder, - 789: grandChildBlockOrder, - }, + value: [ + { + clientId: 456, + name: 'core/quote', + attributes: {}, + }, + { + clientId: 789, + name: 'core/paragraph', + attributes: {}, + }, + ], }, edits: {}, }, }, initialEdits: {}, + currentPost: {}, }; - expect( - getBlockDependantsCacheBust( state, 123 ) - ).not.toBe( getBlockDependantsCacheBust( nextState, 123 ) ); + expect( getSuggestedPostFormat( state ) ).toBe( 'quote' ); } ); } ); - describe( 'getBlockName', () => { - it( 'returns null if no block by clientId', () => { - const state = { - currentPost: {}, - editor: { - present: { - blocks: { - byClientId: {}, - attributes: {}, - order: {}, - }, - edits: {}, - }, - }, - initialEdits: {}, - }; - - const name = getBlockName( state, 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1' ); - - expect( name ).toBe( null ); - } ); - - it( 'returns block name', () => { - const state = { - currentPost: {}, - editor: { - present: { - blocks: { - byClientId: { - 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': { - clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1', - name: 'core/paragraph', - }, - }, - attributes: { - 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': {}, - }, - order: { - '': [ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1' ], - 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': [], - }, - }, - edits: {}, - }, - }, - initialEdits: {}, - }; - - const name = getBlockName( state, 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1' ); - - expect( name ).toBe( 'core/paragraph' ); - } ); - } ); - - describe( 'getBlock', () => { - it( 'should return the block', () => { - const state = { - currentPost: {}, - editor: { - present: { - blocks: { - byClientId: { - 123: { clientId: 123, name: 'core/paragraph' }, - }, - attributes: { - 123: {}, - }, - order: { - '': [ 123 ], - 123: [], - }, - }, - edits: {}, - }, - }, - initialEdits: {}, - }; - - expect( getBlock( state, 123 ) ).toEqual( { - clientId: 123, - name: 'core/paragraph', - attributes: {}, - innerBlocks: [], - } ); - } ); - - it( 'should return null if the block is not present in state', () => { - const state = { - currentPost: {}, - editor: { - present: { - blocks: { - byClientId: {}, - attributes: {}, - order: {}, - }, - edits: {}, - }, - }, - initialEdits: {}, - }; - - expect( getBlock( state, 123 ) ).toBe( null ); - } ); - - it( 'should include inner blocks', () => { - const state = { - currentPost: {}, - editor: { - present: { - blocks: { - byClientId: { - 123: { clientId: 123, name: 'core/paragraph' }, - 456: { clientId: 456, name: 'core/paragraph' }, - }, - attributes: { - 123: {}, - 456: {}, - }, - order: { - '': [ 123 ], - 123: [ 456 ], - 456: [], - }, - }, - edits: {}, - }, - }, - initialEdits: {}, - }; - - expect( getBlock( state, 123 ) ).toEqual( { - clientId: 123, - name: 'core/paragraph', - attributes: {}, - innerBlocks: [ { - clientId: 456, - name: 'core/paragraph', - attributes: {}, - innerBlocks: [], - } ], - } ); - } ); - - it( 'should merge meta attributes for the block', () => { - registerBlockType( 'core/meta-block', { - save: ( props ) => props.attributes.text, - category: 'common', - title: 'test block', - attributes: { - foo: { - type: 'string', - source: 'meta', - meta: 'foo', - }, - }, - } ); - - const state = { - currentPost: { - meta: { - foo: 'bar', - }, - }, - editor: { - present: { - blocks: { - byClientId: { - 123: { clientId: 123, name: 'core/meta-block' }, - }, - attributes: { - 123: {}, - }, - order: { - '': [ 123 ], - 123: [], - }, - }, - edits: {}, - }, - }, - initialEdits: {}, - }; - - expect( getBlock( state, 123 ) ).toEqual( { - clientId: 123, - name: 'core/meta-block', - attributes: { - foo: 'bar', - }, - innerBlocks: [], - } ); - - unregisterBlockType( 'core/meta-block' ); - } ); - } ); - - describe( 'getBlocks', () => { - it( 'should return the ordered blocks', () => { - const state = { - currentPost: {}, - editor: { - present: { - blocks: { - byClientId: { - 23: { clientId: 23, name: 'core/heading' }, - 123: { clientId: 123, name: 'core/paragraph' }, - }, - attributes: { - 23: {}, - 123: {}, - }, - order: { - '': [ 123, 23 ], - }, - }, - edits: {}, - }, - }, - initialEdits: {}, - }; - - expect( getBlocks( state ) ).toEqual( [ - { clientId: 123, name: 'core/paragraph', attributes: {}, innerBlocks: [] }, - { clientId: 23, name: 'core/heading', attributes: {}, innerBlocks: [] }, - ] ); - } ); - } ); - - describe( 'getClientIdsOfDescendants', () => { - it( 'should return the ids of any descendants, given an array of clientIds', () => { - const state = { - currentPost: {}, - editor: { - present: { - blocks: { - byClientId: { - 'uuid-2': { clientId: 'uuid-2', name: 'core/image' }, - 'uuid-4': { clientId: 'uuid-4', name: 'core/paragraph' }, - 'uuid-6': { clientId: 'uuid-6', name: 'core/paragraph' }, - 'uuid-8': { clientId: 'uuid-8', name: 'core/block' }, - 'uuid-10': { clientId: 'uuid-10', name: 'core/columns' }, - 'uuid-12': { clientId: 'uuid-12', name: 'core/column' }, - 'uuid-14': { clientId: 'uuid-14', name: 'core/column' }, - 'uuid-16': { clientId: 'uuid-16', name: 'core/quote' }, - 'uuid-18': { clientId: 'uuid-18', name: 'core/block' }, - 'uuid-20': { clientId: 'uuid-20', name: 'core/gallery' }, - 'uuid-22': { clientId: 'uuid-22', name: 'core/block' }, - 'uuid-24': { clientId: 'uuid-24', name: 'core/columns' }, - 'uuid-26': { clientId: 'uuid-26', name: 'core/column' }, - 'uuid-28': { clientId: 'uuid-28', name: 'core/column' }, - 'uuid-30': { clientId: 'uuid-30', name: 'core/paragraph' }, - }, - attributes: { - 'uuid-2': {}, - 'uuid-4': {}, - 'uuid-6': {}, - 'uuid-8': {}, - 'uuid-10': {}, - 'uuid-12': {}, - 'uuid-14': {}, - 'uuid-16': {}, - 'uuid-18': {}, - 'uuid-20': {}, - 'uuid-22': {}, - 'uuid-24': {}, - 'uuid-26': {}, - 'uuid-28': {}, - 'uuid-30': {}, - }, - order: { - '': [ 'uuid-6', 'uuid-8', 'uuid-10', 'uuid-22' ], - 'uuid-2': [ ], - 'uuid-4': [ ], - 'uuid-6': [ ], - 'uuid-8': [ ], - 'uuid-10': [ 'uuid-12', 'uuid-14' ], - 'uuid-12': [ 'uuid-16' ], - 'uuid-14': [ 'uuid-18' ], - 'uuid-16': [ ], - 'uuid-18': [ 'uuid-24' ], - 'uuid-20': [ ], - 'uuid-22': [ ], - 'uuid-24': [ 'uuid-26', 'uuid-28' ], - 'uuid-26': [ ], - 'uuid-28': [ 'uuid-30' ], - }, - }, - edits: {}, - }, - }, - initialEdits: {}, - }; - expect( getClientIdsOfDescendants( state, [ 'uuid-10' ] ) ).toEqual( [ - 'uuid-12', - 'uuid-14', - 'uuid-16', - 'uuid-18', - 'uuid-24', - 'uuid-26', - 'uuid-28', - 'uuid-30', - ] ); - } ); - } ); - - describe( 'getClientIdsWithDescendants', () => { - it( 'should return the ids for top-level blocks and their descendants of any depth (for nested blocks).', () => { - const state = { - currentPost: {}, - editor: { - present: { - blocks: { - byClientId: { - 'uuid-2': { clientId: 'uuid-2', name: 'core/image' }, - 'uuid-4': { clientId: 'uuid-4', name: 'core/paragraph' }, - 'uuid-6': { clientId: 'uuid-6', name: 'core/paragraph' }, - 'uuid-8': { clientId: 'uuid-8', name: 'core/block' }, - 'uuid-10': { clientId: 'uuid-10', name: 'core/columns' }, - 'uuid-12': { clientId: 'uuid-12', name: 'core/column' }, - 'uuid-14': { clientId: 'uuid-14', name: 'core/column' }, - 'uuid-16': { clientId: 'uuid-16', name: 'core/quote' }, - 'uuid-18': { clientId: 'uuid-18', name: 'core/block' }, - 'uuid-20': { clientId: 'uuid-20', name: 'core/gallery' }, - 'uuid-22': { clientId: 'uuid-22', name: 'core/block' }, - 'uuid-24': { clientId: 'uuid-24', name: 'core/columns' }, - 'uuid-26': { clientId: 'uuid-26', name: 'core/column' }, - 'uuid-28': { clientId: 'uuid-28', name: 'core/column' }, - 'uuid-30': { clientId: 'uuid-30', name: 'core/paragraph' }, - }, - attributes: { - 'uuid-2': {}, - 'uuid-4': {}, - 'uuid-6': {}, - 'uuid-8': {}, - 'uuid-10': {}, - 'uuid-12': {}, - 'uuid-14': {}, - 'uuid-16': {}, - 'uuid-18': {}, - 'uuid-20': {}, - 'uuid-22': {}, - 'uuid-24': {}, - 'uuid-26': {}, - 'uuid-28': {}, - 'uuid-30': {}, - }, - order: { - '': [ 'uuid-6', 'uuid-8', 'uuid-10', 'uuid-22' ], - 'uuid-2': [ ], - 'uuid-4': [ ], - 'uuid-6': [ ], - 'uuid-8': [ ], - 'uuid-10': [ 'uuid-12', 'uuid-14' ], - 'uuid-12': [ 'uuid-16' ], - 'uuid-14': [ 'uuid-18' ], - 'uuid-16': [ ], - 'uuid-18': [ 'uuid-24' ], - 'uuid-20': [ ], - 'uuid-22': [ ], - 'uuid-24': [ 'uuid-26', 'uuid-28' ], - 'uuid-26': [ ], - 'uuid-28': [ 'uuid-30' ], - }, - }, - edits: {}, - }, - }, - initialEdits: {}, - }; - expect( getClientIdsWithDescendants( state ) ).toEqual( [ - 'uuid-6', - 'uuid-8', - 'uuid-10', - 'uuid-22', - 'uuid-12', - 'uuid-14', - 'uuid-16', - 'uuid-18', - 'uuid-24', - 'uuid-26', - 'uuid-28', - 'uuid-30', - ] ); - } ); - } ); - - describe( 'getBlockCount', () => { - it( 'should return the number of top-level blocks in the post', () => { - const state = { - editor: { - present: { - blocks: { - byClientId: { - 23: { clientId: 23, name: 'core/heading' }, - 123: { clientId: 123, name: 'core/paragraph' }, - }, - attributes: { - 23: {}, - 123: {}, - }, - order: { - '': [ 123, 23 ], - }, - }, - }, - }, - }; - - expect( getBlockCount( state ) ).toBe( 2 ); - } ); - - it( 'should return the number of blocks in a nested context', () => { - const state = { - editor: { - present: { - blocks: { - byClientId: { - 123: { clientId: 123, name: 'core/columns' }, - 456: { clientId: 456, name: 'core/paragraph' }, - 789: { clientId: 789, name: 'core/paragraph' }, - }, - attributes: { - 123: {}, - 456: {}, - 789: {}, - }, - order: { - '': [ 123 ], - 123: [ 456, 789 ], - }, - }, - }, - }, - }; - - expect( getBlockCount( state, '123' ) ).toBe( 2 ); - } ); - } ); - - describe( 'hasSelectedBlock', () => { - it( 'should return false if no selection', () => { - const state = { - blockSelection: { - start: null, - end: null, - }, - }; - - expect( hasSelectedBlock( state ) ).toBe( false ); - } ); - - it( 'should return false if multi-selection', () => { - const state = { - blockSelection: { - start: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1', - end: '9db792c6-a25a-495d-adbd-97d56a4c4189', - }, - }; - - expect( hasSelectedBlock( state ) ).toBe( false ); - } ); - - it( 'should return true if singular selection', () => { - const state = { - blockSelection: { - start: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1', - end: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1', - }, - }; - - expect( hasSelectedBlock( state ) ).toBe( true ); - } ); - } ); - - describe( 'getGlobalBlockCount', () => { - const state = { - editor: { - present: { - blocks: { - byClientId: { - 123: { clientId: 123, name: 'core/heading' }, - 456: { clientId: 456, name: 'core/paragraph' }, - 789: { clientId: 789, name: 'core/paragraph' }, - }, - attributes: { - 123: {}, - 456: {}, - 789: {}, - }, - order: { - '': [ 123, 456 ], - }, - }, - }, - }, - }; - - it( 'should return the global number of blocks in the post', () => { - expect( getGlobalBlockCount( state ) ).toBe( 2 ); - } ); - - it( 'should return the global number of blocks in the post of a given type', () => { - expect( getGlobalBlockCount( state, 'core/paragraph' ) ).toBe( 1 ); - } ); - - it( 'should return 0 if no blocks exist', () => { - const emptyState = { - editor: { - present: { - blocks: { - byClientId: {}, - attributes: {}, - order: {}, - }, - }, - }, - }; - expect( getGlobalBlockCount( emptyState ) ).toBe( 0 ); - expect( getGlobalBlockCount( emptyState, 'core/heading' ) ).toBe( 0 ); - } ); - } ); - - describe( 'getSelectedBlockClientId', () => { - it( 'should return null if no block is selected', () => { - const state = { - blockSelection: { start: null, end: null }, - }; - - expect( getSelectedBlockClientId( state ) ).toBe( null ); - } ); - - it( 'should return null if there is multi selection', () => { - const state = { - blockSelection: { start: 23, end: 123 }, - }; - - expect( getSelectedBlockClientId( state ) ).toBe( null ); - } ); - - it( 'should return the selected block ClientId', () => { - const state = { - editor: { present: { blocks: { byClientId: { 23: { name: 'fake block' } } } } }, - blockSelection: { start: 23, end: 23 }, - }; - - expect( getSelectedBlockClientId( state ) ).toEqual( 23 ); - } ); - } ); - - describe( 'getSelectedBlock', () => { - it( 'should return null if no block is selected', () => { - const state = { - currentPost: {}, - editor: { - present: { - blocks: { - byClientId: { - 23: { clientId: 23, name: 'core/heading' }, - 123: { clientId: 123, name: 'core/paragraph' }, - }, - attributes: { - 23: {}, - 123: {}, - }, - order: { - '': [ 23, 123 ], - 23: [], - 123: [], - }, - }, - edits: {}, - }, - }, - initialEdits: {}, - blockSelection: { start: null, end: null }, - }; - - expect( getSelectedBlock( state ) ).toBe( null ); - } ); - - it( 'should return null if there is multi selection', () => { - const state = { - currentPost: {}, - editor: { - present: { - blocks: { - byClientId: { - 23: { clientId: 23, name: 'core/heading' }, - 123: { clientId: 123, name: 'core/paragraph' }, - }, - attributes: { - 23: {}, - 123: {}, - }, - order: { - '': [ 23, 123 ], - 23: [], - 123: [], - }, - }, - edits: {}, - }, - }, - initialEdits: {}, - blockSelection: { start: 23, end: 123 }, - }; - - expect( getSelectedBlock( state ) ).toBe( null ); - } ); - - it( 'should return the selected block', () => { - const state = { - currentPost: {}, - editor: { - present: { - blocks: { - byClientId: { - 23: { clientId: 23, name: 'core/heading' }, - 123: { clientId: 123, name: 'core/paragraph' }, - }, - attributes: { - 23: {}, - 123: {}, - }, - order: { - '': [ 23, 123 ], - 23: [], - 123: [], - }, - }, - edits: {}, - }, - }, - initialEdits: {}, - blockSelection: { start: 23, end: 23 }, - }; - - expect( getSelectedBlock( state ) ).toEqual( { - clientId: 23, - name: 'core/heading', - attributes: {}, - innerBlocks: [], - } ); - } ); - } ); - - describe( 'getBlockRootClientId', () => { - it( 'should return null if the block does not exist', () => { - const state = { - editor: { - present: { - blocks: { - order: {}, - }, - }, - }, - }; - - expect( getBlockRootClientId( state, 56 ) ).toBeNull(); - } ); - - it( 'should return root ClientId relative the block ClientId', () => { - const state = { - editor: { - present: { - blocks: { - order: { - '': [ 123, 23 ], - 123: [ 456, 56 ], - }, - }, - }, - }, - }; - - expect( getBlockRootClientId( state, 56 ) ).toBe( '123' ); - } ); - } ); - - describe( 'getBlockHierarchyRootClientId', () => { - it( 'should return the given block if the block has no parents', () => { - const state = { - editor: { - present: { - blocks: { - order: {}, - }, - }, - }, - }; - - expect( getBlockHierarchyRootClientId( state, 56 ) ).toBe( 56 ); - } ); - - it( 'should return root ClientId relative the block ClientId', () => { - const state = { - editor: { - present: { - blocks: { - order: { - '': [ 123, 23 ], - 123: [ 456, 56 ], - }, - }, - }, - }, - }; - - expect( getBlockHierarchyRootClientId( state, 56 ) ).toBe( '123' ); - } ); - - it( 'should return the top level root ClientId relative the block ClientId', () => { - const state = { - editor: { - present: { - blocks: { - order: { - '': [ '123', '23' ], - 123: [ '456', '56' ], - 56: [ '12' ], - }, - }, - }, - }, - }; - - expect( getBlockHierarchyRootClientId( state, '12' ) ).toBe( '123' ); - } ); - } ); - - describe( 'getMultiSelectedBlockClientIds', () => { - it( 'should return empty if there is no multi selection', () => { - const state = { - editor: { - present: { - blocks: { - order: { - '': [ 123, 23 ], - }, - }, - }, - }, - blockSelection: { start: null, end: null }, - }; - - expect( getMultiSelectedBlockClientIds( state ) ).toEqual( [] ); - } ); - - it( 'should return selected block clientIds if there is multi selection', () => { - const state = { - editor: { - present: { - blocks: { - order: { - '': [ 5, 4, 3, 2, 1 ], - }, - }, - }, - }, - blockSelection: { start: 2, end: 4 }, - }; - - expect( getMultiSelectedBlockClientIds( state ) ).toEqual( [ 4, 3, 2 ] ); - } ); - - it( 'should return selected block clientIds if there is multi selection (nested context)', () => { - const state = { - editor: { - present: { - blocks: { - order: { - '': [ 5, 4, 3, 2, 1 ], - 4: [ 9, 8, 7, 6 ], - }, - }, - }, - }, - blockSelection: { start: 7, end: 9 }, - }; - - expect( getMultiSelectedBlockClientIds( state ) ).toEqual( [ 9, 8, 7 ] ); - } ); - } ); - - describe( 'getMultiSelectedBlocks', () => { - it( 'should return the same reference on subsequent invocations of empty selection', () => { - const state = { - editor: { - present: { - blocks: { - byClientId: {}, - attributes: {}, - order: {}, - }, - edits: {}, - }, - }, - initialEdits: {}, - blockSelection: { start: null, end: null }, - currentPost: {}, - }; - - expect( - getMultiSelectedBlocks( state ) - ).toBe( getMultiSelectedBlocks( state ) ); - } ); - } ); - - describe( 'getMultiSelectedBlocksStartClientId', () => { - it( 'returns null if there is no multi selection', () => { - const state = { - blockSelection: { start: null, end: null }, - }; - - expect( getMultiSelectedBlocksStartClientId( state ) ).toBeNull(); - } ); - - it( 'returns multi selection start', () => { - const state = { - blockSelection: { start: 2, end: 4 }, - }; - - expect( getMultiSelectedBlocksStartClientId( state ) ).toBe( 2 ); - } ); - } ); - - describe( 'getMultiSelectedBlocksEndClientId', () => { - it( 'returns null if there is no multi selection', () => { - const state = { - blockSelection: { start: null, end: null }, - }; - - expect( getMultiSelectedBlocksEndClientId( state ) ).toBeNull(); - } ); - - it( 'returns multi selection end', () => { - const state = { - blockSelection: { start: 2, end: 4 }, - }; - - expect( getMultiSelectedBlocksEndClientId( state ) ).toBe( 4 ); - } ); - } ); - - describe( 'getBlockOrder', () => { - it( 'should return the ordered block ClientIds of top-level blocks by default', () => { - const state = { - editor: { - present: { - blocks: { - order: { - '': [ 123, 23 ], - }, - }, - }, - }, - }; - - expect( getBlockOrder( state ) ).toEqual( [ 123, 23 ] ); - } ); - - it( 'should return the ordered block ClientIds at a specified rootClientId', () => { - const state = { - editor: { - present: { - blocks: { - order: { - '': [ 123, 23 ], - 123: [ 456 ], - }, - }, - }, - }, - }; - - expect( getBlockOrder( state, '123' ) ).toEqual( [ 456 ] ); - } ); - } ); - - describe( 'getBlockIndex', () => { - it( 'should return the block order', () => { - const state = { - editor: { - present: { - blocks: { - order: { - '': [ 123, 23 ], - }, - }, - }, - }, - }; - - expect( getBlockIndex( state, 23 ) ).toBe( 1 ); - } ); - - it( 'should return the block order (nested context)', () => { - const state = { - editor: { - present: { - blocks: { - order: { - '': [ 123, 23 ], - 123: [ 456, 56 ], - }, - }, - }, - }, - }; - - expect( getBlockIndex( state, 56, '123' ) ).toBe( 1 ); - } ); - } ); - - describe( 'getPreviousBlockClientId', () => { - it( 'should return the previous block', () => { - const state = { - editor: { - present: { - blocks: { - order: { - '': [ 123, 23 ], - }, - }, - }, - }, - }; - - expect( getPreviousBlockClientId( state, 23 ) ).toEqual( 123 ); - } ); - - it( 'should return the previous block (nested context)', () => { - const state = { - editor: { - present: { - blocks: { - order: { - '': [ 123, 23 ], - 123: [ 456, 56 ], - }, - }, - }, - }, - }; - - expect( getPreviousBlockClientId( state, 56, '123' ) ).toEqual( 456 ); - } ); - - it( 'should return null for the first block', () => { - const state = { - editor: { - present: { - blocks: { - order: { - '': [ 123, 23 ], - }, - }, - }, - }, - }; - - expect( getPreviousBlockClientId( state, 123 ) ).toBeNull(); - } ); - - it( 'should return null for the first block (nested context)', () => { - const state = { - editor: { - present: { - blocks: { - order: { - '': [ 123, 23 ], - 123: [ 456, 56 ], - }, - }, - }, - }, - }; - - expect( getPreviousBlockClientId( state, 456, '123' ) ).toBeNull(); - } ); - } ); - - describe( 'getNextBlockClientId', () => { - it( 'should return the following block', () => { - const state = { - editor: { - present: { - blocks: { - order: { - '': [ 123, 23 ], - }, - }, - }, - }, - }; - - expect( getNextBlockClientId( state, 123 ) ).toEqual( 23 ); - } ); - - it( 'should return the following block (nested context)', () => { - const state = { - editor: { - present: { - blocks: { - order: { - '': [ 123, 23 ], - 123: [ 456, 56 ], - }, - }, - }, - }, - }; - - expect( getNextBlockClientId( state, 456, '123' ) ).toEqual( 56 ); - } ); - - it( 'should return null for the last block', () => { - const state = { - editor: { - present: { - blocks: { - order: { - '': [ 123, 23 ], - }, - }, - }, - }, - }; - - expect( getNextBlockClientId( state, 23 ) ).toBeNull(); - } ); - - it( 'should return null for the last block (nested context)', () => { - const state = { - editor: { - present: { - blocks: { - order: { - '': [ 123, 23 ], - 123: [ 456, 56 ], - }, - }, - }, - }, - }; - - expect( getNextBlockClientId( state, 56, '123' ) ).toBeNull(); - } ); - } ); - - describe( 'isBlockSelected', () => { - it( 'should return true if the block is selected', () => { - const state = { - blockSelection: { start: 123, end: 123 }, - }; - - expect( isBlockSelected( state, 123 ) ).toBe( true ); - } ); - - it( 'should return false if a multi-selection range exists', () => { - const state = { - blockSelection: { start: 123, end: 124 }, - }; - - expect( isBlockSelected( state, 123 ) ).toBe( false ); - } ); - - it( 'should return false if the block is not selected', () => { - const state = { - blockSelection: { start: null, end: null }, - }; - - expect( isBlockSelected( state, 23 ) ).toBe( false ); - } ); - } ); - - describe( 'hasSelectedInnerBlock', () => { - it( 'should return false if the selected block is a child of the given ClientId', () => { - const state = { - blockSelection: { start: 5, end: 5 }, - editor: { - present: { - blocks: { - order: { - 4: [ 3, 2, 1 ], - }, - }, - }, - }, - }; - - expect( hasSelectedInnerBlock( state, 4 ) ).toBe( false ); - } ); - - it( 'should return true if the selected block is a child of the given ClientId', () => { - const state = { - blockSelection: { start: 3, end: 3 }, - editor: { - present: { - blocks: { - order: { - 4: [ 3, 2, 1 ], - }, - }, - }, - }, - }; - - expect( hasSelectedInnerBlock( state, 4 ) ).toBe( true ); - } ); - - it( 'should return true if a multi selection exists that contains children of the block with the given ClientId', () => { - const state = { - editor: { - present: { - blocks: { - order: { - 6: [ 5, 4, 3, 2, 1 ], - }, - }, - }, - }, - blockSelection: { start: 2, end: 4 }, - }; - expect( hasSelectedInnerBlock( state, 6 ) ).toBe( true ); - } ); - - it( 'should return false if a multi selection exists bot does not contains children of the block with the given ClientId', () => { - const state = { - editor: { - present: { - blocks: { - order: { - 3: [ 2, 1 ], - 6: [ 5, 4 ], - }, - }, - }, - }, - blockSelection: { start: 5, end: 4 }, - }; - expect( hasSelectedInnerBlock( state, 3 ) ).toBe( false ); - } ); - } ); - - describe( 'isBlockWithinSelection', () => { - it( 'should return true if the block is selected but not the last', () => { - const state = { - blockSelection: { start: 5, end: 3 }, - editor: { - present: { - blocks: { - order: { - '': [ 5, 4, 3, 2, 1 ], - }, - }, - }, - }, - }; - - expect( isBlockWithinSelection( state, 4 ) ).toBe( true ); - } ); - - it( 'should return false if the block is the last selected', () => { - const state = { - blockSelection: { start: 5, end: 3 }, - editor: { - present: { - blocks: { - order: { - '': [ 5, 4, 3, 2, 1 ], - }, - }, - }, - }, - }; - - expect( isBlockWithinSelection( state, 3 ) ).toBe( false ); - } ); - - it( 'should return false if the block is not selected', () => { - const state = { - blockSelection: { start: 5, end: 3 }, - editor: { - present: { - blocks: { - order: { - '': [ 5, 4, 3, 2, 1 ], - }, - }, - }, - }, - }; - - expect( isBlockWithinSelection( state, 2 ) ).toBe( false ); - } ); - - it( 'should return false if there is no selection', () => { - const state = { - blockSelection: {}, - editor: { - present: { - blocks: { - order: { - '': [ 5, 4, 3, 2, 1 ], - }, - }, - }, - }, - }; - - expect( isBlockWithinSelection( state, 4 ) ).toBe( false ); - } ); - } ); - - describe( 'hasMultiSelection', () => { - it( 'should return false if no selection', () => { - const state = { - blockSelection: { - start: null, - end: null, - }, - }; - - expect( hasMultiSelection( state ) ).toBe( false ); - } ); - - it( 'should return false if singular selection', () => { - const state = { - blockSelection: { - start: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1', - end: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1', - }, - }; - - expect( hasMultiSelection( state ) ).toBe( false ); - } ); - - it( 'should return true if multi-selection', () => { - const state = { - blockSelection: { - start: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1', - end: '9db792c6-a25a-495d-adbd-97d56a4c4189', - }, - }; - - expect( hasMultiSelection( state ) ).toBe( true ); - } ); - } ); - - describe( 'isBlockMultiSelected', () => { - const state = { - editor: { - present: { - blocks: { - order: { - '': [ 5, 4, 3, 2, 1 ], - }, - }, - }, - }, - blockSelection: { start: 2, end: 4 }, - }; - - it( 'should return true if the block is multi selected', () => { - expect( isBlockMultiSelected( state, 3 ) ).toBe( true ); - } ); - - it( 'should return false if the block is not multi selected', () => { - expect( isBlockMultiSelected( state, 5 ) ).toBe( false ); - } ); - } ); - - describe( 'isFirstMultiSelectedBlock', () => { - const state = { - editor: { - present: { - blocks: { - order: { - '': [ 5, 4, 3, 2, 1 ], - }, - }, - }, - }, - blockSelection: { start: 2, end: 4 }, - }; - - it( 'should return true if the block is first in multi selection', () => { - expect( isFirstMultiSelectedBlock( state, 4 ) ).toBe( true ); - } ); - - it( 'should return false if the block is not first in multi selection', () => { - expect( isFirstMultiSelectedBlock( state, 3 ) ).toBe( false ); - } ); - } ); - - describe( 'getBlockMode', () => { - it( 'should return "visual" if unset', () => { - const state = { - blocksMode: {}, - }; - - expect( getBlockMode( state, 123 ) ).toEqual( 'visual' ); - } ); - - it( 'should return the block mode', () => { - const state = { - blocksMode: { - 123: 'html', - }, - }; - - expect( getBlockMode( state, 123 ) ).toEqual( 'html' ); - } ); - } ); - - describe( 'isTyping', () => { - it( 'should return the isTyping flag if the block is selected', () => { - const state = { - isTyping: true, - }; - - expect( isTyping( state ) ).toBe( true ); - } ); - - it( 'should return false if the block is not selected', () => { - const state = { - isTyping: false, - }; - - expect( isTyping( state ) ).toBe( false ); - } ); - } ); - - describe( 'isCaretWithinFormattedText', () => { - it( 'returns true if the isCaretWithinFormattedText state is also true', () => { - const state = { - isCaretWithinFormattedText: true, - }; - - expect( isCaretWithinFormattedText( state ) ).toBe( true ); - } ); - - it( 'returns false if the isCaretWithinFormattedText state is also false', () => { - const state = { - isCaretWithinFormattedText: false, - }; - - expect( isCaretWithinFormattedText( state ) ).toBe( false ); - } ); - } ); - - describe( 'isSelectionEnabled', () => { - it( 'should return true if selection is enable', () => { - const state = { - blockSelection: { - isEnabled: true, - }, - }; - - expect( isSelectionEnabled( state ) ).toBe( true ); - } ); - - it( 'should return false if selection is disabled', () => { - const state = { - blockSelection: { - isEnabled: false, - }, - }; - - expect( isSelectionEnabled( state ) ).toBe( false ); - } ); - } ); - - describe( 'getBlockInsertionPoint', () => { - it( 'should return the explicitly assigned insertion point', () => { - const state = { - currentPost: {}, - preferences: { mode: 'visual' }, - blockSelection: { - start: 'clientId2', - end: 'clientId2', - }, - editor: { - present: { - blocks: { - byClientId: { - clientId1: { clientId: 'clientId1' }, - clientId2: { clientId: 'clientId2' }, - }, - attributes: { - clientId1: {}, - clientId2: {}, - }, - order: { - '': [ 'clientId1' ], - clientId1: [ 'clientId2' ], - clientId2: [], - }, - }, - edits: {}, - }, - }, - initialEdits: {}, - insertionPoint: { - rootClientId: undefined, - index: 0, - }, - }; - - expect( getBlockInsertionPoint( state ) ).toEqual( { - rootClientId: undefined, - index: 0, - } ); - } ); - - it( 'should return an object for the selected block', () => { - const state = { - currentPost: {}, - preferences: { mode: 'visual' }, - blockSelection: { - start: 'clientId1', - end: 'clientId1', - }, - editor: { - present: { - blocks: { - byClientId: { - clientId1: { clientId: 'clientId1' }, - }, - attributes: { - clientId1: {}, - }, - order: { - '': [ 'clientId1' ], - clientId1: [], - }, - }, - edits: {}, - }, - }, - initialEdits: {}, - insertionPoint: null, - }; - - expect( getBlockInsertionPoint( state ) ).toEqual( { - rootClientId: undefined, - index: 1, - } ); - } ); - - it( 'should return an object for the nested selected block', () => { - const state = { - currentPost: {}, - preferences: { mode: 'visual' }, - blockSelection: { - start: 'clientId2', - end: 'clientId2', - }, - editor: { - present: { - blocks: { - byClientId: { - clientId1: { clientId: 'clientId1' }, - clientId2: { clientId: 'clientId2' }, - }, - attributes: { - clientId1: {}, - clientId2: {}, - }, - order: { - '': [ 'clientId1' ], - clientId1: [ 'clientId2' ], - clientId2: [], - }, - }, - edits: {}, - }, - }, - initialEdits: {}, - insertionPoint: null, - }; - - expect( getBlockInsertionPoint( state ) ).toEqual( { - rootClientId: 'clientId1', - index: 1, - } ); - } ); - - it( 'should return an object for the last multi selected clientId', () => { - const state = { - currentPost: {}, - preferences: { mode: 'visual' }, - blockSelection: { - start: 'clientId1', - end: 'clientId2', - }, - editor: { - present: { - blocks: { - byClientId: { - clientId1: { clientId: 'clientId1' }, - clientId2: { clientId: 'clientId2' }, - }, - attributes: { - clientId1: {}, - clientId2: {}, - }, - order: { - '': [ 'clientId1', 'clientId2' ], - clientId1: [], - clientId2: [], - }, - }, - edits: {}, - }, - }, - initialEdits: {}, - insertionPoint: null, - }; - - expect( getBlockInsertionPoint( state ) ).toEqual( { - rootClientId: undefined, - index: 2, - } ); - } ); - - it( 'should return an object for the last block if no selection', () => { - const state = { - currentPost: {}, - preferences: { mode: 'visual' }, - blockSelection: { - start: null, - end: null, - }, - editor: { - present: { - blocks: { - byClientId: { - clientId1: { clientId: 'clientId1' }, - clientId2: { clientId: 'clientId2' }, - }, - attributes: { - clientId1: {}, - clientId2: {}, - }, - order: { - '': [ 'clientId1', 'clientId2' ], - clientId1: [], - clientId2: [], - }, - }, - edits: {}, - }, - }, - initialEdits: {}, - insertionPoint: null, - }; - - expect( getBlockInsertionPoint( state ) ).toEqual( { - rootClientId: undefined, - index: 2, - } ); - } ); - } ); - - describe( 'isBlockInsertionPointVisible', () => { - it( 'should return false if no assigned insertion point', () => { - const state = { - insertionPoint: null, - }; - - expect( isBlockInsertionPointVisible( state ) ).toBe( false ); - } ); - - it( 'should return true if assigned insertion point', () => { - const state = { - insertionPoint: { - rootClientId: undefined, - index: 5, - }, - }; - - expect( isBlockInsertionPointVisible( state ) ).toBe( true ); - } ); - } ); - - describe( 'isSavingPost', () => { - it( 'should return true if the post is currently being saved', () => { - const state = { - saving: { - requesting: true, - }, - }; - - expect( isSavingPost( state ) ).toBe( true ); - } ); - - it( 'should return false if the post is not currently being saved', () => { - const state = { - saving: { - requesting: false, - }, - }; - - expect( isSavingPost( state ) ).toBe( false ); - } ); - } ); - - describe( 'didPostSaveRequestSucceed', () => { - it( 'should return true if the post save request is successful', () => { - const state = { - saving: { - successful: true, - }, - }; - - expect( didPostSaveRequestSucceed( state ) ).toBe( true ); - } ); - - it( 'should return true if the post save request has failed', () => { - const state = { - saving: { - successful: false, - }, - }; - - expect( didPostSaveRequestSucceed( state ) ).toBe( false ); - } ); - } ); - - describe( 'didPostSaveRequestFail', () => { - it( 'should return true if the post save request has failed', () => { - const state = { - saving: { - error: 'error', - }, - }; - - expect( didPostSaveRequestFail( state ) ).toBe( true ); - } ); - - it( 'should return true if the post save request is successful', () => { - const state = { - saving: { - error: false, - }, - }; - - expect( didPostSaveRequestFail( state ) ).toBe( false ); - } ); - } ); - - describe( 'getSuggestedPostFormat', () => { - it( 'returns null if cannot be determined', () => { - const state = { - editor: { - present: { - blocks: { - byClientId: {}, - attributes: {}, - order: {}, - }, - edits: {}, - }, - }, - initialEdits: {}, - currentPost: {}, - }; - - expect( getSuggestedPostFormat( state ) ).toBeNull(); - } ); - - it( 'returns null if there is more than one block in the post', () => { - const state = { - editor: { - present: { - blocks: { - byClientId: { - 123: { clientId: 123, name: 'core/image' }, - 456: { clientId: 456, name: 'core/quote' }, - }, - attributes: { - 123: {}, - 456: {}, - }, - order: { - '': [ 123, 456 ], - }, - }, - edits: {}, - }, - }, - initialEdits: {}, - currentPost: {}, - }; - - expect( getSuggestedPostFormat( state ) ).toBeNull(); - } ); - - it( 'returns Image if the first block is of type `core/image`', () => { - const state = { - editor: { - present: { - blocks: { - byClientId: { - 123: { clientId: 123, name: 'core/image' }, - }, - attributes: { - 123: {}, - }, - order: { - '': [ 123 ], - }, - }, - edits: {}, - }, - }, - initialEdits: {}, - currentPost: {}, - }; - - expect( getSuggestedPostFormat( state ) ).toBe( 'image' ); - } ); - - it( 'returns Quote if the first block is of type `core/quote`', () => { - const state = { - editor: { - present: { - blocks: { - byClientId: { - 456: { clientId: 456, name: 'core/quote' }, - }, - attributes: { - 456: {}, - }, - order: { - '': [ 456 ], - }, - }, - edits: {}, - }, - }, - initialEdits: {}, - currentPost: {}, - }; - - expect( getSuggestedPostFormat( state ) ).toBe( 'quote' ); - } ); - - it( 'returns Video if the first block is of type `core-embed/youtube`', () => { - const state = { - editor: { - present: { - blocks: { - byClientId: { - 567: { clientId: 567, name: 'core-embed/youtube' }, - }, - attributes: { - 567: {}, - }, - order: { - '': [ 567 ], - }, - }, - edits: {}, - }, - }, - initialEdits: {}, - currentPost: {}, - }; - - expect( getSuggestedPostFormat( state ) ).toBe( 'video' ); - } ); - - it( 'returns Quote if the first block is of type `core/quote` and second is of type `core/paragraph`', () => { - const state = { - editor: { - present: { - blocks: { - byClientId: { - 456: { clientId: 456, name: 'core/quote' }, - 789: { clientId: 789, name: 'core/paragraph' }, - }, - attributes: { - 456: {}, - 789: {}, - }, - order: { - '': [ 456, 789 ], - }, - }, - edits: {}, - }, - }, - initialEdits: {}, - currentPost: {}, - }; - - expect( getSuggestedPostFormat( state ) ).toBe( 'quote' ); - } ); - } ); - - describe( 'getBlocksForSerialization', () => { - it( 'should return blocks', () => { - const state = { - editor: { - present: { - blocks: { - byClientId: { - block1: { - clientId: 'block1', - name: 'core/test-default', - }, - block2: { - clientId: 'block2', - name: 'core/heading', - }, - }, - attributes: { - block1: { - modified: false, - }, - block2: {}, - }, - order: { - '': [ 'block1', 'block2' ], - }, - }, - edits: {}, - }, - }, - initialEdits: {}, - currentPost: {}, - }; - - expect( getBlocksForSerialization( state ) ).toEqual( [ - { clientId: 'block1', name: 'core/test-default', attributes: { modified: false }, innerBlocks: [] }, - { clientId: 'block2', name: 'core/heading', attributes: {}, innerBlocks: [] }, - ] ); - } ); - - it( 'should return an empty set if content is a single unmodified default block', () => { - const state = { - editor: { - present: { - blocks: { - byClientId: { - block1: { - clientId: 'block1', - name: 'core/test-default', - }, - }, - attributes: { - block1: { - modified: false, - }, - }, - order: { - '': [ 'block1' ], - }, - }, - edits: {}, - }, - }, - initialEdits: {}, - currentPost: {}, - }; - - expect( getBlocksForSerialization( state ) ).toEqual( [] ); - } ); - - it( 'should return a set including a single modified default block', () => { - const state = { - editor: { - present: { - blocks: { - byClientId: { - block1: { - clientId: 'block1', - name: 'core/test-default', - }, - }, - attributes: { - block1: { - modified: true, - }, - }, - order: { - '': [ 'block1' ], - }, - }, - edits: {}, - }, - }, - initialEdits: {}, - currentPost: {}, - }; - - expect( getBlocksForSerialization( state ) ).toEqual( [ - { clientId: 'block1', name: 'core/test-default', attributes: { modified: true }, innerBlocks: [] }, - ] ); - } ); - } ); - - describe( 'getEditedPostContent', () => { - it( 'defers to returning an edited post attribute', () => { - const block = createBlock( 'core/block' ); - - const state = { - editor: { - present: { - blocks: { - byClientId: { - [ block.clientId ]: omit( block, 'attributes' ), - }, - attributes: { - [ block.clientId ]: block.attributes, - }, - order: { - '': [ block.clientId ], - }, - }, - edits: { - content: 'custom edit', - }, - }, - }, - initialEdits: {}, - currentPost: {}, - }; - - const content = getEditedPostContent( state ); - - expect( content ).toBe( 'custom edit' ); - } ); - - it( 'returns serialization of blocks', () => { - const block = createBlock( 'core/block' ); - - const state = { - editor: { - present: { - blocks: { - byClientId: { - [ block.clientId ]: omit( block, 'attributes' ), - }, - attributes: { - [ block.clientId ]: block.attributes, - }, - order: { - '': [ block.clientId ], - }, - }, - edits: {}, - }, - }, - initialEdits: {}, - currentPost: {}, - }; - - const content = getEditedPostContent( state ); - - expect( content ).toBe( '<!-- wp:block /-->' ); - } ); - - it( 'returns removep\'d serialization of blocks for single unknown', () => { - const unknownBlock = createBlock( 'core/test-freeform', { - content: '<p>foo</p>', - } ); - const state = { - editor: { - present: { - blocks: { - byClientId: { - [ unknownBlock.clientId ]: omit( unknownBlock, 'attributes' ), - }, - attributes: { - [ unknownBlock.clientId ]: unknownBlock.attributes, - }, - order: { - '': [ unknownBlock.clientId ], - }, - }, - edits: {}, - }, - }, - initialEdits: {}, - currentPost: {}, - }; - - const content = getEditedPostContent( state ); - - expect( content ).toBe( 'foo' ); - } ); - - it( 'returns non-removep\'d serialization of blocks for multiple unknown', () => { - const firstUnknown = createBlock( 'core/test-freeform', { - content: '<p>foo</p>', - } ); - const secondUnknown = createBlock( 'core/test-freeform', { - content: '<p>bar</p>', - } ); - const state = { - editor: { - present: { - blocks: { - byClientId: { - [ firstUnknown.clientId ]: omit( firstUnknown, 'attributes' ), - [ secondUnknown.clientId ]: omit( secondUnknown, 'attributes' ), - }, - attributes: { - [ firstUnknown.clientId ]: firstUnknown.attributes, - [ secondUnknown.clientId ]: secondUnknown.attributes, - }, - order: { - '': [ firstUnknown.clientId, secondUnknown.clientId ], - }, - }, - edits: {}, - }, - }, - initialEdits: {}, - currentPost: {}, - }; - - const content = getEditedPostContent( state ); - - expect( content ).toBe( '<p>foo</p>\n\n<p>bar</p>' ); - } ); - - it( 'returns empty string for single unmodified default block', () => { - const defaultBlock = createBlock( getDefaultBlockName() ); - const state = { - editor: { - present: { - blocks: { - byClientId: { - [ defaultBlock.clientId ]: omit( defaultBlock, 'attributes' ), - }, - attributes: { - [ defaultBlock.clientId ]: defaultBlock.attributes, - }, - order: { - '': [ defaultBlock.clientId ], - }, - }, - edits: {}, - }, - }, - initialEdits: {}, - currentPost: {}, - }; - - const content = getEditedPostContent( state ); - - expect( content ).toBe( '' ); - } ); - - it( 'should not return empty string for modified default block', () => { - const defaultBlock = createBlock( getDefaultBlockName() ); - const state = { - editor: { - present: { - blocks: { - byClientId: { - [ defaultBlock.clientId ]: { - ...omit( defaultBlock, 'attributes' ), - }, - }, - attributes: { - [ defaultBlock.clientId ]: { - ...defaultBlock.attributes, - modified: true, - }, - }, - order: { - '': [ defaultBlock.clientId ], - }, - }, - edits: {}, - }, - }, - initialEdits: {}, - currentPost: {}, - }; - - const content = getEditedPostContent( state ); - - expect( content ).toBe( '<!-- wp:test-default {\"modified\":true} /-->' ); - } ); - } ); - - describe( 'canInsertBlockType', () => { - it( 'should deny blocks that are not registered', () => { - const state = { - editor: { - present: { - blocks: { - byClientId: {}, - attributes: {}, - }, - }, - }, - blockListSettings: {}, - settings: {}, - }; - expect( canInsertBlockType( state, 'core/invalid' ) ).toBe( false ); - } ); - - it( 'should deny blocks that are not allowed by the editor', () => { - const state = { - editor: { - present: { - blocks: { - byClientId: {}, - attributes: {}, - }, - }, - }, - blockListSettings: {}, - settings: { - allowedBlockTypes: [], - }, - }; - expect( canInsertBlockType( state, 'core/test-block-a' ) ).toBe( false ); - } ); - - it( 'should allow blocks that are allowed by the editor', () => { - const state = { - editor: { - present: { - blocks: { - byClientId: {}, - attributes: {}, - }, - }, - }, - blockListSettings: {}, - settings: { - allowedBlockTypes: [ 'core/test-block-a' ], - }, - }; - expect( canInsertBlockType( state, 'core/test-block-a' ) ).toBe( true ); - } ); - - it( 'should deny blocks when the editor has a template lock', () => { - const state = { - editor: { - present: { - blocks: { - byClientId: {}, - attributes: {}, - }, - }, - }, - blockListSettings: {}, - settings: { - templateLock: 'all', - }, - }; - expect( canInsertBlockType( state, 'core/test-block-a' ) ).toBe( false ); - } ); - - it( 'should deny blocks that restrict parent from being inserted into the root', () => { - const state = { - editor: { - present: { - blocks: { - byClientId: {}, - attributes: {}, - }, - }, - }, - blockListSettings: {}, - settings: {}, - }; - expect( canInsertBlockType( state, 'core/test-block-c' ) ).toBe( false ); - } ); - - it( 'should deny blocks that restrict parent from being inserted into a restricted parent', () => { - const state = { - editor: { - present: { - blocks: { - byClientId: { - block1: { name: 'core/test-block-a' }, - }, - attributes: { - block1: {}, - }, - }, - }, - }, - blockListSettings: {}, - settings: {}, - }; - expect( canInsertBlockType( state, 'core/test-block-c', 'block1' ) ).toBe( false ); - } ); - - it( 'should allow blocks that restrict parent to be inserted into an allowed parent', () => { - const state = { - editor: { - present: { - blocks: { - byClientId: { - block1: { name: 'core/test-block-b' }, - }, - attributes: { - block1: {}, - }, - }, - }, - }, - blockListSettings: {}, - settings: {}, - }; - expect( canInsertBlockType( state, 'core/test-block-c', 'block1' ) ).toBe( true ); - } ); - - it( 'should deny restricted blocks from being inserted into a block that restricts allowedBlocks', () => { - const state = { - editor: { - present: { - blocks: { - byClientId: { - block1: { name: 'core/test-block-a' }, - }, - attributes: { - block1: {}, - }, - }, - }, - }, - blockListSettings: { - block1: { - allowedBlocks: [ 'core/test-block-c' ], - }, - }, - settings: {}, - }; - expect( canInsertBlockType( state, 'core/test-block-b', 'block1' ) ).toBe( false ); - } ); - - it( 'should allow allowed blocks to be inserted into a block that restricts allowedBlocks', () => { - const state = { - editor: { - present: { - blocks: { - byClientId: { - block1: { name: 'core/test-block-a' }, - }, - attributes: { - block1: {}, - }, - }, - }, - }, - blockListSettings: { - block1: { - allowedBlocks: [ 'core/test-block-b' ], - }, - }, - settings: {}, - }; - expect( canInsertBlockType( state, 'core/test-block-b', 'block1' ) ).toBe( true ); - } ); + describe( 'getEditedPostContent', () => { + let originalDefaultBlockName; - it( 'should prioritise parent over allowedBlocks', () => { - const state = { - editor: { - present: { - blocks: { - byClientId: { - block1: { name: 'core/test-block-b' }, - }, - attributes: { - block1: {}, - }, - }, - }, - }, - blockListSettings: { - block1: { - allowedBlocks: [], - }, - }, - settings: {}, - }; - expect( canInsertBlockType( state, 'core/test-block-c', 'block1' ) ).toBe( true ); - } ); - } ); + beforeAll( () => { + originalDefaultBlockName = getDefaultBlockName(); - describe( 'getInserterItems', () => { - it( 'should properly list block type and reusable block items', () => { - const state = { - editor: { - present: { - blocks: { - byClientId: { - block1: { name: 'core/test-block-a' }, - }, - attributes: { - block1: {}, - }, - order: {}, - }, - edits: {}, - }, - }, - initialEdits: {}, - reusableBlocks: { - data: { - 1: { clientId: 'block1', title: 'Reusable Block 1' }, + registerBlockType( 'core/default', { + category: 'common', + title: 'default', + attributes: { + modified: { + type: 'boolean', + default: false, }, }, - currentPost: {}, - preferences: { - insertUsage: {}, - }, - blockListSettings: {}, - settings: {}, - }; - const items = getInserterItems( state ); - const testBlockAItem = items.find( ( item ) => item.id === 'core/test-block-a' ); - expect( testBlockAItem ).toEqual( { - id: 'core/test-block-a', - name: 'core/test-block-a', - initialAttributes: {}, - title: 'Test Block A', - icon: { - src: 'test', - }, - category: 'formatting', - keywords: [ 'testing' ], - isDisabled: false, - utility: 0, - frecency: 0, - hasChildBlocksWithInserterSupport: false, - } ); - const reusableBlockItem = items.find( ( item ) => item.id === 'core/block/1' ); - expect( reusableBlockItem ).toEqual( { - id: 'core/block/1', - name: 'core/block', - initialAttributes: { ref: 1 }, - title: 'Reusable Block 1', - icon: { - src: 'test', - }, - category: 'reusable', - keywords: [], - isDisabled: false, - utility: 0, - frecency: 0, + save: () => null, } ); + setDefaultBlockName( 'core/default' ); } ); - it( 'should not list a reusable block item if it is being inserted inside it self', () => { - const state = { - editor: { - present: { - blocks: { - byClientId: { - block1ref: { - name: 'core/block', - clientId: 'block1ref', - }, - itselfBlock1: { name: 'core/test-block-a' }, - itselfBlock2: { name: 'core/test-block-b' }, - }, - attributes: { - block1ref: { - attributes: { - ref: 1, - }, - }, - itselfBlock1: {}, - itselfBlock2: {}, - }, - order: { - '': [ 'block1ref' ], - }, - }, - edits: {}, - }, - }, - initialEdits: {}, - reusableBlocks: { - data: { - 1: { clientId: 'itselfBlock1', title: 'Reusable Block 1' }, - 2: { clientId: 'itselfBlock2', title: 'Reusable Block 2' }, - }, - }, - currentPost: {}, - preferences: { - insertUsage: {}, - }, - blockListSettings: {}, - settings: {}, - }; - const items = getInserterItems( state, 'itselfBlock1' ); - const reusableBlockItems = filter( items, [ 'name', 'core/block' ] ); - expect( reusableBlockItems ).toHaveLength( 1 ); - expect( reusableBlockItems[ 0 ] ).toEqual( { - id: 'core/block/2', - name: 'core/block', - initialAttributes: { ref: 2 }, - title: 'Reusable Block 2', - icon: { - src: 'test', - }, - category: 'reusable', - keywords: [], - isDisabled: false, - utility: 0, - frecency: 0, + afterAll( () => { + setDefaultBlockName( originalDefaultBlockName ); + getBlockTypes().forEach( ( block ) => { + unregisterBlockType( block.name ); } ); } ); - it( 'should not list a reusable block item if it is being inserted inside a descendent', () => { + it( 'defers to returning an edited post attribute', () => { + const block = createBlock( 'core/block' ); + const state = { editor: { present: { blocks: { - byClientId: { - block2ref: { - name: 'core/block', - clientId: 'block1ref', - }, - referredBlock1: { name: 'core/test-block-a' }, - referredBlock2: { name: 'core/test-block-b' }, - childReferredBlock2: { name: 'core/test-block-a' }, - grandchildReferredBlock2: { name: 'core/test-block-b' }, - }, - attributes: { - block2ref: { - attributes: { - ref: 2, - }, - }, - referredBlock1: {}, - referredBlock2: {}, - childReferredBlock2: {}, - grandchildReferredBlock2: {}, - }, - order: { - '': [ 'block2ref' ], - referredBlock2: [ 'childReferredBlock2' ], - childReferredBlock2: [ 'grandchildReferredBlock2' ], - }, + value: [ block ], }, - edits: {}, - }, - }, - initialEdits: {}, - reusableBlocks: { - data: { - 1: { clientId: 'referredBlock1', title: 'Reusable Block 1' }, - 2: { clientId: 'referredBlock2', title: 'Reusable Block 2' }, - }, - }, - currentPost: {}, - preferences: { - insertUsage: {}, - }, - blockListSettings: {}, - settings: {}, - }; - const items = getInserterItems( state, 'grandchildReferredBlock2' ); - const reusableBlockItems = filter( items, [ 'name', 'core/block' ] ); - expect( reusableBlockItems ).toHaveLength( 1 ); - expect( reusableBlockItems[ 0 ] ).toEqual( { - id: 'core/block/1', - name: 'core/block', - initialAttributes: { ref: 1 }, - title: 'Reusable Block 1', - icon: { - src: 'test', - }, - category: 'reusable', - keywords: [], - isDisabled: false, - utility: 0, - frecency: 0, - } ); - } ); - it( 'should order items by descending utility and frecency', () => { - const state = { - editor: { - present: { - blocks: { - byClientId: { - block1: { name: 'core/test-block-a' }, - block2: { name: 'core/test-block-a' }, - }, - attributes: { - block1: {}, - block2: {}, - }, - order: {}, + edits: { + content: 'custom edit', }, - edits: {}, }, }, initialEdits: {}, - reusableBlocks: { - data: { - 1: { clientId: 'block1', title: 'Reusable Block 1' }, - 2: { clientId: 'block1', title: 'Reusable Block 2' }, - }, - }, currentPost: {}, - preferences: { - insertUsage: { - 'core/block/1': { count: 10, time: 1000 }, - 'core/block/2': { count: 20, time: 1000 }, - }, - }, - blockListSettings: {}, - settings: {}, - }; - const itemIDs = getInserterItems( state ).map( ( item ) => item.id ); - expect( itemIDs ).toEqual( [ - 'core/block/2', - 'core/block/1', - 'core/test-block-b', - 'core/test-freeform', - 'core/test-default', - 'core/test-block-a', - ] ); + }; + + const content = getEditedPostContent( state ); + + expect( content ).toBe( 'custom edit' ); } ); - it( 'should correctly cache the return values', () => { + it( 'returns serialization of blocks', () => { + const block = createBlock( 'core/block' ); + const state = { editor: { present: { blocks: { - byClientId: { - block1: { name: 'core/test-block-a' }, - block2: { name: 'core/test-block-a' }, - block3: { name: 'core/test-block-a' }, - block4: { name: 'core/test-block-a' }, - }, - attributes: { - block1: {}, - block2: {}, - block3: {}, - block4: {}, - }, - order: { - '': [ 'block3', 'block4' ], - }, + value: [ block ], }, edits: {}, }, }, initialEdits: {}, - reusableBlocks: { - data: { - 1: { clientId: 'block1', title: 'Reusable Block 1' }, - 2: { clientId: 'block1', title: 'Reusable Block 2' }, - }, - }, currentPost: {}, - preferences: { - insertUsage: {}, - }, - blockListSettings: {}, - settings: {}, - }; - - const stateSecondBlockRestricted = { - ...state, - blockListSettings: { - block4: { - allowedBlocks: [ 'core/test-block-b' ], - }, - }, }; - const firstBlockFirstCall = getInserterItems( state, 'block3' ); - const firstBlockSecondCall = getInserterItems( stateSecondBlockRestricted, 'block3' ); - expect( firstBlockFirstCall ).toBe( firstBlockSecondCall ); - expect( firstBlockFirstCall.map( ( item ) => item.id ) ).toEqual( [ - 'core/test-block-b', - 'core/test-freeform', - 'core/test-default', - 'core/test-block-a', - 'core/block/1', - 'core/block/2', - ] ); + const content = getEditedPostContent( state ); - const secondBlockFirstCall = getInserterItems( state, 'block4' ); - const secondBlockSecondCall = getInserterItems( stateSecondBlockRestricted, 'block4' ); - expect( secondBlockFirstCall.map( ( item ) => item.id ) ).toEqual( [ - 'core/test-block-b', - 'core/test-freeform', - 'core/test-default', - 'core/test-block-a', - 'core/block/1', - 'core/block/2', - ] ); - expect( secondBlockSecondCall.map( ( item ) => item.id ) ).toEqual( [ - 'core/test-block-b', - ] ); + expect( content ).toBe( '<!-- wp:block /-->' ); } ); - it( 'should set isDisabled when a block with `multiple: false` has been used', () => { + it( 'returns removep\'d serialization of blocks for single unknown', () => { + const unknownBlock = createBlock( 'core/test-freeform', { + content: '<p>foo</p>', + } ); const state = { editor: { present: { blocks: { - byClientId: { - block1: { clientId: 'block1', name: 'core/test-block-b' }, - }, - attributes: { - block1: { attribute: {} }, - }, - order: { - '': [ 'block1' ], - }, + value: [ unknownBlock ], }, edits: {}, }, }, initialEdits: {}, - reusableBlocks: { - data: {}, - }, currentPost: {}, - preferences: { - insertUsage: {}, - }, - blockListSettings: {}, - settings: {}, }; - const items = getInserterItems( state ); - const testBlockBItem = items.find( ( item ) => item.id === 'core/test-block-b' ); - expect( testBlockBItem.isDisabled ).toBe( true ); + + const content = getEditedPostContent( state ); + + expect( content ).toBe( 'foo' ); } ); - it( 'should give common blocks a low utility', () => { + it( 'returns non-removep\'d serialization of blocks for multiple unknown', () => { + const firstUnknown = createBlock( 'core/test-freeform', { + content: '<p>foo</p>', + } ); + const secondUnknown = createBlock( 'core/test-freeform', { + content: '<p>bar</p>', + } ); const state = { editor: { present: { blocks: { - byClientId: {}, - attributes: {}, - order: {}, + value: [ firstUnknown, secondUnknown ], }, edits: {}, }, }, initialEdits: {}, - reusableBlocks: { - data: {}, - }, currentPost: {}, - preferences: { - insertUsage: {}, - }, - blockListSettings: {}, - settings: {}, }; - const items = getInserterItems( state ); - const testBlockBItem = items.find( ( item ) => item.id === 'core/test-block-b' ); - expect( testBlockBItem.utility ).toBe( INSERTER_UTILITY_LOW ); + + const content = getEditedPostContent( state ); + + expect( content ).toBe( '<p>foo</p>\n\n<p>bar</p>' ); } ); - it( 'should give used blocks a medium utility and set a frecency', () => { + it( 'returns empty string for single unmodified default block', () => { + const defaultBlock = createBlock( getDefaultBlockName() ); const state = { editor: { present: { blocks: { - byClientId: {}, - attributes: {}, - order: {}, + value: [ defaultBlock ], }, edits: {}, }, }, initialEdits: {}, - reusableBlocks: { - data: {}, - }, currentPost: {}, - preferences: { - insertUsage: { - 'core/test-block-b': { count: 10, time: 1000 }, - }, - }, - blockListSettings: {}, - settings: {}, }; - const items = getInserterItems( state ); - const reusableBlock2Item = items.find( ( item ) => item.id === 'core/test-block-b' ); - expect( reusableBlock2Item.utility ).toBe( INSERTER_UTILITY_MEDIUM ); - expect( reusableBlock2Item.frecency ).toBe( 2.5 ); + + const content = getEditedPostContent( state ); + + expect( content ).toBe( '' ); } ); - it( 'should give contextual blocks a high utility', () => { + it( 'should not return empty string for modified default block', () => { + const defaultBlock = createBlock( getDefaultBlockName() ); const state = { editor: { present: { blocks: { - byClientId: { - block1: { name: 'core/test-block-b' }, - }, - attributes: { - block1: { attribute: {} }, - }, - order: { - '': [ 'block1' ], - }, + value: [ { + ...defaultBlock, + attributes: { + ...defaultBlock.attributes, + modified: true, + }, + } ], }, edits: {}, }, }, initialEdits: {}, - reusableBlocks: { - data: {}, - }, currentPost: {}, - preferences: { - insertUsage: {}, - }, - blockListSettings: {}, - settings: {}, }; - const items = getInserterItems( state, 'block1' ); - const testBlockCItem = items.find( ( item ) => item.id === 'core/test-block-c' ); - expect( testBlockCItem.utility ).toBe( INSERTER_UTILITY_HIGH ); + + const content = getEditedPostContent( state ); + + expect( content ).toBe( '<!-- wp:test-default {\"modified\":true} /-->' ); } ); } ); @@ -5307,84 +2549,6 @@ describe( 'selectors', () => { } ); } ); - describe( 'isValidTemplate', () => { - it( 'should return true if template is valid', () => { - const state = { - template: { isValid: true }, - }; - - expect( isValidTemplate( state ) ).toBe( true ); - } ); - - it( 'should return false if template is not valid', () => { - const state = { - template: { isValid: false }, - }; - - expect( isValidTemplate( state ) ).toBe( false ); - } ); - } ); - - describe( 'getTemplate', () => { - it( 'should return the template object', () => { - const template = []; - const state = { - settings: { template }, - }; - - expect( getTemplate( state ) ).toBe( template ); - } ); - } ); - - describe( 'getTemplateLock', () => { - it( 'should return the general template lock if no clientId was set', () => { - const state = { - settings: { templateLock: 'all' }, - }; - - expect( getTemplateLock( state ) ).toBe( 'all' ); - } ); - - it( 'should return null if the specified clientId was not found ', () => { - const state = { - settings: { templateLock: 'all' }, - blockListSettings: { - chicken: { - templateLock: 'insert', - }, - }, - }; - - expect( getTemplateLock( state, 'ribs' ) ).toBe( null ); - } ); - - it( 'should return null if template lock was not set on the specified block', () => { - const state = { - settings: { templateLock: 'all' }, - blockListSettings: { - chicken: { - test: 'tes1t', - }, - }, - }; - - expect( getTemplateLock( state, 'ribs' ) ).toBe( null ); - } ); - - it( 'should return the template lock for the specified clientId', () => { - const state = { - settings: { templateLock: 'all' }, - blockListSettings: { - chicken: { - templateLock: 'insert', - }, - }, - }; - - expect( getTemplateLock( state, 'chicken' ) ).toBe( 'insert' ); - } ); - } ); - describe( 'isPermalinkEditable', () => { it( 'should be false if there is no permalink', () => { const state = { @@ -5536,38 +2700,13 @@ describe( 'selectors', () => { const state = { currentPost: {}, editor: { - present: {}, - }, - }; - - expect( getPermalinkParts( state ) ).toBeNull(); - } ); - } ); - - describe( 'getBlockListSettings', () => { - it( 'should return the settings of a block', () => { - const state = { - blockListSettings: { - chicken: { - setting1: false, - }, - ribs: { - setting2: true, + present: { + edits: {}, }, }, }; - expect( getBlockListSettings( state, 'chicken' ) ).toEqual( { - setting1: false, - } ); - } ); - - it( 'should return undefined if settings for the block don’t exist', () => { - const state = { - blockListSettings: {}, - }; - - expect( getBlockListSettings( state, 'chicken' ) ).toBe( undefined ); + expect( getPermalinkParts( state ) ).toBeNull(); } ); } ); From 61a418c44ee9beacf884f51330934f9b09788b09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20Van=C2=A0Durpe?= <iseulde@automattic.com> Date: Fri, 22 Feb 2019 10:08:02 +0100 Subject: [PATCH 502/691] RichText: warn when using inline container (#13921) * RichText: warn when using inline container * Add env check * Update documentation --- .../editor/src/components/rich-text/README.md | 2 +- .../editor/src/components/rich-text/index.js | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/editor/src/components/rich-text/README.md b/packages/editor/src/components/rich-text/README.md index 6774ca3879f49..b3565b729cca3 100644 --- a/packages/editor/src/components/rich-text/README.md +++ b/packages/editor/src/components/rich-text/README.md @@ -14,7 +14,7 @@ Render a rich [`contenteditable` input](https://developer.mozilla.org/en-US/docs ### `tagName: String` -*Default: `div`.* The [tag name](https://www.w3.org/TR/html51/syntax.html#tag-name) of the editable element. +*Default: `div`.* The [tag name](https://www.w3.org/TR/html51/syntax.html#tag-name) of the editable element. Elements that display inline are not supported. ### `placeholder: String` diff --git a/packages/editor/src/components/rich-text/index.js b/packages/editor/src/components/rich-text/index.js index 30cc785d63448..d086cf24795fb 100644 --- a/packages/editor/src/components/rich-text/index.js +++ b/packages/editor/src/components/rich-text/index.js @@ -67,7 +67,7 @@ import { RemoveBrowserShortcuts } from './remove-browser-shortcuts'; * Browser dependencies */ -const { getSelection } = window; +const { getSelection, getComputedStyle } = window; /** * All inserting input types that would insert HTML into the DOM. @@ -151,7 +151,20 @@ export class RichText extends Component { } setRef( node ) { - this.editableRef = node; + if ( node ) { + if ( process.env.NODE_ENV === 'development' ) { + const computedStyle = getComputedStyle( node ); + + if ( computedStyle.display === 'inline' ) { + // eslint-disable-next-line no-console + console.warn( 'RichText cannot be used with an inline container. Please use a different tagName.' ); + } + } + + this.editableRef = node; + } else { + delete this.editableRef; + } } setFocusedElement() { From d9b232d1d15db418d22db89c53626542a8efd654 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Fri, 22 Feb 2019 12:07:51 +0100 Subject: [PATCH 503/691] Try updating the WP database (#14048) --- bin/install-wordpress.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bin/install-wordpress.sh b/bin/install-wordpress.sh index a5ba87ec4f4d5..92cee6f514e90 100755 --- a/bin/install-wordpress.sh +++ b/bin/install-wordpress.sh @@ -78,6 +78,8 @@ if [ "$WP_VERSION" == "latest" ]; then # Check for WordPress updates, to make sure we're running the very latest version. echo -e $(status_message "Updating WordPress to the latest version...") docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm -u 33 $CLI core update --quiet + echo -e $(status_message "Updating The WordPress Database...") + docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm -u 33 $CLI core update-db --quiet fi # If the 'wordpress' volume wasn't during the down/up earlier, but the post port has changed, we need to update it. From 666989fd1cbfb81466d575681e282beb175e0ab2 Mon Sep 17 00:00:00 2001 From: Stefanos Togoulidis <stefanostogoulidis@gmail.com> Date: Fri, 22 Feb 2019 20:10:20 +0200 Subject: [PATCH 504/691] Merge native mobile release v1.0 to master (#14061) * Bump plugin version to 5.1.0-rc.1 * RichText: only ignore input types that insert HTML (#13914) * RichText: only ignore input types that insert HTML * Mark RichTextInputEvent as unstable * Use Set * Bump plugin version to 5.1.0 * Deprecate RichTextInputEvent on mobile too (#13975) * The undelying RichText component implementation has changed the parameters returned onChange, and we forgot to update the PostTitle component (#13967) * Fixes wrong state comparison (#13987) Upload media progress bar is missing while media is uploading new * Re-add rootTagsToEliminate prop (#14006) * [Mobile]Update PostTitle to apply borders when it is focused (#13970) * Trigger onFocusStatusChange from PostTitle * Fix lint issue * Update post title shadow mechanism Also open inner ref so that focus state can be updated when focus is made programmatically * Update props * Update onRef as ref * Update title padding&margin * Mobile: Rename ref to innerRef on PostTitle (#14024) * Fixes a red screen in mobile. (#14011) * Change background color on image placeholder block (#14033) * Changed upload media icon color * Changed media placeholder background color * Fix post title native syntax (#14041) * Fix unexpected token in native code * Dummy commit to trigger Travis * Include the rnmobile release branch to Travis builds * Mobile: Links UI using BottomSheet component (#13972) * Mobile: Replaced Links UI with bottom-sheet component * Mobile links UI: Removed commented code. * Mobile: Fix lint issues * Mobile Links UI: Remove autofocus on Android. This hides an issue where the modal sometimes will be under the keyboard on Android. * Fixes pasting links. (#14038) * Update post title vertical paddings (#14040) * Add try/catch fallback to plain text for pasteHandler (#14044) * Fix link interface. (#14052) * [Mobile]Fix title padding on Android (#14057) * Remove title vertical paddings for Android * Revert "Remove title vertical paddings for Android" This reverts commit 09f0d3592f77509e6a19e7712bba725a6118ab0f. * Import padding variables * Revert wrong format image color (#14058) * Stop building the mobile release branch on Travis (#14060) --- .../block-library/src/image/edit.native.js | 2 +- .../editor/src/components/index.native.js | 2 +- .../media-placeholder/styles.native.scss | 2 +- .../mobile/bottom-sheet/cell.native.js | 13 +- .../mobile/bottom-sheet/styles.native.scss | 1 + .../src/components/post-title/index.native.js | 76 ++++++++--- .../components/post-title/style.native.scss | 10 ++ .../src/components/rich-text/index.native.js | 34 +++-- .../rich-text/input-event.native.js | 2 +- .../format-library/src/link/button.native.js | 24 ---- .../format-library/src/link/index.native.js | 20 ++- .../format-library/src/link/modal.native.js | 129 ++++++++---------- .../format-library/src/link/modal.native.scss | 81 +---------- 13 files changed, 174 insertions(+), 222 deletions(-) create mode 100644 packages/editor/src/components/post-title/style.native.scss delete mode 100644 packages/format-library/src/link/button.native.js diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index 73b3eba1ab5fa..eabcdc9941a26 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -124,7 +124,7 @@ class ImageEdit extends React.Component { updateMediaProgress( payload ) { const { setAttributes } = this.props; this.setState( { progress: payload.progress, isUploadInProgress: true, isUploadFailed: false } ); - if ( payload.mediaUrl !== undefined ) { + if ( payload.mediaUrl ) { setAttributes( { url: payload.mediaUrl } ); } } diff --git a/packages/editor/src/components/index.native.js b/packages/editor/src/components/index.native.js index cd974fd2dfea1..d7d35e3873e2a 100644 --- a/packages/editor/src/components/index.native.js +++ b/packages/editor/src/components/index.native.js @@ -5,7 +5,7 @@ export { default as RichText, RichTextShortcut, RichTextToolbarButton, - RichTextInputEvent, + UnstableRichTextInputEvent, } from './rich-text'; export { default as MediaPlaceholder } from './media-placeholder'; export { default as BlockFormatControls } from './block-format-controls'; diff --git a/packages/editor/src/components/media-placeholder/styles.native.scss b/packages/editor/src/components/media-placeholder/styles.native.scss index 964bb4371310d..d3001491eb73a 100644 --- a/packages/editor/src/components/media-placeholder/styles.native.scss +++ b/packages/editor/src/components/media-placeholder/styles.native.scss @@ -4,7 +4,7 @@ flex-direction: column; align-items: center; justify-content: center; - background-color: #f2f2f2; + background-color: #e9eff3; padding-left: 12; padding-right: 12; padding-top: 12; diff --git a/packages/editor/src/components/mobile/bottom-sheet/cell.native.js b/packages/editor/src/components/mobile/bottom-sheet/cell.native.js index 6ac26d2d00ff8..9acc94bbfb3bb 100644 --- a/packages/editor/src/components/mobile/bottom-sheet/cell.native.js +++ b/packages/editor/src/components/mobile/bottom-sheet/cell.native.js @@ -16,10 +16,10 @@ import styles from './styles.scss'; import platformStyles from './cellStyles.scss'; export default class Cell extends Component { - constructor() { + constructor( props ) { super( ...arguments ); this.state = { - isEditingValue: false, + isEditingValue: props.autoFocus || false, }; } @@ -53,7 +53,7 @@ export default class Cell extends Component { const onCellPress = () => { if ( isValueEditable ) { - this.setState( { isEditingValue: true } ); + startEditing(); } else if ( onPress !== undefined ) { onPress(); } @@ -63,6 +63,12 @@ export default class Cell extends Component { this.setState( { isEditingValue: false } ); }; + const startEditing = () => { + if ( this.state.isEditingValue === false ) { + this.setState( { isEditingValue: true } ); + } + }; + const separatorStyle = () => { const leftMarginStyle = { ...styles.cellSeparator, ...platformStyles.separatorMarginLeft }; switch ( separatorType ) { @@ -97,6 +103,7 @@ export default class Cell extends Component { onChangeText={ onChangeValue } editable={ isValueEditable } pointerEvents={ this.state.isEditingValue ? 'auto' : 'none' } + onFocus={ startEditing } onBlur={ finishEditing } { ...valueProps } /> diff --git a/packages/editor/src/components/mobile/bottom-sheet/styles.native.scss b/packages/editor/src/components/mobile/bottom-sheet/styles.native.scss index 198d526092a85..53764ee4fe38f 100644 --- a/packages/editor/src/components/mobile/bottom-sheet/styles.native.scss +++ b/packages/editor/src/components/mobile/bottom-sheet/styles.native.scss @@ -31,6 +31,7 @@ border-top-left-radius: 8px; width: 100%; max-width: 512; + padding-bottom: 0; } .content { diff --git a/packages/editor/src/components/post-title/index.native.js b/packages/editor/src/components/post-title/index.native.js index 8c79b43414a1a..8204220b3be6d 100644 --- a/packages/editor/src/components/post-title/index.native.js +++ b/packages/editor/src/components/post-title/index.native.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import { View } from 'react-native'; + /** * WordPress dependencies */ @@ -8,6 +13,11 @@ import { withDispatch } from '@wordpress/data'; import { withFocusOutside } from '@wordpress/components'; import { withInstanceId, compose } from '@wordpress/compose'; +/** + * Internal dependencies + */ +import styles from './style.scss'; + const minHeight = 30; class PostTitle extends Component { @@ -16,6 +26,7 @@ class PostTitle extends Component { this.onSelect = this.onSelect.bind( this ); this.onUnselect = this.onUnselect.bind( this ); + this.titleViewRef = null; this.state = { isSelected: false, @@ -23,10 +34,23 @@ class PostTitle extends Component { }; } + componentDidMount() { + if ( this.props.innerRef ) { + this.props.innerRef( this ); + } + } + handleFocusOutside() { this.onUnselect(); } + focus() { + if ( this.titleViewRef ) { + this.titleViewRef.focus(); + this.setState( { isSelected: true } ); + } + } + onSelect() { this.setState( { isSelected: true } ); this.props.clearSelectedBlock(); @@ -41,33 +65,41 @@ class PostTitle extends Component { placeholder, style, title, + focusedBorderColor, + borderStyle, } = this.props; const decodedPlaceholder = decodeEntities( placeholder ); + const borderColor = this.state.isSelected ? focusedBorderColor : 'transparent'; return ( - <RichText - tagName={ 'p' } - onFocus={ this.onSelect } - onBlur={ this.props.onBlur } // always assign onBlur as a props - multiline={ false } - style={ [ style, { - minHeight: Math.max( minHeight, this.state.aztecHeight ), - } ] } - fontSize={ 24 } - fontWeight={ 'bold' } - onChange={ ( event ) => { - this.props.onUpdate( event.content ); - } } - onContentSizeChange={ ( event ) => { - this.setState( { aztecHeight: event.aztecHeight } ); - } } - placeholder={ decodedPlaceholder } - value={ title } - onSplit={ this.props.onEnterPress } - setRef={ this.props.setRef } - > - </RichText> + <View style={ [ styles.titleContainer, borderStyle, { borderColor } ] }> + <RichText + tagName={ 'p' } + rootTagsToEliminate={ [ 'strong' ] } + onFocus={ this.onSelect } + onBlur={ this.props.onBlur } // always assign onBlur as a props + multiline={ false } + style={ [ style, { + minHeight: Math.max( minHeight, this.state.aztecHeight ), + } ] } + fontSize={ 24 } + fontWeight={ 'bold' } + onChange={ ( value ) => { + this.props.onUpdate( value ); + } } + onContentSizeChange={ ( event ) => { + this.setState( { aztecHeight: event.aztecHeight } ); + } } + placeholder={ decodedPlaceholder } + value={ title } + onSplit={ this.props.onEnterPress } + setRef={ ( ref ) => { + this.titleViewRef = ref; + } } + > + </RichText> + </View> ); } } diff --git a/packages/editor/src/components/post-title/style.native.scss b/packages/editor/src/components/post-title/style.native.scss new file mode 100644 index 0000000000000..b5d36037d9659 --- /dev/null +++ b/packages/editor/src/components/post-title/style.native.scss @@ -0,0 +1,10 @@ + +@import "variables.scss"; + +.titleContainer { + padding-left: 16; + padding-right: 16; + padding-top: $title-block-padding-top; + padding-bottom: $title-block-padding-bottom; + margin-top: 24; +} diff --git a/packages/editor/src/components/rich-text/index.native.js b/packages/editor/src/components/rich-text/index.native.js index e7deab608e266..1e3de4f168086 100644 --- a/packages/editor/src/components/rich-text/index.native.js +++ b/packages/editor/src/components/rich-text/index.native.js @@ -42,6 +42,27 @@ const unescapeSpaces = ( text ) => { return text.replace( /&nbsp;|&#160;/gi, ' ' ); }; +/** + * Calls {@link pasteHandler} with a fallback to plain text when HTML processing + * results in errors + * + * @param {Object} [options] The options to pass to {@link pasteHandler} + * + * @return {Array|string} A list of blocks or a string, depending on + * `handlerMode`. + */ +const saferPasteHandler = ( options ) => { + try { + return pasteHandler( options ); + } catch ( error ) { + window.console.log( 'Pasting HTML failed:', error ); + window.console.log( 'HTML:', options.HTML ); + window.console.log( 'Falling back to plain text.' ); + // fallback to plain text + return pasteHandler( { ...options, HTML: '' } ); + } +}; + const gutenbergFormatNamesToAztec = { 'core/bold': 'bold', 'core/italic': 'italic', @@ -289,9 +310,8 @@ export class RichText extends Component { }, } ); this.lastContent = this.valueToFormat( linkedRecord ); - this.props.onChange( { - content: this.lastContent, - } ); + this.lastEventCount = undefined; + this.props.onChange( this.lastContent ); // Allows us to ask for this information when we get a report. window.console.log( 'Created link:\n\n', trimmedText ); @@ -310,7 +330,7 @@ export class RichText extends Component { mode = 'AUTO'; } - const pastedContent = pasteHandler( { + const pastedContent = saferPasteHandler( { HTML: pastedHtml, plainText: pastedText, mode, @@ -324,9 +344,7 @@ export class RichText extends Component { const newContent = this.valueToFormat( insertedContent ); this.lastEventCount = undefined; this.lastContent = newContent; - this.props.onChange( { - content: this.lastContent, - } ); + this.props.onChange( this.lastContent ); } else if ( onSplit ) { if ( ! pastedContent.length ) { return; @@ -568,4 +586,4 @@ RichTextContainer.Content.defaultProps = { export default RichTextContainer; export { RichTextShortcut } from './shortcut'; export { RichTextToolbarButton } from './toolbar-button'; -export { RichTextInputEvent } from './input-event'; +export { UnstableRichTextInputEvent } from './input-event'; diff --git a/packages/editor/src/components/rich-text/input-event.native.js b/packages/editor/src/components/rich-text/input-event.native.js index 71f2ce4797e24..b8f0fad0a969c 100644 --- a/packages/editor/src/components/rich-text/input-event.native.js +++ b/packages/editor/src/components/rich-text/input-event.native.js @@ -3,7 +3,7 @@ */ import { Component } from '@wordpress/element'; -export class RichTextInputEvent extends Component { +export class UnstableRichTextInputEvent extends Component { render() { return null; } diff --git a/packages/format-library/src/link/button.native.js b/packages/format-library/src/link/button.native.js deleted file mode 100644 index fa8fd004c5a37..0000000000000 --- a/packages/format-library/src/link/button.native.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * External dependencies - */ -import { TouchableOpacity, View } from 'react-native'; - -export default function Button( props ) { - const { - children, - onClick, - disabled, - } = props; - - return ( - <TouchableOpacity - accessible={ true } - onPress={ onClick } - disabled={ disabled } - > - <View style={ { flexDirection: 'row' } }> - { children } - </View> - </TouchableOpacity> - ); -} diff --git a/packages/format-library/src/link/index.native.js b/packages/format-library/src/link/index.native.js index 4a2def1614323..4ea3ee0e30934 100644 --- a/packages/format-library/src/link/index.native.js +++ b/packages/format-library/src/link/index.native.js @@ -107,17 +107,15 @@ export const link = { return ( <Fragment> - { this.state.addingLink && - <ModalLinkUI - isVisible - isActive={ isActive } - activeAttributes={ activeAttributes } - onClose={ this.stopAddingLink } - onChange={ onChange } - onRemove={ this.onRemoveFormat } - value={ linkSelection } - /> - } + <ModalLinkUI + isVisible={ this.state.addingLink } + isActive={ isActive } + activeAttributes={ activeAttributes } + onClose={ this.stopAddingLink } + onChange={ onChange } + onRemove={ this.onRemoveFormat } + value={ linkSelection } + /> <RichTextToolbarButton name="link" icon="admin-links" diff --git a/packages/format-library/src/link/modal.native.js b/packages/format-library/src/link/modal.native.js index 6736b4a707945..765cbb5b455d3 100644 --- a/packages/format-library/src/link/modal.native.js +++ b/packages/format-library/src/link/modal.native.js @@ -2,15 +2,14 @@ * External dependencies */ import React from 'react'; -import { Switch, Text, TextInput, View } from 'react-native'; -import Modal from 'react-native-modal'; +import { Switch, Platform } from 'react-native'; /** * WordPress dependencies */ import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; -import { URLInput } from '@wordpress/editor'; +import { BottomSheet } from '@wordpress/editor'; import { prependHTTP } from '@wordpress/url'; import { withSpokenMessages, @@ -28,12 +27,11 @@ import { * Internal dependencies */ import { createLinkFormat, isValidHref } from './utils'; -import Button from './button'; import styles from './modal.scss'; class ModalLinkUI extends Component { - constructor( props ) { + constructor() { super( ...arguments ); this.submitLink = this.submitLink.bind( this ); @@ -43,12 +41,24 @@ class ModalLinkUI extends Component { this.removeLink = this.removeLink.bind( this ); this.state = { - inputValue: props.activeAttributes.url || '', - text: getTextContent( slice( props.value ) ), + inputValue: '', + text: '', opensInNewWindow: false, }; } + componentDidUpdate( oldProps ) { + if ( oldProps === this.props ) { + return; + } + + this.setState( { + inputValue: this.props.activeAttributes.url || '', + text: getTextContent( slice( this.props.value ) ), + opensInNewWindow: false, + } ); + } + onChangeInputValue( inputValue ) { this.setState( { inputValue } ); } @@ -74,7 +84,7 @@ class ModalLinkUI extends Component { const placeholderFormats = ( value.formatPlaceholder && value.formatPlaceholder.formats ) || []; if ( isCollapsed( value ) && ! isActive ) { // insert link - const toInsert = applyFormat( create( { text: linkText } ), [ ...placeholderFormats, format ], 0, text.length ); + const toInsert = applyFormat( create( { text: linkText } ), [ ...placeholderFormats, format ], 0, linkText.length ); onChange( insert( value, toInsert ) ); } else if ( text !== getTextContent( slice( value ) ) ) { // edit text in selected link const toInsert = applyFormat( create( { text } ), [ ...placeholderFormats, format ], 0, text.length ); @@ -103,71 +113,48 @@ class ModalLinkUI extends Component { const { isVisible } = this.props; return ( - <Modal + <BottomSheet isVisible={ isVisible } - style={ styles.bottomModal } - animationInTiming={ 500 } - animationOutTiming={ 500 } - backdropTransitionInTiming={ 500 } - backdropTransitionOutTiming={ 500 } - onBackdropPress={ this.props.onClose } - onSwipe={ this.props.onClose } - swipeDirection="down" - avoidKeyboard={ true } + onClose={ this.submitLink } + hideHeader > - <View style={ { ...styles.content, borderColor: 'rgba(0, 0, 0, 0.1)' } }> - <View style={ styles.dragIndicator } /> - <View style={ styles.head }> - <Button onClick={ this.removeLink }> - <Text style={ { ...styles.buttonText, color: 'red' } }> - { __( 'Remove' ) } - </Text> - </Button> - <Text style={ styles.title }> - { __( 'Link Settings' ) } - </Text> - <Button onClick={ this.submitLink }> - <Text style={ { ...styles.buttonText, color: '#0087be' } } > - { __( 'Done' ) } - </Text> - </Button> - </View> - <View style={ styles.separator } /> - <View style={ styles.inlineInput }> - <Text style={ styles.inlineInputLabel }> - { __( 'URL' ) } - </Text> - <URLInput - style={ styles.inlineInputValue } - value={ this.state.inputValue } - onChange={ this.onChangeInputValue } - /> - </View> - <View style={ styles.separator } /> - <View style={ styles.inlineInput }> - <Text style={ styles.inlineInputLabel }> - { __( 'Link Text' ) } - </Text> - <TextInput - style={ styles.inlineInputValue } - value={ this.state.text } - onChangeText={ this.onChangeText } - /> - </View> - <View style={ styles.separator } /> - <View style={ styles.inlineInput }> - <Text style={ styles.inlineInputLabel }> - { __( 'Open in a new window' ) } - </Text> - <View style={ { ...styles.inlineInputValue, ...styles.inlineInputValueSwitch, alignItems: 'flex-end' } }> - <Switch - value={ this.state.opensInNewWindow } - onValueChange={ this.onChangeOpensInNewWindow } - /> - </View> - </View> - </View> - </Modal> + { /* eslint-disable jsx-a11y/no-autofocus */ + <BottomSheet.Cell + icon={ 'admin-links' } + label={ __( 'URL' ) } + value={ this.state.inputValue } + placeholder={ __( 'Add URL' ) } + autoCapitalize="none" + autoCorrect={ false } + textContentType="URL" + onChangeValue={ this.onChangeInputValue } + autoFocus={ Platform.OS === 'ios' } + /> + /* eslint-enable jsx-a11y/no-autofocus */ } + <BottomSheet.Cell + icon={ 'editor-textcolor' } + label={ __( 'Link Text' ) } + value={ this.state.text } + placeholder={ __( 'Add Link Text' ) } + onChangeValue={ this.onChangeText } + /> + <BottomSheet.Cell + icon={ 'external' } + label={ __( 'Open in New Tab' ) } + value={ '' } + > + <Switch + value={ this.state.opensInNewWindow } + onValueChange={ this.onChangeOpensInNewWindow } + /> + </BottomSheet.Cell> + <BottomSheet.Cell + label={ __( 'Remove Link' ) } + labelStyle={ styles.clearLinkButton } + separatorType={ 'none' } + onPress={ this.removeLink } + /> + </BottomSheet> ); } } diff --git a/packages/format-library/src/link/modal.native.scss b/packages/format-library/src/link/modal.native.scss index 72f6a647b2396..e6a590eab85f7 100644 --- a/packages/format-library/src/link/modal.native.scss +++ b/packages/format-library/src/link/modal.native.scss @@ -1,80 +1,3 @@ - -.bottomModal { - justify-content: flex-end; - margin: 0; +.clearLinkButton { + color: $alert-red; } - -.dragIndicator { - background-color: $light-gray-400; - height: 4px; - width: 10%; - top: -12px; - margin: auto; - border-radius: 2px; -} - -.separator { - background-color: $light-gray-400; - height: 1px; - width: 95%; - margin: auto; -} - -.content { - background-color: $white; - padding: 18px 10px 5px 10px; - justify-content: center; - border-top-right-radius: 8px; - border-top-left-radius: 8px; -} - -.head { - flex-direction: row; - width: 100%; - margin-bottom: 5px; - justify-content: space-between; - align-items: center; - align-content: center; -} - -.title { - color: $dark-gray-600; - font-size: 18px; - font-weight: 600; - flex: 1; - text-align: center; -} - -.buttonText { - font-size: 18px; - padding: 5px; -} - -.inlineInput { - flex-direction: row; - width: 100%; - justify-content: space-between; - align-items: center; - margin: 5px 0; -} - -.inlineInputLabel { - padding: 10px 10px; - color: $dark-gray-600; - font-size: 14px; - font-weight: bold; -} - -.inlineInputValue { - flex-grow: 1; - font-size: 14px; - text-align: right; - align-items: stretch; - align-self: flex-end; - padding: 10px; -} - -.inlineInputValueSwitch { - padding: 5px; -} - From 2d39089798f9d941858c7b1c6d2c65b660272959 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Fri, 22 Feb 2019 20:01:05 +0100 Subject: [PATCH 505/691] Avoid mutating imported default config in webpack config (#14039) --- webpack.config.js | 150 +++++++++++++++++++++++----------------------- 1 file changed, 75 insertions(+), 75 deletions(-) diff --git a/webpack.config.js b/webpack.config.js index a5dd63c3bd982..1550b0712ab0d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -13,7 +13,7 @@ const { basename } = require( 'path' ); */ const CustomTemplatedPathPlugin = require( '@wordpress/custom-templated-path-webpack-plugin' ); const LibraryExportDefaultPlugin = require( '@wordpress/library-export-default-webpack-plugin' ); -const config = require( '@wordpress/scripts/config/webpack.config' ); +const defaultConfig = require( '@wordpress/scripts/config/webpack.config' ); const { camelCaseDash } = require( '@wordpress/scripts/utils' ); /** @@ -27,82 +27,82 @@ const gutenbergPackages = Object.keys( dependencies ) .filter( ( packageName ) => packageName.startsWith( WORDPRESS_NAMESPACE ) ) .map( ( packageName ) => packageName.replace( WORDPRESS_NAMESPACE, '' ) ); -config.entry = gutenbergPackages.reduce( ( memo, packageName ) => { - const name = camelCaseDash( packageName ); - memo[ name ] = `./packages/${ packageName }`; - return memo; -}, {} ); +module.exports = { + ...defaultConfig, + entry: gutenbergPackages.reduce( ( memo, packageName ) => { + const name = camelCaseDash( packageName ); + memo[ name ] = `./packages/${ packageName }`; + return memo; + }, {} ), + output: { + filename: './build/[basename]/index.js', + path: __dirname, + library: [ 'wp', '[name]' ], + libraryTarget: 'this', + }, + plugins: [ + ...defaultConfig.plugins, + new DefinePlugin( { + // Inject the `GUTENBERG_PHASE` global, used for feature flagging. + // eslint-disable-next-line @wordpress/gutenberg-phase + 'process.env.GUTENBERG_PHASE': JSON.stringify( parseInt( process.env.npm_package_config_GUTENBERG_PHASE, 10 ) || 1 ), + } ), + // Create RTL files with a -rtl suffix + new WebpackRTLPlugin( { + suffix: '-rtl', + minify: defaultConfig.mode === 'production' ? { safe: true } : false, + } ), + new CustomTemplatedPathPlugin( { + basename( path, data ) { + let rawRequest; -config.output = { - filename: './build/[basename]/index.js', - path: __dirname, - library: [ 'wp', '[name]' ], - libraryTarget: 'this', -}; - -config.plugins.push( - new DefinePlugin( { - // Inject the `GUTENBERG_PHASE` global, used for feature flagging. - // eslint-disable-next-line @wordpress/gutenberg-phase - 'process.env.GUTENBERG_PHASE': JSON.stringify( parseInt( process.env.npm_package_config_GUTENBERG_PHASE, 10 ) || 1 ), - } ), - // Create RTL files with a -rtl suffix - new WebpackRTLPlugin( { - suffix: '-rtl', - minify: config.mode === 'production' ? { safe: true } : false, - } ), - new CustomTemplatedPathPlugin( { - basename( path, data ) { - let rawRequest; - - const entryModule = get( data, [ 'chunk', 'entryModule' ], {} ); - switch ( entryModule.type ) { - case 'javascript/auto': - rawRequest = entryModule.rawRequest; - break; - - case 'javascript/esm': - rawRequest = entryModule.rootModule.rawRequest; - break; - } + const entryModule = get( data, [ 'chunk', 'entryModule' ], {} ); + switch ( entryModule.type ) { + case 'javascript/auto': + rawRequest = entryModule.rawRequest; + break; - if ( rawRequest ) { - return basename( rawRequest ); - } + case 'javascript/esm': + rawRequest = entryModule.rootModule.rawRequest; + break; + } - return path; - }, - } ), - new LibraryExportDefaultPlugin( [ - 'api-fetch', - 'deprecated', - 'dom-ready', - 'redux-routine', - 'token-list', - ].map( camelCaseDash ) ), - new CopyWebpackPlugin( - gutenbergPackages.map( ( packageName ) => ( { - from: `./packages/${ packageName }/build-style/*.css`, - to: `./build/${ packageName }/`, - flatten: true, - transform: ( content ) => { - if ( config.mode === 'production' ) { - return postcss( [ - require( 'cssnano' )( { - preset: [ 'default', { - discardComments: { - removeAll: true, - }, - } ], - } ), - ] ) - .process( content, { from: 'src/app.css', to: 'dest/app.css' } ) - .then( ( result ) => result.css ); + if ( rawRequest ) { + return basename( rawRequest ); } - return content; - }, - } ) ) - ) -); -module.exports = config; + return path; + }, + } ), + new LibraryExportDefaultPlugin( [ + 'api-fetch', + 'deprecated', + 'dom-ready', + 'redux-routine', + 'token-list', + ].map( camelCaseDash ) ), + new CopyWebpackPlugin( + gutenbergPackages.map( ( packageName ) => ( { + from: `./packages/${ packageName }/build-style/*.css`, + to: `./build/${ packageName }/`, + flatten: true, + transform: ( content ) => { + if ( defaultConfig.mode === 'production' ) { + return postcss( [ + require( 'cssnano' )( { + preset: [ 'default', { + discardComments: { + removeAll: true, + }, + } ], + } ), + ] ) + .process( content, { from: 'src/app.css', to: 'dest/app.css' } ) + .then( ( result ) => result.css ); + } + return content; + }, + } ) ) + ), + ], +}; From 4fa15b5fd4117dea45dde052fa9d5c14798e5ae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Fri, 22 Feb 2019 20:44:38 +0100 Subject: [PATCH 506/691] Make Babel import JSX pragma plugin aware of `wp.element.createElement` (#13809) * Test: Add test which verifies wheter JSX pragma detects WP global * Update packages/babel-plugin-import-jsx-pragma/test/index.js Co-Authored-By: gziolo <grzegorz@gziolo.pl> * Skip import when the scope variable is already defined * Add failing tests for inner scope variable defined verification * Add import statement when there is any undefined scope variable * Docs: Add details about changes introduced to Babel plugin --- .../CHANGELOG.md | 6 +++ .../babel-plugin-import-jsx-pragma/README.md | 2 +- .../src/index.js | 9 ++++- .../test/index.js | 40 ++++++++++++++++++- 4 files changed, 53 insertions(+), 4 deletions(-) diff --git a/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md b/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md index 33b89b5ae57b0..ffa68f4404f0d 100644 --- a/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md +++ b/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.0.0 (Unreleased) + +### Breaking Change + +- Plugin skips now adding import JSX pragma when the scope variable is defined for all JSX elements ([#13809](https://github.com/WordPress/gutenberg/pull/13809)). + ## 1.1.0 (2018-09-05) ### New Feature diff --git a/packages/babel-plugin-import-jsx-pragma/README.md b/packages/babel-plugin-import-jsx-pragma/README.md index fc32f27a90204..85d7e69655455 100644 --- a/packages/babel-plugin-import-jsx-pragma/README.md +++ b/packages/babel-plugin-import-jsx-pragma/README.md @@ -4,7 +4,7 @@ Babel transform plugin for automatically injecting an import to be used as the p [JSX](https://reactjs.org/docs/jsx-in-depth.html) is merely a syntactic sugar for a function call, typically to `React.createElement` when used with [React](https://reactjs.org/). As such, it requires that the function referenced by this transform be within the scope of the file where the JSX occurs. In a typical React project, this means React must be imported in any file where JSX exists. -**Babel Plugin Import JSX Pragma** automates this process by introducing the necessary import automatically wherever JSX exists, allowing you to use JSX in your code without thinking to ensure the transformed function is within scope. +**Babel Plugin Import JSX Pragma** automates this process by introducing the necessary import automatically wherever JSX exists, allowing you to use JSX in your code without thinking to ensure the transformed function is within scope. It respects existing import statements, as well as scope variable declarations. ## Installation diff --git a/packages/babel-plugin-import-jsx-pragma/src/index.js b/packages/babel-plugin-import-jsx-pragma/src/index.js index 89963e67d27e8..68e94e1ffc37d 100644 --- a/packages/babel-plugin-import-jsx-pragma/src/index.js +++ b/packages/babel-plugin-import-jsx-pragma/src/index.js @@ -44,6 +44,13 @@ export default function( babel ) { visitor: { JSXElement( path, state ) { state.hasJSX = true; + if ( state.hasUndeclaredScopeVariable ) { + return; + } + + const { scopeVariable } = getOptions( state ); + + state.hasUndeclaredScopeVariable = ! path.scope.hasBinding( scopeVariable ); }, ImportDeclaration( path, state ) { if ( state.hasImportedScopeVariable ) { @@ -71,7 +78,7 @@ export default function( babel ) { }, Program: { exit( path, state ) { - if ( ! state.hasJSX || state.hasImportedScopeVariable ) { + if ( ! state.hasJSX || state.hasImportedScopeVariable || ! state.hasUndeclaredScopeVariable ) { return; } diff --git a/packages/babel-plugin-import-jsx-pragma/test/index.js b/packages/babel-plugin-import-jsx-pragma/test/index.js index 800b75e9727d6..d70cf2313c540 100644 --- a/packages/babel-plugin-import-jsx-pragma/test/index.js +++ b/packages/babel-plugin-import-jsx-pragma/test/index.js @@ -35,11 +35,18 @@ describe( 'babel-plugin-import-jsx-pragma', () => { expect( string ).toBe( original ); } ); + it( 'does nothing if the scope variable is already defined', () => { + const original = 'const React = require("react");\n\nlet foo = <bar />;'; + const string = getTransformedCode( original ); + + expect( string ).toBe( original ); + } ); + it( 'adds import for scope variable', () => { const original = 'let foo = <bar />;'; const string = getTransformedCode( original ); - expect( string ).toBe( 'import React from "react";\nlet foo = <bar />;' ); + expect( string ).toBe( 'import React from "react";\n' + original ); } ); it( 'allows options customization', () => { @@ -50,6 +57,35 @@ describe( 'babel-plugin-import-jsx-pragma', () => { isDefault: false, } ); - expect( string ).toBe( 'import { createElement } from "@wordpress/element";\nlet foo = <bar />;' ); + expect( string ).toBe( 'import { createElement } from "@wordpress/element";\n' + original ); + } ); + + it( 'adds import for scope variable even when defined inside the local scope', () => { + const original = 'let foo = <bar />;\n\nfunction local() {\n const createElement = wp.element.createElement;\n}'; + const string = getTransformedCode( original, { + scopeVariable: 'createElement', + source: '@wordpress/element', + isDefault: false, + } ); + + expect( string ).toBe( 'import { createElement } from "@wordpress/element";\n' + original ); + } ); + + it( 'does nothing if the outer scope variable is already defined when using custom options', () => { + const original = 'const {\n createElement\n} = wp.element;\nlet foo = <bar />;'; + const string = getTransformedCode( original, { + scopeVariable: 'createElement', + } ); + + expect( string ).toBe( original ); + } ); + + it( 'does nothing if the inner scope variable is already defined when using custom options', () => { + const original = '(function () {\n const {\n createElement\n } = wp.element;\n let foo = <bar />;\n})();'; + const string = getTransformedCode( original, { + scopeVariable: 'createElement', + } ); + + expect( string ).toBe( original ); } ); } ); From 3feaa48e4bdb18a5368bd6322bb91a3b873b251f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Sun, 24 Feb 2019 16:24:45 +0100 Subject: [PATCH 507/691] Upgrade Jest to version 24 (breaking changes) (#13922) * Upgrade Jest to version 24 (breaking changes) * Update changelog files to list all dependencies upgraded * Downgrade puppeteer to the previous version 1.6.1 * Try to fix failing e2e tests setup * Added clarification in the changelog * Testing: Remove expect-puppeteer import reference * Address issues raised during code review --- package-lock.json | 4060 ++++++++--------- package.json | 2 +- packages/babel-preset-default/CHANGELOG.md | 6 + packages/babel-preset-default/package.json | 3 +- packages/babel-preset-default/test/index.js | 2 +- .../components/src/slot-fill/test/index.js | 4 +- packages/e2e-test-utils/package.json | 2 +- .../e2e-tests/config/setup-test-framework.js | 1 - packages/e2e-tests/jest.config.js | 9 +- packages/e2e-tests/package.json | 3 +- .../src/components/block-edit/test/edit.js | 7 +- .../test/__snapshots__/index.js.snap | 7 +- .../components/page-attributes/test/check.js | 6 +- packages/jest-console/CHANGELOG.md | 8 +- packages/jest-console/README.md | 12 +- packages/jest-console/package.json | 4 +- packages/jest-preset-default/CHANGELOG.md | 11 + packages/jest-preset-default/README.md | 16 +- packages/jest-preset-default/jest-preset.json | 9 +- packages/jest-preset-default/package.json | 10 +- .../scripts/setup-test-framework.js | 3 - packages/jest-puppeteer-axe/README.md | 12 +- packages/jest-puppeteer-axe/package.json | 4 +- packages/scripts/CHANGELOG.md | 2 + packages/scripts/config/jest-e2e.config.js | 2 +- packages/scripts/config/jest-unit.config.js | 2 +- packages/scripts/package.json | 4 +- 27 files changed, 2048 insertions(+), 2163 deletions(-) diff --git a/package-lock.json b/package-lock.json index a5737be8fefbc..4c9b3ccb412e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2521,8 +2521,7 @@ "@babel/plugin-transform-runtime": "^7.2.0", "@babel/preset-env": "^7.3.1", "@babel/runtime": "^7.3.1", - "@wordpress/browserslist-config": "file:packages/browserslist-config", - "babel-core": "^7.0.0-bridge.0" + "@wordpress/browserslist-config": "file:packages/browserslist-config" } }, "@wordpress/blob": { @@ -2733,7 +2732,6 @@ "@wordpress/e2e-test-utils": "file:packages/e2e-test-utils", "@wordpress/jest-console": "file:packages/jest-console", "@wordpress/scripts": "file:packages/scripts", - "expect-puppeteer": "^3.2.0", "lodash": "^4.17.11" } }, @@ -2886,7 +2884,7 @@ "dev": true, "requires": { "@babel/runtime": "^7.3.1", - "jest-matcher-utils": "^23.6.0", + "jest-matcher-utils": "^24.0.0", "lodash": "^4.17.11" } }, @@ -2895,17 +2893,17 @@ "dev": true, "requires": { "@wordpress/jest-console": "file:packages/jest-console", - "babel-jest": "^23.6.0", - "enzyme": "^3.7.0", - "enzyme-adapter-react-16": "^1.6.0", - "jest-enzyme": "^6.0.2" + "babel-jest": "^24.1.0", + "enzyme": "^3.9.0", + "enzyme-adapter-react-16": "^1.9.1", + "enzyme-to-json": "^3.3.5" } }, "@wordpress/jest-puppeteer-axe": { "version": "file:packages/jest-puppeteer-axe", "dev": true, "requires": { - "axe-puppeteer": "^0.1.0" + "axe-puppeteer": "^1.0.0" } }, "@wordpress/keycodes": { @@ -3021,8 +3019,8 @@ "check-node-version": "^3.1.1", "cross-spawn": "^5.1.0", "eslint": "^5.12.1", - "jest": "^23.6.0", - "jest-puppeteer": "3.2.1", + "jest": "^24.1.0", + "jest-puppeteer": "^4.0.0", "npm-package-json-lint": "^3.3.1", "puppeteer": "1.6.1", "read-pkg-up": "^1.0.1", @@ -3260,12 +3258,12 @@ } }, "append-transform": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-0.4.0.tgz", - "integrity": "sha1-126/jKlNJ24keja61EpLdKthGZE=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", + "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", "dev": true, "requires": { - "default-require-extensions": "^1.0.0" + "default-require-extensions": "^2.0.0" } }, "aproba": { @@ -3331,6 +3329,12 @@ "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", "dev": true }, + "array-filter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", + "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=", + "dev": true + }, "array-find-index": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", @@ -3574,79 +3578,14 @@ "dev": true }, "axe-puppeteer": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/axe-puppeteer/-/axe-puppeteer-0.1.0.tgz", - "integrity": "sha512-9pWYjivWC2lSvTCCUCgTc/62S8UKKkPFb0gYg0zYVcTsyRcIab1o0YoQYYLsI00QVES29x2VrBoid0ROPKk/RQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/axe-puppeteer/-/axe-puppeteer-1.0.0.tgz", + "integrity": "sha512-hTF3u4mtatgTN7fsLVyVgbRdNc15ngjDcTEuqhn9A7ugqLhLCryJWp9fzqZkNlrW8awPcxugyTwLPR7mRdPZmA==", "dev": true, "requires": { "axe-core": "^3.1.2" } }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "babel-core": { - "version": "7.0.0-bridge.0", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", - "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", - "dev": true - }, "babel-eslint": { "version": "8.0.3", "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-8.0.3.tgz", @@ -3750,50 +3689,189 @@ } } }, - "babel-generator": { - "version": "6.26.1", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", - "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", + "babel-jest": { + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-24.1.0.tgz", + "integrity": "sha512-MLcagnVrO9ybQGLEfZUqnOzv36iQzU7Bj4elm39vCukumLVSfoX+tRy3/jW7lUKc7XdpRmB/jech6L/UCsSZjw==", "dev": true, "requires": { - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "detect-indent": "^4.0.0", - "jsesc": "^1.3.0", - "lodash": "^4.17.4", - "source-map": "^0.5.7", - "trim-right": "^1.0.1" + "babel-plugin-istanbul": "^5.1.0", + "babel-preset-jest": "^24.1.0", + "chalk": "^2.4.2", + "slash": "^2.0.0" }, "dependencies": { - "jsesc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", - "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", + "babel-plugin-istanbul": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.1.1.tgz", + "integrity": "sha512-RNNVv2lsHAXJQsEJ5jonQwrJVWK8AcZpG1oxhnjCUaAjL7xahYLANhPUZbzEQHjKy1NMYUwn+0NPKQc8iSY4xQ==", + "dev": true, + "requires": { + "find-up": "^3.0.0", + "istanbul-lib-instrument": "^3.0.0", + "test-exclude": "^5.0.0" + } + }, + "babel-plugin-jest-hoist": { + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.1.0.tgz", + "integrity": "sha512-gljYrZz8w1b6fJzKcsfKsipSru2DU2DmQ39aB6nV3xQ0DDv3zpIzKGortA5gknrhNnPN8DweaEgrnZdmbGmhnw==", + "dev": true + }, + "babel-preset-jest": { + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-24.1.0.tgz", + "integrity": "sha512-FfNLDxFWsNX9lUmtwY7NheGlANnagvxq8LZdl5PKnVG3umP+S/g0XbVBfwtA4Ai3Ri/IMkWabBz3Tyk9wdspcw==", + "dev": true, + "requires": { + "@babel/plugin-syntax-object-rest-spread": "^7.0.0", + "babel-plugin-jest-hoist": "^24.1.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "istanbul-lib-instrument": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.1.0.tgz", + "integrity": "sha512-ooVllVGT38HIk8MxDj/OIHXSYvH+1tq/Vb38s8ixt9GoJadXska4WkGY+0wkmtYCZNYtaARniH/DixUGGLZ0uA==", + "dev": true, + "requires": { + "@babel/generator": "^7.0.0", + "@babel/parser": "^7.0.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0", + "istanbul-lib-coverage": "^2.0.3", + "semver": "^5.5.0" + } + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", + "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "dev": true + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", + "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "dev": true, + "requires": { + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" + } + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", "dev": true + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "test-exclude": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.1.0.tgz", + "integrity": "sha512-gwf0S2fFsANC55fSeSqpb8BYk6w3FDvwZxfNjeF6FRgvFa43r+7wRiA/Q0IxoRU37wB/LE8IQ4221BsNucTaCA==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "minimatch": "^3.0.4", + "read-pkg-up": "^4.0.0", + "require-main-filename": "^1.0.1" + } } } }, - "babel-helpers": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", - "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, - "babel-jest": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-23.6.0.tgz", - "integrity": "sha512-lqKGG6LYXYu+DQh/slrQ8nxXQkEkhugdXsU6St7GmhVS7Ilc/22ArwqXNJrf0QaOBjZB0360qZMwXqDYQHXaew==", - "dev": true, - "requires": { - "babel-plugin-istanbul": "^4.1.6", - "babel-preset-jest": "^23.2.0" - } - }, "babel-loader": { "version": "8.0.5", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.5.tgz", @@ -3871,227 +3949,40 @@ } } }, - "babel-messages": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", - "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-istanbul": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz", - "integrity": "sha512-PWP9FQ1AhZhS01T/4qLSKoHGY/xvkZdVBGlKM/HuxxS3+sC66HhTNR7+MpbO/so/cz/wY94MeSWJuP1hXIPfwQ==", - "dev": true, - "requires": { - "babel-plugin-syntax-object-rest-spread": "^6.13.0", - "find-up": "^2.1.0", - "istanbul-lib-instrument": "^1.10.1", - "test-exclude": "^4.2.1" - } - }, - "babel-plugin-jest-hoist": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-23.2.0.tgz", - "integrity": "sha1-5h+uBaHKiAGq3uV6bWa4zvr0QWc=", + "bail": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.3.tgz", + "integrity": "sha512-1X8CnjFVQ+a+KW36uBNMTU5s8+v5FzeqrP7hTG5aTb4aPreSbZJlhwPon9VKMuEVgV++JM+SQrALY3kr7eswdg==", "dev": true }, - "babel-plugin-syntax-object-rest-spread": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", - "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=", + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, - "babel-preset-jest": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-23.2.0.tgz", - "integrity": "sha1-jsegOhOPABoaj7HoETZSvxpV2kY=", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^23.2.0", - "babel-plugin-syntax-object-rest-spread": "^6.13.0" - } - }, - "babel-register": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", - "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", "dev": true, "requires": { - "babel-core": "^6.26.0", - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "home-or-tmp": "^2.0.0", - "lodash": "^4.17.4", - "mkdirp": "^0.5.1", - "source-map-support": "^0.4.15" + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" }, "dependencies": { - "babel-core": { - "version": "6.26.3", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", - "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-generator": "^6.26.0", - "babel-helpers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-register": "^6.26.0", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "convert-source-map": "^1.5.1", - "debug": "^2.6.9", - "json5": "^0.5.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.4", - "path-is-absolute": "^1.0.1", - "private": "^0.1.8", - "slash": "^1.0.0", - "source-map": "^0.5.7" - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - }, - "dependencies": { - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - } - } - }, - "babel-template": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", - "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "lodash": "^4.17.4" - } - }, - "babel-traverse": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", - "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "debug": "^2.6.8", - "globals": "^9.18.0", - "invariant": "^2.2.2", - "lodash": "^4.17.4" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - } - } - }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" - }, - "dependencies": { - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", - "dev": true - } - } - }, - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true - }, - "bail": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.3.tgz", - "integrity": "sha512-1X8CnjFVQ+a+KW36uBNMTU5s8+v5FzeqrP7hTG5aTb4aPreSbZJlhwPon9VKMuEVgV++JM+SQrALY3kr7eswdg==", - "dev": true - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" } }, "is-accessor-descriptor": { @@ -4626,9 +4517,9 @@ "dev": true }, "callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.0.0.tgz", + "integrity": "sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw==", "dev": true }, "camelcase": { @@ -5435,12 +5326,6 @@ "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", "dev": true }, - "circular-json-es6": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/circular-json-es6/-/circular-json-es6-2.0.2.tgz", - "integrity": "sha512-ODYONMMNb3p658Zv+Pp+/XPa5s6q7afhz3Tzyvo+VRh9WIrJ64J76ZC4GQxnlye/NesTn09jvOiuE8+xxfpwhQ==", - "dev": true - }, "clap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/clap/-/clap-1.2.3.tgz", @@ -5834,6 +5719,12 @@ } } }, + "compare-versions": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.4.0.tgz", + "integrity": "sha512-tK69D7oNXXqUW3ZNo/z7NXTEz22TCF0pTE+YF9cxvaAM9XnkLo1fV621xCLrRR6aevJlKxExkss0vWqUCUpqdg==", + "dev": true + }, "component-emitter": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", @@ -7019,15 +6910,6 @@ "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", "dev": true }, - "deep-equal-ident": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/deep-equal-ident/-/deep-equal-ident-1.1.1.tgz", - "integrity": "sha1-BvS4nlNxDNbOpKd4HHqVZkLejck=", - "dev": true, - "requires": { - "lodash.isequal": "^3.0" - } - }, "deep-freeze": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/deep-freeze/-/deep-freeze-0.0.1.tgz", @@ -7046,12 +6928,20 @@ "integrity": "sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ==" }, "default-require-extensions": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-1.0.0.tgz", - "integrity": "sha1-836hXT4T/9m0N9M+GnW1+5eHTLg=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", + "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", "dev": true, "requires": { - "strip-bom": "^2.0.0" + "strip-bom": "^3.0.0" + }, + "dependencies": { + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } } }, "defaults": { @@ -7158,15 +7048,6 @@ "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", "dev": true }, - "detect-indent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", - "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", - "dev": true, - "requires": { - "repeating": "^2.0.0" - } - }, "detect-newline": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", @@ -7188,6 +7069,12 @@ "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==" }, + "diff-sequences": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.0.0.tgz", + "integrity": "sha512-46OkIuVGBBnrC0soO/4LHu5LHGHx0uhP65OVz8XOrAJpqiCB2aVIuESvjI1F9oqebuvY8lekS1pt6TN7vt7qsw==", + "dev": true + }, "diffie-hellman": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", @@ -7421,18 +7308,20 @@ "dev": true }, "enzyme": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.7.0.tgz", - "integrity": "sha512-QLWx+krGK6iDNyR1KlH5YPZqxZCQaVF6ike1eDJAOg0HvSkSCVImPsdWaNw6v+VrnK92Kg8jIOYhuOSS9sBpyg==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.9.0.tgz", + "integrity": "sha512-JqxI2BRFHbmiP7/UFqvsjxTirWoM1HfeaJrmVSZ9a1EADKkZgdPcAuISPMpoUiHlac9J4dYt81MC5BBIrbJGMg==", "dev": true, "requires": { "array.prototype.flat": "^1.2.1", "cheerio": "^1.0.0-rc.2", "function.prototype.name": "^1.1.0", "has": "^1.0.3", + "html-element-map": "^1.0.0", "is-boolean-object": "^1.0.0", "is-callable": "^1.1.4", "is-number-object": "^1.0.3", + "is-regex": "^1.0.4", "is-string": "^1.0.4", "is-subset": "^0.1.1", "lodash.escape": "^4.0.1", @@ -7445,76 +7334,105 @@ "raf": "^3.4.0", "rst-selector-parser": "^2.2.3", "string.prototype.trim": "^1.1.2" - }, - "dependencies": { - "lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", - "dev": true - } } }, "enzyme-adapter-react-16": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.6.0.tgz", - "integrity": "sha512-ay9eGFpChyUDnjTFMMJHzrb681LF3hPWJLEA7RoLFG9jSWAdAm2V50pGmFV9dYGJgh5HfdiqM+MNvle41Yf/PA==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.9.1.tgz", + "integrity": "sha512-Egzogv1y77DUxdnq/CyHxLHaNxmSSKDDSDNNB/EiAXCZVFXdFibaNy2uUuRQ1n24T2m6KH/1Rw16XDRq+1yVEg==", "dev": true, "requires": { - "enzyme-adapter-utils": "^1.8.0", + "enzyme-adapter-utils": "^1.10.0", "function.prototype.name": "^1.1.0", "object.assign": "^4.1.0", - "object.values": "^1.0.4", + "object.values": "^1.1.0", "prop-types": "^15.6.2", - "react-is": "^16.5.2", + "react-is": "^16.7.0", "react-test-renderer": "^16.0.0-0" }, "dependencies": { + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "object.values": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.0.tgz", + "integrity": "sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.12.0", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, "prop-types": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", - "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", "dev": true, "requires": { - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" } }, "react-is": { - "version": "16.6.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.6.0.tgz", - "integrity": "sha512-q8U7k0Fi7oxF1HvQgyBjPwDXeMplEsArnKt2iYhuIF86+GBbgLHdAmokL3XUFjTd7Q363OSNG55FOGUdONVn1g==", + "version": "16.8.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.2.tgz", + "integrity": "sha512-D+NxhSR2HUCjYky1q1DwpNUD44cDpUXzSmmFyC3ug1bClcU/iDNy0YNn1iwme28fn+NFhpA13IndOd42CrFb+Q==", "dev": true } } }, "enzyme-adapter-utils": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.8.1.tgz", - "integrity": "sha512-s3QB3xQAowaDS2sHhmEqrT13GJC4+n5bG015ZkLv60n9k5vhxxHTQRIneZmQ4hmdCZEBrvUJ89PG6fRI5OEeuQ==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.10.0.tgz", + "integrity": "sha512-VnIXJDYVTzKGbdW+lgK8MQmYHJquTQZiGzu/AseCZ7eHtOMAj4Rtvk8ZRopodkfPves0EXaHkXBDkVhPa3t0jA==", "dev": true, "requires": { "function.prototype.name": "^1.1.0", "object.assign": "^4.1.0", - "prop-types": "^15.6.2" + "object.fromentries": "^2.0.0", + "prop-types": "^15.6.2", + "semver": "^5.6.0" }, "dependencies": { "prop-types": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", - "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", "dev": true, "requires": { - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" } + }, + "react-is": { + "version": "16.8.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.2.tgz", + "integrity": "sha512-D+NxhSR2HUCjYky1q1DwpNUD44cDpUXzSmmFyC3ug1bClcU/iDNy0YNn1iwme28fn+NFhpA13IndOd42CrFb+Q==", + "dev": true + }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true } } }, "enzyme-to-json": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/enzyme-to-json/-/enzyme-to-json-3.3.4.tgz", - "integrity": "sha1-Z8YEDpMRgvGDQYry659DIyWKp38=", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/enzyme-to-json/-/enzyme-to-json-3.3.5.tgz", + "integrity": "sha512-DmH1wJ68HyPqKSYXdQqB33ZotwfUhwQZW3IGXaNXgR69Iodaoj8TF/D9RjLdz4pEhGq2Tx2zwNUIjBuqoZeTgA==", "dev": true, "requires": { "lodash": "^4.17.4" @@ -7920,32 +7838,6 @@ "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", "dev": true }, - "event-stream": { - "version": "3.3.4", - "resolved": "http://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", - "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", - "dev": true, - "requires": { - "duplexer": "~0.1.1", - "from": "~0", - "map-stream": "~0.1.0", - "pause-stream": "0.0.11", - "split": "0.3", - "stream-combiner": "~0.0.4", - "through": "~2.3.1" - }, - "dependencies": { - "split": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", - "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", - "dev": true, - "requires": { - "through": "2" - } - } - } - }, "events": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", @@ -8044,232 +7936,91 @@ } } }, - "expand-range": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", - "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "expect": { + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-24.1.0.tgz", + "integrity": "sha512-lVcAPhaYkQcIyMS+F8RVwzbm1jro20IG8OkvxQ6f1JfqhVZyyudCwYogQ7wnktlf14iF3ii7ArIUO/mqvrW9Gw==", "dev": true, "requires": { - "fill-range": "^2.1.0" + "ansi-styles": "^3.2.0", + "jest-get-type": "^24.0.0", + "jest-matcher-utils": "^24.0.0", + "jest-message-util": "^24.0.0", + "jest-regex-util": "^24.0.0" }, "dependencies": { - "fill-range": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", - "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", - "dev": true, - "requires": { - "is-number": "^2.1.0", - "isobject": "^2.0.0", - "randomatic": "^3.0.0", - "repeat-element": "^1.1.2", - "repeat-string": "^1.5.2" - } - }, - "is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "jest-message-util": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.0.0.tgz", + "integrity": "sha512-J9ROJIwz/IeC+eV1XSwnRK4oAwPuhmxEyYx1+K5UI+pIYwFZDSrfZaiWTdq0d2xYFw4Xiu+0KQWsdsQpgJMf3Q==", "dev": true, "requires": { - "isarray": "1.0.0" + "@babel/code-frame": "^7.0.0", + "chalk": "^2.0.1", + "micromatch": "^3.1.10", + "slash": "^2.0.0", + "stack-utils": "^1.0.1" } }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true } } }, - "expect": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-23.6.0.tgz", - "integrity": "sha512-dgSoOHgmtn/aDGRVFWclQyPDKl2CQRq0hmIEoUAuQs/2rn2NcvCWcSCovm6BLeuB/7EZuLGu2QfnR+qRt5OM4w==", + "expect-puppeteer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/expect-puppeteer/-/expect-puppeteer-4.0.0.tgz", + "integrity": "sha512-K7D6WRUWR0CtQLqf9ZMBQNkpbhQzxWkKEt9/PnVl+aWMF0xcdV7JW9AClxNprzXkrUq/ILws3H2u6dWcEEjRSQ==", + "dev": true + }, + "express": { + "version": "4.16.3", + "resolved": "http://registry.npmjs.org/express/-/express-4.16.3.tgz", + "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", "dev": true, "requires": { - "ansi-styles": "^3.2.0", - "jest-diff": "^23.6.0", - "jest-get-type": "^22.1.0", - "jest-matcher-utils": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-regex-util": "^23.3.0" + "accepts": "~1.3.5", + "array-flatten": "1.1.1", + "body-parser": "1.18.2", + "content-disposition": "0.5.2", + "content-type": "~1.0.4", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.1.1", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.3", + "qs": "6.5.1", + "range-parser": "~1.2.0", + "safe-buffer": "5.1.1", + "send": "0.16.2", + "serve-static": "1.13.2", + "setprototypeof": "1.1.0", + "statuses": "~1.4.0", + "type-is": "~1.6.16", + "utils-merge": "1.0.1", + "vary": "~1.1.2" }, "dependencies": { - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { - "arr-flatten": "^1.0.1" - } - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true - }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true, - "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" - } - }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "dev": true, - "requires": { - "is-posix-bracket": "^0.1.0" - } - }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - }, - "jest-matcher-utils": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-23.6.0.tgz", - "integrity": "sha512-rosyCHQfBcol4NsckTn01cdelzWLU9Cq7aaigDf8VwwpIRvWE/9zLgX2bON+FkEW69/0UuYslUe22SOdEf2nog==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "jest-get-type": "^22.1.0", - "pretty-format": "^23.6.0" - } - }, - "jest-message-util": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-23.4.0.tgz", - "integrity": "sha1-F2EMUJQjSVCNAaPR4L2iwHkIap8=", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0-beta.35", - "chalk": "^2.0.1", - "micromatch": "^2.3.11", - "slash": "^1.0.0", - "stack-utils": "^1.0.1" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true, - "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" - } - }, - "pretty-format": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", - "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - } - } - } - }, - "expect-puppeteer": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/expect-puppeteer/-/expect-puppeteer-3.5.1.tgz", - "integrity": "sha512-SB5JeJCXWSRcUK39fBJlCA6qnVt3BG1/M9vYZ+XYq8gY9jab9Jm4BztsrAwDTWca1L+O/7dRYrG2BPziRtjh+Q==", - "dev": true - }, - "express": { - "version": "4.16.3", - "resolved": "http://registry.npmjs.org/express/-/express-4.16.3.tgz", - "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", - "dev": true, - "requires": { - "accepts": "~1.3.5", - "array-flatten": "1.1.1", - "body-parser": "1.18.2", - "content-disposition": "0.5.2", - "content-type": "~1.0.4", - "cookie": "0.3.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.1.1", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.2", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.3", - "qs": "6.5.1", - "range-parser": "~1.2.0", - "safe-buffer": "5.1.1", - "send": "0.16.2", - "serve-static": "1.13.2", - "setprototypeof": "1.1.0", - "statuses": "~1.4.0", - "type-is": "~1.6.16", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" + "ms": "2.0.0" } }, "qs": { @@ -8572,12 +8323,6 @@ "object-assign": "^4.0.1" } }, - "filename-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", - "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", - "dev": true - }, "fileset": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", @@ -8881,12 +8626,6 @@ "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", "dev": true }, - "from": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", - "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", - "dev": true - }, "from2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", @@ -9972,25 +9711,6 @@ "path-is-absolute": "^1.0.0" } }, - "glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", - "dev": true, - "requires": { - "glob-parent": "^2.0.0", - "is-glob": "^2.0.0" - } - }, - "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", - "dev": true, - "requires": { - "is-glob": "^2.0.0" - } - }, "glob-to-regexp": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", @@ -10260,16 +9980,6 @@ "minimalistic-crypto-utils": "^1.0.1" } }, - "home-or-tmp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", - "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", - "dev": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.1" - } - }, "homedir-polyfill": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz", @@ -10314,6 +10024,15 @@ "integrity": "sha1-ZouTd26q5V696POtRkswekljYl4=", "dev": true }, + "html-element-map": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/html-element-map/-/html-element-map-1.0.0.tgz", + "integrity": "sha512-/SP6aOiM5Ai9zALvCxDubIeez0LvG3qP7R9GcRDnJEP/HBmv0A8A9K0o8+HFudcFt46+i921ANjzKsjPjb7Enw==", + "dev": true, + "requires": { + "array-filter": "^1.0.0" + } + }, "html-encoding-sniffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", @@ -10812,12 +10531,6 @@ "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", "dev": true }, - "is-dotfile": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", - "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", - "dev": true - }, "is-equal-shallow": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", @@ -10833,12 +10546,6 @@ "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", "dev": true }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, "is-finite": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", @@ -10854,20 +10561,11 @@ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, "is-generator-fn": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-1.0.0.tgz", - "integrity": "sha1-lp1J4bszKfa7fwkIm+JleLLd1Go=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.0.0.tgz", + "integrity": "sha512-elzyIdM7iKoFHzcrndIqjYomImhxrFRnGP3galODoII4TB9gI7mZ+FnlLQmmjf27SxHS2gKEeyhX5/+YRS6H9g==", "dev": true }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - }, "is-hexadecimal": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.2.tgz", @@ -10930,12 +10628,6 @@ "isobject": "^3.0.1" } }, - "is-posix-bracket": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", - "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", - "dev": true - }, "is-primitive": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", @@ -11090,630 +10782,649 @@ "dev": true }, "istanbul-api": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-1.3.7.tgz", - "integrity": "sha512-4/ApBnMVeEPG3EkSzcw25wDe4N66wxwn+KKn6b47vyek8Xb3NBAcg4xfuQbS7BqcZuTX4wxfD5lVagdggR3gyA==", - "dev": true, - "requires": { - "async": "^2.1.4", - "fileset": "^2.0.2", - "istanbul-lib-coverage": "^1.2.1", - "istanbul-lib-hook": "^1.2.2", - "istanbul-lib-instrument": "^1.10.2", - "istanbul-lib-report": "^1.1.5", - "istanbul-lib-source-maps": "^1.2.6", - "istanbul-reports": "^1.5.1", - "js-yaml": "^3.7.0", - "mkdirp": "^0.5.1", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-2.1.1.tgz", + "integrity": "sha512-kVmYrehiwyeBAk/wE71tW6emzLiHGjYIiDrc8sfyty4F8M02/lrgXSm+R1kXysmF20zArvmZXjlE/mg24TVPJw==", + "dev": true, + "requires": { + "async": "^2.6.1", + "compare-versions": "^3.2.1", + "fileset": "^2.0.3", + "istanbul-lib-coverage": "^2.0.3", + "istanbul-lib-hook": "^2.0.3", + "istanbul-lib-instrument": "^3.1.0", + "istanbul-lib-report": "^2.0.4", + "istanbul-lib-source-maps": "^3.0.2", + "istanbul-reports": "^2.1.1", + "js-yaml": "^3.12.0", + "make-dir": "^1.3.0", + "minimatch": "^3.0.4", "once": "^1.4.0" }, "dependencies": { - "istanbul-lib-coverage": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", - "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==", - "dev": true - }, "istanbul-lib-instrument": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz", - "integrity": "sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.1.0.tgz", + "integrity": "sha512-ooVllVGT38HIk8MxDj/OIHXSYvH+1tq/Vb38s8ixt9GoJadXska4WkGY+0wkmtYCZNYtaARniH/DixUGGLZ0uA==", "dev": true, "requires": { - "babel-generator": "^6.18.0", - "babel-template": "^6.16.0", - "babel-traverse": "^6.18.0", - "babel-types": "^6.18.0", - "babylon": "^6.18.0", - "istanbul-lib-coverage": "^1.2.1", - "semver": "^5.3.0" + "@babel/generator": "^7.0.0", + "@babel/parser": "^7.0.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0", + "istanbul-lib-coverage": "^2.0.3", + "semver": "^5.5.0" } } } }, "istanbul-lib-coverage": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.0.tgz", - "integrity": "sha512-GvgM/uXRwm+gLlvkWHTjDAvwynZkL9ns15calTrmhGgowlwJBbWMYzWbKqE2DT6JDP1AFXKa+Zi0EkqNCUqY0A==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", + "integrity": "sha512-dKWuzRGCs4G+67VfW9pBFFz2Jpi4vSp/k7zBcJ888ofV5Mi1g5CUML5GvMvV6u9Cjybftu+E8Cgp+k0dI1E5lw==", "dev": true }, "istanbul-lib-hook": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-1.2.2.tgz", - "integrity": "sha512-/Jmq7Y1VeHnZEQ3TL10VHyb564mn6VrQXHchON9Jf/AEcmQ3ZIiyD1BVzNOKTZf/G3gE+kiGK6SmpF9y3qGPLw==", - "dev": true, - "requires": { - "append-transform": "^0.4.0" - } - }, - "istanbul-lib-instrument": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz", - "integrity": "sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.3.tgz", + "integrity": "sha512-CLmEqwEhuCYtGcpNVJjLV1DQyVnIqavMLFHV/DP+np/g3qvdxu3gsPqYoJMXm15sN84xOlckFB3VNvRbf5yEgA==", "dev": true, "requires": { - "babel-generator": "^6.18.0", - "babel-template": "^6.16.0", - "babel-traverse": "^6.18.0", - "babel-types": "^6.18.0", - "babylon": "^6.18.0", - "istanbul-lib-coverage": "^1.2.1", - "semver": "^5.3.0" - }, - "dependencies": { - "istanbul-lib-coverage": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", - "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==", - "dev": true - } + "append-transform": "^1.0.0" } }, "istanbul-lib-report": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-1.1.5.tgz", - "integrity": "sha512-UsYfRMoi6QO/doUshYNqcKJqVmFe9w51GZz8BS3WB0lYxAllQYklka2wP9+dGZeHYaWIdcXUx8JGdbqaoXRXzw==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.4.tgz", + "integrity": "sha512-sOiLZLAWpA0+3b5w5/dq0cjm2rrNdAfHWaGhmn7XEFW6X++IV9Ohn+pnELAl9K3rfpaeBfbmH9JU5sejacdLeA==", "dev": true, "requires": { - "istanbul-lib-coverage": "^1.2.1", - "mkdirp": "^0.5.1", - "path-parse": "^1.0.5", - "supports-color": "^3.1.2" + "istanbul-lib-coverage": "^2.0.3", + "make-dir": "^1.3.0", + "supports-color": "^6.0.0" }, "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "istanbul-lib-coverage": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", - "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==", - "dev": true - }, "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", "dev": true, "requires": { - "has-flag": "^1.0.0" + "has-flag": "^3.0.0" } } } }, "istanbul-lib-source-maps": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.6.tgz", - "integrity": "sha512-TtbsY5GIHgbMsMiRw35YBHGpZ1DVFEO19vxxeiDMYaeOFOCzfnYVxvl6pOUIZR4dtPhAGpSMup8OyF8ubsaqEg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.2.tgz", + "integrity": "sha512-JX4v0CiKTGp9fZPmoxpu9YEkPbEqCqBbO3403VabKjH+NRXo72HafD5UgnjTEqHL2SAjaZK1XDuDOkn6I5QVfQ==", "dev": true, "requires": { - "debug": "^3.1.0", - "istanbul-lib-coverage": "^1.2.1", - "mkdirp": "^0.5.1", - "rimraf": "^2.6.1", - "source-map": "^0.5.3" + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.3", + "make-dir": "^1.3.0", + "rimraf": "^2.6.2", + "source-map": "^0.6.1" }, "dependencies": { - "istanbul-lib-coverage": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", - "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==", + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true } } }, "istanbul-reports": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.5.1.tgz", - "integrity": "sha512-+cfoZ0UXzWjhAdzosCPP3AN8vvef8XDkWtTfgaN+7L3YTpNYITnCaEkceo5SEYy644VkHka/P1FvkWvrG/rrJw==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.1.1.tgz", + "integrity": "sha512-FzNahnidyEPBCI0HcufJoSEoKykesRlFcSzQqjH9x0+LC8tnnE/p/90PBLu8iZTxr8yYZNyTtiAujUqyN+CIxw==", "dev": true, "requires": { - "handlebars": "^4.0.3" + "handlebars": "^4.1.0" } }, "jest": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-23.6.0.tgz", - "integrity": "sha512-lWzcd+HSiqeuxyhG+EnZds6iO3Y3ZEnMrfZq/OTGvF/C+Z4fPMCdhWTGSAiO2Oym9rbEXfwddHhh6jqrTF3+Lw==", + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-24.1.0.tgz", + "integrity": "sha512-+q91L65kypqklvlRFfXfdzUKyngQLOcwGhXQaLmVHv+d09LkNXuBuGxlofTFW42XMzu3giIcChchTsCNUjQ78A==", "dev": true, "requires": { - "import-local": "^1.0.0", - "jest-cli": "^23.6.0" + "import-local": "^2.0.0", + "jest-cli": "^24.1.0" }, "dependencies": { - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true, - "requires": { - "arr-flatten": "^1.0.1" - } + "ansi-regex": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", + "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", + "dev": true }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "callsites": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.0.0.tgz", + "integrity": "sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw==", "dev": true }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true, - "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" - } + "camelcase": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", + "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", + "dev": true }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "is-posix-bracket": "^0.1.0" + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" } }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, + "import-local": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "dev": true, + "requires": { + "pkg-dir": "^3.0.0", + "resolve-cwd": "^2.0.0" + } + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", "dev": true, "requires": { - "is-extglob": "^1.0.0" + "ci-info": "^2.0.0" + } + }, + "istanbul-lib-instrument": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.1.0.tgz", + "integrity": "sha512-ooVllVGT38HIk8MxDj/OIHXSYvH+1tq/Vb38s8ixt9GoJadXska4WkGY+0wkmtYCZNYtaARniH/DixUGGLZ0uA==", + "dev": true, + "requires": { + "@babel/generator": "^7.0.0", + "@babel/parser": "^7.0.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0", + "istanbul-lib-coverage": "^2.0.3", + "semver": "^5.5.0" } }, "jest-cli": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-23.6.0.tgz", - "integrity": "sha512-hgeD1zRUp1E1zsiyOXjEn4LzRLWdJBV//ukAHGlx6s5mfCNJTbhbHjgxnDUXA8fsKWN/HqFFF6X5XcCwC/IvYQ==", + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-24.1.0.tgz", + "integrity": "sha512-U/iyWPwOI0T1CIxVLtk/2uviOTJ/OiSWJSe8qt6X1VkbbgP+nrtLJlmT9lPBe4lK78VNFJtrJ7pttcNv/s7yCw==", "dev": true, "requires": { "ansi-escapes": "^3.0.0", "chalk": "^2.0.1", "exit": "^0.1.2", "glob": "^7.1.2", - "graceful-fs": "^4.1.11", - "import-local": "^1.0.0", - "is-ci": "^1.0.10", - "istanbul-api": "^1.3.1", - "istanbul-lib-coverage": "^1.2.0", - "istanbul-lib-instrument": "^1.10.1", - "istanbul-lib-source-maps": "^1.2.4", - "jest-changed-files": "^23.4.2", - "jest-config": "^23.6.0", - "jest-environment-jsdom": "^23.4.0", - "jest-get-type": "^22.1.0", - "jest-haste-map": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-regex-util": "^23.3.0", - "jest-resolve-dependencies": "^23.6.0", - "jest-runner": "^23.6.0", - "jest-runtime": "^23.6.0", - "jest-snapshot": "^23.6.0", - "jest-util": "^23.4.0", - "jest-validate": "^23.6.0", - "jest-watcher": "^23.4.0", - "jest-worker": "^23.2.0", - "micromatch": "^2.3.11", + "graceful-fs": "^4.1.15", + "import-local": "^2.0.0", + "is-ci": "^2.0.0", + "istanbul-api": "^2.0.8", + "istanbul-lib-coverage": "^2.0.2", + "istanbul-lib-instrument": "^3.0.1", + "istanbul-lib-source-maps": "^3.0.1", + "jest-changed-files": "^24.0.0", + "jest-config": "^24.1.0", + "jest-environment-jsdom": "^24.0.0", + "jest-get-type": "^24.0.0", + "jest-haste-map": "^24.0.0", + "jest-message-util": "^24.0.0", + "jest-regex-util": "^24.0.0", + "jest-resolve-dependencies": "^24.1.0", + "jest-runner": "^24.1.0", + "jest-runtime": "^24.1.0", + "jest-snapshot": "^24.1.0", + "jest-util": "^24.0.0", + "jest-validate": "^24.0.0", + "jest-watcher": "^24.0.0", + "jest-worker": "^24.0.0", + "micromatch": "^3.1.10", "node-notifier": "^5.2.1", - "prompts": "^0.1.9", + "p-each-series": "^1.0.0", + "pirates": "^4.0.0", + "prompts": "^2.0.1", "realpath-native": "^1.0.0", "rimraf": "^2.5.4", - "slash": "^1.0.0", + "slash": "^2.0.0", "string-length": "^2.0.0", - "strip-ansi": "^4.0.0", + "strip-ansi": "^5.0.0", "which": "^1.2.12", - "yargs": "^11.0.0" + "yargs": "^12.0.2" } }, "jest-environment-jsdom": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-23.4.0.tgz", - "integrity": "sha1-BWp5UrP+pROsYqFAosNox52eYCM=", + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-24.0.0.tgz", + "integrity": "sha512-1YNp7xtxajTRaxbylDc2pWvFnfDTH5BJJGyVzyGAKNt/lEULohwEV9zFqTgG4bXRcq7xzdd+sGFws+LxThXXOw==", "dev": true, "requires": { - "jest-mock": "^23.2.0", - "jest-util": "^23.4.0", + "jest-mock": "^24.0.0", + "jest-util": "^24.0.0", "jsdom": "^11.5.1" } }, "jest-message-util": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-23.4.0.tgz", - "integrity": "sha1-F2EMUJQjSVCNAaPR4L2iwHkIap8=", + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.0.0.tgz", + "integrity": "sha512-J9ROJIwz/IeC+eV1XSwnRK4oAwPuhmxEyYx1+K5UI+pIYwFZDSrfZaiWTdq0d2xYFw4Xiu+0KQWsdsQpgJMf3Q==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0-beta.35", + "@babel/code-frame": "^7.0.0", "chalk": "^2.0.1", - "micromatch": "^2.3.11", - "slash": "^1.0.0", + "micromatch": "^3.1.10", + "slash": "^2.0.0", "stack-utils": "^1.0.1" } }, "jest-mock": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-23.2.0.tgz", - "integrity": "sha1-rRxg8p6HGdR8JuETgJi20YsmETQ=", + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-24.0.0.tgz", + "integrity": "sha512-sQp0Hu5fcf5NZEh1U9eIW2qD0BwJZjb63Yqd98PQJFvf/zzUTBoUAwv/Dc/HFeNHIw1f3hl/48vNn+j3STaI7A==", "dev": true }, "jest-util": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-23.4.0.tgz", - "integrity": "sha1-TQY8uSe68KI4Mf9hvsLLv0l5NWE=", + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.0.0.tgz", + "integrity": "sha512-QxsALc4wguYS7cfjdQSOr5HTkmjzkHgmZvIDkcmPfl1ib8PNV8QUWLwbKefCudWS0PRKioV+VbQ0oCUPC691fQ==", "dev": true, "requires": { - "callsites": "^2.0.0", + "callsites": "^3.0.0", "chalk": "^2.0.1", - "graceful-fs": "^4.1.11", - "is-ci": "^1.0.10", - "jest-message-util": "^23.4.0", + "graceful-fs": "^4.1.15", + "is-ci": "^2.0.0", + "jest-message-util": "^24.0.0", "mkdirp": "^0.5.1", - "slash": "^1.0.0", + "slash": "^2.0.0", "source-map": "^0.6.0" } }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "invert-kv": "^2.0.0" } }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" } }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "yargs": { - "version": "11.1.0", - "resolved": "http://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz", - "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==", + "mem": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.1.0.tgz", + "integrity": "sha512-I5u6Q1x7wxO0kdOpYBB28xueHADYps5uty/zg936CiG8NTe5sJL8EjrCuLneuDW3PlMdZBGDIn8BirEVdovZvg==", "dev": true, "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.1.1", - "find-up": "^2.1.0", - "get-caller-file": "^1.0.1", - "os-locale": "^2.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^9.0.2" + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^1.0.0", + "p-is-promise": "^2.0.0" } }, - "yargs-parser": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", - "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", - "dev": true, - "requires": { - "camelcase": "^4.1.0" - } - } - } - }, - "jest-changed-files": { - "version": "23.4.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-23.4.2.tgz", - "integrity": "sha512-EyNhTAUWEfwnK0Is/09LxoqNDOn7mU7S3EHskG52djOFS/z+IT0jT3h3Ql61+dklcG7bJJitIWEMB4Sp1piHmA==", - "dev": true, - "requires": { - "throat": "^4.0.0" - } - }, - "jest-config": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-23.6.0.tgz", - "integrity": "sha512-i8V7z9BeDXab1+VNo78WM0AtWpBRXJLnkT+lyT+Slx/cbP5sZJ0+NDuLcmBE5hXAoK0aUp7vI+MOxR+R4d8SRQ==", - "dev": true, - "requires": { - "babel-core": "^6.0.0", - "babel-jest": "^23.6.0", - "chalk": "^2.0.1", - "glob": "^7.1.1", - "jest-environment-jsdom": "^23.4.0", - "jest-environment-node": "^23.4.0", - "jest-get-type": "^22.1.0", - "jest-jasmine2": "^23.6.0", - "jest-regex-util": "^23.3.0", - "jest-resolve": "^23.6.0", - "jest-util": "^23.4.0", - "jest-validate": "^23.6.0", - "micromatch": "^2.3.11", - "pretty-format": "^23.6.0" - }, - "dependencies": { - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", "dev": true, "requires": { - "arr-flatten": "^1.0.1" + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" } }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true - }, - "babel-core": { - "version": "6.26.3", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", - "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-generator": "^6.26.0", - "babel-helpers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-register": "^6.26.0", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "convert-source-map": "^1.5.1", - "debug": "^2.6.9", - "json5": "^0.5.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.4", - "path-is-absolute": "^1.0.1", - "private": "^0.1.8", - "slash": "^1.0.0", - "source-map": "^0.5.7" - } + "p-is-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.0.0.tgz", + "integrity": "sha512-pzQPhYMCAgLAKPWD2jC3Se9fEfrD9npNos0y150EeqZll7akhEgGhTW/slB6lHku8AvYGiJ+YJ5hfHKePPgFWg==", + "dev": true }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "p-limit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", + "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", "dev": true, "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" + "p-try": "^2.0.0" } }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "ms": "2.0.0" + "p-limit": "^2.0.0" } }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "dev": true, - "requires": { - "is-posix-bracket": "^0.1.0" - } + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "dev": true }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", "dev": true, "requires": { - "is-extglob": "^1.0.0" + "find-up": "^3.0.0" } }, - "jest-environment-jsdom": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-23.4.0.tgz", - "integrity": "sha1-BWp5UrP+pROsYqFAosNox52eYCM=", + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "dev": true, "requires": { - "jest-mock": "^23.2.0", - "jest-util": "^23.4.0", - "jsdom": "^11.5.1" + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } }, - "jest-message-util": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-23.4.0.tgz", - "integrity": "sha1-F2EMUJQjSVCNAaPR4L2iwHkIap8=", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0-beta.35", - "chalk": "^2.0.1", - "micromatch": "^2.3.11", - "slash": "^1.0.0", - "stack-utils": "^1.0.1" - } + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true }, - "jest-mock": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-23.2.0.tgz", - "integrity": "sha1-rRxg8p6HGdR8JuETgJi20YsmETQ=", + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, - "jest-util": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-23.4.0.tgz", - "integrity": "sha1-TQY8uSe68KI4Mf9hvsLLv0l5NWE=", + "strip-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.0.0.tgz", + "integrity": "sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow==", "dev": true, "requires": { - "callsites": "^2.0.0", - "chalk": "^2.0.1", - "graceful-fs": "^4.1.11", - "is-ci": "^1.0.10", - "jest-message-util": "^23.4.0", - "mkdirp": "^0.5.1", - "slash": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } + "ansi-regex": "^4.0.0" } }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" } }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", "dev": true, "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" } } } }, - "jest-dev-server": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/jest-dev-server/-/jest-dev-server-3.6.0.tgz", - "integrity": "sha512-UbDPDBjpD3t9hjZ6z4j1NW8+jYE1rP5jJ6qVLbWsnpPgfJDBziOhhUSspSvyCG3DW+txK8/Xtw1lwwiEponWpg==", + "jest-changed-files": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-24.0.0.tgz", + "integrity": "sha512-nnuU510R9U+UX0WNb5XFEcsrMqriSiRLeO9KWDFgPrpToaQm60prfQYpxsXigdClpvNot5bekDY440x9dNGnsQ==", "dev": true, "requires": { - "chalk": "^2.4.1", - "cwd": "^0.10.0", - "find-process": "^1.2.1", - "inquirer": "^6.2.0", - "spawnd": "^3.5.2", - "terminate": "^2.1.2", - "wait-port": "^0.2.2" + "execa": "^1.0.0", + "throat": "^4.0.0" }, "dependencies": { - "ansi-regex": { - "version": "4.0.0", + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "jest-config": { + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-24.1.0.tgz", + "integrity": "sha512-FbbRzRqtFC6eGjG5VwsbW4E5dW3zqJKLWYiZWhB0/4E5fgsMw8GODLbGSrY5t17kKOtCWb/Z7nsIThRoDpuVyg==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "babel-jest": "^24.1.0", + "chalk": "^2.0.1", + "glob": "^7.1.1", + "jest-environment-jsdom": "^24.0.0", + "jest-environment-node": "^24.0.0", + "jest-get-type": "^24.0.0", + "jest-jasmine2": "^24.1.0", + "jest-regex-util": "^24.0.0", + "jest-resolve": "^24.1.0", + "jest-util": "^24.0.0", + "jest-validate": "^24.0.0", + "micromatch": "^3.1.10", + "pretty-format": "^24.0.0", + "realpath-native": "^1.0.2" + }, + "dependencies": { + "ansi-regex": { + "version": "4.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", "dev": true }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, - "external-editor": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", - "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", + "pretty-format": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.0.0.tgz", + "integrity": "sha512-LszZaKG665djUcqg5ZQq+XzezHLKrxsA86ZABTozp+oNhkdqa+tG2dX4qa6ERl5c/sRDrAa3lHmwnvKoP+OG/g==", "dev": true, "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" + "ansi-regex": "^4.0.0", + "ansi-styles": "^3.2.0" } + } + } + }, + "jest-dev-server": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jest-dev-server/-/jest-dev-server-4.0.0.tgz", + "integrity": "sha512-tq3fHPM8BDbu/71yIxgGgZW62s1Em6rLNDce0/ff/4No093OyjUEPM8yIUaoBt4pxwwRGkaS1EZB5PzCmRLGkg==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "cwd": "^0.10.0", + "find-process": "^1.2.1", + "inquirer": "^6.2.2", + "spawnd": "^4.0.0", + "tree-kill": "^1.2.1", + "wait-port": "^0.2.2" + }, + "dependencies": { + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "ansi-regex": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", + "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, "inquirer": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.1.tgz", - "integrity": "sha512-088kl3DRT2dLU5riVMKKr1DlImd6X7smDhpXUCkJDCKvTEJeRiXh0G132HG9u5a+6Ylw9plFRY7RuTnwohYSpg==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.2.tgz", + "integrity": "sha512-Z2rREiXA6cHRR9KBOarR3WuLlFzlIfAEIiB45ll5SSadMg7WqOh1MKEjjndfuH5ewXdixWCxqnVfGOQzPeiztA==", "dev": true, "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.0", + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", "cli-cursor": "^2.1.0", "cli-width": "^2.0.0", - "external-editor": "^3.0.0", + "external-editor": "^3.0.3", "figures": "^2.0.0", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "mute-stream": "0.0.7", "run-async": "^2.2.0", - "rxjs": "^6.1.0", + "rxjs": "^6.4.0", "string-width": "^2.1.0", "strip-ansi": "^5.0.0", "through": "^2.3.6" } }, "rxjs": { - "version": "6.3.3", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", - "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", + "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", "dev": true, "requires": { "tslib": "^1.9.0" @@ -11727,178 +11438,240 @@ "requires": { "ansi-regex": "^4.0.0" } + }, + "tree-kill": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.1.tgz", + "integrity": "sha512-4hjqbObwlh2dLyW4tcz0Ymw0ggoaVDMveUB9w8kFSQScdRLo0gxO9J7WFcUBo+W3C1TLdFIEwNOWebgZZ0RH9Q==", + "dev": true } } }, "jest-diff": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-23.6.0.tgz", - "integrity": "sha512-Gz9l5Ov+X3aL5L37IT+8hoCUsof1CVYBb2QEkOupK64XyRR3h+uRpYIm97K7sY8diFxowR8pIGEdyfMKTixo3g==", + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-24.0.0.tgz", + "integrity": "sha512-XY5wMpRaTsuMoU+1/B2zQSKQ9RdE9gsLkGydx3nvApeyPijLA8GtEvIcPwISRCer+VDf9W1mStTYYq6fPt8ryA==", "dev": true, "requires": { "chalk": "^2.0.1", - "diff": "^3.2.0", - "jest-get-type": "^22.1.0", - "pretty-format": "^23.6.0" + "diff-sequences": "^24.0.0", + "jest-get-type": "^24.0.0", + "pretty-format": "^24.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", + "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", + "dev": true + }, + "pretty-format": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.0.0.tgz", + "integrity": "sha512-LszZaKG665djUcqg5ZQq+XzezHLKrxsA86ZABTozp+oNhkdqa+tG2dX4qa6ERl5c/sRDrAa3lHmwnvKoP+OG/g==", + "dev": true, + "requires": { + "ansi-regex": "^4.0.0", + "ansi-styles": "^3.2.0" + } + } } }, "jest-docblock": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-23.2.0.tgz", - "integrity": "sha1-8IXh8YVI2Z/dabICB+b9VdkTg6c=", + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-24.0.0.tgz", + "integrity": "sha512-KfAKZ4SN7CFOZpWg4i7g7MSlY0M+mq7K0aMqENaG2vHuhC9fc3vkpU/iNN9sOus7v3h3Y48uEjqz3+Gdn2iptA==", "dev": true, "requires": { "detect-newline": "^2.1.0" } }, "jest-each": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-23.6.0.tgz", - "integrity": "sha512-x7V6M/WGJo6/kLoissORuvLIeAoyo2YqLOoCDkohgJ4XOXSqOtyvr8FbInlAWS77ojBsZrafbozWoKVRdtxFCg==", + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-24.0.0.tgz", + "integrity": "sha512-gFcbY4Cu55yxExXMkjrnLXov3bWO3dbPAW7HXb31h/DNWdNc/6X8MtxGff8nh3/MjkF9DpVqnj0KsPKuPK0cpA==", "dev": true, "requires": { "chalk": "^2.0.1", - "pretty-format": "^23.6.0" - } - }, - "jest-environment-enzyme": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/jest-environment-enzyme/-/jest-environment-enzyme-6.1.2.tgz", - "integrity": "sha512-WHeBKgBYOdryuOTEoK55lJwjg7Raery1OgXHLwukI3mSYgOkm2UrCDDT+vneqVgy7F8KuRHyStfD+TC/m2b7Kg==", - "dev": true, - "requires": { - "jest-environment-jsdom": "^22.4.1" + "jest-get-type": "^24.0.0", + "jest-util": "^24.0.0", + "pretty-format": "^24.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", + "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", + "dev": true + }, + "callsites": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.0.0.tgz", + "integrity": "sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw==", + "dev": true + }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, + "jest-message-util": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.0.0.tgz", + "integrity": "sha512-J9ROJIwz/IeC+eV1XSwnRK4oAwPuhmxEyYx1+K5UI+pIYwFZDSrfZaiWTdq0d2xYFw4Xiu+0KQWsdsQpgJMf3Q==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "chalk": "^2.0.1", + "micromatch": "^3.1.10", + "slash": "^2.0.0", + "stack-utils": "^1.0.1" + } + }, + "jest-util": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.0.0.tgz", + "integrity": "sha512-QxsALc4wguYS7cfjdQSOr5HTkmjzkHgmZvIDkcmPfl1ib8PNV8QUWLwbKefCudWS0PRKioV+VbQ0oCUPC691fQ==", + "dev": true, + "requires": { + "callsites": "^3.0.0", + "chalk": "^2.0.1", + "graceful-fs": "^4.1.15", + "is-ci": "^2.0.0", + "jest-message-util": "^24.0.0", + "mkdirp": "^0.5.1", + "slash": "^2.0.0", + "source-map": "^0.6.0" + } + }, + "pretty-format": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.0.0.tgz", + "integrity": "sha512-LszZaKG665djUcqg5ZQq+XzezHLKrxsA86ZABTozp+oNhkdqa+tG2dX4qa6ERl5c/sRDrAa3lHmwnvKoP+OG/g==", + "dev": true, + "requires": { + "ansi-regex": "^4.0.0", + "ansi-styles": "^3.2.0" + } + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "jest-environment-jsdom": { - "version": "22.4.3", - "resolved": "http://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-22.4.3.tgz", - "integrity": "sha512-FviwfR+VyT3Datf13+ULjIMO5CSeajlayhhYQwpzgunswoaLIPutdbrnfUHEMyJCwvqQFaVtTmn9+Y8WCt6n1w==", + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-24.0.0.tgz", + "integrity": "sha512-1YNp7xtxajTRaxbylDc2pWvFnfDTH5BJJGyVzyGAKNt/lEULohwEV9zFqTgG4bXRcq7xzdd+sGFws+LxThXXOw==", "dev": true, "requires": { - "jest-mock": "^22.4.3", - "jest-util": "^22.4.3", + "jest-mock": "^24.0.0", + "jest-util": "^24.0.0", "jsdom": "^11.5.1" } }, "jest-environment-node": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-23.4.0.tgz", - "integrity": "sha1-V+gO0IQd6jAxZ8zozXlSHeuv3hA=", + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-24.0.0.tgz", + "integrity": "sha512-62fOFcaEdU0VLaq8JL90TqwI7hLn0cOKOl8vY2n477vRkCJRojiRRtJVRzzCcgFvs6gqU97DNqX5R0BrBP6Rxg==", "dev": true, "requires": { - "jest-mock": "^23.2.0", - "jest-util": "^23.4.0" + "jest-mock": "^24.0.0", + "jest-util": "^24.0.0" }, "dependencies": { - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true, - "requires": { - "arr-flatten": "^1.0.1" - } - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "callsites": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.0.0.tgz", + "integrity": "sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw==", "dev": true }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true, - "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" - } + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "dev": true, - "requires": { - "is-posix-bracket": "^0.1.0" - } + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", "dev": true, "requires": { - "is-extglob": "^1.0.0" + "ci-info": "^2.0.0" } }, "jest-message-util": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-23.4.0.tgz", - "integrity": "sha1-F2EMUJQjSVCNAaPR4L2iwHkIap8=", + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.0.0.tgz", + "integrity": "sha512-J9ROJIwz/IeC+eV1XSwnRK4oAwPuhmxEyYx1+K5UI+pIYwFZDSrfZaiWTdq0d2xYFw4Xiu+0KQWsdsQpgJMf3Q==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0-beta.35", + "@babel/code-frame": "^7.0.0", "chalk": "^2.0.1", - "micromatch": "^2.3.11", - "slash": "^1.0.0", + "micromatch": "^3.1.10", + "slash": "^2.0.0", "stack-utils": "^1.0.1" } }, "jest-mock": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-23.2.0.tgz", - "integrity": "sha1-rRxg8p6HGdR8JuETgJi20YsmETQ=", + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-24.0.0.tgz", + "integrity": "sha512-sQp0Hu5fcf5NZEh1U9eIW2qD0BwJZjb63Yqd98PQJFvf/zzUTBoUAwv/Dc/HFeNHIw1f3hl/48vNn+j3STaI7A==", "dev": true }, "jest-util": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-23.4.0.tgz", - "integrity": "sha1-TQY8uSe68KI4Mf9hvsLLv0l5NWE=", + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.0.0.tgz", + "integrity": "sha512-QxsALc4wguYS7cfjdQSOr5HTkmjzkHgmZvIDkcmPfl1ib8PNV8QUWLwbKefCudWS0PRKioV+VbQ0oCUPC691fQ==", "dev": true, "requires": { - "callsites": "^2.0.0", + "callsites": "^3.0.0", "chalk": "^2.0.1", - "graceful-fs": "^4.1.11", - "is-ci": "^1.0.10", - "jest-message-util": "^23.4.0", + "graceful-fs": "^4.1.15", + "is-ci": "^2.0.0", + "jest-message-util": "^24.0.0", "mkdirp": "^0.5.1", - "slash": "^1.0.0", + "slash": "^2.0.0", "source-map": "^0.6.0" } }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true, - "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" - } + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true }, "source-map": { "version": "0.6.1", @@ -11909,260 +11682,219 @@ } }, "jest-environment-puppeteer": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/jest-environment-puppeteer/-/jest-environment-puppeteer-3.6.0.tgz", - "integrity": "sha512-3ULqgH6f+HRu53wxP0NDFb8uZFxn2+97tK4eZicktgst/zWpSJucEpbsVVNWk4cIHrPo79rYoUfomxnui/ndAg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jest-environment-puppeteer/-/jest-environment-puppeteer-4.0.0.tgz", + "integrity": "sha512-IdbfZW1TjT1lmdPlvlHi4S+CAHuGk63fzGUpQAUeadm77saSJISDMuYS5b7kZ6lXZtc4myu53TkMWDYhh60XOA==", "dev": true, "requires": { - "chalk": "^2.4.1", + "chalk": "^2.4.2", "cwd": "^0.10.0", - "jest-dev-server": "^3.6.0", + "jest-dev-server": "^4.0.0", "merge-deep": "^3.0.2" - } - }, - "jest-enzyme": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/jest-enzyme/-/jest-enzyme-6.1.2.tgz", - "integrity": "sha512-+ds7r2ru3QkNJxelQ2tnC6d33pjUSsZHPD3v4TlnHlNMuGX3UKdxm5C46yZBvJICYBvIF+RFKBhLMM4evNM95Q==", - "dev": true, - "requires": { - "enzyme-matchers": "^6.1.2", - "enzyme-to-json": "^3.3.0", - "jest-environment-enzyme": "^6.1.2" }, "dependencies": { - "enzyme-matchers": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/enzyme-matchers/-/enzyme-matchers-6.1.2.tgz", - "integrity": "sha512-cP9p+HMOZ1ZXQ+k2H4dCkxmTZzIvpEy5zv0ZjgoBl6D0U43v+bJGH5IeWHdIovCzgJ0dVcMCKJ6lNu83lYUCAA==", + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { - "circular-json-es6": "^2.0.1", - "deep-equal-ident": "^1.1.1" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } } } }, "jest-get-type": { - "version": "22.4.3", - "resolved": "http://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", - "integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==", + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.0.0.tgz", + "integrity": "sha512-z6/Eyf6s9ZDGz7eOvl+fzpuJmN9i0KyTt1no37/dHu8galssxz5ZEgnc1KaV8R31q1khxyhB4ui/X5ZjjPk77w==", "dev": true }, "jest-haste-map": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-23.6.0.tgz", - "integrity": "sha512-uyNhMyl6dr6HaXGHp8VF7cK6KpC6G9z9LiMNsst+rJIZ8l7wY0tk8qwjPmEghczojZ2/ZhtEdIabZ0OQRJSGGg==", + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-24.0.0.tgz", + "integrity": "sha512-CcViJyUo41IQqttLxXVdI41YErkzBKbE6cS6dRAploCeutePYfUimWd3C9rQEWhX0YBOQzvNsC0O9nYxK2nnxQ==", "dev": true, "requires": { "fb-watchman": "^2.0.0", - "graceful-fs": "^4.1.11", + "graceful-fs": "^4.1.15", "invariant": "^2.2.4", - "jest-docblock": "^23.2.0", - "jest-serializer": "^23.0.1", - "jest-worker": "^23.2.0", - "micromatch": "^2.3.11", - "sane": "^2.0.0" + "jest-serializer": "^24.0.0", + "jest-util": "^24.0.0", + "jest-worker": "^24.0.0", + "micromatch": "^3.1.10", + "sane": "^3.0.0" }, "dependencies": { - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true, - "requires": { - "arr-flatten": "^1.0.1" - } - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "callsites": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.0.0.tgz", + "integrity": "sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw==", "dev": true }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true, - "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" - } + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "dev": true, - "requires": { - "is-posix-bracket": "^0.1.0" - } + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", "dev": true, "requires": { - "is-extglob": "^1.0.0" + "ci-info": "^2.0.0" } }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "jest-message-util": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.0.0.tgz", + "integrity": "sha512-J9ROJIwz/IeC+eV1XSwnRK4oAwPuhmxEyYx1+K5UI+pIYwFZDSrfZaiWTdq0d2xYFw4Xiu+0KQWsdsQpgJMf3Q==", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "@babel/code-frame": "^7.0.0", + "chalk": "^2.0.1", + "micromatch": "^3.1.10", + "slash": "^2.0.0", + "stack-utils": "^1.0.1" } }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "jest-util": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.0.0.tgz", + "integrity": "sha512-QxsALc4wguYS7cfjdQSOr5HTkmjzkHgmZvIDkcmPfl1ib8PNV8QUWLwbKefCudWS0PRKioV+VbQ0oCUPC691fQ==", "dev": true, "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" + "callsites": "^3.0.0", + "chalk": "^2.0.1", + "graceful-fs": "^4.1.15", + "is-ci": "^2.0.0", + "jest-message-util": "^24.0.0", + "mkdirp": "^0.5.1", + "slash": "^2.0.0", + "source-map": "^0.6.0" } + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true } } }, "jest-jasmine2": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-23.6.0.tgz", - "integrity": "sha512-pe2Ytgs1nyCs8IvsEJRiRTPC0eVYd8L/dXJGU08GFuBwZ4sYH/lmFDdOL3ZmvJR8QKqV9MFuwlsAi/EWkFUbsQ==", + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-24.1.0.tgz", + "integrity": "sha512-H+o76SdSNyCh9fM5K8upK45YTo/DiFx5w2YAzblQebSQmukDcoVBVeXynyr7DDnxh+0NTHYRCLwJVf3tC518wg==", "dev": true, "requires": { - "babel-traverse": "^6.0.0", + "@babel/traverse": "^7.1.0", "chalk": "^2.0.1", "co": "^4.6.0", - "expect": "^23.6.0", - "is-generator-fn": "^1.0.0", - "jest-diff": "^23.6.0", - "jest-each": "^23.6.0", - "jest-matcher-utils": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-snapshot": "^23.6.0", - "jest-util": "^23.4.0", - "pretty-format": "^23.6.0" + "expect": "^24.1.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^24.0.0", + "jest-matcher-utils": "^24.0.0", + "jest-message-util": "^24.0.0", + "jest-snapshot": "^24.1.0", + "jest-util": "^24.0.0", + "pretty-format": "^24.0.0", + "throat": "^4.0.0" }, "dependencies": { - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true, - "requires": { - "arr-flatten": "^1.0.1" - } + "ansi-regex": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", + "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", + "dev": true }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "callsites": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.0.0.tgz", + "integrity": "sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw==", "dev": true }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true, - "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" - } + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "dev": true, - "requires": { - "is-posix-bracket": "^0.1.0" - } + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", "dev": true, "requires": { - "is-extglob": "^1.0.0" + "ci-info": "^2.0.0" } }, "jest-message-util": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-23.4.0.tgz", - "integrity": "sha1-F2EMUJQjSVCNAaPR4L2iwHkIap8=", + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.0.0.tgz", + "integrity": "sha512-J9ROJIwz/IeC+eV1XSwnRK4oAwPuhmxEyYx1+K5UI+pIYwFZDSrfZaiWTdq0d2xYFw4Xiu+0KQWsdsQpgJMf3Q==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0-beta.35", + "@babel/code-frame": "^7.0.0", "chalk": "^2.0.1", - "micromatch": "^2.3.11", - "slash": "^1.0.0", + "micromatch": "^3.1.10", + "slash": "^2.0.0", "stack-utils": "^1.0.1" } }, "jest-util": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-23.4.0.tgz", - "integrity": "sha1-TQY8uSe68KI4Mf9hvsLLv0l5NWE=", + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.0.0.tgz", + "integrity": "sha512-QxsALc4wguYS7cfjdQSOr5HTkmjzkHgmZvIDkcmPfl1ib8PNV8QUWLwbKefCudWS0PRKioV+VbQ0oCUPC691fQ==", "dev": true, "requires": { - "callsites": "^2.0.0", + "callsites": "^3.0.0", "chalk": "^2.0.1", - "graceful-fs": "^4.1.11", - "is-ci": "^1.0.10", - "jest-message-util": "^23.4.0", + "graceful-fs": "^4.1.15", + "is-ci": "^2.0.0", + "jest-message-util": "^24.0.0", "mkdirp": "^0.5.1", - "slash": "^1.0.0", + "slash": "^2.0.0", "source-map": "^0.6.0" } }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "pretty-format": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.0.0.tgz", + "integrity": "sha512-LszZaKG665djUcqg5ZQq+XzezHLKrxsA86ZABTozp+oNhkdqa+tG2dX4qa6ERl5c/sRDrAa3lHmwnvKoP+OG/g==", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "ansi-regex": "^4.0.0", + "ansi-styles": "^3.2.0" } }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true, - "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" - } + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true }, "source-map": { "version": "0.6.1", @@ -12173,140 +11905,109 @@ } }, "jest-leak-detector": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-23.6.0.tgz", - "integrity": "sha512-f/8zA04rsl1Nzj10HIyEsXvYlMpMPcy0QkQilVZDFOaPbv2ur71X5u2+C4ZQJGyV/xvVXtCCZ3wQ99IgQxftCg==", + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-24.0.0.tgz", + "integrity": "sha512-ZYHJYFeibxfsDSKowjDP332pStuiFT2xfc5R67Rjm/l+HFJWJgNIOCOlQGeXLCtyUn3A23+VVDdiCcnB6dTTrg==", "dev": true, "requires": { - "pretty-format": "^23.6.0" + "pretty-format": "^24.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", + "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", + "dev": true + }, + "pretty-format": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.0.0.tgz", + "integrity": "sha512-LszZaKG665djUcqg5ZQq+XzezHLKrxsA86ZABTozp+oNhkdqa+tG2dX4qa6ERl5c/sRDrAa3lHmwnvKoP+OG/g==", + "dev": true, + "requires": { + "ansi-regex": "^4.0.0", + "ansi-styles": "^3.2.0" + } + } } }, "jest-matcher-utils": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-23.6.0.tgz", - "integrity": "sha512-rosyCHQfBcol4NsckTn01cdelzWLU9Cq7aaigDf8VwwpIRvWE/9zLgX2bON+FkEW69/0UuYslUe22SOdEf2nog==", + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-24.0.0.tgz", + "integrity": "sha512-LQTDmO+aWRz1Tf9HJg+HlPHhDh1E1c65kVwRFo5mwCVp5aQDzlkz4+vCvXhOKFjitV2f0kMdHxnODrXVoi+rlA==", "dev": true, "requires": { "chalk": "^2.0.1", - "jest-get-type": "^22.1.0", - "pretty-format": "^23.6.0" + "jest-diff": "^24.0.0", + "jest-get-type": "^24.0.0", + "pretty-format": "^24.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", + "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", + "dev": true + }, + "pretty-format": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.0.0.tgz", + "integrity": "sha512-LszZaKG665djUcqg5ZQq+XzezHLKrxsA86ZABTozp+oNhkdqa+tG2dX4qa6ERl5c/sRDrAa3lHmwnvKoP+OG/g==", + "dev": true, + "requires": { + "ansi-regex": "^4.0.0", + "ansi-styles": "^3.2.0" + } + } } }, "jest-message-util": { - "version": "22.4.3", - "resolved": "http://registry.npmjs.org/jest-message-util/-/jest-message-util-22.4.3.tgz", - "integrity": "sha512-iAMeKxhB3Se5xkSjU0NndLLCHtP4n+GtCqV0bISKA5dmOXQfEbdEmYiu2qpnWBDCQdEafNDDU6Q+l6oBMd/+BA==", + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.0.0.tgz", + "integrity": "sha512-J9ROJIwz/IeC+eV1XSwnRK4oAwPuhmxEyYx1+K5UI+pIYwFZDSrfZaiWTdq0d2xYFw4Xiu+0KQWsdsQpgJMf3Q==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0-beta.35", + "@babel/code-frame": "^7.0.0", "chalk": "^2.0.1", - "micromatch": "^2.3.11", - "slash": "^1.0.0", + "micromatch": "^3.1.10", + "slash": "^2.0.0", "stack-utils": "^1.0.1" }, "dependencies": { - "arr-diff": { + "slash": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true, - "requires": { - "arr-flatten": "^1.0.1" - } - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", "dev": true - }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true, - "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" - } - }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "dev": true, - "requires": { - "is-posix-bracket": "^0.1.0" - } - }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true, - "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" - } } } }, "jest-mock": { - "version": "22.4.3", - "resolved": "http://registry.npmjs.org/jest-mock/-/jest-mock-22.4.3.tgz", - "integrity": "sha512-+4R6mH5M1G4NK16CKg9N1DtCaFmuxhcIqF4lQK/Q1CIotqMs/XBemfpDPeVZBFow6iyUNu6EBT9ugdNOTT5o5Q==", + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-24.0.0.tgz", + "integrity": "sha512-sQp0Hu5fcf5NZEh1U9eIW2qD0BwJZjb63Yqd98PQJFvf/zzUTBoUAwv/Dc/HFeNHIw1f3hl/48vNn+j3STaI7A==", "dev": true }, "jest-puppeteer": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/jest-puppeteer/-/jest-puppeteer-3.2.1.tgz", - "integrity": "sha1-cvasvoLy/eFnjwaTEGWD7ec0560=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jest-puppeteer/-/jest-puppeteer-4.0.0.tgz", + "integrity": "sha512-B/5BlePsYmxTVmWD0E3zEuYfrTPk66EZskRlZhdDuXLoUAWPRu2+J/egnXh5GO+pUqkELffn5gfPulpn21rY7A==", "dev": true, "requires": { - "expect-puppeteer": "^3.2.0", - "jest-environment-puppeteer": "^3.2.1" + "expect-puppeteer": "^4.0.0", + "jest-environment-puppeteer": "^4.0.0" } }, "jest-regex-util": { - "version": "23.3.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-23.3.0.tgz", - "integrity": "sha1-X4ZylUfCeFxAAs6qj4Sf6MpHG8U=", + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.0.0.tgz", + "integrity": "sha512-Jv/uOTCuC+PY7WpJl2mpoI+WbY2ut73qwwO9ByJJNwOCwr1qWhEW2Lyi2S9ZewUdJqeVpEBisdEVZSI+Zxo58Q==", "dev": true }, "jest-resolve": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-23.6.0.tgz", - "integrity": "sha512-XyoRxNtO7YGpQDmtQCmZjum1MljDqUCob7XlZ6jy9gsMugHdN2hY4+Acz9Qvjz2mSsOnPSH7skBmDYCHXVZqkA==", + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-24.1.0.tgz", + "integrity": "sha512-TPiAIVp3TG6zAxH28u/6eogbwrvZjBMWroSLBDkwkHKrqxB/RIdwkWDye4uqPlZIXWIaHtifY3L0/eO5Z0f2wg==", "dev": true, "requires": { "browser-resolve": "^1.11.3", @@ -12315,498 +12016,728 @@ } }, "jest-resolve-dependencies": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-23.6.0.tgz", - "integrity": "sha512-EkQWkFWjGKwRtRyIwRwI6rtPAEyPWlUC2MpzHissYnzJeHcyCn1Hc8j7Nn1xUVrS5C6W5+ZL37XTem4D4pLZdA==", + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-24.1.0.tgz", + "integrity": "sha512-2VwPsjd3kRPu7qe2cpytAgowCObk5AKeizfXuuiwgm1a9sijJDZe8Kh1sFj6FKvSaNEfCPlBVkZEJa2482m/Uw==", "dev": true, "requires": { - "jest-regex-util": "^23.3.0", - "jest-snapshot": "^23.6.0" + "jest-regex-util": "^24.0.0", + "jest-snapshot": "^24.1.0" } }, "jest-runner": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-23.6.0.tgz", - "integrity": "sha512-kw0+uj710dzSJKU6ygri851CObtCD9cN8aNkg8jWJf4ewFyEa6kwmiH/r/M1Ec5IL/6VFa0wnAk6w+gzUtjJzA==", + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-24.1.0.tgz", + "integrity": "sha512-CDGOkT3AIFl16BLL/OdbtYgYvbAprwJ+ExKuLZmGSCSldwsuU2dEGauqkpvd9nphVdAnJUcP12e/EIlnTX0QXg==", "dev": true, "requires": { + "chalk": "^2.4.2", "exit": "^0.1.2", - "graceful-fs": "^4.1.11", - "jest-config": "^23.6.0", - "jest-docblock": "^23.2.0", - "jest-haste-map": "^23.6.0", - "jest-jasmine2": "^23.6.0", - "jest-leak-detector": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-runtime": "^23.6.0", - "jest-util": "^23.4.0", - "jest-worker": "^23.2.0", + "graceful-fs": "^4.1.15", + "jest-config": "^24.1.0", + "jest-docblock": "^24.0.0", + "jest-haste-map": "^24.0.0", + "jest-jasmine2": "^24.1.0", + "jest-leak-detector": "^24.0.0", + "jest-message-util": "^24.0.0", + "jest-runtime": "^24.1.0", + "jest-util": "^24.0.0", + "jest-worker": "^24.0.0", "source-map-support": "^0.5.6", "throat": "^4.0.0" }, "dependencies": { - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true, - "requires": { - "arr-flatten": "^1.0.1" - } - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "callsites": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.0.0.tgz", + "integrity": "sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw==", "dev": true }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "dev": true, - "requires": { - "is-posix-bracket": "^0.1.0" - } + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", "dev": true, "requires": { - "is-extglob": "^1.0.0" + "ci-info": "^2.0.0" } }, "jest-message-util": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-23.4.0.tgz", - "integrity": "sha1-F2EMUJQjSVCNAaPR4L2iwHkIap8=", + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.0.0.tgz", + "integrity": "sha512-J9ROJIwz/IeC+eV1XSwnRK4oAwPuhmxEyYx1+K5UI+pIYwFZDSrfZaiWTdq0d2xYFw4Xiu+0KQWsdsQpgJMf3Q==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0-beta.35", + "@babel/code-frame": "^7.0.0", "chalk": "^2.0.1", - "micromatch": "^2.3.11", - "slash": "^1.0.0", + "micromatch": "^3.1.10", + "slash": "^2.0.0", "stack-utils": "^1.0.1" } }, "jest-util": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-23.4.0.tgz", - "integrity": "sha1-TQY8uSe68KI4Mf9hvsLLv0l5NWE=", + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.0.0.tgz", + "integrity": "sha512-QxsALc4wguYS7cfjdQSOr5HTkmjzkHgmZvIDkcmPfl1ib8PNV8QUWLwbKefCudWS0PRKioV+VbQ0oCUPC691fQ==", "dev": true, "requires": { - "callsites": "^2.0.0", + "callsites": "^3.0.0", "chalk": "^2.0.1", - "graceful-fs": "^4.1.11", - "is-ci": "^1.0.10", - "jest-message-util": "^23.4.0", + "graceful-fs": "^4.1.15", + "is-ci": "^2.0.0", + "jest-message-util": "^24.0.0", "mkdirp": "^0.5.1", - "slash": "^1.0.0", + "slash": "^2.0.0", "source-map": "^0.6.0" } }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true, - "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" - } + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true - }, - "source-map-support": { - "version": "0.5.9", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz", - "integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } } } }, "jest-runtime": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-23.6.0.tgz", - "integrity": "sha512-ycnLTNPT2Gv+TRhnAYAQ0B3SryEXhhRj1kA6hBPSeZaNQkJ7GbZsxOLUkwg6YmvWGdX3BB3PYKFLDQCAE1zNOw==", + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-24.1.0.tgz", + "integrity": "sha512-59/BY6OCuTXxGeDhEMU7+N33dpMQyXq7MLK07cNSIY/QYt2QZgJ7Tjx+rykBI0skAoigFl0A5tmT8UdwX92YuQ==", "dev": true, "requires": { - "babel-core": "^6.0.0", - "babel-plugin-istanbul": "^4.1.6", + "@babel/core": "^7.1.0", + "babel-plugin-istanbul": "^5.1.0", "chalk": "^2.0.1", "convert-source-map": "^1.4.0", "exit": "^0.1.2", "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.1.11", - "jest-config": "^23.6.0", - "jest-haste-map": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-regex-util": "^23.3.0", - "jest-resolve": "^23.6.0", - "jest-snapshot": "^23.6.0", - "jest-util": "^23.4.0", - "jest-validate": "^23.6.0", - "micromatch": "^2.3.11", + "glob": "^7.1.3", + "graceful-fs": "^4.1.15", + "jest-config": "^24.1.0", + "jest-haste-map": "^24.0.0", + "jest-message-util": "^24.0.0", + "jest-regex-util": "^24.0.0", + "jest-resolve": "^24.1.0", + "jest-snapshot": "^24.1.0", + "jest-util": "^24.0.0", + "jest-validate": "^24.0.0", + "micromatch": "^3.1.10", "realpath-native": "^1.0.0", - "slash": "^1.0.0", - "strip-bom": "3.0.0", - "write-file-atomic": "^2.1.0", - "yargs": "^11.0.0" + "slash": "^2.0.0", + "strip-bom": "^3.0.0", + "write-file-atomic": "2.4.1", + "yargs": "^12.0.2" }, "dependencies": { - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "babel-plugin-istanbul": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.1.1.tgz", + "integrity": "sha512-RNNVv2lsHAXJQsEJ5jonQwrJVWK8AcZpG1oxhnjCUaAjL7xahYLANhPUZbzEQHjKy1NMYUwn+0NPKQc8iSY4xQ==", "dev": true, "requires": { - "arr-flatten": "^1.0.1" - } - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true - }, - "babel-core": { - "version": "6.26.3", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", - "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-generator": "^6.26.0", - "babel-helpers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-register": "^6.26.0", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "convert-source-map": "^1.5.1", - "debug": "^2.6.9", - "json5": "^0.5.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.4", - "path-is-absolute": "^1.0.1", - "private": "^0.1.8", - "slash": "^1.0.0", - "source-map": "^0.5.7" + "find-up": "^3.0.0", + "istanbul-lib-instrument": "^3.0.0", + "test-exclude": "^5.0.0" } }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true, - "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" - } + "callsites": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.0.0.tgz", + "integrity": "sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw==", + "dev": true }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "camelcase": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", + "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", + "dev": true + }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "ms": "2.0.0" + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" } }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", "dev": true, "requires": { - "is-posix-bracket": "^0.1.0" + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" } }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", "dev": true, "requires": { - "is-extglob": "^1.0.0" + "ci-info": "^2.0.0" + } + }, + "istanbul-lib-instrument": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.1.0.tgz", + "integrity": "sha512-ooVllVGT38HIk8MxDj/OIHXSYvH+1tq/Vb38s8ixt9GoJadXska4WkGY+0wkmtYCZNYtaARniH/DixUGGLZ0uA==", + "dev": true, + "requires": { + "@babel/generator": "^7.0.0", + "@babel/parser": "^7.0.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0", + "istanbul-lib-coverage": "^2.0.3", + "semver": "^5.5.0" } }, "jest-message-util": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-23.4.0.tgz", - "integrity": "sha1-F2EMUJQjSVCNAaPR4L2iwHkIap8=", + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.0.0.tgz", + "integrity": "sha512-J9ROJIwz/IeC+eV1XSwnRK4oAwPuhmxEyYx1+K5UI+pIYwFZDSrfZaiWTdq0d2xYFw4Xiu+0KQWsdsQpgJMf3Q==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0-beta.35", + "@babel/code-frame": "^7.0.0", "chalk": "^2.0.1", - "micromatch": "^2.3.11", - "slash": "^1.0.0", + "micromatch": "^3.1.10", + "slash": "^2.0.0", "stack-utils": "^1.0.1" } }, "jest-util": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-23.4.0.tgz", - "integrity": "sha1-TQY8uSe68KI4Mf9hvsLLv0l5NWE=", + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.0.0.tgz", + "integrity": "sha512-QxsALc4wguYS7cfjdQSOr5HTkmjzkHgmZvIDkcmPfl1ib8PNV8QUWLwbKefCudWS0PRKioV+VbQ0oCUPC691fQ==", "dev": true, "requires": { - "callsites": "^2.0.0", + "callsites": "^3.0.0", "chalk": "^2.0.1", - "graceful-fs": "^4.1.11", - "is-ci": "^1.0.10", - "jest-message-util": "^23.4.0", + "graceful-fs": "^4.1.15", + "is-ci": "^2.0.0", + "jest-message-util": "^24.0.0", "mkdirp": "^0.5.1", - "slash": "^1.0.0", + "slash": "^2.0.0", "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "invert-kv": "^2.0.0" } }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", "dev": true, "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "mem": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.1.0.tgz", + "integrity": "sha512-I5u6Q1x7wxO0kdOpYBB28xueHADYps5uty/zg936CiG8NTe5sJL8EjrCuLneuDW3PlMdZBGDIn8BirEVdovZvg==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^1.0.0", + "p-is-promise": "^2.0.0" + } + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "p-is-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.0.0.tgz", + "integrity": "sha512-pzQPhYMCAgLAKPWD2jC3Se9fEfrD9npNos0y150EeqZll7akhEgGhTW/slB6lHku8AvYGiJ+YJ5hfHKePPgFWg==", + "dev": true + }, + "p-limit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", + "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "dev": true + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" } }, + "read-pkg-up": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", + "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "dev": true, + "requires": { + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" + } + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", "dev": true }, + "test-exclude": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.1.0.tgz", + "integrity": "sha512-gwf0S2fFsANC55fSeSqpb8BYk6w3FDvwZxfNjeF6FRgvFa43r+7wRiA/Q0IxoRU37wB/LE8IQ4221BsNucTaCA==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "minimatch": "^3.0.4", + "read-pkg-up": "^4.0.0", + "require-main-filename": "^1.0.1" + } + }, + "write-file-atomic": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.1.tgz", + "integrity": "sha512-TGHFeZEZMnv+gBFRfjAcxL5bPHrsGKtnb4qsFAws7/vlh+QfwAaySIw4AXP9ZskTTh5GWu3FLuJhsWVdiJPGvg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, "yargs": { - "version": "11.1.0", - "resolved": "http://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz", - "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==", + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", "dev": true, "requires": { "cliui": "^4.0.0", - "decamelize": "^1.1.1", - "find-up": "^2.1.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", "get-caller-file": "^1.0.1", - "os-locale": "^2.0.0", + "os-locale": "^3.0.0", "require-directory": "^2.1.1", "require-main-filename": "^1.0.1", "set-blocking": "^2.0.0", "string-width": "^2.0.0", "which-module": "^2.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^9.0.2" + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" } }, "yargs-parser": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", - "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", "dev": true, "requires": { - "camelcase": "^4.1.0" + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" } } } }, "jest-serializer": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-23.0.1.tgz", - "integrity": "sha1-o3dq6zEekP6D+rnlM+hRAr0WQWU=", + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-24.0.0.tgz", + "integrity": "sha512-9FKxQyrFgHtx3ozU+1a8v938ILBE7S8Ko3uiAVjT8Yfi2o91j/fj81jacCQZ/Ihjiff/VsUCXVgQ+iF1XdImOw==", "dev": true }, "jest-snapshot": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-23.6.0.tgz", - "integrity": "sha512-tM7/Bprftun6Cvj2Awh/ikS7zV3pVwjRYU2qNYS51VZHgaAMBs5l4o/69AiDHhQrj5+LA2Lq4VIvK7zYk/bswg==", + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-24.1.0.tgz", + "integrity": "sha512-th6TDfFqEmXvuViacU1ikD7xFb7lQsPn2rJl7OEmnfIVpnrx3QNY2t3PE88meeg0u/mQ0nkyvmC05PBqO4USFA==", "dev": true, "requires": { - "babel-types": "^6.0.0", + "@babel/types": "^7.0.0", "chalk": "^2.0.1", - "jest-diff": "^23.6.0", - "jest-matcher-utils": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-resolve": "^23.6.0", + "jest-diff": "^24.0.0", + "jest-matcher-utils": "^24.0.0", + "jest-message-util": "^24.0.0", + "jest-resolve": "^24.1.0", "mkdirp": "^0.5.1", "natural-compare": "^1.4.0", - "pretty-format": "^23.6.0", + "pretty-format": "^24.0.0", "semver": "^5.5.0" }, "dependencies": { - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "ansi-regex": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", + "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", + "dev": true + }, + "jest-message-util": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.0.0.tgz", + "integrity": "sha512-J9ROJIwz/IeC+eV1XSwnRK4oAwPuhmxEyYx1+K5UI+pIYwFZDSrfZaiWTdq0d2xYFw4Xiu+0KQWsdsQpgJMf3Q==", "dev": true, "requires": { - "arr-flatten": "^1.0.1" + "@babel/code-frame": "^7.0.0", + "chalk": "^2.0.1", + "micromatch": "^3.1.10", + "slash": "^2.0.0", + "stack-utils": "^1.0.1" } }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "pretty-format": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.0.0.tgz", + "integrity": "sha512-LszZaKG665djUcqg5ZQq+XzezHLKrxsA86ZABTozp+oNhkdqa+tG2dX4qa6ERl5c/sRDrAa3lHmwnvKoP+OG/g==", + "dev": true, + "requires": { + "ansi-regex": "^4.0.0", + "ansi-styles": "^3.2.0" + } + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + } + } + }, + "jest-util": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.0.0.tgz", + "integrity": "sha512-QxsALc4wguYS7cfjdQSOr5HTkmjzkHgmZvIDkcmPfl1ib8PNV8QUWLwbKefCudWS0PRKioV+VbQ0oCUPC691fQ==", + "dev": true, + "requires": { + "callsites": "^3.0.0", + "chalk": "^2.0.1", + "graceful-fs": "^4.1.15", + "is-ci": "^2.0.0", + "jest-message-util": "^24.0.0", + "mkdirp": "^0.5.1", + "slash": "^2.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", "dev": true }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", "dev": true, "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" + "ci-info": "^2.0.0" } }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "jest-validate": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-24.0.0.tgz", + "integrity": "sha512-vMrKrTOP4BBFIeOWsjpsDgVXATxCspC9S1gqvbJ3Tnn/b9ACsJmteYeVx9830UMV28Cob1RX55x96Qq3Tfad4g==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "chalk": "^2.0.1", + "jest-get-type": "^24.0.0", + "leven": "^2.1.0", + "pretty-format": "^24.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", + "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", + "dev": true + }, + "camelcase": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", + "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", + "dev": true + }, + "pretty-format": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.0.0.tgz", + "integrity": "sha512-LszZaKG665djUcqg5ZQq+XzezHLKrxsA86ZABTozp+oNhkdqa+tG2dX4qa6ERl5c/sRDrAa3lHmwnvKoP+OG/g==", "dev": true, "requires": { - "is-posix-bracket": "^0.1.0" + "ansi-regex": "^4.0.0", + "ansi-styles": "^3.2.0" } + } + } + }, + "jest-watcher": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-24.0.0.tgz", + "integrity": "sha512-GxkW2QrZ4YxmW1GUWER05McjVDunBlKMFfExu+VsGmXJmpej1saTEKvONdx5RJBlVdpPI5x6E3+EDQSIGgl53g==", + "dev": true, + "requires": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.1", + "jest-util": "^24.0.0", + "string-length": "^2.0.0" + }, + "dependencies": { + "callsites": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.0.0.tgz", + "integrity": "sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw==", + "dev": true }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", "dev": true, "requires": { - "is-extglob": "^1.0.0" + "ci-info": "^2.0.0" } }, "jest-message-util": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-23.4.0.tgz", - "integrity": "sha1-F2EMUJQjSVCNAaPR4L2iwHkIap8=", + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.0.0.tgz", + "integrity": "sha512-J9ROJIwz/IeC+eV1XSwnRK4oAwPuhmxEyYx1+K5UI+pIYwFZDSrfZaiWTdq0d2xYFw4Xiu+0KQWsdsQpgJMf3Q==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0-beta.35", + "@babel/code-frame": "^7.0.0", "chalk": "^2.0.1", - "micromatch": "^2.3.11", - "slash": "^1.0.0", + "micromatch": "^3.1.10", + "slash": "^2.0.0", "stack-utils": "^1.0.1" } }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "jest-util": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.0.0.tgz", + "integrity": "sha512-QxsALc4wguYS7cfjdQSOr5HTkmjzkHgmZvIDkcmPfl1ib8PNV8QUWLwbKefCudWS0PRKioV+VbQ0oCUPC691fQ==", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "callsites": "^3.0.0", + "chalk": "^2.0.1", + "graceful-fs": "^4.1.15", + "is-ci": "^2.0.0", + "jest-message-util": "^24.0.0", + "mkdirp": "^0.5.1", + "slash": "^2.0.0", + "source-map": "^0.6.0" } }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true, - "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" - } - } - } - }, - "jest-util": { - "version": "22.4.3", - "resolved": "http://registry.npmjs.org/jest-util/-/jest-util-22.4.3.tgz", - "integrity": "sha512-rfDfG8wyC5pDPNdcnAlZgwKnzHvZDu8Td2NJI/jAGKEGxJPYiE4F0ss/gSAkG4778Y23Hvbz+0GMrDJTeo7RjQ==", - "dev": true, - "requires": { - "callsites": "^2.0.0", - "chalk": "^2.0.1", - "graceful-fs": "^4.1.11", - "is-ci": "^1.0.10", - "jest-message-util": "^22.4.3", - "mkdirp": "^0.5.1", - "source-map": "^0.6.0" - }, - "dependencies": { + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -12815,36 +12746,25 @@ } } }, - "jest-validate": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-23.6.0.tgz", - "integrity": "sha512-OFKapYxe72yz7agrDAWi8v2WL8GIfVqcbKRCLbRG9PAxtzF9b1SEDdTpytNDN12z2fJynoBwpMpvj2R39plI2A==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "jest-get-type": "^22.1.0", - "leven": "^2.1.0", - "pretty-format": "^23.6.0" - } - }, - "jest-watcher": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-23.4.0.tgz", - "integrity": "sha1-0uKM50+NrWxq/JIrksq+9u0FyRw=", - "dev": true, - "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.1", - "string-length": "^2.0.0" - } - }, "jest-worker": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-23.2.0.tgz", - "integrity": "sha1-+vcGqNo2+uYOsmlXJX+ntdjqArk=", + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.0.0.tgz", + "integrity": "sha512-s64/OThpfQvoCeHG963MiEZOAAxu8kHsaL/rCMF7lpdzo7vgF0CtPml9hfguOMgykgH/eOm4jFP4ibfHLruytg==", "dev": true, "requires": { - "merge-stream": "^1.0.1" + "merge-stream": "^1.0.1", + "supports-color": "^6.1.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "js-base64": { @@ -13046,9 +12966,9 @@ "dev": true }, "kleur": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-2.0.2.tgz", - "integrity": "sha512-77XF9iTllATmG9lSlIv0qdQ2BQ/h9t0bJllHlbvsQ0zUWfU7Yi0S8L5JXzPZgkefIiajLmBJJ4BsMJmqcf7oxQ==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.2.tgz", + "integrity": "sha512-3h7B2WRT5LNXOtQiAaWonilegHcPSf9nLVXlSTci8lu1dZUuui61+EsPEZqSVxY7rXYmB2DVKMQILxaO5WL61Q==", "dev": true }, "lazy-cache": { @@ -13315,6 +13235,24 @@ "symbol-observable": "^1.1.0" } }, + "jest-get-type": { + "version": "22.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", + "integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==", + "dev": true + }, + "jest-validate": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-23.6.0.tgz", + "integrity": "sha512-OFKapYxe72yz7agrDAWi8v2WL8GIfVqcbKRCLbRG9PAxtzF9b1SEDdTpytNDN12z2fJynoBwpMpvj2R39plI2A==", + "dev": true, + "requires": { + "chalk": "^2.0.1", + "jest-get-type": "^22.1.0", + "leven": "^2.1.0", + "pretty-format": "^23.6.0" + } + }, "listr": { "version": "0.14.3", "resolved": "https://registry.npmjs.org/listr/-/listr-0.14.3.tgz", @@ -13520,29 +13458,6 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" }, - "lodash._baseisequal": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/lodash._baseisequal/-/lodash._baseisequal-3.0.7.tgz", - "integrity": "sha1-2AJfdjOdKTQnZ9zIh85cuVpbUfE=", - "dev": true, - "requires": { - "lodash.isarray": "^3.0.0", - "lodash.istypedarray": "^3.0.0", - "lodash.keys": "^3.0.0" - } - }, - "lodash._bindcallback": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", - "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=", - "dev": true - }, - "lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", - "dev": true - }, "lodash._reinterpolate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", @@ -13579,45 +13494,12 @@ "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", "dev": true }, - "lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", - "dev": true - }, - "lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", - "dev": true - }, "lodash.isequal": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-3.0.4.tgz", - "integrity": "sha1-HDXrO27wzR/1F0Pj6jz3/f/ay2Q=", - "dev": true, - "requires": { - "lodash._baseisequal": "^3.0.0", - "lodash._bindcallback": "^3.0.0" - } - }, - "lodash.istypedarray": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/lodash.istypedarray/-/lodash.istypedarray-3.0.6.tgz", - "integrity": "sha1-yaR3SYYHUB2OhJTSg7h8OSgc72I=", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", "dev": true }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "dev": true, - "requires": { - "lodash._getnative": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" - } - }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -13925,12 +13807,6 @@ "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=", "dev": true }, - "map-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", - "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", - "dev": true - }, "map-values": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-values/-/map-values-1.0.1.tgz", @@ -13964,12 +13840,6 @@ "integrity": "sha1-3oGf282E3M2PrlnGrreWFbnSZqw=", "dev": true }, - "math-random": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.1.tgz", - "integrity": "sha1-izqsWIuKZuSXXjzepn97sylgH6w=", - "dev": true - }, "mathml-tag-names": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.0.tgz", @@ -14628,6 +14498,12 @@ } } }, + "node-modules-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", + "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", + "dev": true + }, "node-notifier": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.3.0.tgz", @@ -15245,6 +15121,18 @@ "has": "^1.0.1" } }, + "object.fromentries": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.0.tgz", + "integrity": "sha512-9iLiI6H083uiqUuvzyY6qrlmc/Gz8hLQFOcb/Ri/0xXFkSNS3ctV+CbE6yM2+AnkYfOB3dGjdzC0wrMLIhQICA==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.11.0", + "function-bind": "^1.1.1", + "has": "^1.0.1" + } + }, "object.getownpropertydescriptors": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", @@ -15255,16 +15143,6 @@ "es-abstract": "^1.5.1" } }, - "object.omit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", - "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", - "dev": true, - "requires": { - "for-own": "^0.1.4", - "is-extendable": "^0.1.1" - } - }, "object.pick": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", @@ -15410,6 +15288,15 @@ "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", "dev": true }, + "p-each-series": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-1.0.0.tgz", + "integrity": "sha1-kw89Et0fUOdDRFeiLNbwSsatf3E=", + "dev": true, + "requires": { + "p-reduce": "^1.0.0" + } + }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", @@ -15734,18 +15621,6 @@ "integrity": "sha1-nn2LslKmy2ukJZUGC3v23z28H1A=", "dev": true }, - "parse-glob": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", - "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", - "dev": true, - "requires": { - "glob-base": "^0.3.0", - "is-dotfile": "^1.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.0" - } - }, "parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", @@ -15867,15 +15742,6 @@ } } }, - "pause-stream": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", - "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", - "dev": true, - "requires": { - "through": "~2.3" - } - }, "pbkdf2": { "version": "3.0.17", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", @@ -15933,6 +15799,15 @@ "pinkie": "^2.0.0" } }, + "pirates": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", + "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", + "dev": true, + "requires": { + "node-modules-regexp": "^1.0.0" + } + }, "pkg-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", @@ -17717,12 +17592,6 @@ "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", "dev": true }, - "preserve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", - "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", - "dev": true - }, "pretty-format": { "version": "23.6.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", @@ -17782,13 +17651,13 @@ } }, "prompts": { - "version": "0.1.14", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-0.1.14.tgz", - "integrity": "sha512-rxkyiE9YH6zAz/rZpywySLKkpaj0NMVyNw1qhsubdbjjSgcayjTShDreZGlFMcGSu5sab3bAKPfFk78PB90+8w==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.0.3.tgz", + "integrity": "sha512-H8oWEoRZpybm6NV4to9/1limhttEo13xK62pNvn2JzY0MA03p7s0OjtmhXyon3uJmxiJJVSuUwEJFFssI3eBiQ==", "dev": true, "requires": { - "kleur": "^2.0.1", - "sisteransi": "^0.1.1" + "kleur": "^3.0.2", + "sisteransi": "^1.0.0" } }, "promzard": { @@ -17862,15 +17731,6 @@ "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", "dev": true }, - "ps-tree": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", - "integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==", - "dev": true, - "requires": { - "event-stream": "=3.3.4" - } - }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -18003,25 +17863,6 @@ "ret": "~0.1.10" } }, - "randomatic": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.0.0.tgz", - "integrity": "sha512-VdxFOIEY3mNO5PtSRkkle/hPJDHvQhK21oa73K4yAc9qmp6N429gAyF1gZMOTMeS0/AYzaV/2Trcef+NaIonSA==", - "dev": true, - "requires": { - "is-number": "^4.0.0", - "kind-of": "^6.0.0", - "math-random": "^1.0.1" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - } - } - }, "randombytes": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.6.tgz", @@ -18555,15 +18396,6 @@ "private": "^0.1.6" } }, - "regex-cache": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", - "dev": true, - "requires": { - "is-equal-shallow": "^0.1.3" - } - }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", @@ -19190,14 +19022,15 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sane": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/sane/-/sane-2.5.2.tgz", - "integrity": "sha1-tNwYYcIbQn6SlQej51HiosuKs/o=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/sane/-/sane-3.1.0.tgz", + "integrity": "sha512-G5GClRRxT1cELXfdAq7UKtUsv8q/ZC5k8lQGmjEm4HcAl3HzBy68iglyNCmw4+0tiXPCBZntslHlRhbnsSws+Q==", "dev": true, "requires": { "anymatch": "^2.0.0", "capture-exit": "^1.2.0", "exec-sh": "^0.2.0", + "execa": "^1.0.0", "fb-watchman": "^2.0.0", "fsevents": "^1.2.3", "micromatch": "^3.1.4", @@ -19206,11 +19039,58 @@ "watch": "~0.18.0" }, "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, "minimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } } } }, @@ -19632,9 +19512,9 @@ } }, "sisteransi": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-0.1.1.tgz", - "integrity": "sha512-PmGOd02bM9YO5ifxpw36nrNMBTptEtfRl4qUYl9SndkolplkrZZOW7PGHjrZL53QvMVj9nQ+TKqUnRsw4tJa4g==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.0.tgz", + "integrity": "sha512-N+z4pHB4AmUv0SjveWRd6q1Nj5w62m5jodv+GD8lvmbY/83T/rpbJGZOnK5T149OldDj4Db07BSv9xY4K6NTPQ==", "dev": true }, "slash": { @@ -19836,12 +19716,21 @@ } }, "source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.10.tgz", + "integrity": "sha512-YfQ3tQFTK/yzlGJuX8pTwa4tifQj4QS2Mj7UegOu8jAz59MqIiMGPXxQhVQiIMNzayuUSF/jEuVnfFF5JqybmQ==", "dev": true, "requires": { - "source-map": "^0.5.6" + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "source-map-url": { @@ -19857,15 +19746,23 @@ "dev": true }, "spawnd": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/spawnd/-/spawnd-3.5.2.tgz", - "integrity": "sha512-taf6nYLIl8b3b1RNt0YuxnIUUgHqfx+nix8Rdr2FkNG8259+Jt8YJahrPDShOPa9vCMnDPfPsefRAY/oJy+QBg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spawnd/-/spawnd-4.0.0.tgz", + "integrity": "sha512-ql3qhJnhAkvXpaqKBWOqou1rUTSQhFRaZkyOT+MTFB4xY3X+brgw6LTWV2wHuE9A6YPhrNe1cbg7S+jAYnbC0Q==", "dev": true, "requires": { "exit": "^0.1.2", "signal-exit": "^3.0.2", - "terminate": "^2.1.2", + "tree-kill": "^1.2.1", "wait-port": "^0.2.2" + }, + "dependencies": { + "tree-kill": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.1.tgz", + "integrity": "sha512-4hjqbObwlh2dLyW4tcz0Ymw0ggoaVDMveUB9w8kFSQScdRLo0gxO9J7WFcUBo+W3C1TLdFIEwNOWebgZZ0RH9Q==", + "dev": true + } } }, "spdx-correct": { @@ -20040,15 +19937,6 @@ "readable-stream": "^2.0.2" } }, - "stream-combiner": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", - "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", - "dev": true, - "requires": { - "duplexer": "~0.1.1" - } - }, "stream-each": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.2.tgz", @@ -20828,28 +20716,6 @@ } } }, - "terminate": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/terminate/-/terminate-2.1.2.tgz", - "integrity": "sha512-ltKc9MkgcRe7gzD7XSttHCF1feKM1pTkCdb58jFVWk1efPN9JIk/BHSlOaYF+hCcWoubeJQ8C8Phb0++fa6iNQ==", - "dev": true, - "requires": { - "ps-tree": "^1.1.1" - } - }, - "test-exclude": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-4.2.1.tgz", - "integrity": "sha512-qpqlP/8Zl+sosLxBcVKl9vYy26T9NPalxSzzCP/OY6K7j938ui2oKgo+kRZYfxAeIpLqpbVnsHq1tyV70E4lWQ==", - "dev": true, - "requires": { - "arrify": "^1.0.1", - "micromatch": "^3.1.8", - "object-assign": "^4.1.0", - "read-pkg-up": "^1.0.1", - "require-main-filename": "^1.0.1" - } - }, "text-extensions": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", diff --git a/package.json b/package.json index 88a28a598a7f7..63f478e6c6fa3 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "deasync": "0.1.14", "deep-freeze": "0.0.1", "doctrine": "2.1.0", - "enzyme": "3.7.0", + "enzyme": "3.9.0", "eslint-plugin-jest": "21.5.0", "espree": "3.5.4", "fbjs": "0.8.17", diff --git a/packages/babel-preset-default/CHANGELOG.md b/packages/babel-preset-default/CHANGELOG.md index dc58eaec5c971..40e47005778e4 100644 --- a/packages/babel-preset-default/CHANGELOG.md +++ b/packages/babel-preset-default/CHANGELOG.md @@ -1,3 +1,9 @@ +## 4.0.0 (Unreleased) + +## Breaking Change + +- Removed `babel-core` dependency acting as Babel 7 bridge ([#13922](https://github.com/WordPress/gutenberg/pull/13922). Ensure all references to `babel-core` are replaced with `@babel/core` . + ## 3.0.0 (2018-09-30) ## Breaking Change diff --git a/packages/babel-preset-default/package.json b/packages/babel-preset-default/package.json index ae77d77c8768b..de20169a475b6 100644 --- a/packages/babel-preset-default/package.json +++ b/packages/babel-preset-default/package.json @@ -30,8 +30,7 @@ "@babel/plugin-transform-runtime": "^7.2.0", "@babel/preset-env": "^7.3.1", "@babel/runtime": "^7.3.1", - "@wordpress/browserslist-config": "file:../browserslist-config", - "babel-core": "^7.0.0-bridge.0" + "@wordpress/browserslist-config": "file:../browserslist-config" }, "peerDependencies": { "@babel/core": "^7.0.0" diff --git a/packages/babel-preset-default/test/index.js b/packages/babel-preset-default/test/index.js index 7465ce2f73512..0be3197b9541c 100644 --- a/packages/babel-preset-default/test/index.js +++ b/packages/babel-preset-default/test/index.js @@ -3,7 +3,7 @@ */ import path from 'path'; import { readFileSync } from 'fs'; -import { transform } from 'babel-core'; +import { transform } from '@babel/core'; /** * Internal dependencies diff --git a/packages/components/src/slot-fill/test/index.js b/packages/components/src/slot-fill/test/index.js index 0b13790602c87..6bd4c3a7c9247 100644 --- a/packages/components/src/slot-fill/test/index.js +++ b/packages/components/src/slot-fill/test/index.js @@ -16,13 +16,13 @@ describe( 'createSlotFill', () => { const wrapper = shallow( <MySlotFill.Fill /> ); expect( wrapper.type() ).toBe( Fill ); - expect( wrapper ).toHaveProp( 'name', SLOT_NAME ); + expect( wrapper.prop( 'name' ) ).toBe( SLOT_NAME ); } ); test( 'should match snapshot for Slot', () => { const wrapper = shallow( <MySlotFill.Slot /> ); expect( wrapper.type() ).toBe( Slot ); - expect( wrapper ).toHaveProp( 'name', SLOT_NAME ); + expect( wrapper.prop( 'name' ) ).toBe( SLOT_NAME ); } ); } ); diff --git a/packages/e2e-test-utils/package.json b/packages/e2e-test-utils/package.json index a297150615e24..ca3e6b12d913f 100644 --- a/packages/e2e-test-utils/package.json +++ b/packages/e2e-test-utils/package.json @@ -30,7 +30,7 @@ "node-fetch": "^1.7.3" }, "peerDependencies": { - "jest": ">=23", + "jest": ">=24", "puppeteer": ">=1.6" }, "publishConfig": { diff --git a/packages/e2e-tests/config/setup-test-framework.js b/packages/e2e-tests/config/setup-test-framework.js index 5a28430dda2fb..a3129c4d82c48 100644 --- a/packages/e2e-tests/config/setup-test-framework.js +++ b/packages/e2e-tests/config/setup-test-framework.js @@ -1,7 +1,6 @@ /** * External dependencies */ -import 'expect-puppeteer'; import { get } from 'lodash'; /** diff --git a/packages/e2e-tests/jest.config.js b/packages/e2e-tests/jest.config.js index f241715197b97..79d6518948624 100644 --- a/packages/e2e-tests/jest.config.js +++ b/packages/e2e-tests/jest.config.js @@ -1,7 +1,14 @@ module.exports = { ...require( '@wordpress/scripts/config/jest-e2e.config' ), - setupTestFrameworkScriptFile: '<rootDir>/config/setup-test-framework.js', setupFiles: [ '<rootDir>/config/gutenberg-phase.js', ], + setupFilesAfterEnv: [ + '<rootDir>/config/setup-test-framework.js', + 'expect-puppeteer', + ], + transformIgnorePatterns: [ + 'node_modules', + 'scripts/config/puppeteer.config.js', + ], }; diff --git a/packages/e2e-tests/package.json b/packages/e2e-tests/package.json index 9dad36681bc9b..a79a08fb53e16 100644 --- a/packages/e2e-tests/package.json +++ b/packages/e2e-tests/package.json @@ -21,11 +21,10 @@ "@wordpress/e2e-test-utils": "file:../e2e-test-utils", "@wordpress/jest-console": "file:../jest-console", "@wordpress/scripts": "file:../scripts", - "expect-puppeteer": "^3.2.0", "lodash": "^4.17.11" }, "peerDependencies": { - "jest": ">=23", + "jest": ">=24", "puppeteer": ">=1.6" }, "publishConfig": { diff --git a/packages/editor/src/components/block-edit/test/edit.js b/packages/editor/src/components/block-edit/test/edit.js index 0f795fdd72503..d09ce608cc14b 100644 --- a/packages/editor/src/components/block-edit/test/edit.js +++ b/packages/editor/src/components/block-edit/test/edit.js @@ -42,7 +42,7 @@ describe( 'Edit', () => { const wrapper = shallow( <Edit name="core/test-block" /> ); - expect( wrapper.find( edit ) ).toExist(); + expect( wrapper.exists( edit ) ).toBe( true ); } ); it( 'should use save implementation of block as fallback', () => { @@ -55,7 +55,7 @@ describe( 'Edit', () => { const wrapper = shallow( <Edit name="core/test-block" /> ); - expect( wrapper.find( save ) ).toExist(); + expect( wrapper.exists( save ) ).toBe( true ); } ); it( 'should combine the default class name with a custom one', () => { @@ -74,6 +74,7 @@ describe( 'Edit', () => { <Edit name="core/test-block" attributes={ attributes } /> ); - expect( wrapper.find( edit ) ).toHaveClassName( 'wp-block-test-block my-class' ); + expect( wrapper.find( edit ).hasClass( 'wp-block-test-block' ) ).toBe( true ); + expect( wrapper.find( edit ).hasClass( 'my-class' ) ).toBe( true ); } ); } ); diff --git a/packages/editor/src/components/default-block-appender/test/__snapshots__/index.js.snap b/packages/editor/src/components/default-block-appender/test/__snapshots__/index.js.snap index c542d5b5dd65e..9d6198255a377 100644 --- a/packages/editor/src/components/default-block-appender/test/__snapshots__/index.js.snap +++ b/packages/editor/src/components/default-block-appender/test/__snapshots__/index.js.snap @@ -16,7 +16,12 @@ exports[`DefaultBlockAppender should append a default block when input focused 1 "calls": Array [ Array [], ], - "results": undefined, + "results": Array [ + Object { + "type": "return", + "value": undefined, + }, + ], } } readOnly={true} diff --git a/packages/editor/src/components/page-attributes/test/check.js b/packages/editor/src/components/page-attributes/test/check.js index 3faa6405ca9d9..9519d0540b78a 100644 --- a/packages/editor/src/components/page-attributes/test/check.js +++ b/packages/editor/src/components/page-attributes/test/check.js @@ -40,18 +40,18 @@ describe( 'PageAttributesCheck', () => { it( 'should render if page attributes support is true and no available templates exist', () => { const wrapper = shallow( <PageAttributesCheck postType={ postType }>content</PageAttributesCheck> ); - expect( wrapper ).toHaveText( 'content' ); + expect( wrapper.text() ).toContain( 'content' ); } ); it( 'should render if page attributes support is false/unknown and available templates exist', () => { const wrapper = shallow( <PageAttributesCheck availableTemplates={ { 'example.php': 'Example template' } } >content</PageAttributesCheck> ); - expect( wrapper ).toHaveText( 'content' ); + expect( wrapper.text() ).toContain( 'content' ); } ); it( 'should render if page attributes support is true and available templates exist', () => { const wrapper = shallow( <PageAttributesCheck availableTemplates={ { 'example.php': 'Example template' } } postType={ postType }>content</PageAttributesCheck> ); - expect( wrapper ).toHaveText( 'content' ); + expect( wrapper.text() ).toContain( 'content' ); } ); } ); diff --git a/packages/jest-console/CHANGELOG.md b/packages/jest-console/CHANGELOG.md index 040d1ce478d1f..3561c61387c0e 100644 --- a/packages/jest-console/CHANGELOG.md +++ b/packages/jest-console/CHANGELOG.md @@ -1,3 +1,9 @@ +## 3.0.0 (Unreleased) + +### Breaking Changes + +- Increased the recommended Jest dependency to version 24 ([#13922](https://github.com/WordPress/gutenberg/pull/13922). + ## 2.0.7 (2018-11-20) ## 2.0.5 (2018-09-30) @@ -8,7 +14,7 @@ ## 2.0.0 (2018-07-12) -### Breaking Change +### Breaking Changes - Add new API methods `toHaveInformed`, `toHaveInformedWith`, `toHaveLogged` and `toHaveLoggedWith` ([#137](https://github.com/WordPress/packages/pull/137)). If the code under test calls `console.log` or `console.info` it will fail, unless one of the newly introduced methods is explicitly used to verify it. - Updated code to work with Babel 7 ([#7832](https://github.com/WordPress/gutenberg/pull/7832)) diff --git a/packages/jest-console/README.md b/packages/jest-console/README.md index 8ba0a5346a7ef..31909d570122a 100644 --- a/packages/jest-console/README.md +++ b/packages/jest-console/README.md @@ -20,20 +20,16 @@ npm install @wordpress/jest-console --save-dev ### Setup -The simplest setup is to use Jest's `setupTestFrameworkScriptFile` config option: +The simplest setup is to use Jest's `setupFilesAfterEnv` config option: ```js "jest": { - "setupTestFrameworkScriptFile": "./node_modules/@wordpress/jest-console/build/index.js" + "setupFilesAfterEnv": [ + "@wordpress/jest-console" + ] }, ``` -If your project already has a script file which sets up the test framework, you will need the following import statement: - -```js -import '@wordpress/jest-console'; -``` - ### Usage ### `.toHaveErrored()` diff --git a/packages/jest-console/package.json b/packages/jest-console/package.json index 424b590cf64ad..ae0f3902fa50f 100644 --- a/packages/jest-console/package.json +++ b/packages/jest-console/package.json @@ -26,11 +26,11 @@ "module": "build-module/index.js", "dependencies": { "@babel/runtime": "^7.3.1", - "jest-matcher-utils": "^23.6.0", + "jest-matcher-utils": "^24.0.0", "lodash": "^4.17.11" }, "peerDependencies": { - "jest": ">=22" + "jest": ">=24" }, "publishConfig": { "access": "public" diff --git a/packages/jest-preset-default/CHANGELOG.md b/packages/jest-preset-default/CHANGELOG.md index d8e7f82455649..7ff9fd2ced551 100644 --- a/packages/jest-preset-default/CHANGELOG.md +++ b/packages/jest-preset-default/CHANGELOG.md @@ -1,3 +1,14 @@ +## 4.0.0 (Unreleased) + +### Breaking Changes + +- The bundled `jest` dependency has been updated from requiring `^23.6.0` to requiring `^24.1.0` (see [Breaking Changes](https://jestjs.io/blog/2019/01/25/jest-24-refreshing-polished-typescript-friendly#breaking-changes), [#13922](https://github.com/WordPress/gutenberg/pull/13922)). +- The bundled `jest-enzyme` dependency has been removed completely ([#13922](https://github.com/WordPress/gutenberg/pull/13922)). + +### Internal + +- The bundled `enzyme` dependency has been updated from requiring `^3.7.0` to requiring `^3.9.0` ([#13922](https://github.com/WordPress/gutenberg/pull/13922)). + ## 3.0.3 (2018-11-20) ## 3.0.2 (2018-11-09) diff --git a/packages/jest-preset-default/README.md b/packages/jest-preset-default/README.md index 059bdfc9a8859..52e7df8c715fd 100644 --- a/packages/jest-preset-default/README.md +++ b/packages/jest-preset-default/README.md @@ -27,21 +27,11 @@ npm install @wordpress/jest-preset-default --save-dev * `moduleNameMapper` - all `css` and `scss` files containing CSS styles will be stubbed out. * `modulePaths` - the root dir of the project is used as a location to search when resolving modules. * `setupFiles` - runs code before each test which sets up global variables required in the testing environment. -* `setupTestFrameworkScriptFile` - runs code which adds improved support for `Console` object and `React` components to the testing framework before each test. +* `setupFilesAfterEnv` - runs code which adds improved support for `Console` object and `React` components to the testing framework before each test. +* `snapshotSerializers` - makes it possible to use snapshot tests on `Enzyme` wrappers. * `testMatch`- includes `/test/` subfolder in the glob patterns Jest uses to detect test files. It detects only test files containing `.js` extension. * `timers` - use of [fake timers](https://jestjs.io/docs/en/timer-mocks.html) for functions such as `setTimeout` is enabled. -* `transform` - adds support for [PEG.js]( https://github.com/pegjs/pegjs#javascript-api) transformed necessary for WordPress blocks. It also keeps the default [babel-jest](https://github.com/facebook/jest/tree/master/packages/babel-jest) transformer. +* `transform` - keeps the default [babel-jest](https://github.com/facebook/jest/tree/master/packages/babel-jest) transformer. * `verbose` - each individual test won't be reported during the run. -#### Overriding `setupTestFrameworkScriptFile` - -It is also possible to override the script that runs some code to configure or set up the testing framework before each test. To do so you will need to create `setup-test-framework.js` inside your project with the following content: - -```js -// You can still load the default script provided by this setup -import '@wordpress/jest-preset-default'; - -// Your code goes here -``` - <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/jest-preset-default/jest-preset.json b/packages/jest-preset-default/jest-preset.json index 3995497bfb5ba..be09fe7d6bb57 100644 --- a/packages/jest-preset-default/jest-preset.json +++ b/packages/jest-preset-default/jest-preset.json @@ -8,7 +8,12 @@ "setupFiles": [ "<rootDir>/node_modules/@wordpress/jest-preset-default/scripts/setup-globals.js" ], - "setupTestFrameworkScriptFile": "<rootDir>/node_modules/@wordpress/jest-preset-default/scripts/setup-test-framework.js", + "setupFilesAfterEnv": [ + "<rootDir>/node_modules/@wordpress/jest-preset-default/scripts/setup-test-framework.js" + ], + "snapshotSerializers": [ + "<rootDir>/node_modules/enzyme-to-json/serializer.js" + ], "testMatch": [ "**/__tests__/**/*.js", "**/?(*.)(spec|test).js", @@ -16,7 +21,7 @@ ], "timers": "fake", "transform": { - "^.+\\.jsx?$": "<rootDir>/node_modules/babel-jest" + "^.+\\.[jt]sx?$": "<rootDir>/node_modules/babel-jest" }, "verbose": true } diff --git a/packages/jest-preset-default/package.json b/packages/jest-preset-default/package.json index 29eb38c9cb823..fbabe4c53bc69 100644 --- a/packages/jest-preset-default/package.json +++ b/packages/jest-preset-default/package.json @@ -27,13 +27,13 @@ "main": "index.js", "dependencies": { "@wordpress/jest-console": "file:../jest-console", - "babel-jest": "^23.6.0", - "enzyme": "^3.7.0", - "enzyme-adapter-react-16": "^1.6.0", - "jest-enzyme": "^6.0.2" + "babel-jest": "^24.1.0", + "enzyme": "^3.9.0", + "enzyme-adapter-react-16": "^1.9.1", + "enzyme-to-json": "^3.3.5" }, "peerDependencies": { - "jest": ">=23" + "jest": ">=24" }, "publishConfig": { "access": "public" diff --git a/packages/jest-preset-default/scripts/setup-test-framework.js b/packages/jest-preset-default/scripts/setup-test-framework.js index f2374bfd4f4f7..9c026a633ff46 100644 --- a/packages/jest-preset-default/scripts/setup-test-framework.js +++ b/packages/jest-preset-default/scripts/setup-test-framework.js @@ -14,9 +14,6 @@ jest.mock( 'enzyme', () => { // configure enzyme 3 for React, from docs: http://airbnb.io/enzyme/docs/installation/index.html const Adapter = require.requireActual( 'enzyme-adapter-react-16' ); actualEnzyme.configure( { adapter: new Adapter() } ); - - // configure assertions for enzyme - require.requireActual( 'jest-enzyme' ); } return actualEnzyme; } ); diff --git a/packages/jest-puppeteer-axe/README.md b/packages/jest-puppeteer-axe/README.md index fc85725e27e9a..29fd06538ed47 100644 --- a/packages/jest-puppeteer-axe/README.md +++ b/packages/jest-puppeteer-axe/README.md @@ -14,20 +14,16 @@ npm install @wordpress/jest-puppeteer-axe --save-dev ### Setup -The simplest setup is to use Jest's `setupTestFrameworkScriptFile` config option: +The simplest setup is to use Jest's `setupFilesAfterEnv` config option: ```js "jest": { - "setupTestFrameworkScriptFile": "./node_modules/@wordpress/jest-puppeteer-axe/build/index.js" + "setupFilesAfterEnv": [ + "@wordpress/jest-puppeteer-axe" + ] }, ``` -If your project already has a script file which sets up the test framework, you will need the following import statement: - -```js -import '@wordpress/jest-puppeteer-axe'; -``` - ## Usage In your Jest test suite add the following code to the test's body: diff --git a/packages/jest-puppeteer-axe/package.json b/packages/jest-puppeteer-axe/package.json index 5006bbf5344a1..2c73930c9bca0 100644 --- a/packages/jest-puppeteer-axe/package.json +++ b/packages/jest-puppeteer-axe/package.json @@ -26,10 +26,10 @@ "main": "build/index.js", "module": "build-module/index.js", "dependencies": { - "axe-puppeteer": "^0.1.0" + "axe-puppeteer": "^1.0.0" }, "peerDependencies": { - "jest": ">=23", + "jest": ">=24", "puppeteer": ">=1.6" }, "publishConfig": { diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index fe24a86900ee0..342ada51d8374 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -3,6 +3,8 @@ ### Breaking Changes - The bundled `eslint` dependency has been updated from requiring `^4.19.1` to requiring `^5.12.1` (see [Migration Guide](https://eslint.org/docs/user-guide/migrating-to-5.0.0)). +- The bundled `jest` dependency has been updated from requiring `^23.6.0` to requiring `^24.1.0` (see [Breaking Changes](https://jestjs.io/blog/2019/01/25/jest-24-refreshing-polished-typescript-friendly#breaking-changes), [#13922](https://github.com/WordPress/gutenberg/pull/13922)). +- The bundled `jest-puppeteer` dependency has been updated from requiring `3.2.1` to requiring `^4.0.0` ([#13922](https://github.com/WordPress/gutenberg/pull/13922)). ### New Features diff --git a/packages/scripts/config/jest-e2e.config.js b/packages/scripts/config/jest-e2e.config.js index fd8d722b8c3fc..d27b8d1b8cd2b 100644 --- a/packages/scripts/config/jest-e2e.config.js +++ b/packages/scripts/config/jest-e2e.config.js @@ -19,7 +19,7 @@ const jestE2EConfig = { if ( ! hasBabelConfig() ) { jestE2EConfig.transform = { - '^.+\\.jsx?$': path.join( __dirname, 'babel-transform' ), + '^.+\\.[jt]sx?$': path.join( __dirname, 'babel-transform' ), }; } diff --git a/packages/scripts/config/jest-unit.config.js b/packages/scripts/config/jest-unit.config.js index 34f48ea0e3376..3244e163925d6 100644 --- a/packages/scripts/config/jest-unit.config.js +++ b/packages/scripts/config/jest-unit.config.js @@ -14,7 +14,7 @@ const jestUnitConfig = { if ( ! hasBabelConfig() ) { jestUnitConfig.transform = { - '^.+\\.jsx?$': path.join( __dirname, 'babel-transform' ), + '^.+\\.[jt]sx?$': path.join( __dirname, 'babel-transform' ), }; } diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 388bc0a26a3b8..3a1c92123c8b1 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -40,8 +40,8 @@ "check-node-version": "^3.1.1", "cross-spawn": "^5.1.0", "eslint": "^5.12.1", - "jest": "^23.6.0", - "jest-puppeteer": "3.2.1", + "jest": "^24.1.0", + "jest-puppeteer": "^4.0.0", "npm-package-json-lint": "^3.3.1", "puppeteer": "1.6.1", "read-pkg-up": "^1.0.1", From 8ea75bf7bf0626260ad4569dd6c99a6e1a177782 Mon Sep 17 00:00:00 2001 From: Chris Van Patten <hello@chrisvanpatten.com> Date: Sun, 24 Feb 2019 10:49:57 -0500 Subject: [PATCH 508/691] Codeowners changes for @chrisvanpatten (#14062) * Codeowners changes for @chrisvanpatten * Remove obsolete ghost placeholder for no longer onowned styles --- .github/CODEOWNERS | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index dcd01eefb79cc..945b03b7b08d8 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -40,12 +40,12 @@ /packages/scripts @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw @mkaz # UI Components -/packages/components @youknowriad @gziolo @aduth @chrisvanpatten @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks -/packages/compose @youknowriad @gziolo @aduth @chrisvanpatten @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks -/packages/element @youknowriad @gziolo @aduth @chrisvanpatten @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks -/packages/notices @youknowriad @gziolo @aduth @chrisvanpatten @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks -/packages/nux @youknowriad @gziolo @aduth @chrisvanpatten @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks -/packages/viewport @youknowriad @gziolo @aduth @chrisvanpatten @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks +/packages/components @youknowriad @gziolo @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks @chrisvanpatten +/packages/compose @youknowriad @gziolo @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks +/packages/element @youknowriad @gziolo @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks +/packages/notices @youknowriad @gziolo @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks +/packages/nux @youknowriad @gziolo @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks +/packages/viewport @youknowriad @gziolo @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks # Utilities /packages/a11y @youknowriad @gziolo @aduth @@ -79,8 +79,8 @@ # Documentation /docs @youknowriad @gziolo @chrisvanpatten @mkaz @ajitbohra @nosolosw @notnownikki -# Styles (Unowned) -*.scss @ghost +# Styles +*.scss @chrisvanpatten # Native (Unowned) *.native.js @ghost From 3397ae408a870ded1516fcea77b63cdafb72f873 Mon Sep 17 00:00:00 2001 From: Ned Zimmerman <ned@bight.ca> Date: Sun, 24 Feb 2019 11:52:13 -0400 Subject: [PATCH 509/691] Add repository.directory fields (fixes #13946) (#14059) --- packages/a11y/package.json | 3 ++- packages/annotations/package.json | 3 ++- packages/api-fetch/package.json | 3 ++- packages/autop/package.json | 3 ++- packages/babel-plugin-import-jsx-pragma/package.json | 3 ++- packages/babel-plugin-makepot/package.json | 3 ++- packages/babel-preset-default/package.json | 3 ++- packages/blob/package.json | 3 ++- packages/block-editor/package.json | 3 ++- packages/block-library/package.json | 3 ++- packages/block-serialization-default-parser/package.json | 3 ++- packages/block-serialization-spec-parser/package.json | 3 ++- packages/blocks/package.json | 3 ++- packages/browserslist-config/package.json | 3 ++- packages/components/package.json | 3 ++- packages/compose/package.json | 3 ++- packages/core-data/package.json | 3 ++- packages/custom-templated-path-webpack-plugin/package.json | 3 ++- packages/data/package.json | 3 ++- packages/date/package.json | 3 ++- packages/deprecated/package.json | 3 ++- packages/dom-ready/package.json | 3 ++- packages/dom/package.json | 3 ++- packages/e2e-test-utils/package.json | 3 ++- packages/e2e-tests/package.json | 3 ++- packages/edit-post/package.json | 3 ++- packages/edit-widgets/package.json | 3 ++- packages/editor/package.json | 3 ++- packages/element/package.json | 3 ++- packages/escape-html/package.json | 3 ++- packages/eslint-plugin/package.json | 3 ++- packages/format-library/package.json | 3 ++- packages/hooks/package.json | 3 ++- packages/html-entities/package.json | 3 ++- packages/i18n/package.json | 3 ++- packages/is-shallow-equal/package.json | 3 ++- packages/jest-console/package.json | 3 ++- packages/jest-preset-default/package.json | 3 ++- packages/jest-puppeteer-axe/package.json | 3 ++- packages/keycodes/package.json | 3 ++- packages/library-export-default-webpack-plugin/package.json | 3 ++- packages/list-reusable-blocks/package.json | 3 ++- packages/notices/package.json | 3 ++- packages/npm-package-json-lint-config/package.json | 3 ++- packages/nux/package.json | 3 ++- packages/plugins/package.json | 3 ++- packages/postcss-themes/package.json | 3 ++- packages/priority-queue/package.json | 3 ++- packages/redux-routine/package.json | 3 ++- packages/rich-text/package.json | 3 ++- packages/scripts/package.json | 3 ++- packages/shortcode/package.json | 3 ++- packages/token-list/package.json | 3 ++- packages/url/package.json | 3 ++- packages/viewport/package.json | 3 ++- packages/wordcount/package.json | 3 ++- 56 files changed, 112 insertions(+), 56 deletions(-) diff --git a/packages/a11y/package.json b/packages/a11y/package.json index 975ab31b994b3..dac861cbc1869 100644 --- a/packages/a11y/package.json +++ b/packages/a11y/package.json @@ -12,7 +12,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/a11y/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/a11y" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/annotations/package.json b/packages/annotations/package.json index ebbc0b39ca84f..af891a4376c52 100644 --- a/packages/annotations/package.json +++ b/packages/annotations/package.json @@ -11,7 +11,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/annotations/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/annotations" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/api-fetch/package.json b/packages/api-fetch/package.json index 8f9b6568013a7..d5d4b55da93f7 100644 --- a/packages/api-fetch/package.json +++ b/packages/api-fetch/package.json @@ -12,7 +12,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/api-fetch/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/api-fetch" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/autop/package.json b/packages/autop/package.json index 9735d5475aac5..b89416ed15b2f 100644 --- a/packages/autop/package.json +++ b/packages/autop/package.json @@ -11,7 +11,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/autop/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/autop" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/babel-plugin-import-jsx-pragma/package.json b/packages/babel-plugin-import-jsx-pragma/package.json index 7dac8d0786bfb..3f9efdc87ffa1 100644 --- a/packages/babel-plugin-import-jsx-pragma/package.json +++ b/packages/babel-plugin-import-jsx-pragma/package.json @@ -14,7 +14,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/babel-plugin-import-jsx-pragma/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/babel-plugin-import-jsx-pragma" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/babel-plugin-makepot/package.json b/packages/babel-plugin-makepot/package.json index 1dfd50b4c9984..fae36a7f7ac3d 100644 --- a/packages/babel-plugin-makepot/package.json +++ b/packages/babel-plugin-makepot/package.json @@ -13,7 +13,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/babel-plugin-makepot/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/babel-plugin-makepot" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/babel-preset-default/package.json b/packages/babel-preset-default/package.json index de20169a475b6..a1f2f02e0ac88 100644 --- a/packages/babel-preset-default/package.json +++ b/packages/babel-preset-default/package.json @@ -13,7 +13,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/babel-preset-default/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/babel-preset-default" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/blob/package.json b/packages/blob/package.json index f8c08ee648276..39bdfcf6377b2 100644 --- a/packages/blob/package.json +++ b/packages/blob/package.json @@ -11,7 +11,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/blob/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/blob" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json index d4f3cf7f757c7..6f593a1063edf 100644 --- a/packages/block-editor/package.json +++ b/packages/block-editor/package.json @@ -12,7 +12,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/block-editor/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/block-editor" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/block-library/package.json b/packages/block-library/package.json index 431108dd29f3a..9b5d871edf42f 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -11,7 +11,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/block-library/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/block-library" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/block-serialization-default-parser/package.json b/packages/block-serialization-default-parser/package.json index d698187f122ec..28e9bf0f6c54b 100644 --- a/packages/block-serialization-default-parser/package.json +++ b/packages/block-serialization-default-parser/package.json @@ -12,7 +12,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/block-serialization-default-parser/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/block-serialization-default-parser" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/block-serialization-spec-parser/package.json b/packages/block-serialization-spec-parser/package.json index 5d40a8cfd03de..335d245be2b9a 100644 --- a/packages/block-serialization-spec-parser/package.json +++ b/packages/block-serialization-spec-parser/package.json @@ -13,7 +13,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/block-serialization-spec-parser/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/block-serialization-spec-parser" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/blocks/package.json b/packages/blocks/package.json index 077d6f5a228c9..eea81e9985e50 100644 --- a/packages/blocks/package.json +++ b/packages/blocks/package.json @@ -11,7 +11,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/blocks/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/blocks" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/browserslist-config/package.json b/packages/browserslist-config/package.json index 81baf916c0726..84c51f2345659 100644 --- a/packages/browserslist-config/package.json +++ b/packages/browserslist-config/package.json @@ -12,7 +12,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/browserslist-config/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/browserslist-config" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/components/package.json b/packages/components/package.json index 963e7b1e3545e..9613e850a722d 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -11,7 +11,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/components/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/components" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/compose/package.json b/packages/compose/package.json index c02e9d263c2e2..875dbf0852d35 100644 --- a/packages/compose/package.json +++ b/packages/compose/package.json @@ -12,7 +12,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/compose/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/compose" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/core-data/package.json b/packages/core-data/package.json index e9e2bb3c97a48..b2173586318f5 100644 --- a/packages/core-data/package.json +++ b/packages/core-data/package.json @@ -12,7 +12,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/core-data/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/core-data" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/custom-templated-path-webpack-plugin/package.json b/packages/custom-templated-path-webpack-plugin/package.json index 4f41dc79fb064..49ca17003c257 100644 --- a/packages/custom-templated-path-webpack-plugin/package.json +++ b/packages/custom-templated-path-webpack-plugin/package.json @@ -12,7 +12,8 @@ "homepage": "https://github.com/WordPress/gutenberg/blob/master/packages/custom-templated-path-webpack-plugin/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/custom-templated-path-webpack-plugin" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/data/package.json b/packages/data/package.json index 96264a852eadd..c351248f049a9 100644 --- a/packages/data/package.json +++ b/packages/data/package.json @@ -12,7 +12,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/data/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/data" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/date/package.json b/packages/date/package.json index 3aa2437a296bd..becb695d02ebd 100644 --- a/packages/date/package.json +++ b/packages/date/package.json @@ -11,7 +11,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/date/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/date" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/deprecated/package.json b/packages/deprecated/package.json index 6d0dccf40317b..b8584a7e4f266 100644 --- a/packages/deprecated/package.json +++ b/packages/deprecated/package.json @@ -11,7 +11,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/deprecated/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/deprecated" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/dom-ready/package.json b/packages/dom-ready/package.json index 2c377f2957314..22f70e8341189 100644 --- a/packages/dom-ready/package.json +++ b/packages/dom-ready/package.json @@ -11,7 +11,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/dom-ready/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/dom-ready" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/dom/package.json b/packages/dom/package.json index 6e94a8fc0dae5..75289e1e27ce4 100644 --- a/packages/dom/package.json +++ b/packages/dom/package.json @@ -12,7 +12,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/dom/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/dom" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/e2e-test-utils/package.json b/packages/e2e-test-utils/package.json index ca3e6b12d913f..071541ed4449c 100644 --- a/packages/e2e-test-utils/package.json +++ b/packages/e2e-test-utils/package.json @@ -12,7 +12,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/e2e-test-utils/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/e2e-test-utils" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/e2e-tests/package.json b/packages/e2e-tests/package.json index a79a08fb53e16..881a497511505 100644 --- a/packages/e2e-tests/package.json +++ b/packages/e2e-tests/package.json @@ -12,7 +12,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/e2e-tests/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/e2e-tests" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json index 88a7818e2fac8..0327ee5121f38 100644 --- a/packages/edit-post/package.json +++ b/packages/edit-post/package.json @@ -11,7 +11,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/edit-post/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/edit-post" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/edit-widgets/package.json b/packages/edit-widgets/package.json index a308d8cd6035f..3adbdad1f19a1 100644 --- a/packages/edit-widgets/package.json +++ b/packages/edit-widgets/package.json @@ -11,7 +11,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/edit-widgets/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/edit-widgets" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/editor/package.json b/packages/editor/package.json index acbb00bec5532..8893dbf4fea7c 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -11,7 +11,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/editor/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/editor" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/element/package.json b/packages/element/package.json index 24df06e6702af..ce76fa344232c 100644 --- a/packages/element/package.json +++ b/packages/element/package.json @@ -12,7 +12,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/element/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/element" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/escape-html/package.json b/packages/escape-html/package.json index 6fb68c88808ec..5e60de55ab031 100644 --- a/packages/escape-html/package.json +++ b/packages/escape-html/package.json @@ -10,7 +10,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/escape-html/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/escape-html" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 125acdac5189e..f115d81c5c41e 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -11,7 +11,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/eslint-plugin/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/eslint-plugin" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/format-library/package.json b/packages/format-library/package.json index d8fd5b4b7f366..ec4e8a02a90be 100644 --- a/packages/format-library/package.json +++ b/packages/format-library/package.json @@ -11,7 +11,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/format-library/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/format-library" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/hooks/package.json b/packages/hooks/package.json index b4868eb1f4ed6..3469fc00f451e 100644 --- a/packages/hooks/package.json +++ b/packages/hooks/package.json @@ -11,7 +11,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/hooks/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/hooks" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/html-entities/package.json b/packages/html-entities/package.json index 002fd8b8d4a4e..06c7a9d25d59c 100644 --- a/packages/html-entities/package.json +++ b/packages/html-entities/package.json @@ -13,7 +13,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/html-entities/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/html-entities" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/i18n/package.json b/packages/i18n/package.json index e05c99de5c587..91e9e20fd3ea0 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -11,7 +11,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/i18n/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/i18n" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/is-shallow-equal/package.json b/packages/is-shallow-equal/package.json index d293276b1e946..06c38ebf63cd2 100644 --- a/packages/is-shallow-equal/package.json +++ b/packages/is-shallow-equal/package.json @@ -13,7 +13,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/is-shallow-equal/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/is-shallow-equal" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/jest-console/package.json b/packages/jest-console/package.json index ae0f3902fa50f..d39dbe4b23026 100644 --- a/packages/jest-console/package.json +++ b/packages/jest-console/package.json @@ -13,7 +13,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/jest-console/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/jest-console" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/jest-preset-default/package.json b/packages/jest-preset-default/package.json index fbabe4c53bc69..42110a066fe89 100644 --- a/packages/jest-preset-default/package.json +++ b/packages/jest-preset-default/package.json @@ -15,7 +15,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/jest-preset-default/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/jest-preset-default" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/jest-puppeteer-axe/package.json b/packages/jest-puppeteer-axe/package.json index 2c73930c9bca0..6c5e7ba6bc88d 100644 --- a/packages/jest-puppeteer-axe/package.json +++ b/packages/jest-puppeteer-axe/package.json @@ -14,7 +14,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/jest-puppeteer-axe/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/jest-puppeteer-axe" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/keycodes/package.json b/packages/keycodes/package.json index 12a8c50895e66..d062bf6f75a36 100644 --- a/packages/keycodes/package.json +++ b/packages/keycodes/package.json @@ -11,7 +11,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/keycodes/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/keycodes" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/library-export-default-webpack-plugin/package.json b/packages/library-export-default-webpack-plugin/package.json index 7222df5a7b4ff..e2a2881f72902 100644 --- a/packages/library-export-default-webpack-plugin/package.json +++ b/packages/library-export-default-webpack-plugin/package.json @@ -12,7 +12,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/library-export-default-webpack-plugin/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/library-export-default-webpack-plugin" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/list-reusable-blocks/package.json b/packages/list-reusable-blocks/package.json index ca1bcefadedc6..9501833c972fb 100644 --- a/packages/list-reusable-blocks/package.json +++ b/packages/list-reusable-blocks/package.json @@ -11,7 +11,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/list-reusable-blocks/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/list-reusable-blocks" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/notices/package.json b/packages/notices/package.json index aacbd324448f1..93f5e16eac4b1 100644 --- a/packages/notices/package.json +++ b/packages/notices/package.json @@ -11,7 +11,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/notices/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/notices" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/npm-package-json-lint-config/package.json b/packages/npm-package-json-lint-config/package.json index 6fdb45fd6c612..f2dabfdf29af2 100644 --- a/packages/npm-package-json-lint-config/package.json +++ b/packages/npm-package-json-lint-config/package.json @@ -12,7 +12,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/npm-package-json-lint-config/README.md", "repository": { "type": "git", - "url": "git+https://github.com/WordPress/gutenberg.git" + "url": "git+https://github.com/WordPress/gutenberg.git", + "directory": "packages/npm-package-json-lint-config" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/nux/package.json b/packages/nux/package.json index 0476ec4e77a0f..1b9ebbe5f7e4d 100644 --- a/packages/nux/package.json +++ b/packages/nux/package.json @@ -11,7 +11,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/nux/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/nux" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/plugins/package.json b/packages/plugins/package.json index 1050d8aa78554..5f433e110f6b8 100644 --- a/packages/plugins/package.json +++ b/packages/plugins/package.json @@ -11,7 +11,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/plugins/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/plugins" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/postcss-themes/package.json b/packages/postcss-themes/package.json index 0836e71badf9a..cafbbdf93e6b4 100644 --- a/packages/postcss-themes/package.json +++ b/packages/postcss-themes/package.json @@ -15,7 +15,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/postcss-themes/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/postcss-themes" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/priority-queue/package.json b/packages/priority-queue/package.json index cf921b4739cef..afa5df6e857ec 100644 --- a/packages/priority-queue/package.json +++ b/packages/priority-queue/package.json @@ -12,7 +12,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/priority-queue/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/priority-queue" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/redux-routine/package.json b/packages/redux-routine/package.json index c1a73f09f8f92..3ee1016b84b68 100644 --- a/packages/redux-routine/package.json +++ b/packages/redux-routine/package.json @@ -13,7 +13,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/redux-routine/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/redux-routine" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/rich-text/package.json b/packages/rich-text/package.json index cfae343520b6e..029e2056d3a5c 100644 --- a/packages/rich-text/package.json +++ b/packages/rich-text/package.json @@ -11,7 +11,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/rich-text/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/rich-text" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 3a1c92123c8b1..6a2f625c182ab 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -13,7 +13,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/scripts/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/scripts" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/shortcode/package.json b/packages/shortcode/package.json index f110b0c3542ef..5fd63da90e79e 100644 --- a/packages/shortcode/package.json +++ b/packages/shortcode/package.json @@ -11,7 +11,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/shortcode/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/shortcode" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/token-list/package.json b/packages/token-list/package.json index 57b74a9c092e8..5c04a6bcb92f3 100644 --- a/packages/token-list/package.json +++ b/packages/token-list/package.json @@ -10,7 +10,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/token-list/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/token-list" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/url/package.json b/packages/url/package.json index 6257dbc7c8674..2ae9d56532c18 100644 --- a/packages/url/package.json +++ b/packages/url/package.json @@ -11,7 +11,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/url/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/url" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/viewport/package.json b/packages/viewport/package.json index 5c38011385207..106bd96b57f3b 100644 --- a/packages/viewport/package.json +++ b/packages/viewport/package.json @@ -11,7 +11,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/viewport/README.md", "repository": { "type": "git", - "url": "https://github.com/WordPress/gutenberg.git" + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/viewport" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/wordcount/package.json b/packages/wordcount/package.json index c31b0009aad30..a5ec368498b1e 100644 --- a/packages/wordcount/package.json +++ b/packages/wordcount/package.json @@ -11,7 +11,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/wordcount/README.md", "repository": { "type": "git", - "url": "git+https://github.com/WordPress/gutenberg.git" + "url": "git+https://github.com/WordPress/gutenberg.git", + "directory": "packages/wordcount" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" From b0aa8929c887fba9e47263fc8ba901d92cabeea1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Mon, 25 Feb 2019 10:50:41 +0100 Subject: [PATCH 510/691] Improve default Babel preset to include JSX pragma (#13540) * Include custome JSX pragma support in Babel preset * Stop using Babel tranpiliation for two packages babel-plugin-import-jsx-pragma and postcss-themes * Add engines field to node based packages --- babel.config.js | 10 ----- bin/packages/get-babel-config.js | 13 ++----- bin/packages/get-packages.js | 38 +------------------ package-lock.json | 7 +--- package.json | 4 +- .../CHANGELOG.md | 1 + .../{src => }/index.js | 9 ++--- .../package.json | 12 +++--- .../test/index.js | 2 +- packages/babel-plugin-makepot/package.json | 3 ++ packages/babel-preset-default/CHANGELOG.md | 1 + packages/babel-preset-default/index.js | 8 ++++ packages/babel-preset-default/package.json | 4 ++ .../package.json | 3 ++ packages/e2e-test-utils/package.json | 3 ++ packages/e2e-tests/package.json | 3 ++ packages/jest-console/package.json | 3 ++ packages/jest-preset-default/package.json | 3 ++ packages/jest-puppeteer-axe/package.json | 3 ++ .../package.json | 3 ++ .../npm-package-json-lint-config/package.json | 3 ++ packages/postcss-themes/CHANGELOG.md | 5 +++ packages/postcss-themes/{src => }/index.js | 3 ++ packages/postcss-themes/package.json | 9 +++-- packages/postcss-themes/test/index.js | 2 +- 25 files changed, 73 insertions(+), 82 deletions(-) rename packages/babel-plugin-import-jsx-pragma/{src => }/index.js (96%) create mode 100644 packages/postcss-themes/CHANGELOG.md rename packages/postcss-themes/{src => }/index.js (98%) diff --git a/babel.config.js b/babel.config.js index 6a903eff6c1d9..4dc16df8337b2 100644 --- a/babel.config.js +++ b/babel.config.js @@ -3,16 +3,6 @@ module.exports = function( api ) { return { presets: [ '@wordpress/babel-preset-default' ], - plugins: [ - [ - '@wordpress/babel-plugin-import-jsx-pragma', - { - scopeVariable: 'createElement', - source: '@wordpress/element', - isDefault: false, - }, - ], - ], env: { production: { plugins: [ diff --git a/bin/packages/get-babel-config.js b/bin/packages/get-babel-config.js index e79bc306d07c4..b2646da46955c 100644 --- a/bin/packages/get-babel-config.js +++ b/bin/packages/get-babel-config.js @@ -10,14 +10,7 @@ const babel = require( '@babel/core' ); const { options: babelDefaultConfig } = babel.loadPartialConfig( { configFile: '@wordpress/babel-preset-default', } ); -const plugins = babelDefaultConfig.plugins; -if ( ! process.env.SKIP_JSX_PRAGMA_TRANSFORM ) { - plugins.push( [ '@wordpress/babel-plugin-import-jsx-pragma', { - scopeVariable: 'createElement', - source: '@wordpress/element', - isDefault: false, - } ] ); -} +const { plugins, presets } = babelDefaultConfig; const overrideOptions = ( target, targetName, options ) => { if ( get( target, [ 'file', 'request' ] ) === targetName ) { @@ -37,7 +30,7 @@ const babelConfigs = { { plugins, presets: map( - babelDefaultConfig.presets, + presets, ( preset ) => overrideOptions( preset, '@babel/preset-env', { modules: 'commonjs', } ) @@ -55,7 +48,7 @@ const babelConfigs = { } ) ), presets: map( - babelDefaultConfig.presets, + presets, ( preset ) => overrideOptions( preset, '@babel/preset-env', { modules: false, } ) diff --git a/bin/packages/get-packages.js b/bin/packages/get-packages.js index 30093a22abba6..ed271db0434f2 100644 --- a/bin/packages/get-packages.js +++ b/bin/packages/get-packages.js @@ -3,7 +3,7 @@ */ const fs = require( 'fs' ); const path = require( 'path' ); -const { overEvery, compact, includes, negate } = require( 'lodash' ); +const { overEvery } = require( 'lodash' ); /** * Absolute path to packages directory. @@ -12,36 +12,6 @@ const { overEvery, compact, includes, negate } = require( 'lodash' ); */ const PACKAGES_DIR = path.resolve( __dirname, '../../packages' ); -const { - /** - * Comma-separated string of packages to include in build. - * - * @type {string} - */ - INCLUDE_PACKAGES, - - /** - * Comma-separated string of packages to exclude from build. - * - * @type {string} - */ - EXCLUDE_PACKAGES, -} = process.env; - -/** - * Given a comma-separated string, returns a filter function which returns true - * if the item is contained within as a comma-separated entry. - * - * @param {Function} filterFn Filter function to call with item to test. - * @param {string} list Comma-separated list of items. - * - * @return {Function} Filter function. - */ -const createCommaSeparatedFilter = ( filterFn, list ) => { - const listItems = list.split( ',' ); - return ( item ) => filterFn( listItems, item ); -}; - /** * Returns true if the given base file name for a file within the packages * directory is itself a directory. @@ -62,11 +32,7 @@ function isDirectory( file ) { * * @return {boolean} Whether to include file in build. */ -const filterPackages = overEvery( compact( [ - isDirectory, - INCLUDE_PACKAGES && createCommaSeparatedFilter( includes, INCLUDE_PACKAGES ), - EXCLUDE_PACKAGES && createCommaSeparatedFilter( negate( includes ), EXCLUDE_PACKAGES ), -] ) ); +const filterPackages = overEvery( isDirectory ); /** * Returns the absolute path of all WordPress packages diff --git a/package-lock.json b/package-lock.json index 4c9b3ccb412e8..064963872225f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2496,10 +2496,7 @@ }, "@wordpress/babel-plugin-import-jsx-pragma": { "version": "file:packages/babel-plugin-import-jsx-pragma", - "dev": true, - "requires": { - "@babel/runtime": "^7.3.1" - } + "dev": true }, "@wordpress/babel-plugin-makepot": { "version": "file:packages/babel-plugin-makepot", @@ -2521,6 +2518,7 @@ "@babel/plugin-transform-runtime": "^7.2.0", "@babel/preset-env": "^7.3.1", "@babel/runtime": "^7.3.1", + "@wordpress/babel-plugin-import-jsx-pragma": "file:packages/babel-plugin-import-jsx-pragma", "@wordpress/browserslist-config": "file:packages/browserslist-config" } }, @@ -2975,7 +2973,6 @@ "version": "file:packages/postcss-themes", "dev": true, "requires": { - "@babel/runtime": "^7.3.1", "autoprefixer": "^9.4.5", "postcss": "^7.0.13", "postcss-color-function": "^4.0.1" diff --git a/package.json b/package.json index 63f478e6c6fa3..eb1b308d87f2a 100644 --- a/package.json +++ b/package.json @@ -150,8 +150,8 @@ "scripts": { "prebuild": "npm run check-engines", "clean:packages": "rimraf ./packages/*/build ./packages/*/build-module ./packages/*/build-style", - "prebuild:packages": "npm run clean:packages && lerna run build && cross-env INCLUDE_PACKAGES=babel-plugin-import-jsx-pragma,postcss-themes,jest-console SKIP_JSX_PRAGMA_TRANSFORM=1 node ./bin/packages/build.js", - "build:packages": "cross-env EXCLUDE_PACKAGES=babel-plugin-import-jsx-pragma,jest-console,postcss-themes node ./bin/packages/build.js", + "prebuild:packages": "npm run clean:packages && lerna run build", + "build:packages": "node ./bin/packages/build.js", "build": "npm run build:packages && wp-scripts build", "check-engines": "wp-scripts check-engines", "check-licenses": "concurrently \"wp-scripts check-licenses --prod --gpl2\" \"wp-scripts check-licenses --dev\"", diff --git a/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md b/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md index ffa68f4404f0d..32ad201bf1884 100644 --- a/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md +++ b/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md @@ -3,6 +3,7 @@ ### Breaking Change - Plugin skips now adding import JSX pragma when the scope variable is defined for all JSX elements ([#13809](https://github.com/WordPress/gutenberg/pull/13809)). +- Stop using Babel transpilation internally and set node 8 as a minimal version required ([#13540](https://github.com/WordPress/gutenberg/pull/13540)). ## 1.1.0 (2018-09-05) diff --git a/packages/babel-plugin-import-jsx-pragma/src/index.js b/packages/babel-plugin-import-jsx-pragma/index.js similarity index 96% rename from packages/babel-plugin-import-jsx-pragma/src/index.js rename to packages/babel-plugin-import-jsx-pragma/index.js index 68e94e1ffc37d..d431b6823aea3 100644 --- a/packages/babel-plugin-import-jsx-pragma/src/index.js +++ b/packages/babel-plugin-import-jsx-pragma/index.js @@ -26,15 +26,12 @@ const DEFAULT_OPTIONS = { * * @return {Object} Babel transform plugin. */ -export default function( babel ) { +module.exports = function( babel ) { const { types: t } = babel; function getOptions( state ) { if ( ! state._options ) { - state._options = { - ...DEFAULT_OPTIONS, - ...state.opts, - }; + state._options = Object.assign( {}, DEFAULT_OPTIONS, state.opts ); } return state._options; @@ -106,4 +103,4 @@ export default function( babel ) { }, }, }; -} +}; diff --git a/packages/babel-plugin-import-jsx-pragma/package.json b/packages/babel-plugin-import-jsx-pragma/package.json index 3f9efdc87ffa1..c06f76a26f3bb 100644 --- a/packages/babel-plugin-import-jsx-pragma/package.json +++ b/packages/babel-plugin-import-jsx-pragma/package.json @@ -20,15 +20,13 @@ "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" }, + "engines": { + "node": ">=8" + }, "files": [ - "build", - "build-module" + "index.js" ], - "main": "build/index.js", - "module": "build-module/index.js", - "dependencies": { - "@babel/runtime": "^7.3.1" - }, + "main": "index.js", "peerDependencies": { "@babel/core": "^7.0.0" }, diff --git a/packages/babel-plugin-import-jsx-pragma/test/index.js b/packages/babel-plugin-import-jsx-pragma/test/index.js index d70cf2313c540..a10207a5d5563 100644 --- a/packages/babel-plugin-import-jsx-pragma/test/index.js +++ b/packages/babel-plugin-import-jsx-pragma/test/index.js @@ -6,7 +6,7 @@ import { transformSync } from '@babel/core'; /** * Internal dependencies */ -import plugin from '../src'; +import plugin from '../'; describe( 'babel-plugin-import-jsx-pragma', () => { function getTransformedCode( source, options = {} ) { diff --git a/packages/babel-plugin-makepot/package.json b/packages/babel-plugin-makepot/package.json index fae36a7f7ac3d..10b4d4eb90d02 100644 --- a/packages/babel-plugin-makepot/package.json +++ b/packages/babel-plugin-makepot/package.json @@ -19,6 +19,9 @@ "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" }, + "engines": { + "node": ">=8" + }, "files": [ "build", "build-module" diff --git a/packages/babel-preset-default/CHANGELOG.md b/packages/babel-preset-default/CHANGELOG.md index 40e47005778e4..3fe7a9f29b100 100644 --- a/packages/babel-preset-default/CHANGELOG.md +++ b/packages/babel-preset-default/CHANGELOG.md @@ -3,6 +3,7 @@ ## Breaking Change - Removed `babel-core` dependency acting as Babel 7 bridge ([#13922](https://github.com/WordPress/gutenberg/pull/13922). Ensure all references to `babel-core` are replaced with `@babel/core` . +- Preset updated to include `@wordpress/babel-plugin-import-jsx-pragma` plugin integration ([#13540](https://github.com/WordPress/gutenberg/pull/13540)). ## 3.0.0 (2018-09-30) diff --git a/packages/babel-preset-default/index.js b/packages/babel-preset-default/index.js index a2bfedebe1b37..44a0392cd82cb 100644 --- a/packages/babel-preset-default/index.js +++ b/packages/babel-preset-default/index.js @@ -15,6 +15,14 @@ module.exports = function( api ) { ].filter( Boolean ), plugins: [ '@babel/plugin-proposal-object-rest-spread', + [ + '@wordpress/babel-plugin-import-jsx-pragma', + { + scopeVariable: 'createElement', + source: '@wordpress/element', + isDefault: false, + }, + ], [ '@babel/plugin-transform-react-jsx', { pragma: 'createElement', } ], diff --git a/packages/babel-preset-default/package.json b/packages/babel-preset-default/package.json index a1f2f02e0ac88..1bf66c0c54e6c 100644 --- a/packages/babel-preset-default/package.json +++ b/packages/babel-preset-default/package.json @@ -22,6 +22,9 @@ "engines": { "node": ">=8" }, + "files": [ + "index.js" + ], "main": "index.js", "dependencies": { "@babel/core": "^7.2.2", @@ -31,6 +34,7 @@ "@babel/plugin-transform-runtime": "^7.2.0", "@babel/preset-env": "^7.3.1", "@babel/runtime": "^7.3.1", + "@wordpress/babel-plugin-import-jsx-pragma": "file:../babel-plugin-import-jsx-pragma", "@wordpress/browserslist-config": "file:../browserslist-config" }, "peerDependencies": { diff --git a/packages/custom-templated-path-webpack-plugin/package.json b/packages/custom-templated-path-webpack-plugin/package.json index 49ca17003c257..02cd1d4c4d7c9 100644 --- a/packages/custom-templated-path-webpack-plugin/package.json +++ b/packages/custom-templated-path-webpack-plugin/package.json @@ -18,6 +18,9 @@ "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" }, + "engines": { + "node": ">=8" + }, "files": [ "build", "build-module" diff --git a/packages/e2e-test-utils/package.json b/packages/e2e-test-utils/package.json index 071541ed4449c..f31d67677475e 100644 --- a/packages/e2e-test-utils/package.json +++ b/packages/e2e-test-utils/package.json @@ -18,6 +18,9 @@ "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" }, + "engines": { + "node": ">=8" + }, "files": [ "build", "build-module" diff --git a/packages/e2e-tests/package.json b/packages/e2e-tests/package.json index 881a497511505..c7e096b781d76 100644 --- a/packages/e2e-tests/package.json +++ b/packages/e2e-tests/package.json @@ -18,6 +18,9 @@ "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" }, + "engines": { + "node": ">=8" + }, "dependencies": { "@wordpress/e2e-test-utils": "file:../e2e-test-utils", "@wordpress/jest-console": "file:../jest-console", diff --git a/packages/jest-console/package.json b/packages/jest-console/package.json index d39dbe4b23026..2b54b68bb8ff8 100644 --- a/packages/jest-console/package.json +++ b/packages/jest-console/package.json @@ -19,6 +19,9 @@ "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" }, + "engines": { + "node": ">=8" + }, "files": [ "build", "build-module" diff --git a/packages/jest-preset-default/package.json b/packages/jest-preset-default/package.json index 42110a066fe89..bc73370bb4706 100644 --- a/packages/jest-preset-default/package.json +++ b/packages/jest-preset-default/package.json @@ -21,6 +21,9 @@ "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" }, + "engines": { + "node": ">=8" + }, "files": [ "scripts", "jest-preset.json" diff --git a/packages/jest-puppeteer-axe/package.json b/packages/jest-puppeteer-axe/package.json index 6c5e7ba6bc88d..c8a534ec613d4 100644 --- a/packages/jest-puppeteer-axe/package.json +++ b/packages/jest-puppeteer-axe/package.json @@ -20,6 +20,9 @@ "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" }, + "engines": { + "node": ">=8" + }, "files": [ "build", "build-module" diff --git a/packages/library-export-default-webpack-plugin/package.json b/packages/library-export-default-webpack-plugin/package.json index e2a2881f72902..c64a2f3bee854 100644 --- a/packages/library-export-default-webpack-plugin/package.json +++ b/packages/library-export-default-webpack-plugin/package.json @@ -18,6 +18,9 @@ "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" }, + "engines": { + "node": ">=8" + }, "files": [ "build", "build-module" diff --git a/packages/npm-package-json-lint-config/package.json b/packages/npm-package-json-lint-config/package.json index f2dabfdf29af2..f61270594fd16 100644 --- a/packages/npm-package-json-lint-config/package.json +++ b/packages/npm-package-json-lint-config/package.json @@ -18,6 +18,9 @@ "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" }, + "engines": { + "node": ">=8" + }, "main": "index.js", "peerDependencies": { "npm-package-json-lint": ">=3.3.1" diff --git a/packages/postcss-themes/CHANGELOG.md b/packages/postcss-themes/CHANGELOG.md new file mode 100644 index 0000000000000..23a814422fd86 --- /dev/null +++ b/packages/postcss-themes/CHANGELOG.md @@ -0,0 +1,5 @@ +## 2.0.0 (Unreleased) + +### Breaking change + +- Stop using Babel transpilation internally and set node 8 as a minimal version required ([#13540](https://github.com/WordPress/gutenberg/pull/13540)). diff --git a/packages/postcss-themes/src/index.js b/packages/postcss-themes/index.js similarity index 98% rename from packages/postcss-themes/src/index.js rename to packages/postcss-themes/index.js index 5018a0dd39a4e..9c7dd80c64501 100644 --- a/packages/postcss-themes/src/index.js +++ b/packages/postcss-themes/index.js @@ -1,3 +1,6 @@ +/** + * External dependencies + */ const postcss = require( 'postcss' ); module.exports = postcss.plugin( 'postcss-themes', function( options ) { diff --git a/packages/postcss-themes/package.json b/packages/postcss-themes/package.json index cafbbdf93e6b4..8a089ee2c8ccf 100644 --- a/packages/postcss-themes/package.json +++ b/packages/postcss-themes/package.json @@ -21,13 +21,14 @@ "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" }, + "engines": { + "node": ">=8" + }, "files": [ - "build", - "build-module" + "index.js" ], - "main": "build/index.js", + "main": "index.js", "dependencies": { - "@babel/runtime": "^7.3.1", "autoprefixer": "^9.4.5", "postcss": "^7.0.13", "postcss-color-function": "^4.0.1" diff --git a/packages/postcss-themes/test/index.js b/packages/postcss-themes/test/index.js index 9a670ba55a588..45a058d6e2a31 100644 --- a/packages/postcss-themes/test/index.js +++ b/packages/postcss-themes/test/index.js @@ -6,7 +6,7 @@ import postcss from 'postcss'; /** * Internal dependencies */ -import plugin from '../src'; +import plugin from '../'; /** * Module constants From cb809b98654151416646c56f5de42c93ce656315 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Mon, 25 Feb 2019 12:22:03 +0100 Subject: [PATCH 511/691] Use the block editor store instead of the editor one (#13105) --- lib/packages-dependencies.php | 2 ++ package-lock.json | 2 ++ packages/block-library/package.json | 1 + packages/block-library/src/block/edit.js | 9 ++++++--- packages/block-library/src/html/edit.js | 2 +- packages/block-library/src/image/edit.js | 2 +- packages/block-library/src/index.js | 2 ++ packages/block-library/src/missing/index.js | 2 +- packages/block-library/src/paragraph/edit.js | 2 +- packages/block-library/src/pullquote/index.js | 2 +- packages/edit-post/package.json | 1 + .../plugin-block-settings-menu-group.js | 2 +- .../components/header/header-toolbar/index.js | 3 ++- .../edit-post/src/components/header/index.js | 2 +- .../components/header/mode-switcher/index.js | 2 +- .../src/components/keyboard-shortcuts/index.js | 4 ++-- .../edit-post/src/components/layout/index.js | 2 +- .../options-modal/meta-boxes-section.js | 3 ++- .../options/enable-custom-fields.js | 2 +- .../components/sidebar/settings-header/index.js | 2 +- .../src/components/text-editor/index.js | 2 +- .../src/hooks/validate-multiple-use/index.js | 4 ++-- packages/edit-post/src/index.js | 1 + .../edit-post/src/prevent-event-discovery.js | 2 +- packages/edit-post/src/store/effects.js | 4 ++-- .../src/components/alignment-toolbar/index.js | 2 +- .../src/components/autocompleters/block.js | 6 +++--- .../src/components/autosave-monitor/index.js | 4 ++-- .../src/components/block-actions/index.js | 10 +++++----- .../components/block-alignment-toolbar/index.js | 4 ++-- .../src/components/block-draggable/index.js | 2 +- .../src/components/block-drop-zone/index.js | 4 ++-- .../src/components/block-inspector/index.js | 2 +- .../src/components/block-list-appender/index.js | 2 +- .../src/components/block-list/block-html.js | 4 ++-- .../block-list/block-invalid-warning.js | 4 ++-- .../editor/src/components/block-list/block.js | 17 ++++++++++------- .../src/components/block-list/breadcrumb.js | 2 +- .../src/components/block-list/hover-area.js | 2 +- .../editor/src/components/block-list/index.js | 4 ++-- .../components/block-list/insertion-point.js | 2 +- .../src/components/block-list/multi-controls.js | 2 +- .../editor/src/components/block-mover/index.js | 4 ++-- .../src/components/block-navigation/dropdown.js | 2 +- .../src/components/block-navigation/index.js | 4 ++-- .../components/block-selection-clearer/index.js | 4 ++-- .../block-html-convert-button.js | 4 ++-- .../block-settings-menu/block-mode-toggle.js | 4 ++-- .../block-unknown-convert-button.js | 4 ++-- .../src/components/block-settings-menu/index.js | 2 +- .../reusable-block-convert-button.js | 2 ++ .../reusable-block-delete-button.js | 9 +++------ .../editor/src/components/block-styles/index.js | 4 ++-- .../src/components/block-switcher/index.js | 4 ++-- .../block-switcher/multi-blocks-switcher.js | 2 +- .../editor/src/components/block-title/index.js | 2 +- .../src/components/block-toolbar/index.js | 2 +- .../color-palette/with-color-context.js | 2 +- .../editor/src/components/colors/with-colors.js | 2 +- .../editor/src/components/copy-handler/index.js | 4 ++-- .../components/default-block-appender/index.js | 4 ++-- .../default-block-appender/index.native.js | 4 ++-- .../src/components/document-outline/check.js | 2 +- .../src/components/document-outline/index.js | 4 ++-- .../components/font-sizes/font-size-picker.js | 2 +- .../components/font-sizes/with-font-sizes.js | 2 +- .../global-keyboard-shortcuts/save-shortcut.js | 4 +--- .../visual-editor-shortcuts.js | 8 ++++++-- .../editor/src/components/inner-blocks/index.js | 4 ++-- .../components/inserter-with-shortcuts/index.js | 6 +++--- .../src/components/inserter/child-blocks.js | 2 +- .../editor/src/components/inserter/index.js | 7 ++++++- packages/editor/src/components/inserter/menu.js | 14 +++++++++----- .../multi-select-scroll-into-view/index.js | 2 +- .../multi-selection-inspector/index.js | 2 +- .../src/components/observe-typing/index.js | 4 ++-- .../src/components/page-attributes/check.js | 5 ++++- .../src/components/page-attributes/template.js | 3 ++- .../editor/src/components/post-format/check.js | 3 ++- .../src/components/post-locked-modal/index.js | 5 ++++- .../editor/src/components/post-title/index.js | 7 +++++-- .../src/components/post-title/index.native.js | 7 +++++-- .../preserve-scroll-in-reorder/index.js | 4 ++-- .../editor/src/components/rich-text/index.js | 4 +++- .../components/skip-to-selected-block/index.js | 2 +- .../src/components/table-of-contents/index.js | 2 +- .../src/components/table-of-contents/panel.js | 2 +- .../template-validation-notice/index.js | 4 ++-- .../editor/src/components/writing-flow/index.js | 4 ++-- packages/editor/src/hooks/align.js | 2 +- packages/editor/src/utils/media-upload/index.js | 8 ++++---- 91 files changed, 186 insertions(+), 143 deletions(-) diff --git a/lib/packages-dependencies.php b/lib/packages-dependencies.php index e2f135bda24d9..47c7d15422940 100644 --- a/lib/packages-dependencies.php +++ b/lib/packages-dependencies.php @@ -27,6 +27,7 @@ 'wp-api-fetch', 'wp-autop', 'wp-blob', + 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', @@ -121,6 +122,7 @@ 'media-views', 'wp-a11y', 'wp-api-fetch', + 'wp-block-editor', 'wp-block-library', 'wp-blocks', 'wp-components', diff --git a/package-lock.json b/package-lock.json index 064963872225f..a86ed000f301b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2549,6 +2549,7 @@ "@babel/runtime": "^7.3.1", "@wordpress/autop": "file:packages/autop", "@wordpress/blob": "file:packages/blob", + "@wordpress/block-editor": "file:packages/block-editor", "@wordpress/blocks": "file:packages/blocks", "@wordpress/components": "file:packages/components", "@wordpress/compose": "file:packages/compose", @@ -2739,6 +2740,7 @@ "@babel/runtime": "^7.3.1", "@wordpress/a11y": "file:packages/a11y", "@wordpress/api-fetch": "file:packages/api-fetch", + "@wordpress/block-editor": "file:packages/block-editor", "@wordpress/block-library": "file:packages/block-library", "@wordpress/blocks": "file:packages/blocks", "@wordpress/components": "file:packages/components", diff --git a/packages/block-library/package.json b/packages/block-library/package.json index 9b5d871edf42f..5847272862360 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -24,6 +24,7 @@ "@babel/runtime": "^7.3.1", "@wordpress/autop": "file:../autop", "@wordpress/blob": "file:../blob", + "@wordpress/block-editor": "file:../block-editor", "@wordpress/blocks": "file:../blocks", "@wordpress/components": "file:../components", "@wordpress/compose": "file:../compose", diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js index 62e4acf0640d1..5db7bda7c7f60 100644 --- a/packages/block-library/src/block/edit.js +++ b/packages/block-library/src/block/edit.js @@ -150,10 +150,11 @@ export default compose( [ __experimentalGetReusableBlock: getReusableBlock, __experimentalIsFetchingReusableBlock: isFetchingReusableBlock, __experimentalIsSavingReusableBlock: isSavingReusableBlock, - getBlock, } = select( 'core/editor' ); const { canUser } = select( 'core' ); - + const { + getBlock, + } = select( 'core/block-editor' ); const { ref } = ownProps.attributes; const reusableBlock = getReusableBlock( ref ); @@ -168,10 +169,12 @@ export default compose( [ withDispatch( ( dispatch, ownProps ) => { const { __experimentalFetchReusableBlocks: fetchReusableBlocks, - updateBlockAttributes, __experimentalUpdateReusableBlockTitle: updateReusableBlockTitle, __experimentalSaveReusableBlock: saveReusableBlock, } = dispatch( 'core/editor' ); + const { + updateBlockAttributes, + } = dispatch( 'core/block-editor' ); const { ref } = ownProps.attributes; return { diff --git a/packages/block-library/src/html/edit.js b/packages/block-library/src/html/edit.js index 44aa79544415b..6dd2bb6f1fd92 100644 --- a/packages/block-library/src/html/edit.js +++ b/packages/block-library/src/html/edit.js @@ -87,7 +87,7 @@ class HTMLEdit extends Component { } } export default withSelect( ( select ) => { - const { getEditorSettings } = select( 'core/editor' ); + const { getEditorSettings } = select( 'core/block-editor' ); return { styles: getEditorSettings().styles, }; diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index 44534956f5b5a..deaeaf79766dc 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -705,7 +705,7 @@ class ImageEdit extends Component { export default compose( [ withSelect( ( select, props ) => { const { getMedia } = select( 'core' ); - const { getEditorSettings } = select( 'core/editor' ); + const { getEditorSettings } = select( 'core/block-editor' ); const { id } = props.attributes; const { maxWidth, isRTL, imageSizes } = getEditorSettings(); diff --git a/packages/block-library/src/index.js b/packages/block-library/src/index.js index 4b15aac33c4a4..eb6cd561d887a 100644 --- a/packages/block-library/src/index.js +++ b/packages/block-library/src/index.js @@ -2,6 +2,8 @@ * WordPress dependencies */ import '@wordpress/core-data'; +import '@wordpress/block-editor'; +import '@wordpress/editor'; import { registerBlockType, setDefaultBlockName, diff --git a/packages/block-library/src/missing/index.js b/packages/block-library/src/missing/index.js index 8ec90b2d5cd44..62ad7ed96b999 100644 --- a/packages/block-library/src/missing/index.js +++ b/packages/block-library/src/missing/index.js @@ -43,7 +43,7 @@ function MissingBlockWarning( { attributes, convertToHTML } ) { } const edit = withDispatch( ( dispatch, { clientId, attributes } ) => { - const { replaceBlock } = dispatch( 'core/editor' ); + const { replaceBlock } = dispatch( 'core/block-editor' ); return { convertToHTML() { replaceBlock( clientId, createBlock( 'core/html', { diff --git a/packages/block-library/src/paragraph/edit.js b/packages/block-library/src/paragraph/edit.js index 4d57376ee10c3..2aaad4c8060ff 100644 --- a/packages/block-library/src/paragraph/edit.js +++ b/packages/block-library/src/paragraph/edit.js @@ -259,7 +259,7 @@ const ParagraphEdit = compose( [ withFontSizes( 'fontSize' ), applyFallbackStyles, withSelect( ( select ) => { - const { getEditorSettings } = select( 'core/editor' ); + const { getEditorSettings } = select( 'core/block-editor' ); return { isRTL: getEditorSettings().isRTL, diff --git a/packages/block-library/src/pullquote/index.js b/packages/block-library/src/pullquote/index.js index b7782b68a0551..beaf1d9549aaa 100644 --- a/packages/block-library/src/pullquote/index.js +++ b/packages/block-library/src/pullquote/index.js @@ -100,7 +100,7 @@ export const settings = { // Is normal style and a named color is being used, we need to retrieve the color value to set the style, // as there is no expectation that themes create classes that set border colors. } else if ( mainColor ) { - const colors = get( select( 'core/editor' ).getEditorSettings(), [ 'colors' ], [] ); + const colors = get( select( 'core/block-editor' ).getEditorSettings(), [ 'colors' ], [] ); const colorObject = getColorObjectByAttributeValues( colors, mainColor ); figureStyles = { borderColor: colorObject.color, diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json index 0327ee5121f38..74756043aec27 100644 --- a/packages/edit-post/package.json +++ b/packages/edit-post/package.json @@ -23,6 +23,7 @@ "@babel/runtime": "^7.3.1", "@wordpress/a11y": "file:../a11y", "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/block-editor": "file:../block-editor", "@wordpress/block-library": "file:../block-library", "@wordpress/blocks": "file:../blocks", "@wordpress/components": "file:../components", diff --git a/packages/edit-post/src/components/block-settings-menu/plugin-block-settings-menu-group.js b/packages/edit-post/src/components/block-settings-menu/plugin-block-settings-menu-group.js index 24859cb92fcc3..dfeb824f600fa 100644 --- a/packages/edit-post/src/components/block-settings-menu/plugin-block-settings-menu-group.js +++ b/packages/edit-post/src/components/block-settings-menu/plugin-block-settings-menu-group.js @@ -27,7 +27,7 @@ const PluginBlockSettingsMenuGroupSlot = ( { fillProps, selectedBlocks } ) => { }; PluginBlockSettingsMenuGroup.Slot = withSelect( ( select, { fillProps: { clientIds } } ) => ( { - selectedBlocks: select( 'core/editor' ).getBlocksByClientId( clientIds ), + selectedBlocks: select( 'core/block-editor' ).getBlocksByClientId( clientIds ), } ) )( PluginBlockSettingsMenuGroupSlot ); export default PluginBlockSettingsMenuGroup; diff --git a/packages/edit-post/src/components/header/header-toolbar/index.js b/packages/edit-post/src/components/header/header-toolbar/index.js index ef56ca64b3ad7..87a24a85e2a8a 100644 --- a/packages/edit-post/src/components/header/header-toolbar/index.js +++ b/packages/edit-post/src/components/header/header-toolbar/index.js @@ -56,7 +56,8 @@ function HeaderToolbar( { hasFixedToolbar, isLargeViewport, showInserter } ) { export default compose( [ withSelect( ( select ) => ( { hasFixedToolbar: select( 'core/edit-post' ).isFeatureActive( 'fixedToolbar' ), - showInserter: select( 'core/edit-post' ).getEditorMode() === 'visual' && select( 'core/editor' ).getEditorSettings().richEditingEnabled, + // This setting (richEditingEnabled) should not live in the block editor's setting. + showInserter: select( 'core/edit-post' ).getEditorMode() === 'visual' && select( 'core/block-editor' ).getEditorSettings().richEditingEnabled, } ) ), withViewportMatch( { isLargeViewport: 'medium' } ), ] )( HeaderToolbar ); diff --git a/packages/edit-post/src/components/header/index.js b/packages/edit-post/src/components/header/index.js index 7b59784d5d83e..2a16364c7bd2a 100644 --- a/packages/edit-post/src/components/header/index.js +++ b/packages/edit-post/src/components/header/index.js @@ -87,7 +87,7 @@ export default compose( isSaving: select( 'core/edit-post' ).isSavingMetaBoxes(), } ) ), withDispatch( ( dispatch, ownProps, { select } ) => { - const { getBlockSelectionStart } = select( 'core/editor' ); + const { getBlockSelectionStart } = select( 'core/block-editor' ); const { openGeneralSidebar, closeGeneralSidebar } = dispatch( 'core/edit-post' ); return { diff --git a/packages/edit-post/src/components/header/mode-switcher/index.js b/packages/edit-post/src/components/header/mode-switcher/index.js index 52ca0b78cfc94..058d9c74eeb8c 100644 --- a/packages/edit-post/src/components/header/mode-switcher/index.js +++ b/packages/edit-post/src/components/header/mode-switcher/index.js @@ -49,7 +49,7 @@ function ModeSwitcher( { onSwitch, mode } ) { export default compose( [ withSelect( ( select ) => ( { - isRichEditingEnabled: select( 'core/editor' ).getEditorSettings().richEditingEnabled, + isRichEditingEnabled: select( 'core/block-editor' ).getEditorSettings().richEditingEnabled, mode: select( 'core/edit-post' ).getEditorMode(), } ) ), ifCondition( ( { isRichEditingEnabled } ) => isRichEditingEnabled ), diff --git a/packages/edit-post/src/components/keyboard-shortcuts/index.js b/packages/edit-post/src/components/keyboard-shortcuts/index.js index 3fd18ca87a09d..b5c09178efd68 100644 --- a/packages/edit-post/src/components/keyboard-shortcuts/index.js +++ b/packages/edit-post/src/components/keyboard-shortcuts/index.js @@ -55,7 +55,7 @@ class EditorModeKeyboardShortcuts extends Component { export default compose( [ withSelect( ( select ) => ( { - isRichEditingEnabled: select( 'core/editor' ).getEditorSettings().richEditingEnabled, + isRichEditingEnabled: select( 'core/block-editor' ).getEditorSettings().richEditingEnabled, mode: select( 'core/edit-post' ).getEditorMode(), isEditorSidebarOpen: select( 'core/edit-post' ).isEditorSidebarOpened(), } ) ), @@ -64,7 +64,7 @@ export default compose( [ dispatch( 'core/edit-post' ).switchEditorMode( mode ); }, openSidebar() { - const { getBlockSelectionStart } = select( 'core/editor' ); + const { getBlockSelectionStart } = select( 'core/block-editor' ); const sidebarToOpen = getBlockSelectionStart() ? 'edit-post/block' : 'edit-post/document'; dispatch( 'core/edit-post' ).openGeneralSidebar( sidebarToOpen ); }, diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index f0876f4672287..76c10e8a78032 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -137,7 +137,7 @@ export default compose( hasFixedToolbar: select( 'core/edit-post' ).isFeatureActive( 'fixedToolbar' ), hasActiveMetaboxes: select( 'core/edit-post' ).hasMetaBoxes(), isSaving: select( 'core/edit-post' ).isSavingMetaBoxes(), - isRichEditingEnabled: select( 'core/editor' ).getEditorSettings().richEditingEnabled, + isRichEditingEnabled: select( 'core/block-editor' ).getEditorSettings().richEditingEnabled, } ) ), withDispatch( ( dispatch ) => { const { closePublishSidebar, togglePublishSidebar } = dispatch( 'core/edit-post' ); diff --git a/packages/edit-post/src/components/options-modal/meta-boxes-section.js b/packages/edit-post/src/components/options-modal/meta-boxes-section.js index bbec30e0eadb7..a5b8e0e85ad50 100644 --- a/packages/edit-post/src/components/options-modal/meta-boxes-section.js +++ b/packages/edit-post/src/components/options-modal/meta-boxes-section.js @@ -34,10 +34,11 @@ export function MetaBoxesSection( { areCustomFieldsRegistered, metaBoxes, ...sec } export default withSelect( ( select ) => { - const { getEditorSettings } = select( 'core/editor' ); + const { getEditorSettings } = select( 'core/block-editor' ); const { getAllMetaBoxes } = select( 'core/edit-post' ); return { + // This setting should not live in the block editor's store. areCustomFieldsRegistered: getEditorSettings().enableCustomFields !== undefined, metaBoxes: getAllMetaBoxes(), }; diff --git a/packages/edit-post/src/components/options-modal/options/enable-custom-fields.js b/packages/edit-post/src/components/options-modal/options/enable-custom-fields.js index 140c6f1c46d2d..e9a319efbebfe 100644 --- a/packages/edit-post/src/components/options-modal/options/enable-custom-fields.js +++ b/packages/edit-post/src/components/options-modal/options/enable-custom-fields.js @@ -43,5 +43,5 @@ export class EnableCustomFieldsOption extends Component { } export default withSelect( ( select ) => ( { - isChecked: !! select( 'core/editor' ).getEditorSettings().enableCustomFields, + isChecked: !! select( 'core/block-editor' ).getEditorSettings().enableCustomFields, } ) )( EnableCustomFieldsOption ); diff --git a/packages/edit-post/src/components/sidebar/settings-header/index.js b/packages/edit-post/src/components/sidebar/settings-header/index.js index e2866c231a1c9..eeb95a872166f 100644 --- a/packages/edit-post/src/components/sidebar/settings-header/index.js +++ b/packages/edit-post/src/components/sidebar/settings-header/index.js @@ -57,7 +57,7 @@ const SettingsHeader = ( { openDocumentSettings, openBlockSettings, sidebarName export default withDispatch( ( dispatch ) => { const { openGeneralSidebar } = dispatch( 'core/edit-post' ); - const { clearSelectedBlock } = dispatch( 'core/editor' ); + const { clearSelectedBlock } = dispatch( 'core/block-editor' ); return { openDocumentSettings() { openGeneralSidebar( 'edit-post/document' ); diff --git a/packages/edit-post/src/components/text-editor/index.js b/packages/edit-post/src/components/text-editor/index.js index 882e97dd7e83c..0cebd6e7396ad 100644 --- a/packages/edit-post/src/components/text-editor/index.js +++ b/packages/edit-post/src/components/text-editor/index.js @@ -38,7 +38,7 @@ function TextEditor( { onExit, isRichEditingEnabled } ) { export default compose( withSelect( ( select ) => ( { - isRichEditingEnabled: select( 'core/editor' ).getEditorSettings().richEditingEnabled, + isRichEditingEnabled: select( 'core/block-editor' ).getEditorSettings().richEditingEnabled, } ) ), withDispatch( ( dispatch ) => { return { diff --git a/packages/edit-post/src/hooks/validate-multiple-use/index.js b/packages/edit-post/src/hooks/validate-multiple-use/index.js index 74d6b183ee62e..8a9f15a2c1f49 100644 --- a/packages/edit-post/src/hooks/validate-multiple-use/index.js +++ b/packages/edit-post/src/hooks/validate-multiple-use/index.js @@ -43,7 +43,7 @@ const enhance = compose( // Otherwise, only pass `originalBlockClientId` if it refers to a different // block from the current one. - const blocks = select( 'core/editor' ).getBlocks(); + const blocks = select( 'core/block-editor' ).getBlocks(); const firstOfSameType = find( blocks, ( { name } ) => block.name === name ); const isInvalid = firstOfSameType && firstOfSameType.clientId !== block.clientId; return { @@ -51,7 +51,7 @@ const enhance = compose( }; } ), withDispatch( ( dispatch, { originalBlockClientId } ) => ( { - selectFirst: () => dispatch( 'core/editor' ).selectBlock( originalBlockClientId ), + selectFirst: () => dispatch( 'core/block-editor' ).selectBlock( originalBlockClientId ), } ) ), ); diff --git a/packages/edit-post/src/index.js b/packages/edit-post/src/index.js index 59e870e7929da..ecfac09f9347e 100644 --- a/packages/edit-post/src/index.js +++ b/packages/edit-post/src/index.js @@ -2,6 +2,7 @@ * WordPress dependencies */ import '@wordpress/core-data'; +import '@wordpress/block-editor'; import '@wordpress/editor'; import '@wordpress/nux'; import '@wordpress/viewport'; diff --git a/packages/edit-post/src/prevent-event-discovery.js b/packages/edit-post/src/prevent-event-discovery.js index 97e7b7d8a8fbb..36e58f798752c 100644 --- a/packages/edit-post/src/prevent-event-discovery.js +++ b/packages/edit-post/src/prevent-event-discovery.js @@ -8,7 +8,7 @@ export default { } event.preventDefault(); - window.wp.data.dispatch( 'core/editor' ).insertBlock( + window.wp.data.dispatch( 'core/block-editor' ).insertBlock( window.wp.blocks.createBlock( 'core/paragraph', { content: '🐡🐢🦀🐤🦋🐘🐧🐹🦁🦄🦍🐼🐿🎃🐴🐝🐆🦕🦔🌱🍇π🍌🐉💧🥨🌌🍂🍠🥦🥚🥝🎟🥥🥒🛵🥖🍒🍯🎾🎲🐺🐚🐮⌛️', } ) diff --git a/packages/edit-post/src/store/effects.js b/packages/edit-post/src/store/effects.js index a177c36c1b761..673025c3fae51 100644 --- a/packages/edit-post/src/store/effects.js +++ b/packages/edit-post/src/store/effects.js @@ -119,7 +119,7 @@ const effects = { SWITCH_MODE( action ) { // Unselect blocks when we switch to the code editor. if ( action.mode !== 'visual' ) { - dispatch( 'core/editor' ).clearSelectedBlock(); + dispatch( 'core/block-editor' ).clearSelectedBlock(); } const message = action.mode === 'visual' ? __( 'Visual editor selected' ) : __( 'Code editor selected' ); @@ -128,7 +128,7 @@ const effects = { INIT( _, store ) { // Select the block settings tab when the selected block changes subscribe( onChangeListener( - () => !! select( 'core/editor' ).getBlockSelectionStart(), + () => !! select( 'core/block-editor' ).getBlockSelectionStart(), ( hasBlockSelection ) => { if ( ! select( 'core/edit-post' ).isEditorSidebarOpened() ) { return; diff --git a/packages/editor/src/components/alignment-toolbar/index.js b/packages/editor/src/components/alignment-toolbar/index.js index e06ee34f4d7d6..bf5fe27650e2a 100644 --- a/packages/editor/src/components/alignment-toolbar/index.js +++ b/packages/editor/src/components/alignment-toolbar/index.js @@ -69,7 +69,7 @@ export default compose( } ), withViewportMatch( { isLargeViewport: 'medium' } ), withSelect( ( select, { clientId, isLargeViewport, isCollapsed } ) => { - const { getBlockRootClientId, getEditorSettings } = select( 'core/editor' ); + const { getBlockRootClientId, getEditorSettings } = select( 'core/block-editor' ); return { isCollapsed: isCollapsed || ! isLargeViewport || ( ! getEditorSettings().hasFixedToolbar && diff --git a/packages/editor/src/components/autocompleters/block.js b/packages/editor/src/components/autocompleters/block.js index d40b18d55c38b..2945ea4322786 100644 --- a/packages/editor/src/components/autocompleters/block.js +++ b/packages/editor/src/components/autocompleters/block.js @@ -17,7 +17,7 @@ import BlockIcon from '../block-icon'; * be placed. */ function defaultGetBlockInsertionParentClientId() { - return select( 'core/editor' ).getBlockInsertionPoint().rootClientId; + return select( 'core/block-editor' ).getBlockInsertionPoint().rootClientId; } /** @@ -30,7 +30,7 @@ function defaultGetBlockInsertionParentClientId() { * parent. */ function defaultGetInserterItems( rootClientId ) { - return select( 'core/editor' ).getInserterItems( rootClientId ); + return select( 'core/block-editor' ).getInserterItems( rootClientId ); } /** @@ -40,7 +40,7 @@ function defaultGetInserterItems( rootClientId ) { * block is selected. */ function defaultGetSelectedBlockName() { - const { getSelectedBlockClientId, getBlockName } = select( 'core/editor' ); + const { getSelectedBlockClientId, getBlockName } = select( 'core/block-editor' ); const selectedBlockClientId = getSelectedBlockClientId(); return selectedBlockClientId ? getBlockName( selectedBlockClientId ) : null; } diff --git a/packages/editor/src/components/autosave-monitor/index.js b/packages/editor/src/components/autosave-monitor/index.js index c1be1b20e0a40..23cff18b19411 100644 --- a/packages/editor/src/components/autosave-monitor/index.js +++ b/packages/editor/src/components/autosave-monitor/index.js @@ -62,12 +62,12 @@ export default compose( [ const { isEditedPostDirty, isEditedPostAutosaveable, - getEditorSettings, getReferenceByDistinctEdits, isAutosavingPost, } = select( 'core/editor' ); - const { autosaveInterval } = getEditorSettings(); + // This settings should not live in the block editor. + const { autosaveInterval } = select( 'core/block-editor' ).getEditorSettings(); return { isDirty: isEditedPostDirty(), diff --git a/packages/editor/src/components/block-actions/index.js b/packages/editor/src/components/block-actions/index.js index b90325b0affd5..3b3f8032432e8 100644 --- a/packages/editor/src/components/block-actions/index.js +++ b/packages/editor/src/components/block-actions/index.js @@ -35,7 +35,7 @@ export default compose( [ getBlocksByClientId, getTemplateLock, getBlockRootClientId, - } = select( 'core/editor' ); + } = select( 'core/block-editor' ); const blocks = getBlocksByClientId( props.clientIds ); const canDuplicate = every( blocks, ( block ) => { @@ -65,7 +65,7 @@ export default compose( [ multiSelect, removeBlocks, insertDefaultBlock, - } = dispatch( 'core/editor' ); + } = dispatch( 'core/block-editor' ); return { onDuplicate() { @@ -73,7 +73,7 @@ export default compose( [ return; } - const { getBlockIndex } = select( 'core/editor' ); + const { getBlockIndex } = select( 'core/block-editor' ); const lastSelectedIndex = getBlockIndex( last( castArray( clientIds ) ), rootClientId ); const clonedBlocks = blocks.map( ( block ) => cloneBlock( block ) ); insertBlocks( @@ -95,14 +95,14 @@ export default compose( [ }, onInsertBefore() { if ( ! isLocked ) { - const { getBlockIndex } = select( 'core/editor' ); + const { getBlockIndex } = select( 'core/block-editor' ); const firstSelectedIndex = getBlockIndex( first( castArray( clientIds ) ), rootClientId ); insertDefaultBlock( {}, rootClientId, firstSelectedIndex ); } }, onInsertAfter() { if ( ! isLocked ) { - const { getBlockIndex } = select( 'core/editor' ); + const { getBlockIndex } = select( 'core/block-editor' ); const lastSelectedIndex = getBlockIndex( last( castArray( clientIds ) ), rootClientId ); insertDefaultBlock( {}, rootClientId, lastSelectedIndex + 1 ); } diff --git a/packages/editor/src/components/block-alignment-toolbar/index.js b/packages/editor/src/components/block-alignment-toolbar/index.js index acfe1cb4ed344..0dcc4bda1412f 100644 --- a/packages/editor/src/components/block-alignment-toolbar/index.js +++ b/packages/editor/src/components/block-alignment-toolbar/index.js @@ -75,9 +75,9 @@ export default compose( } ), withViewportMatch( { isLargeViewport: 'medium' } ), withSelect( ( select, { clientId, isLargeViewport, isCollapsed } ) => { - const { getBlockRootClientId, getEditorSettings } = select( 'core/editor' ); + const { getBlockRootClientId, getEditorSettings } = select( 'core/block-editor' ); return { - wideControlsEnabled: select( 'core/editor' ).getEditorSettings().alignWide, + wideControlsEnabled: select( 'core/block-editor' ).getEditorSettings().alignWide, isCollapsed: isCollapsed || ! isLargeViewport || ( ! getEditorSettings().hasFixedToolbar && getBlockRootClientId( clientId ) diff --git a/packages/editor/src/components/block-draggable/index.js b/packages/editor/src/components/block-draggable/index.js index 2f7c3436819a1..8818d58be9c2d 100644 --- a/packages/editor/src/components/block-draggable/index.js +++ b/packages/editor/src/components/block-draggable/index.js @@ -32,7 +32,7 @@ const BlockDraggable = ( { children, clientId, rootClientId, blockElementId, ind }; export default withSelect( ( select, { clientId } ) => { - const { getBlockIndex, getBlockRootClientId } = select( 'core/editor' ); + const { getBlockIndex, getBlockRootClientId } = select( 'core/block-editor' ); const rootClientId = getBlockRootClientId( clientId ); return { index: getBlockIndex( clientId, rootClientId ), diff --git a/packages/editor/src/components/block-drop-zone/index.js b/packages/editor/src/components/block-drop-zone/index.js index f1c95a53242a6..4434a6c338a97 100644 --- a/packages/editor/src/components/block-drop-zone/index.js +++ b/packages/editor/src/components/block-drop-zone/index.js @@ -138,7 +138,7 @@ export default compose( insertBlocks, updateBlockAttributes, moveBlockToPosition, - } = dispatch( 'core/editor' ); + } = dispatch( 'core/block-editor' ); return { insertBlocks( blocks, index ) { @@ -156,7 +156,7 @@ export default compose( }; } ), withSelect( ( select, { rootClientId } ) => { - const { getClientIdsOfDescendants, getTemplateLock, getBlockIndex } = select( 'core/editor' ); + const { getClientIdsOfDescendants, getTemplateLock, getBlockIndex } = select( 'core/block-editor' ); return { isLocked: !! getTemplateLock( rootClientId ), getClientIdsOfDescendants, diff --git a/packages/editor/src/components/block-inspector/index.js b/packages/editor/src/components/block-inspector/index.js index ee2405a3bac14..f7cca99eb5af4 100644 --- a/packages/editor/src/components/block-inspector/index.js +++ b/packages/editor/src/components/block-inspector/index.js @@ -79,7 +79,7 @@ const BlockInspector = ( { selectedBlockClientId, selectedBlockName, blockType, export default withSelect( ( select ) => { - const { getSelectedBlockClientId, getSelectedBlockCount, getBlockName } = select( 'core/editor' ); + const { getSelectedBlockClientId, getSelectedBlockCount, getBlockName } = select( 'core/block-editor' ); const { getBlockStyles } = select( 'core/blocks' ); const selectedBlockClientId = getSelectedBlockClientId(); const selectedBlockName = selectedBlockClientId && getBlockName( selectedBlockClientId ); diff --git a/packages/editor/src/components/block-list-appender/index.js b/packages/editor/src/components/block-list-appender/index.js index 21ac587e42cd6..5af9c48a66f04 100644 --- a/packages/editor/src/components/block-list-appender/index.js +++ b/packages/editor/src/components/block-list-appender/index.js @@ -65,7 +65,7 @@ export default withSelect( ( select, { rootClientId } ) => { getBlockOrder, canInsertBlockType, getTemplateLock, - } = select( 'core/editor' ); + } = select( 'core/block-editor' ); return { isLocked: !! getTemplateLock( rootClientId ), diff --git a/packages/editor/src/components/block-list/block-html.js b/packages/editor/src/components/block-list/block-html.js index fc7b3da86ef08..9ec13410b367a 100644 --- a/packages/editor/src/components/block-list/block-html.js +++ b/packages/editor/src/components/block-list/block-html.js @@ -67,11 +67,11 @@ export class BlockHTML extends Component { export default compose( [ withSelect( ( select, ownProps ) => ( { - block: select( 'core/editor' ).getBlock( ownProps.clientId ), + block: select( 'core/block-editor' ).getBlock( ownProps.clientId ), } ) ), withDispatch( ( dispatch ) => ( { onChange( clientId, attributes, originalContent, isValid ) { - dispatch( 'core/editor' ).updateBlock( clientId, { attributes, originalContent, isValid } ); + dispatch( 'core/block-editor' ).updateBlock( clientId, { attributes, originalContent, isValid } ); }, } ) ), ] )( BlockHTML ); diff --git a/packages/editor/src/components/block-list/block-invalid-warning.js b/packages/editor/src/components/block-list/block-invalid-warning.js index 265e36c78a2e8..7d9efe1693523 100644 --- a/packages/editor/src/components/block-list/block-invalid-warning.js +++ b/packages/editor/src/components/block-list/block-invalid-warning.js @@ -101,10 +101,10 @@ const recoverBlock = ( { name, attributes, innerBlocks } ) => createBlock( name, export default compose( [ withSelect( ( select, { clientId } ) => ( { - block: select( 'core/editor' ).getBlock( clientId ), + block: select( 'core/block-editor' ).getBlock( clientId ), } ) ), withDispatch( ( dispatch, { block } ) => { - const { replaceBlock } = dispatch( 'core/editor' ); + const { replaceBlock } = dispatch( 'core/block-editor' ); return { convertToClassic() { diff --git a/packages/editor/src/components/block-list/block.js b/packages/editor/src/components/block-list/block.js index 22b037c7e2e52..4a977903e44c7 100644 --- a/packages/editor/src/components/block-list/block.js +++ b/packages/editor/src/components/block-list/block.js @@ -637,7 +637,7 @@ const applyWithSelect = withSelect( hasSelectedInnerBlock, getTemplateLock, __unstableGetBlockWithoutInnerBlocks, - } = select( 'core/editor' ); + } = select( 'core/block-editor' ); const block = __unstableGetBlockWithoutInnerBlocks( clientId ); const isSelected = isBlockSelected( clientId ); const { hasFixedToolbar, focusMode } = getEditorSettings(); @@ -683,7 +683,6 @@ const applyWithSelect = withSelect( ); const applyWithDispatch = withDispatch( ( dispatch, ownProps, { select } ) => { - const { getBlockSelectionStart } = select( 'core/editor' ); const { updateBlockAttributes, selectBlock, @@ -694,7 +693,7 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, { select } ) => { mergeBlocks, replaceBlocks, toggleSelection, - } = dispatch( 'core/editor' ); + } = dispatch( 'core/block-editor' ); return { onChange( clientId, attributes ) { @@ -711,7 +710,7 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, { select } ) => { const { clientId, rootClientId } = ownProps; const { getBlockIndex, - } = select( 'core/editor' ); + } = select( 'core/block-editor' ); const index = getBlockIndex( clientId, rootClientId ); insertDefaultBlock( {}, rootClientId, index + 1 ); }, @@ -719,7 +718,7 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, { select } ) => { const { clientId, rootClientId } = ownProps; const { getBlockIndex, - } = select( 'core/editor' ); + } = select( 'core/block-editor' ); const index = getBlockIndex( clientId, rootClientId ); insertBlocks( blocks, index + 1, rootClientId ); }, @@ -731,7 +730,7 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, { select } ) => { const { getPreviousBlockClientId, getNextBlockClientId, - } = select( 'core/editor' ); + } = select( 'core/block-editor' ); if ( forward ) { const nextBlockClientId = getNextBlockClientId( clientId ); @@ -749,7 +748,7 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, { select } ) => { replaceBlocks( [ ownProps.clientId ], blocks ); }, onMetaChange( updatedMeta ) { - const { getEditorSettings } = select( 'core/editor' ); + const { getEditorSettings } = select( 'core/block-editor' ); const onChangeMeta = getEditorSettings().__experimentalMetaSource.onChange; onChangeMeta( updatedMeta ); }, @@ -758,6 +757,10 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, { select } ) => { return; } + const { + getBlockSelectionStart, + } = select( 'core/block-editor' ); + if ( getBlockSelectionStart() ) { multiSelect( getBlockSelectionStart(), ownProps.clientId ); } else { diff --git a/packages/editor/src/components/block-list/breadcrumb.js b/packages/editor/src/components/block-list/breadcrumb.js index ca14b1d346c94..544ef992daf1f 100644 --- a/packages/editor/src/components/block-list/breadcrumb.js +++ b/packages/editor/src/components/block-list/breadcrumb.js @@ -68,7 +68,7 @@ export class BlockBreadcrumb extends Component { export default compose( [ withSelect( ( select, ownProps ) => { - const { getBlockRootClientId } = select( 'core/editor' ); + const { getBlockRootClientId } = select( 'core/block-editor' ); const { clientId } = ownProps; return { diff --git a/packages/editor/src/components/block-list/hover-area.js b/packages/editor/src/components/block-list/hover-area.js index ff0a16144b4f0..d7091552b4885 100644 --- a/packages/editor/src/components/block-list/hover-area.js +++ b/packages/editor/src/components/block-list/hover-area.js @@ -76,7 +76,7 @@ class HoverArea extends Component { export default withSelect( ( select ) => { return { - isRTL: select( 'core/editor' ).getEditorSettings().isRTL, + isRTL: select( 'core/block-editor' ).getEditorSettings().isRTL, }; } )( HoverArea ); diff --git a/packages/editor/src/components/block-list/index.js b/packages/editor/src/components/block-list/index.js index 6ab8cd3956239..cc998be715c68 100644 --- a/packages/editor/src/components/block-list/index.js +++ b/packages/editor/src/components/block-list/index.js @@ -243,7 +243,7 @@ export default compose( [ getSelectedBlockClientId, getMultiSelectedBlockClientIds, hasMultiSelection, - } = select( 'core/editor' ); + } = select( 'core/block-editor' ); const { rootClientId } = ownProps; return { @@ -262,7 +262,7 @@ export default compose( [ startMultiSelect, stopMultiSelect, multiSelect, - } = dispatch( 'core/editor' ); + } = dispatch( 'core/block-editor' ); return { onStartMultiSelect: startMultiSelect, diff --git a/packages/editor/src/components/block-list/insertion-point.js b/packages/editor/src/components/block-list/insertion-point.js index f44f4b69d86e7..b0c7b5b270f16 100644 --- a/packages/editor/src/components/block-list/insertion-point.js +++ b/packages/editor/src/components/block-list/insertion-point.js @@ -85,7 +85,7 @@ export default withSelect( ( select, { clientId, rootClientId } ) => { getBlockIndex, getBlockInsertionPoint, isBlockInsertionPointVisible, - } = select( 'core/editor' ); + } = select( 'core/block-editor' ); const blockIndex = getBlockIndex( clientId, rootClientId ); const insertionPoint = getBlockInsertionPoint(); const showInsertionPoint = ( diff --git a/packages/editor/src/components/block-list/multi-controls.js b/packages/editor/src/components/block-list/multi-controls.js index d00afc5a6af6d..5fb34625389ee 100644 --- a/packages/editor/src/components/block-list/multi-controls.js +++ b/packages/editor/src/components/block-list/multi-controls.js @@ -41,7 +41,7 @@ export default withSelect( ( select, { clientId } ) => { isMultiSelecting, getBlockIndex, getBlockCount, - } = select( 'core/editor' ); + } = select( 'core/block-editor' ); const clientIds = getMultiSelectedBlockClientIds(); const firstIndex = getBlockIndex( first( clientIds ), clientId ); const lastIndex = getBlockIndex( last( clientIds ), clientId ); diff --git a/packages/editor/src/components/block-mover/index.js b/packages/editor/src/components/block-mover/index.js index 1996740feb40a..0ea8166cc279c 100644 --- a/packages/editor/src/components/block-mover/index.js +++ b/packages/editor/src/components/block-mover/index.js @@ -117,7 +117,7 @@ export class BlockMover extends Component { export default compose( withSelect( ( select, { clientIds } ) => { - const { getBlock, getBlockIndex, getTemplateLock, getBlockRootClientId } = select( 'core/editor' ); + const { getBlock, getBlockIndex, getTemplateLock, getBlockRootClientId } = select( 'core/block-editor' ); const firstClientId = first( castArray( clientIds ) ); const block = getBlock( firstClientId ); const rootClientId = getBlockRootClientId( first( castArray( clientIds ) ) ); @@ -130,7 +130,7 @@ export default compose( }; } ), withDispatch( ( dispatch, { clientIds, rootClientId } ) => { - const { moveBlocksDown, moveBlocksUp } = dispatch( 'core/editor' ); + const { moveBlocksDown, moveBlocksUp } = dispatch( 'core/block-editor' ); return { onMoveDown: partial( moveBlocksDown, clientIds, rootClientId ), onMoveUp: partial( moveBlocksUp, clientIds, rootClientId ), diff --git a/packages/editor/src/components/block-navigation/dropdown.js b/packages/editor/src/components/block-navigation/dropdown.js index bfcb5f3f0e2d3..104279fc877b1 100644 --- a/packages/editor/src/components/block-navigation/dropdown.js +++ b/packages/editor/src/components/block-navigation/dropdown.js @@ -50,7 +50,7 @@ function BlockNavigationDropdown( { hasBlocks, isTextModeEnabled } ) { export default withSelect( ( select ) => { return { - hasBlocks: !! select( 'core/editor' ).getBlockCount(), + hasBlocks: !! select( 'core/block-editor' ).getBlockCount(), isTextModeEnabled: select( 'core/edit-post' ).getEditorMode() === 'text', }; } )( BlockNavigationDropdown ); diff --git a/packages/editor/src/components/block-navigation/index.js b/packages/editor/src/components/block-navigation/index.js index 98a55c38af7d7..a3f67764fa36a 100644 --- a/packages/editor/src/components/block-navigation/index.js +++ b/packages/editor/src/components/block-navigation/index.js @@ -110,7 +110,7 @@ export default compose( getBlockHierarchyRootClientId, getBlock, getBlocks, - } = select( 'core/editor' ); + } = select( 'core/block-editor' ); const selectedBlockClientId = getSelectedBlockClientId(); return { rootBlocks: getBlocks(), @@ -121,7 +121,7 @@ export default compose( withDispatch( ( dispatch, { onSelect = noop } ) => { return { selectBlock( clientId ) { - dispatch( 'core/editor' ).selectBlock( clientId ); + dispatch( 'core/block-editor' ).selectBlock( clientId ); onSelect( clientId ); }, }; diff --git a/packages/editor/src/components/block-selection-clearer/index.js b/packages/editor/src/components/block-selection-clearer/index.js index 7e092163d81b8..be39e5df9e7de 100644 --- a/packages/editor/src/components/block-selection-clearer/index.js +++ b/packages/editor/src/components/block-selection-clearer/index.js @@ -60,7 +60,7 @@ class BlockSelectionClearer extends Component { export default compose( [ withSelect( ( select ) => { - const { hasSelectedBlock, hasMultiSelection } = select( 'core/editor' ); + const { hasSelectedBlock, hasMultiSelection } = select( 'core/block-editor' ); return { hasSelectedBlock: hasSelectedBlock(), @@ -68,7 +68,7 @@ export default compose( [ }; } ), withDispatch( ( dispatch ) => { - const { clearSelectedBlock } = dispatch( 'core/editor' ); + const { clearSelectedBlock } = dispatch( 'core/block-editor' ); return { clearSelectedBlock }; } ), ] )( BlockSelectionClearer ); diff --git a/packages/editor/src/components/block-settings-menu/block-html-convert-button.js b/packages/editor/src/components/block-settings-menu/block-html-convert-button.js index 2e49c011e6d7f..3527d7d0d460c 100644 --- a/packages/editor/src/components/block-settings-menu/block-html-convert-button.js +++ b/packages/editor/src/components/block-settings-menu/block-html-convert-button.js @@ -12,7 +12,7 @@ import BlockConvertButton from './block-convert-button'; export default compose( withSelect( ( select, { clientId } ) => { - const block = select( 'core/editor' ).getBlock( clientId ); + const block = select( 'core/block-editor' ).getBlock( clientId ); return { block, @@ -20,7 +20,7 @@ export default compose( }; } ), withDispatch( ( dispatch, { block } ) => ( { - onClick: () => dispatch( 'core/editor' ).replaceBlocks( + onClick: () => dispatch( 'core/block-editor' ).replaceBlocks( block.clientId, rawHandler( { HTML: getBlockContent( block ) } ), ), diff --git a/packages/editor/src/components/block-settings-menu/block-mode-toggle.js b/packages/editor/src/components/block-settings-menu/block-mode-toggle.js index a8702f58f2574..ffafcffff75f5 100644 --- a/packages/editor/src/components/block-settings-menu/block-mode-toggle.js +++ b/packages/editor/src/components/block-settings-menu/block-mode-toggle.js @@ -35,7 +35,7 @@ export function BlockModeToggle( { blockType, mode, onToggleMode, small = false export default compose( [ withSelect( ( select, { clientId } ) => { - const { getBlock, getBlockMode } = select( 'core/editor' ); + const { getBlock, getBlockMode } = select( 'core/block-editor' ); const block = getBlock( clientId ); return { @@ -45,7 +45,7 @@ export default compose( [ } ), withDispatch( ( dispatch, { onToggle = noop, clientId } ) => ( { onToggleMode() { - dispatch( 'core/editor' ).toggleBlockMode( clientId ); + dispatch( 'core/block-editor' ).toggleBlockMode( clientId ); onToggle(); }, } ) ), diff --git a/packages/editor/src/components/block-settings-menu/block-unknown-convert-button.js b/packages/editor/src/components/block-settings-menu/block-unknown-convert-button.js index e2bc0d3f8e0b7..46eda45141ad1 100644 --- a/packages/editor/src/components/block-settings-menu/block-unknown-convert-button.js +++ b/packages/editor/src/components/block-settings-menu/block-unknown-convert-button.js @@ -12,7 +12,7 @@ import BlockConvertButton from './block-convert-button'; export default compose( withSelect( ( select, { clientId } ) => { - const block = select( 'core/editor' ).getBlock( clientId ); + const block = select( 'core/block-editor' ).getBlock( clientId ); return { block, @@ -20,7 +20,7 @@ export default compose( }; } ), withDispatch( ( dispatch, { block } ) => ( { - onClick: () => dispatch( 'core/editor' ).replaceBlocks( + onClick: () => dispatch( 'core/block-editor' ).replaceBlocks( block.clientId, rawHandler( { HTML: serialize( block ) } ) ), diff --git a/packages/editor/src/components/block-settings-menu/index.js b/packages/editor/src/components/block-settings-menu/index.js index 8436276c8f99b..b07452bdce393 100644 --- a/packages/editor/src/components/block-settings-menu/index.js +++ b/packages/editor/src/components/block-settings-menu/index.js @@ -137,7 +137,7 @@ export function BlockSettingsMenu( { clientIds, onSelect } ) { } export default withDispatch( ( dispatch ) => { - const { selectBlock } = dispatch( 'core/editor' ); + const { selectBlock } = dispatch( 'core/block-editor' ); return { onSelect( clientId ) { diff --git a/packages/editor/src/components/block-settings-menu/reusable-block-convert-button.js b/packages/editor/src/components/block-settings-menu/reusable-block-convert-button.js index 2e528a01e7d91..3ec5076891eec 100644 --- a/packages/editor/src/components/block-settings-menu/reusable-block-convert-button.js +++ b/packages/editor/src/components/block-settings-menu/reusable-block-convert-button.js @@ -52,6 +52,8 @@ export default compose( [ const { getBlocksByClientId, canInsertBlockType, + } = select( 'core/block-editor' ); + const { __experimentalGetReusableBlock: getReusableBlock, } = select( 'core/editor' ); const { canUser } = select( 'core' ); diff --git a/packages/editor/src/components/block-settings-menu/reusable-block-delete-button.js b/packages/editor/src/components/block-settings-menu/reusable-block-delete-button.js index 5a73912b36ee7..a6271874d6533 100644 --- a/packages/editor/src/components/block-settings-menu/reusable-block-delete-button.js +++ b/packages/editor/src/components/block-settings-menu/reusable-block-delete-button.js @@ -31,12 +31,9 @@ export function ReusableBlockDeleteButton( { isVisible, isDisabled, onDelete } ) export default compose( [ withSelect( ( select, { clientId } ) => { - const { - getBlock, - __experimentalGetReusableBlock: getReusableBlock, - } = select( 'core/editor' ); + const { getBlock } = select( 'core/block-editor' ); const { canUser } = select( 'core' ); - + const { __experimentalGetReusableBlock: getReusableBlock } = select( 'core/editor' ); const block = getBlock( clientId ); const reusableBlock = block && isReusableBlock( block ) ? @@ -52,7 +49,7 @@ export default compose( [ const { __experimentalDeleteReusableBlock: deleteReusableBlock, } = dispatch( 'core/editor' ); - const { getBlock } = select( 'core/editor' ); + const { getBlock } = select( 'core/block-editor' ); return { onDelete() { diff --git a/packages/editor/src/components/block-styles/index.js b/packages/editor/src/components/block-styles/index.js index c44f8f686e191..ae7da2d26a559 100644 --- a/packages/editor/src/components/block-styles/index.js +++ b/packages/editor/src/components/block-styles/index.js @@ -143,7 +143,7 @@ function BlockStyles( { export default compose( [ withSelect( ( select, { clientId } ) => { - const { getBlock } = select( 'core/editor' ); + const { getBlock } = select( 'core/block-editor' ); const { getBlockStyles } = select( 'core/blocks' ); const block = getBlock( clientId ); const blockType = getBlockType( block.name ); @@ -159,7 +159,7 @@ export default compose( [ withDispatch( ( dispatch, { clientId } ) => { return { onChangeClassName( newClassName ) { - dispatch( 'core/editor' ).updateBlockAttributes( clientId, { + dispatch( 'core/block-editor' ).updateBlockAttributes( clientId, { className: newClassName, } ); }, diff --git a/packages/editor/src/components/block-switcher/index.js b/packages/editor/src/components/block-switcher/index.js index 4a7413fb36422..25ef98edf8230 100644 --- a/packages/editor/src/components/block-switcher/index.js +++ b/packages/editor/src/components/block-switcher/index.js @@ -175,7 +175,7 @@ export class BlockSwitcher extends Component { export default compose( withSelect( ( select, { clientIds } ) => { - const { getBlocksByClientId, getBlockRootClientId, getInserterItems } = select( 'core/editor' ); + const { getBlocksByClientId, getBlockRootClientId, getInserterItems } = select( 'core/block-editor' ); const { getBlockStyles } = select( 'core/blocks' ); const rootClientId = getBlockRootClientId( first( castArray( clientIds ) ) ); const blocks = getBlocksByClientId( clientIds ); @@ -189,7 +189,7 @@ export default compose( } ), withDispatch( ( dispatch, ownProps ) => ( { onTransform( blocks, name ) { - dispatch( 'core/editor' ).replaceBlocks( + dispatch( 'core/block-editor' ).replaceBlocks( ownProps.clientIds, switchToBlockType( blocks, name ) ); diff --git a/packages/editor/src/components/block-switcher/multi-blocks-switcher.js b/packages/editor/src/components/block-switcher/multi-blocks-switcher.js index 25d9e8c339e7a..8adae420dab99 100644 --- a/packages/editor/src/components/block-switcher/multi-blocks-switcher.js +++ b/packages/editor/src/components/block-switcher/multi-blocks-switcher.js @@ -19,7 +19,7 @@ export function MultiBlocksSwitcher( { isMultiBlockSelection, selectedBlockClien export default withSelect( ( select ) => { - const selectedBlockClientIds = select( 'core/editor' ).getMultiSelectedBlockClientIds(); + const selectedBlockClientIds = select( 'core/block-editor' ).getMultiSelectedBlockClientIds(); return { isMultiBlockSelection: selectedBlockClientIds.length > 1, selectedBlockClientIds, diff --git a/packages/editor/src/components/block-title/index.js b/packages/editor/src/components/block-title/index.js index 6629c16768437..99519655419e1 100644 --- a/packages/editor/src/components/block-title/index.js +++ b/packages/editor/src/components/block-title/index.js @@ -32,7 +32,7 @@ export function BlockTitle( { name } ) { } export default withSelect( ( select, ownProps ) => { - const { getBlockName } = select( 'core/editor' ); + const { getBlockName } = select( 'core/block-editor' ); const { clientId } = ownProps; return { diff --git a/packages/editor/src/components/block-toolbar/index.js b/packages/editor/src/components/block-toolbar/index.js index 3da857dbd1b99..2c3fa018731e9 100644 --- a/packages/editor/src/components/block-toolbar/index.js +++ b/packages/editor/src/components/block-toolbar/index.js @@ -47,7 +47,7 @@ export default withSelect( ( select ) => { getBlockMode, getMultiSelectedBlockClientIds, isBlockValid, - } = select( 'core/editor' ); + } = select( 'core/block-editor' ); const selectedBlockClientId = getSelectedBlockClientId(); const blockClientIds = selectedBlockClientId ? [ selectedBlockClientId ] : diff --git a/packages/editor/src/components/color-palette/with-color-context.js b/packages/editor/src/components/color-palette/with-color-context.js index 7112550219bbf..48eaa8085fca9 100644 --- a/packages/editor/src/components/color-palette/with-color-context.js +++ b/packages/editor/src/components/color-palette/with-color-context.js @@ -13,7 +13,7 @@ import { withSelect } from '@wordpress/data'; export default createHigherOrderComponent( withSelect( ( select, ownProps ) => { - const settings = select( 'core/editor' ).getEditorSettings(); + const settings = select( 'core/block-editor' ).getEditorSettings(); const colors = ownProps.colors === undefined ? settings.colors : ownProps.colors; diff --git a/packages/editor/src/components/colors/with-colors.js b/packages/editor/src/components/colors/with-colors.js index 3afb5e7d7469c..11524408b4a29 100644 --- a/packages/editor/src/components/colors/with-colors.js +++ b/packages/editor/src/components/colors/with-colors.js @@ -36,7 +36,7 @@ const withCustomColorPalette = ( colorsArray ) => createHigherOrderComponent( ( * @return {function} The higher order component. */ const withEditorColorPalette = () => withSelect( ( select ) => { - const settings = select( 'core/editor' ).getEditorSettings(); + const settings = select( 'core/block-editor' ).getEditorSettings(); return { colors: get( settings, [ 'colors' ], DEFAULT_COLORS ), }; diff --git a/packages/editor/src/components/copy-handler/index.js b/packages/editor/src/components/copy-handler/index.js index 90d8324d033fc..3942eaae71b03 100644 --- a/packages/editor/src/components/copy-handler/index.js +++ b/packages/editor/src/components/copy-handler/index.js @@ -37,8 +37,8 @@ export default compose( [ getMultiSelectedBlockClientIds, getSelectedBlockClientId, hasMultiSelection, - } = select( 'core/editor' ); - const { removeBlocks } = dispatch( 'core/editor' ); + } = select( 'core/block-editor' ); + const { removeBlocks } = dispatch( 'core/block-editor' ); const onCopy = ( event ) => { const selectedBlockClientIds = getSelectedBlockClientId() ? diff --git a/packages/editor/src/components/default-block-appender/index.js b/packages/editor/src/components/default-block-appender/index.js index 594c357064de6..5a320ba4c105d 100644 --- a/packages/editor/src/components/default-block-appender/index.js +++ b/packages/editor/src/components/default-block-appender/index.js @@ -74,7 +74,7 @@ export function DefaultBlockAppender( { export default compose( withState( { hovered: false } ), withSelect( ( select, ownProps ) => { - const { getBlockCount, getBlockName, isBlockValid, getEditorSettings, getTemplateLock } = select( 'core/editor' ); + const { getBlockCount, getBlockName, isBlockValid, getEditorSettings, getTemplateLock } = select( 'core/block-editor' ); const isEmpty = ! getBlockCount( ownProps.rootClientId ); const isLastBlockDefault = getBlockName( ownProps.lastBlockClientId ) === getDefaultBlockName(); @@ -92,7 +92,7 @@ export default compose( const { insertDefaultBlock, startTyping, - } = dispatch( 'core/editor' ); + } = dispatch( 'core/block-editor' ); return { onAppend() { diff --git a/packages/editor/src/components/default-block-appender/index.native.js b/packages/editor/src/components/default-block-appender/index.native.js index 8255afc3cd8fc..ab34b5ebbde21 100644 --- a/packages/editor/src/components/default-block-appender/index.native.js +++ b/packages/editor/src/components/default-block-appender/index.native.js @@ -49,7 +49,7 @@ export function DefaultBlockAppender( { export default compose( withSelect( ( select, ownProps ) => { - const { getBlockCount, getEditorSettings, getTemplateLock } = select( 'core/editor' ); + const { getBlockCount, getEditorSettings, getTemplateLock } = select( 'core/block-editor' ); const isEmpty = ! getBlockCount( ownProps.rootClientId ); const { bodyPlaceholder } = getEditorSettings(); @@ -64,7 +64,7 @@ export default compose( const { insertDefaultBlock, startTyping, - } = dispatch( 'core/editor' ); + } = dispatch( 'core/block-editor' ); return { onAppend() { diff --git a/packages/editor/src/components/document-outline/check.js b/packages/editor/src/components/document-outline/check.js index 80da12a58e882..ccbf66c27d150 100644 --- a/packages/editor/src/components/document-outline/check.js +++ b/packages/editor/src/components/document-outline/check.js @@ -19,5 +19,5 @@ function DocumentOutlineCheck( { blocks, children } ) { } export default withSelect( ( select ) => ( { - blocks: select( 'core/editor' ).getBlocks(), + blocks: select( 'core/block-editor' ).getBlocks(), } ) )( DocumentOutlineCheck ); diff --git a/packages/editor/src/components/document-outline/index.js b/packages/editor/src/components/document-outline/index.js index 258bb792e7e3f..689144d040acc 100644 --- a/packages/editor/src/components/document-outline/index.js +++ b/packages/editor/src/components/document-outline/index.js @@ -140,7 +140,7 @@ export const DocumentOutline = ( { blocks = [], title, onSelect, isTitleSupporte export default compose( withSelect( ( select ) => { - const { getEditedPostAttribute, getBlocks } = select( 'core/editor' ); + const { getEditedPostAttribute, getBlocks } = select( 'core/block-editor' ); const { getPostType } = select( 'core' ); const postType = getPostType( getEditedPostAttribute( 'type' ) ); @@ -151,7 +151,7 @@ export default compose( }; } ), withDispatch( ( dispatch ) => { - const { selectBlock } = dispatch( 'core/editor' ); + const { selectBlock } = dispatch( 'core/block-editor' ); return { onSelect: selectBlock, }; diff --git a/packages/editor/src/components/font-sizes/font-size-picker.js b/packages/editor/src/components/font-sizes/font-size-picker.js index 20e3fea9c2ad0..6989bb2233669 100644 --- a/packages/editor/src/components/font-sizes/font-size-picker.js +++ b/packages/editor/src/components/font-sizes/font-size-picker.js @@ -9,7 +9,7 @@ export default withSelect( const { disableCustomFontSizes, fontSizes, - } = select( 'core/editor' ).getEditorSettings(); + } = select( 'core/block-editor' ).getEditorSettings(); return { disableCustomFontSizes, diff --git a/packages/editor/src/components/font-sizes/with-font-sizes.js b/packages/editor/src/components/font-sizes/with-font-sizes.js index 3c9be5c1a4440..39953baf801f2 100644 --- a/packages/editor/src/components/font-sizes/with-font-sizes.js +++ b/packages/editor/src/components/font-sizes/with-font-sizes.js @@ -38,7 +38,7 @@ export default ( ...fontSizeNames ) => { return createHigherOrderComponent( compose( [ withSelect( ( select ) => { - const { fontSizes } = select( 'core/editor' ).getEditorSettings(); + const { fontSizes } = select( 'core/block-editor' ).getEditorSettings(); return { fontSizes, }; diff --git a/packages/editor/src/components/global-keyboard-shortcuts/save-shortcut.js b/packages/editor/src/components/global-keyboard-shortcuts/save-shortcut.js index c2a117ec6816f..826915136888c 100644 --- a/packages/editor/src/components/global-keyboard-shortcuts/save-shortcut.js +++ b/packages/editor/src/components/global-keyboard-shortcuts/save-shortcut.js @@ -29,9 +29,7 @@ export default compose( [ }; } ), withDispatch( ( dispatch, ownProps, { select } ) => { - const { - savePost, - } = dispatch( 'core/editor' ); + const { savePost } = dispatch( 'core/editor' ); return { onSave() { diff --git a/packages/editor/src/components/global-keyboard-shortcuts/visual-editor-shortcuts.js b/packages/editor/src/components/global-keyboard-shortcuts/visual-editor-shortcuts.js index 9242734d0e9e4..02fe1d0e94b7e 100644 --- a/packages/editor/src/components/global-keyboard-shortcuts/visual-editor-shortcuts.js +++ b/packages/editor/src/components/global-keyboard-shortcuts/visual-editor-shortcuts.js @@ -146,7 +146,7 @@ const EnhancedVisualEditorGlobalKeyboardShortcuts = compose( [ getBlockRootClientId, getTemplateLock, getSelectedBlockClientId, - } = select( 'core/editor' ); + } = select( 'core/block-editor' ); const selectedBlockClientId = getSelectedBlockClientId(); const selectedBlockClientIds = selectedBlockClientId ? [ selectedBlockClientId ] : getMultiSelectedBlockClientIds(); @@ -161,12 +161,16 @@ const EnhancedVisualEditorGlobalKeyboardShortcuts = compose( [ }; } ), withDispatch( ( dispatch ) => { + // This component should probably be split into to + // A block editor specific one and a post editor one. const { clearSelectedBlock, multiSelect, + removeBlocks, + } = dispatch( 'core/block-editor' ); + const { redo, undo, - removeBlocks, } = dispatch( 'core/editor' ); return { diff --git a/packages/editor/src/components/inner-blocks/index.js b/packages/editor/src/components/inner-blocks/index.js index c4cb0ac5d4099..eba0122e68856 100644 --- a/packages/editor/src/components/inner-blocks/index.js +++ b/packages/editor/src/components/inner-blocks/index.js @@ -132,7 +132,7 @@ InnerBlocks = compose( [ getBlockListSettings, getBlockRootClientId, getTemplateLock, - } = select( 'core/editor' ); + } = select( 'core/block-editor' ); const { clientId } = ownProps; const rootClientId = getBlockRootClientId( clientId ); return { @@ -147,7 +147,7 @@ InnerBlocks = compose( [ replaceBlocks, insertBlocks, updateBlockListSettings, - } = dispatch( 'core/editor' ); + } = dispatch( 'core/block-editor' ); const { block, clientId, templateInsertUpdatesSelection = true } = ownProps; return { diff --git a/packages/editor/src/components/inserter-with-shortcuts/index.js b/packages/editor/src/components/inserter-with-shortcuts/index.js index 935a7fa67095c..8e64fc6eaa580 100644 --- a/packages/editor/src/components/inserter-with-shortcuts/index.js +++ b/packages/editor/src/components/inserter-with-shortcuts/index.js @@ -49,7 +49,7 @@ function InserterWithShortcuts( { items, isLocked, onInsert } ) { export default compose( withSelect( ( select, { rootClientId } ) => { - const { getInserterItems, getTemplateLock } = select( 'core/editor' ); + const { getInserterItems, getTemplateLock } = select( 'core/block-editor' ); return { items: getInserterItems( rootClientId ), isLocked: !! getTemplateLock( rootClientId ), @@ -62,9 +62,9 @@ export default compose( onInsert( { name, initialAttributes } ) { const block = createBlock( name, initialAttributes ); if ( clientId ) { - dispatch( 'core/editor' ).replaceBlocks( clientId, block ); + dispatch( 'core/block-editor' ).replaceBlocks( clientId, block ); } else { - dispatch( 'core/editor' ).insertBlock( block, undefined, rootClientId ); + dispatch( 'core/block-editor' ).insertBlock( block, undefined, rootClientId ); } }, }; diff --git a/packages/editor/src/components/inserter/child-blocks.js b/packages/editor/src/components/inserter/child-blocks.js index 312e2cc69ebe9..832c1e2b73038 100644 --- a/packages/editor/src/components/inserter/child-blocks.js +++ b/packages/editor/src/components/inserter/child-blocks.js @@ -32,7 +32,7 @@ export default compose( } = select( 'core/blocks' ); const { getBlockName, - } = select( 'core/editor' ); + } = select( 'core/block-editor' ); const rootBlockName = getBlockName( rootClientId ); const rootBlockType = getBlockType( rootBlockName ); return { diff --git a/packages/editor/src/components/inserter/index.js b/packages/editor/src/components/inserter/index.js index 8c181f3cbd24c..4dfd3303fd4e5 100644 --- a/packages/editor/src/components/inserter/index.js +++ b/packages/editor/src/components/inserter/index.js @@ -103,8 +103,13 @@ class Inserter extends Component { export default compose( [ withSelect( ( select, { rootClientId } ) => { const { - getEditedPostAttribute, hasInserterItems, + } = select( 'core/block-editor' ); + + // The title should be removed from the inserter + // or replaced by a prop passed to the inserter. + const { + getEditedPostAttribute, } = select( 'core/editor' ); return { diff --git a/packages/editor/src/components/inserter/menu.js b/packages/editor/src/components/inserter/menu.js index 7fb3cb2ffdca6..023a4b1102fe5 100644 --- a/packages/editor/src/components/inserter/menu.js +++ b/packages/editor/src/components/inserter/menu.js @@ -349,7 +349,7 @@ export default compose( const { getInserterItems, getBlockName, - } = select( 'core/editor' ); + } = select( 'core/block-editor' ); const { getChildBlockNames, } = select( 'core/blocks' ); @@ -364,9 +364,13 @@ export default compose( } ), withDispatch( ( dispatch, ownProps, { select } ) => { const { - __experimentalFetchReusableBlocks: fetchReusableBlocks, showInsertionPoint, hideInsertionPoint, + } = dispatch( 'core/block-editor' ); + + // This should be an external action provided in the editor settings. + const { + __experimentalFetchReusableBlocks: fetchReusableBlocks, } = dispatch( 'core/editor' ); // To avoid duplication, getInsertionPoint is extracted and used in two event handlers @@ -380,7 +384,7 @@ export default compose( getBlockRootClientId, getBlockSelectionEnd, getBlockOrder, - } = select( 'core/editor' ); + } = select( 'core/block-editor' ); const { clientId, rootClientId, isAppender } = ownProps; // If the clientId is defined, we insert at the position of the block. @@ -419,10 +423,10 @@ export default compose( const { replaceBlocks, insertBlock, - } = dispatch( 'core/editor' ); + } = dispatch( 'core/block-editor' ); const { getSelectedBlock, - } = select( 'core/editor' ); + } = select( 'core/block-editor' ); const { isAppender } = ownProps; const { name, initialAttributes } = item; const selectedBlock = getSelectedBlock(); diff --git a/packages/editor/src/components/multi-select-scroll-into-view/index.js b/packages/editor/src/components/multi-select-scroll-into-view/index.js index b616e370d0287..e49a90a520cbe 100644 --- a/packages/editor/src/components/multi-select-scroll-into-view/index.js +++ b/packages/editor/src/components/multi-select-scroll-into-view/index.js @@ -58,7 +58,7 @@ class MultiSelectScrollIntoView extends Component { } export default withSelect( ( select ) => { - const { getLastMultiSelectedBlockClientId } = select( 'core/editor' ); + const { getLastMultiSelectedBlockClientId } = select( 'core/block-editor' ); return { extentClientId: getLastMultiSelectedBlockClientId(), diff --git a/packages/editor/src/components/multi-selection-inspector/index.js b/packages/editor/src/components/multi-selection-inspector/index.js index 51b48333923a5..5032676ae3ab1 100644 --- a/packages/editor/src/components/multi-selection-inspector/index.js +++ b/packages/editor/src/components/multi-selection-inspector/index.js @@ -42,7 +42,7 @@ function MultiSelectionInspector( { blocks } ) { } export default withSelect( ( select ) => { - const { getMultiSelectedBlocks } = select( 'core/editor' ); + const { getMultiSelectedBlocks } = select( 'core/block-editor' ); return { blocks: getMultiSelectedBlocks(), }; diff --git a/packages/editor/src/components/observe-typing/index.js b/packages/editor/src/components/observe-typing/index.js index fa3f669da5410..a45931bcee0fd 100644 --- a/packages/editor/src/components/observe-typing/index.js +++ b/packages/editor/src/components/observe-typing/index.js @@ -201,14 +201,14 @@ class ObserveTyping extends Component { export default compose( [ withSelect( ( select ) => { - const { isTyping } = select( 'core/editor' ); + const { isTyping } = select( 'core/block-editor' ); return { isTyping: isTyping(), }; } ), withDispatch( ( dispatch ) => { - const { startTyping, stopTyping } = dispatch( 'core/editor' ); + const { startTyping, stopTyping } = dispatch( 'core/block-editor' ); return { onStartTyping: startTyping, diff --git a/packages/editor/src/components/page-attributes/check.js b/packages/editor/src/components/page-attributes/check.js index 56be444a04985..014b2aa29136a 100644 --- a/packages/editor/src/components/page-attributes/check.js +++ b/packages/editor/src/components/page-attributes/check.js @@ -20,8 +20,11 @@ export function PageAttributesCheck( { availableTemplates, postType, children } } export default withSelect( ( select ) => { - const { getEditedPostAttribute, getEditorSettings } = select( 'core/editor' ); + const { getEditedPostAttribute } = select( 'core/editor' ); + const { getEditorSettings } = select( 'core/block-editor' ); const { getPostType } = select( 'core' ); + + // This setting should not live in the block-editor module. const { availableTemplates } = getEditorSettings(); return { postType: getPostType( getEditedPostAttribute( 'type' ) ), diff --git a/packages/editor/src/components/page-attributes/template.js b/packages/editor/src/components/page-attributes/template.js index 3646a05e3206f..8e6d9cef689cd 100644 --- a/packages/editor/src/components/page-attributes/template.js +++ b/packages/editor/src/components/page-attributes/template.js @@ -33,7 +33,8 @@ export function PageTemplate( { availableTemplates, selectedTemplate, onUpdate } export default compose( withSelect( ( select ) => { - const { getEditedPostAttribute, getEditorSettings } = select( 'core/editor' ); + const { getEditedPostAttribute } = select( 'core/editor' ); + const { getEditorSettings } = select( 'core/block-editor' ); const { availableTemplates } = getEditorSettings(); return { selectedTemplate: getEditedPostAttribute( 'template' ), diff --git a/packages/editor/src/components/post-format/check.js b/packages/editor/src/components/post-format/check.js index cd124553a7cf1..d13e354655e9d 100644 --- a/packages/editor/src/components/post-format/check.js +++ b/packages/editor/src/components/post-format/check.js @@ -15,7 +15,8 @@ function PostFormatCheck( { disablePostFormats, ...props } ) { export default withSelect( ( select ) => { - const editorSettings = select( 'core/editor' ).getEditorSettings(); + // This setting should not live in the block-editor's store. + const editorSettings = select( 'core/block-editor' ).getEditorSettings(); return { disablePostFormats: editorSettings.disablePostFormats, }; diff --git a/packages/editor/src/components/post-locked-modal/index.js b/packages/editor/src/components/post-locked-modal/index.js index ecdb4b6a54b32..8a9b88cbc2109 100644 --- a/packages/editor/src/components/post-locked-modal/index.js +++ b/packages/editor/src/components/post-locked-modal/index.js @@ -214,7 +214,6 @@ class PostLockedModal extends Component { export default compose( withSelect( ( select ) => { const { - getEditorSettings, isPostLocked, isPostLockTakeover, getPostLockUser, @@ -222,12 +221,16 @@ export default compose( getActivePostLock, getEditedPostAttribute, } = select( 'core/editor' ); + const { + getEditorSettings, + } = select( 'core/block-editor' ); const { getPostType } = select( 'core' ); return { isLocked: isPostLocked(), isTakeover: isPostLockTakeover(), user: getPostLockUser(), postId: getCurrentPostId(), + // This setting should not live in the block-editor's store. postLockUtils: getEditorSettings().postLockUtils, activePostLock: getActivePostLock(), postType: getPostType( getEditedPostAttribute( 'type' ) ), diff --git a/packages/editor/src/components/post-title/index.js b/packages/editor/src/components/post-title/index.js index 9adb9e5c9b64a..761fcff095df4 100644 --- a/packages/editor/src/components/post-title/index.js +++ b/packages/editor/src/components/post-title/index.js @@ -149,7 +149,8 @@ class PostTitle extends Component { } const applyWithSelect = withSelect( ( select ) => { - const { getEditedPostAttribute, getEditorSettings, isCleanNewPost } = select( 'core/editor' ); + const { getEditedPostAttribute, isCleanNewPost } = select( 'core/editor' ); + const { getEditorSettings } = select( 'core/block-editor' ); const { getPostType } = select( 'core' ); const postType = getPostType( getEditedPostAttribute( 'type' ) ); const { titlePlaceholder, focusMode, hasFixedToolbar } = getEditorSettings(); @@ -167,8 +168,10 @@ const applyWithSelect = withSelect( ( select ) => { const applyWithDispatch = withDispatch( ( dispatch ) => { const { insertDefaultBlock, - editPost, clearSelectedBlock, + } = dispatch( 'core/block-editor' ); + const { + editPost, undo, redo, } = dispatch( 'core/editor' ); diff --git a/packages/editor/src/components/post-title/index.native.js b/packages/editor/src/components/post-title/index.native.js index 8204220b3be6d..55e56d03a101d 100644 --- a/packages/editor/src/components/post-title/index.native.js +++ b/packages/editor/src/components/post-title/index.native.js @@ -106,12 +106,15 @@ class PostTitle extends Component { const applyWithDispatch = withDispatch( ( dispatch ) => { const { - insertDefaultBlock, - clearSelectedBlock, undo, redo, } = dispatch( 'core/editor' ); + const { + insertDefaultBlock, + clearSelectedBlock, + } = dispatch( 'core/block-editor' ); + return { onEnterPress() { insertDefaultBlock( undefined, undefined, 0 ); diff --git a/packages/editor/src/components/preserve-scroll-in-reorder/index.js b/packages/editor/src/components/preserve-scroll-in-reorder/index.js index 25a28bff45d40..e791dea222655 100644 --- a/packages/editor/src/components/preserve-scroll-in-reorder/index.js +++ b/packages/editor/src/components/preserve-scroll-in-reorder/index.js @@ -77,7 +77,7 @@ class PreserveScrollInReorder extends Component { export default withSelect( ( select ) => { return { - blockOrder: select( 'core/editor' ).getBlockOrder(), - selectionStart: select( 'core/editor' ).getBlockSelectionStart(), + blockOrder: select( 'core/block-editor' ).getBlockOrder(), + selectionStart: select( 'core/block-editor' ).getBlockSelectionStart(), }; } )( PreserveScrollInReorder ); diff --git a/packages/editor/src/components/rich-text/index.js b/packages/editor/src/components/rich-text/index.js index d086cf24795fb..ccfe32326d944 100644 --- a/packages/editor/src/components/rich-text/index.js +++ b/packages/editor/src/components/rich-text/index.js @@ -1104,7 +1104,9 @@ const RichTextContainer = compose( [ }; } ), withSelect( ( select ) => { - const { canUserUseUnfilteredHTML, isCaretWithinFormattedText } = select( 'core/editor' ); + // This should probably be moved to the block editor settings. + const { canUserUseUnfilteredHTML } = select( 'core/editor' ); + const { isCaretWithinFormattedText } = select( 'core/block-editor' ); const { getFormatTypes } = select( 'core/rich-text' ); return { diff --git a/packages/editor/src/components/skip-to-selected-block/index.js b/packages/editor/src/components/skip-to-selected-block/index.js index 7fb58d88b59da..419d94b537344 100644 --- a/packages/editor/src/components/skip-to-selected-block/index.js +++ b/packages/editor/src/components/skip-to-selected-block/index.js @@ -26,6 +26,6 @@ const SkipToSelectedBlock = ( { selectedBlockClientId } ) => { export default withSelect( ( select ) => { return { - selectedBlockClientId: select( 'core/editor' ).getBlockSelectionStart(), + selectedBlockClientId: select( 'core/block-editor' ).getBlockSelectionStart(), }; } )( SkipToSelectedBlock ); diff --git a/packages/editor/src/components/table-of-contents/index.js b/packages/editor/src/components/table-of-contents/index.js index 473cc5cd5a1aa..af398e060085c 100644 --- a/packages/editor/src/components/table-of-contents/index.js +++ b/packages/editor/src/components/table-of-contents/index.js @@ -33,6 +33,6 @@ function TableOfContents( { hasBlocks } ) { export default withSelect( ( select ) => { return { - hasBlocks: !! select( 'core/editor' ).getBlockCount(), + hasBlocks: !! select( 'core/block-editor' ).getBlockCount(), }; } )( TableOfContents ); diff --git a/packages/editor/src/components/table-of-contents/panel.js b/packages/editor/src/components/table-of-contents/panel.js index e9f2cc188fd4e..00489ccaf1e1d 100644 --- a/packages/editor/src/components/table-of-contents/panel.js +++ b/packages/editor/src/components/table-of-contents/panel.js @@ -57,7 +57,7 @@ function TableOfContentsPanel( { headingCount, paragraphCount, numberOfBlocks } } export default withSelect( ( select ) => { - const { getGlobalBlockCount } = select( 'core/editor' ); + const { getGlobalBlockCount } = select( 'core/block-editor' ); return { headingCount: getGlobalBlockCount( 'core/heading' ), paragraphCount: getGlobalBlockCount( 'core/paragraph' ), diff --git a/packages/editor/src/components/template-validation-notice/index.js b/packages/editor/src/components/template-validation-notice/index.js index f8ada453dc3f4..f9b612b3e7039 100644 --- a/packages/editor/src/components/template-validation-notice/index.js +++ b/packages/editor/src/components/template-validation-notice/index.js @@ -31,10 +31,10 @@ function TemplateValidationNotice( { isValid, ...props } ) { export default compose( [ withSelect( ( select ) => ( { - isValid: select( 'core/editor' ).isValidTemplate(), + isValid: select( 'core/block-editor' ).isValidTemplate(), } ) ), withDispatch( ( dispatch ) => { - const { setTemplateValidity, synchronizeTemplate } = dispatch( 'core/editor' ); + const { setTemplateValidity, synchronizeTemplate } = dispatch( 'core/block-editor' ); return { resetTemplateValidity: () => setTemplateValidity( true ), synchronizeTemplate, diff --git a/packages/editor/src/components/writing-flow/index.js b/packages/editor/src/components/writing-flow/index.js index 0d4ea96eaa7c4..12ea7da18f2e2 100644 --- a/packages/editor/src/components/writing-flow/index.js +++ b/packages/editor/src/components/writing-flow/index.js @@ -356,7 +356,7 @@ export default compose( [ getLastMultiSelectedBlockClientId, hasMultiSelection, getBlockOrder, - } = select( 'core/editor' ); + } = select( 'core/block-editor' ); const selectedBlockClientId = getSelectedBlockClientId(); const selectionStartClientId = getMultiSelectedBlocksStartClientId(); @@ -374,7 +374,7 @@ export default compose( [ }; } ), withDispatch( ( dispatch ) => { - const { multiSelect, selectBlock } = dispatch( 'core/editor' ); + const { multiSelect, selectBlock } = dispatch( 'core/block-editor' ); return { onMultiSelect: multiSelect, onSelectBlock: selectBlock, diff --git a/packages/editor/src/hooks/align.js b/packages/editor/src/hooks/align.js index e22922c198b2c..b9d2b842cef1d 100644 --- a/packages/editor/src/hooks/align.js +++ b/packages/editor/src/hooks/align.js @@ -168,7 +168,7 @@ export const withDataAlign = createHigherOrderComponent( compose( [ withSelect( ( select ) => { - const { getEditorSettings } = select( 'core/editor' ); + const { getEditorSettings } = select( 'core/block-editor' ); return { hasWideEnabled: !! getEditorSettings().alignWide, }; diff --git a/packages/editor/src/utils/media-upload/index.js b/packages/editor/src/utils/media-upload/index.js index 12e0335033247..ced9e70c6b253 100644 --- a/packages/editor/src/utils/media-upload/index.js +++ b/packages/editor/src/utils/media-upload/index.js @@ -33,10 +33,10 @@ export default function( { onError = noop, onFileChange, } ) { - const { - getCurrentPostId, - getEditorSettings, - } = select( 'core/editor' ); + const { getCurrentPostId } = select( 'core/editor' ); + const { getEditorSettings } = select( 'core/block-editor' ); + + // These settings should not live in the block editor's store. const wpAllowedMimeTypes = getEditorSettings().allowedMimeTypes; maxUploadFileSize = maxUploadFileSize || getEditorSettings().maxUploadFileSize; From 1919f839c011d310dcff4002d540394a9c425207 Mon Sep 17 00:00:00 2001 From: Miguel Fonseca <miguelcsf@gmail.com> Date: Mon, 25 Feb 2019 14:47:38 +0000 Subject: [PATCH 512/691] Docs: release.md: miscellaneous fixes (#14083) * Docs: release.md: misc. fixes --- docs/contributors/release.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/contributors/release.md b/docs/contributors/release.md index b59a5472167f3..2321e63b70b42 100644 --- a/docs/contributors/release.md +++ b/docs/contributors/release.md @@ -2,7 +2,7 @@ This Repository is used to perform several types of releases. This document serves as a checklist for each one of these. It is helpful if you'd like to understand the different workflows. -To release Gutenberg, you need commit access to the [WordPress.org plugin repository]. 🙂 +To release Gutenberg, you need commit access to the [WordPress.org plugin repository][plugin repository]. 🙂 ## Plugin Releases @@ -192,22 +192,23 @@ WordPress Core Updates are based on the `g-minor` branch. Releasing packages in For major WordPress releases, the last Gutenberg plugin release is merged into `g-minor`. This involves the following steps: -1. Checkout the last published Gutenberg's release branch `git checkout release/x.x` +1. Check out the last published Gutenberg release branch `git checkout release/x.x` 2. Create a Pull Request from this branch into `g-minor`. 3. Merge the branch. ### Minor WordPress Releases -For minor releases, the critical fixes targetted for this WordPress Minor release should be cherry-picked into the `g-minor` branch one by one in their chronological order. +For minor releases, the critical fixes targeted for this WordPress Minor release should be cherry-picked into the `g-minor` branch one by one in their chronological order. ### Releasing the WordPress packages -1. Checkout the `g-minor` branch. -2. Run [the package release process](https://github.com/WordPress/gutenberg/blob/master/CONTRIBUTING.md#releasing-packages) -3. Update the `CHANGELOG` files of the published packages with the new released versions and commit to the `g-minor` branch. +1. Check out the `g-minor` branch. +2. Run the [package release process]. +3. Update the `CHANGELOG.md` files of the published packages with the new released versions and commit to the `g-minor` branch. --------- Ta-da! 🎉 [plugin repository]: https://plugins.trac.wordpress.org/browser/gutenberg/ +[package release process]: https://github.com/WordPress/gutenberg/blob/master/packages/README.md#releasing-packages From c8c1381f77c0cf1a2d67b96e5b612db2c4291395 Mon Sep 17 00:00:00 2001 From: Andrea Fercia <a.fercia@gmail.com> Date: Mon, 25 Feb 2019 16:15:02 +0100 Subject: [PATCH 513/691] Disable block navigation and document outline items in text mode (#14081) * Use aria-disabled instead of disabled. * Fix unrecognized prop warning. * Disable Document Outline items in text mode. * Improve buttons alignment. * Pass isTextModeEnabled as prop from parent. --- .../src/components/header/header-toolbar/index.js | 7 ++++--- .../src/components/block-navigation/dropdown.js | 11 ++++++----- .../editor/src/components/block-navigation/index.js | 3 +-- .../editor/src/components/document-outline/index.js | 4 +++- .../editor/src/components/document-outline/item.js | 6 ++++-- .../editor/src/components/document-outline/style.scss | 10 ++++++++++ .../editor/src/components/table-of-contents/index.js | 4 ++-- .../editor/src/components/table-of-contents/panel.js | 4 ++-- 8 files changed, 32 insertions(+), 17 deletions(-) diff --git a/packages/edit-post/src/components/header/header-toolbar/index.js b/packages/edit-post/src/components/header/header-toolbar/index.js index 87a24a85e2a8a..7740b3b157a99 100644 --- a/packages/edit-post/src/components/header/header-toolbar/index.js +++ b/packages/edit-post/src/components/header/header-toolbar/index.js @@ -21,7 +21,7 @@ import { */ import FullscreenModeClose from '../fullscreen-mode-close'; -function HeaderToolbar( { hasFixedToolbar, isLargeViewport, showInserter } ) { +function HeaderToolbar( { hasFixedToolbar, isLargeViewport, showInserter, isTextModeEnabled } ) { const toolbarAriaLabel = hasFixedToolbar ? /* translators: accessibility text for the editor toolbar when Top Toolbar is on */ __( 'Document and block tools' ) : @@ -42,8 +42,8 @@ function HeaderToolbar( { hasFixedToolbar, isLargeViewport, showInserter } ) { </div> <EditorHistoryUndo /> <EditorHistoryRedo /> - <TableOfContents /> - <BlockNavigationDropdown /> + <TableOfContents hasOutlineItemsDisabled={ isTextModeEnabled } /> + <BlockNavigationDropdown isDisabled={ isTextModeEnabled } /> { hasFixedToolbar && isLargeViewport && ( <div className="edit-post-header-toolbar__block-toolbar"> <BlockToolbar /> @@ -58,6 +58,7 @@ export default compose( [ hasFixedToolbar: select( 'core/edit-post' ).isFeatureActive( 'fixedToolbar' ), // This setting (richEditingEnabled) should not live in the block editor's setting. showInserter: select( 'core/edit-post' ).getEditorMode() === 'visual' && select( 'core/block-editor' ).getEditorSettings().richEditingEnabled, + isTextModeEnabled: select( 'core/edit-post' ).getEditorMode() === 'text', } ) ), withViewportMatch( { isLargeViewport: 'medium' } ), ] )( HeaderToolbar ); diff --git a/packages/editor/src/components/block-navigation/dropdown.js b/packages/editor/src/components/block-navigation/dropdown.js index 104279fc877b1..35db5d9b2d1f2 100644 --- a/packages/editor/src/components/block-navigation/dropdown.js +++ b/packages/editor/src/components/block-navigation/dropdown.js @@ -18,12 +18,14 @@ const MenuIcon = ( </SVG> ); -function BlockNavigationDropdown( { hasBlocks, isTextModeEnabled } ) { +function BlockNavigationDropdown( { hasBlocks, isDisabled } ) { + const isEnabled = hasBlocks && ! isDisabled; + return ( <Dropdown renderToggle={ ( { isOpen, onToggle } ) => ( <Fragment> - { hasBlocks && ! isTextModeEnabled && <KeyboardShortcuts + { isEnabled && <KeyboardShortcuts bindGlobal shortcuts={ { [ rawShortcut.access( 'o' ) ]: onToggle, @@ -33,11 +35,11 @@ function BlockNavigationDropdown( { hasBlocks, isTextModeEnabled } ) { <IconButton icon={ MenuIcon } aria-expanded={ isOpen } - onClick={ hasBlocks ? onToggle : undefined } + onClick={ isEnabled ? onToggle : undefined } label={ __( 'Block Navigation' ) } className="editor-block-navigation" shortcut={ displayShortcut.access( 'o' ) } - disabled={ ! hasBlocks || isTextModeEnabled } + aria-disabled={ ! isEnabled } /> </Fragment> ) } @@ -51,6 +53,5 @@ function BlockNavigationDropdown( { hasBlocks, isTextModeEnabled } ) { export default withSelect( ( select ) => { return { hasBlocks: !! select( 'core/block-editor' ).getBlockCount(), - isTextModeEnabled: select( 'core/edit-post' ).getEditorMode() === 'text', }; } )( BlockNavigationDropdown ); diff --git a/packages/editor/src/components/block-navigation/index.js b/packages/editor/src/components/block-navigation/index.js index a3f67764fa36a..be7206d6cac0e 100644 --- a/packages/editor/src/components/block-navigation/index.js +++ b/packages/editor/src/components/block-navigation/index.js @@ -40,10 +40,9 @@ function BlockNavigationList( { <div className="editor-block-navigation__item"> <Button className={ classnames( 'editor-block-navigation__item-button', { - 'is-selected': block.clientId === selectedBlockClientId, + 'is-selected': isSelected, } ) } onClick={ () => selectBlock( block.clientId ) } - isSelected={ isSelected } > <BlockIcon icon={ blockType.icon } showColors /> { blockType.title } diff --git a/packages/editor/src/components/document-outline/index.js b/packages/editor/src/components/document-outline/index.js index 689144d040acc..63349419e43b6 100644 --- a/packages/editor/src/components/document-outline/index.js +++ b/packages/editor/src/components/document-outline/index.js @@ -64,7 +64,7 @@ const computeOutlineHeadings = ( blocks = [], path = [] ) => { const isEmptyHeading = ( heading ) => ! heading.attributes.content || heading.attributes.content.length === 0; -export const DocumentOutline = ( { blocks = [], title, onSelect, isTitleSupported } ) => { +export const DocumentOutline = ( { blocks = [], title, onSelect, isTitleSupported, hasOutlineItemsDisabled } ) => { const headings = computeOutlineHeadings( blocks ); if ( headings.length < 1 ) { @@ -96,6 +96,7 @@ export const DocumentOutline = ( { blocks = [], title, onSelect, isTitleSupporte level={ __( 'Title' ) } isValid onClick={ focusTitle } + isDisabled={ hasOutlineItemsDisabled } > { title } </DocumentOutlineItem> @@ -120,6 +121,7 @@ export const DocumentOutline = ( { blocks = [], title, onSelect, isTitleSupporte isValid={ isValid } onClick={ () => onSelectHeading( item.clientId ) } path={ item.path } + isDisabled={ hasOutlineItemsDisabled } > { item.isEmpty ? emptyHeadingContent : diff --git a/packages/editor/src/components/document-outline/item.js b/packages/editor/src/components/document-outline/item.js index 3db43383c5a5f..eb6b147056f67 100644 --- a/packages/editor/src/components/document-outline/item.js +++ b/packages/editor/src/components/document-outline/item.js @@ -18,6 +18,7 @@ const TableOfContentsItem = ( { isValid, level, onClick, + isDisabled, path = [], } ) => ( <li @@ -31,7 +32,8 @@ const TableOfContentsItem = ( { > <button className="document-outline__button" - onClick={ onClick } + onClick={ isDisabled ? undefined : onClick } + disabled={ isDisabled } > <span className="document-outline__emdash" aria-hidden="true"></span> { @@ -49,7 +51,7 @@ const TableOfContentsItem = ( { <span className="document-outline__item-content"> { children } </span> - <span className="screen-reader-text">{ __( '(Click to focus this heading)' ) }</span> + { ! isDisabled && <span className="screen-reader-text">{ __( '(Click to focus this heading)' ) }</span> } </button> </li> ); diff --git a/packages/editor/src/components/document-outline/style.scss b/packages/editor/src/components/document-outline/style.scss index 336f53d196687..53008b91e6b3d 100644 --- a/packages/editor/src/components/document-outline/style.scss +++ b/packages/editor/src/components/document-outline/style.scss @@ -43,9 +43,15 @@ border: none; display: flex; align-items: flex-start; + margin: 0 0 0 -1px; + padding: 2px 5px 2px 1px; color: $dark-gray-800; text-align: left; + &:disabled { + cursor: default; + } + &:focus { @include button-style__focus-active; } @@ -63,3 +69,7 @@ background: $alert-yellow; } } + +.document-outline__item-content { + padding: 1px 0; +} diff --git a/packages/editor/src/components/table-of-contents/index.js b/packages/editor/src/components/table-of-contents/index.js index af398e060085c..767f51dc6c1af 100644 --- a/packages/editor/src/components/table-of-contents/index.js +++ b/packages/editor/src/components/table-of-contents/index.js @@ -10,7 +10,7 @@ import { withSelect } from '@wordpress/data'; */ import TableOfContentsPanel from './panel'; -function TableOfContents( { hasBlocks } ) { +function TableOfContents( { hasBlocks, hasOutlineItemsDisabled } ) { return ( <Dropdown position="bottom" @@ -26,7 +26,7 @@ function TableOfContents( { hasBlocks } ) { aria-disabled={ ! hasBlocks } /> ) } - renderContent={ () => <TableOfContentsPanel /> } + renderContent={ () => <TableOfContentsPanel hasOutlineItemsDisabled={ hasOutlineItemsDisabled } /> } /> ); } diff --git a/packages/editor/src/components/table-of-contents/panel.js b/packages/editor/src/components/table-of-contents/panel.js index 00489ccaf1e1d..a1a2a4b413055 100644 --- a/packages/editor/src/components/table-of-contents/panel.js +++ b/packages/editor/src/components/table-of-contents/panel.js @@ -11,7 +11,7 @@ import { withSelect } from '@wordpress/data'; import WordCount from '../word-count'; import DocumentOutline from '../document-outline'; -function TableOfContentsPanel( { headingCount, paragraphCount, numberOfBlocks } ) { +function TableOfContentsPanel( { headingCount, paragraphCount, numberOfBlocks, hasOutlineItemsDisabled } ) { return ( <Fragment> <div @@ -49,7 +49,7 @@ function TableOfContentsPanel( { headingCount, paragraphCount, numberOfBlocks } <span className="table-of-contents__title"> { __( 'Document Outline' ) } </span> - <DocumentOutline /> + <DocumentOutline hasOutlineItemsDisabled={ hasOutlineItemsDisabled } /> </Fragment> ) } </Fragment> From cd82d07a70c5485396427f7196e78ae5c2d3b284 Mon Sep 17 00:00:00 2001 From: Andrea Fercia <a.fercia@gmail.com> Date: Mon, 25 Feb 2019 20:04:18 +0100 Subject: [PATCH 514/691] Fix selector in document outline. (#14094) * Fix selector in document outline. * Import getBlocks from its new location. --- packages/editor/src/components/document-outline/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/editor/src/components/document-outline/index.js b/packages/editor/src/components/document-outline/index.js index 63349419e43b6..b73af4a92d087 100644 --- a/packages/editor/src/components/document-outline/index.js +++ b/packages/editor/src/components/document-outline/index.js @@ -142,7 +142,8 @@ export const DocumentOutline = ( { blocks = [], title, onSelect, isTitleSupporte export default compose( withSelect( ( select ) => { - const { getEditedPostAttribute, getBlocks } = select( 'core/block-editor' ); + const { getBlocks } = select( 'core/block-editor' ); + const { getEditedPostAttribute } = select( 'core/editor' ); const { getPostType } = select( 'core' ); const postType = getPostType( getEditedPostAttribute( 'type' ) ); From 89573e83a5e3ab2f8fad26e2dde9f87e047831e7 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Tue, 26 Feb 2019 08:59:09 -0500 Subject: [PATCH 515/691] Plugin: Require includes for deprecated `use_block_editor_for_` functions (#14096) * Plugin: Fix 5.1.0 deprecated functions to correct plugin version * Plugin: Require includes for deprecated `use_block_editor_for_` functions --- lib/register.php | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/register.php b/lib/register.php index 16d5aa85b623e..9f86fdb58200b 100644 --- a/lib/register.php +++ b/lib/register.php @@ -25,16 +25,16 @@ function gutenberg_collect_meta_box_data() { * Return whether the post can be edited in Gutenberg and by the current user. * * @since 0.5.0 - * @deprecated 5.0.0 use_block_editor_for_post + * @deprecated 5.1.0 use_block_editor_for_post * * @param int|WP_Post $post Post ID or WP_Post object. * @return bool Whether the post can be edited with Gutenberg. */ function gutenberg_can_edit_post( $post ) { - _deprecated_function( __FUNCTION__, '5.0.0', 'use_block_editor_for_post' ); + _deprecated_function( __FUNCTION__, '5.1.0', 'use_block_editor_for_post' ); + require_once ABSPATH . 'wp-admin/includes/post.php'; return use_block_editor_for_post( $post ); - } /** @@ -44,14 +44,15 @@ function gutenberg_can_edit_post( $post ) { * REST API, then the post cannot be edited in Gutenberg. * * @since 1.5.2 - * @deprecated 5.0.0 use_block_editor_for_post_type + * @deprecated 5.1.0 use_block_editor_for_post_type * * @param string $post_type The post type. * @return bool Whether the post type can be edited with Gutenberg. */ function gutenberg_can_edit_post_type( $post_type ) { - _deprecated_function( __FUNCTION__, '5.0.0', 'use_block_editor_for_post_type' ); + _deprecated_function( __FUNCTION__, '5.1.0', 'use_block_editor_for_post_type' ); + require_once ABSPATH . 'wp-admin/includes/post.php'; return use_block_editor_for_post_type( $post_type ); } @@ -155,23 +156,23 @@ function gutenberg_bulk_post_updated_messages( $messages ) { * Injects a hidden input in the edit form to propagate the information that classic editor is selected. * * @since 1.5.2 - * @deprecated 5.0.0 + * @deprecated 5.1.0 */ function gutenberg_remember_classic_editor_when_saving_posts() { - _deprecated_function( __FUNCTION__, '5.0.0' ); + _deprecated_function( __FUNCTION__, '5.1.0' ); } /** * Appends a query argument to the redirect url to make sure it gets redirected to the classic editor. * * @since 1.5.2 - * @deprecated 5.0.0 + * @deprecated 5.1.0 * * @param string $url Redirect url. * @return string Redirect url. */ function gutenberg_redirect_to_classic_editor_when_saving_posts( $url ) { - _deprecated_function( __FUNCTION__, '5.0.0' ); + _deprecated_function( __FUNCTION__, '5.1.0' ); return $url; } @@ -181,13 +182,13 @@ function gutenberg_redirect_to_classic_editor_when_saving_posts( $url ) { * the editor from which the user navigated. * * @since 1.5.2 - * @deprecated 5.0.0 + * @deprecated 5.1.0 * * @param string $url Edit url. * @return string Edit url. */ function gutenberg_revisions_link_to_editor( $url ) { - _deprecated_function( __FUNCTION__, '5.0.0' ); + _deprecated_function( __FUNCTION__, '5.1.0' ); return $url; } From e0adbe0748ac3f1e7e7d261f7039231dc0aa0914 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Tue, 26 Feb 2019 08:59:54 -0500 Subject: [PATCH 516/691] Plugin: Update server blocks script to use core equivalent function (#14097) `gutenberg_prepare_blocks_for_js` was deprecated in Gutenberg 5.0 --- bin/get-server-blocks.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bin/get-server-blocks.php b/bin/get-server-blocks.php index 851dbccfab6e6..164fafd467db9 100755 --- a/bin/get-server-blocks.php +++ b/bin/get-server-blocks.php @@ -24,10 +24,10 @@ require_once ABSPATH . WPINC . '/functions.php'; wp_load_translations_early(); wp_set_lang_dir(); -require_once dirname( dirname( __FILE__ ) ) . '/lib/blocks.php'; -require_once dirname( dirname( __FILE__ ) ) . '/lib/class-wp-block-type-registry.php'; -require_once dirname( dirname( __FILE__ ) ) . '/lib/class-wp-block-type.php'; -require_once dirname( dirname( __FILE__ ) ) . '/lib/client-assets.php'; +require_once ABSPATH . WPINC . '/blocks.php'; +require_once ABSPATH . WPINC . '/class-wp-block-type-registry.php'; +require_once ABSPATH . WPINC . '/class-wp-block-type.php'; +require_once ABSPATH . '/wp-admin/includes/post.php'; // Register server-side code for individual blocks. foreach ( glob( dirname( dirname( __FILE__ ) ) . '/packages/block-library/src/*/index.php' ) as $block_logic ) { @@ -36,4 +36,4 @@ do_action( 'init' ); -echo json_encode( gutenberg_prepare_blocks_for_js() ); +echo json_encode( get_block_editor_server_block_settings() ); From 63832a2a5bce7df64330c747c39ccde8f90eaa2e Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell@kjellr.com> Date: Tue, 26 Feb 2019 10:47:55 -0500 Subject: [PATCH 517/691] Correct visual error in the quote block icon (#14091) Fixes #13659. The current quote block icon appears to have some over-simplified edges. This replaces it with a crisper version. --- packages/block-library/src/quote/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/quote/index.js b/packages/block-library/src/quote/index.js index 6a2506eb5f2e0..43c380be09f3a 100644 --- a/packages/block-library/src/quote/index.js +++ b/packages/block-library/src/quote/index.js @@ -15,7 +15,7 @@ import { RichText, } from '@wordpress/editor'; import { join, split, create, toHTMLString } from '@wordpress/rich-text'; -import { G, Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; const ATTRIBUTE_QUOTE = 'value'; const ATTRIBUTE_CITATION = 'citation'; @@ -44,7 +44,7 @@ export const name = 'core/quote'; export const settings = { title: __( 'Quote' ), description: __( 'Give quoted text visual emphasis. "In quoting others, we cite ourselves." — Julio Cortázar' ), - icon: <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><G><Path d="M19 18h-6l2-4h-2V6h8v7l-2 5zm-2-2l2-3V8h-4v4h4l-2 4zm-8 2H3l2-4H3V6h8v7l-2 5zm-2-2l2-3V8H5v4h4l-2 4z" /></G></SVG>, + icon: <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><Path d="M18.62 18h-5.24l2-4H13V6h8v7.24L18.62 18zm-2-2h.76L19 12.76V8h-4v4h3.62l-2 4zm-8 2H3.38l2-4H3V6h8v7.24L8.62 18zm-2-2h.76L9 12.76V8H5v4h3.62l-2 4z" /></SVG>, category: 'common', keywords: [ __( 'blockquote' ) ], From f8912c2075720f4f3b5d8478cafc2fd2143dc3b3 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell@kjellr.com> Date: Tue, 26 Feb 2019 11:07:47 -0500 Subject: [PATCH 518/691] Try: Add a subtle animation to the is-active indicator for sidebar tabs (#13956) * Add subtle animation to the is-active indicator for sidebar tabs * Re-instate the empty border, to prevent browser defaults from kicking in. * Remove extra 1px of empty space * Focus state cleanup for tab buttons Allow them to inherit the box shadow (used for the active tab) Use the usual standard dotted outline instead of a solid one. * Add a pseudoclass to ensure the active border appears in Windows High Contrast Mode. * Fix typo. This should match the value from `/packages/edit-post/src/components/sidebar/settings-header/style.scss` --- assets/stylesheets/_mixins.scss | 4 ++-- .../sidebar/settings-header/style.scss | 19 +++++++++++++++---- .../src/components/sidebar/style.scss | 18 +++++++++++++++--- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/assets/stylesheets/_mixins.scss b/assets/stylesheets/_mixins.scss index fa79974e2a112..31134cd44d09b 100644 --- a/assets/stylesheets/_mixins.scss +++ b/assets/stylesheets/_mixins.scss @@ -195,8 +195,8 @@ @mixin square-style__focus() { color: $dark-gray-900; - outline: 1px solid $dark-gray-300; - box-shadow: none; + outline-offset: -1px; + outline: 1px dotted $dark-gray-500; } // Menu items. diff --git a/packages/edit-post/src/components/sidebar/settings-header/style.scss b/packages/edit-post/src/components/sidebar/settings-header/style.scss index 3fcfe94481b4f..457f7e0361b21 100644 --- a/packages/edit-post/src/components/sidebar/settings-header/style.scss +++ b/packages/edit-post/src/components/sidebar/settings-header/style.scss @@ -18,14 +18,14 @@ .edit-post-sidebar__panel-tab { background: transparent; border: none; - border-radius: 0; + box-shadow: none; cursor: pointer; - height: $grid-size * 6; padding: 3px 15px; // Use padding to offset the is-active border, this benefits Windows High Contrast mode margin-left: 0; font-weight: 400; color: $dark-gray-900; @include square-style__neutral; + transition: box-shadow 0.1s linear; // This pseudo-element "duplicates" the tab label and sets the text to bold. // This ensures that the tab doesn't change width when selected. @@ -41,9 +41,20 @@ } &.is-active { - padding-bottom: 0; - border-bottom: 3px solid theme(primary); + box-shadow: inset 0 -3px theme(outlines); font-weight: 600; + position: relative; + + // This border appears in Windows High Contrast mode instead of the box-shadow. + &::before { + content: ""; + position: absolute; + top: 0; + bottom: 1px; + right: 0; + left: 0; + border-bottom: 3px solid transparent; + } } &:focus { diff --git a/packages/edit-post/src/components/sidebar/style.scss b/packages/edit-post/src/components/sidebar/style.scss index 823256093ffbb..cfc42eb75f7b3 100644 --- a/packages/edit-post/src/components/sidebar/style.scss +++ b/packages/edit-post/src/components/sidebar/style.scss @@ -163,18 +163,30 @@ .edit-post-sidebar__panel-tab { background: transparent; border: none; - border-radius: 0; + box-shadow: none; cursor: pointer; height: 50px; padding: 3px 15px; // Use padding to offset the is-active border, this benefits Windows High Contrast mode margin-left: 0; font-weight: 400; @include square-style__neutral; + transition: box-shadow 0.1s linear; &.is-active { - padding-bottom: 0; - border-bottom: 3px solid theme(primary); + box-shadow: inset 0 -3px theme(outlines); font-weight: 600; + position: relative; + + // This border appears in Windows High Contrast mode instead of the box-shadow. + &::before { + content: ""; + position: absolute; + top: 0; + bottom: 1px; + right: 0; + left: 0; + border-bottom: 3px solid transparent; + } } &:focus { From eac0a6466b54e79bed7f32293aa50aeb00cdd605 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Tue, 26 Feb 2019 16:46:36 -0500 Subject: [PATCH 519/691] Plugin: Remove vendor script registration (#13573) --- lib/client-assets.php | 92 ++----------------------------------------- 1 file changed, 4 insertions(+), 88 deletions(-) diff --git a/lib/client-assets.php b/lib/client-assets.php index 72d3522075a01..ac84f94a19025 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -139,21 +139,6 @@ function gutenberg_register_scripts_and_styles() { global $wp_scripts; gutenberg_register_vendor_scripts(); - - wp_add_inline_script( - 'wp-polyfill', - wp_get_script_polyfill( - $wp_scripts, - array( - '\'fetch\' in window' => 'wp-polyfill-fetch', - 'document.contains' => 'wp-polyfill-node-contains', - 'window.FormData && window.FormData.prototype.keys' => 'wp-polyfill-formdata', - 'Element.prototype.matches && Element.prototype.closest' => 'wp-polyfill-element-closest', - ), - 'after' - ) - ); - gutenberg_register_packages_scripts(); // Inline scripts. @@ -246,33 +231,6 @@ function gutenberg_register_scripts_and_styles() { ), 'after' ); - wp_add_inline_script( - 'moment', - sprintf( - "moment.locale( '%s', %s );", - get_user_locale(), - wp_json_encode( - array( - 'months' => array_values( $wp_locale->month ), - 'monthsShort' => array_values( $wp_locale->month_abbrev ), - 'weekdays' => array_values( $wp_locale->weekday ), - 'weekdaysShort' => array_values( $wp_locale->weekday_abbrev ), - 'week' => array( - 'dow' => (int) get_option( 'start_of_week', 0 ), - ), - 'longDateFormat' => array( - 'LT' => get_option( 'time_format', __( 'g:i a', 'default' ) ), - 'LTS' => null, - 'L' => null, - 'LL' => get_option( 'date_format', __( 'F j, Y', 'default' ) ), - 'LLL' => __( 'F j, Y g:i a', 'default' ), - 'LLLL' => null, - ), - ) - ) - ), - 'after' - ); // Loading the old editor and its config to ensure the classic block works as expected. wp_add_inline_script( 'editor', @@ -534,52 +492,10 @@ function gutenberg_preload_api_request( $memo, $path ) { * @since 0.1.0 */ function gutenberg_register_vendor_scripts() { - $suffix = SCRIPT_DEBUG ? '' : '.min'; - - // Vendor Scripts. - $react_suffix = ( SCRIPT_DEBUG ? '.development' : '.production' ) . $suffix; - - gutenberg_register_vendor_script( - 'react', - 'https://unpkg.com/react@16.6.3/umd/react' . $react_suffix . '.js', - array( 'wp-polyfill' ) - ); - gutenberg_register_vendor_script( - 'react-dom', - 'https://unpkg.com/react-dom@16.6.3/umd/react-dom' . $react_suffix . '.js', - array( 'react' ) - ); - $moment_script = SCRIPT_DEBUG ? 'moment.js' : 'min/moment.min.js'; - gutenberg_register_vendor_script( - 'moment', - 'https://unpkg.com/moment@2.22.1/' . $moment_script, - array() - ); - gutenberg_register_vendor_script( - 'lodash', - 'https://unpkg.com/lodash@4.17.11/lodash' . $suffix . '.js' - ); - wp_add_inline_script( 'lodash', 'window.lodash = _.noConflict();' ); - gutenberg_register_vendor_script( - 'wp-polyfill-fetch', - 'https://unpkg.com/whatwg-fetch@3.0.0/dist/fetch.umd.js' - ); - gutenberg_register_vendor_script( - 'wp-polyfill-formdata', - 'https://unpkg.com/formdata-polyfill@3.0.9/formdata.min.js' - ); - gutenberg_register_vendor_script( - 'wp-polyfill-node-contains', - 'https://unpkg.com/polyfill-library@3.26.0-0/polyfills/Node/prototype/contains/polyfill.js' - ); - gutenberg_register_vendor_script( - 'wp-polyfill-element-closest', - 'https://unpkg.com/element-closest@2.0.2/element-closest.js' - ); - gutenberg_register_vendor_script( - 'wp-polyfill', - 'https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/7.0.0/polyfill' . $suffix . '.js' - ); + /* + * This function is kept as an empty stub, in case Gutenberg should need to + * explicitly provide a version newer than that provided by core. + */ } /** From fd35a1b7045d01723de6b884123b4d74ef41084d Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Tue, 26 Feb 2019 22:13:04 +0000 Subject: [PATCH 520/691] chore: Fix: FormToggle docs documents props the component does not uses (#14099) --- packages/components/src/form-toggle/README.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/packages/components/src/form-toggle/README.md b/packages/components/src/form-toggle/README.md index e2bfcefb07196..f247a2a12d1a6 100644 --- a/packages/components/src/form-toggle/README.md +++ b/packages/components/src/form-toggle/README.md @@ -71,20 +71,6 @@ const MyFormToggle = withState( { The component accepts the following props: -#### label - -If this property is added, a label will be generated using label property as the content. - -- Type: `String` -- Required: No - -#### help - -If this property is added, a help text will be generated using help property as the content. - -- Type: `String` | `Function` -- Required: No - #### checked If checked is true the toggle will be checked. If checked is false the toggle will be unchecked. From 1010f0da5599073f27860622a68aa9bc69426d33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Wed, 27 Feb 2019 06:56:48 +0100 Subject: [PATCH 521/691] Babel Plugin Import JSX Pragma: Remove import visitor (#14106) Props to @aduth for coding this optimization. --- .../CHANGELOG.md | 5 +++- .../babel-plugin-import-jsx-pragma/index.js | 28 +------------------ 2 files changed, 5 insertions(+), 28 deletions(-) diff --git a/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md b/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md index 32ad201bf1884..7a7c426e6ead9 100644 --- a/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md +++ b/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md @@ -2,9 +2,12 @@ ### Breaking Change -- Plugin skips now adding import JSX pragma when the scope variable is defined for all JSX elements ([#13809](https://github.com/WordPress/gutenberg/pull/13809)). - Stop using Babel transpilation internally and set node 8 as a minimal version required ([#13540](https://github.com/WordPress/gutenberg/pull/13540)). +### Enhancement + +- Plugin skips now adding import JSX pragma when the scope variable is defined for all JSX elements ([#13809](https://github.com/WordPress/gutenberg/pull/13809)). + ## 1.1.0 (2018-09-05) ### New Feature diff --git a/packages/babel-plugin-import-jsx-pragma/index.js b/packages/babel-plugin-import-jsx-pragma/index.js index d431b6823aea3..7953640c484ab 100644 --- a/packages/babel-plugin-import-jsx-pragma/index.js +++ b/packages/babel-plugin-import-jsx-pragma/index.js @@ -40,42 +40,16 @@ module.exports = function( babel ) { return { visitor: { JSXElement( path, state ) { - state.hasJSX = true; if ( state.hasUndeclaredScopeVariable ) { return; } const { scopeVariable } = getOptions( state ); - state.hasUndeclaredScopeVariable = ! path.scope.hasBinding( scopeVariable ); }, - ImportDeclaration( path, state ) { - if ( state.hasImportedScopeVariable ) { - return; - } - - const { scopeVariable, isDefault } = getOptions( state ); - - // Test that at least one import specifier exists matching the - // scope variable name. The module source is not verified since - // we must avoid introducing a conflicting import name, even if - // the scope variable is referenced from a different source. - state.hasImportedScopeVariable = path.node.specifiers.some( ( specifier ) => { - switch ( specifier.type ) { - case 'ImportSpecifier': - return ( - ! isDefault && - specifier.imported.name === scopeVariable - ); - - case 'ImportDefaultSpecifier': - return isDefault; - } - } ); - }, Program: { exit( path, state ) { - if ( ! state.hasJSX || state.hasImportedScopeVariable || ! state.hasUndeclaredScopeVariable ) { + if ( ! state.hasUndeclaredScopeVariable ) { return; } From 1ed999561b4b6c6d0f0a520e4ff0e302af14d09e Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Wed, 27 Feb 2019 09:45:11 +0000 Subject: [PATCH 522/691] Add: Block specific toolbar button sample to the format api tutorial (#14113) ## Description This PR updates the format API tutorial (toolbar button section) to include a sample of a button that only renders on a certain block. Answers a question posted on https://github.com/WordPress/gutenberg/issues/14104. Closes: https://github.com/WordPress/gutenberg/issues/14104 ## How has this been tested? I pasted the sample code on the browser console and verified it works as expected. --- .../tutorials/format-api/2-toolbar-button.md | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md b/docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md index 8aea061c5ef16..c6e72011f1782 100644 --- a/docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md +++ b/docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md @@ -35,3 +35,54 @@ Let's check that everything is working as expected. Reload the post/page and sel ![Toolbar with custom button](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/assets/toolbar-with-custom-button.png) You may also want to check that upon clicking the button the `toggle format` message is shown in your browser's console. + +## Show the button only for specific blocks + +By default, the button is rendered on every rich text toolbar (image captions, buttons, paragraphs, etc). +It is possible to render the button only on blocks of a certain type by using `wp.data.withSelect` together with `wp.compose.ifCondition`. +The following sample code renders the previously shown button only on paragraph blocks: + +```js +( function( wp ) { + var withSelect = wp.data.withSelect; + var ifCondition = wp.compose.ifCondition; + var compose = wp.compose.compose; + var MyCustomButton = function( props ) { + return wp.element.createElement( + wp.editor.RichTextToolbarButton, { + icon: 'editor-code', + title: 'Sample output', + onClick: function() { + console.log( 'toggle format' ); + }, + } + ); + } + var ConditionalButton = compose( + withSelect( function( select ) { + return { + selectedBlock: select( 'core/editor' ).getSelectedBlock() + } + } ), + ifCondition( function( props ) { + return ( + props.selectedBlock && + props.selectedBlock.name === 'core/paragraph' + ); + } ) + )( MyCustomButton ); + + wp.richText.registerFormatType( + 'my-custom-format/sample-output', { + title: 'Sample output', + tagName: 'samp', + className: null, + edit: ConditionalButton, + } + ); +} )( window.wp ); +``` + +Don't forget adding `wp-compose` and `wp-data` to the dependencies array in the PHP script. + +More advanced conditions can be used, e.g., only render the button depending on specific attributes of the block. From 05570ff1bf0907ec3a801d78d1578a32ca65565b Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Wed, 27 Feb 2019 13:19:17 +0100 Subject: [PATCH 523/691] Use the editor settings to pass a mediaUpload handler (#14115) * Use the editor settings to pass a mediaUpload handler * Update media block snapshots --- .../audio/test/__snapshots__/index.js.snap | 32 ---------------- .../cover/test/__snapshots__/index.js.snap | 35 +---------------- .../gallery/test/__snapshots__/index.js.snap | 36 +----------------- .../video/test/__snapshots__/index.js.snap | 32 ---------------- .../src/components/media-placeholder/index.js | 38 +++++++++++-------- .../editor/src/components/provider/index.js | 3 ++ 6 files changed, 27 insertions(+), 149 deletions(-) diff --git a/packages/block-library/src/audio/test/__snapshots__/index.js.snap b/packages/block-library/src/audio/test/__snapshots__/index.js.snap index 6ebf5b5e4f09c..09f551226b702 100644 --- a/packages/block-library/src/audio/test/__snapshots__/index.js.snap +++ b/packages/block-library/src/audio/test/__snapshots__/index.js.snap @@ -38,38 +38,6 @@ exports[`core/audio block edit matches snapshot 1`] = ` <div class="components-placeholder__fieldset" > - <div - class="components-drop-zone" - /> - <div - class="components-form-file-upload" - > - <button - class="components-button components-icon-button editor-media-placeholder__button has-text is-button is-default is-large" - type="button" - > - <svg - aria-hidden="true" - class="dashicon dashicons-upload" - focusable="false" - height="20" - role="img" - viewBox="0 0 20 20" - width="20" - xmlns="http://www.w3.org/2000/svg" - > - <path - d="M8 14V8H5l5-6 5 6h-3v6H8zm-2 2v-6H4v8h12.01v-8H14v6H6z" - /> - </svg> - Upload - </button> - <input - accept="audio/*" - style="display:none" - type="file" - /> - </div> <div class="editor-media-placeholder__url-input-container" > diff --git a/packages/block-library/src/cover/test/__snapshots__/index.js.snap b/packages/block-library/src/cover/test/__snapshots__/index.js.snap index 44e8409be5d7b..36fb23ceb5916 100644 --- a/packages/block-library/src/cover/test/__snapshots__/index.js.snap +++ b/packages/block-library/src/cover/test/__snapshots__/index.js.snap @@ -37,39 +37,6 @@ exports[`core/cover block edit matches snapshot 1`] = ` </div> <div class="components-placeholder__fieldset" - > - <div - class="components-drop-zone" - /> - <div - class="components-form-file-upload" - > - <button - class="components-button components-icon-button editor-media-placeholder__button has-text is-button is-default is-large" - type="button" - > - <svg - aria-hidden="true" - class="dashicon dashicons-upload" - focusable="false" - height="20" - role="img" - viewBox="0 0 20 20" - width="20" - xmlns="http://www.w3.org/2000/svg" - > - <path - d="M8 14V8H5l5-6 5 6h-3v6H8zm-2 2v-6H4v8h12.01v-8H14v6H6z" - /> - </svg> - Upload - </button> - <input - accept="image/*,video/*" - style="display:none" - type="file" - /> - </div> - </div> + /> </div> `; diff --git a/packages/block-library/src/gallery/test/__snapshots__/index.js.snap b/packages/block-library/src/gallery/test/__snapshots__/index.js.snap index 9fd3dff017b9d..6e88dd4a4f949 100644 --- a/packages/block-library/src/gallery/test/__snapshots__/index.js.snap +++ b/packages/block-library/src/gallery/test/__snapshots__/index.js.snap @@ -39,40 +39,6 @@ exports[`core/gallery block edit matches snapshot 1`] = ` </div> <div class="components-placeholder__fieldset" - > - <div - class="components-drop-zone" - /> - <div - class="components-form-file-upload" - > - <button - class="components-button components-icon-button editor-media-placeholder__button has-text is-button is-default is-large" - type="button" - > - <svg - aria-hidden="true" - class="dashicon dashicons-upload" - focusable="false" - height="20" - role="img" - viewBox="0 0 20 20" - width="20" - xmlns="http://www.w3.org/2000/svg" - > - <path - d="M8 14V8H5l5-6 5 6h-3v6H8zm-2 2v-6H4v8h12.01v-8H14v6H6z" - /> - </svg> - Upload - </button> - <input - accept="image/*" - multiple="" - style="display:none" - type="file" - /> - </div> - </div> + /> </div> `; diff --git a/packages/block-library/src/video/test/__snapshots__/index.js.snap b/packages/block-library/src/video/test/__snapshots__/index.js.snap index d728e5e6b6ce0..88942a51fb5c1 100644 --- a/packages/block-library/src/video/test/__snapshots__/index.js.snap +++ b/packages/block-library/src/video/test/__snapshots__/index.js.snap @@ -38,38 +38,6 @@ exports[`core/video block edit matches snapshot 1`] = ` <div class="components-placeholder__fieldset" > - <div - class="components-drop-zone" - /> - <div - class="components-form-file-upload" - > - <button - class="components-button components-icon-button editor-media-placeholder__button has-text is-button is-default is-large" - type="button" - > - <svg - aria-hidden="true" - class="dashicon dashicons-upload" - focusable="false" - height="20" - role="img" - viewBox="0 0 20 20" - width="20" - xmlns="http://www.w3.org/2000/svg" - > - <path - d="M8 14V8H5l5-6 5 6h-3v6H8zm-2 2v-6H4v8h12.01v-8H14v6H6z" - /> - </svg> - Upload - </button> - <input - accept="video/*" - style="display:none" - type="file" - /> - </div> <div class="editor-media-placeholder__url-input-container" > diff --git a/packages/editor/src/components/media-placeholder/index.js b/packages/editor/src/components/media-placeholder/index.js index 11b80bcc58b0f..33b2792beb65d 100644 --- a/packages/editor/src/components/media-placeholder/index.js +++ b/packages/editor/src/components/media-placeholder/index.js @@ -16,7 +16,7 @@ import { withFilters, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { Component } from '@wordpress/element'; +import { Component, Fragment } from '@wordpress/element'; import { compose } from '@wordpress/compose'; import { withSelect } from '@wordpress/data'; @@ -26,7 +26,6 @@ import { withSelect } from '@wordpress/data'; import MediaUpload from '../media-upload'; import MediaUploadCheck from '../media-upload/check'; import URLPopover from '../url-popover'; -import { mediaUpload } from '../../utils/'; const InsertFromURLPopover = ( { src, onChange, onSubmit, onClose } ) => ( <URLPopover onClose={ onClose }> @@ -104,7 +103,7 @@ export class MediaPlaceholder extends Component { } onFilesUpload( files ) { - const { onSelect, multiple, onError, allowedTypes } = this.props; + const { onSelect, multiple, onError, allowedTypes, mediaUpload } = this.props; const setMedia = multiple ? onSelect : ( [ media ] ) => onSelect( media ); mediaUpload( { allowedTypes, @@ -136,6 +135,7 @@ export class MediaPlaceholder extends Component { notices, allowedTypes = [], hasUploadPermissions, + mediaUpload, } = this.props; const { @@ -202,19 +202,23 @@ export class MediaPlaceholder extends Component { notices={ notices } > <MediaUploadCheck> - <DropZone - onFilesDrop={ this.onFilesUpload } - onHTMLDrop={ onHTMLDrop } - /> - <FormFileUpload - isLarge - className="editor-media-placeholder__button" - onChange={ this.onUpload } - accept={ accept } - multiple={ multiple } - > - { __( 'Upload' ) } - </FormFileUpload> + { !! mediaUpload && ( + <Fragment> + <DropZone + onFilesDrop={ this.onFilesUpload } + onHTMLDrop={ onHTMLDrop } + /> + <FormFileUpload + isLarge + className="editor-media-placeholder__button" + onChange={ this.onUpload } + accept={ accept } + multiple={ multiple } + > + { __( 'Upload' ) } + </FormFileUpload> + </Fragment> + ) } <MediaUpload gallery={ multiple && this.onlyAllowsImages() } multiple={ multiple } @@ -259,9 +263,11 @@ export class MediaPlaceholder extends Component { const applyWithSelect = withSelect( ( select ) => { const { canUser } = select( 'core' ); + const { getEditorSettings } = select( 'core/block-editor' ); return { hasUploadPermissions: defaultTo( canUser( 'create', 'media' ), true ), + mediaUpload: getEditorSettings().__experimentalMediaUpload, }; } ); diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index 62bf481698b0a..fa85e1b1abadd 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -17,6 +17,8 @@ import { BlockEditorProvider } from '@wordpress/block-editor'; * Internal dependencies */ import transformStyles from '../../editor-styles'; +import { mediaUpload } from '../../utils'; + class EditorProvider extends Component { constructor( props ) { super( ...arguments ); @@ -57,6 +59,7 @@ class EditorProvider extends Component { onChange: onMetaChange, }, __experimentalReusableBlocks: reusableBlocks, + __experimentalMediaUpload: mediaUpload, }; } From 845185b4b6e0f7c15a44697c9b74ce067834a81b Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Wed, 27 Feb 2019 09:16:40 -0500 Subject: [PATCH 524/691] Block Editor: Consider RECEIVE_BLOCKS as non-persistent change (#14108) * Block Editor: Consider RECEIVE_BLOCKS as non-persistent change * Block Editor: Compare last action in reducer enhancer only if non-ignored --- packages/block-editor/src/store/reducer.js | 44 ++++++++-- .../block-editor/src/store/test/reducer.js | 86 ++++++++++++++++++- packages/editor/src/store/reducer.js | 1 - 3 files changed, 121 insertions(+), 10 deletions(-) diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index d6cc2444af050..bcd8b3295c4bc 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -12,6 +12,7 @@ import { keys, isEqual, isEmpty, + get, } from 'lodash'; /** @@ -187,20 +188,51 @@ export function isUpdatingSameBlockAttribute( action, lastAction ) { function withPersistentBlockChange( reducer ) { let lastAction; + /** + * Set of action types for which a blocks state change should be considered + * non-persistent. + * + * @type {Set} + */ + const IGNORED_ACTION_TYPES = new Set( [ + 'RECEIVE_BLOCKS', + ] ); + return ( state, action ) => { let nextState = reducer( state, action ); + const isExplicitPersistentChange = action.type === 'MARK_LAST_CHANGE_AS_PERSISTENT'; - if ( state !== nextState || isExplicitPersistentChange ) { - nextState = { + // Defer to previous state value (or default) unless changing or + // explicitly marking as persistent. + if ( state === nextState && ! isExplicitPersistentChange ) { + return { + ...nextState, + isPersistentChange: get( state, [ 'isPersistentChange' ], true ), + }; + } + + // Some state changes should not be considered persistent, namely those + // which are not a direct result of user interaction. + const isIgnoredActionType = IGNORED_ACTION_TYPES.has( action.type ); + if ( isIgnoredActionType ) { + return { ...nextState, - isPersistentChange: ( - isExplicitPersistentChange || - ! isUpdatingSameBlockAttribute( action, lastAction ) - ), + isPersistentChange: false, }; } + nextState = { + ...nextState, + isPersistentChange: ( + isExplicitPersistentChange || + ! isUpdatingSameBlockAttribute( action, lastAction ) + ), + }; + + // In comparing against the previous action, consider only those which + // would have qualified as one which would have been ignored or not + // have resulted in a changed state. lastAction = action; return nextState; diff --git a/packages/block-editor/src/store/test/reducer.js b/packages/block-editor/src/store/test/reducer.js index 6f1fae9dc0f03..778875b99ef59 100644 --- a/packages/block-editor/src/store/test/reducer.js +++ b/packages/block-editor/src/store/test/reducer.js @@ -1046,6 +1046,12 @@ describe( 'state', () => { } ); describe( 'isPersistentChange', () => { + it( 'should default a changing state to true', () => { + const state = deepFreeze( blocks( undefined, {} ) ); + + expect( state.isPersistentChange ).toBe( true ); + } ); + it( 'should consider any non-exempt block change as persistent', () => { const original = deepFreeze( blocks( undefined, { type: 'RESET_BLOCKS', @@ -1063,8 +1069,38 @@ describe( 'state', () => { expect( state.isPersistentChange ).toBe( true ); } ); + it( 'should consider any non-exempt block change as persistent across unchanging actions', () => { + let original = deepFreeze( blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ { + clientId: 'kumquat', + attributes: {}, + innerBlocks: [], + } ], + } ) ); + original = blocks( original, { + type: 'NOOP', + } ); + original = blocks( original, { + // While RECEIVE_BLOCKS changes state, it's considered + // as ignored, confirmed by this test. + type: 'RECEIVE_BLOCKS', + blocks: [], + } ); + + const state = blocks( original, { + type: 'UPDATE_BLOCK_ATTRIBUTES', + clientId: 'kumquat', + attributes: { + updated: false, + }, + } ); + + expect( state.isPersistentChange ).toBe( true ); + } ); + it( 'should consider same block attribute update as exempt', () => { - const original = deepFreeze( blocks( undefined, { + let original = deepFreeze( blocks( undefined, { type: 'RESET_BLOCKS', blocks: [ { clientId: 'kumquat', @@ -1072,7 +1108,7 @@ describe( 'state', () => { innerBlocks: [], } ], } ) ); - let state = blocks( original, { + original = blocks( original, { type: 'UPDATE_BLOCK_ATTRIBUTES', clientId: 'kumquat', attributes: { @@ -1080,7 +1116,34 @@ describe( 'state', () => { }, } ); - state = blocks( state, { + const state = blocks( original, { + type: 'UPDATE_BLOCK_ATTRIBUTES', + clientId: 'kumquat', + attributes: { + updated: true, + }, + } ); + + expect( state.isPersistentChange ).toBe( false ); + } ); + + it( 'should flag an explicitly marked persistent change', () => { + let original = deepFreeze( blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ { + clientId: 'kumquat', + attributes: {}, + innerBlocks: [], + } ], + } ) ); + original = blocks( original, { + type: 'UPDATE_BLOCK_ATTRIBUTES', + clientId: 'kumquat', + attributes: { + updated: false, + }, + } ); + original = blocks( original, { type: 'UPDATE_BLOCK_ATTRIBUTES', clientId: 'kumquat', attributes: { @@ -1088,6 +1151,23 @@ describe( 'state', () => { }, } ); + const state = blocks( original, { + type: 'MARK_LAST_CHANGE_AS_PERSISTENT', + } ); + + expect( state.isPersistentChange ).toBe( true ); + } ); + + it( 'should not consider received blocks as persistent change', () => { + const state = blocks( undefined, { + type: 'RECEIVE_BLOCKS', + blocks: [ { + clientId: 'kumquat', + attributes: {}, + innerBlocks: [], + } ], + } ); + expect( state.isPersistentChange ).toBe( false ); } ); } ); diff --git a/packages/editor/src/store/reducer.js b/packages/editor/src/store/reducer.js index 2cc1791a0a4a0..68a6624163ae0 100644 --- a/packages/editor/src/store/reducer.js +++ b/packages/editor/src/store/reducer.js @@ -134,7 +134,6 @@ export const editor = flow( [ withHistory( { resetTypes: [ 'SETUP_EDITOR_STATE' ], ignoreTypes: [ - 'RECEIVE_BLOCKS', 'RESET_POST', 'UPDATE_POST', ], From 4d4f1ef51ef92a771e9d6bbf1e194180d19cb993 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Wed, 27 Feb 2019 10:21:09 -0500 Subject: [PATCH 525/691] Plugin: Remove PHP functions slated for removal in 5.2 (#14090) * Plugin: Remove PHP functions slated for removal in 5.2 * Documentation: Update FAQ to avoid reference to deprecated function --- docs/designers-developers/faq.md | 3 +- gutenberg.php | 100 --------------------- lib/blocks.php | 142 ------------------------------ lib/client-assets.php | 115 ------------------------ lib/compat.php | 93 ------------------- lib/load.php | 4 - lib/meta-box-partial-page.php | 118 ------------------------- lib/plugin-compat.php | 33 ------- lib/register.php | 124 -------------------------- lib/rest-api.php | 121 ------------------------- phpunit/class-admin-test.php | 69 --------------- phpunit/class-blocks-api-test.php | 95 -------------------- 12 files changed, 1 insertion(+), 1016 deletions(-) delete mode 100644 lib/blocks.php delete mode 100644 lib/compat.php delete mode 100644 lib/meta-box-partial-page.php delete mode 100644 lib/plugin-compat.php delete mode 100644 phpunit/class-admin-test.php delete mode 100644 phpunit/class-blocks-api-test.php diff --git a/docs/designers-developers/faq.md b/docs/designers-developers/faq.md index 4fcccc36723bf..eb4b21b2a1a6c 100644 --- a/docs/designers-developers/faq.md +++ b/docs/designers-developers/faq.md @@ -365,8 +365,7 @@ var blocks = wp.blocks.parse( postContent ); In PHP: ```php -$blocks = gutenberg_parse_blocks( $post_content ); // plugin -$blocks = parse_blocks( $post_content ); // WordPress 5.0 +$blocks = parse_blocks( $post_content ); ``` ## Why should I start using this once released? diff --git a/gutenberg.php b/gutenberg.php index 40da12a8c4b8c..db7f70d1cab95 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -234,103 +234,3 @@ function gutenberg_init( $return, $post ) { return true; } - -/** - * Adds the filters to register additional links for the Gutenberg editor in - * the post/page screens. - * - * @since 1.5.0 - * @deprecated 5.0.0 - */ -function gutenberg_add_edit_link_filters() { - _deprecated_function( __FUNCTION__, '5.0.0' ); -} - -/** - * Registers an additional link in the post/page screens to edit any post/page in - * the Classic editor. - * - * @since 1.5.0 - * @deprecated 5.0.0 - * - * @param array $actions Post actions. - * - * @return array Updated post actions. - */ -function gutenberg_add_edit_link( $actions ) { - _deprecated_function( __FUNCTION__, '5.0.0' ); - - return $actions; -} - -/** - * Removes the Edit action from the reusable block list's Bulk Actions dropdown. - * - * @since 3.8.0 - * @deprecated 5.0.0 - * - * @param array $actions Bulk actions. - * - * @return array Updated bulk actions. - */ -function gutenberg_block_bulk_actions( $actions ) { - _deprecated_function( __FUNCTION__, '5.0.0' ); - - return $actions; -} - -/** - * Prints the JavaScript to replace the default "Add New" button.$_COOKIE - * - * @since 1.5.0 - * @deprecated 5.0.0 - */ -function gutenberg_replace_default_add_new_button() { - _deprecated_function( __FUNCTION__, '5.0.0' ); -} - -/** - * Adds the block-editor-page class to the body tag on the Gutenberg page. - * - * @since 1.5.0 - * @deprecated 5.0.0 - * - * @param string $classes Space separated string of classes being added to the body tag. - * @return string The $classes string, with block-editor-page appended. - */ -function gutenberg_add_admin_body_class( $classes ) { - _deprecated_function( __FUNCTION__, '5.0.0' ); - - return $classes; -} - -/** - * Adds attributes to kses allowed tags that aren't in the default list - * and that Gutenberg needs to save blocks such as the Gallery block. - * - * @deprecated 5.0.0 - * - * @param array $tags Allowed HTML. - * @return array (Maybe) modified allowed HTML. - */ -function gutenberg_kses_allowedtags( $tags ) { - _deprecated_function( __FUNCTION__, '5.0.0' ); - - return $tags; -} - -/** - * Adds the wp-embed-responsive class to the body tag if the theme has opted in to - * Gutenberg responsive embeds. - * - * @since 4.1.0 - * @deprecated 5.0.0 - * - * @param Array $classes Array of classes being added to the body tag. - * @return Array The $classes array, with wp-embed-responsive appended. - */ -function gutenberg_add_responsive_body_class( $classes ) { - _deprecated_function( __FUNCTION__, '5.0.0' ); - - return $classes; -} diff --git a/lib/blocks.php b/lib/blocks.php deleted file mode 100644 index 3419e81c8bb38..0000000000000 --- a/lib/blocks.php +++ /dev/null @@ -1,142 +0,0 @@ -<?php -/** - * Functions related to editor blocks for the Gutenberg editor plugin. - * - * @package gutenberg - */ - -if ( ! defined( 'ABSPATH' ) ) { - die( 'Silence is golden.' ); -} - -if ( ! function_exists( 'gutenberg_parse_blocks' ) ) { - /** - * Parses blocks out of a content string. - * - * @since 0.5.0 - * @deprecated 5.0.0 parse_blocks() - * - * @param string $content Post content. - * @return array Array of parsed block objects. - */ - function gutenberg_parse_blocks( $content ) { - _deprecated_function( __FUNCTION__, '5.0.0', 'parse_blocks()' ); - - return parse_blocks( $content ); - } -} - -if ( ! function_exists( 'get_dynamic_blocks_regex' ) ) { - /** - * Retrieve the dynamic blocks regular expression for searching. - * - * @since 3.6.0 - * @deprecated 5.0.0 - * - * @return string - */ - function get_dynamic_blocks_regex() { - _deprecated_function( __FUNCTION__, '5.0.0' ); - - $dynamic_block_names = get_dynamic_block_names(); - $dynamic_block_pattern = ( - '/<!--\s+wp:(' . - str_replace( - '/', - '\/', // Escape namespace, not handled by preg_quote. - str_replace( - 'core/', - '(?:core/)?', // Allow implicit core namespace, but don't capture. - implode( - '|', // Join block names into capture group alternation. - array_map( - 'preg_quote', // Escape block name for regular expression. - $dynamic_block_names - ) - ) - ) - ) . - ')(\s+(\{.*?\}))?\s+(\/)?-->/' - ); - - return $dynamic_block_pattern; - } -} - -/** - * Renders a single block into a HTML string. - * - * @since 1.9.0 - * @since 4.4.0 renders full nested tree of blocks before reassembling into HTML string - * @global WP_Post $post The post to edit. - * @deprecated 5.0.0 render_block() - * - * @param array $block A single parsed block object. - * @return string String of rendered HTML. - */ -function gutenberg_render_block( $block ) { - _deprecated_function( __FUNCTION__, '5.0.0', 'render_block()' ); - - return render_block( $block ); -} - -if ( ! function_exists( 'strip_dynamic_blocks' ) ) { - /** - * Remove all dynamic blocks from the given content. - * - * @since 3.6.0 - * @deprecated 5.0.0 - * - * @param string $content Content of the current post. - * @return string - */ - function strip_dynamic_blocks( $content ) { - _deprecated_function( __FUNCTION__, '5.0.0' ); - - return preg_replace( get_dynamic_blocks_regex(), '', $content ); - } -} - -if ( ! function_exists( 'strip_dynamic_blocks_add_filter' ) ) { - /** - * Adds the content filter to strip dynamic blocks from excerpts. - * - * It's a bit hacky for now, but once this gets merged into core the function - * can just be called in `wp_trim_excerpt()`. - * - * @since 3.6.0 - * @deprecated 5.0.0 - * - * @param string $text Excerpt. - * @return string - */ - function strip_dynamic_blocks_add_filter( $text ) { - _deprecated_function( __FUNCTION__, '5.0.0' ); - - add_filter( 'the_content', 'strip_dynamic_blocks', 6 ); - - return $text; - } -} - -if ( ! function_exists( 'strip_dynamic_blocks_remove_filter' ) ) { - /** - * Removes the content filter to strip dynamic blocks from excerpts. - * - * It's a bit hacky for now, but once this gets merged into core the function - * can just be called in `wp_trim_excerpt()`. - * - * @since 3.6.0 - * @deprecated 5.0.0 - * - * @param string $text Excerpt. - * @return string - */ - function strip_dynamic_blocks_remove_filter( $text ) { - _deprecated_function( __FUNCTION__, '5.0.0' ); - - remove_filter( 'the_content', 'strip_dynamic_blocks', 6 ); - - return $text; - } -} diff --git a/lib/client-assets.php b/lib/client-assets.php index ac84f94a19025..834a08a2d83a9 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -34,35 +34,6 @@ function gutenberg_url( $path ) { return plugins_url( $path, dirname( __FILE__ ) ); } -/** - * Returns contents of an inline script used in appending polyfill scripts for - * browsers which fail the provided tests. The provided array is a mapping from - * a condition to verify feature support to its polyfill script handle. - * - * @param array $tests Features to detect. - * @return string Conditional polyfill inline script. - */ -function gutenberg_get_script_polyfill( $tests ) { - _deprecated_function( __FUNCTION__, '5.0.0', 'wp_get_script_polyfill' ); - - global $wp_scripts; - return wp_get_script_polyfill( $wp_scripts, $tests ); -} - -if ( ! function_exists( 'register_tinymce_scripts' ) ) { - /** - * Registers the main TinyMCE scripts. - * - * @deprecated 5.0.0 wp_register_tinymce_scripts - */ - function register_tinymce_scripts() { - _deprecated_function( __FUNCTION__, '5.0.0', 'wp_register_tinymce_scripts' ); - - global $wp_scripts; - return wp_register_tinymce_scripts( $wp_scripts ); - } -} - /** * Registers a script according to `wp_register_script`. Honors this request by * deregistering any script by the same handler before registration. @@ -465,23 +436,6 @@ function gutenberg_register_scripts_and_styles() { add_action( 'wp_enqueue_scripts', 'gutenberg_register_scripts_and_styles', 5 ); add_action( 'admin_enqueue_scripts', 'gutenberg_register_scripts_and_styles', 5 ); -/** - * Append result of internal request to REST API for purpose of preloading - * data to be attached to the page. Expected to be called in the context of - * `array_reduce`. - * - * @deprecated 5.0.0 rest_preload_api_request - * - * @param array $memo Reduce accumulator. - * @param string $path REST API path to preload. - * @return array Modified reduce accumulator. - */ -function gutenberg_preload_api_request( $memo, $path ) { - _deprecated_function( __FUNCTION__, '5.0.0', 'rest_preload_api_request' ); - - return rest_preload_api_request( $memo, $path ); -} - /** * Registers vendor JavaScript files to be used as dependencies of the editor * and plugins. @@ -597,51 +551,6 @@ function gutenberg_register_vendor_script( $handle, $src, $deps = array() ) { ); } -/** - * Prepares server-registered blocks for JavaScript, returning an associative - * array of registered block data keyed by block name. Data includes properties - * of a block relevant for client registration. - * - * @deprecated 5.0.0 get_block_editor_server_block_settings - * - * @return array An associative array of registered block data. - */ -function gutenberg_prepare_blocks_for_js() { - _deprecated_function( __FUNCTION__, '5.0.0', 'get_block_editor_server_block_settings' ); - - return get_block_editor_server_block_settings(); -} - -/** - * Handles the enqueueing of block scripts and styles that are common to both - * the editor and the front-end. - * - * Note: This function must remain *before* - * `gutenberg_editor_scripts_and_styles` so that editor-specific stylesheets - * are loaded last. - * - * @since 0.4.0 - * @deprecated 5.0.0 wp_common_block_scripts_and_styles - */ -function gutenberg_common_scripts_and_styles() { - _deprecated_function( __FUNCTION__, '5.0.0', 'wp_common_block_scripts_and_styles' ); - - wp_common_block_scripts_and_styles(); -} - -/** - * Enqueues registered block scripts and styles, depending on current rendered - * context (only enqueuing editor scripts while in context of the editor). - * - * @since 2.0.0 - * @deprecated 5.0.0 wp_enqueue_registered_block_scripts_and_styles - */ -function gutenberg_enqueue_registered_block_scripts_and_styles() { - _deprecated_function( __FUNCTION__, '5.0.0', 'wp_enqueue_registered_block_scripts_and_styles' ); - - wp_enqueue_registered_block_scripts_and_styles(); -} - /** * Assigns a default editor template with a default block by post format, if * not otherwise assigned for a new post of type "post". @@ -711,21 +620,6 @@ function gutenberg_get_autosave_newer_than_post_save( $post ) { return false; } -/** - * Returns all the block categories. - * - * @since 2.2.0 - * @deprecated 5.0.0 get_block_categories - * - * @param WP_Post $post Post object. - * @return Object[] Block categories. - */ -function gutenberg_get_block_categories( $post ) { - _deprecated_function( __FUNCTION__, '5.0.0', 'get_block_categories' ); - - return get_block_categories( $post ); -} - /** * Loads Gutenberg Locale Data. */ @@ -1198,12 +1092,3 @@ function gutenberg_editor_scripts_and_styles( $hook ) { */ do_action( 'enqueue_block_editor_assets' ); } - -/** - * Enqueue the reusable blocks listing page's script - * - * @deprecated 5.0.0 - */ -function gutenberg_load_list_reusable_blocks() { - _deprecated_function( __FUNCTION__, '5.0.0' ); -} diff --git a/lib/compat.php b/lib/compat.php deleted file mode 100644 index dc090cfe047de..0000000000000 --- a/lib/compat.php +++ /dev/null @@ -1,93 +0,0 @@ -<?php -/** - * PHP and WordPress configuration compatibility functions for the Gutenberg - * editor plugin. - * - * @package gutenberg - */ - -if ( ! defined( 'ABSPATH' ) ) { - die( 'Silence is golden.' ); -} - -/** - * Splits a UTF-8 string into an array of UTF-8-encoded codepoints. - * - * @since 0.5.0 - * @deprecated 5.0.0 _mb_substr - * - * @param string $str The string to split. - * @return string Extracted substring. - */ -function _gutenberg_utf8_split( $str ) { - _deprecated_function( __FUNCTION__, '5.0.0', '_mb_substr' ); - - return _mb_substr( $str ); -} - -/** - * Disables wpautop behavior in classic editor when post contains blocks, to - * prevent removep from invalidating paragraph blocks. - * - * @link https://core.trac.wordpress.org/ticket/45113 - * @link https://core.trac.wordpress.org/changeset/43758 - * @deprecated 5.0.0 - * - * @param array $settings Original editor settings. - * @return array Filtered settings. - */ -function gutenberg_disable_editor_settings_wpautop( $settings ) { - _deprecated_function( __FUNCTION__, '5.0.0' ); - - return $settings; -} - -/** - * Add rest nonce to the heartbeat response. - * - * @link https://core.trac.wordpress.org/ticket/45113 - * @link https://core.trac.wordpress.org/changeset/43939 - * @deprecated 5.0.0 - * - * @param array $response Original heartbeat response. - * @return array New heartbeat response. - */ -function gutenberg_add_rest_nonce_to_heartbeat_response_headers( $response ) { - _deprecated_function( __FUNCTION__, '5.0.0' ); - - return $response; -} - -/** - * Check if we need to load the block warning in the Classic Editor. - * - * @deprecated 5.0.0 - */ -function gutenberg_check_if_classic_needs_warning_about_blocks() { - _deprecated_function( __FUNCTION__, '5.0.0' ); -} - -/** - * Adds a warning to the Classic Editor when trying to edit a post containing blocks. - * - * @since 3.4.0 - * @deprecated 5.0.0 - */ -function gutenberg_warn_classic_about_blocks() { - _deprecated_function( __FUNCTION__, '5.0.0' ); -} - -/** - * Display the privacy policy help notice. - * - * In Gutenberg, the `edit_form_after_title` hook is not supported. Because - * WordPress Core uses this hook to display this notice, it never displays. - * Outputting the notice on the `admin_notices` hook allows Gutenberg to - * consume the notice and display it with the Notices API. - * - * @since 4.5.0 - * @deprecated 5.0.0 - */ -function gutenberg_show_privacy_policy_help_text() { - _deprecated_function( __FUNCTION__, '5.0.0' ); -} diff --git a/lib/load.php b/lib/load.php index 580f9852b53a5..999523ed52ff6 100644 --- a/lib/load.php +++ b/lib/load.php @@ -15,11 +15,7 @@ require dirname( __FILE__ ) . '/rest-api.php'; } -require dirname( __FILE__ ) . '/meta-box-partial-page.php'; -require dirname( __FILE__ ) . '/blocks.php'; require dirname( __FILE__ ) . '/client-assets.php'; -require dirname( __FILE__ ) . '/compat.php'; -require dirname( __FILE__ ) . '/plugin-compat.php'; require dirname( __FILE__ ) . '/i18n.php'; require dirname( __FILE__ ) . '/register.php'; require dirname( __FILE__ ) . '/demo.php'; diff --git a/lib/meta-box-partial-page.php b/lib/meta-box-partial-page.php deleted file mode 100644 index 3e78a00ec506e..0000000000000 --- a/lib/meta-box-partial-page.php +++ /dev/null @@ -1,118 +0,0 @@ -<?php -/** - * Initialization and wp-admin integration for the Gutenberg editor plugin. - * - * @package gutenberg - */ - -if ( ! defined( 'ABSPATH' ) ) { - die( 'Silence is golden.' ); -} - -/** - * Page loaded when saving the meta boxes. - * The HTML returned by this page is irrelevant, it's being called in AJAX ignoring its output - * - * @since 1.8.0 - * @deprecated 5.0.0 - */ -function gutenberg_meta_box_save() { - _deprecated_function( __FUNCTION__, '5.0.0' ); -} - -/** - * Allows the meta box endpoint to correctly redirect to the meta box endpoint - * when a post is saved. - * - * @since 1.5.0 - * @deprecated 5.0.0 - * - * @param string $location The location of the meta box, 'side', 'normal'. - * @return string Modified location of the meta box. - */ -function gutenberg_meta_box_save_redirect( $location ) { - _deprecated_function( __FUNCTION__, '5.0.0' ); - - return $location; -} - -/** - * Filter out core meta boxes as well as the post thumbnail. - * - * @since 1.5.0 - * @deprecated 5.0.0 - * - * @param array $meta_boxes Meta box data. - * @return array Meta box data without core meta boxes. - */ -function gutenberg_filter_meta_boxes( $meta_boxes ) { - _deprecated_function( __FUNCTION__, '5.0.0' ); - - return $meta_boxes; -} - -/** - * Go through the global metaboxes, and override the render callback, so we can trigger our warning if needed. - * - * @since 1.8.0 - * @deprecated 5.0.0 - */ -function gutenberg_intercept_meta_box_render() { - _deprecated_function( __FUNCTION__, '5.0.0' ); -} - -/** - * Check if this metabox only exists for back compat purposes, show a warning if it doesn't. - * - * @since 1.8.0 - * @deprecated 5.0.0 - */ -function gutenberg_override_meta_box_callback() { - _deprecated_function( __FUNCTION__, '5.0.0' ); -} - -/** - * Display a warning in the metabox that the current plugin is causing the fallback to the old editor. - * - * @since 1.8.0 - * @deprecated 5.0.0 - */ -function gutenberg_show_meta_box_warning() { - _deprecated_function( __FUNCTION__, '5.0.0' ); -} - -/** - * Renders the WP meta boxes forms. - * - * @since 1.8.0 - * @deprecated 5.0.0 the_block_editor_meta_boxes - */ -function the_gutenberg_metaboxes() { - _deprecated_function( __FUNCTION__, '5.0.0', 'the_block_editor_meta_boxes' ); - - the_block_editor_meta_boxes(); -} - -/** - * Renders the hidden form required for the meta boxes form. - * - * @since 1.8.0 - * @deprecated 5.0.0 the_block_editor_meta_box_post_form_hidden_fields - */ -function gutenberg_meta_box_post_form_hidden_fields() { - _deprecated_function( __FUNCTION__, '5.0.0', 'the_block_editor_meta_box_post_form_hidden_fields' ); - - the_block_editor_meta_box_post_form_hidden_fields(); -} - -/** - * Admin action which toggles the 'enable_custom_fields' option, then redirects - * back to the editor. This allows Gutenberg to render a control that lets the - * user to completely enable or disable the 'postcustom' meta box. - * - * @since 5.2.0 - * @deprecated 5.0.0 - */ -function gutenberg_toggle_custom_fields() { - _deprecated_function( __FUNCTION__, '5.0.0' ); -} diff --git a/lib/plugin-compat.php b/lib/plugin-compat.php deleted file mode 100644 index 74f6dbb2cb0c8..0000000000000 --- a/lib/plugin-compat.php +++ /dev/null @@ -1,33 +0,0 @@ -<?php -/** - * This file includes a set of temporary fixes for known compatibility - * issues with third-party plugins. These fixes likely should not to be - * included with core merge. - * - * @package gutenberg - * @since 1.3.0 - * - * The goal is to provide a fix so - * 1. users of the plugin can continue to use and test Gutenberg, - * 2. provide a reference for developers of the plugin to work with, and - * 3. provide reference for other plugin developers on how they might work - * with Gutenberg. - */ - -/** - * WPCOM markdown support causes issues when saving a Gutenberg post by - * stripping out the <p> tags. This adds a filter prior to saving the post via - * REST API to disable markdown support. Disables markdown support provided by - * plugins Jetpack, JP-Markdown, and WP Editor.MD - * - * @since 1.3.0 - * @deprecated 5.0.0 - * - * @param array $post Post object which contains content to check for block. - * @return array $post Post object. - */ -function gutenberg_remove_wpcom_markdown_support( $post ) { - _deprecated_function( __FUNCTION__, '5.0.0' ); - - return $post; -} diff --git a/lib/register.php b/lib/register.php index 9f86fdb58200b..91ef3ee26e9aa 100644 --- a/lib/register.php +++ b/lib/register.php @@ -9,18 +9,6 @@ die( 'Silence is golden.' ); } -/** - * Collect information about meta_boxes registered for the current post. - * - * Redirects to classic editor if a meta box is incompatible. - * - * @since 1.5.0 - * @deprecated 5.0.0 register_and_do_post_meta_boxes - */ -function gutenberg_collect_meta_box_data() { - _deprecated_function( __FUNCTION__, '5.0.0', 'register_and_do_post_meta_boxes' ); -} - /** * Return whether the post can be edited in Gutenberg and by the current user. * @@ -56,102 +44,6 @@ function gutenberg_can_edit_post_type( $post_type ) { return use_block_editor_for_post_type( $post_type ); } -/** - * Determine whether a post has blocks. This test optimizes for performance - * rather than strict accuracy, detecting the pattern of a block but not - * validating its structure. For strict accuracy, you should use the block - * parser on post content. - * - * @see gutenberg_parse_blocks() - * - * @since 0.5.0 - * @deprecated 3.6.0 Use has_blocks() - * - * @param object $post Post. - * @return bool Whether the post has blocks. - */ -function gutenberg_post_has_blocks( $post ) { - _deprecated_function( __FUNCTION__, '3.6.0', 'has_blocks()' ); - return has_blocks( $post ); -} - -/** - * Determine whether a content string contains blocks. This test optimizes for - * performance rather than strict accuracy, detecting the pattern of a block - * but not validating its structure. For strict accuracy, you should use the - * block parser on post content. - * - * @see gutenberg_parse_blocks() - * - * @since 1.6.0 - * @deprecated 3.6.0 Use has_blocks() - * - * @param string $content Content to test. - * @return bool Whether the content contains blocks. - */ -function gutenberg_content_has_blocks( $content ) { - _deprecated_function( __FUNCTION__, '3.6.0', 'has_blocks()' ); - return has_blocks( $content ); -} - -/** - * Returns the current version of the block format that the content string is using. - * - * If the string doesn't contain blocks, it returns 0. - * - * @since 2.8.0 - * @see gutenberg_content_has_blocks() - * @deprecated 5.0.0 block_version - * - * @param string $content Content to test. - * @return int The block format version. - */ -function gutenberg_content_block_version( $content ) { - _deprecated_function( __FUNCTION__, '5.0.0', 'block_version' ); - - return block_version( $content ); -} - -/** - * Adds a "Gutenberg" post state for post tables, if the post contains blocks. - * - * @deprecated 5.0.0 - * - * @param array $post_states An array of post display states. - * @return array A filtered array of post display states. - */ -function gutenberg_add_gutenberg_post_state( $post_states ) { - _deprecated_function( __FUNCTION__, '5.0.0' ); - - return $post_states; -} - -/** - * Registers custom post types required by the Gutenberg editor. - * - * @since 0.10.0 - * @deprecated 5.0.0 - */ -function gutenberg_register_post_types() { - _deprecated_function( __FUNCTION__, '5.0.0' ); -} - -/** - * Apply the correct labels for Reusable Blocks in the bulk action updated messages. - * - * @since 4.3.0 - * @deprecated 5.0.0 - * - * @param array $messages Arrays of messages, each keyed by the corresponding post type. - * - * @return array - */ -function gutenberg_bulk_post_updated_messages( $messages ) { - _deprecated_function( __FUNCTION__, '5.0.0' ); - - return $messages; -} - /** * Injects a hidden input in the edit form to propagate the information that classic editor is selected. * @@ -192,19 +84,3 @@ function gutenberg_revisions_link_to_editor( $url ) { return $url; } - -/** - * Modifies revisions data to preserve Gutenberg argument used in determining - * where to redirect user returning to editor. - * - * @since 1.9.0 - * @deprecated 5.0.0 - * - * @param array $revisions_data The bootstrapped data for the revisions screen. - * @return array Modified bootstrapped data for the revisions screen. - */ -function gutenberg_revisions_restore( $revisions_data ) { - _deprecated_function( __FUNCTION__, '5.0.0' ); - - return $revisions_data; -} diff --git a/lib/rest-api.php b/lib/rest-api.php index d12d7df12dfcc..1b36ba7df9b38 100644 --- a/lib/rest-api.php +++ b/lib/rest-api.php @@ -10,16 +10,6 @@ die( 'Silence is golden.' ); } -/** - * Registers the REST API routes needed by the Gutenberg editor. - * - * @since 2.8.0 - * @deprecated 5.0.0 - */ -function gutenberg_register_rest_routes() { - _deprecated_function( __FUNCTION__, '5.0.0' ); -} - /** * Handle a failing oEmbed proxy request to try embedding as a shortcode. * @@ -61,114 +51,3 @@ function gutenberg_filter_oembed_result( $response, $handler, $request ) { ); } add_filter( 'rest_request_after_callbacks', 'gutenberg_filter_oembed_result', 10, 3 ); - -/** - * Add additional 'visibility' rest api field to taxonomies. - * - * Used so private taxonomies are not displayed in the UI. - * - * @see https://core.trac.wordpress.org/ticket/42707 - * @deprecated 5.0.0 - */ -function gutenberg_add_taxonomy_visibility_field() { - _deprecated_function( __FUNCTION__, '5.0.0' ); -} - -/** - * Gets taxonomy visibility property data. - * - * @see https://core.trac.wordpress.org/ticket/42707 - * @deprecated 5.0.0 - * - * @param array $object Taxonomy data from REST API. - * @return array Array of taxonomy visibility data. - */ -function gutenberg_get_taxonomy_visibility_data( $object ) { - _deprecated_function( __FUNCTION__, '5.0.0' ); - - return isset( $object['visibility'] ) ? $object['visibility'] : array(); -} - -/** - * Add a permalink template to posts in the post REST API response. - * - * @see https://core.trac.wordpress.org/ticket/45017 - * @deprecated 5.0.0 - * - * @param WP_REST_Response $response WP REST API response of a post. - * @return WP_REST_Response Response containing the permalink_template. - */ -function gutenberg_add_permalink_template_to_posts( $response ) { - _deprecated_function( __FUNCTION__, '5.0.0' ); - - return $response; -} - -/** - * Add the block format version to post content in the post REST API response. - * - * @see https://core.trac.wordpress.org/ticket/43887 - * @deprecated 5.0.0 - * - * @param WP_REST_Response $response WP REST API response of a post. - * @return WP_REST_Response Response containing the block_format. - */ -function gutenberg_add_block_format_to_post_content( $response ) { - _deprecated_function( __FUNCTION__, '5.0.0' ); - - return $response; -} - -/** - * Include target schema attributes to links, based on whether the user can. - * - * @see https://core.trac.wordpress.org/ticket/45014 - * @deprecated 5.0.0 - * - * @param WP_REST_Response $response WP REST API response of a post. - * @return WP_REST_Response Response containing the new links. - */ -function gutenberg_add_target_schema_to_links( $response ) { - _deprecated_function( __FUNCTION__, '5.0.0' ); - - return $response; -} - -/** - * Whenever a post type is registered, ensure we're hooked into it's WP REST API response. - * - * @deprecated 5.0.0 - * - * @param string $post_type The newly registered post type. - * @return string That same post type. - */ -function gutenberg_register_post_prepare_functions( $post_type ) { - _deprecated_function( __FUNCTION__, '5.0.0' ); - - return $post_type; -} - -/** - * Silence PHP Warnings and Errors in JSON requests - * - * @see https://core.trac.wordpress.org/ticket/44534 - * @deprecated 5.0.0 - */ -function gutenberg_silence_rest_errors() { - _deprecated_function( __FUNCTION__, '5.0.0' ); -} - -/** - * Include additional labels for registered post types - * - * @see https://core.trac.wordpress.org/ticket/45101 - * @deprecated 5.0.0 - * - * @param array $args Arguments supplied to register_post_type(). - * @return array Arguments supplied to register_post_type() - */ -function gutenberg_filter_post_type_labels( $args ) { - _deprecated_function( __FUNCTION__, '5.0.0' ); - - return $args; -} diff --git a/phpunit/class-admin-test.php b/phpunit/class-admin-test.php deleted file mode 100644 index e96f4958366ca..0000000000000 --- a/phpunit/class-admin-test.php +++ /dev/null @@ -1,69 +0,0 @@ -<?php -/** - * Admin Tests - * - * @package Gutenberg - */ - -/** - * Test functions in register.php - */ -class Admin_Test extends WP_UnitTestCase { - - /** - * ID for a post containing blocks. - * - * @var int - */ - protected static $post_with_blocks; - - /** - * ID for a post without blocks. - * - * @var int - */ - protected static $post_without_blocks; - - /** - * Set up before class. - */ - public static function wpSetUpBeforeClass() { - self::$post_with_blocks = self::factory()->post->create( - array( - 'post_title' => 'Example', - 'post_content' => "<!-- wp:core/text {\"dropCap\":true} -->\n<p class=\"has-drop-cap\">Tester</p>\n<!-- /wp:core/text -->", - ) - ); - self::$post_without_blocks = self::factory()->post->create( - array( - 'post_title' => 'Example', - 'post_content' => 'Tester', - ) - ); - } - - /** - * Tests gutenberg_post_has_blocks(). - * - * @covers ::gutenberg_post_has_blocks - * @expectedDeprecated gutenberg_post_has_blocks - */ - function test_gutenberg_post_has_blocks() { - $this->assertTrue( gutenberg_post_has_blocks( self::$post_with_blocks ) ); - $this->assertFalse( gutenberg_post_has_blocks( self::$post_without_blocks ) ); - } - - /** - * Tests gutenberg_content_has_blocks(). - * - * @covers ::gutenberg_content_has_blocks - * @expectedDeprecated gutenberg_content_has_blocks - */ - function test_gutenberg_content_has_blocks() { - $content_with_blocks = get_post_field( 'post_content', self::$post_with_blocks ); - $content_without_blocks = get_post_field( 'post_content', self::$post_without_blocks ); - - $this->assertTrue( gutenberg_content_has_blocks( $content_with_blocks ) ); - $this->assertFalse( gutenberg_content_has_blocks( $content_without_blocks ) ); - } -} diff --git a/phpunit/class-blocks-api-test.php b/phpunit/class-blocks-api-test.php deleted file mode 100644 index 1573f980f6368..0000000000000 --- a/phpunit/class-blocks-api-test.php +++ /dev/null @@ -1,95 +0,0 @@ -<?php -/** - * Blocks API Tests - * - * @package Gutenberg - */ - -/** - * Test functions in blocks.php - */ -class Blocks_API extends WP_UnitTestCase { - - public static $post_id; - - public $content = ' -<!-- wp:paragraph --> -<p>paragraph</p> -<!-- /wp:paragraph --> - -<!-- wp:latest-posts {"postsToShow":3,"displayPostDate":true,"order":"asc","orderBy":"title"} /--> - -<!-- wp:spacer --> -<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div> -<!-- /wp:spacer -->'; - - public $filtered_content = ' -<!-- wp:paragraph --> -<p>paragraph</p> -<!-- /wp:paragraph --> - - - -<!-- wp:spacer --> -<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div> -<!-- /wp:spacer -->'; - - /** - * Dummy block rendering function. - * - * @return string Block output. - */ - function render_dummy_block() { - return get_the_excerpt( self::$post_id ); - } - - /** - * Set up. - */ - function setUp() { - parent::setUp(); - - self::$post_id = $this->factory()->post->create( - array( - 'post_excerpt' => '', // Empty excerpt, so it has to be generated. - 'post_content' => '<!-- wp:core/dummy /-->', - ) - ); - - register_block_type( - 'core/dummy', - array( - 'render_callback' => array( $this, 'render_dummy_block' ), - ) - ); - } - - /** - * Tear down. - */ - function tearDown() { - parent::tearDown(); - - $registry = WP_Block_Type_Registry::get_instance(); - $registry->unregister( 'core/dummy' ); - - wp_delete_post( self::$post_id, true ); - } - - /** - * Tests strip_dynamic_blocks(). - * - * @covers ::strip_dynamic_blocks - */ - function test_strip_dynamic_blocks() { - $this->setExpectedDeprecated( 'strip_dynamic_blocks' ); - $this->setExpectedDeprecated( 'get_dynamic_blocks_regex' ); - - // Simple dynamic block.. - $content = '<!-- wp:core/block /-->'; - $this->assertEmpty( strip_dynamic_blocks( $content ) ); - - // Dynamic block with options, embedded in other content. - $this->assertEquals( $this->filtered_content, strip_dynamic_blocks( $this->content ) ); - } -} From ede325860ce76698d3915d045bb2e45d593f425c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20Van=C2=A0Durpe?= <iseulde@automattic.com> Date: Wed, 27 Feb 2019 16:41:24 +0100 Subject: [PATCH 526/691] Paste: ignore Google Docs UID tag (#14138) --- .../raw-handling/google-docs-uid-remover.js | 12 +++++++++++ .../src/api/raw-handling/paste-handler.js | 4 +++- test/integration/blocks-raw-handling.spec.js | 20 +++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 packages/blocks/src/api/raw-handling/google-docs-uid-remover.js diff --git a/packages/blocks/src/api/raw-handling/google-docs-uid-remover.js b/packages/blocks/src/api/raw-handling/google-docs-uid-remover.js new file mode 100644 index 0000000000000..adced320975d7 --- /dev/null +++ b/packages/blocks/src/api/raw-handling/google-docs-uid-remover.js @@ -0,0 +1,12 @@ +/** + * WordPress dependencies + */ +import { unwrap } from '@wordpress/dom'; + +export default function( node ) { + if ( ! node.id || node.id.indexOf( 'docs-internal-guid-' ) !== 0 ) { + return; + } + + unwrap( node ); +} diff --git a/packages/blocks/src/api/raw-handling/paste-handler.js b/packages/blocks/src/api/raw-handling/paste-handler.js index cc14ace649b5a..b40164f549689 100644 --- a/packages/blocks/src/api/raw-handling/paste-handler.js +++ b/packages/blocks/src/api/raw-handling/paste-handler.js @@ -22,6 +22,7 @@ import figureContentReducer from './figure-content-reducer'; import shortcodeConverter from './shortcode-converter'; import markdownConverter from './markdown-converter'; import iframeRemover from './iframe-remover'; +import googleDocsUIDRemover from './google-docs-uid-remover'; import { getPhrasingContentSchema } from './phrasing-content'; import { deepFilterHTML, @@ -43,7 +44,7 @@ const { console } = window; * @return {string} HTML only containing phrasing content. */ function filterInlineHTML( HTML ) { - HTML = deepFilterHTML( HTML, [ phrasingContentReducer ] ); + HTML = deepFilterHTML( HTML, [ googleDocsUIDRemover, phrasingContentReducer ] ); HTML = removeInvalidHTML( HTML, getPhrasingContentSchema(), { inline: true } ); // Allows us to ask for this information when we get a report. @@ -191,6 +192,7 @@ export function pasteHandler( { HTML = '', plainText = '', mode = 'AUTO', tagNam } const filters = [ + googleDocsUIDRemover, msListConverter, headRemover, listReducer, diff --git a/test/integration/blocks-raw-handling.spec.js b/test/integration/blocks-raw-handling.spec.js index 4e0b6c4d60b70..72426e00094fb 100644 --- a/test/integration/blocks-raw-handling.spec.js +++ b/test/integration/blocks-raw-handling.spec.js @@ -36,6 +36,26 @@ describe( 'Blocks raw handling', () => { expect( console ).toHaveLogged(); } ); + it( 'should ignore Google Docs UID tag', () => { + const filtered = pasteHandler( { + HTML: '<b id="docs-internal-guid-0"><em>test</em></b>', + mode: 'AUTO', + } ).map( getBlockContent ).join( '' ); + + expect( filtered ).toBe( '<p><em>test</em></p>' ); + expect( console ).toHaveLogged(); + } ); + + it( 'should ignore Google Docs UID tag in inline mode', () => { + const filtered = pasteHandler( { + HTML: '<b id="docs-internal-guid-0"><em>test</em></b>', + mode: 'INLINE', + } ); + + expect( filtered ).toBe( '<em>test</em>' ); + expect( console ).toHaveLogged(); + } ); + it( 'should parse Markdown', () => { const filtered = pasteHandler( { HTML: '* one<br>* two<br>* three', From fc92e93052f5809c84a422a7cbf9b9a6e26454fa Mon Sep 17 00:00:00 2001 From: Sven van Hal <sven@svenvanhal.nl> Date: Thu, 28 Feb 2019 08:30:50 +0100 Subject: [PATCH 527/691] Fix typos in copy-guide.md, readme.md and scripts.md. (#14089) --- docs/contributors/copy-guide.md | 2 +- docs/contributors/readme.md | 2 +- docs/contributors/scripts.md | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/contributors/copy-guide.md b/docs/contributors/copy-guide.md index e5505e30f3acb..a3178b69109b3 100644 --- a/docs/contributors/copy-guide.md +++ b/docs/contributors/copy-guide.md @@ -75,7 +75,7 @@ Here, every line has different phrasing (some start with a verb, some with a nou Reading this list takes more work because the reader has to parse each bullet anew. They can’t assume each bullet will contain similar information. -Note: this doesn't mean every bullet has to be super short and start with an action verb! “Predicable” doesn’t have to mean “simple.” It just means that each bullet should have the same sentence structure. This list would also be fine: +Note: this doesn't mean every bullet has to be super short and start with an action verb! “Predictable” doesn’t have to mean “simple.” It just means that each bullet should have the same sentence structure. This list would also be fine: > What can you do with this block? Lots of things! > * Try adding a quote. Sometimes someone else said things best! diff --git a/docs/contributors/readme.md b/docs/contributors/readme.md index e3979c229e25c..685af4590b473 100644 --- a/docs/contributors/readme.md +++ b/docs/contributors/readme.md @@ -6,7 +6,7 @@ The following guidelines are in place to create consistency across the project a ## Philosophy -* [Architecturial and UX Principles of Gutenberg](/docs/contributors/principles.md) +* [Architectural and UX Principles of Gutenberg](/docs/contributors/principles.md) ## Sections diff --git a/docs/contributors/scripts.md b/docs/contributors/scripts.md index 5d5c1f672a4b2..4928ec91bde64 100644 --- a/docs/contributors/scripts.md +++ b/docs/contributors/scripts.md @@ -30,7 +30,7 @@ The editor includes a number of packages to enable various pieces of functionali | [I18N](/packages/i18n/README.md) | wp-i18n | Internationalization utilities for client-side localization | | [Is Shallow Equal](/packages/is-shallow-equal/README.md) | wp-is-shallow-equal | A function for performing a shallow comparison between two objects or arrays | | [Keycodes](/packages/keycodes/README.md) | wp-keycodes | Keycodes utilities for WordPress, used to check the key pressed in events like `onKeyDown` | -| [List Reusable Bocks](/packages/list-reusable-blocks/README.md) | wp-list-reusable-blocks | Package used to add import/export links to the listing page of the reusable blocks | +| [List Reusable Blocks](/packages/list-reusable-blocks/README.md) | wp-list-reusable-blocks | Package used to add import/export links to the listing page of the reusable blocks | | [NUX](/packages/nux/README.md) | wp-nux | Components, and wp.data methods useful for onboarding a new user to the WordPress admin interface | | [Plugins](/packages/plugins/README.md) | wp-plugins | Plugins module for WordPress | | [Redux Routine](/packages/redux-routine/README.md) | wp-redux-routine | Redux middleware for generator coroutines | @@ -55,7 +55,7 @@ The editor also uses some popular third-party packages and scripts. Plugin devel ## Polyfill Scripts The editor also provides polyfills for certain features that may not be available in all modern browsers. -It is recommened to use the main `wp-polyfill` script handle which takes care of loading all the below mentioned polyfills. +It is recommended to use the main `wp-polyfill` script handle which takes care of loading all the below mentioned polyfills. | Script Name | Handle | Description | |-------------|--------|-------------| From 17d509ba2512326409244c8fe70739f68d0df018 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Thu, 28 Feb 2019 09:37:39 +0100 Subject: [PATCH 528/691] Separate the block editor shortcuts from the post editor shortcuts (#14116) --- .../components/block-settings-menu/index.js | 2 +- .../block-editor-shortcuts.js | 158 ++++++++++++++++++ .../visual-editor-shortcuts.js | 154 ++--------------- 3 files changed, 174 insertions(+), 140 deletions(-) create mode 100644 packages/editor/src/components/global-keyboard-shortcuts/block-editor-shortcuts.js diff --git a/packages/editor/src/components/block-settings-menu/index.js b/packages/editor/src/components/block-settings-menu/index.js index b07452bdce393..c15bc11c63b29 100644 --- a/packages/editor/src/components/block-settings-menu/index.js +++ b/packages/editor/src/components/block-settings-menu/index.js @@ -15,7 +15,7 @@ import { withDispatch } from '@wordpress/data'; /** * Internal dependencies */ -import { shortcuts } from '../global-keyboard-shortcuts/visual-editor-shortcuts'; +import { shortcuts } from '../global-keyboard-shortcuts/block-editor-shortcuts'; import BlockActions from '../block-actions'; import BlockModeToggle from './block-mode-toggle'; import ReusableBlockConvertButton from './reusable-block-convert-button'; diff --git a/packages/editor/src/components/global-keyboard-shortcuts/block-editor-shortcuts.js b/packages/editor/src/components/global-keyboard-shortcuts/block-editor-shortcuts.js new file mode 100644 index 0000000000000..4e97184cd93f9 --- /dev/null +++ b/packages/editor/src/components/global-keyboard-shortcuts/block-editor-shortcuts.js @@ -0,0 +1,158 @@ +/** + * External dependencies + */ +import { first, last, some, flow } from 'lodash'; + +/** + * WordPress dependencies + */ +import { Component, Fragment } from '@wordpress/element'; +import { KeyboardShortcuts } from '@wordpress/components'; +import { withSelect, withDispatch } from '@wordpress/data'; +import { rawShortcut, displayShortcut } from '@wordpress/keycodes'; +import { compose } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import BlockActions from '../block-actions'; + +const preventDefault = ( event ) => { + event.preventDefault(); + return event; +}; + +export const shortcuts = { + duplicate: { + raw: rawShortcut.primaryShift( 'd' ), + display: displayShortcut.primaryShift( 'd' ), + }, + removeBlock: { + raw: rawShortcut.access( 'z' ), + display: displayShortcut.access( 'z' ), + }, + insertBefore: { + raw: rawShortcut.primaryAlt( 't' ), + display: displayShortcut.primaryAlt( 't' ), + }, + insertAfter: { + raw: rawShortcut.primaryAlt( 'y' ), + display: displayShortcut.primaryAlt( 'y' ), + }, +}; + +class BlockEditorKeyboardShortcuts extends Component { + constructor() { + super( ...arguments ); + + this.selectAll = this.selectAll.bind( this ); + this.deleteSelectedBlocks = this.deleteSelectedBlocks.bind( this ); + this.clearMultiSelection = this.clearMultiSelection.bind( this ); + } + + selectAll( event ) { + const { rootBlocksClientIds, onMultiSelect } = this.props; + event.preventDefault(); + onMultiSelect( first( rootBlocksClientIds ), last( rootBlocksClientIds ) ); + } + + deleteSelectedBlocks( event ) { + const { selectedBlockClientIds, hasMultiSelection, onRemove, isLocked } = this.props; + if ( hasMultiSelection ) { + event.preventDefault(); + if ( ! isLocked ) { + onRemove( selectedBlockClientIds ); + } + } + } + + /** + * Clears current multi-selection, if one exists. + */ + clearMultiSelection() { + const { hasMultiSelection, clearSelectedBlock } = this.props; + if ( hasMultiSelection ) { + clearSelectedBlock(); + window.getSelection().removeAllRanges(); + } + } + + render() { + const { selectedBlockClientIds } = this.props; + return ( + <Fragment> + <KeyboardShortcuts + shortcuts={ { + [ rawShortcut.primary( 'a' ) ]: this.selectAll, + backspace: this.deleteSelectedBlocks, + del: this.deleteSelectedBlocks, + escape: this.clearMultiSelection, + } } + /> + { selectedBlockClientIds.length > 0 && ( + <BlockActions clientIds={ selectedBlockClientIds }> + { ( { onDuplicate, onRemove, onInsertAfter, onInsertBefore } ) => ( + <KeyboardShortcuts + bindGlobal + shortcuts={ { + // Prevents bookmark all Tabs shortcut in Chrome when devtools are closed. + // Prevents reposition Chrome devtools pane shortcut when devtools are open. + [ shortcuts.duplicate.raw ]: flow( preventDefault, onDuplicate ), + + // Does not clash with any known browser/native shortcuts, but preventDefault + // is used to prevent any obscure unknown shortcuts from triggering. + [ shortcuts.removeBlock.raw ]: flow( preventDefault, onRemove ), + + // Prevent 'view recently closed tabs' in Opera using preventDefault. + [ shortcuts.insertBefore.raw ]: flow( preventDefault, onInsertBefore ), + + // Does not clash with any known browser/native shortcuts, but preventDefault + // is used to prevent any obscure unknown shortcuts from triggering. + [ shortcuts.insertAfter.raw ]: flow( preventDefault, onInsertAfter ), + } } + /> + ) } + </BlockActions> + ) } + </Fragment> + ); + } +} + +export default compose( [ + withSelect( ( select ) => { + const { + getBlockOrder, + getMultiSelectedBlockClientIds, + hasMultiSelection, + getBlockRootClientId, + getTemplateLock, + getSelectedBlockClientId, + } = select( 'core/block-editor' ); + const selectedBlockClientId = getSelectedBlockClientId(); + const selectedBlockClientIds = selectedBlockClientId ? [ selectedBlockClientId ] : getMultiSelectedBlockClientIds(); + + return { + rootBlocksClientIds: getBlockOrder(), + hasMultiSelection: hasMultiSelection(), + isLocked: some( + selectedBlockClientIds, + ( clientId ) => !! getTemplateLock( getBlockRootClientId( clientId ) ) + ), + selectedBlockClientIds, + }; + } ), + withDispatch( ( dispatch ) => { + const { + clearSelectedBlock, + multiSelect, + removeBlocks, + } = dispatch( 'core/block-editor' ); + + return { + clearSelectedBlock, + onMultiSelect: multiSelect, + onRemove: removeBlocks, + }; + } ), +] )( BlockEditorKeyboardShortcuts ); diff --git a/packages/editor/src/components/global-keyboard-shortcuts/visual-editor-shortcuts.js b/packages/editor/src/components/global-keyboard-shortcuts/visual-editor-shortcuts.js index 02fe1d0e94b7e..4a0cd7f8fd4f7 100644 --- a/packages/editor/src/components/global-keyboard-shortcuts/visual-editor-shortcuts.js +++ b/packages/editor/src/components/global-keyboard-shortcuts/visual-editor-shortcuts.js @@ -1,62 +1,22 @@ -/** - * External dependencies - */ -import { first, last, some, flow } from 'lodash'; - /** * WordPress dependencies */ import { Component, Fragment } from '@wordpress/element'; import { KeyboardShortcuts } from '@wordpress/components'; -import { withSelect, withDispatch } from '@wordpress/data'; -import { rawShortcut, displayShortcut } from '@wordpress/keycodes'; -import { compose } from '@wordpress/compose'; +import { withDispatch } from '@wordpress/data'; +import { rawShortcut } from '@wordpress/keycodes'; import deprecated from '@wordpress/deprecated'; /** * Internal dependencies */ -import BlockActions from '../block-actions'; import SaveShortcut from './save-shortcut'; - -const preventDefault = ( event ) => { - event.preventDefault(); - return event; -}; - -export const shortcuts = { - duplicate: { - raw: rawShortcut.primaryShift( 'd' ), - display: displayShortcut.primaryShift( 'd' ), - }, - removeBlock: { - raw: rawShortcut.access( 'z' ), - display: displayShortcut.access( 'z' ), - }, - insertBefore: { - raw: rawShortcut.primaryAlt( 't' ), - display: displayShortcut.primaryAlt( 't' ), - }, - insertAfter: { - raw: rawShortcut.primaryAlt( 'y' ), - display: displayShortcut.primaryAlt( 'y' ), - }, -}; +import BlockEditorKeyboardShortcuts from './block-editor-shortcuts'; class VisualEditorGlobalKeyboardShortcuts extends Component { constructor() { super( ...arguments ); - - this.selectAll = this.selectAll.bind( this ); this.undoOrRedo = this.undoOrRedo.bind( this ); - this.deleteSelectedBlocks = this.deleteSelectedBlocks.bind( this ); - this.clearMultiSelection = this.clearMultiSelection.bind( this ); - } - - selectAll( event ) { - const { rootBlocksClientIds, onMultiSelect } = this.props; - event.preventDefault(); - onMultiSelect( first( rootBlocksClientIds ), last( rootBlocksClientIds ) ); } undoOrRedo( event ) { @@ -71,117 +31,33 @@ class VisualEditorGlobalKeyboardShortcuts extends Component { event.preventDefault(); } - deleteSelectedBlocks( event ) { - const { selectedBlockClientIds, hasMultiSelection, onRemove, isLocked } = this.props; - if ( hasMultiSelection ) { - event.preventDefault(); - if ( ! isLocked ) { - onRemove( selectedBlockClientIds ); - } - } - } - - /** - * Clears current multi-selection, if one exists. - */ - clearMultiSelection() { - const { hasMultiSelection, clearSelectedBlock } = this.props; - if ( hasMultiSelection ) { - clearSelectedBlock(); - window.getSelection().removeAllRanges(); - } - } - render() { - const { selectedBlockClientIds } = this.props; return ( <Fragment> + <BlockEditorKeyboardShortcuts /> <KeyboardShortcuts shortcuts={ { - [ rawShortcut.primary( 'a' ) ]: this.selectAll, [ rawShortcut.primary( 'z' ) ]: this.undoOrRedo, [ rawShortcut.primaryShift( 'z' ) ]: this.undoOrRedo, - backspace: this.deleteSelectedBlocks, - del: this.deleteSelectedBlocks, - escape: this.clearMultiSelection, } } /> <SaveShortcut /> - { selectedBlockClientIds.length > 0 && ( - <BlockActions clientIds={ selectedBlockClientIds }> - { ( { onDuplicate, onRemove, onInsertAfter, onInsertBefore } ) => ( - <KeyboardShortcuts - bindGlobal - shortcuts={ { - // Prevents bookmark all Tabs shortcut in Chrome when devtools are closed. - // Prevents reposition Chrome devtools pane shortcut when devtools are open. - [ shortcuts.duplicate.raw ]: flow( preventDefault, onDuplicate ), - - // Does not clash with any known browser/native shortcuts, but preventDefault - // is used to prevent any obscure unknown shortcuts from triggering. - [ shortcuts.removeBlock.raw ]: flow( preventDefault, onRemove ), - - // Prevent 'view recently closed tabs' in Opera using preventDefault. - [ shortcuts.insertBefore.raw ]: flow( preventDefault, onInsertBefore ), - - // Does not clash with any known browser/native shortcuts, but preventDefault - // is used to prevent any obscure unknown shortcuts from triggering. - [ shortcuts.insertAfter.raw ]: flow( preventDefault, onInsertAfter ), - } } - /> - ) } - </BlockActions> - ) } </Fragment> ); } } -const EnhancedVisualEditorGlobalKeyboardShortcuts = compose( [ - withSelect( ( select ) => { - const { - getBlockOrder, - getMultiSelectedBlockClientIds, - hasMultiSelection, - getBlockRootClientId, - getTemplateLock, - getSelectedBlockClientId, - } = select( 'core/block-editor' ); - const selectedBlockClientId = getSelectedBlockClientId(); - const selectedBlockClientIds = selectedBlockClientId ? [ selectedBlockClientId ] : getMultiSelectedBlockClientIds(); - - return { - rootBlocksClientIds: getBlockOrder(), - hasMultiSelection: hasMultiSelection(), - isLocked: some( - selectedBlockClientIds, - ( clientId ) => !! getTemplateLock( getBlockRootClientId( clientId ) ) - ), - selectedBlockClientIds, - }; - } ), - withDispatch( ( dispatch ) => { - // This component should probably be split into to - // A block editor specific one and a post editor one. - const { - clearSelectedBlock, - multiSelect, - removeBlocks, - } = dispatch( 'core/block-editor' ); - const { - redo, - undo, - } = dispatch( 'core/editor' ); - - return { - clearSelectedBlock, - onMultiSelect: multiSelect, - onRedo: redo, - onUndo: undo, - onRemove: removeBlocks, - }; - } ), -] )( VisualEditorGlobalKeyboardShortcuts ); +const EnhancedVisualEditorGlobalKeyboardShortcuts = withDispatch( ( dispatch ) => { + const { + redo, + undo, + } = dispatch( 'core/editor' ); + + return { + onRedo: redo, + onUndo: undo, + }; +} )( VisualEditorGlobalKeyboardShortcuts ); export default EnhancedVisualEditorGlobalKeyboardShortcuts; From 4472b1bf3da10f3e382b68f6e7f8894757d7c395 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Thu, 28 Feb 2019 09:23:17 +0000 Subject: [PATCH 529/691] Fix: FocalPointPicker renders unlabelled input fields (#14152) --- packages/components/src/focal-point-picker/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/src/focal-point-picker/index.js b/packages/components/src/focal-point-picker/index.js index 315c28f7303b7..865fbd43b651c 100644 --- a/packages/components/src/focal-point-picker/index.js +++ b/packages/components/src/focal-point-picker/index.js @@ -209,7 +209,7 @@ export class FocalPointPicker extends Component { </div> </div> <div className="components-focal-point-picker_position-display-container"> - <BaseControl label={ __( 'Horizontal Pos.' ) }> + <BaseControl label={ __( 'Horizontal Pos.' ) } id={ horizontalPositionId }> <input className="components-text-control__input" id={ horizontalPositionId } @@ -221,7 +221,7 @@ export class FocalPointPicker extends Component { /> <span>%</span> </BaseControl> - <BaseControl label={ __( 'Vertical Pos.' ) }> + <BaseControl label={ __( 'Vertical Pos.' ) } id={ verticalPositionId }> <input className="components-text-control__input" id={ verticalPositionId } From fe2136dd39be002b0c5301656c1f50eb4af6d980 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Thu, 28 Feb 2019 08:24:19 -0500 Subject: [PATCH 530/691] Testing: Remove unnecessary Enzyme React 16 workarounds (#14156) * Testing: Bump `enzyme-adapter-react-16` to 1.10.0 * Testing: Avoid unforwarded Button mock No longer necessary with native support for forwardRef in Enzyme * Testing: Un-skip BlockControls snapshot test --- package-lock.json | 2 +- .../components/src/button/__mocks__/index.js | 17 ----- .../test/__snapshots__/index.js.snap | 8 +-- .../src/color-palette/test/index.js | 2 - .../components/src/icon-button/test/index.js | 10 ++- .../test/__snapshots__/index.js.snap | 8 +-- .../components/src/menu-item/test/index.js | 2 - packages/components/src/panel/test/body.js | 2 - .../test/__snapshots__/index.js.snap | 4 +- .../components/header/more-menu/test/index.js | 2 - .../plugin-post-publish-panel/test/index.js | 2 - .../plugin-pre-publish-panel/test/index.js | 2 - .../test/__snapshots__/index.js.snap | 69 +++++++++++-------- .../components/block-controls/test/index.js | 17 +++-- .../test/__snapshots__/index.js.snap | 8 +-- .../post-preview-button/test/index.js | 2 - .../post-publish-button/test/index.js | 4 +- packages/jest-preset-default/CHANGELOG.md | 1 + packages/jest-preset-default/package.json | 2 +- .../dot-tip/test/__snapshots__/index.js.snap | 4 +- .../nux/src/components/dot-tip/test/index.js | 4 +- 21 files changed, 78 insertions(+), 94 deletions(-) delete mode 100644 packages/components/src/button/__mocks__/index.js diff --git a/package-lock.json b/package-lock.json index a86ed000f301b..b9bda6a34c73e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2895,7 +2895,7 @@ "@wordpress/jest-console": "file:packages/jest-console", "babel-jest": "^24.1.0", "enzyme": "^3.9.0", - "enzyme-adapter-react-16": "^1.9.1", + "enzyme-adapter-react-16": "^1.10.0", "enzyme-to-json": "^3.3.5" } }, diff --git a/packages/components/src/button/__mocks__/index.js b/packages/components/src/button/__mocks__/index.js deleted file mode 100644 index d43b7380b09ec..0000000000000 --- a/packages/components/src/button/__mocks__/index.js +++ /dev/null @@ -1,17 +0,0 @@ -// [TEMPORARY]: Button uses React.forwardRef, added in react@16.3.0 but not yet -// supported by Enzyme as of enzyme-adapter-react-16@1.1.1 . This mock unwraps -// the ref forwarding, so any tests relying on this behavior will fail. -// -// See: https://github.com/airbnb/enzyme/issues/1604 -// See: https://github.com/airbnb/enzyme/pull/1592/files - -const { Component } = require( 'react' ); -const { Button: RawButton } = require.requireActual( '../index' ); - -class Button extends Component { - render() { - return RawButton( this.props ); - } -} - -export default Button; diff --git a/packages/components/src/color-palette/test/__snapshots__/index.js.snap b/packages/components/src/color-palette/test/__snapshots__/index.js.snap index 3a49ace1959ad..e0ad2ef51ff3d 100644 --- a/packages/components/src/color-palette/test/__snapshots__/index.js.snap +++ b/packages/components/src/color-palette/test/__snapshots__/index.js.snap @@ -180,7 +180,7 @@ exports[`ColorPalette should allow disabling custom color picker 1`] = ` <div className="components-color-palette__custom-clear-wrapper" > - <Button + <ForwardRef(Button) className="components-color-palette__clear" isDefault={true} isSmall={true} @@ -188,7 +188,7 @@ exports[`ColorPalette should allow disabling custom color picker 1`] = ` type="button" > Clear - </Button> + </ForwardRef(Button)> </div> </div> `; @@ -272,7 +272,7 @@ exports[`ColorPalette should render a dynamic toolbar of colors 1`] = ` renderContent={[Function]} renderToggle={[Function]} /> - <Button + <ForwardRef(Button) className="components-color-palette__clear" isDefault={true} isSmall={true} @@ -280,7 +280,7 @@ exports[`ColorPalette should render a dynamic toolbar of colors 1`] = ` type="button" > Clear - </Button> + </ForwardRef(Button)> </div> </div> `; diff --git a/packages/components/src/color-palette/test/index.js b/packages/components/src/color-palette/test/index.js index 8983537ed7dc1..a515b9b769628 100644 --- a/packages/components/src/color-palette/test/index.js +++ b/packages/components/src/color-palette/test/index.js @@ -8,8 +8,6 @@ import { shallow } from 'enzyme'; */ import ColorPalette from '../'; -jest.mock( '../../button' ); - describe( 'ColorPalette', () => { const colors = [ { name: 'red', color: '#f00' }, { name: 'white', color: '#fff' }, { name: 'blue', color: '#00f' } ]; const currentColor = '#f00'; diff --git a/packages/components/src/icon-button/test/index.js b/packages/components/src/icon-button/test/index.js index d5b67eec7930f..133224941c1b5 100644 --- a/packages/components/src/icon-button/test/index.js +++ b/packages/components/src/icon-button/test/index.js @@ -8,8 +8,6 @@ import { shallow } from 'enzyme'; */ import IconButton from '../'; -jest.mock( '../../button' ); - describe( 'IconButton', () => { describe( 'basic rendering', () => { it( 'should render an top level element with only a class property', () => { @@ -30,7 +28,7 @@ describe( 'IconButton', () => { it( 'should add an aria-label when the label property is used', () => { const iconButton = shallow( <IconButton label="WordPress">WordPress</IconButton> ); - expect( iconButton.name() ).toBe( 'Button' ); + expect( iconButton.name() ).toBe( 'ForwardRef(Button)' ); expect( iconButton.prop( 'aria-label' ) ).toBe( 'WordPress' ); } ); @@ -38,7 +36,7 @@ describe( 'IconButton', () => { const iconButton = shallow( <IconButton label="WordPress" /> ); expect( iconButton.name() ).toBe( 'Tooltip' ); expect( iconButton.prop( 'text' ) ).toBe( 'WordPress' ); - expect( iconButton.find( 'Button' ).prop( 'aria-label' ) ).toBe( 'WordPress' ); + expect( iconButton.find( 'ForwardRef(Button)' ).prop( 'aria-label' ) ).toBe( 'WordPress' ); } ); it( 'should support explicit aria-label override', () => { @@ -60,12 +58,12 @@ describe( 'IconButton', () => { const iconButton = shallow( <IconButton label="WordPress" tooltip="Custom" /> ); expect( iconButton.name() ).toBe( 'Tooltip' ); expect( iconButton.prop( 'text' ) ).toBe( 'Custom' ); - expect( iconButton.find( 'Button' ).prop( 'aria-label' ) ).toBe( 'WordPress' ); + expect( iconButton.find( 'ForwardRef(Button)' ).prop( 'aria-label' ) ).toBe( 'WordPress' ); } ); it( 'should allow tooltip disable', () => { const iconButton = shallow( <IconButton label="WordPress" tooltip={ false } /> ); - expect( iconButton.name() ).toBe( 'Button' ); + expect( iconButton.name() ).toBe( 'ForwardRef(Button)' ); expect( iconButton.prop( 'aria-label' ) ).toBe( 'WordPress' ); } ); diff --git a/packages/components/src/menu-item/test/__snapshots__/index.js.snap b/packages/components/src/menu-item/test/__snapshots__/index.js.snap index 9ec9378f631a1..caa467a67a66b 100644 --- a/packages/components/src/menu-item/test/__snapshots__/index.js.snap +++ b/packages/components/src/menu-item/test/__snapshots__/index.js.snap @@ -17,7 +17,7 @@ exports[`MenuItem should match snapshot when all props provided 1`] = ` `; exports[`MenuItem should match snapshot when info is provided 1`] = ` -<Button +<ForwardRef(Button) aria-describedby="edit-post-feature-toggle__info-1" className="components-menu-item__button" role="menuitem" @@ -36,7 +36,7 @@ exports[`MenuItem should match snapshot when info is provided 1`] = ` <Shortcut className="components-menu-item__shortcut" /> -</Button> +</ForwardRef(Button)> `; exports[`MenuItem should match snapshot when isSelected and role are optionally provided 1`] = ` @@ -55,7 +55,7 @@ exports[`MenuItem should match snapshot when isSelected and role are optionally `; exports[`MenuItem should match snapshot when only label provided 1`] = ` -<Button +<ForwardRef(Button) className="components-menu-item__button" role="menuitem" > @@ -63,5 +63,5 @@ exports[`MenuItem should match snapshot when only label provided 1`] = ` <Shortcut className="components-menu-item__shortcut" /> -</Button> +</ForwardRef(Button)> `; diff --git a/packages/components/src/menu-item/test/index.js b/packages/components/src/menu-item/test/index.js index c5508e55629ac..f521add7452c8 100644 --- a/packages/components/src/menu-item/test/index.js +++ b/packages/components/src/menu-item/test/index.js @@ -9,8 +9,6 @@ import { noop } from 'lodash'; */ import { MenuItem } from '../'; -jest.mock( '../../button' ); - describe( 'MenuItem', () => { it( 'should match snapshot when only label provided', () => { const wrapper = shallow( diff --git a/packages/components/src/panel/test/body.js b/packages/components/src/panel/test/body.js index cda8c66bbf6d5..78d4774afccd8 100644 --- a/packages/components/src/panel/test/body.js +++ b/packages/components/src/panel/test/body.js @@ -8,8 +8,6 @@ import { shallow, mount } from 'enzyme'; */ import { PanelBody } from '../body'; -jest.mock( '../../button' ); - describe( 'PanelBody', () => { describe( 'basic rendering', () => { it( 'should render an empty div with the matching className', () => { diff --git a/packages/edit-post/src/components/header/more-menu/test/__snapshots__/index.js.snap b/packages/edit-post/src/components/header/more-menu/test/__snapshots__/index.js.snap index 4049a747b3fe0..9e8a474f6f921 100644 --- a/packages/edit-post/src/components/header/more-menu/test/__snapshots__/index.js.snap +++ b/packages/edit-post/src/components/header/more-menu/test/__snapshots__/index.js.snap @@ -23,7 +23,7 @@ exports[`MoreMenu should match snapshot 1`] = ` position="bottom" text="Show more tools & options" > - <Button + <ForwardRef(Button) aria-expanded={false} aria-label="Show more tools & options" className="components-icon-button" @@ -79,7 +79,7 @@ exports[`MoreMenu should match snapshot 1`] = ` </SVG> </Dashicon> </button> - </Button> + </ForwardRef(Button)> </Tooltip> </IconButton> </div> diff --git a/packages/edit-post/src/components/header/more-menu/test/index.js b/packages/edit-post/src/components/header/more-menu/test/index.js index 92daff0997151..83f9de19ba706 100644 --- a/packages/edit-post/src/components/header/more-menu/test/index.js +++ b/packages/edit-post/src/components/header/more-menu/test/index.js @@ -8,8 +8,6 @@ import { mount } from 'enzyme'; */ import MoreMenu from '../index'; -jest.mock( '../../../../../../components/src/button' ); - describe( 'MoreMenu', () => { it( 'should match snapshot', () => { const wrapper = mount( diff --git a/packages/edit-post/src/components/sidebar/plugin-post-publish-panel/test/index.js b/packages/edit-post/src/components/sidebar/plugin-post-publish-panel/test/index.js index 66b159bb25533..f4b5ca77eca16 100644 --- a/packages/edit-post/src/components/sidebar/plugin-post-publish-panel/test/index.js +++ b/packages/edit-post/src/components/sidebar/plugin-post-publish-panel/test/index.js @@ -9,8 +9,6 @@ import { render } from '@wordpress/element'; */ import PluginPostPublishPanel from '../'; -jest.mock( '../../../../../../components/src/button' ); - describe( 'PluginPostPublishPanel', () => { test( 'renders fill properly', () => { const div = document.createElement( 'div' ); diff --git a/packages/edit-post/src/components/sidebar/plugin-pre-publish-panel/test/index.js b/packages/edit-post/src/components/sidebar/plugin-pre-publish-panel/test/index.js index 1383341bfe705..9c011e1cbc2a0 100644 --- a/packages/edit-post/src/components/sidebar/plugin-pre-publish-panel/test/index.js +++ b/packages/edit-post/src/components/sidebar/plugin-pre-publish-panel/test/index.js @@ -9,8 +9,6 @@ import { render } from '@wordpress/element'; */ import PluginPrePublishPanel from '../'; -jest.mock( '../../../../../../components/src/button' ); - describe( 'PluginPrePublishPanel', () => { test( 'renders fill properly', () => { const div = document.createElement( 'div' ); diff --git a/packages/editor/src/components/block-controls/test/__snapshots__/index.js.snap b/packages/editor/src/components/block-controls/test/__snapshots__/index.js.snap index f1cf3c119e9ad..681c33a42d997 100644 --- a/packages/editor/src/components/block-controls/test/__snapshots__/index.js.snap +++ b/packages/editor/src/components/block-controls/test/__snapshots__/index.js.snap @@ -1,32 +1,45 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`BlockControls Should render a dynamic toolbar of controls 1`] = ` -<Fill - name="Block.Toolbar" -> - <Toolbar - controls={ - Array [ - Object { - "align": "left", - "icon": "editor-alignleft", - "title": "Align left", - }, - Object { - "align": "center", - "icon": "editor-aligncenter", - "title": "Align center", - }, - Object { - "align": "right", - "icon": "editor-alignright", - "title": "Align right", - }, - ] +exports[`BlockControls should render a dynamic toolbar of controls 1`] = ` +<ContextProvider + value={ + Object { + "clientId": undefined, + "focusedElement": null, + "isSelected": true, + "name": undefined, + "setFocusedElement": [Function], } - /> - <p> - Child - </p> -</Fill> + } +> + <WithFilters(Edit) + isSelected={true} + > + <IfBlockEditSelected(BlockControlsFill) + controls={ + Array [ + Object { + "align": "left", + "icon": "editor-alignleft", + "title": "Align left", + }, + Object { + "align": "center", + "icon": "editor-aligncenter", + "title": "Align center", + }, + Object { + "align": "right", + "icon": "editor-alignright", + "title": "Align right", + }, + ] + } + > + <p> + Child + </p> + </IfBlockEditSelected(BlockControlsFill)> + </WithFilters(Edit)> +</ContextProvider> `; diff --git a/packages/editor/src/components/block-controls/test/index.js b/packages/editor/src/components/block-controls/test/index.js index 1eee2fbf5af2a..f970dc030cadb 100644 --- a/packages/editor/src/components/block-controls/test/index.js +++ b/packages/editor/src/components/block-controls/test/index.js @@ -6,7 +6,8 @@ import { shallow } from 'enzyme'; /** * Internal dependencies */ -import { BlockControls } from '../'; +import BlockControls from '../'; +import BlockEdit from '../../block-edit'; describe( 'BlockControls', () => { const controls = [ @@ -27,9 +28,15 @@ describe( 'BlockControls', () => { }, ]; - // Skipped temporarily until Enzyme publishes new version that works with React 16.3.0 APIs. - // eslint-disable-next-line jest/no-disabled-tests - test.skip( 'Should render a dynamic toolbar of controls', () => { - expect( shallow( <BlockControls controls={ controls } children={ <p>Child</p> } /> ) ).toMatchSnapshot(); + it( 'should render a dynamic toolbar of controls', () => { + const wrapper = shallow( + <BlockEdit isSelected> + <BlockControls controls={ controls }> + <p>Child</p> + </BlockControls> + </BlockEdit> + ); + + expect( wrapper ).toMatchSnapshot(); } ); } ); diff --git a/packages/editor/src/components/post-preview-button/test/__snapshots__/index.js.snap b/packages/editor/src/components/post-preview-button/test/__snapshots__/index.js.snap index cfbde2342e780..fc928b5e12cb9 100644 --- a/packages/editor/src/components/post-preview-button/test/__snapshots__/index.js.snap +++ b/packages/editor/src/components/post-preview-button/test/__snapshots__/index.js.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`PostPreviewButton render() should render currentPostLink otherwise 1`] = ` -<Button +<ForwardRef(Button) className="editor-post-preview" disabled={false} href="https://wordpress.org/?p=1" @@ -20,11 +20,11 @@ exports[`PostPreviewButton render() should render currentPostLink otherwise 1`] > Click “Preview” to load a preview of this page, so you can make sure you’re happy with your blocks. </WithSelect(WithDispatch(DotTip))> -</Button> +</ForwardRef(Button)> `; exports[`PostPreviewButton render() should render previewLink if provided 1`] = ` -<Button +<ForwardRef(Button) className="editor-post-preview" disabled={false} href="https://wordpress.org/?p=1&preview=true" @@ -43,5 +43,5 @@ exports[`PostPreviewButton render() should render previewLink if provided 1`] = > Click “Preview” to load a preview of this page, so you can make sure you’re happy with your blocks. </WithSelect(WithDispatch(DotTip))> -</Button> +</ForwardRef(Button)> `; diff --git a/packages/editor/src/components/post-preview-button/test/index.js b/packages/editor/src/components/post-preview-button/test/index.js index 6920f27fc809c..7bd9856de05c7 100644 --- a/packages/editor/src/components/post-preview-button/test/index.js +++ b/packages/editor/src/components/post-preview-button/test/index.js @@ -8,8 +8,6 @@ import { shallow } from 'enzyme'; */ import { PostPreviewButton } from '../'; -jest.mock( '../../../../../components/src/button' ); - describe( 'PostPreviewButton', () => { describe( 'setPreviewWindowLink()', () => { it( 'should do nothing if there is no preview window', () => { diff --git a/packages/editor/src/components/post-publish-button/test/index.js b/packages/editor/src/components/post-publish-button/test/index.js index 30156438cc14f..be407f86e14cd 100644 --- a/packages/editor/src/components/post-publish-button/test/index.js +++ b/packages/editor/src/components/post-publish-button/test/index.js @@ -8,8 +8,6 @@ import { shallow } from 'enzyme'; */ import { PostPublishButton } from '../'; -jest.mock( '../../../../../components/src/button' ); - describe( 'PostPublishButton', () => { describe( 'aria-disabled', () => { it( 'should be true if post is currently saving', () => { @@ -196,6 +194,6 @@ describe( 'PostPublishButton', () => { /> ); - expect( wrapper.find( 'Button' ).prop( 'isBusy' ) ).toBe( true ); + expect( wrapper.find( 'ForwardRef(Button)' ).prop( 'isBusy' ) ).toBe( true ); } ); } ); diff --git a/packages/jest-preset-default/CHANGELOG.md b/packages/jest-preset-default/CHANGELOG.md index 7ff9fd2ced551..04eb1446a749d 100644 --- a/packages/jest-preset-default/CHANGELOG.md +++ b/packages/jest-preset-default/CHANGELOG.md @@ -8,6 +8,7 @@ ### Internal - The bundled `enzyme` dependency has been updated from requiring `^3.7.0` to requiring `^3.9.0` ([#13922](https://github.com/WordPress/gutenberg/pull/13922)). +- The bundled `enzyme-adapter-react-16` dependency has been updated from requiring `^1.6.0` to requiring `^1.10.0` ([#13922](https://github.com/WordPress/gutenberg/pull/13922)). ## 3.0.3 (2018-11-20) diff --git a/packages/jest-preset-default/package.json b/packages/jest-preset-default/package.json index bc73370bb4706..e8bbd45dd0b8b 100644 --- a/packages/jest-preset-default/package.json +++ b/packages/jest-preset-default/package.json @@ -33,7 +33,7 @@ "@wordpress/jest-console": "file:../jest-console", "babel-jest": "^24.1.0", "enzyme": "^3.9.0", - "enzyme-adapter-react-16": "^1.9.1", + "enzyme-adapter-react-16": "^1.10.0", "enzyme-to-json": "^3.3.5" }, "peerDependencies": { diff --git a/packages/nux/src/components/dot-tip/test/__snapshots__/index.js.snap b/packages/nux/src/components/dot-tip/test/__snapshots__/index.js.snap index d9639628174e7..155eac26c893a 100644 --- a/packages/nux/src/components/dot-tip/test/__snapshots__/index.js.snap +++ b/packages/nux/src/components/dot-tip/test/__snapshots__/index.js.snap @@ -15,11 +15,11 @@ exports[`DotTip should render correctly 1`] = ` It looks like you’re writing a letter. Would you like help? </p> <p> - <Button + <ForwardRef(Button) isLink={true} > Got it - </Button> + </ForwardRef(Button)> </p> <IconButton className="nux-dot-tip__disable" diff --git a/packages/nux/src/components/dot-tip/test/index.js b/packages/nux/src/components/dot-tip/test/index.js index aa69872d5e6e8..2f4736be1316c 100644 --- a/packages/nux/src/components/dot-tip/test/index.js +++ b/packages/nux/src/components/dot-tip/test/index.js @@ -9,8 +9,6 @@ import { noop } from 'lodash'; */ import { DotTip } from '..'; -jest.mock( '../../../../../components/src/button' ); - describe( 'DotTip', () => { it( 'should not render anything if invisible', () => { const wrapper = shallow( @@ -37,7 +35,7 @@ describe( 'DotTip', () => { It looks like you’re writing a letter. Would you like help? </DotTip> ); - wrapper.find( 'Button[children="Got it"]' ).first().simulate( 'click' ); + wrapper.find( 'ForwardRef(Button)[children="Got it"]' ).first().simulate( 'click' ); expect( onDismiss ).toHaveBeenCalled(); } ); From f7b002d284ff1a08bc257c5dc41303a7f532789f Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Thu, 28 Feb 2019 14:11:39 -0500 Subject: [PATCH 531/691] Plugin: Preserve inline scripts in Gutenberg override (#13581) * Plugin: Preserve inline scripts in Gutenberg override * Plugin: Restore storageKey assignment for persistence migration --- lib/client-assets.php | 209 ++++++------------------- phpunit/class-override-script-test.php | 66 ++++++++ 2 files changed, 113 insertions(+), 162 deletions(-) create mode 100644 phpunit/class-override-script-test.php diff --git a/lib/client-assets.php b/lib/client-assets.php index 834a08a2d83a9..dc9fff22a8386 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -36,7 +36,9 @@ function gutenberg_url( $path ) { /** * Registers a script according to `wp_register_script`. Honors this request by - * deregistering any script by the same handler before registration. + * reassigning internal dependency properties of any script handle already + * registered by that name. It does not deregister the original script, to + * avoid losing inline scripts which may have been attached. * * @since 4.1.0 * @@ -51,8 +53,36 @@ function gutenberg_url( $path ) { * Default 'false'. */ function gutenberg_override_script( $handle, $src, $deps = array(), $ver = false, $in_footer = false ) { - wp_deregister_script( $handle ); - wp_register_script( $handle, $src, $deps, $ver, $in_footer ); + global $wp_scripts; + + $script = $wp_scripts->query( $handle, 'registered' ); + if ( $script ) { + /* + * In many ways, this is a reimplementation of `wp_register_script` but + * bypassing consideration of whether a script by the given handle had + * already been registered. + */ + + // See: `_WP_Dependency::__construct` . + $script->src = $src; + $script->deps = $deps; + $script->ver = $ver; + + /* + * The script's `group` designation is an indication of whether it is + * to be printed in the header or footer. The behavior here defers to + * the arguments as passed. Specifically, group data is not assigned + * for a script unless it is designated to be printed in the footer. + */ + + // See: `wp_register_script` . + unset( $script->extra['group'] ); + if ( $in_footer ) { + $script->add_data( 'group', 1 ); + } + } else { + wp_register_script( $handle, $src, $deps, $ver, $in_footer ); + } } /** @@ -112,7 +142,15 @@ function gutenberg_register_scripts_and_styles() { gutenberg_register_vendor_scripts(); gutenberg_register_packages_scripts(); - // Inline scripts. + // Add nonce middleware which accounts for the absence of the heartbeat + // listener. This relies on API Fetch implementation running middlewares in + // order of last added, and that the original nonce middleware would defer + // to an X-WP-Nonce header already being present. This inline script should + // be removed once the following Core ticket is resolved in assigning the + // nonce received from heartbeat to the created middleware. + // + // See: https://core.trac.wordpress.org/ticket/46107 . + // See: https://github.com/WordPress/gutenberg/pull/13451 . global $wp_scripts; if ( isset( $wp_scripts->registered['wp-api-fetch'] ) ) { $wp_scripts->registered['wp-api-fetch']->deps[] = 'wp-hooks'; @@ -135,21 +173,16 @@ function gutenberg_register_scripts_and_styles() { ' }', ' }', ' )', - '} )()', + '} )();', ) ), ( wp_installing() && ! is_multisite() ) ? '' : wp_create_nonce( 'wp_rest' ) ), 'after' ); - wp_add_inline_script( - 'wp-api-fetch', - sprintf( - 'wp.apiFetch.use( wp.apiFetch.createRootURLMiddleware( "%s" ) );', - esc_url_raw( get_rest_url() ) - ), - 'after' - ); + + // TEMPORARY: Core does not (yet) provide persistence migration from the + // introduction of the block editor. wp_add_inline_script( 'wp-data', implode( @@ -158,160 +191,11 @@ function gutenberg_register_scripts_and_styles() { '( function() {', ' var userId = ' . get_current_user_ID() . ';', ' var storageKey = "WP_DATA_USER_" + userId;', - ' wp.data', - ' .use( wp.data.plugins.persistence, { storageKey: storageKey } )', - ' .use( wp.data.plugins.controls );', ' wp.data.plugins.persistence.__unstableMigrate( { storageKey: storageKey } );', '} )()', ) ) ); - global $wp_locale; - wp_add_inline_script( - 'wp-date', - sprintf( - 'wp.date.setSettings( %s );', - wp_json_encode( - array( - 'l10n' => array( - 'locale' => get_user_locale(), - 'months' => array_values( $wp_locale->month ), - 'monthsShort' => array_values( $wp_locale->month_abbrev ), - 'weekdays' => array_values( $wp_locale->weekday ), - 'weekdaysShort' => array_values( $wp_locale->weekday_abbrev ), - 'meridiem' => (object) $wp_locale->meridiem, - 'relative' => array( - /* translators: %s: duration */ - 'future' => __( '%s from now', 'default' ), - /* translators: %s: duration */ - 'past' => __( '%s ago', 'default' ), - ), - ), - 'formats' => array( - 'time' => get_option( 'time_format', __( 'g:i a', 'default' ) ), - 'date' => get_option( 'date_format', __( 'F j, Y', 'default' ) ), - 'datetime' => __( 'F j, Y g:i a', 'default' ), - 'datetimeAbbreviated' => __( 'M j, Y g:i a', 'default' ), - ), - 'timezone' => array( - 'offset' => get_option( 'gmt_offset', 0 ), - 'string' => get_option( 'timezone_string', 'UTC' ), - ), - ) - ) - ), - 'after' - ); - // Loading the old editor and its config to ensure the classic block works as expected. - wp_add_inline_script( - 'editor', - 'window.wp.oldEditor = window.wp.editor;', - 'after' - ); - - $tinymce_plugins = array( - 'charmap', - 'colorpicker', - 'hr', - 'lists', - 'media', - 'paste', - 'tabfocus', - 'textcolor', - 'fullscreen', - 'wordpress', - 'wpautoresize', - 'wpeditimage', - 'wpemoji', - 'wpgallery', - 'wplink', - 'wpdialogs', - 'wptextpattern', - 'wpview', - ); - $tinymce_plugins = apply_filters( 'tiny_mce_plugins', $tinymce_plugins, 'classic-block' ); - $tinymce_plugins = array_unique( $tinymce_plugins ); - - $toolbar1 = array( - 'formatselect', - 'bold', - 'italic', - 'bullist', - 'numlist', - 'blockquote', - 'alignleft', - 'aligncenter', - 'alignright', - 'link', - 'unlink', - 'wp_more', - 'spellchecker', - 'wp_add_media', - 'kitchensink', - ); - $toolbar1 = apply_filters( 'mce_buttons', $toolbar1, 'classic-block' ); - - $toolbar2 = array( - 'strikethrough', - 'hr', - 'forecolor', - 'pastetext', - 'removeformat', - 'charmap', - 'outdent', - 'indent', - 'undo', - 'redo', - 'wp_help', - ); - $toolbar2 = apply_filters( 'mce_buttons_2', $toolbar2, 'classic-block' ); - - $toolbar3 = apply_filters( 'mce_buttons_3', array(), 'classic-block' ); - $toolbar4 = apply_filters( 'mce_buttons_4', array(), 'classic-block' ); - - $external_plugins = apply_filters( 'mce_external_plugins', array(), 'classic-block' ); - - $tinymce_settings = array( - 'plugins' => implode( ',', $tinymce_plugins ), - 'toolbar1' => implode( ',', $toolbar1 ), - 'toolbar2' => implode( ',', $toolbar2 ), - 'toolbar3' => implode( ',', $toolbar3 ), - 'toolbar4' => implode( ',', $toolbar4 ), - 'external_plugins' => wp_json_encode( $external_plugins ), - 'classic_block_editor' => true, - ); - $tinymce_settings = apply_filters( 'tiny_mce_before_init', $tinymce_settings, 'classic-block' ); - - // Do "by hand" translation from PHP array to js object. - // Prevents breakage in some custom settings. - $init_obj = ''; - foreach ( $tinymce_settings as $key => $value ) { - if ( is_bool( $value ) ) { - $val = $value ? 'true' : 'false'; - $init_obj .= $key . ':' . $val . ','; - continue; - } elseif ( ! empty( $value ) && is_string( $value ) && ( - ( '{' == $value{0} && '}' == $value{strlen( $value ) - 1} ) || - ( '[' == $value{0} && ']' == $value{strlen( $value ) - 1} ) || - preg_match( '/^\(?function ?\(/', $value ) ) ) { - - $init_obj .= $key . ':' . $value . ','; - continue; - } - $init_obj .= $key . ':"' . $value . '",'; - } - - $init_obj = '{' . trim( $init_obj, ' ,' ) . '}'; - - $script = 'window.wpEditorL10n = { - tinymce: { - baseURL: ' . wp_json_encode( includes_url( 'js/tinymce' ) ) . ', - suffix: ' . ( SCRIPT_DEBUG ? '""' : '".min"' ) . ', - settings: ' . $init_obj . ', - } - }'; - - wp_add_inline_script( 'wp-block-library', $script, 'before' ); // Editor Styles. // This empty stylesheet is defined to ensure backward compatibility. @@ -1073,6 +957,7 @@ function gutenberg_editor_scripts_and_styles( $hook ) { 'post' => $post->ID, ) ); + wp_tinymce_inline_scripts(); wp_enqueue_editor(); /** diff --git a/phpunit/class-override-script-test.php b/phpunit/class-override-script-test.php new file mode 100644 index 0000000000000..af9e5d73fff19 --- /dev/null +++ b/phpunit/class-override-script-test.php @@ -0,0 +1,66 @@ +<?php +/** + * Test `gutenberg_override_script`. + * + * @package Gutenberg + */ + +class Override_Script_Test extends WP_UnitTestCase { + function setUp() { + parent::setUp(); + + wp_register_script( + 'gutenberg-dummy-script', + 'https://example.com/original', + array( 'original-dependency' ), + 'original-version', + false + ); + } + + function tearDown() { + parent::tearDown(); + + wp_deregister_script( 'gutenberg-dummy-script' ); + } + + /** + * Tests that script properties are overridden. + */ + function test_replaces_registered_properties() { + gutenberg_override_script( + 'gutenberg-dummy-script', + 'https://example.com/updated', + array( 'updated-dependency' ), + 'updated-version', + true + ); + + global $wp_scripts; + $script = $wp_scripts->query( 'gutenberg-dummy-script', 'registered' ); + $this->assertEquals( 'https://example.com/updated', $script->src ); + $this->assertEquals( array( 'updated-dependency' ), $script->deps ); + $this->assertEquals( 'updated-version', $script->ver ); + $this->assertEquals( 1, $script->extra['group'] ); + } + + /** + * Tests that new script registers normally if no handle by the name. + */ + function test_registers_new_script() { + gutenberg_override_script( + 'gutenberg-second-dummy-script', + 'https://example.com/', + array( 'dependency' ), + 'version', + true + ); + + global $wp_scripts; + $script = $wp_scripts->query( 'gutenberg-second-dummy-script', 'registered' ); + $this->assertEquals( 'https://example.com/', $script->src ); + $this->assertEquals( array( 'dependency' ), $script->deps ); + $this->assertEquals( 'version', $script->ver ); + $this->assertEquals( 1, $script->extra['group'] ); + } +} From c3cacd3d88014c913478d76e8bc24c849638f848 Mon Sep 17 00:00:00 2001 From: Darren Ethier <darren@roughsmootheng.in> Date: Thu, 28 Feb 2019 14:26:11 -0500 Subject: [PATCH 532/691] Refactor to remove usage of post related effects in packages/editor. (#13716) This pull is the first step in moving away from the lingering usage of effects in various data stores among packages. This pull specifically deals with post related effects in the @wordpress/editor package (`core/editor` store). --- .../developers/data/data-core-editor.md | 76 +- packages/editor/CHANGELOG.md | 1 + packages/editor/src/store/actions.js | 396 ++++++++- packages/editor/src/store/constants.js | 12 + packages/editor/src/store/controls.js | 103 ++- packages/editor/src/store/effects.js | 20 - packages/editor/src/store/effects/posts.js | 319 ------- packages/editor/src/store/index.js | 8 +- packages/editor/src/store/selectors.js | 22 +- packages/editor/src/store/test/actions.js | 775 +++++++++++++++++- packages/editor/src/store/test/effects.js | 197 ----- packages/editor/src/store/test/selectors.js | 2 +- .../editor/src/store/utils/notice-builder.js | 123 +++ .../src/store/utils/test/notice-builder.js | 182 ++++ 14 files changed, 1588 insertions(+), 648 deletions(-) delete mode 100644 packages/editor/src/store/effects/posts.js create mode 100644 packages/editor/src/store/utils/notice-builder.js create mode 100644 packages/editor/src/store/utils/test/notice-builder.js diff --git a/docs/designers-developers/developers/data/data-core-editor.md b/docs/designers-developers/developers/data/data-core-editor.md index 7e95832dc5bdb..3a22e11c90121 100644 --- a/docs/designers-developers/developers/data/data-core-editor.md +++ b/docs/designers-developers/developers/data/data-core-editor.md @@ -749,6 +749,43 @@ post has been received, by initialization or autosave. * post: Autosave post object. +### __experimentalRequestPostUpdateStart + +Optimistic action for dispatching that a post update request has started. + +*Parameters* + + * options: null + +### __experimentalRequestPostUpdateSuccess + +Optimistic action for indicating that the request post update has completed +successfully. + +*Parameters* + + * data: The data for the action. + * data.previousPost: The previous post prior to update. + * data.post: The new post after update + * data.isRevision: Whether the post is a revision or not. + * data.options: Options passed through from the original + action dispatch. + * data.postType: The post type object. + +### __experimentalRequestPostUpdateFailure + +Optimistic action for indicating that the request post update has completed +with a failure. + +*Parameters* + + * data: The data for the action + * data.post: The post that failed updating. + * data.edits: The fields that were being updated. + * data.error: The error from the failed call. + * data.options: Options passed through from the original + action dispatch. + ### updatePost Returns an action object used in signalling that a patch of updates for the @@ -760,7 +797,8 @@ latest version of the post have been received. ### setupEditorState -Returns an action object used to setup the editor state when first opening an editor. +Returns an action object used to setup the editor state when first opening +an editor. *Parameters* @@ -775,18 +813,34 @@ been edited. * edits: Post attributes to edit. +### __experimentalOptimisticUpdatePost + +Returns action object produced by the updatePost creator augmented by +an optimist option that signals optimistically applying updates. + +*Parameters* + + * edits: Updated post fields. + ### savePost -Returns an action object to save the post. +Action generator for saving the current post in the editor. *Parameters* - * options: Options for the save. - * options.isAutosave: Perform an autosave if true. + * options: null + +### refreshPost + +Action generator for handling refreshing the current post. + +### trashPost + +Action generator for trashing the current post in the editor. ### autosave -Returns an action object used in signalling that the post should autosave. +Action generator used in signalling that the post should autosave. *Parameters* @@ -864,7 +918,8 @@ to be updated. ### __experimentalConvertBlockToStatic -Returns an action object used to convert a reusable block into a static block. +Returns an action object used to convert a reusable block into a static +block. *Parameters* @@ -872,7 +927,8 @@ Returns an action object used to convert a reusable block into a static block. ### __experimentalConvertBlockToReusable -Returns an action object used to convert a static block into a reusable block. +Returns an action object used to convert a static block into a reusable +block. *Parameters* @@ -880,11 +936,13 @@ Returns an action object used to convert a static block into a reusable block. ### enablePublishSidebar -Returns an action object used in signalling that the user has enabled the publish sidebar. +Returns an action object used in signalling that the user has enabled the +publish sidebar. ### disablePublishSidebar -Returns an action object used in signalling that the user has disabled the publish sidebar. +Returns an action object used in signalling that the user has disabled the +publish sidebar. ### lockPostSaving diff --git a/packages/editor/CHANGELOG.md b/packages/editor/CHANGELOG.md index 8b64a350c46ac..7c9eb8c623ed9 100644 --- a/packages/editor/CHANGELOG.md +++ b/packages/editor/CHANGELOG.md @@ -18,6 +18,7 @@ - Removed `jQuery` dependency. - Removed `TinyMCE` dependency. - RichText: improve format boundaries. +- Refactor all post effects to action-generators using controls ([#13716](https://github.com/WordPress/gutenberg/pull/13716)) ## 9.0.7 (2019-01-03) diff --git a/packages/editor/src/store/actions.js b/packages/editor/src/store/actions.js index 0d508375c37c8..f1271ed3d96d1 100644 --- a/packages/editor/src/store/actions.js +++ b/packages/editor/src/store/actions.js @@ -1,12 +1,29 @@ /** * External dependencies */ -import { castArray } from 'lodash'; +import { castArray, pick } from 'lodash'; +import { BEGIN, COMMIT, REVERT } from 'redux-optimist'; /** * Internal dependencies */ -import { dispatch } from './controls'; +import { + dispatch, + select, + resolveSelect, + apiFetch, +} from './controls'; +import { + STORE_KEY, + POST_UPDATE_TRANSACTION_ID, + SAVE_POST_NOTICE_ID, + TRASH_POST_NOTICE_ID, +} from './constants'; +import { + getNotificationArgumentsForSaveSuccess, + getNotificationArgumentsForSaveFail, + getNotificationArgumentsForTrashFail, +} from './utils/notice-builder'; /** * Returns an action object used in signalling that editor has initialized with @@ -57,6 +74,87 @@ export function resetAutosave( post ) { }; } +/** + * Optimistic action for dispatching that a post update request has started. + * + * @param {Object} options + * + * @return {Object} An action object + */ +export function __experimentalRequestPostUpdateStart( options = {} ) { + return { + type: 'REQUEST_POST_UPDATE_START', + optimist: { type: BEGIN, id: POST_UPDATE_TRANSACTION_ID }, + options, + }; +} + +/** + * Optimistic action for indicating that the request post update has completed + * successfully. + * + * @param {Object} data The data for the action. + * @param {Object} data.previousPost The previous post prior to update. + * @param {Object} data.post The new post after update + * @param {boolean} data.isRevision Whether the post is a revision or not. + * @param {Object} data.options Options passed through from the original + * action dispatch. + * @param {Object} data.postType The post type object. + * + * @return {Object} Action object. + */ +export function __experimentalRequestPostUpdateSuccess( { + previousPost, + post, + isRevision, + options, + postType, +} ) { + return { + type: 'REQUEST_POST_UPDATE_SUCCESS', + previousPost, + post, + optimist: { + // Note: REVERT is not a failure case here. Rather, it + // is simply reversing the assumption that the updates + // were applied to the post proper, such that the post + // treated as having unsaved changes. + type: isRevision ? REVERT : COMMIT, + id: POST_UPDATE_TRANSACTION_ID, + }, + options, + postType, + }; +} + +/** + * Optimistic action for indicating that the request post update has completed + * with a failure. + * + * @param {Object} data The data for the action + * @param {Object} data.post The post that failed updating. + * @param {Object} data.edits The fields that were being updated. + * @param {*} data.error The error from the failed call. + * @param {Object} data.options Options passed through from the original + * action dispatch. + * @return {Object} An action object + */ +export function __experimentalRequestPostUpdateFailure( { + post, + edits, + error, + options, +} ) { + return { + type: 'REQUEST_POST_UPDATE_FAILURE', + optimist: { type: REVERT, id: POST_UPDATE_TRANSACTION_ID }, + post, + edits, + error, + options, + }; +} + /** * Returns an action object used in signalling that a patch of updates for the * latest version of the post have been received. @@ -73,7 +171,8 @@ export function updatePost( edits ) { } /** - * Returns an action object used to setup the editor state when first opening an editor. + * Returns an action object used to setup the editor state when first opening + * an editor. * * @param {Object} post Post object. * @@ -102,43 +201,282 @@ export function editPost( edits ) { } /** - * Returns an action object to save the post. + * Returns action object produced by the updatePost creator augmented by + * an optimist option that signals optimistically applying updates. * - * @param {Object} options Options for the save. - * @param {boolean} options.isAutosave Perform an autosave if true. + * @param {Object} edits Updated post fields. * * @return {Object} Action object. */ -export function savePost( options = {} ) { +export function __experimentalOptimisticUpdatePost( edits ) { return { - type: 'REQUEST_POST_UPDATE', - options, + ...updatePost( edits ), + optimist: { id: POST_UPDATE_TRANSACTION_ID }, }; } -export function refreshPost() { - return { - type: 'REFRESH_POST', +/** + * Action generator for saving the current post in the editor. + * + * @param {Object} options + */ +export function* savePost( options = {} ) { + const isEditedPostSaveable = yield select( + STORE_KEY, + 'isEditedPostSaveable' + ); + if ( ! isEditedPostSaveable ) { + return; + } + let edits = yield select( + STORE_KEY, + 'getPostEdits' + ); + const isAutosave = !! options.isAutosave; + + if ( isAutosave ) { + edits = pick( edits, [ 'title', 'content', 'excerpt' ] ); + } + + const isEditedPostNew = yield select( + STORE_KEY, + 'isEditedPostNew', + ); + + // New posts (with auto-draft status) must be explicitly assigned draft + // status if there is not already a status assigned in edits (publish). + // Otherwise, they are wrongly left as auto-draft. Status is not always + // respected for autosaves, so it cannot simply be included in the pick + // above. This behavior relies on an assumption that an auto-draft post + // would never be saved by anyone other than the owner of the post, per + // logic within autosaves REST controller to save status field only for + // draft/auto-draft by current user. + // + // See: https://core.trac.wordpress.org/ticket/43316#comment:88 + // See: https://core.trac.wordpress.org/ticket/43316#comment:89 + if ( isEditedPostNew ) { + edits = { status: 'draft', ...edits }; + } + + const post = yield select( + STORE_KEY, + 'getCurrentPost' + ); + + const editedPostContent = yield select( + STORE_KEY, + 'getEditedPostContent' + ); + + let toSend = { + ...edits, + content: editedPostContent, + id: post.id, }; + + const currentPostType = yield select( + STORE_KEY, + 'getCurrentPostType' + ); + + const postType = yield resolveSelect( + 'core', + 'getPostType', + currentPostType + ); + + yield dispatch( + STORE_KEY, + '__experimentalRequestPostUpdateStart', + options, + ); + + // Optimistically apply updates under the assumption that the post + // will be updated. See below logic in success resolution for revert + // if the autosave is applied as a revision. + yield dispatch( + STORE_KEY, + '__experimentalOptimisticUpdatePost', + toSend + ); + + let path = `/wp/v2/${ postType.rest_base }/${ post.id }`; + let method = 'PUT'; + if ( isAutosave ) { + const autoSavePost = yield select( + STORE_KEY, + 'getAutosave', + ); + // Ensure autosaves contain all expected fields, using autosave or + // post values as fallback if not otherwise included in edits. + toSend = { + ...pick( post, [ 'title', 'content', 'excerpt' ] ), + ...autoSavePost, + ...toSend, + }; + path += '/autosaves'; + method = 'POST'; + } else { + yield dispatch( + 'core/notices', + 'removeNotice', + SAVE_POST_NOTICE_ID, + ); + yield dispatch( + 'core/notices', + 'removeNotice', + 'autosave-exists', + ); + } + + try { + const newPost = yield apiFetch( { + path, + method, + data: toSend, + } ); + const resetAction = isAutosave ? 'resetAutosave' : 'resetPost'; + + yield dispatch( STORE_KEY, resetAction, newPost ); + + yield dispatch( + STORE_KEY, + '__experimentalRequestPostUpdateSuccess', + { + previousPost: post, + post: newPost, + options, + postType, + // An autosave may be processed by the server as a regular save + // when its update is requested by the author and the post was + // draft or auto-draft. + isRevision: newPost.id !== post.id, + } + ); + + const notifySuccessArgs = getNotificationArgumentsForSaveSuccess( { + previousPost: post, + post: newPost, + postType, + options, + } ); + if ( notifySuccessArgs.length > 0 ) { + yield dispatch( + 'core/notices', + 'createSuccessNotice', + ...notifySuccessArgs + ); + } + } catch ( error ) { + yield dispatch( + STORE_KEY, + '__experimentalRequestPostUpdateFailure', + { post, edits, error, options } + ); + const notifyFailArgs = getNotificationArgumentsForSaveFail( { + post, + edits, + error, + } ); + if ( notifyFailArgs.length > 0 ) { + yield dispatch( + 'core/notices', + 'createErrorNotice', + ...notifyFailArgs + ); + } + } } -export function trashPost( postId, postType ) { - return { - type: 'TRASH_POST', - postId, - postType, - }; +/** + * Action generator for handling refreshing the current post. + */ +export function* refreshPost() { + const post = yield select( + STORE_KEY, + 'getCurrentPost' + ); + const postTypeSlug = yield select( + STORE_KEY, + 'getCurrentPostType' + ); + const postType = yield resolveSelect( + 'core', + 'getPostType', + postTypeSlug + ); + const newPost = yield apiFetch( + { + // Timestamp arg allows caller to bypass browser caching, which is + // expected for this specific function. + path: `/wp/v2/${ postType.rest_base }/${ post.id }` + + `?context=edit&_timestamp=${ Date.now() }`, + } + ); + yield dispatch( + STORE_KEY, + 'resetPost', + newPost + ); } /** - * Returns an action object used in signalling that the post should autosave. + * Action generator for trashing the current post in the editor. + */ +export function* trashPost() { + const postTypeSlug = yield select( + STORE_KEY, + 'getCurrentPostType' + ); + const postType = yield resolveSelect( + 'core', + 'getPostType', + postTypeSlug + ); + yield dispatch( + 'core/notices', + 'removeNotice', + TRASH_POST_NOTICE_ID + ); + try { + const post = yield select( + STORE_KEY, + 'getCurrentPost' + ); + yield apiFetch( + { + path: `/wp/v2/${ postType.rest_base }/${ post.id }`, + method: 'DELETE', + } + ); + + // TODO: This should be an updatePost action (updating subsets of post + // properties), but right now editPost is tied with change detection. + yield dispatch( + STORE_KEY, + 'resetPost', + { ...post, status: 'trash' } + ); + } catch ( error ) { + yield dispatch( + 'core/notices', + 'createErrorNotice', + ...getNotificationArgumentsForTrashFail( { error } ), + ); + } +} + +/** + * Action generator used in signalling that the post should autosave. * * @param {Object?} options Extra flags to identify the autosave. - * - * @return {Object} Action object. */ -export function autosave( options ) { - return savePost( { isAutosave: true, ...options } ); +export function* autosave( options ) { + yield dispatch( + STORE_KEY, + 'savePost', + { isAutosave: true, ...options } + ); } /** @@ -264,7 +602,8 @@ export function __experimentalUpdateReusableBlockTitle( id, title ) { } /** - * Returns an action object used to convert a reusable block into a static block. + * Returns an action object used to convert a reusable block into a static + * block. * * @param {string} clientId The client ID of the block to attach. * @@ -278,7 +617,8 @@ export function __experimentalConvertBlockToStatic( clientId ) { } /** - * Returns an action object used to convert a static block into a reusable block. + * Returns an action object used to convert a static block into a reusable + * block. * * @param {string} clientIds The client IDs of the block to detach. * @@ -292,7 +632,8 @@ export function __experimentalConvertBlockToReusable( clientIds ) { } /** - * Returns an action object used in signalling that the user has enabled the publish sidebar. + * Returns an action object used in signalling that the user has enabled the + * publish sidebar. * * @return {Object} Action object */ @@ -303,7 +644,8 @@ export function enablePublishSidebar() { } /** - * Returns an action object used in signalling that the user has disabled the publish sidebar. + * Returns an action object used in signalling that the user has disabled the + * publish sidebar. * * @return {Object} Action object */ diff --git a/packages/editor/src/store/constants.js b/packages/editor/src/store/constants.js index f07ca417f9d6e..8f8f1bd0afcef 100644 --- a/packages/editor/src/store/constants.js +++ b/packages/editor/src/store/constants.js @@ -7,3 +7,15 @@ export const EDIT_MERGE_PROPERTIES = new Set( [ 'meta', ] ); + +/** + * Constant for the store module (or reducer) key. + * @type {string} + */ +export const STORE_KEY = 'core/editor'; + +export const POST_UPDATE_TRANSACTION_ID = 'post-update'; +export const SAVE_POST_NOTICE_ID = 'SAVE_POST_NOTICE_ID'; +export const TRASH_POST_NOTICE_ID = 'TRASH_POST_NOTICE_ID'; +export const PERMALINK_POSTNAME_REGEX = /%(?:postname|pagename)%/; +export const ONE_MINUTE_IN_MS = 60 * 1000; diff --git a/packages/editor/src/store/controls.js b/packages/editor/src/store/controls.js index fc873ad43aa39..597a5f726145b 100644 --- a/packages/editor/src/store/controls.js +++ b/packages/editor/src/store/controls.js @@ -1,17 +1,69 @@ /** * WordPress dependencies */ +import triggerFetch from '@wordpress/api-fetch'; import { createRegistryControl } from '@wordpress/data'; /** - * Dispatches an action. + * Dispatches a control action for triggering an api fetch call. * - * @param {string} storeKey Store key. - * @param {string} actionName Action name. - * @param {Array} args Action arguments. + * @param {Object} request Arguments for the fetch request. * * @return {Object} control descriptor. */ +export function apiFetch( request ) { + return { + type: 'API_FETCH', + request, + }; +} + +/** + * Dispatches a control action for triggering a registry select. + * + * @param {string} storeKey + * @param {string} selectorName + * @param {Array} args Arguments for the select. + * + * @return {Object} control descriptor. + */ +export function select( storeKey, selectorName, ...args ) { + return { + type: 'SELECT', + storeKey, + selectorName, + args, + }; +} + +/** + * Dispatches a control action for triggering a registry select that has a + * resolver. + * + * @param {string} storeKey + * @param {string} selectorName + * @param {Array} args Arguments for the select. + * + * @return {Object} control descriptor. + */ +export function resolveSelect( storeKey, selectorName, ...args ) { + return { + type: 'RESOLVE_SELECT', + storeKey, + selectorName, + args, + }; +} + +/** + * Dispatches a control action for triggering a registry dispatch. + * + * @param {string} storeKey + * @param {string} actionName + * @param {Array} args Arguments for the dispatch action. + * + * @return {Object} control descriptor. + */ export function dispatch( storeKey, actionName, ...args ) { return { type: 'DISPATCH', @@ -21,10 +73,41 @@ export function dispatch( storeKey, actionName, ...args ) { }; } -const controls = { - DISPATCH: createRegistryControl( ( registry ) => ( { storeKey, actionName, args } ) => { - return registry.dispatch( storeKey )[ actionName ]( ...args ); - } ), -}; +export default { + API_FETCH( { request } ) { + return triggerFetch( request ); + }, + SELECT: createRegistryControl( + ( registry ) => ( { storeKey, selectorName, args } ) => { + return registry.select( storeKey )[ selectorName ]( ...args ); + } + ), + DISPATCH: createRegistryControl( + ( registry ) => ( { storeKey, actionName, args } ) => { + return registry.dispatch( storeKey )[ actionName ]( ...args ); + } + ), + RESOLVE_SELECT: createRegistryControl( + ( registry ) => ( { storeKey, selectorName, args } ) => { + return new Promise( ( resolve ) => { + const hasFinished = () => registry.select( 'core/data' ) + .hasFinishedResolution( storeKey, selectorName, args ); + const getResult = () => registry.select( storeKey )[ selectorName ] + .apply( null, args ); -export default controls; + // trigger the selector (to trigger the resolver) + const result = getResult(); + if ( hasFinished() ) { + return resolve( result ); + } + + const unsubscribe = registry.subscribe( () => { + if ( hasFinished() ) { + unsubscribe(); + resolve( getResult() ); + } + } ); + } ); + } + ), +}; diff --git a/packages/editor/src/store/effects.js b/packages/editor/src/store/effects.js index de77fbc45aab5..84f5115113766 100644 --- a/packages/editor/src/store/effects.js +++ b/packages/editor/src/store/effects.js @@ -26,28 +26,8 @@ import { convertBlockToStatic, receiveReusableBlocks, } from './effects/reusable-blocks'; -import { - requestPostUpdate, - requestPostUpdateSuccess, - requestPostUpdateFailure, - trashPost, - trashPostFailure, - refreshPost, -} from './effects/posts'; export default { - REQUEST_POST_UPDATE: ( action, store ) => { - requestPostUpdate( action, store ); - }, - REQUEST_POST_UPDATE_SUCCESS: requestPostUpdateSuccess, - REQUEST_POST_UPDATE_FAILURE: requestPostUpdateFailure, - TRASH_POST: ( action, store ) => { - trashPost( action, store ); - }, - TRASH_POST_FAILURE: trashPostFailure, - REFRESH_POST: ( action, store ) => { - refreshPost( action, store ); - }, SETUP_EDITOR( action ) { const { post, edits, template } = action; diff --git a/packages/editor/src/store/effects/posts.js b/packages/editor/src/store/effects/posts.js deleted file mode 100644 index f9cd28fd0adbb..0000000000000 --- a/packages/editor/src/store/effects/posts.js +++ /dev/null @@ -1,319 +0,0 @@ -/** - * External dependencies - */ -import { BEGIN, COMMIT, REVERT } from 'redux-optimist'; -import { get, pick, includes } from 'lodash'; - -/** - * WordPress dependencies - */ -import apiFetch from '@wordpress/api-fetch'; -import { __ } from '@wordpress/i18n'; -// TODO: Ideally this would be the only dispatch in scope. This requires either -// refactoring editor actions to yielded controls, or replacing direct dispatch -// on the editor store with action creators (e.g. `REQUEST_POST_UPDATE_START`). -import { dispatch as dataDispatch } from '@wordpress/data'; - -/** - * Internal dependencies - */ -import { - resetAutosave, - resetPost, - updatePost, -} from '../actions'; -import { - getCurrentPost, - getPostEdits, - getEditedPostContent, - getAutosave, - getCurrentPostType, - isEditedPostSaveable, - isEditedPostNew, - POST_UPDATE_TRANSACTION_ID, -} from '../selectors'; -import { resolveSelector } from './utils'; - -/** - * Module Constants - */ -export const SAVE_POST_NOTICE_ID = 'SAVE_POST_NOTICE_ID'; -const TRASH_POST_NOTICE_ID = 'TRASH_POST_NOTICE_ID'; - -/** - * Request Post Update Effect handler - * - * @param {Object} action the fetchReusableBlocks action object. - * @param {Object} store Redux Store. - */ -export const requestPostUpdate = async ( action, store ) => { - const { dispatch, getState } = store; - const state = getState(); - - // Prevent save if not saveable. - // We don't check for dirtiness here as this can be overridden in the UI. - if ( ! isEditedPostSaveable( state ) ) { - return; - } - - let edits = getPostEdits( state ); - const isAutosave = !! action.options.isAutosave; - if ( isAutosave ) { - edits = pick( edits, [ 'title', 'content', 'excerpt' ] ); - } - - // New posts (with auto-draft status) must be explicitly assigned draft - // status if there is not already a status assigned in edits (publish). - // Otherwise, they are wrongly left as auto-draft. Status is not always - // respected for autosaves, so it cannot simply be included in the pick - // above. This behavior relies on an assumption that an auto-draft post - // would never be saved by anyone other than the owner of the post, per - // logic within autosaves REST controller to save status field only for - // draft/auto-draft by current user. - // - // See: https://core.trac.wordpress.org/ticket/43316#comment:88 - // See: https://core.trac.wordpress.org/ticket/43316#comment:89 - if ( isEditedPostNew( state ) ) { - edits = { status: 'draft', ...edits }; - } - - const post = getCurrentPost( state ); - - let toSend = { - ...edits, - content: getEditedPostContent( state ), - id: post.id, - }; - - const postType = await resolveSelector( 'core', 'getPostType', getCurrentPostType( state ) ); - - dispatch( { - type: 'REQUEST_POST_UPDATE_START', - optimist: { type: BEGIN, id: POST_UPDATE_TRANSACTION_ID }, - options: action.options, - } ); - - // Optimistically apply updates under the assumption that the post - // will be updated. See below logic in success resolution for revert - // if the autosave is applied as a revision. - dispatch( { - ...updatePost( toSend ), - optimist: { id: POST_UPDATE_TRANSACTION_ID }, - } ); - - let request; - if ( isAutosave ) { - // Ensure autosaves contain all expected fields, using autosave or - // post values as fallback if not otherwise included in edits. - toSend = { - ...pick( post, [ 'title', 'content', 'excerpt' ] ), - ...getAutosave( state ), - ...toSend, - }; - - request = apiFetch( { - path: `/wp/v2/${ postType.rest_base }/${ post.id }/autosaves`, - method: 'POST', - data: toSend, - } ); - } else { - dataDispatch( 'core/notices' ).removeNotice( SAVE_POST_NOTICE_ID ); - dataDispatch( 'core/notices' ).removeNotice( 'autosave-exists' ); - - request = apiFetch( { - path: `/wp/v2/${ postType.rest_base }/${ post.id }`, - method: 'PUT', - data: toSend, - } ); - } - - try { - const newPost = await request; - const reset = isAutosave ? resetAutosave : resetPost; - dispatch( reset( newPost ) ); - - // An autosave may be processed by the server as a regular save - // when its update is requested by the author and the post was - // draft or auto-draft. - const isRevision = newPost.id !== post.id; - - dispatch( { - type: 'REQUEST_POST_UPDATE_SUCCESS', - previousPost: post, - post: newPost, - optimist: { - // Note: REVERT is not a failure case here. Rather, it - // is simply reversing the assumption that the updates - // were applied to the post proper, such that the post - // treated as having unsaved changes. - type: isRevision ? REVERT : COMMIT, - id: POST_UPDATE_TRANSACTION_ID, - }, - options: action.options, - postType, - } ); - } catch ( error ) { - dispatch( { - type: 'REQUEST_POST_UPDATE_FAILURE', - optimist: { type: REVERT, id: POST_UPDATE_TRANSACTION_ID }, - post, - edits, - error, - options: action.options, - } ); - } -}; - -/** - * Request Post Update Success Effect handler - * - * @param {Object} action action object. - * @param {Object} store Redux Store. - */ -export const requestPostUpdateSuccess = ( action ) => { - const { previousPost, post, postType } = action; - - // Autosaves are neither shown a notice nor redirected. - if ( get( action.options, [ 'isAutosave' ] ) ) { - return; - } - - const publishStatus = [ 'publish', 'private', 'future' ]; - const isPublished = includes( publishStatus, previousPost.status ); - const willPublish = includes( publishStatus, post.status ); - - let noticeMessage; - let shouldShowLink = get( postType, [ 'viewable' ], false ); - - if ( ! isPublished && ! willPublish ) { - // If saving a non-published post, don't show notice. - noticeMessage = null; - } else if ( isPublished && ! willPublish ) { - // If undoing publish status, show specific notice - noticeMessage = postType.labels.item_reverted_to_draft; - shouldShowLink = false; - } else if ( ! isPublished && willPublish ) { - // If publishing or scheduling a post, show the corresponding - // publish message - noticeMessage = { - publish: postType.labels.item_published, - private: postType.labels.item_published_privately, - future: postType.labels.item_scheduled, - }[ post.status ]; - } else { - // Generic fallback notice - noticeMessage = postType.labels.item_updated; - } - - if ( noticeMessage ) { - const actions = []; - if ( shouldShowLink ) { - actions.push( { - label: postType.labels.view_item, - url: post.link, - } ); - } - - dataDispatch( 'core/notices' ).createSuccessNotice( - noticeMessage, - { - id: SAVE_POST_NOTICE_ID, - actions, - } - ); - } -}; - -/** - * Request Post Update Failure Effect handler - * - * @param {Object} action action object. - */ -export const requestPostUpdateFailure = ( action ) => { - const { post, edits, error } = action; - - if ( error && 'rest_autosave_no_changes' === error.code ) { - // Autosave requested a new autosave, but there were no changes. This shouldn't - // result in an error notice for the user. - return; - } - - const publishStatus = [ 'publish', 'private', 'future' ]; - const isPublished = publishStatus.indexOf( post.status ) !== -1; - // If the post was being published, we show the corresponding publish error message - // Unless we publish an "updating failed" message - const messages = { - publish: __( 'Publishing failed' ), - private: __( 'Publishing failed' ), - future: __( 'Scheduling failed' ), - }; - const noticeMessage = ! isPublished && publishStatus.indexOf( edits.status ) !== -1 ? - messages[ edits.status ] : - __( 'Updating failed' ); - - dataDispatch( 'core/notices' ).createErrorNotice( noticeMessage, { - id: SAVE_POST_NOTICE_ID, - } ); -}; - -/** - * Trash Post Effect handler - * - * @param {Object} action action object. - * @param {Object} store Redux Store. - */ -export const trashPost = async ( action, store ) => { - const { dispatch, getState } = store; - const { postId } = action; - const postTypeSlug = getCurrentPostType( getState() ); - const postType = await resolveSelector( 'core', 'getPostType', postTypeSlug ); - - dataDispatch( 'core/notices' ).removeNotice( TRASH_POST_NOTICE_ID ); - try { - await apiFetch( { path: `/wp/v2/${ postType.rest_base }/${ postId }`, method: 'DELETE' } ); - const post = getCurrentPost( getState() ); - - // TODO: This should be an updatePost action (updating subsets of post properties), - // But right now editPost is tied with change detection. - dispatch( resetPost( { ...post, status: 'trash' } ) ); - } catch ( error ) { - dispatch( { - ...action, - type: 'TRASH_POST_FAILURE', - error, - } ); - } -}; - -/** - * Trash Post Failure Effect handler - * - * @param {Object} action action object. - * @param {Object} store Redux Store. - */ -export const trashPostFailure = ( action ) => { - const message = action.error.message && action.error.code !== 'unknown_error' ? action.error.message : __( 'Trashing failed' ); - dataDispatch( 'core/notices' ).createErrorNotice( message, { - id: TRASH_POST_NOTICE_ID, - } ); -}; - -/** - * Refresh Post Effect handler - * - * @param {Object} action action object. - * @param {Object} store Redux Store. - */ -export const refreshPost = async ( action, store ) => { - const { dispatch, getState } = store; - - const state = getState(); - const post = getCurrentPost( state ); - const postTypeSlug = getCurrentPostType( getState() ); - const postType = await resolveSelector( 'core', 'getPostType', postTypeSlug ); - const newPost = await apiFetch( { - // Timestamp arg allows caller to bypass browser caching, which is expected for this specific function. - path: `/wp/v2/${ postType.rest_base }/${ post.id }?context=edit&_timestamp=${ Date.now() }`, - } ); - dispatch( resetPost( newPost ) ); -}; diff --git a/packages/editor/src/store/index.js b/packages/editor/src/store/index.js index bc7b51a604fad..42af629bcce0d 100644 --- a/packages/editor/src/store/index.js +++ b/packages/editor/src/store/index.js @@ -11,13 +11,9 @@ import applyMiddlewares from './middlewares'; import * as selectors from './selectors'; import * as actions from './actions'; import controls from './controls'; +import { STORE_KEY } from './constants'; -/** - * Module Constants - */ -const MODULE_KEY = 'core/editor'; - -const store = registerStore( MODULE_KEY, { +const store = registerStore( STORE_KEY, { reducer, selectors, actions, diff --git a/packages/editor/src/store/selectors.js b/packages/editor/src/store/selectors.js index c3147abda7063..c4d97cbd741ba 100644 --- a/packages/editor/src/store/selectors.js +++ b/packages/editor/src/store/selectors.js @@ -27,18 +27,12 @@ import { createRegistrySelector } from '@wordpress/data'; * Internal dependencies */ import { PREFERENCES_DEFAULTS } from './defaults'; -import { EDIT_MERGE_PROPERTIES } from './constants'; - -/*** - * Module constants - */ -export const POST_UPDATE_TRANSACTION_ID = 'post-update'; -const PERMALINK_POSTNAME_REGEX = /%(?:postname|pagename)%/; -export const INSERTER_UTILITY_HIGH = 3; -export const INSERTER_UTILITY_MEDIUM = 2; -export const INSERTER_UTILITY_LOW = 1; -export const INSERTER_UTILITY_NONE = 0; -const ONE_MINUTE_IN_MS = 60 * 1000; +import { + EDIT_MERGE_PROPERTIES, + POST_UPDATE_TRANSACTION_ID, + PERMALINK_POSTNAME_REGEX, + ONE_MINUTE_IN_MS, +} from './constants'; /** * Shared reference to an empty object for cases where it is important to avoid @@ -124,7 +118,7 @@ export function isEditedPostDirty( state ) { return true; } - // Edits and change detectiona are reset at the start of a save, but a post + // Edits and change detection are reset at the start of a save, but a post // is still considered dirty until the point at which the save completes. // Because the save is performed optimistically, the prior states are held // until committed. These can be referenced to determine whether there's a @@ -264,7 +258,7 @@ export function getCurrentPostAttribute( state, attributeName ) { /** * Returns a single attribute of the post being edited, preferring the unsaved - * edit if one exists, but mergiging with the attribute value for the last known + * edit if one exists, but merging with the attribute value for the last known * saved state of the post (this is needed for some nested attributes like meta). * * @param {Object} state Global application state. diff --git a/packages/editor/src/store/test/actions.js b/packages/editor/src/store/test/actions.js index ec46b287c394e..296a365fe4c49 100644 --- a/packages/editor/src/store/test/actions.js +++ b/packages/editor/src/store/test/actions.js @@ -1,26 +1,635 @@ +/** + * External dependencies + */ +import { BEGIN, COMMIT, REVERT } from 'redux-optimist'; + /** * Internal dependencies */ +import * as actions from '../actions'; +import { select, dispatch, apiFetch, resolveSelect } from '../controls'; import { - __experimentalFetchReusableBlocks as fetchReusableBlocks, - __experimentalSaveReusableBlock as saveReusableBlock, - __experimentalDeleteReusableBlock as deleteReusableBlock, - __experimentalConvertBlockToStatic as convertBlockToStatic, - __experimentalConvertBlockToReusable as convertBlockToReusable, - setupEditor, - resetPost, - editPost, - savePost, - trashPost, - redo, - undo, -} from '../actions'; + STORE_KEY, + SAVE_POST_NOTICE_ID, + TRASH_POST_NOTICE_ID, + POST_UPDATE_TRANSACTION_ID, +} from '../constants'; + +jest.mock( '../controls' ); + +select.mockImplementation( ( ...args ) => { + const { select: actualSelect } = jest.requireActual( '../controls' ); + return actualSelect( ...args ); +} ); + +dispatch.mockImplementation( ( ...args ) => { + const { dispatch: actualDispatch } = jest.requireActual( '../controls' ); + return actualDispatch( ...args ); +} ); + +resolveSelect.mockImplementation( ( ...args ) => { + const { resolveSelect: selectResolver } = jest + .requireActual( '../controls' ); + return selectResolver( ...args ); +} ); + +const apiFetchThrowError = ( error ) => { + apiFetch.mockClear(); + apiFetch.mockImplementation( () => { + throw error; + } ); +}; + +const apiFetchDoActual = () => { + apiFetch.mockClear(); + apiFetch.mockImplementation( ( ...args ) => { + const { apiFetch: fetch } = jest.requireActual( '../controls' ); + return fetch( ...args ); + } ); +}; + +const postType = { + rest_base: 'posts', + labels: { + item_updated: 'Updated Post', + item_published: 'Post published', + }, +}; +const postTypeSlug = 'post'; + +describe( 'Post generator actions', () => { + describe( 'savePost()', () => { + let fulfillment, + edits, + currentPost, + currentPostStatus, + editPostToSendOptimistic, + autoSavePost, + autoSavePostToSend, + savedPost, + savedPostStatus, + isAutosave, + isEditedPostNew, + savedPostMessage; + beforeEach( () => { + edits = ( defaultStatus = null ) => { + const postObject = { + title: 'foo', + content: 'bar', + excerpt: 'cheese', + foo: 'bar', + }; + if ( defaultStatus !== null ) { + postObject.status = defaultStatus; + } + return postObject; + }; + currentPost = () => ( { + id: 44, + title: 'bar', + content: 'bar', + excerpt: 'crackers', + status: currentPostStatus, + } ); + editPostToSendOptimistic = () => { + const postObject = { + ...edits(), + content: editedPostContent, + id: currentPost().id, + }; + if ( ! postObject.status && isEditedPostNew ) { + postObject.status = 'draft'; + } + if ( isAutosave ) { + delete postObject.foo; + } + return postObject; + }; + autoSavePost = { status: 'autosave', bar: 'foo' }; + autoSavePostToSend = () => ( + { + ...editPostToSendOptimistic(), + bar: 'foo', + status: 'autosave', + } + ); + savedPost = () => ( + { + ...currentPost(), + ...editPostToSendOptimistic(), + content: editedPostContent, + status: savedPostStatus, + } + ); + } ); + const editedPostContent = 'to infinity and beyond'; + const reset = ( isAutosaving ) => fulfillment = actions.savePost( + { isAutosave: isAutosaving } + ); + const rewind = ( isAutosaving, isNewPost ) => { + reset( isAutosaving ); + fulfillment.next(); + fulfillment.next( true ); + fulfillment.next( edits() ); + fulfillment.next( isNewPost ); + fulfillment.next( currentPost() ); + fulfillment.next( editedPostContent ); + fulfillment.next( postTypeSlug ); + fulfillment.next( postType ); + fulfillment.next(); + if ( isAutosaving ) { + fulfillment.next(); + } else { + fulfillment.next(); + fulfillment.next(); + } + }; + const initialTestConditions = [ + [ + 'yields action for selecting if edited post is saveable', + () => true, + () => { + reset( isAutosave ); + const { value } = fulfillment.next(); + expect( value ).toEqual( + select( STORE_KEY, 'isEditedPostSaveable' ) + ); + }, + ], + [ + 'yields action for selecting the post edits done', + () => true, + () => { + const { value } = fulfillment.next( true ); + expect( value ).toEqual( + select( STORE_KEY, 'getPostEdits' ) + ); + }, + ], + [ + 'yields action for selecting whether the edited post is new', + () => true, + () => { + const { value } = fulfillment.next( edits() ); + expect( value ).toEqual( + select( STORE_KEY, 'isEditedPostNew' ) + ); + }, + ], + [ + 'yields action for selecting the current post', + () => true, + () => { + const { value } = fulfillment.next( isEditedPostNew ); + expect( value ).toEqual( + select( STORE_KEY, 'getCurrentPost' ) + ); + }, + ], + [ + 'yields action for selecting the edited post content', + () => true, + () => { + const { value } = fulfillment.next( currentPost() ); + expect( value ).toEqual( + select( STORE_KEY, 'getEditedPostContent' ) + ); + }, + ], + [ + 'yields action for selecting current post type slug', + () => true, + () => { + const { value } = fulfillment.next( editedPostContent ); + expect( value ).toEqual( + select( STORE_KEY, 'getCurrentPostType' ) + ); + }, + ], + [ + 'yields action for selecting the post type object', + () => true, + () => { + const { value } = fulfillment.next( postTypeSlug ); + expect( value ).toEqual( + resolveSelect( 'core', 'getPostType', postTypeSlug ) + ); + }, + ], + [ + 'yields action for dispatching request post update start', + () => true, + () => { + const { value } = fulfillment.next( postType ); + expect( value ).toEqual( + dispatch( + STORE_KEY, + '__experimentalRequestPostUpdateStart', + { isAutosave } + ) + ); + }, + ], + [ + 'yields action for dispatching optimistic update of post', + () => true, + () => { + const { value } = fulfillment.next(); + expect( value ).toEqual( + dispatch( + STORE_KEY, + '__experimentalOptimisticUpdatePost', + editPostToSendOptimistic() + ) + ); + }, + ], + [ + 'yields action for dispatching the removal of save post notice', + ( isAutosaving ) => ! isAutosaving, + () => { + const { value } = fulfillment.next(); + expect( value ).toEqual( + dispatch( + 'core/notices', + 'removeNotice', + SAVE_POST_NOTICE_ID, + ) + ); + }, + ], + [ + 'yields action for dispatching the removal of autosave notice', + ( isAutosaving ) => ! isAutosaving, + () => { + const { value } = fulfillment.next(); + expect( value ).toEqual( + dispatch( + 'core/notices', + 'removeNotice', + 'autosave-exists' + ) + ); + }, + ], + [ + 'yield action for selecting the autoSavePost', + ( isAutosaving ) => isAutosaving, + () => { + const { value } = fulfillment.next(); + expect( value ).toEqual( + select( + STORE_KEY, + 'getAutosave' + ) + ); + }, + ], + ]; + const fetchErrorConditions = [ + [ + 'yields action for dispatching post update failure', + () => { + const error = { foo: 'bar', code: 'fail' }; + apiFetchThrowError( error ); + const editsObject = edits(); + const { value } = isAutosave ? + fulfillment.next( autoSavePost ) : + fulfillment.next(); + if ( isAutosave ) { + delete editsObject.foo; + } + expect( value ).toEqual( + dispatch( + STORE_KEY, + '__experimentalRequestPostUpdateFailure', + { + post: currentPost(), + edits: isEditedPostNew ? + { ...editsObject, status: 'draft' } : + editsObject, + error, + options: { isAutosave }, + } + ) + ); + }, + ], + [ + 'yields action for dispatching an appropriate error notice', + () => { + const { value } = fulfillment.next( [ 'foo', 'bar' ] ); + expect( value ).toEqual( + dispatch( + 'core/notices', + 'createErrorNotice', + ...[ 'Updating failed', { id: 'SAVE_POST_NOTICE_ID' } ] + ) + ); + }, + ], + ]; + const fetchSuccessConditions = [ + [ + 'yields action for updating the post via the api', + () => { + apiFetchDoActual(); + rewind( isAutosave, isEditedPostNew ); + const { value } = isAutosave ? + fulfillment.next( autoSavePost ) : + fulfillment.next(); + const data = isAutosave ? + autoSavePostToSend() : + editPostToSendOptimistic(); + const path = isAutosave ? '/autosaves' : ''; + expect( value ).toEqual( + apiFetch( + { + path: `/wp/v2/${ postType.rest_base }/${ editPostToSendOptimistic().id }${ path }`, + method: isAutosave ? 'POST' : 'PUT', + data, + } + ) + ); + }, + ], + [ + 'yields action for dispatch the appropriate reset action', + () => { + const { value } = fulfillment.next( savedPost() ); + expect( value ).toEqual( + dispatch( + STORE_KEY, + isAutosave ? 'resetAutosave' : 'resetPost', + savedPost() + ) + ); + }, + ], + [ + 'yields action for dispatching the post update success', + () => { + const { value } = fulfillment.next(); + expect( value ).toEqual( + dispatch( + STORE_KEY, + '__experimentalRequestPostUpdateSuccess', + { + previousPost: currentPost(), + post: savedPost(), + options: { isAutosave }, + postType, + isRevision: false, + } + ) + ); + }, + ], + [ + 'yields dispatch action for success notification', + () => { + const { value } = fulfillment.next( [ 'foo', 'bar' ] ); + const expected = isAutosave ? + undefined : + dispatch( + 'core/notices', + 'createSuccessNotice', + ...[ + savedPostMessage, + { actions: [], id: 'SAVE_POST_NOTICE_ID' }, + ] + ); + expect( value ).toEqual( expected ); + }, + ], + ]; + + const conditionalRunTestRoutine = ( isAutosaving ) => ( [ + testDescription, + shouldRun, + testRoutine, + ] ) => { + if ( shouldRun( isAutosaving ) ) { + it( testDescription, () => { + testRoutine(); + } ); + } + }; + + const testRunRoutine = ( [ testDescription, testRoutine ] ) => { + it( testDescription, () => { + testRoutine(); + } ); + }; + + describe( 'yields with expected responses when edited post is not ' + + 'saveable', () => { + it( 'yields action for selecting if edited post is saveable', () => { + reset( false ); + const { value } = fulfillment.next(); + expect( value ).toEqual( + select( STORE_KEY, 'isEditedPostSaveable' ) + ); + } ); + it( 'if edited post is not saveable then bails', () => { + const { value, done } = fulfillment.next( false ); + expect( done ).toBe( true ); + expect( value ).toBeUndefined(); + } ); + } ); + describe( 'yields with expected responses for when not autosaving ' + + 'and edited post is new', () => { + beforeEach( () => { + isAutosave = false; + isEditedPostNew = true; + savedPostStatus = 'publish'; + currentPostStatus = 'draft'; + savedPostMessage = 'Post published'; + } ); + initialTestConditions.forEach( conditionalRunTestRoutine( false ) ); + describe( 'fetch action throwing an error', () => { + fetchErrorConditions.forEach( testRunRoutine ); + } ); + describe( 'fetch action not throwing an error', () => { + fetchSuccessConditions.forEach( testRunRoutine ); + } ); + } ); + + describe( 'yields with expected responses for when not autosaving ' + + 'and edited post is not new', () => { + beforeEach( () => { + isAutosave = false; + isEditedPostNew = false; + currentPostStatus = 'publish'; + savedPostStatus = 'publish'; + savedPostMessage = 'Updated Post'; + } ); + initialTestConditions.forEach( conditionalRunTestRoutine( false ) ); + describe( 'fetch action throwing error', () => { + fetchErrorConditions.forEach( testRunRoutine ); + } ); + describe( 'fetch action not throwing error', () => { + fetchSuccessConditions.forEach( testRunRoutine ); + } ); + } ); + describe( 'yields with expected responses for when autosaving is true ' + + 'and edited post is not new', () => { + beforeEach( () => { + isAutosave = true; + isEditedPostNew = false; + currentPostStatus = 'autosave'; + savedPostStatus = 'publish'; + savedPostMessage = 'Post published'; + } ); + initialTestConditions.forEach( conditionalRunTestRoutine( true ) ); + describe( 'fetch action throwing error', () => { + fetchErrorConditions.forEach( testRunRoutine ); + } ); + describe( 'fetch action not throwing error', () => { + fetchSuccessConditions.forEach( testRunRoutine ); + } ); + } ); + } ); + describe( 'autosave()', () => { + it( 'dispatches savePost with the correct arguments', () => { + const fulfillment = actions.autosave(); + const { value } = fulfillment.next(); + expect( value.actionName ).toBe( 'savePost' ); + expect( value.args ).toEqual( [ { isAutosave: true } ] ); + } ); + } ); + describe( 'trashPost()', () => { + let fulfillment; + const currentPost = { id: 10, content: 'foo', status: 'publish' }; + const reset = () => fulfillment = actions.trashPost(); + const rewind = () => { + reset(); + fulfillment.next(); + fulfillment.next( postTypeSlug ); + fulfillment.next( postType ); + fulfillment.next(); + }; + it( 'yields expected action for selecting the current post type slug', + () => { + reset(); + const { value } = fulfillment.next(); + expect( value ).toEqual( select( + STORE_KEY, + 'getCurrentPostType', + ) ); + } + ); + it( 'yields expected action for selecting the post type object', () => { + const { value } = fulfillment.next( postTypeSlug ); + expect( value ).toEqual( resolveSelect( + 'core', + 'getPostType', + postTypeSlug + ) ); + } ); + it( 'yields expected action for dispatching removing the trash notice ' + + 'for the post', () => { + const { value } = fulfillment.next( postType ); + expect( value ).toEqual( dispatch( + 'core/notices', + 'removeNotice', + TRASH_POST_NOTICE_ID + ) ); + } ); + it( 'yields expected action for selecting the currentPost', () => { + const { value } = fulfillment.next(); + expect( value ).toEqual( select( + STORE_KEY, + 'getCurrentPost' + ) ); + } ); + describe( 'expected yields when fetch throws an error', () => { + it( 'yields expected action for dispatching an error notice', () => { + const error = { foo: 'bar', code: 'fail' }; + apiFetchThrowError( error ); + const { value } = fulfillment.next( currentPost ); + expect( value ).toEqual( dispatch( + 'core/notices', + 'createErrorNotice', + 'Trashing failed', + { id: TRASH_POST_NOTICE_ID }, + ) ); + } ); + } ); + describe( 'expected yields when fetch does not throw an error', () => { + it( 'yields expected action object for the api fetch', () => { + apiFetchDoActual(); + rewind(); + const { value } = fulfillment.next( currentPost ); + expect( value ).toEqual( apiFetch( + { + path: `/wp/v2/${ postType.rest_base }/${ currentPost.id }`, + method: 'DELETE', + } + ) ); + } ); + it( 'yields expected dispatch action for resetting the post', () => { + const { value } = fulfillment.next(); + expect( value ).toEqual( dispatch( + STORE_KEY, + 'resetPost', + { ...currentPost, status: 'trash' } + ) ); + } ); + } ); + } ); + describe( 'refreshPost()', () => { + let fulfillment; + const currentPost = { id: 10, content: 'foo' }; + const reset = () => fulfillment = actions.refreshPost(); + it( 'yields expected action for selecting the currentPost', () => { + reset(); + const { value } = fulfillment.next(); + expect( value ).toEqual( select( + STORE_KEY, + 'getCurrentPost', + ) ); + } ); + it( 'yields expected action for selecting the current post type', () => { + const { value } = fulfillment.next( currentPost ); + expect( value ).toEqual( select( + STORE_KEY, + 'getCurrentPostType' + ) ); + } ); + it( 'yields expected action for selecting the post type object', () => { + const { value } = fulfillment.next( postTypeSlug ); + expect( value ).toEqual( resolveSelect( + 'core', + 'getPostType', + postTypeSlug + ) ); + } ); + it( 'yields expected action for the api fetch call', () => { + const { value } = fulfillment.next( postType ); + apiFetchDoActual(); + // since the timestamp is a computed value we can't do a direct comparison. + // so we'll just see if the path has most of the value. + expect( value.request.path ).toEqual( expect.stringContaining( + `/wp/v2/${ postType.rest_base }/${ currentPost.id }?context=edit&_timestamp=` + ) ); + } ); + it( 'yields expected action for dispatching the reset of the post', () => { + const { value } = fulfillment.next( currentPost ); + expect( value ).toEqual( dispatch( + STORE_KEY, + 'resetPost', + currentPost + ) ); + } ); + } ); +} ); describe( 'actions', () => { describe( 'setupEditor', () => { it( 'should return the SETUP_EDITOR action', () => { const post = {}; - const result = setupEditor( post ); + const result = actions.setupEditor( post ); expect( result ).toEqual( { type: 'SETUP_EDITOR', post, @@ -31,7 +640,7 @@ describe( 'actions', () => { describe( 'resetPost', () => { it( 'should return the RESET_POST action', () => { const post = {}; - const result = resetPost( post ); + const result = actions.resetPost( post ); expect( result ).toEqual( { type: 'RESET_POST', post, @@ -39,47 +648,103 @@ describe( 'actions', () => { } ); } ); - describe( 'editPost', () => { - it( 'should return EDIT_POST action', () => { - const edits = { format: 'sample' }; - expect( editPost( edits ) ).toEqual( { - type: 'EDIT_POST', - edits, + describe( 'resetAutosave', () => { + it( 'should return the RESET_AUTOSAVE action', () => { + const post = {}; + const result = actions.resetAutosave( post ); + expect( result ).toEqual( { + type: 'RESET_AUTOSAVE', + post, } ); } ); } ); - describe( 'savePost', () => { - it( 'should return REQUEST_POST_UPDATE action', () => { - expect( savePost() ).toEqual( { - type: 'REQUEST_POST_UPDATE', + describe( 'requestPostUpdateStart', () => { + it( 'should return the REQUEST_POST_UPDATE_START action', () => { + const result = actions.__experimentalRequestPostUpdateStart(); + expect( result ).toEqual( { + type: 'REQUEST_POST_UPDATE_START', + optimist: { type: BEGIN, id: POST_UPDATE_TRANSACTION_ID }, options: {}, } ); } ); + } ); - it( 'should pass through options argument', () => { - expect( savePost( { autosave: true } ) ).toEqual( { - type: 'REQUEST_POST_UPDATE', - options: { autosave: true }, + describe( 'requestPostUpdateSuccess', () => { + it( 'should return the REQUEST_POST_UPDATE_SUCCESS action', () => { + const testActionData = { + previousPost: {}, + post: {}, + options: {}, + postType: 'post', + }; + const result = actions.__experimentalRequestPostUpdateSuccess( { + ...testActionData, + isRevision: false, + } ); + expect( result ).toEqual( { + ...testActionData, + type: 'REQUEST_POST_UPDATE_SUCCESS', + optimist: { type: COMMIT, id: POST_UPDATE_TRANSACTION_ID }, } ); } ); } ); - describe( 'trashPost', () => { - it( 'should return TRASH_POST action', () => { - const postId = 1; - const postType = 'post'; - expect( trashPost( postId, postType ) ).toEqual( { - type: 'TRASH_POST', - postId, - postType, + describe( 'requestPostUpdateFailure', () => { + it( 'should return the REQUEST_POST_UPDATE_FAILURE action', () => { + const testActionData = { + post: {}, + options: {}, + edits: {}, + error: {}, + }; + const result = actions.__experimentalRequestPostUpdateFailure( + testActionData + ); + expect( result ).toEqual( { + ...testActionData, + type: 'REQUEST_POST_UPDATE_FAILURE', + optimist: { type: REVERT, id: POST_UPDATE_TRANSACTION_ID }, + } ); + } ); + } ); + + describe( 'updatePost', () => { + it( 'should return the UPDATE_POST action', () => { + const edits = {}; + const result = actions.updatePost( edits ); + expect( result ).toEqual( { + type: 'UPDATE_POST', + edits, + } ); + } ); + } ); + + describe( 'editPost', () => { + it( 'should return EDIT_POST action', () => { + const edits = { format: 'sample' }; + expect( actions.editPost( edits ) ).toEqual( { + type: 'EDIT_POST', + edits, + } ); + } ); + } ); + + describe( 'optimisticUpdatePost', () => { + it( 'should return the UPDATE_POST action with optimist property', () => { + const edits = {}; + const result = actions.__experimentalOptimisticUpdatePost( edits ); + expect( result ).toEqual( { + type: 'UPDATE_POST', + edits, + optimist: { id: POST_UPDATE_TRANSACTION_ID }, } ); } ); } ); describe( 'redo', () => { it( 'should return REDO action', () => { - expect( redo() ).toEqual( { + expect( actions.redo() ).toEqual( { type: 'REDO', } ); } ); @@ -87,7 +752,7 @@ describe( 'actions', () => { describe( 'undo', () => { it( 'should return UNDO action', () => { - expect( undo() ).toEqual( { + expect( actions.undo() ).toEqual( { type: 'UNDO', } ); } ); @@ -95,13 +760,13 @@ describe( 'actions', () => { describe( 'fetchReusableBlocks', () => { it( 'should return the FETCH_REUSABLE_BLOCKS action', () => { - expect( fetchReusableBlocks() ).toEqual( { + expect( actions.__experimentalFetchReusableBlocks() ).toEqual( { type: 'FETCH_REUSABLE_BLOCKS', } ); } ); it( 'should take an optional id argument', () => { - expect( fetchReusableBlocks( 123 ) ).toEqual( { + expect( actions.__experimentalFetchReusableBlocks( 123 ) ).toEqual( { type: 'FETCH_REUSABLE_BLOCKS', id: 123, } ); @@ -110,7 +775,7 @@ describe( 'actions', () => { describe( 'saveReusableBlock', () => { it( 'should return the SAVE_REUSABLE_BLOCK action', () => { - expect( saveReusableBlock( 123 ) ).toEqual( { + expect( actions.__experimentalSaveReusableBlock( 123 ) ).toEqual( { type: 'SAVE_REUSABLE_BLOCK', id: 123, } ); @@ -119,7 +784,7 @@ describe( 'actions', () => { describe( 'deleteReusableBlock', () => { it( 'should return the DELETE_REUSABLE_BLOCK action', () => { - expect( deleteReusableBlock( 123 ) ).toEqual( { + expect( actions.__experimentalDeleteReusableBlock( 123 ) ).toEqual( { type: 'DELETE_REUSABLE_BLOCK', id: 123, } ); @@ -129,7 +794,7 @@ describe( 'actions', () => { describe( 'convertBlockToStatic', () => { it( 'should return the CONVERT_BLOCK_TO_STATIC action', () => { const clientId = '358b59ee-bab3-4d6f-8445-e8c6971a5605'; - expect( convertBlockToStatic( clientId ) ).toEqual( { + expect( actions.__experimentalConvertBlockToStatic( clientId ) ).toEqual( { type: 'CONVERT_BLOCK_TO_STATIC', clientId, } ); @@ -139,10 +804,30 @@ describe( 'actions', () => { describe( 'convertBlockToReusable', () => { it( 'should return the CONVERT_BLOCK_TO_REUSABLE action', () => { const clientId = '358b59ee-bab3-4d6f-8445-e8c6971a5605'; - expect( convertBlockToReusable( clientId ) ).toEqual( { + expect( actions.__experimentalConvertBlockToReusable( clientId ) ).toEqual( { type: 'CONVERT_BLOCK_TO_REUSABLE', clientIds: [ clientId ], } ); } ); } ); + + describe( 'lockPostSaving', () => { + it( 'should return the LOCK_POST_SAVING action', () => { + const result = actions.lockPostSaving( 'test' ); + expect( result ).toEqual( { + type: 'LOCK_POST_SAVING', + lockName: 'test', + } ); + } ); + } ); + + describe( 'unlockPostSaving', () => { + it( 'should return the UNLOCK_POST_SAVING action', () => { + const result = actions.unlockPostSaving( 'test' ); + expect( result ).toEqual( { + type: 'UNLOCK_POST_SAVING', + lockName: 'test', + } ); + } ); + } ); } ); diff --git a/packages/editor/src/store/test/effects.js b/packages/editor/src/store/test/effects.js index 2e9170d2175a0..cdc7223858e0f 100644 --- a/packages/editor/src/store/test/effects.js +++ b/packages/editor/src/store/test/effects.js @@ -6,214 +6,17 @@ import { unregisterBlockType, registerBlockType, } from '@wordpress/blocks'; -import { dispatch as dataDispatch } from '@wordpress/data'; /** * Internal dependencies */ import { setupEditorState, resetEditorBlocks } from '../actions'; import effects from '../effects'; -import { SAVE_POST_NOTICE_ID } from '../effects/posts'; import '../../'; describe( 'effects', () => { - beforeAll( () => { - jest.spyOn( dataDispatch( 'core/notices' ), 'createErrorNotice' ); - jest.spyOn( dataDispatch( 'core/notices' ), 'createSuccessNotice' ); - } ); - - beforeEach( () => { - dataDispatch( 'core/notices' ).createErrorNotice.mockReset(); - dataDispatch( 'core/notices' ).createSuccessNotice.mockReset(); - } ); - const defaultBlockSettings = { save: () => 'Saved', category: 'common', title: 'block title' }; - describe( '.REQUEST_POST_UPDATE_SUCCESS', () => { - const handler = effects.REQUEST_POST_UPDATE_SUCCESS; - - const defaultPost = { - id: 1, - title: { - raw: 'A History of Pork', - }, - content: { - raw: '', - }, - }; - const getDraftPost = () => ( { - ...defaultPost, - status: 'draft', - } ); - const getPublishedPost = () => ( { - ...defaultPost, - status: 'publish', - } ); - const getPostType = () => ( { - labels: { - view_item: 'View post', - item_published: 'Post published.', - item_reverted_to_draft: 'Post reverted to draft.', - item_updated: 'Post updated.', - }, - viewable: true, - } ); - - it( 'should dispatch notices when publishing or scheduling a post', () => { - const previousPost = getDraftPost(); - const post = getPublishedPost(); - const postType = getPostType(); - - handler( { post, previousPost, postType } ); - - expect( dataDispatch( 'core/notices' ).createSuccessNotice ).toHaveBeenCalledWith( - 'Post published.', - { - id: SAVE_POST_NOTICE_ID, - actions: [ - { label: 'View post', url: undefined }, - ], - } - ); - } ); - - it( 'should dispatch notices when publishing or scheduling an unviewable post', () => { - const previousPost = getDraftPost(); - const post = getPublishedPost(); - const postType = { ...getPostType(), viewable: false }; - - handler( { post, previousPost, postType } ); - - expect( dataDispatch( 'core/notices' ).createSuccessNotice ).toHaveBeenCalledWith( - 'Post published.', - { - id: SAVE_POST_NOTICE_ID, - actions: [], - } - ); - } ); - - it( 'should dispatch notices when reverting a published post to a draft', () => { - const previousPost = getPublishedPost(); - const post = getDraftPost(); - const postType = getPostType(); - - handler( { post, previousPost, postType } ); - - expect( dataDispatch( 'core/notices' ).createSuccessNotice ).toHaveBeenCalledWith( - 'Post reverted to draft.', - { - id: SAVE_POST_NOTICE_ID, - actions: [], - } - ); - } ); - - it( 'should dispatch notices when just updating a published post again', () => { - const previousPost = getPublishedPost(); - const post = getPublishedPost(); - const postType = getPostType(); - - handler( { post, previousPost, postType } ); - - expect( dataDispatch( 'core/notices' ).createSuccessNotice ).toHaveBeenCalledWith( - 'Post updated.', - { - id: SAVE_POST_NOTICE_ID, - actions: [ - { label: 'View post', url: undefined }, - ], - } - ); - } ); - - it( 'should do nothing if the updated post was autosaved', () => { - const previousPost = getPublishedPost(); - const post = { ...getPublishedPost(), id: defaultPost.id + 1 }; - - handler( { post, previousPost, options: { isAutosave: true } } ); - - expect( dataDispatch( 'core/notices' ).createSuccessNotice ).not.toHaveBeenCalled(); - } ); - } ); - - describe( '.REQUEST_POST_UPDATE_FAILURE', () => { - it( 'should dispatch a notice on failure when publishing a draft fails.', () => { - const handler = effects.REQUEST_POST_UPDATE_FAILURE; - - const action = { - post: { - id: 1, - title: { - raw: 'A History of Pork', - }, - content: { - raw: '', - }, - status: 'draft', - }, - edits: { - status: 'publish', - }, - }; - - handler( action ); - - expect( dataDispatch( 'core/notices' ).createErrorNotice ).toHaveBeenCalledWith( 'Publishing failed', { id: SAVE_POST_NOTICE_ID } ); - } ); - - it( 'should not dispatch a notice when there were no changes for autosave to save.', () => { - const handler = effects.REQUEST_POST_UPDATE_FAILURE; - - const action = { - post: { - id: 1, - title: { - raw: 'A History of Pork', - }, - content: { - raw: '', - }, - status: 'draft', - }, - edits: { - status: 'publish', - }, - error: { - code: 'rest_autosave_no_changes', - }, - }; - - handler( action ); - - expect( dataDispatch( 'core/notices' ).createErrorNotice ).not.toHaveBeenCalled(); - } ); - - it( 'should dispatch a notice on failure when trying to update a draft.', () => { - const handler = effects.REQUEST_POST_UPDATE_FAILURE; - - const action = { - post: { - id: 1, - title: { - raw: 'A History of Pork', - }, - content: { - raw: '', - }, - status: 'draft', - }, - edits: { - status: 'draft', - }, - }; - - handler( action ); - - expect( dataDispatch( 'core/notices' ).createErrorNotice ).toHaveBeenCalledWith( 'Updating failed', { id: SAVE_POST_NOTICE_ID } ); - } ); - } ); - describe( '.SETUP_EDITOR', () => { const handler = effects.SETUP_EDITOR; diff --git a/packages/editor/src/store/test/selectors.js b/packages/editor/src/store/test/selectors.js index 459161ee03801..01f2d09199e52 100644 --- a/packages/editor/src/store/test/selectors.js +++ b/packages/editor/src/store/test/selectors.js @@ -22,6 +22,7 @@ import { RawHTML } from '@wordpress/element'; */ import * as selectors from '../selectors'; import { PREFERENCES_DEFAULTS } from '../defaults'; +import { POST_UPDATE_TRANSACTION_ID } from '../constants'; const { hasEditorUndo, @@ -64,7 +65,6 @@ const { getStateBeforeOptimisticTransaction, isPublishingPost, isPublishSidebarEnabled, - POST_UPDATE_TRANSACTION_ID, isPermalinkEditable, getPermalink, getPermalinkParts, diff --git a/packages/editor/src/store/utils/notice-builder.js b/packages/editor/src/store/utils/notice-builder.js new file mode 100644 index 0000000000000..4ef98c74e3a54 --- /dev/null +++ b/packages/editor/src/store/utils/notice-builder.js @@ -0,0 +1,123 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { SAVE_POST_NOTICE_ID, TRASH_POST_NOTICE_ID } from '../constants'; + +/** + * External dependencies + */ +import { get, includes } from 'lodash'; + +/** + * Builds the arguments for a success notification dispatch. + * + * @param {Object} data Incoming data to build the arguments from. + * + * @return {Array} Arguments for dispatch. An empty array signals no + * notification should be sent. + */ +export function getNotificationArgumentsForSaveSuccess( data ) { + const { previousPost, post, postType } = data; + // Autosaves are neither shown a notice nor redirected. + if ( get( data.options, [ 'isAutosave' ] ) ) { + return []; + } + + const publishStatus = [ 'publish', 'private', 'future' ]; + const isPublished = includes( publishStatus, previousPost.status ); + const willPublish = includes( publishStatus, post.status ); + + let noticeMessage; + let shouldShowLink = get( postType, [ 'viewable' ], false ); + + if ( ! isPublished && ! willPublish ) { + // If saving a non-published post, don't show notice. + noticeMessage = null; + } else if ( isPublished && ! willPublish ) { + // If undoing publish status, show specific notice + noticeMessage = postType.labels.item_reverted_to_draft; + shouldShowLink = false; + } else if ( ! isPublished && willPublish ) { + // If publishing or scheduling a post, show the corresponding + // publish message + noticeMessage = { + publish: postType.labels.item_published, + private: postType.labels.item_published_privately, + future: postType.labels.item_scheduled, + }[ post.status ]; + } else { + // Generic fallback notice + noticeMessage = postType.labels.item_updated; + } + + if ( noticeMessage ) { + const actions = []; + if ( shouldShowLink ) { + actions.push( { + label: postType.labels.view_item, + url: post.link, + } ); + } + return [ + noticeMessage, + { + id: SAVE_POST_NOTICE_ID, + actions, + }, + ]; + } + return []; +} + +/** + * Builds the fail notification arguments for dispatch. + * + * @param {Object} data Incoming data to build the arguments with. + * + * @return {Array} Arguments for dispatch. An empty array signals no + * notification should be sent. + */ +export function getNotificationArgumentsForSaveFail( data ) { + const { post, edits, error } = data; + if ( error && 'rest_autosave_no_changes' === error.code ) { + // Autosave requested a new autosave, but there were no changes. This shouldn't + // result in an error notice for the user. + return []; + } + + const publishStatus = [ 'publish', 'private', 'future' ]; + const isPublished = publishStatus.indexOf( post.status ) !== -1; + // If the post was being published, we show the corresponding publish error message + // Unless we publish an "updating failed" message + const messages = { + publish: __( 'Publishing failed' ), + private: __( 'Publishing failed' ), + future: __( 'Scheduling failed' ), + }; + const noticeMessage = ! isPublished && publishStatus.indexOf( edits.status ) !== -1 ? + messages[ edits.status ] : + __( 'Updating failed' ); + + return [ noticeMessage, { id: SAVE_POST_NOTICE_ID } ]; +} + +/** + * Builds the trash fail notification arguments for dispatch. + * + * @param {Object} data + * + * @return {Array} Arguments for dispatch. + */ +export function getNotificationArgumentsForTrashFail( data ) { + return [ + data.error.message && data.error.code !== 'unknown_error' ? + data.error.message : + __( 'Trashing failed' ), + { id: TRASH_POST_NOTICE_ID }, + ]; +} diff --git a/packages/editor/src/store/utils/test/notice-builder.js b/packages/editor/src/store/utils/test/notice-builder.js new file mode 100644 index 0000000000000..a78d03f81fad7 --- /dev/null +++ b/packages/editor/src/store/utils/test/notice-builder.js @@ -0,0 +1,182 @@ +/** + * Internal dependencies + */ +import { + getNotificationArgumentsForSaveSuccess, + getNotificationArgumentsForSaveFail, + getNotificationArgumentsForTrashFail, +} from '../notice-builder'; +import { + SAVE_POST_NOTICE_ID, + TRASH_POST_NOTICE_ID, +} from '../../constants'; + +describe( 'getNotificationArgumentsForSaveSuccess()', () => { + const postType = { + labels: { + item_reverted_to_draft: 'draft', + item_published: 'publish', + item_published_privately: 'private', + item_scheduled: 'scheduled', + item_updated: 'updated', + view_item: 'view', + }, + viewable: false, + }; + const previousPost = { + status: 'publish', + link: 'some_link', + }; + const post = { ...previousPost }; + const defaultExpectedAction = { id: SAVE_POST_NOTICE_ID, actions: [] }; + [ + [ + 'when previous post is not published and post will not be published', + [ 'draft', 'draft', false ], + [], + ], + [ + 'when previous post is published and post will be unpublished', + [ 'publish', 'draft', false ], + [ 'draft', defaultExpectedAction ], + ], + [ + 'when previous post is not published and post will be published', + [ 'draft', 'publish', false ], + [ 'publish', defaultExpectedAction ], + ], + [ + 'when previous post is not published and post will be privately ' + + 'published', + [ 'draft', 'private', false ], + [ 'private', defaultExpectedAction ], + ], + [ + 'when previous post is not published and post will be scheduled for ' + + 'publishing', + [ 'draft', 'future', false ], + [ 'scheduled', defaultExpectedAction ], + ], + [ + 'when both are considered published', + [ 'private', 'publish', false ], + [ 'updated', defaultExpectedAction ], + ], + [ + 'when both are considered published and the post type is viewable', + [ 'private', 'publish', true ], + [ + 'updated', + { + ...defaultExpectedAction, + actions: [ { label: 'view', url: 'some_link' } ], + }, + ], + ], + ].forEach( ( [ + description, + [ previousPostStatus, postStatus, isViewable ], + expectedValue, + ] ) => { + it( description, () => { + previousPost.status = previousPostStatus; + post.status = postStatus; + postType.viewable = isViewable; + expect( getNotificationArgumentsForSaveSuccess( + { + previousPost, + post, + postType, + } + ) ).toEqual( expectedValue ); + } ); + } ); +} ); +describe( 'getNotificationArgumentsForSaveFail()', () => { + const error = { code: '42' }; + const post = { status: 'publish' }; + const edits = { status: 'publish' }; + const defaultExpectedAction = { id: SAVE_POST_NOTICE_ID }; + [ + [ + 'when error code is `rest_autosave_no_changes`', + 'rest_autosave_no_changes', + [ 'publish', 'publish' ], + [], + ], + [ + 'when post is not published and edits is published', + '', + [ 'draft', 'publish' ], + [ 'Publishing failed', defaultExpectedAction ], + ], + [ + 'when post is published and edits is privately published', + '', + [ 'draft', 'private' ], + [ 'Publishing failed', defaultExpectedAction ], + ], + [ + 'when post is published and edits is scheduled to be published', + '', + [ 'draft', 'future' ], + [ 'Scheduling failed', defaultExpectedAction ], + ], + [ + 'when post is published and edits is published', + '', + [ 'publish', 'publish' ], + [ 'Updating failed', defaultExpectedAction ], + ], + ].forEach( ( [ + description, + errorCode, + [ postStatus, editsStatus ], + expectedValue, + ] ) => { + it( description, () => { + post.status = postStatus; + error.code = errorCode; + edits.status = editsStatus; + expect( getNotificationArgumentsForSaveFail( + { + post, + edits, + error, + } + ) ).toEqual( expectedValue ); + } ); + } ); +} ); +describe( 'getNotificationArgumentsForTrashFail()', () => { + [ + [ + 'when there is an error message and the error code is not "unknown_error"', + { message: 'foo', code: '' }, + 'foo', + ], + [ + 'when there is an error message and the error code is "unknown error"', + { message: 'foo', code: 'unknown_error' }, + 'Trashing failed', + ], + [ + 'when there is not an error message', + { code: 42 }, + 'Trashing failed', + ], + ].forEach( ( [ + description, + error, + message, + ] ) => { + it( description, () => { + const expectedValue = [ + message, + { id: TRASH_POST_NOTICE_ID }, + ]; + expect( getNotificationArgumentsForTrashFail( { error } ) ) + .toEqual( expectedValue ); + } ); + } ); +} ); From 29bff8571b1338aa8c909859f026d7bd9c50739e Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Thu, 28 Feb 2019 20:44:41 +0000 Subject: [PATCH 533/691] Add array-callback-return rule; Fix current code breaking the rule. (#14154) ## Description Adds a rule to make sure Array functions that iterate on the array and should return something contain the return statement, otherwise, a forEach should probably be used instead. A case like this was fixed at https://github.com/WordPress/gutenberg/pull/13953. In PR https://github.com/WordPress/gutenberg/pull/13953 @aduth suggested the implementation of a lint rule to catch these cases. While trying to implement the rule and researching the best ways to do it, I noticed a rule like that already existed in the community and this PR is enabling it. We are also changing the code to respect the new rule no observable changes should be expected. ## How has this been tested? Observe the tests pass. Do some smoke testing, adding blocks, uploading files, and verify everything still works as before. --- docs/tool/manifest.js | 2 +- packages/block-library/src/file/index.js | 2 +- packages/edit-post/src/hooks/components/media-upload/index.js | 2 +- packages/eslint-plugin/CHANGELOG.md | 1 + packages/eslint-plugin/configs/es5.js | 1 + packages/scripts/scripts/check-licenses.js | 1 + 6 files changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/tool/manifest.js b/docs/tool/manifest.js index 56f7a2c8772c4..f21130e448e11 100644 --- a/docs/tool/manifest.js +++ b/docs/tool/manifest.js @@ -70,7 +70,7 @@ function getRootManifest( tocFileName ) { function generateRootManifestFromTOCItems( items, parent = null ) { let pageItems = []; - items.map( ( obj ) => { + items.forEach( ( obj ) => { const fileName = Object.keys( obj )[ 0 ]; const children = obj[ fileName ]; diff --git a/packages/block-library/src/file/index.js b/packages/block-library/src/file/index.js index 1c7087039a5cc..9ce871bcfa277 100644 --- a/packages/block-library/src/file/index.js +++ b/packages/block-library/src/file/index.js @@ -86,7 +86,7 @@ export const settings = { transform: ( files ) => { const blocks = []; - files.map( ( file ) => { + files.forEach( ( file ) => { const blobURL = createBlobURL( file ); // File will be uploaded in componentDidMount() diff --git a/packages/edit-post/src/hooks/components/media-upload/index.js b/packages/edit-post/src/hooks/components/media-upload/index.js index 0b4fe40ce4ea9..bcf4d49d700c5 100644 --- a/packages/edit-post/src/hooks/components/media-upload/index.js +++ b/packages/edit-post/src/hooks/components/media-upload/index.js @@ -172,7 +172,7 @@ class MediaUpload extends Component { } if ( ! this.props.gallery ) { const selection = this.frame.state().get( 'selection' ); - castArray( this.props.value ).map( ( id ) => { + castArray( this.props.value ).forEach( ( id ) => { selection.add( wp.media.attachment( id ) ); } ); } diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 4e3f6f8430996..0d563005b9613 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -3,6 +3,7 @@ ### Breaking Changes - The `esnext` and `recommended` rulesets now enforce [`object-shorthand`](https://eslint.org/docs/rules/object-shorthand) +- The `es5` and `recommended` rulesets now enforce [`array-callback-return`](https://eslint.org/docs/rules/array-callback-return) ### New Features diff --git a/packages/eslint-plugin/configs/es5.js b/packages/eslint-plugin/configs/es5.js index 167e542ef69a6..a13168d1223e0 100644 --- a/packages/eslint-plugin/configs/es5.js +++ b/packages/eslint-plugin/configs/es5.js @@ -1,6 +1,7 @@ module.exports = { rules: { 'array-bracket-spacing': [ 'error', 'always' ], + 'array-callback-return': 'error', 'brace-style': [ 'error', '1tbs' ], camelcase: [ 'error', { properties: 'never', diff --git a/packages/scripts/scripts/check-licenses.js b/packages/scripts/scripts/check-licenses.js index 2120d0cbfe75e..2bc95d51e0913 100644 --- a/packages/scripts/scripts/check-licenses.js +++ b/packages/scripts/scripts/check-licenses.js @@ -258,6 +258,7 @@ modules.forEach( ( path ) => { }, stringDetectedType ); }, detectedType ); } + return detectedType; }, false ); } From 18d6dee1861e5edf5e6f61d7a1098e4fe48b06f5 Mon Sep 17 00:00:00 2001 From: Brent Swisher <brent@brentswisher.com> Date: Thu, 28 Feb 2019 16:01:16 -0500 Subject: [PATCH 534/691] Update text displayed when an embed can't be previewed (#13715) * Update text displayed when an embed can't be previewed * Add translator note for update to embedded content np preview message --- packages/block-library/src/embed/embed-preview.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/embed/embed-preview.js b/packages/block-library/src/embed/embed-preview.js index cf8d35460ae3d..1b1f2dfb93787 100644 --- a/packages/block-library/src/embed/embed-preview.js +++ b/packages/block-library/src/embed/embed-preview.js @@ -97,7 +97,12 @@ class EmbedPreview extends Component { { ( cannotPreview ) ? ( <Placeholder icon={ <BlockIcon icon={ icon } showColors /> } label={ label }> <p className="components-placeholder__error"><a href={ url }>{ url }</a></p> - <p className="components-placeholder__error">{ __( 'Sorry, this embedded content cannot be previewed in the editor.' ) }</p> + <p className="components-placeholder__error"> + { + /* translators: %s: host providing embed content e.g: www.youtube.com */ + sprintf( __( "Embedded content from %s can't be previewed in the editor." ), parsedHostBaseUrl ) + } + </p> </Placeholder> ) : embedWrapper } { ( ! RichText.isEmpty( caption ) || isSelected ) && ( From 77a945c86ccc209dccd14eea617ccd3bcaed70ae Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Thu, 28 Feb 2019 21:19:26 +0000 Subject: [PATCH 535/691] Fix: Image that is uploaded to an existing gallery does not appear in the edit gallery view (#12435) --- .../hooks/components/media-upload/index.js | 68 ++++++++++++++----- 1 file changed, 52 insertions(+), 16 deletions(-) diff --git a/packages/edit-post/src/hooks/components/media-upload/index.js b/packages/edit-post/src/hooks/components/media-upload/index.js index bcf4d49d700c5..df65411e1c305 100644 --- a/packages/edit-post/src/hooks/components/media-upload/index.js +++ b/packages/edit-post/src/hooks/components/media-upload/index.js @@ -82,7 +82,6 @@ class MediaUpload extends Component { gallery = false, title = __( 'Select or Upload Media' ), modalClass, - value, } ) { super( ...arguments ); this.openModal = this.openModal.bind( this ); @@ -92,21 +91,7 @@ class MediaUpload extends Component { this.onClose = this.onClose.bind( this ); if ( gallery ) { - const currentState = value ? 'gallery-edit' : 'gallery'; - const GalleryDetailsMediaFrame = getGalleryDetailsMediaFrame(); - const attachments = getAttachmentsCollection( value ); - const selection = new wp.media.model.Selection( attachments.models, { - props: attachments.props.toJSON(), - multiple, - } ); - this.frame = new GalleryDetailsMediaFrame( { - mimeType: allowedTypes, - state: currentState, - multiple, - selection, - editing: ( value ) ? true : false, - } ); - wp.media.frame = this.frame; + this.buildAndSetGalleryFrame(); } else { const frameConfig = { title, @@ -126,6 +111,10 @@ class MediaUpload extends Component { this.frame.$el.addClass( modalClass ); } + this.initializeListeners(); + } + + initializeListeners() { // When an image is selected in the media frame... this.frame.on( 'select', this.onSelect ); this.frame.on( 'update', this.onUpdate ); @@ -133,6 +122,45 @@ class MediaUpload extends Component { this.frame.on( 'close', this.onClose ); } + buildAndSetGalleryFrame() { + const { + allowedTypes, + multiple = false, + value = null, + } = this.props; + // If the value did not changed there is no need to rebuild the frame, + // we can continue to use the existing one. + if ( value === this.lastGalleryValue ) { + return; + } + + this.lastGalleryValue = value; + + // If a frame already existed remove it. + if ( this.frame ) { + this.frame.remove(); + } + const currentState = value ? 'gallery-edit' : 'gallery'; + if ( ! this.GalleryDetailsMediaFrame ) { + this.GalleryDetailsMediaFrame = getGalleryDetailsMediaFrame(); + } + + const attachments = getAttachmentsCollection( value ); + const selection = new wp.media.model.Selection( attachments.models, { + props: attachments.props.toJSON(), + multiple, + } ); + this.frame = new this.GalleryDetailsMediaFrame( { + mimeType: allowedTypes, + state: currentState, + multiple, + selection, + editing: ( value ) ? true : false, + } ); + wp.media.frame = this.frame; + this.initializeListeners(); + } + componentWillUnmount() { this.frame.remove(); } @@ -176,6 +204,7 @@ class MediaUpload extends Component { selection.add( wp.media.attachment( id ) ); } ); } + // load the images so they are available in the media modal. getAttachmentsCollection( castArray( this.props.value ) ).more(); } @@ -205,6 +234,13 @@ class MediaUpload extends Component { } openModal() { + if ( + this.props.gallery && + this.props.value && + this.props.value.length > 0 + ) { + this.buildAndSetGalleryFrame(); + } this.frame.open(); } From 535e0752051957f3e6ab7aca2e71e99ceffc9a6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20Van=C2=A0Durpe?= <iseulde@automattic.com> Date: Fri, 1 Mar 2019 13:41:12 +0100 Subject: [PATCH 536/691] RichText: fix wordwise selection on Windows (#14184) * RichText: fix wordwise selection on Windows * Fix crtl ctrl typo. * Add docs --- packages/editor/src/components/rich-text/index.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/editor/src/components/rich-text/index.js b/packages/editor/src/components/rich-text/index.js index ccfe32326d944..d6be0a622307e 100644 --- a/packages/editor/src/components/rich-text/index.js +++ b/packages/editor/src/components/rich-text/index.js @@ -573,13 +573,14 @@ export class RichText extends Component { /** * Handles a keydown event. * - * @param {KeyboardEvent} event The keydown event. + * @param {SyntheticEvent} event A synthetic keyboard event. */ onKeyDown( event ) { - const { keyCode, shiftKey, altKey, metaKey } = event; + const { keyCode, shiftKey, altKey, metaKey, ctrlKey } = event; if ( - ! shiftKey && ! altKey && ! metaKey && + // Only override left and right keys without modifiers pressed. + ! shiftKey && ! altKey && ! metaKey && ! ctrlKey && ( keyCode === LEFT || keyCode === RIGHT ) ) { this.handleHorizontalNavigation( event ); @@ -661,6 +662,13 @@ export class RichText extends Component { } } + /** + * Handles horizontal keyboard navigation when no modifiers are pressed. The + * navigation is handled separately to move correctly around format + * boundaries. + * + * @param {SyntheticEvent} event A synthetic keyboard event. + */ handleHorizontalNavigation( event ) { const value = this.createRecord(); const { formats, text, start, end } = value; From a3b722d1e43c9a46c97fe7fc35557c3d462b199d Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Fri, 1 Mar 2019 16:24:57 +0000 Subject: [PATCH 537/691] Chore: Update: Code Quality: Remove some editor store references from block-editor (#14161) We missed to update the editor references from selectPreviousBlock, and selectNextBlock. ## Tests I added multiple blocks, I verified that when I remove a block the previous block still gets selected. --- packages/block-editor/src/store/actions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index 382a25f438d34..a94a526f4a5da 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -107,7 +107,7 @@ export function selectBlock( clientId, initialPosition = null ) { */ export function* selectPreviousBlock( clientId ) { const previousBlockClientId = yield select( - 'core/editor', + 'core/block-editor', 'getPreviousBlockClientId', clientId ); @@ -123,7 +123,7 @@ export function* selectPreviousBlock( clientId ) { */ export function* selectNextBlock( clientId ) { const nextBlockClientId = yield select( - 'core/editor', + 'core/block-editor', 'getNextBlockClientId', clientId ); From 8ddfa589e095b64e7d6fd7728311e0fe5cb7be4d Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 1 Mar 2019 11:53:55 -0500 Subject: [PATCH 538/691] Framework: Update package-lock.json for new Enzyme adapter (#14192) --- package-lock.json | 109 +++++++++++++++++++++++----------------------- 1 file changed, 54 insertions(+), 55 deletions(-) diff --git a/package-lock.json b/package-lock.json index b9bda6a34c73e..1b825c2ffbb3a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2897,6 +2897,60 @@ "enzyme": "^3.9.0", "enzyme-adapter-react-16": "^1.10.0", "enzyme-to-json": "^3.3.5" + }, + "dependencies": { + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "enzyme-adapter-react-16": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.10.0.tgz", + "integrity": "sha512-0QqwEZcBv1xEEla+a3H7FMci+y4ybLia9cZzsdIrId7qcig4MK0kqqf6iiCILH1lsKS6c6AVqL3wGPhCevv5aQ==", + "dev": true, + "requires": { + "enzyme-adapter-utils": "^1.10.0", + "object.assign": "^4.1.0", + "object.values": "^1.1.0", + "prop-types": "^15.6.2", + "react-is": "^16.7.0", + "react-test-renderer": "^16.0.0-0" + } + }, + "object.values": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.0.tgz", + "integrity": "sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.12.0", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "dev": true, + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + }, + "react-is": { + "version": "16.8.3", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.3.tgz", + "integrity": "sha512-Y4rC1ZJmsxxkkPuMLwvKvlL1Zfpbcu+Bf4ZigkHup3v9EfdYhAlWAaVyA19olXq2o2mGn0w+dFKvk3pVVlYcIA==", + "dev": true + } } }, "@wordpress/jest-puppeteer-axe": { @@ -7335,61 +7389,6 @@ "string.prototype.trim": "^1.1.2" } }, - "enzyme-adapter-react-16": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.9.1.tgz", - "integrity": "sha512-Egzogv1y77DUxdnq/CyHxLHaNxmSSKDDSDNNB/EiAXCZVFXdFibaNy2uUuRQ1n24T2m6KH/1Rw16XDRq+1yVEg==", - "dev": true, - "requires": { - "enzyme-adapter-utils": "^1.10.0", - "function.prototype.name": "^1.1.0", - "object.assign": "^4.1.0", - "object.values": "^1.1.0", - "prop-types": "^15.6.2", - "react-is": "^16.7.0", - "react-test-renderer": "^16.0.0-0" - }, - "dependencies": { - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "object.values": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.0.tgz", - "integrity": "sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.12.0", - "function-bind": "^1.1.1", - "has": "^1.0.3" - } - }, - "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "dev": true, - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - } - }, - "react-is": { - "version": "16.8.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.2.tgz", - "integrity": "sha512-D+NxhSR2HUCjYky1q1DwpNUD44cDpUXzSmmFyC3ug1bClcU/iDNy0YNn1iwme28fn+NFhpA13IndOd42CrFb+Q==", - "dev": true - } - } - }, "enzyme-adapter-utils": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.10.0.tgz", From 468393c7c0901e554aebb80c2fd5d1acb0ea3840 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 1 Mar 2019 12:08:25 -0500 Subject: [PATCH 539/691] Notices: Remove inaccurate createNotice sole argument feature (#14177) --- packages/notices/CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/notices/CHANGELOG.md b/packages/notices/CHANGELOG.md index 02d757332e51e..3ceb48ea97a3f 100644 --- a/packages/notices/CHANGELOG.md +++ b/packages/notices/CHANGELOG.md @@ -6,7 +6,6 @@ ### New Feature -- The `createNotice` can now optionally accept a WPNotice object as the sole argument. - New option `speak` enables control as to whether the notice content is announced to screen readers (defaults to `true`) ### Bug Fixes From 8f451a017b9a020917da37fac49a62885ca20b05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Fri, 1 Mar 2019 18:15:02 +0100 Subject: [PATCH 540/691] New package to auto-generate public API documentation (#13329) --- docs/manifest.json | 6 + package-lock.json | 26 + package.json | 2 + packages/docgen/.npmrc | 1 + packages/docgen/CHANGELOG.md | 3 + packages/docgen/README.md | 250 ++++++++ packages/docgen/bin/cli.js | 44 ++ packages/docgen/coverage.md | 83 +++ packages/docgen/package.json | 33 + packages/docgen/src/engine.js | 56 ++ packages/docgen/src/get-dependency-path.js | 3 + packages/docgen/src/get-export-entries.js | 97 +++ .../src/get-intermediate-representation.js | 152 +++++ packages/docgen/src/get-jsdoc-from-token.js | 37 ++ packages/docgen/src/get-leading-comments.js | 20 + packages/docgen/src/get-type-as-string.js | 44 ++ packages/docgen/src/index.js | 126 ++++ packages/docgen/src/markdown/embed.js | 51 ++ packages/docgen/src/markdown/formatter.js | 128 ++++ packages/docgen/src/markdown/index.js | 44 ++ packages/docgen/src/test/engine.js | 11 + .../fixtures/default-class-anonymous/code.js | 4 + .../default-class-anonymous/exports.json | 82 +++ .../test/fixtures/default-class-named/code.js | 4 + .../fixtures/default-class-named/exports.json | 101 +++ .../default-function-anonymous/code.js | 4 + .../default-function-anonymous/exports.json | 85 +++ .../fixtures/default-function-named/ast.json | 148 +++++ .../fixtures/default-function-named/code.js | 4 + .../default-function-named/exports.json | 104 +++ .../fixtures/default-function-named/ir.json | 7 + .../test/fixtures/default-identifier/ast.json | 165 +++++ .../test/fixtures/default-identifier/code.js | 6 + .../fixtures/default-identifier/exports.json | 39 ++ .../fixtures/default-import-default/ast.json | 143 +++++ .../fixtures/default-import-default/code.js | 6 + .../default-import-default/exports.json | 39 ++ .../default-import-default/module-code.js | 6 + .../default-import-default/module-ir.json | 7 + .../fixtures/default-import-named/ast.json | 163 +++++ .../fixtures/default-import-named/code.js | 6 + .../default-import-named/exports.json | 39 ++ .../default-import-named/module-code.js | 6 + .../default-import-named/module-ir.json | 1 + .../fixtures/default-named-export/ast.json | 189 ++++++ .../fixtures/default-named-export/code.js | 6 + .../default-named-export/exports.json | 147 +++++ .../default-undocumented-nocomments/code.js | 3 + .../exports.json | 39 ++ .../default-undocumented-oneliner/code.js | 2 + .../exports.json | 85 +++ .../test/fixtures/default-variable/code.js | 4 + .../fixtures/default-variable/exports.json | 62 ++ .../docgen/src/test/fixtures/markdown/code.js | 24 + .../docgen/src/test/fixtures/markdown/docs.md | 41 ++ .../src/test/fixtures/named-class/code.js | 4 + .../test/fixtures/named-class/exports.json | 103 +++ .../fixtures/named-default-exported/code.js | 1 + .../named-default-exported/exports.json | 102 +++ .../named-default-exported/module-code.js | 4 + .../named-default-exported/module-ir.json | 5 + .../src/test/fixtures/named-default/code.js | 1 + .../test/fixtures/named-default/exports.json | 102 +++ .../fixtures/named-default/module-code.js | 4 + .../fixtures/named-default/module-ir.json | 5 + .../src/test/fixtures/named-function/code.js | 4 + .../test/fixtures/named-function/exports.json | 106 ++++ .../src/test/fixtures/named-function/ir.json | 7 + .../named-identifier-destructuring/ast.json | 381 +++++++++++ .../named-identifier-destructuring/code.js | 6 + .../exports.json | 82 +++ .../test/fixtures/named-identifier/ast.json | 211 ++++++ .../test/fixtures/named-identifier/code.js | 6 + .../fixtures/named-identifier/exports.json | 82 +++ .../named-identifiers-and-inline/ast.json | 561 ++++++++++++++++ .../named-identifiers-and-inline/code.js | 17 + .../named-identifiers-and-inline/exports.json | 290 +++++++++ .../test/fixtures/named-identifiers/ast.json | 599 ++++++++++++++++++ .../test/fixtures/named-identifiers/code.js | 16 + .../fixtures/named-identifiers/exports.json | 200 ++++++ .../test/fixtures/named-identifiers/ir.json | 1 + .../test/fixtures/named-import-named/code.js | 5 + .../fixtures/named-import-named/exports.json | 220 +++++++ .../fixtures/named-import-namespace/ast.json | 186 ++++++ .../fixtures/named-import-namespace/code.js | 6 + .../named-import-namespace/exports.json | 82 +++ .../named-import-namespace/module-code.js | 1 + .../module-exports.json | 102 +++ .../named-import-namespace/module-ir.json | 1 + .../src/test/fixtures/named-variable/code.js | 5 + .../test/fixtures/named-variable/exports.json | 125 ++++ .../src/test/fixtures/named-variables/code.js | 6 + .../fixtures/named-variables/exports.json | 185 ++++++ .../test/fixtures/namespace-commented/code.js | 4 + .../fixtures/namespace-commented/exports.json | 62 ++ .../namespace-commented/module-ir.json | 22 + .../fixtures/namespace-commented/module.js | 19 + .../src/test/fixtures/namespace/code.js | 1 + .../src/test/fixtures/namespace/exports.json | 40 ++ .../test/fixtures/namespace/module-ir.json | 22 + .../src/test/fixtures/namespace/module.js | 19 + .../src/test/fixtures/tags-function/code.js | 24 + .../test/fixtures/tags-function/exports.json | 269 ++++++++ .../src/test/fixtures/tags-variable/code.js | 7 + .../test/fixtures/tags-variable/exports.json | 125 ++++ .../docgen/src/test/formatter-markdown.js | 34 + .../docgen/src/test/get-export-entries.js | 352 ++++++++++ .../test/get-intermediate-representation.js | 454 +++++++++++++ .../docgen/src/test/get-jsdoc-from-token.js | 78 +++ .../docgen/src/test/get-type-as-string.js | 108 ++++ packages/e2e-test-utils/README.md | 87 ++- packages/e2e-test-utils/package.json | 6 + 112 files changed, 8511 insertions(+), 34 deletions(-) create mode 100644 packages/docgen/.npmrc create mode 100644 packages/docgen/CHANGELOG.md create mode 100644 packages/docgen/README.md create mode 100755 packages/docgen/bin/cli.js create mode 100644 packages/docgen/coverage.md create mode 100644 packages/docgen/package.json create mode 100644 packages/docgen/src/engine.js create mode 100644 packages/docgen/src/get-dependency-path.js create mode 100644 packages/docgen/src/get-export-entries.js create mode 100644 packages/docgen/src/get-intermediate-representation.js create mode 100644 packages/docgen/src/get-jsdoc-from-token.js create mode 100644 packages/docgen/src/get-leading-comments.js create mode 100644 packages/docgen/src/get-type-as-string.js create mode 100644 packages/docgen/src/index.js create mode 100644 packages/docgen/src/markdown/embed.js create mode 100644 packages/docgen/src/markdown/formatter.js create mode 100644 packages/docgen/src/markdown/index.js create mode 100644 packages/docgen/src/test/engine.js create mode 100644 packages/docgen/src/test/fixtures/default-class-anonymous/code.js create mode 100644 packages/docgen/src/test/fixtures/default-class-anonymous/exports.json create mode 100644 packages/docgen/src/test/fixtures/default-class-named/code.js create mode 100644 packages/docgen/src/test/fixtures/default-class-named/exports.json create mode 100644 packages/docgen/src/test/fixtures/default-function-anonymous/code.js create mode 100644 packages/docgen/src/test/fixtures/default-function-anonymous/exports.json create mode 100644 packages/docgen/src/test/fixtures/default-function-named/ast.json create mode 100644 packages/docgen/src/test/fixtures/default-function-named/code.js create mode 100644 packages/docgen/src/test/fixtures/default-function-named/exports.json create mode 100644 packages/docgen/src/test/fixtures/default-function-named/ir.json create mode 100644 packages/docgen/src/test/fixtures/default-identifier/ast.json create mode 100644 packages/docgen/src/test/fixtures/default-identifier/code.js create mode 100644 packages/docgen/src/test/fixtures/default-identifier/exports.json create mode 100644 packages/docgen/src/test/fixtures/default-import-default/ast.json create mode 100644 packages/docgen/src/test/fixtures/default-import-default/code.js create mode 100644 packages/docgen/src/test/fixtures/default-import-default/exports.json create mode 100644 packages/docgen/src/test/fixtures/default-import-default/module-code.js create mode 100644 packages/docgen/src/test/fixtures/default-import-default/module-ir.json create mode 100644 packages/docgen/src/test/fixtures/default-import-named/ast.json create mode 100644 packages/docgen/src/test/fixtures/default-import-named/code.js create mode 100644 packages/docgen/src/test/fixtures/default-import-named/exports.json create mode 100644 packages/docgen/src/test/fixtures/default-import-named/module-code.js create mode 100644 packages/docgen/src/test/fixtures/default-import-named/module-ir.json create mode 100644 packages/docgen/src/test/fixtures/default-named-export/ast.json create mode 100644 packages/docgen/src/test/fixtures/default-named-export/code.js create mode 100644 packages/docgen/src/test/fixtures/default-named-export/exports.json create mode 100644 packages/docgen/src/test/fixtures/default-undocumented-nocomments/code.js create mode 100644 packages/docgen/src/test/fixtures/default-undocumented-nocomments/exports.json create mode 100644 packages/docgen/src/test/fixtures/default-undocumented-oneliner/code.js create mode 100644 packages/docgen/src/test/fixtures/default-undocumented-oneliner/exports.json create mode 100644 packages/docgen/src/test/fixtures/default-variable/code.js create mode 100644 packages/docgen/src/test/fixtures/default-variable/exports.json create mode 100644 packages/docgen/src/test/fixtures/markdown/code.js create mode 100644 packages/docgen/src/test/fixtures/markdown/docs.md create mode 100644 packages/docgen/src/test/fixtures/named-class/code.js create mode 100644 packages/docgen/src/test/fixtures/named-class/exports.json create mode 100644 packages/docgen/src/test/fixtures/named-default-exported/code.js create mode 100644 packages/docgen/src/test/fixtures/named-default-exported/exports.json create mode 100644 packages/docgen/src/test/fixtures/named-default-exported/module-code.js create mode 100644 packages/docgen/src/test/fixtures/named-default-exported/module-ir.json create mode 100644 packages/docgen/src/test/fixtures/named-default/code.js create mode 100644 packages/docgen/src/test/fixtures/named-default/exports.json create mode 100644 packages/docgen/src/test/fixtures/named-default/module-code.js create mode 100644 packages/docgen/src/test/fixtures/named-default/module-ir.json create mode 100644 packages/docgen/src/test/fixtures/named-function/code.js create mode 100644 packages/docgen/src/test/fixtures/named-function/exports.json create mode 100644 packages/docgen/src/test/fixtures/named-function/ir.json create mode 100644 packages/docgen/src/test/fixtures/named-identifier-destructuring/ast.json create mode 100644 packages/docgen/src/test/fixtures/named-identifier-destructuring/code.js create mode 100644 packages/docgen/src/test/fixtures/named-identifier-destructuring/exports.json create mode 100644 packages/docgen/src/test/fixtures/named-identifier/ast.json create mode 100644 packages/docgen/src/test/fixtures/named-identifier/code.js create mode 100644 packages/docgen/src/test/fixtures/named-identifier/exports.json create mode 100644 packages/docgen/src/test/fixtures/named-identifiers-and-inline/ast.json create mode 100644 packages/docgen/src/test/fixtures/named-identifiers-and-inline/code.js create mode 100644 packages/docgen/src/test/fixtures/named-identifiers-and-inline/exports.json create mode 100644 packages/docgen/src/test/fixtures/named-identifiers/ast.json create mode 100644 packages/docgen/src/test/fixtures/named-identifiers/code.js create mode 100644 packages/docgen/src/test/fixtures/named-identifiers/exports.json create mode 100644 packages/docgen/src/test/fixtures/named-identifiers/ir.json create mode 100644 packages/docgen/src/test/fixtures/named-import-named/code.js create mode 100644 packages/docgen/src/test/fixtures/named-import-named/exports.json create mode 100644 packages/docgen/src/test/fixtures/named-import-namespace/ast.json create mode 100644 packages/docgen/src/test/fixtures/named-import-namespace/code.js create mode 100644 packages/docgen/src/test/fixtures/named-import-namespace/exports.json create mode 100644 packages/docgen/src/test/fixtures/named-import-namespace/module-code.js create mode 100644 packages/docgen/src/test/fixtures/named-import-namespace/module-exports.json create mode 100644 packages/docgen/src/test/fixtures/named-import-namespace/module-ir.json create mode 100644 packages/docgen/src/test/fixtures/named-variable/code.js create mode 100644 packages/docgen/src/test/fixtures/named-variable/exports.json create mode 100644 packages/docgen/src/test/fixtures/named-variables/code.js create mode 100644 packages/docgen/src/test/fixtures/named-variables/exports.json create mode 100644 packages/docgen/src/test/fixtures/namespace-commented/code.js create mode 100644 packages/docgen/src/test/fixtures/namespace-commented/exports.json create mode 100644 packages/docgen/src/test/fixtures/namespace-commented/module-ir.json create mode 100644 packages/docgen/src/test/fixtures/namespace-commented/module.js create mode 100644 packages/docgen/src/test/fixtures/namespace/code.js create mode 100644 packages/docgen/src/test/fixtures/namespace/exports.json create mode 100644 packages/docgen/src/test/fixtures/namespace/module-ir.json create mode 100644 packages/docgen/src/test/fixtures/namespace/module.js create mode 100644 packages/docgen/src/test/fixtures/tags-function/code.js create mode 100644 packages/docgen/src/test/fixtures/tags-function/exports.json create mode 100644 packages/docgen/src/test/fixtures/tags-variable/code.js create mode 100644 packages/docgen/src/test/fixtures/tags-variable/exports.json create mode 100644 packages/docgen/src/test/formatter-markdown.js create mode 100644 packages/docgen/src/test/get-export-entries.js create mode 100644 packages/docgen/src/test/get-intermediate-representation.js create mode 100644 packages/docgen/src/test/get-jsdoc-from-token.js create mode 100644 packages/docgen/src/test/get-type-as-string.js diff --git a/docs/manifest.json b/docs/manifest.json index c6eccbce1d73d..fbaa1039cf678 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -635,6 +635,12 @@ "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/deprecated/README.md", "parent": "packages" }, + { + "title": "@wordpress/docgen", + "slug": "packages-docgen", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/docgen/README.md", + "parent": "packages" + }, { "title": "@wordpress/dom-ready", "slug": "packages-dom-ready", diff --git a/package-lock.json b/package-lock.json index 1b825c2ffbb3a..2f0d39cd7a38c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2701,6 +2701,17 @@ "@wordpress/hooks": "file:packages/hooks" } }, + "@wordpress/docgen": { + "version": "file:packages/docgen", + "dev": true, + "requires": { + "mdast-util-inject": "1.1.0", + "optionator": "0.8.2", + "remark": "10.0.1", + "remark-parse": "6.0.3", + "unified": "7.1.0" + } + }, "@wordpress/dom": { "version": "file:packages/dom", "requires": { @@ -13865,6 +13876,21 @@ "unist-util-visit": "^1.1.0" } }, + "mdast-util-inject": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-inject/-/mdast-util-inject-1.1.0.tgz", + "integrity": "sha1-2wa4tYW+lZotzS+H9HK6m3VvNnU=", + "dev": true, + "requires": { + "mdast-util-to-string": "^1.0.0" + } + }, + "mdast-util-to-string": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-1.0.5.tgz", + "integrity": "sha512-2qLt/DEOo5F6nc2VFScQiHPzQ0XXcabquRJxKMhKte8nt42o08HUxNDPk7tt0YPxnWjAT11I1SYi0X0iPnfI5A==", + "dev": true + }, "mdn-data": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-1.1.4.tgz", diff --git a/package.json b/package.json index eb1b308d87f2a..b0965325aa435 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "@wordpress/babel-preset-default": "file:packages/babel-preset-default", "@wordpress/browserslist-config": "file:packages/browserslist-config", "@wordpress/custom-templated-path-webpack-plugin": "file:packages/custom-templated-path-webpack-plugin", + "@wordpress/docgen": "file:packages/docgen", "@wordpress/e2e-test-utils": "file:packages/e2e-test-utils", "@wordpress/e2e-tests": "file:packages/e2e-tests", "@wordpress/eslint-plugin": "file:packages/eslint-plugin", @@ -161,6 +162,7 @@ "dev": "npm run build:packages && concurrently \"wp-scripts start\" \"npm run dev:packages\"", "dev:packages": "node ./bin/packages/watch.js", "docs:build": "node docs/tool", + "docs:generate": "lerna run docs:generate", "fixtures:clean": "rimraf \"packages/e2e-tests/fixtures/blocks/*.+(json|serialized.html)\"", "fixtures:server-registered": "docker-compose run -w /var/www/html/wp-content/plugins/gutenberg --rm wordpress ./bin/get-server-blocks.php > test/integration/full-content/server-registered.json", "fixtures:generate": "npm run fixtures:server-registered && cross-env GENERATE_MISSING_FIXTURES=y npm run test-unit", diff --git a/packages/docgen/.npmrc b/packages/docgen/.npmrc new file mode 100644 index 0000000000000..43c97e719a5a8 --- /dev/null +++ b/packages/docgen/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/packages/docgen/CHANGELOG.md b/packages/docgen/CHANGELOG.md new file mode 100644 index 0000000000000..ae69187866b19 --- /dev/null +++ b/packages/docgen/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 (Unreleased) + +- Initial release diff --git a/packages/docgen/README.md b/packages/docgen/README.md new file mode 100644 index 0000000000000..c62b96fc4e44b --- /dev/null +++ b/packages/docgen/README.md @@ -0,0 +1,250 @@ +# `docgen` + +`docgen` helps you to generate the _public API_ of your code. Given an entry point file, it outputs the ES6 export statements and their corresponding JSDoc comments in human-readable format. + +Some characteristics: + +* If the export statement doesn't contain any JSDoc, it'll look up for JSDoc up to the declaration. +* It can resolve relative dependencies, either files or directories. For example, `import default from './dependency'` will find `dependency.js` or `dependency/index.js` + +## Installation + +Install the module + +```bash +npm install @wordpress/docgen --save-dev +``` + +## Usage + +```bash +npx docgen <entry-point.js> +``` + +This command will generate a file named `entry-point-api.md` containing all the exports and their JSDoc comments. + +### CLI options + +* **--formatter** `(String)`: A path to a custom formatter to control the contents of the output file. It should be a CommonJS module that exports a function that takes as input: + * *rootDir* `(String)`: current working directory as seen by docgen. + * *docPath* `(String)`: path of the output document to generate. + * *symbols* `(Array)`: the symbols found. +* **--ignore** `(RegExp)`: A regular expression used to ignore symbols whose name match it. +* **--output** `(String)`: Output file that will contain the API documentation. +* **--to-section** `(String)`: Append generated documentation to this section in the Markdown output. To be used by the default Markdown formatter. Depends on `--output` and bypasses the custom `--formatter` passed, if any. +* **--to-token**: Embed generated documentation within the start and end tokens in the Markdown output. To be used by the default Markdown formatter.Depends on `--output` and bypasses the custom `--formatter` passed, if any. + * Start token: `<!-- START TOKEN(Autogenerated API docs) -->` + * End token: `<!-- END TOKEN(Autogenerated API docs) -->` +* **--use-token** `(String)`: This options allows you to customize the string between the tokens. For example, `--use-token my-api` will look up for the start token `<!-- START TOKEN(my-api) -->` and the end token `<!-- END TOKEN(my-api) -->`. Depends on `--to-token`. +* **--debug**: Run in debug mode, which outputs some intermediate files useful for debugging. + +## Examples + +### Default export + +Entry point `index.js`: + +```js +/** + * Adds two numbers. + * + * @param {number} term1 First number. + * @param {number} term2 Second number. + * @return {number} The result of adding the two numbers. + */ +export default function addition( term1, term2 ) { + // Implementation would go here. +} +``` + +Output of `npx docgen index.js` would be `index-api.js`: + +```markdown +# API + +## default + +[example.js#L8-L10](example.js#L8-L10) + +Adds two numbers. + +**Parameters** + +- **term1** `number`: First number. +- **term2** `number`: Second number. + +**Returns** + +`number` The result of adding the two numbers. +``` + +### Named export + +Entry point `index.js`: + +```js +/** + * Adds two numbers. + * + * @param {number} term1 First number. + * @param {number} term2 Second number. + * @return {number} The result of adding the two numbers. + */ +function addition( term1, term2 ) { + return term1 + term2; +} + +/** + * Adds two numbers. + * + * @deprecated Use `addition` instead. + * + * @param {number} term1 First number. + * @param {number} term2 Second number. + * @return {number} The result of adding the two numbers. + */ +function count( term1, term2 ) { + return term1 + term2; +} + +export { count, addition }; +``` + +Output of `npx docgen index.js` would be `index-api.js`: + +```markdown +# API + +## addition + +[example.js#L25-L25](example.js#L25-L25) + +Adds two numbers. + +**Parameters** + +- **term1** `number`: First number. +- **term2** `number`: Second number. + +**Returns** + +`number` The result of adding the two numbers. + +## count + +[example.js#L25-L25](example.js#L25-L25) + +> **Deprecated** Use `addition` instead. + +Adds two numbers. + +**Parameters** + +- **term1** `number`: First number. +- **term2** `number`: Second number. + +**Returns** + +`number` The result of adding the two numbers. +``` + +### Namespace export + +Let the entry point be `index.js`: + +```js +export * from './count'; +``` + +with `./count/index.js` contents being: + +```js +/** + * Substracts two numbers. + * + * @example + * + * ```js + * const result = substraction( 5, 2 ); + * console.log( result ); // Will log 3 + * ``` + * + * @param {number} term1 First number. + * @param {number} term2 Second number. + * @return {number} The result of subtracting the two numbers. + */ +export function substraction( term1, term2 ) { + return term1 - term2; +} + +/** + * Adds two numbers. + * + * @example + * + * ```js + * const result = addition( 5, 2 ); + * console.log( result ); // Will log 7 + * ``` + * + * @param {number} term1 First number. + * @param {number} term2 Second number. + * @return {number} The result of adding the two numbers. + */ +export function addition( term1, term2 ) { + // Implementation would go here. + return term1 - term2; +} +``` + +Output of `npx docgen index.js` would be `index-api.js`: + +````markdown +# API + +## addition + +[example-module.js#L1-L1](example-module.js#L1-L1) + +Adds two numbers. + +**Usage** + +```js +const result = addition( 5, 2 ); +console.log( result ); // Will log 7 +``` + +**Parameters** + +- **term1** `number`: First number. +- **term2** `number`: Second number. + +**Returns** + +`number` The result of adding the two numbers. + +## substraction + +[example-module.js#L1-L1](example-module.js#L1-L1) + +Substracts two numbers. + +**Usage** + +```js +const result = substraction( 5, 2 ); +console.log( result ); // Will log 3 +``` + +**Parameters** + +- **term1** `number`: First number. +- **term2** `number`: Second number. + +**Returns** + +`number` The result of subtracting the two numbers. +```` + +<br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/docgen/bin/cli.js b/packages/docgen/bin/cli.js new file mode 100755 index 0000000000000..bcc1a6f1e245d --- /dev/null +++ b/packages/docgen/bin/cli.js @@ -0,0 +1,44 @@ +#!/usr/bin/env node + +const docgen = require( '../src' ); + +const optionator = require( 'optionator' )( { + prepend: 'Usage: node <path-to-docgen> <relative-path-to-entry-point>', + options: [ { + option: 'formatter', + type: 'String', + description: 'A custom function to format the generated documentation. By default, a Markdown formatter will be used.', + }, { + option: 'output', + type: 'String', + description: 'Output file to contain the API documentation.', + }, { + option: 'ignore', + type: 'RegExp', + description: 'A regular expression used to ignore symbols whose name match it.', + }, { + option: 'to-section', + type: 'String', + description: 'Append generated documentation to this section in the Markdown output. To be used by the default Markdown formatter.', + dependsOn: 'output', + }, { + option: 'to-token', + type: 'Boolean', + description: 'Embed generated documentation within this token in the Markdown output. To be used by the default Markdown formatter.', + dependsOn: 'output', + }, { + option: 'use-token', + type: 'String', + default: 'Autogenerated API docs', + description: 'Add this string to the start/end tokens.', + dependsOn: 'to-token', + }, { + option: 'debug', + type: 'Boolean', + default: false, + description: 'Run in debug mode, which outputs some intermediate files useful for debugging.', + } ], +} ); + +const options = optionator.parseArgv( process.argv ); +docgen( options._[ 0 ], options ); diff --git a/packages/docgen/coverage.md b/packages/docgen/coverage.md new file mode 100644 index 0000000000000..83c5dfd3d59fa --- /dev/null +++ b/packages/docgen/coverage.md @@ -0,0 +1,83 @@ +# Coverage + +## Packages outside of scope + +- babel-plugin-makepot. CommonJS module. Babel plugin. +- babel-preset-default. CommonJS module. Babel preset. +- browserslist-config. CommonJS module. Config. +- custom-templated-path-webpack-plugin. CommonJS module. Webpack plugin. +- docgen. CommonJS module. +- e2e-tests. Do not export anything. +- eslint-plugin. CommonJS module. ESLint plugin. +- is-shallow-equal. CommonJS module. +- jest-preset-default. CommonJS module. Jest preset. +- library-export-default-webpack-plugin. CommonJS. Webpack plugin. +- npm-package-json-lint-config. CommonJS. Config. +- postcss-themes. CommonJS module. +- scripts. CommonJS module. + +## TODO + +These either happen in private API, aren't relevant, or are pending of decission. + +- [ ] go undocummented: `unstable__*`, rich-text `unstableToDom`, `experimental__` +- [ ] `constants` keycodes, rich-text `LINE_SEPARATOR` +- [ ] `{?{ time: number, count: number }}`packages/editor/src/store/selectors.js +- [ ] `{type=}` packages/block-library/src/image/edit.js +- [ ] `@api` packages/editor/src/editor-styles/ast/stringify/compiler.js +- [ ] `@callback` packages/components/src/autocomplete/index.js +- [ ] `@cite` packages/block-serialization-default-parser/src/index.js +- [ ] `@class` packages/edit-post/src/hooks/components/media-upload/index.js +- [ ] `@const` packages/editor/src/editor-styles/transforms/wrap.js +- [ ] `@constant` packages/editor/src/hooks/align.js +- [ ] `@constructor` packages/edit-post/src/hooks/components/media-upload/index.js +- [ ] `@inheritdoc` packages/edit-post/src/components/meta-boxes/meta-boxes-area/index.js +- [ ] `@private` packages/editor/src/components/rich-text/index.js +- [ ] `@property` babel-plugin-import-jsx-pragma +- [ ] `@since` packages/block-serialization-default-parser/src/index.js +- [ ] `@throws` packages/blocks/src/api/node.js +- [ ] `@typedef` packages/blocks/src/api/registration.js + +## DONE + +- [x] a11y +- [x] annotations +- [x] api-fetch +- [x] babel-plugin-import-jsx-pragma +- [x] blob +- [x] block-library +- [x] block-serialization-default-parser +- [x] block-serialization-spec-parser +- [x] blocks +- [x] components +- [x] compose +- [x] core-data +- [x] data +- [x] date +- [x] deprecated +- [x] dom +- [x] dom-ready +- [x] e2e-test-utils +- [x] edit-post +- [x] editor +- [x] element +- [x] escape-html +- [x] format-library +- [x] hooks +- [x] html-entities +- [x] i18n +- [x] jest-console +- [x] jest-puppeteer-axe +- [x] keycodes +- [x] list-reusable-blocks +- [x] notices +- [x] nux +- [x] plugins +- [x] priority-queue +- [x] redux-routine +- [x] rich-text +- [x] shortcode +- [x] token-list +- [x] url +- [x] viewport +- [x] wordcount diff --git a/packages/docgen/package.json b/packages/docgen/package.json new file mode 100644 index 0000000000000..a73ed098f3142 --- /dev/null +++ b/packages/docgen/package.json @@ -0,0 +1,33 @@ +{ + "name": "@wordpress/docgen", + "version": "1.0.0-beta.0", + "description": "Autogenerate public API documentation from exports and JSDoc comments.", + "author": "The WordPress Contributors", + "license": "GPL-2.0-or-later", + "keywords": [ + "jsdoc", + "documentation", + "wordpress" + ], + "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/docgen/README.md", + "repository": { + "type": "git", + "url": "git+https://github.com/WordPress/gutenberg.git" + }, + "bugs": { + "url": "https://github.com/WordPress/gutenberg/issues" + }, + "bin": { + "docgen": "./bin/cli.js" + }, + "dependencies": { + "mdast-util-inject": "1.1.0", + "optionator": "0.8.2", + "remark": "10.0.1", + "remark-parse": "6.0.3", + "unified": "7.1.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/docgen/src/engine.js b/packages/docgen/src/engine.js new file mode 100644 index 0000000000000..0c01f94ef9b50 --- /dev/null +++ b/packages/docgen/src/engine.js @@ -0,0 +1,56 @@ +/** +* External dependencies. +*/ +const espree = require( 'espree' ); +const { flatten } = require( 'lodash' ); + +/** +* Internal dependencies. +*/ +const getIntermediateRepresentation = require( './get-intermediate-representation' ); + +const getAST = ( source ) => espree.parse( source, { + attachComment: true, + loc: true, + ecmaVersion: 2018, + ecmaFeatures: { + jsx: true, + }, + sourceType: 'module', +} ); + +const getExportTokens = ( ast ) => ast.body.filter( + ( node ) => [ + 'ExportNamedDeclaration', + 'ExportDefaultDeclaration', + 'ExportAllDeclaration', + ].some( ( declaration ) => declaration === node.type ) +); + +const engine = ( path, code, getIRFromPath = () => {} ) => { + const result = {}; + result.ast = getAST( code ); + result.tokens = getExportTokens( result.ast ); + result.ir = flatten( result.tokens.map( + ( token ) => getIntermediateRepresentation( + path, + token, + result.ast, + getIRFromPath + ) + ) ); + + return result; +}; + +/** + * Function that takes code and returns an intermediate representation. + * + * @param {string} code The code to parse. + * @param {Function} [getIRFromPath=noop] Callback to retrieve the + * Intermediate Representation from a path relative to the file + * being parsed. + * + * @return {Object} Intermediate Representation in JSON. + */ +module.exports = engine; diff --git a/packages/docgen/src/get-dependency-path.js b/packages/docgen/src/get-dependency-path.js new file mode 100644 index 0000000000000..83c043023ed44 --- /dev/null +++ b/packages/docgen/src/get-dependency-path.js @@ -0,0 +1,3 @@ +module.exports = function( token ) { + return token.source.value; +}; diff --git a/packages/docgen/src/get-export-entries.js b/packages/docgen/src/get-export-entries.js new file mode 100644 index 0000000000000..a69811b2b6545 --- /dev/null +++ b/packages/docgen/src/get-export-entries.js @@ -0,0 +1,97 @@ +/** + * External dependencies + */ +const { get } = require( 'lodash' ); + +/** + * Returns the export entry records of the given export statement. + * Unlike [the standard](http://www.ecma-international.org/ecma-262/9.0/#exportentry-record), + * the `importName` and the `localName` are merged together. + * + * @param {Object} token Espree node representing an export. + * + * @return {Array} Exported entry records. Example: + * [ { + * localName: 'localName', + * exportName: 'exportedName', + * module: null, + * lineStart: 2, + * lineEnd: 3, + * } ] + */ +module.exports = function( token ) { + if ( token.type === 'ExportDefaultDeclaration' ) { + const getLocalName = ( t ) => { + let name; + switch ( t.declaration.type ) { + case 'Identifier': + name = t.declaration.name; + break; + case 'AssignmentExpression': + name = t.declaration.left.name; + break; + //case 'FunctionDeclaration' + //case 'ClassDeclaration' + default: + name = get( t.declaration, [ 'id', 'name' ], '*default*' ); + } + return name; + }; + return [ { + localName: getLocalName( token ), + exportName: 'default', + module: null, + lineStart: token.loc.start.line, + lineEnd: token.loc.end.line, + } ]; + } + + if ( token.type === 'ExportAllDeclaration' ) { + return [ { + localName: '*', + exportName: null, + module: token.source.value, + lineStart: token.loc.start.line, + lineEnd: token.loc.end.line, + } ]; + } + + const name = []; + if ( token.declaration === null ) { + token.specifiers.forEach( ( specifier ) => name.push( { + localName: specifier.local.name, + exportName: specifier.exported.name, + module: get( token.source, [ 'value' ], null ), + lineStart: specifier.loc.start.line, + lineEnd: specifier.loc.end.line, + } ) ); + return name; + } + + switch ( token.declaration.type ) { + case 'ClassDeclaration': + case 'FunctionDeclaration': + name.push( { + localName: token.declaration.id.name, + exportName: token.declaration.id.name, + module: null, + lineStart: token.declaration.loc.start.line, + lineEnd: token.declaration.loc.end.line, + } ); + break; + + case 'VariableDeclaration': + token.declaration.declarations.forEach( ( declaration ) => { + name.push( { + localName: declaration.id.name, + exportName: declaration.id.name, + module: null, + lineStart: token.declaration.loc.start.line, + lineEnd: token.declaration.loc.end.line, + } ); + } ); + break; + } + + return name; +}; diff --git a/packages/docgen/src/get-intermediate-representation.js b/packages/docgen/src/get-intermediate-representation.js new file mode 100644 index 0000000000000..eee7775d69979 --- /dev/null +++ b/packages/docgen/src/get-intermediate-representation.js @@ -0,0 +1,152 @@ +/** + * External dependencies. + */ +const { get } = require( 'lodash' ); + +/** + * Internal dependencies. + */ +const getExportEntries = require( './get-export-entries' ); +const getJSDocFromToken = require( './get-jsdoc-from-token' ); +const getDependencyPath = require( './get-dependency-path' ); + +const UNDOCUMENTED = 'Undocumented declaration.'; +const NAMESPACE_EXPORT = '*'; +const DEFAULT_EXPORT = 'default'; + +const hasClassWithName = ( node, name ) => + node.type === 'ClassDeclaration' && + node.id.name === name; + +const hasFunctionWithName = ( node, name ) => + node.type === 'FunctionDeclaration' && + node.id.name === name; + +const hasVariableWithName = ( node, name ) => + node.type === 'VariableDeclaration' && + node.declarations.some( ( declaration ) => { + if ( declaration.id.type === 'ObjectPattern' ) { + return declaration.id.properties.some( + ( property ) => property.key.name === name + ); + } + return declaration.id.name === name; + } ); + +const hasNamedExportWithName = ( node, name ) => + node.type === 'ExportNamedDeclaration' && ( + ( node.declaration && hasClassWithName( node.declaration, name ) ) || + ( node.declaration && hasFunctionWithName( node.declaration, name ) ) || + ( node.declaration && hasVariableWithName( node.declaration, name ) ) + ); + +const hasImportWithName = ( node, name ) => + node.type === 'ImportDeclaration' && + node.specifiers.some( ( specifier ) => specifier.local.name === name ); + +const isImportDeclaration = ( node ) => node.type === 'ImportDeclaration'; + +const someImportMatchesName = ( name, token ) => { + let matches = false; + token.specifiers.forEach( ( specifier ) => { + if ( ( specifier.type === 'ImportDefaultSpecifier' ) && ( name === 'default' ) ) { + matches = true; + } + if ( ( specifier.type === 'ImportSpecifier' ) && ( name === specifier.imported.name ) ) { + matches = true; + } + } ); + return matches; +}; + +const someEntryMatchesName = ( name, entry, token ) => + ( token.type === 'ExportNamedDeclaration' && entry.localName === name ) || + ( token.type === 'ImportDeclaration' && someImportMatchesName( name, token ) ); + +const getJSDocFromDependency = ( token, entry, parseDependency ) => { + let doc; + const ir = parseDependency( getDependencyPath( token ) ); + if ( entry.localName === NAMESPACE_EXPORT ) { + doc = ir.filter( ( { name } ) => name !== DEFAULT_EXPORT ); + } else { + doc = ir.find( ( { name } ) => someEntryMatchesName( name, entry, token ) ); + } + return doc; +}; + +const getJSDoc = ( token, entry, ast, parseDependency ) => { + let doc; + if ( entry.localName !== NAMESPACE_EXPORT ) { + doc = getJSDocFromToken( token ); + if ( ( doc !== undefined ) ) { + return doc; + } + } + + if ( entry && entry.module === null ) { + const candidates = ast.body.filter( ( node ) => { + return hasClassWithName( node, entry.localName ) || + hasFunctionWithName( node, entry.localName ) || + hasVariableWithName( node, entry.localName ) || + hasNamedExportWithName( node, entry.localName ) || + hasImportWithName( node, entry.localName ); + } ); + if ( candidates.length !== 1 ) { + return doc; + } + const node = candidates[ 0 ]; + if ( isImportDeclaration( node ) ) { + doc = getJSDocFromDependency( node, entry, parseDependency ); + } else { + doc = getJSDocFromToken( node ); + } + return doc; + } + + return getJSDocFromDependency( token, entry, parseDependency ); +}; + +/** + * Takes a export token and returns an intermediate representation in JSON. + * + * If the export token doesn't contain any JSDoc, and it's a identifier, + * the identifier declaration will be looked up in the file or dependency + * if an `ast` and `parseDependency` callback are provided. + * + * @param {string} path Path to file being processed. + * @param {Object} token Espree export token. + * @param {Object} [ast] Espree ast of the file being parsed. + * @param {Function} [parseDependency] Function that takes a path + * and returns the intermediate representation of the dependency file. + * + * @return {Object} Intermediate Representation in JSON. + */ +module.exports = function( path, token, ast = { body: [] }, parseDependency = () => {} ) { + const exportEntries = getExportEntries( token ); + const ir = []; + exportEntries.forEach( ( entry ) => { + const doc = getJSDoc( token, entry, ast, parseDependency ); + if ( entry.localName === NAMESPACE_EXPORT ) { + doc.forEach( ( namedExport ) => { + ir.push( { + path, + name: namedExport.name, + description: namedExport.description, + tags: namedExport.tags, + lineStart: entry.lineStart, + lineEnd: entry.lineEnd, + } ); + } ); + } else { + ir.push( { + path, + name: entry.exportName, + description: get( doc, [ 'description' ], UNDOCUMENTED ), + tags: get( doc, [ 'tags' ], [] ), + lineStart: entry.lineStart, + lineEnd: entry.lineEnd, + } ); + } + } ); + return ir; +}; diff --git a/packages/docgen/src/get-jsdoc-from-token.js b/packages/docgen/src/get-jsdoc-from-token.js new file mode 100644 index 0000000000000..e888abc53d06c --- /dev/null +++ b/packages/docgen/src/get-jsdoc-from-token.js @@ -0,0 +1,37 @@ +/** + * External dependencies. + */ +const doctrine = require( 'doctrine' ); + +/** + * Internal dependencies. + */ +const getLeadingComments = require( './get-leading-comments' ); +const getTypeAsString = require( './get-type-as-string' ); + +/** + * Function that takes an Espree token and returns + * a object representing the leading JSDoc comment of the token, + * if any. + * + * @param {Object} token Espree token. + * @return {Object} Object representing the JSDoc comment. + */ +module.exports = function( token ) { + let jsdoc; + const comments = getLeadingComments( token ); + if ( comments && comments.startsWith( '*\n' ) ) { + jsdoc = doctrine.parse( comments, { + unwrap: true, + recoverable: true, + sloppy: true, + } ); + jsdoc.tags = jsdoc.tags.map( ( tag ) => { + if ( tag.type ) { + tag.type = getTypeAsString( tag.type ); + } + return tag; + } ); + } + return jsdoc; +}; diff --git a/packages/docgen/src/get-leading-comments.js b/packages/docgen/src/get-leading-comments.js new file mode 100644 index 0000000000000..3f025278b1033 --- /dev/null +++ b/packages/docgen/src/get-leading-comments.js @@ -0,0 +1,20 @@ +/** + * External dependencies. + */ +const { last } = require( 'lodash' ); + +/** + * Function that returns the leading comment + * of a Espree node. + * + * @param {Object} declaration Espree node to inspect + * + * @return {?string} Leading comment or undefined if there is none. + */ +module.exports = function( declaration ) { + let comments; + if ( declaration.leadingComments ) { + comments = last( declaration.leadingComments ).value; + } + return comments; +}; diff --git a/packages/docgen/src/get-type-as-string.js b/packages/docgen/src/get-type-as-string.js new file mode 100644 index 0000000000000..64bfaefd00d52 --- /dev/null +++ b/packages/docgen/src/get-type-as-string.js @@ -0,0 +1,44 @@ +const maybeAddDefault = function( value, defaultValue ) { + if ( defaultValue ) { + return `value=${ defaultValue }`; + } + return value; +}; + +const getType = function( param, defaultValue ) { + if ( ! defaultValue ) { + defaultValue = param.default; + } + + if ( param.type.type ) { + return getType( param.type, defaultValue ); + } else if ( param.expression ) { + if ( param.type === 'RestType' ) { + return `...${ getType( param.expression, defaultValue ) }`; + } else if ( param.type === 'NullableType' ) { + return `?${ getType( param.expression, defaultValue ) }`; + } else if ( param.type === 'TypeApplication' ) { + return `${ getType( param.expression, defaultValue ) }<${ + param.applications.map( ( application ) => getType( application ) ).join( ',' ) + }>`; + } else if ( param.type === 'OptionalType' ) { + return `[${ getType( param.expression, defaultValue ) }]`; + } + return getType( param.expression, defaultValue ); + } else if ( param.elements ) { + const types = param.elements.map( ( element ) => getType( element ) ); + return maybeAddDefault( `(${ types.join( '|' ) })`, defaultValue ); + } else if ( param.type === 'AllLiteral' ) { + return maybeAddDefault( '*', defaultValue ); + } else if ( param.type === 'NullLiteral' ) { + return maybeAddDefault( 'null', defaultValue ); + } else if ( param.type === 'UndefinedLiteral' ) { + return maybeAddDefault( 'undefined', defaultValue ); + } + + return maybeAddDefault( param.name, defaultValue ); +}; + +module.exports = function( param ) { + return getType( param ); +}; diff --git a/packages/docgen/src/index.js b/packages/docgen/src/index.js new file mode 100644 index 0000000000000..1bc2e71a37897 --- /dev/null +++ b/packages/docgen/src/index.js @@ -0,0 +1,126 @@ +/** + * External dependencies + */ +const fs = require( 'fs' ); +const path = require( 'path' ); +const { last } = require( 'lodash' ); + +/** + * Internal dependencies + */ +const engine = require( './engine' ); +const defaultMarkdownFormatter = require( './markdown' ); + +/** + * Helpers functions. + */ + +const relativeToAbsolute = ( basePath, relativePath ) => { + const target = path.join( path.dirname( basePath ), relativePath ); + if ( path.extname( target ) === '.js' ) { + return target; + } + let targetFile = target + '.js'; + if ( fs.existsSync( targetFile ) ) { + return targetFile; + } + targetFile = path.join( target, 'index.js' ); + if ( fs.existsSync( targetFile ) ) { + return targetFile; + } + process.stdout.write( '\nRelative path does not exists.' ); + process.stdout.write( '\n' ); + process.stdout.write( `\nBase: ${ basePath }` ); + process.stdout.write( `\nRelative: ${ relativePath }` ); + process.stdout.write( '\n\n' ); + process.exit( 1 ); +}; + +const getIRFromRelativePath = ( rootDir, basePath ) => ( relativePath ) => { + if ( ! relativePath.startsWith( '.' ) ) { + return []; + } + const absolutePath = relativeToAbsolute( basePath, relativePath ); + const result = processFile( rootDir, absolutePath ); + return result.ir || undefined; +}; + +const processFile = ( rootDir, inputFile ) => { + try { + const data = fs.readFileSync( inputFile, 'utf8' ); + currentFileStack.push( inputFile ); + const relativePath = path.relative( rootDir, inputFile ); + const result = engine( relativePath, data, getIRFromRelativePath( rootDir, last( currentFileStack ) ) ); + currentFileStack.pop( inputFile ); + return result; + } catch ( e ) { + process.stdout.write( `\n${ e }` ); + process.stdout.write( '\n\n' ); + process.exit( 1 ); + } +}; + +const runCustomFormatter = ( customFormatterFile, rootDir, doc, symbols, headingTitle ) => { + try { + const customFormatter = require( customFormatterFile ); + const output = customFormatter( rootDir, doc, symbols, headingTitle ); + fs.writeFileSync( doc, output ); + } catch ( e ) { + process.stdout.write( `\n${ e }` ); + process.stdout.write( '\n\n' ); + process.exit( 1 ); + } + return 'custom formatter'; +}; + +// To keep track of file being processed. +const currentFileStack = []; + +module.exports = function( sourceFile, options ) { + // Input: process CLI args, prepare files, etc + const processDir = process.cwd(); + if ( sourceFile === undefined ) { + process.stdout.write( '\n' ); + process.stdout.write( 'No source file provided' ); + process.stdout.write( '\n\n' ); + process.exit( 1 ); + } + sourceFile = path.join( processDir, sourceFile ); + + const debugMode = options.debug ? true : false; + + const inputBase = path.join( + path.dirname( sourceFile ), + path.basename( sourceFile, path.extname( sourceFile ) ) + ); + const ast = inputBase + '-ast.json'; + const tokens = inputBase + '-exports.json'; + const ir = inputBase + '-ir.json'; + const doc = options.output ? + path.join( processDir, options.output ) : + inputBase + '-api.md'; + + // Process + const result = processFile( processDir, sourceFile ); + const filteredIr = result.ir.filter( ( { name } ) => options.ignore ? ! name.match( options.ignore ) : true ); + + // Ouput + if ( result === undefined ) { + process.stdout.write( '\nFile was processed, but contained no ES6 module exports:' ); + process.stdout.write( `\n${ sourceFile }` ); + process.stdout.write( '\n\n' ); + process.exit( 0 ); + } + + if ( options.formatter ) { + runCustomFormatter( path.join( processDir, options.formatter ), processDir, doc, filteredIr, 'API' ); + } else { + defaultMarkdownFormatter( options, processDir, doc, filteredIr, 'API' ); + } + + if ( debugMode ) { + fs.writeFileSync( ir, JSON.stringify( result.ir ) ); + fs.writeFileSync( tokens, JSON.stringify( result.tokens ) ); + fs.writeFileSync( ast, JSON.stringify( result.ast ) ); + } +}; diff --git a/packages/docgen/src/markdown/embed.js b/packages/docgen/src/markdown/embed.js new file mode 100644 index 0000000000000..9b52064c3377a --- /dev/null +++ b/packages/docgen/src/markdown/embed.js @@ -0,0 +1,51 @@ +/** + * External dependencies + */ +const { findLast } = require( 'lodash' ); + +const getHeadingIndex = ( ast, index ) => { + const astBeforeIndex = ast.children.slice( 0, index ); + const lastHeading = findLast( astBeforeIndex, ( node ) => node.type === 'heading' ); + return lastHeading ? lastHeading.depth : 1; +}; + +/** + * Inserts new contents within the token boundaries. + * + * @param {string} token String to embed in the start/end tokens. + * @param {Object} targetAst The remark AST of the file where the new contents are to be embedded. + * @param {Object} newContentAst The new contents to be embedded in remark AST format. + * @return {boolean} Whether the contents were embedded or not. + */ +const embed = function( token, targetAst, newContentAst ) { + let headingIndex = -1; + + const START_TOKEN = `<!-- START TOKEN(${ token }) -->`; + const END_TOKEN = `<!-- END TOKEN(${ token }) -->`; + const startIndex = targetAst.children.findIndex( + ( node ) => node.type === 'html' && node.value === START_TOKEN + ); + if ( startIndex === -1 ) { + return false; + } + const endIndex = targetAst.children.findIndex( + ( node ) => node.type === 'html' && node.value === END_TOKEN + ); + if ( endIndex === -1 ) { + return false; + } + + if ( startIndex !== -1 && endIndex !== -1 && startIndex < endIndex ) { + headingIndex = getHeadingIndex( targetAst, startIndex ); + newContentAst.children.forEach( ( node ) => { + if ( node.type === 'heading' ) { + node.depth = headingIndex + 1; + } + } ); + targetAst.children.splice( startIndex + 1, endIndex - startIndex - 1, newContentAst ); + return true; + } + return false; +}; + +module.exports = embed; diff --git a/packages/docgen/src/markdown/formatter.js b/packages/docgen/src/markdown/formatter.js new file mode 100644 index 0000000000000..d9e41536f2f9a --- /dev/null +++ b/packages/docgen/src/markdown/formatter.js @@ -0,0 +1,128 @@ +/** + * External dependencies + */ +const path = require( 'path' ); + +const getTagsByName = ( tags, ...names ) => tags.filter( ( tag ) => names.some( ( name ) => name === tag.title ) ); + +const cleanSpaces = ( paragraph ) => + paragraph ? + paragraph.split( '\n' ).map( + ( sentence ) => sentence.trim() + ).reduce( + ( acc, current ) => acc + ' ' + current, + '' + ).trim() : + ''; + +const formatTag = ( title, tags, formatter, docs ) => { + if ( tags && tags.length > 0 ) { + docs.push( '\n' ); + docs.push( '\n' ); + docs.push( `**${ title }**` ); + docs.push( '\n' ); + docs.push( ...tags.map( formatter ) ); + } +}; + +const formatExamples = ( tags, docs ) => { + if ( tags && tags.length > 0 ) { + docs.push( '\n' ); + docs.push( '\n' ); + docs.push( '**Usage**' ); + docs.push( '\n' ); + docs.push( '\n' ); + docs.push( ...tags.map( + ( tag ) => `${ tag.description }` + ).join( '\n\n' ) ); + } +}; + +const formatDeprecated = ( tags, docs ) => { + if ( tags && tags.length > 0 ) { + docs.push( '\n' ); + docs.push( ...tags.map( + ( tag ) => `\n> **Deprecated** ${ cleanSpaces( tag.description ) }` + ) ); + } +}; + +const formatDescription = ( description, docs ) => { + docs.push( '\n' ); + docs.push( '\n' ); + docs.push( description ); +}; + +const getHeading = ( index, text ) => { + return '#'.repeat( index ) + ' ' + text; +}; + +module.exports = function( rootDir, docPath, symbols, headingTitle, headingStartIndex ) { + const docs = [ ]; + let headingIndex = headingStartIndex || 1; + if ( headingTitle ) { + docs.push( getHeading( headingIndex, `${ headingTitle }` ) ); + headingIndex++; + } + docs.push( '\n' ); + docs.push( '\n' ); + symbols.sort( ( first, second ) => { + const firstName = first.name.toUpperCase(); + const secondName = second.name.toUpperCase(); + if ( firstName < secondName ) { + return -1; + } + if ( firstName > secondName ) { + return 1; + } + return 0; + } ); + if ( symbols && symbols.length > 0 ) { + symbols.forEach( ( symbol ) => { + const symbolPath = path.join( + path.relative( + path.dirname( docPath ), + path.join( rootDir, path.dirname( symbol.path ) ) + ), + path.basename( symbol.path ), + ); + const symbolPathWithLines = `${ symbolPath }#L${ symbol.lineStart }-L${ symbol.lineEnd }`; + docs.push( getHeading( headingIndex, `${ symbol.name }` ) ); + docs.push( `\n\n[${ symbolPathWithLines }](${ symbolPathWithLines })` ); + formatDeprecated( getTagsByName( symbol.tags, 'deprecated' ), docs ); + formatDescription( symbol.description, docs ); + formatTag( + 'Related', + getTagsByName( symbol.tags, 'see', 'link' ), + ( tag ) => `\n- ${ tag.description }`, + docs + ); + formatExamples( getTagsByName( symbol.tags, 'example' ), docs ); + formatTag( + 'Type', + getTagsByName( symbol.tags, 'type' ), + ( tag ) => `\n\`${ tag.type }\` ${ cleanSpaces( tag.description ) }`, + docs + ); + formatTag( + 'Parameters', + getTagsByName( symbol.tags, 'param' ), + ( tag ) => `\n- **${ tag.name }** \`${ tag.type }\`: ${ cleanSpaces( tag.description ) }`, + docs + ); + formatTag( + 'Returns', + getTagsByName( symbol.tags, 'return' ), + ( tag ) => `\n\`${ tag.type }\` ${ cleanSpaces( tag.description ) }`, + docs + ); + docs.push( '\n' ); + docs.push( '\n' ); + } ); + docs.pop(); // remove last \n, we want one blank line at the end of the file. + } else { + docs.push( 'Nothing to document.' ); + docs.push( '\n' ); + } + return docs.join( '' ); +}; diff --git a/packages/docgen/src/markdown/index.js b/packages/docgen/src/markdown/index.js new file mode 100644 index 0000000000000..499e211e1873a --- /dev/null +++ b/packages/docgen/src/markdown/index.js @@ -0,0 +1,44 @@ +/** + * External dependencies. + */ +const remark = require( 'remark' ); +const unified = require( 'unified' ); +const remarkParser = require( 'remark-parse' ); +const inject = require( 'mdast-util-inject' ); +const fs = require( 'fs' ); + +/** + * Internal dependencies. + */ +const formatter = require( './formatter' ); +const embed = require( './embed' ); + +const appendOrEmbedContents = ( { options, newContents } ) => { + return function transform( targetAst, file, next ) { + if ( options.toSection && ! inject( options.toSection, targetAst, newContents ) ) { + return next( new Error( `Heading ${ options.toSection } not found.` ) ); + } else if ( options.toToken && ! embed( options.useToken, targetAst, newContents ) ) { + return next( new Error( `Start and/or end tokens for ${ options.useToken } not found.` ) ); + } + next(); + }; +}; + +module.exports = function( options, processDir, doc, filteredIr, headingTitle ) { + if ( options.toSection || options.toToken ) { + const currentReadmeFile = fs.readFileSync( options.output, 'utf8' ); + const newContents = unified().use( remarkParser ).parse( formatter( processDir, doc, filteredIr, null ) ); + remark() + .use( { settings: { commonmark: true } } ) + .use( appendOrEmbedContents, { options, newContents } ) + .process( currentReadmeFile, function( err, file ) { + if ( err ) { + throw err; + } + fs.writeFileSync( doc, file ); + } ); + } else { + const output = formatter( processDir, doc, filteredIr, headingTitle ); + fs.writeFileSync( doc, output ); + } +}; diff --git a/packages/docgen/src/test/engine.js b/packages/docgen/src/test/engine.js new file mode 100644 index 0000000000000..3a9efc921a2f0 --- /dev/null +++ b/packages/docgen/src/test/engine.js @@ -0,0 +1,11 @@ +/** + * Internal dependencies. + */ +const engine = require( '../engine' ); + +describe( 'Engine', () => { + it( 'should return a void IR for undefined code', () => { + const { ir } = engine( undefined ); + expect( ir ).toHaveLength( 0 ); + } ); +} ); diff --git a/packages/docgen/src/test/fixtures/default-class-anonymous/code.js b/packages/docgen/src/test/fixtures/default-class-anonymous/code.js new file mode 100644 index 0000000000000..79ca0d6a39fcd --- /dev/null +++ b/packages/docgen/src/test/fixtures/default-class-anonymous/code.js @@ -0,0 +1,4 @@ +/** + * Class declaration example. + */ +export default class {} diff --git a/packages/docgen/src/test/fixtures/default-class-anonymous/exports.json b/packages/docgen/src/test/fixtures/default-class-anonymous/exports.json new file mode 100644 index 0000000000000..858f07abe98df --- /dev/null +++ b/packages/docgen/src/test/fixtures/default-class-anonymous/exports.json @@ -0,0 +1,82 @@ +{ + "type": "ExportDefaultDeclaration", + "start": 38, + "end": 61, + "loc": { + "start": { + "line": 4, + "column": 0 + }, + "end": { + "line": 4, + "column": 23 + } + }, + "range": [ + 38, + 61 + ], + "declaration": { + "type": "ClassDeclaration", + "start": 53, + "end": 61, + "loc": { + "start": { + "line": 4, + "column": 15 + }, + "end": { + "line": 4, + "column": 23 + } + }, + "range": [ + 53, + 61 + ], + "id": null, + "superClass": null, + "body": { + "type": "ClassBody", + "start": 59, + "end": 61, + "loc": { + "start": { + "line": 4, + "column": 21 + }, + "end": { + "line": 4, + "column": 23 + } + }, + "range": [ + 59, + 61 + ], + "body": [] + } + }, + "leadingComments": [ + { + "type": "Block", + "value": "*\n * Class declaration example.\n ", + "start": 0, + "end": 37, + "range": [ + 0, + 37 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 3 + } + } + } + ] +} \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/default-class-named/code.js b/packages/docgen/src/test/fixtures/default-class-named/code.js new file mode 100644 index 0000000000000..91649c8da44b5 --- /dev/null +++ b/packages/docgen/src/test/fixtures/default-class-named/code.js @@ -0,0 +1,4 @@ +/** + * Class declaration example. + */ +export default class ClassDeclaration {} diff --git a/packages/docgen/src/test/fixtures/default-class-named/exports.json b/packages/docgen/src/test/fixtures/default-class-named/exports.json new file mode 100644 index 0000000000000..7533ef32e60de --- /dev/null +++ b/packages/docgen/src/test/fixtures/default-class-named/exports.json @@ -0,0 +1,101 @@ +{ + "type": "ExportDefaultDeclaration", + "start": 38, + "end": 78, + "loc": { + "start": { + "line": 4, + "column": 0 + }, + "end": { + "line": 4, + "column": 40 + } + }, + "range": [ + 38, + 78 + ], + "declaration": { + "type": "ClassDeclaration", + "start": 53, + "end": 78, + "loc": { + "start": { + "line": 4, + "column": 15 + }, + "end": { + "line": 4, + "column": 40 + } + }, + "range": [ + 53, + 78 + ], + "id": { + "type": "Identifier", + "start": 59, + "end": 75, + "loc": { + "start": { + "line": 4, + "column": 21 + }, + "end": { + "line": 4, + "column": 37 + } + }, + "range": [ + 59, + 75 + ], + "name": "ClassDeclaration" + }, + "superClass": null, + "body": { + "type": "ClassBody", + "start": 76, + "end": 78, + "loc": { + "start": { + "line": 4, + "column": 38 + }, + "end": { + "line": 4, + "column": 40 + } + }, + "range": [ + 76, + 78 + ], + "body": [] + } + }, + "leadingComments": [ + { + "type": "Block", + "value": "*\n * Class declaration example.\n ", + "start": 0, + "end": 37, + "range": [ + 0, + 37 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 3 + } + } + } + ] +} \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/default-function-anonymous/code.js b/packages/docgen/src/test/fixtures/default-function-anonymous/code.js new file mode 100644 index 0000000000000..5c8c7e23a946c --- /dev/null +++ b/packages/docgen/src/test/fixtures/default-function-anonymous/code.js @@ -0,0 +1,4 @@ +/** + * Function declaration example. + */ +export default function() {} diff --git a/packages/docgen/src/test/fixtures/default-function-anonymous/exports.json b/packages/docgen/src/test/fixtures/default-function-anonymous/exports.json new file mode 100644 index 0000000000000..9d10cd6c962f0 --- /dev/null +++ b/packages/docgen/src/test/fixtures/default-function-anonymous/exports.json @@ -0,0 +1,85 @@ +{ + "type": "ExportDefaultDeclaration", + "start": 41, + "end": 69, + "loc": { + "start": { + "line": 4, + "column": 0 + }, + "end": { + "line": 4, + "column": 28 + } + }, + "range": [ + 41, + 69 + ], + "declaration": { + "type": "FunctionDeclaration", + "start": 56, + "end": 69, + "loc": { + "start": { + "line": 4, + "column": 15 + }, + "end": { + "line": 4, + "column": 28 + } + }, + "range": [ + 56, + 69 + ], + "id": null, + "generator": false, + "expression": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start": 67, + "end": 69, + "loc": { + "start": { + "line": 4, + "column": 26 + }, + "end": { + "line": 4, + "column": 28 + } + }, + "range": [ + 67, + 69 + ], + "body": [] + } + }, + "leadingComments": [ + { + "type": "Block", + "value": "*\n * Function declaration example.\n ", + "start": 0, + "end": 40, + "range": [ + 0, + 40 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 3 + } + } + } + ] +} \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/default-function-named/ast.json b/packages/docgen/src/test/fixtures/default-function-named/ast.json new file mode 100644 index 0000000000000..a082f805974d2 --- /dev/null +++ b/packages/docgen/src/test/fixtures/default-function-named/ast.json @@ -0,0 +1,148 @@ +{ + "type": "Program", + "start": 0, + "end": 84, + "loc": { + "start": { + "line": 4, + "column": 0 + }, + "end": { + "line": 4, + "column": 42 + } + }, + "range": [ + 41, + 83 + ], + "body": [ + { + "type": "ExportDefaultDeclaration", + "start": 41, + "end": 83, + "loc": { + "start": { + "line": 4, + "column": 0 + }, + "end": { + "line": 4, + "column": 42 + } + }, + "range": [ + 41, + 83 + ], + "declaration": { + "type": "FunctionDeclaration", + "start": 56, + "end": 83, + "loc": { + "start": { + "line": 4, + "column": 15 + }, + "end": { + "line": 4, + "column": 42 + } + }, + "range": [ + 56, + 83 + ], + "id": { + "type": "Identifier", + "start": 65, + "end": 78, + "loc": { + "start": { + "line": 4, + "column": 24 + }, + "end": { + "line": 4, + "column": 37 + } + }, + "range": [ + 65, + 78 + ], + "name": "myDeclaration" + }, + "generator": false, + "expression": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start": 81, + "end": 83, + "loc": { + "start": { + "line": 4, + "column": 40 + }, + "end": { + "line": 4, + "column": 42 + } + }, + "range": [ + 81, + 83 + ], + "body": [] + } + }, + "leadingComments": [ + { + "type": "Block", + "value": "*\n * Function declaration example.\n ", + "start": 0, + "end": 40, + "range": [ + 0, + 40 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 3 + } + } + } + ] + } + ], + "sourceType": "module", + "comments": [ + { + "type": "Block", + "value": "*\n * Function declaration example.\n ", + "start": 0, + "end": 40, + "range": [ + 0, + 40 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 3 + } + } + } + ] +} \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/default-function-named/code.js b/packages/docgen/src/test/fixtures/default-function-named/code.js new file mode 100644 index 0000000000000..74318a75d6ab9 --- /dev/null +++ b/packages/docgen/src/test/fixtures/default-function-named/code.js @@ -0,0 +1,4 @@ +/** + * Function declaration example. + */ +export default function myDeclaration() {} diff --git a/packages/docgen/src/test/fixtures/default-function-named/exports.json b/packages/docgen/src/test/fixtures/default-function-named/exports.json new file mode 100644 index 0000000000000..7765cbef0da6d --- /dev/null +++ b/packages/docgen/src/test/fixtures/default-function-named/exports.json @@ -0,0 +1,104 @@ +{ + "type": "ExportDefaultDeclaration", + "start": 41, + "end": 83, + "loc": { + "start": { + "line": 4, + "column": 0 + }, + "end": { + "line": 4, + "column": 42 + } + }, + "range": [ + 41, + 83 + ], + "declaration": { + "type": "FunctionDeclaration", + "start": 56, + "end": 83, + "loc": { + "start": { + "line": 4, + "column": 15 + }, + "end": { + "line": 4, + "column": 42 + } + }, + "range": [ + 56, + 83 + ], + "id": { + "type": "Identifier", + "start": 65, + "end": 78, + "loc": { + "start": { + "line": 4, + "column": 24 + }, + "end": { + "line": 4, + "column": 37 + } + }, + "range": [ + 65, + 78 + ], + "name": "myDeclaration" + }, + "generator": false, + "expression": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start": 81, + "end": 83, + "loc": { + "start": { + "line": 4, + "column": 40 + }, + "end": { + "line": 4, + "column": 42 + } + }, + "range": [ + 81, + 83 + ], + "body": [] + } + }, + "leadingComments": [ + { + "type": "Block", + "value": "*\n * Function declaration example.\n ", + "start": 0, + "end": 40, + "range": [ + 0, + 40 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 3 + } + } + } + ] +} \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/default-function-named/ir.json b/packages/docgen/src/test/fixtures/default-function-named/ir.json new file mode 100644 index 0000000000000..526885397806c --- /dev/null +++ b/packages/docgen/src/test/fixtures/default-function-named/ir.json @@ -0,0 +1,7 @@ +[ + { + "name": "default", + "description": "Function declaration example.", + "tags": [] + } +] \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/default-identifier/ast.json b/packages/docgen/src/test/fixtures/default-identifier/ast.json new file mode 100644 index 0000000000000..d83f628004f7d --- /dev/null +++ b/packages/docgen/src/test/fixtures/default-identifier/ast.json @@ -0,0 +1,165 @@ +{ + "type": "Program", + "start": 0, + "end": 98, + "loc": { + "start": { + "line": 4, + "column": 0 + }, + "end": { + "line": 6, + "column": 32 + } + }, + "range": [ + 38, + 97 + ], + "body": [ + { + "type": "ClassDeclaration", + "start": 38, + "end": 63, + "loc": { + "start": { + "line": 4, + "column": 0 + }, + "end": { + "line": 4, + "column": 25 + } + }, + "range": [ + 38, + 63 + ], + "id": { + "type": "Identifier", + "start": 44, + "end": 60, + "loc": { + "start": { + "line": 4, + "column": 6 + }, + "end": { + "line": 4, + "column": 22 + } + }, + "range": [ + 44, + 60 + ], + "name": "ClassDeclaration" + }, + "superClass": null, + "body": { + "type": "ClassBody", + "start": 61, + "end": 63, + "loc": { + "start": { + "line": 4, + "column": 23 + }, + "end": { + "line": 4, + "column": 25 + } + }, + "range": [ + 61, + 63 + ], + "body": [] + }, + "leadingComments": [ + { + "type": "Block", + "value": "*\n * Class declaration example.\n ", + "start": 0, + "end": 37, + "range": [ + 0, + 37 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 3 + } + } + } + ] + }, + { + "type": "ExportDefaultDeclaration", + "start": 65, + "end": 97, + "loc": { + "start": { + "line": 6, + "column": 0 + }, + "end": { + "line": 6, + "column": 32 + } + }, + "range": [ + 65, + 97 + ], + "declaration": { + "type": "Identifier", + "start": 80, + "end": 96, + "loc": { + "start": { + "line": 6, + "column": 15 + }, + "end": { + "line": 6, + "column": 31 + } + }, + "range": [ + 80, + 96 + ], + "name": "ClassDeclaration" + } + } + ], + "sourceType": "module", + "comments": [ + { + "type": "Block", + "value": "*\n * Class declaration example.\n ", + "start": 0, + "end": 37, + "range": [ + 0, + 37 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 3 + } + } + } + ] +} \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/default-identifier/code.js b/packages/docgen/src/test/fixtures/default-identifier/code.js new file mode 100644 index 0000000000000..2a172ecc0578f --- /dev/null +++ b/packages/docgen/src/test/fixtures/default-identifier/code.js @@ -0,0 +1,6 @@ +/** + * Class declaration example. + */ +class ClassDeclaration {} + +export default ClassDeclaration; diff --git a/packages/docgen/src/test/fixtures/default-identifier/exports.json b/packages/docgen/src/test/fixtures/default-identifier/exports.json new file mode 100644 index 0000000000000..c5cb42d5ad4ca --- /dev/null +++ b/packages/docgen/src/test/fixtures/default-identifier/exports.json @@ -0,0 +1,39 @@ +{ + "type": "ExportDefaultDeclaration", + "start": 65, + "end": 97, + "loc": { + "start": { + "line": 6, + "column": 0 + }, + "end": { + "line": 6, + "column": 32 + } + }, + "range": [ + 65, + 97 + ], + "declaration": { + "type": "Identifier", + "start": 80, + "end": 96, + "loc": { + "start": { + "line": 6, + "column": 15 + }, + "end": { + "line": 6, + "column": 31 + } + }, + "range": [ + 80, + 96 + ], + "name": "ClassDeclaration" + } +} \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/default-import-default/ast.json b/packages/docgen/src/test/fixtures/default-import-default/ast.json new file mode 100644 index 0000000000000..ee77f27a561e7 --- /dev/null +++ b/packages/docgen/src/test/fixtures/default-import-default/ast.json @@ -0,0 +1,143 @@ +{ + "type": "Program", + "start": 0, + "end": 92, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 29 + } + }, + "range": [ + 0, + 91 + ], + "body": [ + { + "type": "ImportDeclaration", + "start": 0, + "end": 60, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 60 + } + }, + "range": [ + 0, + 60 + ], + "specifiers": [ + { + "type": "ImportDefaultSpecifier", + "start": 7, + "end": 20, + "loc": { + "start": { + "line": 1, + "column": 7 + }, + "end": { + "line": 1, + "column": 20 + } + }, + "range": [ + 7, + 20 + ], + "local": { + "type": "Identifier", + "start": 7, + "end": 20, + "loc": { + "start": { + "line": 1, + "column": 7 + }, + "end": { + "line": 1, + "column": 20 + } + }, + "range": [ + 7, + 20 + ], + "name": "fnDeclaration" + } + } + ], + "source": { + "type": "Literal", + "start": 26, + "end": 59, + "loc": { + "start": { + "line": 1, + "column": 26 + }, + "end": { + "line": 1, + "column": 59 + } + }, + "range": [ + 26, + 59 + ], + "value": "./default-import-default-module", + "raw": "'./default-import-default-module'" + } + }, + { + "type": "ExportDefaultDeclaration", + "start": 62, + "end": 91, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 29 + } + }, + "range": [ + 62, + 91 + ], + "declaration": { + "type": "Identifier", + "start": 77, + "end": 90, + "loc": { + "start": { + "line": 3, + "column": 15 + }, + "end": { + "line": 3, + "column": 28 + } + }, + "range": [ + 77, + 90 + ], + "name": "fnDeclaration" + } + } + ], + "sourceType": "module", + "comments": [] +} \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/default-import-default/code.js b/packages/docgen/src/test/fixtures/default-import-default/code.js new file mode 100644 index 0000000000000..70619a579c57b --- /dev/null +++ b/packages/docgen/src/test/fixtures/default-import-default/code.js @@ -0,0 +1,6 @@ +/** + * Internal dependencies + */ +import fnDeclaration from './module-code'; + +export default fnDeclaration; diff --git a/packages/docgen/src/test/fixtures/default-import-default/exports.json b/packages/docgen/src/test/fixtures/default-import-default/exports.json new file mode 100644 index 0000000000000..fd453ed301100 --- /dev/null +++ b/packages/docgen/src/test/fixtures/default-import-default/exports.json @@ -0,0 +1,39 @@ +{ + "type": "ExportDefaultDeclaration", + "start": 62, + "end": 91, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 29 + } + }, + "range": [ + 62, + 91 + ], + "declaration": { + "type": "Identifier", + "start": 77, + "end": 90, + "loc": { + "start": { + "line": 3, + "column": 15 + }, + "end": { + "line": 3, + "column": 28 + } + }, + "range": [ + 77, + 90 + ], + "name": "fnDeclaration" + } +} \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/default-import-default/module-code.js b/packages/docgen/src/test/fixtures/default-import-default/module-code.js new file mode 100644 index 0000000000000..4da030a5b9d0a --- /dev/null +++ b/packages/docgen/src/test/fixtures/default-import-default/module-code.js @@ -0,0 +1,6 @@ +/** + * Function declaration. + */ +function functionDeclaration() {} + +export default functionDeclaration; diff --git a/packages/docgen/src/test/fixtures/default-import-default/module-ir.json b/packages/docgen/src/test/fixtures/default-import-default/module-ir.json new file mode 100644 index 0000000000000..9aaa9dc2213f5 --- /dev/null +++ b/packages/docgen/src/test/fixtures/default-import-default/module-ir.json @@ -0,0 +1,7 @@ +[ + { + "name": "default", + "description": "Function declaration.", + "tags": [] + } +] \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/default-import-named/ast.json b/packages/docgen/src/test/fixtures/default-import-named/ast.json new file mode 100644 index 0000000000000..73947334d5ac8 --- /dev/null +++ b/packages/docgen/src/test/fixtures/default-import-named/ast.json @@ -0,0 +1,163 @@ +{ + "type": "Program", + "start": 0, + "end": 117, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 29 + } + }, + "range": [ + 0, + 116 + ], + "body": [ + { + "type": "ImportDeclaration", + "start": 0, + "end": 85, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 85 + } + }, + "range": [ + 0, + 85 + ], + "specifiers": [ + { + "type": "ImportSpecifier", + "start": 9, + "end": 45, + "loc": { + "start": { + "line": 1, + "column": 9 + }, + "end": { + "line": 1, + "column": 45 + } + }, + "range": [ + 9, + 45 + ], + "imported": { + "type": "Identifier", + "start": 9, + "end": 28, + "loc": { + "start": { + "line": 1, + "column": 9 + }, + "end": { + "line": 1, + "column": 28 + } + }, + "range": [ + 9, + 28 + ], + "name": "functionDeclaration" + }, + "local": { + "type": "Identifier", + "start": 32, + "end": 45, + "loc": { + "start": { + "line": 1, + "column": 32 + }, + "end": { + "line": 1, + "column": 45 + } + }, + "range": [ + 32, + 45 + ], + "name": "fnDeclaration" + } + } + ], + "source": { + "type": "Literal", + "start": 53, + "end": 84, + "loc": { + "start": { + "line": 1, + "column": 53 + }, + "end": { + "line": 1, + "column": 84 + } + }, + "range": [ + 53, + 84 + ], + "value": "./default-import-named-module", + "raw": "'./default-import-named-module'" + } + }, + { + "type": "ExportDefaultDeclaration", + "start": 87, + "end": 116, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 29 + } + }, + "range": [ + 87, + 116 + ], + "declaration": { + "type": "Identifier", + "start": 102, + "end": 115, + "loc": { + "start": { + "line": 3, + "column": 15 + }, + "end": { + "line": 3, + "column": 28 + } + }, + "range": [ + 102, + 115 + ], + "name": "fnDeclaration" + } + } + ], + "sourceType": "module", + "comments": [] +} \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/default-import-named/code.js b/packages/docgen/src/test/fixtures/default-import-named/code.js new file mode 100644 index 0000000000000..b8e89023444b5 --- /dev/null +++ b/packages/docgen/src/test/fixtures/default-import-named/code.js @@ -0,0 +1,6 @@ +/** + * Internal dependencies + */ +import { functionDeclaration as fnDeclaration } from './module-code'; + +export default fnDeclaration; diff --git a/packages/docgen/src/test/fixtures/default-import-named/exports.json b/packages/docgen/src/test/fixtures/default-import-named/exports.json new file mode 100644 index 0000000000000..25d0f6024356f --- /dev/null +++ b/packages/docgen/src/test/fixtures/default-import-named/exports.json @@ -0,0 +1,39 @@ +{ + "type": "ExportDefaultDeclaration", + "start": 87, + "end": 116, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 29 + } + }, + "range": [ + 87, + 116 + ], + "declaration": { + "type": "Identifier", + "start": 102, + "end": 115, + "loc": { + "start": { + "line": 3, + "column": 15 + }, + "end": { + "line": 3, + "column": 28 + } + }, + "range": [ + 102, + 115 + ], + "name": "fnDeclaration" + } +} \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/default-import-named/module-code.js b/packages/docgen/src/test/fixtures/default-import-named/module-code.js new file mode 100644 index 0000000000000..7ec5c68c3fbb3 --- /dev/null +++ b/packages/docgen/src/test/fixtures/default-import-named/module-code.js @@ -0,0 +1,6 @@ +/** + * Function declaration. + */ +function functionDeclaration() {} + +export { functionDeclaration }; diff --git a/packages/docgen/src/test/fixtures/default-import-named/module-ir.json b/packages/docgen/src/test/fixtures/default-import-named/module-ir.json new file mode 100644 index 0000000000000..3c04255485cb9 --- /dev/null +++ b/packages/docgen/src/test/fixtures/default-import-named/module-ir.json @@ -0,0 +1 @@ +[{"name":"functionDeclaration","description":"Function declaration.","tags":[]}] \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/default-named-export/ast.json b/packages/docgen/src/test/fixtures/default-named-export/ast.json new file mode 100644 index 0000000000000..31fde5d2e6883 --- /dev/null +++ b/packages/docgen/src/test/fixtures/default-named-export/ast.json @@ -0,0 +1,189 @@ +{ + "type": "Program", + "start": 0, + "end": 119, + "loc": { + "start": { + "line": 4, + "column": 0 + }, + "end": { + "line": 6, + "column": 35 + } + }, + "range": [ + 41, + 118 + ], + "body": [ + { + "type": "ExportNamedDeclaration", + "start": 41, + "end": 81, + "loc": { + "start": { + "line": 4, + "column": 0 + }, + "end": { + "line": 4, + "column": 40 + } + }, + "range": [ + 41, + 81 + ], + "declaration": { + "type": "FunctionDeclaration", + "start": 48, + "end": 81, + "loc": { + "start": { + "line": 4, + "column": 7 + }, + "end": { + "line": 4, + "column": 40 + } + }, + "range": [ + 48, + 81 + ], + "id": { + "type": "Identifier", + "start": 57, + "end": 76, + "loc": { + "start": { + "line": 4, + "column": 16 + }, + "end": { + "line": 4, + "column": 35 + } + }, + "range": [ + 57, + 76 + ], + "name": "functionDeclaration" + }, + "generator": false, + "expression": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start": 79, + "end": 81, + "loc": { + "start": { + "line": 4, + "column": 38 + }, + "end": { + "line": 4, + "column": 40 + } + }, + "range": [ + 79, + 81 + ], + "body": [] + } + }, + "specifiers": [], + "source": null, + "leadingComments": [ + { + "type": "Block", + "value": "*\n * Function declaration example.\n ", + "start": 0, + "end": 40, + "range": [ + 0, + 40 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 3 + } + } + } + ] + }, + { + "type": "ExportDefaultDeclaration", + "start": 83, + "end": 118, + "loc": { + "start": { + "line": 6, + "column": 0 + }, + "end": { + "line": 6, + "column": 35 + } + }, + "range": [ + 83, + 118 + ], + "declaration": { + "type": "Identifier", + "start": 98, + "end": 117, + "loc": { + "start": { + "line": 6, + "column": 15 + }, + "end": { + "line": 6, + "column": 34 + } + }, + "range": [ + 98, + 117 + ], + "name": "functionDeclaration" + } + } + ], + "sourceType": "module", + "comments": [ + { + "type": "Block", + "value": "*\n * Function declaration example.\n ", + "start": 0, + "end": 40, + "range": [ + 0, + 40 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 3 + } + } + } + ] +} \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/default-named-export/code.js b/packages/docgen/src/test/fixtures/default-named-export/code.js new file mode 100644 index 0000000000000..bd45a875a5eb6 --- /dev/null +++ b/packages/docgen/src/test/fixtures/default-named-export/code.js @@ -0,0 +1,6 @@ +/** + * Function declaration example. + */ +export function functionDeclaration() {} + +export default functionDeclaration; diff --git a/packages/docgen/src/test/fixtures/default-named-export/exports.json b/packages/docgen/src/test/fixtures/default-named-export/exports.json new file mode 100644 index 0000000000000..b432555e4d7a4 --- /dev/null +++ b/packages/docgen/src/test/fixtures/default-named-export/exports.json @@ -0,0 +1,147 @@ +[ + { + "type": "ExportNamedDeclaration", + "start": 41, + "end": 81, + "loc": { + "start": { + "line": 4, + "column": 0 + }, + "end": { + "line": 4, + "column": 40 + } + }, + "range": [ + 41, + 81 + ], + "declaration": { + "type": "FunctionDeclaration", + "start": 48, + "end": 81, + "loc": { + "start": { + "line": 4, + "column": 7 + }, + "end": { + "line": 4, + "column": 40 + } + }, + "range": [ + 48, + 81 + ], + "id": { + "type": "Identifier", + "start": 57, + "end": 76, + "loc": { + "start": { + "line": 4, + "column": 16 + }, + "end": { + "line": 4, + "column": 35 + } + }, + "range": [ + 57, + 76 + ], + "name": "functionDeclaration" + }, + "generator": false, + "expression": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start": 79, + "end": 81, + "loc": { + "start": { + "line": 4, + "column": 38 + }, + "end": { + "line": 4, + "column": 40 + } + }, + "range": [ + 79, + 81 + ], + "body": [] + } + }, + "specifiers": [], + "source": null, + "leadingComments": [ + { + "type": "Block", + "value": "*\n * Function declaration example.\n ", + "start": 0, + "end": 40, + "range": [ + 0, + 40 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 3 + } + } + } + ] + }, + { + "type": "ExportDefaultDeclaration", + "start": 83, + "end": 118, + "loc": { + "start": { + "line": 6, + "column": 0 + }, + "end": { + "line": 6, + "column": 35 + } + }, + "range": [ + 83, + 118 + ], + "declaration": { + "type": "Identifier", + "start": 98, + "end": 117, + "loc": { + "start": { + "line": 6, + "column": 15 + }, + "end": { + "line": 6, + "column": 34 + } + }, + "range": [ + 98, + 117 + ], + "name": "functionDeclaration" + } + } +] \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/default-undocumented-nocomments/code.js b/packages/docgen/src/test/fixtures/default-undocumented-nocomments/code.js new file mode 100644 index 0000000000000..e82c517463fd0 --- /dev/null +++ b/packages/docgen/src/test/fixtures/default-undocumented-nocomments/code.js @@ -0,0 +1,3 @@ +const myDeclaration = function() {}; + +export default myDeclaration; diff --git a/packages/docgen/src/test/fixtures/default-undocumented-nocomments/exports.json b/packages/docgen/src/test/fixtures/default-undocumented-nocomments/exports.json new file mode 100644 index 0000000000000..ac0eef8437933 --- /dev/null +++ b/packages/docgen/src/test/fixtures/default-undocumented-nocomments/exports.json @@ -0,0 +1,39 @@ +{ + "type": "ExportDefaultDeclaration", + "start": 38, + "end": 67, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 29 + } + }, + "range": [ + 38, + 67 + ], + "declaration": { + "type": "Identifier", + "start": 53, + "end": 66, + "loc": { + "start": { + "line": 3, + "column": 15 + }, + "end": { + "line": 3, + "column": 28 + } + }, + "range": [ + 53, + 66 + ], + "name": "myDeclaration" + } +} \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/default-undocumented-oneliner/code.js b/packages/docgen/src/test/fixtures/default-undocumented-oneliner/code.js new file mode 100644 index 0000000000000..11e8966dd62d1 --- /dev/null +++ b/packages/docgen/src/test/fixtures/default-undocumented-oneliner/code.js @@ -0,0 +1,2 @@ +// This comment should be ignored +export default function() { } diff --git a/packages/docgen/src/test/fixtures/default-undocumented-oneliner/exports.json b/packages/docgen/src/test/fixtures/default-undocumented-oneliner/exports.json new file mode 100644 index 0000000000000..45c6ab79f0913 --- /dev/null +++ b/packages/docgen/src/test/fixtures/default-undocumented-oneliner/exports.json @@ -0,0 +1,85 @@ +{ + "type": "ExportDefaultDeclaration", + "start": 34, + "end": 63, + "loc": { + "start": { + "line": 2, + "column": 0 + }, + "end": { + "line": 2, + "column": 29 + } + }, + "range": [ + 34, + 63 + ], + "declaration": { + "type": "FunctionDeclaration", + "start": 49, + "end": 63, + "loc": { + "start": { + "line": 2, + "column": 15 + }, + "end": { + "line": 2, + "column": 29 + } + }, + "range": [ + 49, + 63 + ], + "id": null, + "generator": false, + "expression": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start": 60, + "end": 63, + "loc": { + "start": { + "line": 2, + "column": 26 + }, + "end": { + "line": 2, + "column": 29 + } + }, + "range": [ + 60, + 63 + ], + "body": [] + } + }, + "leadingComments": [ + { + "type": "Line", + "value": " This comment should be ignored", + "start": 0, + "end": 33, + "range": [ + 0, + 33 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 33 + } + } + } + ] +} \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/default-variable/code.js b/packages/docgen/src/test/fixtures/default-variable/code.js new file mode 100644 index 0000000000000..2e1a56238583f --- /dev/null +++ b/packages/docgen/src/test/fixtures/default-variable/code.js @@ -0,0 +1,4 @@ +/** + * Variable declaration example. + */ +export default true; diff --git a/packages/docgen/src/test/fixtures/default-variable/exports.json b/packages/docgen/src/test/fixtures/default-variable/exports.json new file mode 100644 index 0000000000000..e6204ba061364 --- /dev/null +++ b/packages/docgen/src/test/fixtures/default-variable/exports.json @@ -0,0 +1,62 @@ +{ + "type": "ExportDefaultDeclaration", + "start": 41, + "end": 61, + "loc": { + "start": { + "line": 4, + "column": 0 + }, + "end": { + "line": 4, + "column": 20 + } + }, + "range": [ + 41, + 61 + ], + "declaration": { + "type": "Literal", + "start": 56, + "end": 60, + "loc": { + "start": { + "line": 4, + "column": 15 + }, + "end": { + "line": 4, + "column": 19 + } + }, + "range": [ + 56, + 60 + ], + "value": true, + "raw": "true" + }, + "leadingComments": [ + { + "type": "Block", + "value": "*\n * Variable declaration example.\n ", + "start": 0, + "end": 40, + "range": [ + 0, + 40 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 3 + } + } + } + ] +} \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/markdown/code.js b/packages/docgen/src/test/fixtures/markdown/code.js new file mode 100644 index 0000000000000..8d4261e0f6328 --- /dev/null +++ b/packages/docgen/src/test/fixtures/markdown/code.js @@ -0,0 +1,24 @@ +/** + * A function that adds two parameters. + * + * @deprecated Use native addition instead. + * @since v2 + * + * @see addition + * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators + * + * @param {number} firstParam The first param to add. + * @param {number} secondParam The second param to add. + * + * @example + * + * ```js + * const addResult = sum( 1, 3 ); + * console.log( addResult ); // will yield 4 + * ``` + * + * @return {number} The result of adding the two params. + */ +export const sum = ( firstParam, secondParam ) => { + return firstParam + secondParam; +}; diff --git a/packages/docgen/src/test/fixtures/markdown/docs.md b/packages/docgen/src/test/fixtures/markdown/docs.md new file mode 100644 index 0000000000000..12baa9046d028 --- /dev/null +++ b/packages/docgen/src/test/fixtures/markdown/docs.md @@ -0,0 +1,41 @@ +# Package docs + +This is some package docs. + +## API Docs + +<!-- START TOKEN(Autogenerated API docs) --> + +### sum + +[code.js#L22-L24](code.js#L22-L24) + +> **Deprecated** Use native addition instead. + +A function that adds two parameters. + +**Related** + +- addition +- <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators> + +**Usage** + +```js +const addResult = sum( 1, 3 ); +console.log( addResult ); // will yield 4 +``` + +**Parameters** + +- **firstParam** `number`: The first param to add. +- **secondParam** `number`: The second param to add. + +**Returns** + +`number` The result of adding the two params. + + +<!-- END TOKEN(Autogenerated API docs) --> + +After token content. diff --git a/packages/docgen/src/test/fixtures/named-class/code.js b/packages/docgen/src/test/fixtures/named-class/code.js new file mode 100644 index 0000000000000..8310ea0c9f7f1 --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-class/code.js @@ -0,0 +1,4 @@ +/** + * My declaration example. + */ +export class MyDeclaration {} diff --git a/packages/docgen/src/test/fixtures/named-class/exports.json b/packages/docgen/src/test/fixtures/named-class/exports.json new file mode 100644 index 0000000000000..e6da37a8974a0 --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-class/exports.json @@ -0,0 +1,103 @@ +{ + "type": "ExportNamedDeclaration", + "start": 35, + "end": 64, + "loc": { + "start": { + "line": 4, + "column": 0 + }, + "end": { + "line": 4, + "column": 29 + } + }, + "range": [ + 35, + 64 + ], + "declaration": { + "type": "ClassDeclaration", + "start": 42, + "end": 64, + "loc": { + "start": { + "line": 4, + "column": 7 + }, + "end": { + "line": 4, + "column": 29 + } + }, + "range": [ + 42, + 64 + ], + "id": { + "type": "Identifier", + "start": 48, + "end": 61, + "loc": { + "start": { + "line": 4, + "column": 13 + }, + "end": { + "line": 4, + "column": 26 + } + }, + "range": [ + 48, + 61 + ], + "name": "MyDeclaration" + }, + "superClass": null, + "body": { + "type": "ClassBody", + "start": 62, + "end": 64, + "loc": { + "start": { + "line": 4, + "column": 27 + }, + "end": { + "line": 4, + "column": 29 + } + }, + "range": [ + 62, + 64 + ], + "body": [] + } + }, + "specifiers": [], + "source": null, + "leadingComments": [ + { + "type": "Block", + "value": "*\n * My declaration example.\n ", + "start": 0, + "end": 34, + "range": [ + 0, + 34 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 3 + } + } + } + ] +} \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/named-default-exported/code.js b/packages/docgen/src/test/fixtures/named-default-exported/code.js new file mode 100644 index 0000000000000..7aebc3a91fa1c --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-default-exported/code.js @@ -0,0 +1 @@ +export { default as moduleName } from './named-default-module'; diff --git a/packages/docgen/src/test/fixtures/named-default-exported/exports.json b/packages/docgen/src/test/fixtures/named-default-exported/exports.json new file mode 100644 index 0000000000000..5cfbfc1661581 --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-default-exported/exports.json @@ -0,0 +1,102 @@ +{ + "type": "ExportNamedDeclaration", + "start": 0, + "end": 63, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 63 + } + }, + "range": [ + 0, + 63 + ], + "declaration": null, + "specifiers": [ + { + "type": "ExportSpecifier", + "start": 9, + "end": 30, + "loc": { + "start": { + "line": 1, + "column": 9 + }, + "end": { + "line": 1, + "column": 30 + } + }, + "range": [ + 9, + 30 + ], + "local": { + "type": "Identifier", + "start": 9, + "end": 16, + "loc": { + "start": { + "line": 1, + "column": 9 + }, + "end": { + "line": 1, + "column": 16 + } + }, + "range": [ + 9, + 16 + ], + "name": "default" + }, + "exported": { + "type": "Identifier", + "start": 20, + "end": 30, + "loc": { + "start": { + "line": 1, + "column": 20 + }, + "end": { + "line": 1, + "column": 30 + } + }, + "range": [ + 20, + 30 + ], + "name": "moduleName" + } + } + ], + "source": { + "type": "Literal", + "start": 38, + "end": 62, + "loc": { + "start": { + "line": 1, + "column": 38 + }, + "end": { + "line": 1, + "column": 62 + } + }, + "range": [ + 38, + 62 + ], + "value": "./named-default-module", + "raw": "'./named-default-module'" + } +} \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/named-default-exported/module-code.js b/packages/docgen/src/test/fixtures/named-default-exported/module-code.js new file mode 100644 index 0000000000000..92127f1f85059 --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-default-exported/module-code.js @@ -0,0 +1,4 @@ +/** + * Module declaration. + */ +export default function( ) {} diff --git a/packages/docgen/src/test/fixtures/named-default-exported/module-ir.json b/packages/docgen/src/test/fixtures/named-default-exported/module-ir.json new file mode 100644 index 0000000000000..9e9b2ce30509e --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-default-exported/module-ir.json @@ -0,0 +1,5 @@ +[{ + "name": "default", + "description": "Module declaration.", + "tags": [] +}] \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/named-default/code.js b/packages/docgen/src/test/fixtures/named-default/code.js new file mode 100644 index 0000000000000..441f6e366117f --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-default/code.js @@ -0,0 +1 @@ +export { default } from './module-code'; diff --git a/packages/docgen/src/test/fixtures/named-default/exports.json b/packages/docgen/src/test/fixtures/named-default/exports.json new file mode 100644 index 0000000000000..fd3a3c79d6a8b --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-default/exports.json @@ -0,0 +1,102 @@ +{ + "type": "ExportNamedDeclaration", + "start": 0, + "end": 49, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 49 + } + }, + "range": [ + 0, + 49 + ], + "declaration": null, + "specifiers": [ + { + "type": "ExportSpecifier", + "start": 9, + "end": 16, + "loc": { + "start": { + "line": 1, + "column": 9 + }, + "end": { + "line": 1, + "column": 16 + } + }, + "range": [ + 9, + 16 + ], + "local": { + "type": "Identifier", + "start": 9, + "end": 16, + "loc": { + "start": { + "line": 1, + "column": 9 + }, + "end": { + "line": 1, + "column": 16 + } + }, + "range": [ + 9, + 16 + ], + "name": "default" + }, + "exported": { + "type": "Identifier", + "start": 9, + "end": 16, + "loc": { + "start": { + "line": 1, + "column": 9 + }, + "end": { + "line": 1, + "column": 16 + } + }, + "range": [ + 9, + 16 + ], + "name": "default" + } + } + ], + "source": { + "type": "Literal", + "start": 24, + "end": 48, + "loc": { + "start": { + "line": 1, + "column": 24 + }, + "end": { + "line": 1, + "column": 48 + } + }, + "range": [ + 24, + 48 + ], + "value": "./named-default-module", + "raw": "'./named-default-module'" + } +} \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/named-default/module-code.js b/packages/docgen/src/test/fixtures/named-default/module-code.js new file mode 100644 index 0000000000000..92127f1f85059 --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-default/module-code.js @@ -0,0 +1,4 @@ +/** + * Module declaration. + */ +export default function( ) {} diff --git a/packages/docgen/src/test/fixtures/named-default/module-ir.json b/packages/docgen/src/test/fixtures/named-default/module-ir.json new file mode 100644 index 0000000000000..9e9b2ce30509e --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-default/module-ir.json @@ -0,0 +1,5 @@ +[{ + "name": "default", + "description": "Module declaration.", + "tags": [] +}] \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/named-function/code.js b/packages/docgen/src/test/fixtures/named-function/code.js new file mode 100644 index 0000000000000..eadb279918d04 --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-function/code.js @@ -0,0 +1,4 @@ +/** + * My declaration example. + */ +export function myDeclaration() {} diff --git a/packages/docgen/src/test/fixtures/named-function/exports.json b/packages/docgen/src/test/fixtures/named-function/exports.json new file mode 100644 index 0000000000000..8de0b612c6af6 --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-function/exports.json @@ -0,0 +1,106 @@ +{ + "type": "ExportNamedDeclaration", + "start": 35, + "end": 69, + "loc": { + "start": { + "line": 4, + "column": 0 + }, + "end": { + "line": 4, + "column": 34 + } + }, + "range": [ + 35, + 69 + ], + "declaration": { + "type": "FunctionDeclaration", + "start": 42, + "end": 69, + "loc": { + "start": { + "line": 4, + "column": 7 + }, + "end": { + "line": 4, + "column": 34 + } + }, + "range": [ + 42, + 69 + ], + "id": { + "type": "Identifier", + "start": 51, + "end": 64, + "loc": { + "start": { + "line": 4, + "column": 16 + }, + "end": { + "line": 4, + "column": 29 + } + }, + "range": [ + 51, + 64 + ], + "name": "myDeclaration" + }, + "generator": false, + "expression": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start": 67, + "end": 69, + "loc": { + "start": { + "line": 4, + "column": 32 + }, + "end": { + "line": 4, + "column": 34 + } + }, + "range": [ + 67, + 69 + ], + "body": [] + } + }, + "specifiers": [], + "source": null, + "leadingComments": [ + { + "type": "Block", + "value": "*\n * My declaration example.\n ", + "start": 0, + "end": 34, + "range": [ + 0, + 34 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 3 + } + } + } + ] +} \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/named-function/ir.json b/packages/docgen/src/test/fixtures/named-function/ir.json new file mode 100644 index 0000000000000..a9d9ab4175c9c --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-function/ir.json @@ -0,0 +1,7 @@ +[ + { + "name": "myDeclaration", + "description": "My declaration example.", + "tags": [] + } +] \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/named-identifier-destructuring/ast.json b/packages/docgen/src/test/fixtures/named-identifier-destructuring/ast.json new file mode 100644 index 0000000000000..9c17f5ad7401d --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-identifier-destructuring/ast.json @@ -0,0 +1,381 @@ +{ + "type": "Program", + "start": 0, + "end": 141, + "loc": { + "start": { + "line": 4, + "column": 0 + }, + "end": { + "line": 6, + "column": 44 + } + }, + "range": [ + 35, + 140 + ], + "body": [ + { + "type": "VariableDeclaration", + "start": 35, + "end": 94, + "loc": { + "start": { + "line": 4, + "column": 0 + }, + "end": { + "line": 4, + "column": 59 + } + }, + "range": [ + 35, + 94 + ], + "declarations": [ + { + "type": "VariableDeclarator", + "start": 41, + "end": 93, + "loc": { + "start": { + "line": 4, + "column": 6 + }, + "end": { + "line": 4, + "column": 58 + } + }, + "range": [ + 41, + 93 + ], + "id": { + "type": "ObjectPattern", + "start": 41, + "end": 60, + "loc": { + "start": { + "line": 4, + "column": 6 + }, + "end": { + "line": 4, + "column": 25 + } + }, + "range": [ + 41, + 60 + ], + "properties": [ + { + "type": "Property", + "start": 43, + "end": 58, + "loc": { + "start": { + "line": 4, + "column": 8 + }, + "end": { + "line": 4, + "column": 23 + } + }, + "range": [ + 43, + 58 + ], + "method": false, + "shorthand": true, + "computed": false, + "key": { + "type": "Identifier", + "start": 43, + "end": 58, + "loc": { + "start": { + "line": 4, + "column": 8 + }, + "end": { + "line": 4, + "column": 23 + } + }, + "range": [ + 43, + 58 + ], + "name": "someDeclaration" + }, + "kind": "init", + "value": { + "type": "Identifier", + "start": 43, + "end": 58, + "loc": { + "start": { + "line": 4, + "column": 8 + }, + "end": { + "line": 4, + "column": 23 + } + }, + "range": [ + 43, + 58 + ], + "name": "someDeclaration" + } + } + ] + }, + "init": { + "type": "ObjectExpression", + "start": 63, + "end": 93, + "loc": { + "start": { + "line": 4, + "column": 28 + }, + "end": { + "line": 4, + "column": 58 + } + }, + "range": [ + 63, + 93 + ], + "properties": [ + { + "type": "Property", + "start": 65, + "end": 91, + "loc": { + "start": { + "line": 4, + "column": 30 + }, + "end": { + "line": 4, + "column": 56 + } + }, + "range": [ + 65, + 91 + ], + "method": false, + "shorthand": false, + "computed": false, + "key": { + "type": "Identifier", + "start": 65, + "end": 80, + "loc": { + "start": { + "line": 4, + "column": 30 + }, + "end": { + "line": 4, + "column": 45 + } + }, + "range": [ + 65, + 80 + ], + "name": "someDeclaration" + }, + "value": { + "type": "ArrowFunctionExpression", + "start": 82, + "end": 91, + "loc": { + "start": { + "line": 4, + "column": 47 + }, + "end": { + "line": 4, + "column": 56 + } + }, + "range": [ + 82, + 91 + ], + "id": null, + "generator": false, + "expression": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start": 88, + "end": 91, + "loc": { + "start": { + "line": 4, + "column": 53 + }, + "end": { + "line": 4, + "column": 56 + } + }, + "range": [ + 88, + 91 + ], + "body": [] + } + }, + "kind": "init" + } + ] + } + } + ], + "kind": "const", + "leadingComments": [ + { + "type": "Block", + "value": "*\n * My declaration example.\n ", + "start": 0, + "end": 34, + "range": [ + 0, + 34 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 3 + } + } + } + ] + }, + { + "type": "ExportNamedDeclaration", + "start": 96, + "end": 140, + "loc": { + "start": { + "line": 6, + "column": 0 + }, + "end": { + "line": 6, + "column": 44 + } + }, + "range": [ + 96, + 140 + ], + "declaration": null, + "specifiers": [ + { + "type": "ExportSpecifier", + "start": 105, + "end": 137, + "loc": { + "start": { + "line": 6, + "column": 9 + }, + "end": { + "line": 6, + "column": 41 + } + }, + "range": [ + 105, + 137 + ], + "local": { + "type": "Identifier", + "start": 105, + "end": 120, + "loc": { + "start": { + "line": 6, + "column": 9 + }, + "end": { + "line": 6, + "column": 24 + } + }, + "range": [ + 105, + 120 + ], + "name": "someDeclaration" + }, + "exported": { + "type": "Identifier", + "start": 124, + "end": 137, + "loc": { + "start": { + "line": 6, + "column": 28 + }, + "end": { + "line": 6, + "column": 41 + } + }, + "range": [ + 124, + 137 + ], + "name": "myDeclaration" + } + } + ], + "source": null + } + ], + "sourceType": "module", + "comments": [ + { + "type": "Block", + "value": "*\n * My declaration example.\n ", + "start": 0, + "end": 34, + "range": [ + 0, + 34 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 3 + } + } + } + ] +} \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/named-identifier-destructuring/code.js b/packages/docgen/src/test/fixtures/named-identifier-destructuring/code.js new file mode 100644 index 0000000000000..d5e1b5d46160d --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-identifier-destructuring/code.js @@ -0,0 +1,6 @@ +/** + * My declaration example. + */ +const { someDeclaration } = { someDeclaration: () => { } }; + +export { someDeclaration as myDeclaration }; diff --git a/packages/docgen/src/test/fixtures/named-identifier-destructuring/exports.json b/packages/docgen/src/test/fixtures/named-identifier-destructuring/exports.json new file mode 100644 index 0000000000000..ac2695f1c68fa --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-identifier-destructuring/exports.json @@ -0,0 +1,82 @@ +{ + "type": "ExportNamedDeclaration", + "start": 96, + "end": 140, + "loc": { + "start": { + "line": 6, + "column": 0 + }, + "end": { + "line": 6, + "column": 44 + } + }, + "range": [ + 96, + 140 + ], + "declaration": null, + "specifiers": [ + { + "type": "ExportSpecifier", + "start": 105, + "end": 137, + "loc": { + "start": { + "line": 6, + "column": 9 + }, + "end": { + "line": 6, + "column": 41 + } + }, + "range": [ + 105, + 137 + ], + "local": { + "type": "Identifier", + "start": 105, + "end": 120, + "loc": { + "start": { + "line": 6, + "column": 9 + }, + "end": { + "line": 6, + "column": 24 + } + }, + "range": [ + 105, + 120 + ], + "name": "someDeclaration" + }, + "exported": { + "type": "Identifier", + "start": 124, + "end": 137, + "loc": { + "start": { + "line": 6, + "column": 28 + }, + "end": { + "line": 6, + "column": 41 + } + }, + "range": [ + 124, + 137 + ], + "name": "myDeclaration" + } + } + ], + "source": null +} \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/named-identifier/ast.json b/packages/docgen/src/test/fixtures/named-identifier/ast.json new file mode 100644 index 0000000000000..40eb2039185e3 --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-identifier/ast.json @@ -0,0 +1,211 @@ +{ + "type": "Program", + "start": 0, + "end": 90, + "loc": { + "start": { + "line": 4, + "column": 0 + }, + "end": { + "line": 6, + "column": 25 + } + }, + "range": [ + 35, + 89 + ], + "body": [ + { + "type": "FunctionDeclaration", + "start": 35, + "end": 62, + "loc": { + "start": { + "line": 4, + "column": 0 + }, + "end": { + "line": 4, + "column": 27 + } + }, + "range": [ + 35, + 62 + ], + "id": { + "type": "Identifier", + "start": 44, + "end": 57, + "loc": { + "start": { + "line": 4, + "column": 9 + }, + "end": { + "line": 4, + "column": 22 + } + }, + "range": [ + 44, + 57 + ], + "name": "myDeclaration" + }, + "generator": false, + "expression": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start": 60, + "end": 62, + "loc": { + "start": { + "line": 4, + "column": 25 + }, + "end": { + "line": 4, + "column": 27 + } + }, + "range": [ + 60, + 62 + ], + "body": [] + }, + "leadingComments": [ + { + "type": "Block", + "value": "*\n * My declaration example.\n ", + "start": 0, + "end": 34, + "range": [ + 0, + 34 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 3 + } + } + } + ] + }, + { + "type": "ExportNamedDeclaration", + "start": 64, + "end": 89, + "loc": { + "start": { + "line": 6, + "column": 0 + }, + "end": { + "line": 6, + "column": 25 + } + }, + "range": [ + 64, + 89 + ], + "declaration": null, + "specifiers": [ + { + "type": "ExportSpecifier", + "start": 73, + "end": 86, + "loc": { + "start": { + "line": 6, + "column": 9 + }, + "end": { + "line": 6, + "column": 22 + } + }, + "range": [ + 73, + 86 + ], + "local": { + "type": "Identifier", + "start": 73, + "end": 86, + "loc": { + "start": { + "line": 6, + "column": 9 + }, + "end": { + "line": 6, + "column": 22 + } + }, + "range": [ + 73, + 86 + ], + "name": "myDeclaration" + }, + "exported": { + "type": "Identifier", + "start": 73, + "end": 86, + "loc": { + "start": { + "line": 6, + "column": 9 + }, + "end": { + "line": 6, + "column": 22 + } + }, + "range": [ + 73, + 86 + ], + "name": "myDeclaration" + } + } + ], + "source": null + } + ], + "sourceType": "module", + "comments": [ + { + "type": "Block", + "value": "*\n * My declaration example.\n ", + "start": 0, + "end": 34, + "range": [ + 0, + 34 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 3 + } + } + } + ] +} \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/named-identifier/code.js b/packages/docgen/src/test/fixtures/named-identifier/code.js new file mode 100644 index 0000000000000..8fbd063086c19 --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-identifier/code.js @@ -0,0 +1,6 @@ +/** + * My declaration example. + */ +function myDeclaration() {} + +export { myDeclaration }; diff --git a/packages/docgen/src/test/fixtures/named-identifier/exports.json b/packages/docgen/src/test/fixtures/named-identifier/exports.json new file mode 100644 index 0000000000000..3c2c7680042cf --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-identifier/exports.json @@ -0,0 +1,82 @@ +{ + "type": "ExportNamedDeclaration", + "start": 64, + "end": 89, + "loc": { + "start": { + "line": 6, + "column": 0 + }, + "end": { + "line": 6, + "column": 25 + } + }, + "range": [ + 64, + 89 + ], + "declaration": null, + "specifiers": [ + { + "type": "ExportSpecifier", + "start": 73, + "end": 86, + "loc": { + "start": { + "line": 6, + "column": 9 + }, + "end": { + "line": 6, + "column": 22 + } + }, + "range": [ + 73, + 86 + ], + "local": { + "type": "Identifier", + "start": 73, + "end": 86, + "loc": { + "start": { + "line": 6, + "column": 9 + }, + "end": { + "line": 6, + "column": 22 + } + }, + "range": [ + 73, + 86 + ], + "name": "myDeclaration" + }, + "exported": { + "type": "Identifier", + "start": 73, + "end": 86, + "loc": { + "start": { + "line": 6, + "column": 9 + }, + "end": { + "line": 6, + "column": 22 + } + }, + "range": [ + 73, + 86 + ], + "name": "myDeclaration" + } + } + ], + "source": null +} \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/named-identifiers-and-inline/ast.json b/packages/docgen/src/test/fixtures/named-identifiers-and-inline/ast.json new file mode 100644 index 0000000000000..d761a6035d6c2 --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-identifiers-and-inline/ast.json @@ -0,0 +1,561 @@ +{ + "type": "Program", + "start": 0, + "end": 275, + "loc": { + "start": { + "line": 4, + "column": 0 + }, + "end": { + "line": 16, + "column": 40 + } + }, + "range": [ + 41, + 273 + ], + "body": [ + { + "type": "FunctionDeclaration", + "start": 41, + "end": 74, + "loc": { + "start": { + "line": 4, + "column": 0 + }, + "end": { + "line": 4, + "column": 33 + } + }, + "range": [ + 41, + 74 + ], + "id": { + "type": "Identifier", + "start": 50, + "end": 69, + "loc": { + "start": { + "line": 4, + "column": 9 + }, + "end": { + "line": 4, + "column": 28 + } + }, + "range": [ + 50, + 69 + ], + "name": "functionDeclaration" + }, + "generator": false, + "expression": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start": 72, + "end": 74, + "loc": { + "start": { + "line": 4, + "column": 31 + }, + "end": { + "line": 4, + "column": 33 + } + }, + "range": [ + 72, + 74 + ], + "body": [] + }, + "leadingComments": [ + { + "type": "Block", + "value": "*\n * Function declaration example.\n ", + "start": 0, + "end": 40, + "range": [ + 0, + 40 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 3 + } + } + } + ], + "trailingComments": [ + { + "type": "Block", + "value": "*\n * Class declaration example.\n ", + "start": 76, + "end": 113, + "range": [ + 76, + 113 + ], + "loc": { + "start": { + "line": 6, + "column": 0 + }, + "end": { + "line": 8, + "column": 3 + } + } + } + ] + }, + { + "type": "ClassDeclaration", + "start": 114, + "end": 139, + "loc": { + "start": { + "line": 9, + "column": 0 + }, + "end": { + "line": 9, + "column": 25 + } + }, + "range": [ + 114, + 139 + ], + "id": { + "type": "Identifier", + "start": 120, + "end": 136, + "loc": { + "start": { + "line": 9, + "column": 6 + }, + "end": { + "line": 9, + "column": 22 + } + }, + "range": [ + 120, + 136 + ], + "name": "ClassDeclaration" + }, + "superClass": null, + "body": { + "type": "ClassBody", + "start": 137, + "end": 139, + "loc": { + "start": { + "line": 9, + "column": 23 + }, + "end": { + "line": 9, + "column": 25 + } + }, + "range": [ + 137, + 139 + ], + "body": [] + }, + "leadingComments": [ + { + "type": "Block", + "value": "*\n * Class declaration example.\n ", + "start": 76, + "end": 113, + "range": [ + 76, + 113 + ], + "loc": { + "start": { + "line": 6, + "column": 0 + }, + "end": { + "line": 8, + "column": 3 + } + } + } + ] + }, + { + "type": "ExportNamedDeclaration", + "start": 141, + "end": 190, + "loc": { + "start": { + "line": 11, + "column": 0 + }, + "end": { + "line": 11, + "column": 49 + } + }, + "range": [ + 141, + 190 + ], + "declaration": null, + "specifiers": [ + { + "type": "ExportSpecifier", + "start": 150, + "end": 169, + "loc": { + "start": { + "line": 11, + "column": 9 + }, + "end": { + "line": 11, + "column": 28 + } + }, + "range": [ + 150, + 169 + ], + "local": { + "type": "Identifier", + "start": 150, + "end": 169, + "loc": { + "start": { + "line": 11, + "column": 9 + }, + "end": { + "line": 11, + "column": 28 + } + }, + "range": [ + 150, + 169 + ], + "name": "functionDeclaration" + }, + "exported": { + "type": "Identifier", + "start": 150, + "end": 169, + "loc": { + "start": { + "line": 11, + "column": 9 + }, + "end": { + "line": 11, + "column": 28 + } + }, + "range": [ + 150, + 169 + ], + "name": "functionDeclaration" + } + }, + { + "type": "ExportSpecifier", + "start": 171, + "end": 187, + "loc": { + "start": { + "line": 11, + "column": 30 + }, + "end": { + "line": 11, + "column": 46 + } + }, + "range": [ + 171, + 187 + ], + "local": { + "type": "Identifier", + "start": 171, + "end": 187, + "loc": { + "start": { + "line": 11, + "column": 30 + }, + "end": { + "line": 11, + "column": 46 + } + }, + "range": [ + 171, + 187 + ], + "name": "ClassDeclaration" + }, + "exported": { + "type": "Identifier", + "start": 171, + "end": 187, + "loc": { + "start": { + "line": 11, + "column": 30 + }, + "end": { + "line": 11, + "column": 46 + } + }, + "range": [ + 171, + 187 + ], + "name": "ClassDeclaration" + } + } + ], + "source": null, + "trailingComments": [ + { + "type": "Block", + "value": "*\n * Variable declaration example.\n ", + "start": 192, + "end": 232, + "range": [ + 192, + 232 + ], + "loc": { + "start": { + "line": 13, + "column": 0 + }, + "end": { + "line": 15, + "column": 3 + } + } + } + ] + }, + { + "type": "ExportNamedDeclaration", + "start": 233, + "end": 273, + "loc": { + "start": { + "line": 16, + "column": 0 + }, + "end": { + "line": 16, + "column": 40 + } + }, + "range": [ + 233, + 273 + ], + "declaration": { + "type": "VariableDeclaration", + "start": 240, + "end": 273, + "loc": { + "start": { + "line": 16, + "column": 7 + }, + "end": { + "line": 16, + "column": 40 + } + }, + "range": [ + 240, + 273 + ], + "declarations": [ + { + "type": "VariableDeclarator", + "start": 246, + "end": 272, + "loc": { + "start": { + "line": 16, + "column": 13 + }, + "end": { + "line": 16, + "column": 39 + } + }, + "range": [ + 246, + 272 + ], + "id": { + "type": "Identifier", + "start": 246, + "end": 265, + "loc": { + "start": { + "line": 16, + "column": 13 + }, + "end": { + "line": 16, + "column": 32 + } + }, + "range": [ + 246, + 265 + ], + "name": "variableDeclaration" + }, + "init": { + "type": "Literal", + "start": 268, + "end": 272, + "loc": { + "start": { + "line": 16, + "column": 35 + }, + "end": { + "line": 16, + "column": 39 + } + }, + "range": [ + 268, + 272 + ], + "value": true, + "raw": "true" + } + } + ], + "kind": "const" + }, + "specifiers": [], + "source": null, + "leadingComments": [ + { + "type": "Block", + "value": "*\n * Variable declaration example.\n ", + "start": 192, + "end": 232, + "range": [ + 192, + 232 + ], + "loc": { + "start": { + "line": 13, + "column": 0 + }, + "end": { + "line": 15, + "column": 3 + } + } + } + ] + } + ], + "sourceType": "module", + "comments": [ + { + "type": "Block", + "value": "*\n * Function declaration example.\n ", + "start": 0, + "end": 40, + "range": [ + 0, + 40 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 3 + } + } + }, + { + "type": "Block", + "value": "*\n * Class declaration example.\n ", + "start": 76, + "end": 113, + "range": [ + 76, + 113 + ], + "loc": { + "start": { + "line": 6, + "column": 0 + }, + "end": { + "line": 8, + "column": 3 + } + } + }, + { + "type": "Block", + "value": "*\n * Variable declaration example.\n ", + "start": 192, + "end": 232, + "range": [ + 192, + 232 + ], + "loc": { + "start": { + "line": 13, + "column": 0 + }, + "end": { + "line": 15, + "column": 3 + } + } + } + ] +} \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/named-identifiers-and-inline/code.js b/packages/docgen/src/test/fixtures/named-identifiers-and-inline/code.js new file mode 100644 index 0000000000000..10b836d597856 --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-identifiers-and-inline/code.js @@ -0,0 +1,17 @@ +/** + * Function declaration example. + */ +function functionDeclaration() {} + +/** + * Class declaration example. + */ +class ClassDeclaration {} + +export { functionDeclaration, ClassDeclaration }; + +/** + * Variable declaration example. + */ +export const variableDeclaration = true; + diff --git a/packages/docgen/src/test/fixtures/named-identifiers-and-inline/exports.json b/packages/docgen/src/test/fixtures/named-identifiers-and-inline/exports.json new file mode 100644 index 0000000000000..960e07f08fd7c --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-identifiers-and-inline/exports.json @@ -0,0 +1,290 @@ +[ + { + "type": "ExportNamedDeclaration", + "start": 141, + "end": 190, + "loc": { + "start": { + "line": 11, + "column": 0 + }, + "end": { + "line": 11, + "column": 49 + } + }, + "range": [ + 141, + 190 + ], + "declaration": null, + "specifiers": [ + { + "type": "ExportSpecifier", + "start": 150, + "end": 169, + "loc": { + "start": { + "line": 11, + "column": 9 + }, + "end": { + "line": 11, + "column": 28 + } + }, + "range": [ + 150, + 169 + ], + "local": { + "type": "Identifier", + "start": 150, + "end": 169, + "loc": { + "start": { + "line": 11, + "column": 9 + }, + "end": { + "line": 11, + "column": 28 + } + }, + "range": [ + 150, + 169 + ], + "name": "functionDeclaration" + }, + "exported": { + "type": "Identifier", + "start": 150, + "end": 169, + "loc": { + "start": { + "line": 11, + "column": 9 + }, + "end": { + "line": 11, + "column": 28 + } + }, + "range": [ + 150, + 169 + ], + "name": "functionDeclaration" + } + }, + { + "type": "ExportSpecifier", + "start": 171, + "end": 187, + "loc": { + "start": { + "line": 11, + "column": 30 + }, + "end": { + "line": 11, + "column": 46 + } + }, + "range": [ + 171, + 187 + ], + "local": { + "type": "Identifier", + "start": 171, + "end": 187, + "loc": { + "start": { + "line": 11, + "column": 30 + }, + "end": { + "line": 11, + "column": 46 + } + }, + "range": [ + 171, + 187 + ], + "name": "ClassDeclaration" + }, + "exported": { + "type": "Identifier", + "start": 171, + "end": 187, + "loc": { + "start": { + "line": 11, + "column": 30 + }, + "end": { + "line": 11, + "column": 46 + } + }, + "range": [ + 171, + 187 + ], + "name": "ClassDeclaration" + } + } + ], + "source": null, + "trailingComments": [ + { + "type": "Block", + "value": "*\n * Variable declaration example.\n ", + "start": 192, + "end": 232, + "range": [ + 192, + 232 + ], + "loc": { + "start": { + "line": 13, + "column": 0 + }, + "end": { + "line": 15, + "column": 3 + } + } + } + ] + }, + { + "type": "ExportNamedDeclaration", + "start": 233, + "end": 273, + "loc": { + "start": { + "line": 16, + "column": 0 + }, + "end": { + "line": 16, + "column": 40 + } + }, + "range": [ + 233, + 273 + ], + "declaration": { + "type": "VariableDeclaration", + "start": 240, + "end": 273, + "loc": { + "start": { + "line": 16, + "column": 7 + }, + "end": { + "line": 16, + "column": 40 + } + }, + "range": [ + 240, + 273 + ], + "declarations": [ + { + "type": "VariableDeclarator", + "start": 246, + "end": 272, + "loc": { + "start": { + "line": 16, + "column": 13 + }, + "end": { + "line": 16, + "column": 39 + } + }, + "range": [ + 246, + 272 + ], + "id": { + "type": "Identifier", + "start": 246, + "end": 265, + "loc": { + "start": { + "line": 16, + "column": 13 + }, + "end": { + "line": 16, + "column": 32 + } + }, + "range": [ + 246, + 265 + ], + "name": "variableDeclaration" + }, + "init": { + "type": "Literal", + "start": 268, + "end": 272, + "loc": { + "start": { + "line": 16, + "column": 35 + }, + "end": { + "line": 16, + "column": 39 + } + }, + "range": [ + 268, + 272 + ], + "value": true, + "raw": "true" + } + } + ], + "kind": "const" + }, + "specifiers": [], + "source": null, + "leadingComments": [ + { + "type": "Block", + "value": "*\n * Variable declaration example.\n ", + "start": 192, + "end": 232, + "range": [ + 192, + 232 + ], + "loc": { + "start": { + "line": 13, + "column": 0 + }, + "end": { + "line": 15, + "column": 3 + } + } + } + ] + } +] \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/named-identifiers/ast.json b/packages/docgen/src/test/fixtures/named-identifiers/ast.json new file mode 100644 index 0000000000000..f6aa5b92541ab --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-identifiers/ast.json @@ -0,0 +1,599 @@ +{ + "type": "Program", + "start": 0, + "end": 288, + "loc": { + "start": { + "line": 4, + "column": 0 + }, + "end": { + "line": 16, + "column": 70 + } + }, + "range": [ + 41, + 287 + ], + "body": [ + { + "type": "FunctionDeclaration", + "start": 41, + "end": 74, + "loc": { + "start": { + "line": 4, + "column": 0 + }, + "end": { + "line": 4, + "column": 33 + } + }, + "range": [ + 41, + 74 + ], + "id": { + "type": "Identifier", + "start": 50, + "end": 69, + "loc": { + "start": { + "line": 4, + "column": 9 + }, + "end": { + "line": 4, + "column": 28 + } + }, + "range": [ + 50, + 69 + ], + "name": "functionDeclaration" + }, + "generator": false, + "expression": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start": 72, + "end": 74, + "loc": { + "start": { + "line": 4, + "column": 31 + }, + "end": { + "line": 4, + "column": 33 + } + }, + "range": [ + 72, + 74 + ], + "body": [] + }, + "leadingComments": [ + { + "type": "Block", + "value": "*\n * Function declaration example.\n ", + "start": 0, + "end": 40, + "range": [ + 0, + 40 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 3 + } + } + } + ], + "trailingComments": [ + { + "type": "Block", + "value": "*\n * Class declaration example.\n ", + "start": 76, + "end": 113, + "range": [ + 76, + 113 + ], + "loc": { + "start": { + "line": 6, + "column": 0 + }, + "end": { + "line": 8, + "column": 3 + } + } + } + ] + }, + { + "type": "ClassDeclaration", + "start": 114, + "end": 139, + "loc": { + "start": { + "line": 9, + "column": 0 + }, + "end": { + "line": 9, + "column": 25 + } + }, + "range": [ + 114, + 139 + ], + "id": { + "type": "Identifier", + "start": 120, + "end": 136, + "loc": { + "start": { + "line": 9, + "column": 6 + }, + "end": { + "line": 9, + "column": 22 + } + }, + "range": [ + 120, + 136 + ], + "name": "ClassDeclaration" + }, + "superClass": null, + "body": { + "type": "ClassBody", + "start": 137, + "end": 139, + "loc": { + "start": { + "line": 9, + "column": 23 + }, + "end": { + "line": 9, + "column": 25 + } + }, + "range": [ + 137, + 139 + ], + "body": [] + }, + "leadingComments": [ + { + "type": "Block", + "value": "*\n * Class declaration example.\n ", + "start": 76, + "end": 113, + "range": [ + 76, + 113 + ], + "loc": { + "start": { + "line": 6, + "column": 0 + }, + "end": { + "line": 8, + "column": 3 + } + } + } + ], + "trailingComments": [ + { + "type": "Block", + "value": "*\n * Variable declaration example.\n ", + "start": 141, + "end": 181, + "range": [ + 141, + 181 + ], + "loc": { + "start": { + "line": 11, + "column": 0 + }, + "end": { + "line": 13, + "column": 3 + } + } + } + ] + }, + { + "type": "VariableDeclaration", + "start": 182, + "end": 215, + "loc": { + "start": { + "line": 14, + "column": 0 + }, + "end": { + "line": 14, + "column": 33 + } + }, + "range": [ + 182, + 215 + ], + "declarations": [ + { + "type": "VariableDeclarator", + "start": 188, + "end": 214, + "loc": { + "start": { + "line": 14, + "column": 6 + }, + "end": { + "line": 14, + "column": 32 + } + }, + "range": [ + 188, + 214 + ], + "id": { + "type": "Identifier", + "start": 188, + "end": 207, + "loc": { + "start": { + "line": 14, + "column": 6 + }, + "end": { + "line": 14, + "column": 25 + } + }, + "range": [ + 188, + 207 + ], + "name": "variableDeclaration" + }, + "init": { + "type": "Literal", + "start": 210, + "end": 214, + "loc": { + "start": { + "line": 14, + "column": 28 + }, + "end": { + "line": 14, + "column": 32 + } + }, + "range": [ + 210, + 214 + ], + "value": true, + "raw": "true" + } + } + ], + "kind": "const", + "leadingComments": [ + { + "type": "Block", + "value": "*\n * Variable declaration example.\n ", + "start": 141, + "end": 181, + "range": [ + 141, + 181 + ], + "loc": { + "start": { + "line": 11, + "column": 0 + }, + "end": { + "line": 13, + "column": 3 + } + } + } + ] + }, + { + "type": "ExportNamedDeclaration", + "start": 217, + "end": 287, + "loc": { + "start": { + "line": 16, + "column": 0 + }, + "end": { + "line": 16, + "column": 70 + } + }, + "range": [ + 217, + 287 + ], + "declaration": null, + "specifiers": [ + { + "type": "ExportSpecifier", + "start": 226, + "end": 245, + "loc": { + "start": { + "line": 16, + "column": 9 + }, + "end": { + "line": 16, + "column": 28 + } + }, + "range": [ + 226, + 245 + ], + "local": { + "type": "Identifier", + "start": 226, + "end": 245, + "loc": { + "start": { + "line": 16, + "column": 9 + }, + "end": { + "line": 16, + "column": 28 + } + }, + "range": [ + 226, + 245 + ], + "name": "functionDeclaration" + }, + "exported": { + "type": "Identifier", + "start": 226, + "end": 245, + "loc": { + "start": { + "line": 16, + "column": 9 + }, + "end": { + "line": 16, + "column": 28 + } + }, + "range": [ + 226, + 245 + ], + "name": "functionDeclaration" + } + }, + { + "type": "ExportSpecifier", + "start": 247, + "end": 266, + "loc": { + "start": { + "line": 16, + "column": 30 + }, + "end": { + "line": 16, + "column": 49 + } + }, + "range": [ + 247, + 266 + ], + "local": { + "type": "Identifier", + "start": 247, + "end": 266, + "loc": { + "start": { + "line": 16, + "column": 30 + }, + "end": { + "line": 16, + "column": 49 + } + }, + "range": [ + 247, + 266 + ], + "name": "variableDeclaration" + }, + "exported": { + "type": "Identifier", + "start": 247, + "end": 266, + "loc": { + "start": { + "line": 16, + "column": 30 + }, + "end": { + "line": 16, + "column": 49 + } + }, + "range": [ + 247, + 266 + ], + "name": "variableDeclaration" + } + }, + { + "type": "ExportSpecifier", + "start": 268, + "end": 284, + "loc": { + "start": { + "line": 16, + "column": 51 + }, + "end": { + "line": 16, + "column": 67 + } + }, + "range": [ + 268, + 284 + ], + "local": { + "type": "Identifier", + "start": 268, + "end": 284, + "loc": { + "start": { + "line": 16, + "column": 51 + }, + "end": { + "line": 16, + "column": 67 + } + }, + "range": [ + 268, + 284 + ], + "name": "ClassDeclaration" + }, + "exported": { + "type": "Identifier", + "start": 268, + "end": 284, + "loc": { + "start": { + "line": 16, + "column": 51 + }, + "end": { + "line": 16, + "column": 67 + } + }, + "range": [ + 268, + 284 + ], + "name": "ClassDeclaration" + } + } + ], + "source": null + } + ], + "sourceType": "module", + "comments": [ + { + "type": "Block", + "value": "*\n * Function declaration example.\n ", + "start": 0, + "end": 40, + "range": [ + 0, + 40 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 3 + } + } + }, + { + "type": "Block", + "value": "*\n * Class declaration example.\n ", + "start": 76, + "end": 113, + "range": [ + 76, + 113 + ], + "loc": { + "start": { + "line": 6, + "column": 0 + }, + "end": { + "line": 8, + "column": 3 + } + } + }, + { + "type": "Block", + "value": "*\n * Variable declaration example.\n ", + "start": 141, + "end": 181, + "range": [ + 141, + 181 + ], + "loc": { + "start": { + "line": 11, + "column": 0 + }, + "end": { + "line": 13, + "column": 3 + } + } + } + ] +} \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/named-identifiers/code.js b/packages/docgen/src/test/fixtures/named-identifiers/code.js new file mode 100644 index 0000000000000..6c608a4eea4e7 --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-identifiers/code.js @@ -0,0 +1,16 @@ +/** + * Function declaration example. + */ +function functionDeclaration() {} + +/** + * Class declaration example. + */ +class ClassDeclaration {} + +/** + * Variable declaration example. + */ +const variableDeclaration = true; + +export { functionDeclaration, variableDeclaration, ClassDeclaration }; diff --git a/packages/docgen/src/test/fixtures/named-identifiers/exports.json b/packages/docgen/src/test/fixtures/named-identifiers/exports.json new file mode 100644 index 0000000000000..eb1e017e311b4 --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-identifiers/exports.json @@ -0,0 +1,200 @@ +{ + "type": "ExportNamedDeclaration", + "start": 217, + "end": 287, + "loc": { + "start": { + "line": 16, + "column": 0 + }, + "end": { + "line": 16, + "column": 70 + } + }, + "range": [ + 217, + 287 + ], + "declaration": null, + "specifiers": [ + { + "type": "ExportSpecifier", + "start": 226, + "end": 245, + "loc": { + "start": { + "line": 16, + "column": 9 + }, + "end": { + "line": 16, + "column": 28 + } + }, + "range": [ + 226, + 245 + ], + "local": { + "type": "Identifier", + "start": 226, + "end": 245, + "loc": { + "start": { + "line": 16, + "column": 9 + }, + "end": { + "line": 16, + "column": 28 + } + }, + "range": [ + 226, + 245 + ], + "name": "functionDeclaration" + }, + "exported": { + "type": "Identifier", + "start": 226, + "end": 245, + "loc": { + "start": { + "line": 16, + "column": 9 + }, + "end": { + "line": 16, + "column": 28 + } + }, + "range": [ + 226, + 245 + ], + "name": "functionDeclaration" + } + }, + { + "type": "ExportSpecifier", + "start": 247, + "end": 266, + "loc": { + "start": { + "line": 16, + "column": 30 + }, + "end": { + "line": 16, + "column": 49 + } + }, + "range": [ + 247, + 266 + ], + "local": { + "type": "Identifier", + "start": 247, + "end": 266, + "loc": { + "start": { + "line": 16, + "column": 30 + }, + "end": { + "line": 16, + "column": 49 + } + }, + "range": [ + 247, + 266 + ], + "name": "variableDeclaration" + }, + "exported": { + "type": "Identifier", + "start": 247, + "end": 266, + "loc": { + "start": { + "line": 16, + "column": 30 + }, + "end": { + "line": 16, + "column": 49 + } + }, + "range": [ + 247, + 266 + ], + "name": "variableDeclaration" + } + }, + { + "type": "ExportSpecifier", + "start": 268, + "end": 284, + "loc": { + "start": { + "line": 16, + "column": 51 + }, + "end": { + "line": 16, + "column": 67 + } + }, + "range": [ + 268, + 284 + ], + "local": { + "type": "Identifier", + "start": 268, + "end": 284, + "loc": { + "start": { + "line": 16, + "column": 51 + }, + "end": { + "line": 16, + "column": 67 + } + }, + "range": [ + 268, + 284 + ], + "name": "ClassDeclaration" + }, + "exported": { + "type": "Identifier", + "start": 268, + "end": 284, + "loc": { + "start": { + "line": 16, + "column": 51 + }, + "end": { + "line": 16, + "column": 67 + } + }, + "range": [ + 268, + 284 + ], + "name": "ClassDeclaration" + } + } + ], + "source": null +} \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/named-identifiers/ir.json b/packages/docgen/src/test/fixtures/named-identifiers/ir.json new file mode 100644 index 0000000000000..ab7e9787fa15f --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-identifiers/ir.json @@ -0,0 +1 @@ +[{"name":"ClassDeclaration","description":"Class declaration example.","tags":[]},{"name":"functionDeclaration","description":"Function declaration example.","tags":[]},{"name":"variableDeclaration","description":"Variable declaration example.","tags":[]}] \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/named-import-named/code.js b/packages/docgen/src/test/fixtures/named-import-named/code.js new file mode 100644 index 0000000000000..68e9b4499792c --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-import-named/code.js @@ -0,0 +1,5 @@ +export { + functionDeclaration, + variableDeclaration, + ClassDeclaration, +} from './named-identifiers'; diff --git a/packages/docgen/src/test/fixtures/named-import-named/exports.json b/packages/docgen/src/test/fixtures/named-import-named/exports.json new file mode 100644 index 0000000000000..8d3340586992f --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-import-named/exports.json @@ -0,0 +1,220 @@ +{ + "type": "ExportNamedDeclaration", + "start": 0, + "end": 101, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 5, + "column": 29 + } + }, + "range": [ + 0, + 101 + ], + "declaration": null, + "specifiers": [ + { + "type": "ExportSpecifier", + "start": 10, + "end": 29, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 20 + } + }, + "range": [ + 10, + 29 + ], + "local": { + "type": "Identifier", + "start": 10, + "end": 29, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 20 + } + }, + "range": [ + 10, + 29 + ], + "name": "functionDeclaration" + }, + "exported": { + "type": "Identifier", + "start": 10, + "end": 29, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 20 + } + }, + "range": [ + 10, + 29 + ], + "name": "functionDeclaration" + } + }, + { + "type": "ExportSpecifier", + "start": 32, + "end": 51, + "loc": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 20 + } + }, + "range": [ + 32, + 51 + ], + "local": { + "type": "Identifier", + "start": 32, + "end": 51, + "loc": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 20 + } + }, + "range": [ + 32, + 51 + ], + "name": "variableDeclaration" + }, + "exported": { + "type": "Identifier", + "start": 32, + "end": 51, + "loc": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 20 + } + }, + "range": [ + 32, + 51 + ], + "name": "variableDeclaration" + } + }, + { + "type": "ExportSpecifier", + "start": 54, + "end": 70, + "loc": { + "start": { + "line": 4, + "column": 1 + }, + "end": { + "line": 4, + "column": 17 + } + }, + "range": [ + 54, + 70 + ], + "local": { + "type": "Identifier", + "start": 54, + "end": 70, + "loc": { + "start": { + "line": 4, + "column": 1 + }, + "end": { + "line": 4, + "column": 17 + } + }, + "range": [ + 54, + 70 + ], + "name": "ClassDeclaration" + }, + "exported": { + "type": "Identifier", + "start": 54, + "end": 70, + "loc": { + "start": { + "line": 4, + "column": 1 + }, + "end": { + "line": 4, + "column": 17 + } + }, + "range": [ + 54, + 70 + ], + "name": "ClassDeclaration" + } + } + ], + "source": { + "type": "Literal", + "start": 79, + "end": 100, + "loc": { + "start": { + "line": 5, + "column": 7 + }, + "end": { + "line": 5, + "column": 28 + } + }, + "range": [ + 79, + 100 + ], + "value": "./named-identifiers", + "raw": "'./named-identifiers'" + } +} \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/named-import-namespace/ast.json b/packages/docgen/src/test/fixtures/named-import-namespace/ast.json new file mode 100644 index 0000000000000..f17b43fcef753 --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-import-namespace/ast.json @@ -0,0 +1,186 @@ +{ + "type": "Program", + "start": 0, + "end": 85, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 21 + } + }, + "range": [ + 0, + 84 + ], + "body": [ + { + "type": "ImportDeclaration", + "start": 0, + "end": 61, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 61 + } + }, + "range": [ + 0, + 61 + ], + "specifiers": [ + { + "type": "ImportNamespaceSpecifier", + "start": 7, + "end": 21, + "loc": { + "start": { + "line": 1, + "column": 7 + }, + "end": { + "line": 1, + "column": 21 + } + }, + "range": [ + 7, + 21 + ], + "local": { + "type": "Identifier", + "start": 12, + "end": 21, + "loc": { + "start": { + "line": 1, + "column": 12 + }, + "end": { + "line": 1, + "column": 21 + } + }, + "range": [ + 12, + 21 + ], + "name": "variables" + } + } + ], + "source": { + "type": "Literal", + "start": 27, + "end": 60, + "loc": { + "start": { + "line": 1, + "column": 27 + }, + "end": { + "line": 1, + "column": 60 + } + }, + "range": [ + 27, + 60 + ], + "value": "./named-import-namespace-module", + "raw": "'./named-import-namespace-module'" + } + }, + { + "type": "ExportNamedDeclaration", + "start": 63, + "end": 84, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 21 + } + }, + "range": [ + 63, + 84 + ], + "declaration": null, + "specifiers": [ + { + "type": "ExportSpecifier", + "start": 72, + "end": 81, + "loc": { + "start": { + "line": 3, + "column": 9 + }, + "end": { + "line": 3, + "column": 18 + } + }, + "range": [ + 72, + 81 + ], + "local": { + "type": "Identifier", + "start": 72, + "end": 81, + "loc": { + "start": { + "line": 3, + "column": 9 + }, + "end": { + "line": 3, + "column": 18 + } + }, + "range": [ + 72, + 81 + ], + "name": "variables" + }, + "exported": { + "type": "Identifier", + "start": 72, + "end": 81, + "loc": { + "start": { + "line": 3, + "column": 9 + }, + "end": { + "line": 3, + "column": 18 + } + }, + "range": [ + 72, + 81 + ], + "name": "variables" + } + } + ], + "source": null + } + ], + "sourceType": "module", + "comments": [] +} \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/named-import-namespace/code.js b/packages/docgen/src/test/fixtures/named-import-namespace/code.js new file mode 100644 index 0000000000000..a6b9234374d42 --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-import-namespace/code.js @@ -0,0 +1,6 @@ +/** + * Internal dependencies + */ +import * as variables from './named-import-namespace-module'; + +export { variables }; diff --git a/packages/docgen/src/test/fixtures/named-import-namespace/exports.json b/packages/docgen/src/test/fixtures/named-import-namespace/exports.json new file mode 100644 index 0000000000000..a2ea24189479d --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-import-namespace/exports.json @@ -0,0 +1,82 @@ +{ + "type": "ExportNamedDeclaration", + "start": 63, + "end": 84, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 21 + } + }, + "range": [ + 63, + 84 + ], + "declaration": null, + "specifiers": [ + { + "type": "ExportSpecifier", + "start": 72, + "end": 81, + "loc": { + "start": { + "line": 3, + "column": 9 + }, + "end": { + "line": 3, + "column": 18 + } + }, + "range": [ + 72, + 81 + ], + "local": { + "type": "Identifier", + "start": 72, + "end": 81, + "loc": { + "start": { + "line": 3, + "column": 9 + }, + "end": { + "line": 3, + "column": 18 + } + }, + "range": [ + 72, + 81 + ], + "name": "variables" + }, + "exported": { + "type": "Identifier", + "start": 72, + "end": 81, + "loc": { + "start": { + "line": 3, + "column": 9 + }, + "end": { + "line": 3, + "column": 18 + } + }, + "range": [ + 72, + 81 + ], + "name": "variables" + } + } + ], + "source": null +} \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/named-import-namespace/module-code.js b/packages/docgen/src/test/fixtures/named-import-namespace/module-code.js new file mode 100644 index 0000000000000..5c29fce852840 --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-import-namespace/module-code.js @@ -0,0 +1 @@ +export { default as controls } from './default-function-named'; diff --git a/packages/docgen/src/test/fixtures/named-import-namespace/module-exports.json b/packages/docgen/src/test/fixtures/named-import-namespace/module-exports.json new file mode 100644 index 0000000000000..3e467a9404209 --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-import-namespace/module-exports.json @@ -0,0 +1,102 @@ +{ + "type": "ExportNamedDeclaration", + "start": 0, + "end": 63, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 63 + } + }, + "range": [ + 0, + 63 + ], + "declaration": null, + "specifiers": [ + { + "type": "ExportSpecifier", + "start": 9, + "end": 28, + "loc": { + "start": { + "line": 1, + "column": 9 + }, + "end": { + "line": 1, + "column": 28 + } + }, + "range": [ + 9, + 28 + ], + "local": { + "type": "Identifier", + "start": 9, + "end": 16, + "loc": { + "start": { + "line": 1, + "column": 9 + }, + "end": { + "line": 1, + "column": 16 + } + }, + "range": [ + 9, + 16 + ], + "name": "default" + }, + "exported": { + "type": "Identifier", + "start": 20, + "end": 28, + "loc": { + "start": { + "line": 1, + "column": 20 + }, + "end": { + "line": 1, + "column": 28 + } + }, + "range": [ + 20, + 28 + ], + "name": "controls" + } + } + ], + "source": { + "type": "Literal", + "start": 36, + "end": 62, + "loc": { + "start": { + "line": 1, + "column": 36 + }, + "end": { + "line": 1, + "column": 62 + } + }, + "range": [ + 36, + 62 + ], + "value": "./default-function-named", + "raw": "'./default-function-named'" + } +} \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/named-import-namespace/module-ir.json b/packages/docgen/src/test/fixtures/named-import-namespace/module-ir.json new file mode 100644 index 0000000000000..6488f6035d017 --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-import-namespace/module-ir.json @@ -0,0 +1 @@ +[{"name":"controls","description":"Function declaration example.","tags":[]}] \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/named-variable/code.js b/packages/docgen/src/test/fixtures/named-variable/code.js new file mode 100644 index 0000000000000..e89c8a9751394 --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-variable/code.js @@ -0,0 +1,5 @@ +/** + * My declaration example. + */ +export const myDeclaration = true; + diff --git a/packages/docgen/src/test/fixtures/named-variable/exports.json b/packages/docgen/src/test/fixtures/named-variable/exports.json new file mode 100644 index 0000000000000..1c5797990ceab --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-variable/exports.json @@ -0,0 +1,125 @@ +{ + "type": "ExportNamedDeclaration", + "start": 35, + "end": 69, + "loc": { + "start": { + "line": 4, + "column": 0 + }, + "end": { + "line": 4, + "column": 34 + } + }, + "range": [ + 35, + 69 + ], + "declaration": { + "type": "VariableDeclaration", + "start": 42, + "end": 69, + "loc": { + "start": { + "line": 4, + "column": 7 + }, + "end": { + "line": 4, + "column": 34 + } + }, + "range": [ + 42, + 69 + ], + "declarations": [ + { + "type": "VariableDeclarator", + "start": 48, + "end": 68, + "loc": { + "start": { + "line": 4, + "column": 13 + }, + "end": { + "line": 4, + "column": 33 + } + }, + "range": [ + 48, + 68 + ], + "id": { + "type": "Identifier", + "start": 48, + "end": 61, + "loc": { + "start": { + "line": 4, + "column": 13 + }, + "end": { + "line": 4, + "column": 26 + } + }, + "range": [ + 48, + 61 + ], + "name": "myDeclaration" + }, + "init": { + "type": "Literal", + "start": 64, + "end": 68, + "loc": { + "start": { + "line": 4, + "column": 29 + }, + "end": { + "line": 4, + "column": 33 + } + }, + "range": [ + 64, + 68 + ], + "value": true, + "raw": "true" + } + } + ], + "kind": "const" + }, + "specifiers": [], + "source": null, + "leadingComments": [ + { + "type": "Block", + "value": "*\n * My declaration example.\n ", + "start": 0, + "end": 34, + "range": [ + 0, + 34 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 3 + } + } + } + ] +} \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/named-variables/code.js b/packages/docgen/src/test/fixtures/named-variables/code.js new file mode 100644 index 0000000000000..8f56e0aff46e5 --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-variables/code.js @@ -0,0 +1,6 @@ +/** + * My declaration example. + */ +export const firstDeclaration = true, + secondDeclaration = 42; + diff --git a/packages/docgen/src/test/fixtures/named-variables/exports.json b/packages/docgen/src/test/fixtures/named-variables/exports.json new file mode 100644 index 0000000000000..24d1d1525e950 --- /dev/null +++ b/packages/docgen/src/test/fixtures/named-variables/exports.json @@ -0,0 +1,185 @@ +{ + "type": "ExportNamedDeclaration", + "start": 35, + "end": 97, + "loc": { + "start": { + "line": 4, + "column": 0 + }, + "end": { + "line": 5, + "column": 24 + } + }, + "range": [ + 35, + 97 + ], + "declaration": { + "type": "VariableDeclaration", + "start": 42, + "end": 97, + "loc": { + "start": { + "line": 4, + "column": 7 + }, + "end": { + "line": 5, + "column": 24 + } + }, + "range": [ + 42, + 97 + ], + "declarations": [ + { + "type": "VariableDeclarator", + "start": 48, + "end": 71, + "loc": { + "start": { + "line": 4, + "column": 13 + }, + "end": { + "line": 4, + "column": 36 + } + }, + "range": [ + 48, + 71 + ], + "id": { + "type": "Identifier", + "start": 48, + "end": 64, + "loc": { + "start": { + "line": 4, + "column": 13 + }, + "end": { + "line": 4, + "column": 29 + } + }, + "range": [ + 48, + 64 + ], + "name": "firstDeclaration" + }, + "init": { + "type": "Literal", + "start": 67, + "end": 71, + "loc": { + "start": { + "line": 4, + "column": 32 + }, + "end": { + "line": 4, + "column": 36 + } + }, + "range": [ + 67, + 71 + ], + "value": true, + "raw": "true" + } + }, + { + "type": "VariableDeclarator", + "start": 74, + "end": 96, + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 5, + "column": 23 + } + }, + "range": [ + 74, + 96 + ], + "id": { + "type": "Identifier", + "start": 74, + "end": 91, + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 5, + "column": 18 + } + }, + "range": [ + 74, + 91 + ], + "name": "secondDeclaration" + }, + "init": { + "type": "Literal", + "start": 94, + "end": 96, + "loc": { + "start": { + "line": 5, + "column": 21 + }, + "end": { + "line": 5, + "column": 23 + } + }, + "range": [ + 94, + 96 + ], + "value": 42, + "raw": "42" + } + } + ], + "kind": "const" + }, + "specifiers": [], + "source": null, + "leadingComments": [ + { + "type": "Block", + "value": "*\n * My declaration example.\n ", + "start": 0, + "end": 34, + "range": [ + 0, + 34 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 3 + } + } + } + ] +} \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/namespace-commented/code.js b/packages/docgen/src/test/fixtures/namespace-commented/code.js new file mode 100644 index 0000000000000..5008b26d090aa --- /dev/null +++ b/packages/docgen/src/test/fixtures/namespace-commented/code.js @@ -0,0 +1,4 @@ +/** + * This comment should be ignored. + */ +export * from './namespace-module'; diff --git a/packages/docgen/src/test/fixtures/namespace-commented/exports.json b/packages/docgen/src/test/fixtures/namespace-commented/exports.json new file mode 100644 index 0000000000000..37166ddd12a5c --- /dev/null +++ b/packages/docgen/src/test/fixtures/namespace-commented/exports.json @@ -0,0 +1,62 @@ +{ + "type": "ExportAllDeclaration", + "start": 43, + "end": 78, + "loc": { + "start": { + "line": 4, + "column": 0 + }, + "end": { + "line": 4, + "column": 35 + } + }, + "range": [ + 43, + 78 + ], + "source": { + "type": "Literal", + "start": 57, + "end": 77, + "loc": { + "start": { + "line": 4, + "column": 14 + }, + "end": { + "line": 4, + "column": 34 + } + }, + "range": [ + 57, + 77 + ], + "value": "./namespace-module", + "raw": "'./namespace-module'" + }, + "leadingComments": [ + { + "type": "Block", + "value": "*\n * This comment should be ignored.\n ", + "start": 0, + "end": 42, + "range": [ + 0, + 42 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 3 + } + } + } + ] +} \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/namespace-commented/module-ir.json b/packages/docgen/src/test/fixtures/namespace-commented/module-ir.json new file mode 100644 index 0000000000000..7798095b482fa --- /dev/null +++ b/packages/docgen/src/test/fixtures/namespace-commented/module-ir.json @@ -0,0 +1,22 @@ +[ + { + "name": "default", + "description": "Default variable declaration.", + "tags": [] + }, + { + "name": "MyClass", + "description": "Named class.", + "tags": [] + }, + { + "name": "myFunction", + "description": "Named function.", + "tags": [] + }, + { + "name": "myVariable", + "description": "Named variable.", + "tags": [] + } +] \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/namespace-commented/module.js b/packages/docgen/src/test/fixtures/namespace-commented/module.js new file mode 100644 index 0000000000000..1ff51c686236a --- /dev/null +++ b/packages/docgen/src/test/fixtures/namespace-commented/module.js @@ -0,0 +1,19 @@ +/** + * Named variable. + */ +export const myVariable = true; + +/** + * Named function. + */ +export const myFunction = () => {}; + +/** + * Named class. + */ +export class MyClass {} + +/** + * Default variable declaration. + */ +export default 42; diff --git a/packages/docgen/src/test/fixtures/namespace/code.js b/packages/docgen/src/test/fixtures/namespace/code.js new file mode 100644 index 0000000000000..d2705ea5f8e63 --- /dev/null +++ b/packages/docgen/src/test/fixtures/namespace/code.js @@ -0,0 +1 @@ +export * from './namespace-module'; diff --git a/packages/docgen/src/test/fixtures/namespace/exports.json b/packages/docgen/src/test/fixtures/namespace/exports.json new file mode 100644 index 0000000000000..d6aa32af9ac61 --- /dev/null +++ b/packages/docgen/src/test/fixtures/namespace/exports.json @@ -0,0 +1,40 @@ +{ + "type": "ExportAllDeclaration", + "start": 0, + "end": 35, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 35 + } + }, + "range": [ + 0, + 35 + ], + "source": { + "type": "Literal", + "start": 14, + "end": 34, + "loc": { + "start": { + "line": 1, + "column": 14 + }, + "end": { + "line": 1, + "column": 34 + } + }, + "range": [ + 14, + 34 + ], + "value": "./namespace-module", + "raw": "'./namespace-module'" + } +} \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/namespace/module-ir.json b/packages/docgen/src/test/fixtures/namespace/module-ir.json new file mode 100644 index 0000000000000..7798095b482fa --- /dev/null +++ b/packages/docgen/src/test/fixtures/namespace/module-ir.json @@ -0,0 +1,22 @@ +[ + { + "name": "default", + "description": "Default variable declaration.", + "tags": [] + }, + { + "name": "MyClass", + "description": "Named class.", + "tags": [] + }, + { + "name": "myFunction", + "description": "Named function.", + "tags": [] + }, + { + "name": "myVariable", + "description": "Named variable.", + "tags": [] + } +] \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/namespace/module.js b/packages/docgen/src/test/fixtures/namespace/module.js new file mode 100644 index 0000000000000..1ff51c686236a --- /dev/null +++ b/packages/docgen/src/test/fixtures/namespace/module.js @@ -0,0 +1,19 @@ +/** + * Named variable. + */ +export const myVariable = true; + +/** + * Named function. + */ +export const myFunction = () => {}; + +/** + * Named class. + */ +export class MyClass {} + +/** + * Default variable declaration. + */ +export default 42; diff --git a/packages/docgen/src/test/fixtures/tags-function/code.js b/packages/docgen/src/test/fixtures/tags-function/code.js new file mode 100644 index 0000000000000..8d4261e0f6328 --- /dev/null +++ b/packages/docgen/src/test/fixtures/tags-function/code.js @@ -0,0 +1,24 @@ +/** + * A function that adds two parameters. + * + * @deprecated Use native addition instead. + * @since v2 + * + * @see addition + * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators + * + * @param {number} firstParam The first param to add. + * @param {number} secondParam The second param to add. + * + * @example + * + * ```js + * const addResult = sum( 1, 3 ); + * console.log( addResult ); // will yield 4 + * ``` + * + * @return {number} The result of adding the two params. + */ +export const sum = ( firstParam, secondParam ) => { + return firstParam + secondParam; +}; diff --git a/packages/docgen/src/test/fixtures/tags-function/exports.json b/packages/docgen/src/test/fixtures/tags-function/exports.json new file mode 100644 index 0000000000000..3d0677a818c4b --- /dev/null +++ b/packages/docgen/src/test/fixtures/tags-function/exports.json @@ -0,0 +1,269 @@ +{ + "type": "ExportNamedDeclaration", + "start": 521, + "end": 609, + "loc": { + "start": { + "line": 22, + "column": 0 + }, + "end": { + "line": 24, + "column": 2 + } + }, + "range": [ + 521, + 609 + ], + "declaration": { + "type": "VariableDeclaration", + "start": 528, + "end": 609, + "loc": { + "start": { + "line": 22, + "column": 7 + }, + "end": { + "line": 24, + "column": 2 + } + }, + "range": [ + 528, + 609 + ], + "declarations": [ + { + "type": "VariableDeclarator", + "start": 534, + "end": 608, + "loc": { + "start": { + "line": 22, + "column": 13 + }, + "end": { + "line": 24, + "column": 1 + } + }, + "range": [ + 534, + 608 + ], + "id": { + "type": "Identifier", + "start": 534, + "end": 537, + "loc": { + "start": { + "line": 22, + "column": 13 + }, + "end": { + "line": 22, + "column": 16 + } + }, + "range": [ + 534, + 537 + ], + "name": "sum" + }, + "init": { + "type": "ArrowFunctionExpression", + "start": 540, + "end": 608, + "loc": { + "start": { + "line": 22, + "column": 19 + }, + "end": { + "line": 24, + "column": 1 + } + }, + "range": [ + 540, + 608 + ], + "id": null, + "generator": false, + "expression": false, + "async": false, + "params": [ + { + "type": "Identifier", + "start": 542, + "end": 552, + "loc": { + "start": { + "line": 22, + "column": 21 + }, + "end": { + "line": 22, + "column": 31 + } + }, + "range": [ + 542, + 552 + ], + "name": "firstParam" + }, + { + "type": "Identifier", + "start": 554, + "end": 565, + "loc": { + "start": { + "line": 22, + "column": 33 + }, + "end": { + "line": 22, + "column": 44 + } + }, + "range": [ + 554, + 565 + ], + "name": "secondParam" + } + ], + "body": { + "type": "BlockStatement", + "start": 571, + "end": 608, + "loc": { + "start": { + "line": 22, + "column": 50 + }, + "end": { + "line": 24, + "column": 1 + } + }, + "range": [ + 571, + 608 + ], + "body": [ + { + "type": "ReturnStatement", + "start": 574, + "end": 606, + "loc": { + "start": { + "line": 23, + "column": 1 + }, + "end": { + "line": 23, + "column": 33 + } + }, + "range": [ + 574, + 606 + ], + "argument": { + "type": "BinaryExpression", + "start": 581, + "end": 605, + "loc": { + "start": { + "line": 23, + "column": 8 + }, + "end": { + "line": 23, + "column": 32 + } + }, + "range": [ + 581, + 605 + ], + "left": { + "type": "Identifier", + "start": 581, + "end": 591, + "loc": { + "start": { + "line": 23, + "column": 8 + }, + "end": { + "line": 23, + "column": 18 + } + }, + "range": [ + 581, + 591 + ], + "name": "firstParam" + }, + "operator": "+", + "right": { + "type": "Identifier", + "start": 594, + "end": 605, + "loc": { + "start": { + "line": 23, + "column": 21 + }, + "end": { + "line": 23, + "column": 32 + } + }, + "range": [ + 594, + 605 + ], + "name": "secondParam" + } + } + } + ] + } + } + } + ], + "kind": "const" + }, + "specifiers": [], + "source": null, + "leadingComments": [ + { + "type": "Block", + "value": "*\n * A function that adds two parameters.\n *\n * @deprecated Use native addition instead.\n * @since v2\n *\n * @see addition\n * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators\n *\n * @param {number} firstParam The first param to add.\n * @param {number} secondParam The second param to add.\n *\n * @example\n *\n * ```js\n * const addResult = sum( 1, 3 );\n * console.log( addResult ); // will yield 4\n * ```\n *\n * @return {number} The result of adding the two params.\n ", + "start": 0, + "end": 520, + "range": [ + 0, + 520 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 21, + "column": 3 + } + } + } + ] +} \ No newline at end of file diff --git a/packages/docgen/src/test/fixtures/tags-variable/code.js b/packages/docgen/src/test/fixtures/tags-variable/code.js new file mode 100644 index 0000000000000..d2d32717a67bc --- /dev/null +++ b/packages/docgen/src/test/fixtures/tags-variable/code.js @@ -0,0 +1,7 @@ +/** + * Constant to document the meaning of life, + * the universe and everything else. + * + * @type {number} + */ +export const THE_MEANING = 42; diff --git a/packages/docgen/src/test/fixtures/tags-variable/exports.json b/packages/docgen/src/test/fixtures/tags-variable/exports.json new file mode 100644 index 0000000000000..80482610d28e5 --- /dev/null +++ b/packages/docgen/src/test/fixtures/tags-variable/exports.json @@ -0,0 +1,125 @@ +{ + "type": "ExportNamedDeclaration", + "start": 111, + "end": 141, + "loc": { + "start": { + "line": 7, + "column": 0 + }, + "end": { + "line": 7, + "column": 30 + } + }, + "range": [ + 111, + 141 + ], + "declaration": { + "type": "VariableDeclaration", + "start": 118, + "end": 141, + "loc": { + "start": { + "line": 7, + "column": 7 + }, + "end": { + "line": 7, + "column": 30 + } + }, + "range": [ + 118, + 141 + ], + "declarations": [ + { + "type": "VariableDeclarator", + "start": 124, + "end": 140, + "loc": { + "start": { + "line": 7, + "column": 13 + }, + "end": { + "line": 7, + "column": 29 + } + }, + "range": [ + 124, + 140 + ], + "id": { + "type": "Identifier", + "start": 124, + "end": 135, + "loc": { + "start": { + "line": 7, + "column": 13 + }, + "end": { + "line": 7, + "column": 24 + } + }, + "range": [ + 124, + 135 + ], + "name": "THE_MEANING" + }, + "init": { + "type": "Literal", + "start": 138, + "end": 140, + "loc": { + "start": { + "line": 7, + "column": 27 + }, + "end": { + "line": 7, + "column": 29 + } + }, + "range": [ + 138, + 140 + ], + "value": 42, + "raw": "42" + } + } + ], + "kind": "const" + }, + "specifiers": [], + "source": null, + "leadingComments": [ + { + "type": "Block", + "value": "*\n * Constant to document the meaning of life,\n * the universe and everything else.\n *\n * @type {number}\n ", + "start": 0, + "end": 110, + "range": [ + 0, + 110 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 6, + "column": 3 + } + } + } + ] +} \ No newline at end of file diff --git a/packages/docgen/src/test/formatter-markdown.js b/packages/docgen/src/test/formatter-markdown.js new file mode 100644 index 0000000000000..cd7e66a12e267 --- /dev/null +++ b/packages/docgen/src/test/formatter-markdown.js @@ -0,0 +1,34 @@ +/** + * Internal dependencies. + */ +const formatter = require( '../markdown/formatter' ); + +describe( 'Formatter', () => { + it( 'returns markdown', () => { + const rootDir = '/home/my-path'; + const docPath = '/home/my-path/docs'; + const docs = formatter( rootDir, docPath + '-api.md', [ { + path: docPath + '-code.js', + description: 'My declaration example.', + tags: [ + { + title: 'param', + description: 'First declaration parameter.', + type: 'number', + name: 'firstParam', + }, + { + title: 'return', + description: 'The result of the declaration.', + type: 'number', + }, + ], + name: 'myDeclaration', + lineStart: 1, + lineEnd: 2, + } ], 'API docs' ); + expect( docs ).toBe( + '# API docs\n\n## myDeclaration\n\n[home/my-path/docs-code.js#L1-L2](home/my-path/docs-code.js#L1-L2)\n\nMy declaration example.\n\n**Parameters**\n\n- **firstParam** `number`: First declaration parameter.\n\n**Returns**\n\n`number` The result of the declaration.\n' + ); + } ); +} ); diff --git a/packages/docgen/src/test/get-export-entries.js b/packages/docgen/src/test/get-export-entries.js new file mode 100644 index 0000000000000..b1e06f9d145a9 --- /dev/null +++ b/packages/docgen/src/test/get-export-entries.js @@ -0,0 +1,352 @@ +/** + * Node dependencies. + */ +const fs = require( 'fs' ); +const path = require( 'path' ); + +/** + * Internal dependencies. + */ +const getExportEntries = require( '../get-export-entries' ); + +describe( 'Export entries', function() { + it( 'default class (anonymous)', () => { + const token = fs.readFileSync( + path.join( __dirname, './fixtures/default-class-anonymous/exports.json' ), + 'utf-8' + ); + const name = getExportEntries( JSON.parse( token ) ); + expect( name ).toHaveLength( 1 ); + expect( name[ 0 ] ).toEqual( { + localName: '*default*', + exportName: 'default', + module: null, + lineStart: 4, + lineEnd: 4, + } ); + } ); + + it( 'default class (named)', function() { + const token = fs.readFileSync( + path.join( __dirname, './fixtures/default-class-named/exports.json' ), + 'utf-8' + ); + const name = getExportEntries( JSON.parse( token ) ); + expect( name ).toHaveLength( 1 ); + expect( name[ 0 ] ).toEqual( { + localName: 'ClassDeclaration', + exportName: 'default', + module: null, + lineStart: 4, + lineEnd: 4, + } ); + } ); + + it( 'default function (anonymous)', function() { + const token = fs.readFileSync( + path.join( __dirname, './fixtures/default-function-anonymous/exports.json' ), + 'utf-8' + ); + const name = getExportEntries( JSON.parse( token ) ); + expect( name ).toHaveLength( 1 ); + expect( name[ 0 ] ).toEqual( { + localName: '*default*', + exportName: 'default', + module: null, + lineStart: 4, + lineEnd: 4, + } ); + } ); + + it( 'default function (named)', function() { + const token = fs.readFileSync( + path.join( __dirname, './fixtures/default-function-named/exports.json' ), + 'utf-8' + ); + const name = getExportEntries( JSON.parse( token ) ); + expect( name ).toHaveLength( 1 ); + expect( name[ 0 ] ).toEqual( { + localName: 'myDeclaration', + exportName: 'default', + module: null, + lineStart: 4, + lineEnd: 4, + } ); + } ); + + it( 'default identifier', function() { + const token = fs.readFileSync( + path.join( __dirname, './fixtures/default-identifier/exports.json' ), + 'utf-8' + ); + const name = getExportEntries( JSON.parse( token ) ); + expect( name ).toHaveLength( 1 ); + expect( name[ 0 ] ).toEqual( { + localName: 'ClassDeclaration', + exportName: 'default', + module: null, + lineStart: 6, + lineEnd: 6, + } ); + } ); + + it( 'default import (named)', function() { + const token = fs.readFileSync( + path.join( __dirname, './fixtures/default-import-named/exports.json' ), + 'utf-8' + ); + const name = getExportEntries( JSON.parse( token ) ); + expect( name ).toHaveLength( 1 ); + expect( name[ 0 ] ).toEqual( { + localName: 'fnDeclaration', + exportName: 'default', + module: null, + lineStart: 3, + lineEnd: 3, + } ); + } ); + + it( 'default import (default)', function() { + const token = fs.readFileSync( + path.join( __dirname, './fixtures/default-import-default/exports.json' ), + 'utf-8' + ); + const name = getExportEntries( JSON.parse( token ) ); + expect( name ).toHaveLength( 1 ); + expect( name[ 0 ] ).toEqual( { + localName: 'fnDeclaration', + exportName: 'default', + module: null, + lineStart: 3, + lineEnd: 3, + } ); + } ); + + it( 'default named export', function() { + const tokens = fs.readFileSync( + path.join( __dirname, './fixtures/default-named-export/exports.json' ), + 'utf-8' + ); + const namedExport = getExportEntries( JSON.parse( tokens )[ 0 ] ); + expect( namedExport ).toHaveLength( 1 ); + expect( namedExport[ 0 ] ).toEqual( { + localName: 'functionDeclaration', + exportName: 'functionDeclaration', + module: null, + lineStart: 4, + lineEnd: 4, + } ); + const defaultExport = getExportEntries( JSON.parse( tokens )[ 1 ] ); + expect( defaultExport ).toHaveLength( 1 ); + expect( defaultExport[ 0 ] ).toEqual( { + localName: 'functionDeclaration', + exportName: 'default', + module: null, + lineStart: 6, + lineEnd: 6, + } ); + } ); + + it( 'default variable', function() { + const token = fs.readFileSync( + path.join( __dirname, './fixtures/default-variable/exports.json' ), + 'utf-8' + ); + const name = getExportEntries( JSON.parse( token ) ); + expect( name ).toHaveLength( 1 ); + expect( name[ 0 ] ).toEqual( { + localName: '*default*', + exportName: 'default', + module: null, + lineStart: 4, + lineEnd: 4, + } ); + } ); + + it( 'named class', function() { + const token = fs.readFileSync( + path.join( __dirname, './fixtures/named-class/exports.json' ), + 'utf-8' + ); + const name = getExportEntries( JSON.parse( token ) ); + expect( name ).toHaveLength( 1 ); + expect( name[ 0 ] ).toEqual( { + localName: 'MyDeclaration', + exportName: 'MyDeclaration', + module: null, + lineStart: 4, + lineEnd: 4, + } ); + } ); + + it( 'named default', function() { + const token = fs.readFileSync( + path.join( __dirname, './fixtures/named-default/exports.json' ), + 'utf-8' + ); + const name = getExportEntries( JSON.parse( token ) ); + expect( name ).toHaveLength( 1 ); + expect( name[ 0 ] ).toEqual( { + localName: 'default', + exportName: 'default', + module: './named-default-module', + lineStart: 1, + lineEnd: 1, + } ); + } ); + + it( 'named default (exported)', function() { + const token = fs.readFileSync( + path.join( __dirname, './fixtures/named-default-exported/exports.json' ), + 'utf-8' + ); + const name = getExportEntries( JSON.parse( token ) ); + expect( name ).toHaveLength( 1 ); + expect( name[ 0 ] ).toEqual( { + localName: 'default', + exportName: 'moduleName', + module: './named-default-module', + lineStart: 1, + lineEnd: 1, + } ); + } ); + + it( 'named function', function() { + const token = fs.readFileSync( + path.join( __dirname, './fixtures/named-function/exports.json' ), + 'utf-8' + ); + const name = getExportEntries( JSON.parse( token ) ); + expect( name ).toHaveLength( 1 ); + expect( name[ 0 ] ).toEqual( { + localName: 'myDeclaration', + exportName: 'myDeclaration', + module: null, + lineStart: 4, + lineEnd: 4, + } ); + } ); + + it( 'named identifier', function() { + const token = fs.readFileSync( + path.join( __dirname, './fixtures/named-identifier/exports.json' ), + 'utf-8' + ); + const name = getExportEntries( JSON.parse( token ) ); + expect( name ).toHaveLength( 1 ); + expect( name[ 0 ] ).toEqual( { + localName: 'myDeclaration', + exportName: 'myDeclaration', + module: null, + lineStart: 6, + lineEnd: 6, + } ); + const tokenObject = fs.readFileSync( + path.join( __dirname, './fixtures/named-identifier-destructuring/exports.json' ), + 'utf-8' + ); + const nameObject = getExportEntries( JSON.parse( tokenObject ) ); + expect( nameObject ).toHaveLength( 1 ); + expect( nameObject[ 0 ] ).toEqual( { + localName: 'someDeclaration', + exportName: 'myDeclaration', + module: null, + lineStart: 6, + lineEnd: 6, + } ); + } ); + + it( 'named identifiers', function() { + const token = fs.readFileSync( + path.join( __dirname, './fixtures/named-identifiers/exports.json' ), + 'utf-8' + ); + const name = getExportEntries( JSON.parse( token ) ); + expect( name ).toHaveLength( 3 ); + expect( name[ 0 ] ).toEqual( + { localName: 'functionDeclaration', exportName: 'functionDeclaration', module: null, lineStart: 16, lineEnd: 16 } + ); + expect( name[ 1 ] ).toEqual( + { localName: 'variableDeclaration', exportName: 'variableDeclaration', module: null, lineStart: 16, lineEnd: 16 }, + ); + expect( name[ 2 ] ).toEqual( + { localName: 'ClassDeclaration', exportName: 'ClassDeclaration', module: null, lineStart: 16, lineEnd: 16 }, + ); + const tokenIdentifiersAndInline = fs.readFileSync( + path.join( __dirname, './fixtures/named-identifiers-and-inline/exports.json' ), + 'utf-8' + ); + const nameInline0 = getExportEntries( JSON.parse( tokenIdentifiersAndInline )[ 0 ] ); + expect( nameInline0 ).toHaveLength( 2 ); + expect( nameInline0[ 0 ] ).toEqual( + { localName: 'functionDeclaration', exportName: 'functionDeclaration', module: null, lineStart: 11, lineEnd: 11 }, + ); + expect( nameInline0[ 1 ] ).toEqual( + { localName: 'ClassDeclaration', exportName: 'ClassDeclaration', module: null, lineStart: 11, lineEnd: 11 }, + ); + const nameInline1 = getExportEntries( JSON.parse( tokenIdentifiersAndInline )[ 1 ] ); + expect( nameInline1 ).toHaveLength( 1 ); + expect( nameInline1[ 0 ] ).toEqual( + { localName: 'variableDeclaration', exportName: 'variableDeclaration', module: null, lineStart: 16, lineEnd: 16 }, + ); + } ); + + it( 'named import namespace', function() { + const token = fs.readFileSync( + path.join( __dirname, './fixtures/named-import-namespace/exports.json' ), + 'utf-8' + ); + const name = getExportEntries( JSON.parse( token ) ); + expect( name ).toHaveLength( 1 ); + expect( name[ 0 ] ).toEqual( + { localName: 'variables', exportName: 'variables', module: null, lineStart: 3, lineEnd: 3 }, + ); + } ); + + it( 'named variable', function() { + const token = fs.readFileSync( + path.join( __dirname, './fixtures/named-variable/exports.json' ), + 'utf-8' + ); + const name = getExportEntries( JSON.parse( token ) ); + expect( name ).toHaveLength( 1 ); + expect( name[ 0 ] ).toEqual( { + localName: 'myDeclaration', + exportName: 'myDeclaration', + module: null, + lineStart: 4, + lineEnd: 4, + } ); + } ); + + it( 'named variables', function() { + const token = fs.readFileSync( + path.join( __dirname, './fixtures/named-variables/exports.json' ), + 'utf-8' + ); + const name = getExportEntries( JSON.parse( token ) ); + expect( name ).toHaveLength( 2 ); + expect( name[ 0 ] ).toEqual( + { localName: 'firstDeclaration', exportName: 'firstDeclaration', module: null, lineStart: 4, lineEnd: 5 }, + ); + expect( name[ 1 ] ).toEqual( + { localName: 'secondDeclaration', exportName: 'secondDeclaration', module: null, lineStart: 4, lineEnd: 5 }, + ); + } ); + + it( 'namespace (*)', function() { + const token = fs.readFileSync( + path.join( __dirname, './fixtures/namespace/exports.json' ), + 'utf-8' + ); + const name = getExportEntries( JSON.parse( token ) ); + expect( name ).toHaveLength( 1 ); + expect( name[ 0 ] ).toEqual( { + localName: '*', + exportName: null, + module: './namespace-module', + lineStart: 1, + lineEnd: 1, + } ); + } ); +} ); diff --git a/packages/docgen/src/test/get-intermediate-representation.js b/packages/docgen/src/test/get-intermediate-representation.js new file mode 100644 index 0000000000000..53cb937a2be51 --- /dev/null +++ b/packages/docgen/src/test/get-intermediate-representation.js @@ -0,0 +1,454 @@ +/** + * Node dependencies. + */ +const fs = require( 'fs' ); +const path = require( 'path' ); + +/** + * Internal dependencies. + */ +const getIntermediateRepresentation = require( '../get-intermediate-representation' ); + +describe( 'Intermediate Representation', function() { + it( 'undocumented', function() { + const token = fs.readFileSync( + path.join( __dirname, './fixtures/default-undocumented-nocomments/exports.json' ), + 'utf-8' + ); + const ir = getIntermediateRepresentation( null, JSON.parse( token ) ); + expect( ir ).toHaveLength( 1 ); + expect( ir[ 0 ] ).toEqual( { + path: null, + name: 'default', + description: 'Undocumented declaration.', + tags: [], + lineStart: 3, + lineEnd: 3, + } ); + const tokenOneliner = fs.readFileSync( + path.join( __dirname, './fixtures/default-undocumented-oneliner/exports.json' ), + 'utf-8' + ); + const irOneliner = getIntermediateRepresentation( null, JSON.parse( tokenOneliner ) ); + expect( irOneliner ).toHaveLength( 1 ); + expect( irOneliner[ 0 ] ).toEqual( { + path: null, + name: 'default', + description: 'Undocumented declaration.', + tags: [], + lineStart: 2, + lineEnd: 2, + } ); + } ); + + describe( 'JSDoc in export statement', function() { + it( 'default export', function() { + const tokenClassAnonymous = fs.readFileSync( + path.join( __dirname, './fixtures/default-class-anonymous/exports.json' ), + 'utf-8' + ); + const irClassAnonymous = getIntermediateRepresentation( null, JSON.parse( tokenClassAnonymous ) ); + expect( irClassAnonymous ).toHaveLength( 1 ); + expect( irClassAnonymous[ 0 ] ).toEqual( { + path: null, + name: 'default', + description: 'Class declaration example.', + tags: [], + lineStart: 4, + lineEnd: 4, + } ); + const tokenClassNamed = fs.readFileSync( + path.join( __dirname, './fixtures/default-class-named/exports.json' ), + 'utf-8' + ); + const irClassNamed = getIntermediateRepresentation( null, JSON.parse( tokenClassNamed ) ); + expect( irClassNamed ).toHaveLength( 1 ); + expect( irClassNamed[ 0 ] ).toEqual( { + path: null, + name: 'default', + description: 'Class declaration example.', + tags: [], + lineStart: 4, + lineEnd: 4, + } ); + const tokenFnAnonymous = fs.readFileSync( + path.join( __dirname, './fixtures/default-function-anonymous/exports.json' ), + 'utf-8' + ); + const irFnAnonymous = getIntermediateRepresentation( null, JSON.parse( tokenFnAnonymous ) ); + expect( irFnAnonymous ).toHaveLength( 1 ); + expect( irFnAnonymous[ 0 ] ).toEqual( { + path: null, + name: 'default', + description: 'Function declaration example.', + tags: [], + lineStart: 4, + lineEnd: 4, + } ); + const tokenFnNamed = fs.readFileSync( + path.join( __dirname, './fixtures/default-function-named/exports.json' ), + 'utf-8' + ); + const irFnNamed = getIntermediateRepresentation( null, JSON.parse( tokenFnNamed ) ); + expect( irFnNamed[ 0 ] ).toEqual( { + path: null, + name: 'default', + description: 'Function declaration example.', + tags: [], + lineStart: 4, + lineEnd: 4, + } ); + const tokenVariable = fs.readFileSync( + path.join( __dirname, './fixtures/default-variable/exports.json' ), + 'utf-8' + ); + const irVar = getIntermediateRepresentation( null, JSON.parse( tokenVariable ) ); + expect( irVar[ 0 ] ).toEqual( { + path: null, + name: 'default', + description: 'Variable declaration example.', + tags: [], + lineStart: 4, + lineEnd: 4, + } ); + } ); + it( 'named export', function() { + const tokenClass = fs.readFileSync( + path.join( __dirname, './fixtures/named-class/exports.json' ), + 'utf-8' + ); + const irNamedClass = getIntermediateRepresentation( null, JSON.parse( tokenClass ) ); + expect( irNamedClass ).toHaveLength( 1 ); + expect( irNamedClass[ 0 ] ).toEqual( { + path: null, + name: 'MyDeclaration', + description: 'My declaration example.', + tags: [], + lineStart: 4, + lineEnd: 4, + } ); + const tokenFn = fs.readFileSync( + path.join( __dirname, './fixtures/named-function/exports.json' ), + 'utf-8' + ); + const irNamedFn = getIntermediateRepresentation( null, JSON.parse( tokenFn ) ); + expect( irNamedFn ).toHaveLength( 1 ); + expect( irNamedFn[ 0 ] ).toEqual( { + path: null, + name: 'myDeclaration', + description: 'My declaration example.', + tags: [], + lineStart: 4, + lineEnd: 4, + } ); + const tokenVariable = fs.readFileSync( + path.join( __dirname, './fixtures/named-variable/exports.json' ), + 'utf-8' + ); + const irNamedVar = getIntermediateRepresentation( null, JSON.parse( tokenVariable ) ); + expect( irNamedVar ).toHaveLength( 1 ); + expect( irNamedVar[ 0 ] ).toEqual( { + path: null, + name: 'myDeclaration', + description: 'My declaration example.', + tags: [], + lineStart: 4, + lineEnd: 4, + } ); + const tokenVariables = fs.readFileSync( + path.join( __dirname, './fixtures/named-variables/exports.json' ), + 'utf-8' + ); + const irNamedVars = getIntermediateRepresentation( null, JSON.parse( tokenVariables ) ); + expect( irNamedVars ).toHaveLength( 2 ); + expect( irNamedVars[ 0 ] ).toEqual( + { path: null, name: 'firstDeclaration', description: 'My declaration example.', tags: [], lineStart: 4, lineEnd: 5 }, + ); + expect( irNamedVars[ 1 ] ).toEqual( + { path: null, name: 'secondDeclaration', description: 'My declaration example.', tags: [], lineStart: 4, lineEnd: 5 }, + ); + } ); + } ); + + describe( 'JSDoc in same file', function() { + it( 'default export', function() { + const token = fs.readFileSync( + path.join( __dirname, './fixtures/default-identifier/exports.json' ), + 'utf-8' + ); + const ast = fs.readFileSync( + path.join( __dirname, './fixtures/default-identifier/ast.json' ), + 'utf-8' + ); + const irDefaultId = getIntermediateRepresentation( null, JSON.parse( token ), JSON.parse( ast ) ); + expect( irDefaultId ).toHaveLength( 1 ); + expect( irDefaultId[ 0 ] ).toEqual( { + path: null, + name: 'default', + description: 'Class declaration example.', + tags: [], + lineStart: 6, + lineEnd: 6, + } ); + const namedExport = fs.readFileSync( + path.join( __dirname, './fixtures/default-named-export/exports.json' ), + 'utf-8' + ); + const namedExportAST = fs.readFileSync( + path.join( __dirname, './fixtures/default-named-export/ast.json' ), + 'utf-8' + ); + const irDefaultNamed0 = getIntermediateRepresentation( null, JSON.parse( namedExport )[ 0 ], JSON.parse( namedExportAST ) ); + expect( irDefaultNamed0 ).toHaveLength( 1 ); + expect( irDefaultNamed0[ 0 ] ).toEqual( + { path: null, name: 'functionDeclaration', description: 'Function declaration example.', tags: [], lineStart: 4, lineEnd: 4 } + ); + const irDefaultNamed1 = getIntermediateRepresentation( null, JSON.parse( namedExport )[ 1 ], JSON.parse( namedExportAST ) ); + expect( irDefaultNamed1[ 0 ] ).toEqual( + { path: null, name: 'default', description: 'Function declaration example.', tags: [], lineStart: 6, lineEnd: 6 } + ); + } ); + + it( 'named export', function() { + const token = fs.readFileSync( + path.join( __dirname, './fixtures/named-identifier/exports.json' ), + 'utf-8' + ); + const ast = fs.readFileSync( + path.join( __dirname, './fixtures/named-identifier/ast.json' ), + 'utf-8' + ); + const irNamedId = getIntermediateRepresentation( null, JSON.parse( token ), JSON.parse( ast ) ); + expect( irNamedId ).toHaveLength( 1 ); + expect( irNamedId[ 0 ] ).toEqual( { + path: null, + name: 'myDeclaration', + description: 'My declaration example.', + tags: [], + lineStart: 6, + lineEnd: 6, + } ); + const tokenObject = fs.readFileSync( + path.join( __dirname, './fixtures/named-identifier-destructuring/exports.json' ), + 'utf-8' + ); + const astObject = fs.readFileSync( + path.join( __dirname, './fixtures/named-identifier-destructuring/ast.json' ), + 'utf-8' + ); + const irNamedIdDestructuring = getIntermediateRepresentation( null, JSON.parse( tokenObject ), JSON.parse( astObject ) ); + expect( irNamedIdDestructuring ).toHaveLength( 1 ); + expect( irNamedIdDestructuring[ 0 ] ).toEqual( { + path: null, + name: 'myDeclaration', + description: 'My declaration example.', + tags: [], + lineStart: 6, + lineEnd: 6, + } ); + const tokens = fs.readFileSync( + path.join( __dirname, './fixtures/named-identifiers/exports.json' ), + 'utf-8' + ); + const asts = fs.readFileSync( + path.join( __dirname, './fixtures/named-identifiers/ast.json' ), + 'utf-8' + ); + const irIds = getIntermediateRepresentation( null, JSON.parse( tokens ), JSON.parse( asts ) ); + expect( irIds ).toHaveLength( 3 ); + expect( irIds[ 0 ] ).toEqual( + { path: null, name: 'functionDeclaration', description: 'Function declaration example.', tags: [], lineStart: 16, lineEnd: 16 } + ); + expect( irIds[ 1 ] ).toEqual( + { path: null, name: 'variableDeclaration', description: 'Variable declaration example.', tags: [], lineStart: 16, lineEnd: 16 } + ); + expect( irIds[ 2 ] ).toEqual( + { path: null, name: 'ClassDeclaration', description: 'Class declaration example.', tags: [], lineStart: 16, lineEnd: 16 } + ); + const foo = fs.readFileSync( + path.join( __dirname, './fixtures/named-identifiers-and-inline/exports.json' ), + 'utf-8' + ); + const bar = fs.readFileSync( + path.join( __dirname, './fixtures/named-identifiers-and-inline/ast.json' ), + 'utf-8' + ); + const irIdInline0 = getIntermediateRepresentation( null, JSON.parse( foo )[ 0 ], JSON.parse( bar ) ); + expect( irIdInline0 ).toHaveLength( 2 ); + expect( irIdInline0[ 0 ] ).toEqual( + { path: null, name: 'functionDeclaration', description: 'Function declaration example.', tags: [], lineStart: 11, lineEnd: 11 } + ); + expect( irIdInline0[ 1 ] ).toEqual( + { path: null, name: 'ClassDeclaration', description: 'Class declaration example.', tags: [], lineStart: 11, lineEnd: 11 } + ); + const irIdInline1 = getIntermediateRepresentation( null, JSON.parse( foo )[ 1 ], JSON.parse( bar ) ); + expect( irIdInline1[ 0 ] ).toEqual( + { path: null, name: 'variableDeclaration', description: 'Variable declaration example.', tags: [], lineStart: 16, lineEnd: 16 } + ); + } ); + } ); + + describe( 'JSDoc in module dependency', function() { + it( 'named export', function() { + const tokenImportNamed = fs.readFileSync( + path.join( __dirname, './fixtures/named-import-named/exports.json' ), + 'utf-8' + ); + const getModuleImportNamed = () => JSON.parse( fs.readFileSync( + path.join( __dirname, './fixtures/named-identifiers/ir.json' ), + 'utf-8' + ) ); + const ir = getIntermediateRepresentation( null, JSON.parse( tokenImportNamed ), { body: [] }, getModuleImportNamed ); + expect( ir ).toHaveLength( 3 ); + expect( ir[ 0 ] ).toEqual( + { path: null, name: 'functionDeclaration', description: 'Function declaration example.', tags: [], lineStart: 2, lineEnd: 2 } + ); + expect( ir[ 1 ] ).toEqual( + { path: null, name: 'variableDeclaration', description: 'Variable declaration example.', tags: [], lineStart: 3, lineEnd: 3 } + ); + expect( ir[ 2 ] ).toEqual( + { path: null, name: 'ClassDeclaration', description: 'Class declaration example.', tags: [], lineStart: 4, lineEnd: 4 } + + ); + } ); + + it( 'named default export', function() { + const tokenDefault = fs.readFileSync( + path.join( __dirname, './fixtures/named-default/exports.json' ), + 'utf-8' + ); + const getModule = () => JSON.parse( fs.readFileSync( + path.join( __dirname, './fixtures/named-default/module-ir.json' ), + 'utf-8' + ) ); + const irNamedDefault = getIntermediateRepresentation( null, JSON.parse( tokenDefault ), { body: [] }, getModule ); + expect( irNamedDefault ).toHaveLength( 1 ); + expect( irNamedDefault[ 0 ] ).toEqual( + { path: null, name: 'default', description: 'Module declaration.', tags: [], lineStart: 1, lineEnd: 1 } + ); + const tokenDefaultExported = fs.readFileSync( + path.join( __dirname, './fixtures/named-default-exported/exports.json' ), + 'utf-8' + ); + const irNamedDefaultExported = getIntermediateRepresentation( null, JSON.parse( tokenDefaultExported ), { body: [] }, getModule ); + expect( irNamedDefaultExported ).toHaveLength( 1 ); + expect( irNamedDefaultExported[ 0 ] ).toEqual( + { path: null, name: 'moduleName', description: 'Module declaration.', tags: [], lineStart: 1, lineEnd: 1 } + ); + } ); + + it( 'namespace export', function() { + const token = fs.readFileSync( + path.join( __dirname, './fixtures/namespace/exports.json' ), + 'utf-8' + ); + const getModule = () => JSON.parse( fs.readFileSync( + path.join( __dirname, './fixtures/namespace/module-ir.json' ), + 'utf-8' + ) ); + const irNamespace = getIntermediateRepresentation( null, JSON.parse( token ), { body: [] }, getModule ); + expect( irNamespace ).toHaveLength( 3 ); + expect( irNamespace[ 0 ] ).toEqual( + { path: null, name: 'MyClass', description: 'Named class.', tags: [], lineStart: 1, lineEnd: 1 } + ); + expect( irNamespace[ 1 ] ).toEqual( + { path: null, name: 'myFunction', description: 'Named function.', tags: [], lineStart: 1, lineEnd: 1 } + ); + expect( irNamespace[ 2 ] ).toEqual( + { path: null, name: 'myVariable', description: 'Named variable.', tags: [], lineStart: 1, lineEnd: 1 } + ); + const tokenCommented = fs.readFileSync( + path.join( __dirname, './fixtures/namespace-commented/exports.json' ), + 'utf-8' + ); + const irNamespaceCommented = getIntermediateRepresentation( null, JSON.parse( tokenCommented ), { body: [] }, getModule ); + expect( irNamespaceCommented ).toHaveLength( 3 ); + expect( irNamespaceCommented[ 0 ] ).toEqual( + { path: null, name: 'MyClass', description: 'Named class.', tags: [], lineStart: 4, lineEnd: 4 } + ); + expect( irNamespaceCommented[ 1 ] ).toEqual( + { path: null, name: 'myFunction', description: 'Named function.', tags: [], lineStart: 4, lineEnd: 4 } + ); + expect( irNamespaceCommented[ 2 ] ).toEqual( + { path: null, name: 'myVariable', description: 'Named variable.', tags: [], lineStart: 4, lineEnd: 4 } + ); + } ); + } ); + + describe( 'JSDoc in module dependency through import', function() { + it( 'default export', function() { + const tokenDefault = fs.readFileSync( + path.join( __dirname, './fixtures/default-import-default/exports.json' ), + 'utf-8' + ); + const astDefault = fs.readFileSync( + path.join( __dirname, './fixtures/default-import-default/ast.json' ), + 'utf-8' + ); + const getModuleDefault = () => JSON.parse( fs.readFileSync( + path.join( __dirname, './fixtures/default-import-default/module-ir.json' ), + 'utf-8' + ) ); + const irDefault = getIntermediateRepresentation( null, JSON.parse( tokenDefault ), JSON.parse( astDefault ), getModuleDefault ); + expect( irDefault ).toHaveLength( 1 ); + expect( irDefault[ 0 ] ).toEqual( { + path: null, + name: 'default', + description: 'Function declaration.', + tags: [], + lineStart: 3, + lineEnd: 3, + } ); + const tokenNamed = fs.readFileSync( + path.join( __dirname, './fixtures/default-import-named/exports.json' ), + 'utf-8' + ); + const astNamed = fs.readFileSync( + path.join( __dirname, './fixtures/default-import-named/ast.json' ), + 'utf-8' + ); + const getModuleNamed = () => JSON.parse( fs.readFileSync( + path.join( __dirname, './fixtures/default-import-named/module-ir.json' ), + 'utf-8' + ) ); + const irNamed = getIntermediateRepresentation( null, JSON.parse( tokenNamed ), JSON.parse( astNamed ), getModuleNamed ); + expect( irNamed ).toHaveLength( 1 ); + expect( irNamed[ 0 ] ).toEqual( { + path: null, + name: 'default', + description: 'Function declaration.', + tags: [], + lineStart: 3, + lineEnd: 3, + } ); + } ); + + it( 'named export', function() { + const tokenImportNamespace = fs.readFileSync( + path.join( __dirname, './fixtures/named-import-namespace/exports.json' ), + 'utf-8' + ); + const astImportNamespace = fs.readFileSync( + path.join( __dirname, './fixtures/named-import-namespace/ast.json' ), + 'utf-8' + ); + const getModuleImportNamespace = ( filePath ) => { + if ( filePath === './named-import-namespace-module' ) { + return JSON.parse( fs.readFileSync( + path.join( __dirname, './fixtures/named-import-namespace/module-ir.json' ), + 'utf-8' + ) ); + } + return JSON.parse( fs.readFileSync( + path.join( __dirname, './fixtures/default-function/ir.json' ), + 'utf-8' + ) ); + }; + const ir = getIntermediateRepresentation( null, JSON.parse( tokenImportNamespace ), JSON.parse( astImportNamespace ), getModuleImportNamespace ); + expect( ir ).toHaveLength( 1 ); + expect( ir[ 0 ] ).toEqual( + { path: null, name: 'variables', description: 'Undocumented declaration.', tags: [], lineStart: 3, lineEnd: 3 } + ); + } ); + } ); +} ); diff --git a/packages/docgen/src/test/get-jsdoc-from-token.js b/packages/docgen/src/test/get-jsdoc-from-token.js new file mode 100644 index 0000000000000..335d63e7935ac --- /dev/null +++ b/packages/docgen/src/test/get-jsdoc-from-token.js @@ -0,0 +1,78 @@ +/** + * Internal dependencies. + */ +const getJSDocFromToken = require( '../get-jsdoc-from-token' ); + +describe( 'JSDoc', () => { + it( 'extracts description and tags', () => { + expect( + getJSDocFromToken( { + leadingComments: [ { + value: '*\n * A function that adds two parameters.\n *\n * @deprecated Use native addition instead.\n * @since v2\n *\n * @see addition\n * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators\n *\n * @param {number} firstParam The first param to add.\n * @param {number} secondParam The second param to add.\n *\n * @example\n *\n * ```js\n * const addResult = sum( 1, 3 );\n * console.log( addResult ); // will yield 4\n * ```\n *\n * @return {number} The result of adding the two params.\n ', + } ], + } ) + ).toEqual( + { + description: 'A function that adds two parameters.', + tags: [ + { + title: 'deprecated', + description: 'Use native addition instead.', + }, + { + title: 'since', + description: 'v2', + }, + { + title: 'see', + description: 'addition', + }, + { + title: 'link', + description: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators', + }, + { + title: 'param', + description: 'The first param to add.', + type: 'number', + name: 'firstParam', + }, + { + title: 'param', + description: 'The second param to add.', + type: 'number', + name: 'secondParam', + }, + { + title: 'example', + description: '```js\nconst addResult = sum( 1, 3 );\nconsole.log( addResult ); // will yield 4\n```', + }, + { + title: 'return', + description: 'The result of adding the two params.', + type: 'number', + }, + ], + } + ); + + expect( + getJSDocFromToken( { + leadingComments: [ { + value: '*\n * Constant to document the meaning of life,\n * the universe and everything else.\n *\n * @type {number}\n ', + } ], + } ) + ).toEqual( + { + description: 'Constant to document the meaning of life,\nthe universe and everything else.', + tags: [ + { + title: 'type', + description: null, + type: 'number', + }, + ], + } + ); + } ); +} ); diff --git a/packages/docgen/src/test/get-type-as-string.js b/packages/docgen/src/test/get-type-as-string.js new file mode 100644 index 0000000000000..6ba09acd33df1 --- /dev/null +++ b/packages/docgen/src/test/get-type-as-string.js @@ -0,0 +1,108 @@ +/** + * Internal dependencies. + */ +const getType = require( '../get-type-as-string' ); + +describe( 'getType from JSDoc', () => { + it( 'NameExpression', () => { + const type = getType( { + title: 'param', + description: 'description', + type: { + type: 'NameExpression', + name: 'Array', + }, + name: 'paramName', + } ); + expect( type ).toBe( 'Array' ); + } ); + + it( 'AllLiteral', () => { + const type = getType( { + title: 'param', + description: 'description', + type: { + type: 'AllLiteral', + }, + name: 'paramName', + } ); + expect( type ).toBe( '*' ); + } ); + + it( 'Applications', () => { + const type = getType( { + title: 'param', + description: 'description', + type: { + type: 'TypeApplication', + expression: { + type: 'NameExpression', + name: 'Array', + }, + applications: [ + { + type: 'NameExpression', + name: 'Object', + }, + { + type: 'NameExpression', + name: 'String', + }, + ], + }, + } ); + expect( type ).toBe( 'Array<Object,String>' ); + } ); + + it( 'NullableType', () => { + const type = getType( { + title: 'param', + description: 'description', + type: { + type: 'NullableType', + expression: { + type: 'NameExpression', + name: 'string', + }, + prefix: true, + }, + name: 'paramName', + } ); + expect( type ).toBe( '?string' ); + } ); + + it( 'RestType', () => { + const type = getType( { + title: 'param', + description: 'description', + type: { + type: 'RestType', + expression: { + type: 'NameExpression', + name: 'Function', + }, + }, + name: 'paramName', + } ); + expect( type ).toBe( '...Function' ); + } ); + + it( 'RestType with UnionType', () => { + const type = getType( { + title: 'param', + description: 'description', + type: { + type: 'RestType', + expression: { + type: 'UnionType', + elements: [ + { type: 'NameExpression', name: 'Object' }, + { type: 'NameExpression', name: 'string' }, + ], + }, + }, + name: 'paramName', + } ); + expect( type ).toBe( '...(Object|string)' ); + } ); +} ); diff --git a/packages/e2e-test-utils/README.md b/packages/e2e-test-utils/README.md index 6743b099d8abb..4958201be7297 100644 --- a/packages/e2e-test-utils/README.md +++ b/packages/e2e-test-utils/README.md @@ -12,6 +12,8 @@ npm install @wordpress/e2e-test-utils --save-dev ## API +<!-- START TOKEN(Autogenerated API docs) --> + ### activatePlugin [src/index.js#L1-L1](src/index.js#L1-L1) @@ -76,7 +78,7 @@ Clicks on More Menu item, searches for the button with the text provided and cli ### createEmbeddingMatcher -[src/index.js#L45-L45](src/index.js#L45-L45) +[src/index.js#L46-L46](src/index.js#L46-L46) Creates a function to determine if a request is embedding a certain URL. @@ -90,7 +92,7 @@ Creates a function to determine if a request is embedding a certain URL. ### createJSONResponse -[src/index.js#L45-L45](src/index.js#L45-L45) +[src/index.js#L46-L46](src/index.js#L46-L46) Respond to a request with a JSON response. @@ -129,7 +131,7 @@ Creates new URL by parsing base URL, WPPath and query string. ### createURLMatcher -[src/index.js#L45-L45](src/index.js#L45-L45) +[src/index.js#L46-L46](src/index.js#L46-L46) Creates a function to determine if a request is calling a URL with the substring present. @@ -180,7 +182,7 @@ Verifies that the edit post sidebar is opened, and if it is not, opens it. `Promise` Promise resolving once the edit post sidebar is opened. -### findSidebarPanelWithTitle +### findSidebarPanelToggleButtonWithTitle [src/index.js#L15-L15](src/index.js#L15-L15) @@ -194,10 +196,24 @@ Finds a sidebar panel with the provided title. `?ElementHandle` Object that represents an in-page DOM element. -### getAllBlocks +### findSidebarPanelWithTitle [src/index.js#L16-L16](src/index.js#L16-L16) +Finds the button responsible for toggling the sidebar panel with the provided title. + +**Parameters** + +- **panelTitle** `string`: The name of sidebar panel. + +**Returns** + +`?ElementHandle` Object that represents an in-page DOM element. + +### getAllBlocks + +[src/index.js#L17-L17](src/index.js#L17-L17) + Returns an array with all blocks; Equivalent to calling wp.data.select( 'core/editor' ).getBlocks(); **Returns** @@ -206,7 +222,7 @@ Returns an array with all blocks; Equivalent to calling wp.data.select( 'core/ed ### getAvailableBlockTransforms -[src/index.js#L17-L17](src/index.js#L17-L17) +[src/index.js#L18-L18](src/index.js#L18-L18) Returns an array of strings with all block titles, that the current selected block can be transformed into. @@ -217,7 +233,7 @@ that the current selected block can be transformed into. ### getEditedPostContent -[src/index.js#L18-L18](src/index.js#L18-L18) +[src/index.js#L19-L19](src/index.js#L19-L19) Returns a promise which resolves with the edited post content (HTML string). @@ -227,7 +243,7 @@ Returns a promise which resolves with the edited post content (HTML string). ### hasBlockSwitcher -[src/index.js#L19-L19](src/index.js#L19-L19) +[src/index.js#L20-L20](src/index.js#L20-L20) Returns a boolean indicating if the current selected block has a block switcher or not. @@ -237,7 +253,7 @@ Returns a boolean indicating if the current selected block has a block switcher ### insertBlock -[src/index.js#L20-L20](src/index.js#L20-L20) +[src/index.js#L21-L21](src/index.js#L21-L21) Opens the inserter, searches for the given term, then selects the first result that appears. @@ -249,7 +265,7 @@ result that appears. ### installPlugin -[src/index.js#L21-L21](src/index.js#L21-L21) +[src/index.js#L22-L22](src/index.js#L22-L22) Installs a plugin from the WP.org repository. @@ -260,7 +276,7 @@ Installs a plugin from the WP.org repository. ### isCurrentURL -[src/index.js#L22-L22](src/index.js#L22-L22) +[src/index.js#L23-L23](src/index.js#L23-L23) Checks if current URL is a WordPress path. @@ -275,7 +291,7 @@ Checks if current URL is a WordPress path. ### loginUser -[src/index.js#L23-L23](src/index.js#L23-L23) +[src/index.js#L24-L24](src/index.js#L24-L24) Performs log in with specified username and password. @@ -286,7 +302,7 @@ Performs log in with specified username and password. ### mockOrTransform -[src/index.js#L45-L45](src/index.js#L45-L45) +[src/index.js#L46-L46](src/index.js#L46-L46) Mocks a request with the supplied mock object, or allows it to run with an optional transform, based on the deserialised JSON response for the request. @@ -303,26 +319,26 @@ deserialised JSON response for the request. ### observeFocusLoss -[src/index.js#L24-L24](src/index.js#L24-L24) +[src/index.js#L25-L25](src/index.js#L25-L25) Binds to the document on page load which throws an error if a `focusout` event occurs without a related target (i.e. focus loss). ### openDocumentSettingsSidebar -[src/index.js#L25-L25](src/index.js#L25-L25) +[src/index.js#L26-L26](src/index.js#L26-L26) Clicks on the button in the header which opens Document Settings sidebar when it is closed. ### openPublishPanel -[src/index.js#L26-L26](src/index.js#L26-L26) +[src/index.js#L27-L27](src/index.js#L27-L27) Opens the publish panel. ### pressKeyTimes -[src/index.js#L27-L27](src/index.js#L27-L27) +[src/index.js#L28-L28](src/index.js#L28-L28) Presses the given keyboard key a number of times in sequence. @@ -337,7 +353,7 @@ Presses the given keyboard key a number of times in sequence. ### pressKeyWithModifier -[src/index.js#L28-L28](src/index.js#L28-L28) +[src/index.js#L29-L29](src/index.js#L29-L29) Performs a key press with modifier (Shift, Control, Meta, Alt), where each modifier is normalized to platform-specific modifier. @@ -349,7 +365,7 @@ is normalized to platform-specific modifier. ### publishPost -[src/index.js#L29-L29](src/index.js#L29-L29) +[src/index.js#L30-L30](src/index.js#L30-L30) Publishes the post, resolving once the request is complete (once a notice is displayed). @@ -360,7 +376,7 @@ is displayed). ### publishPostWithPrePublishChecksDisabled -[src/index.js#L30-L30](src/index.js#L30-L30) +[src/index.js#L31-L31](src/index.js#L31-L31) Publishes the post without the pre-publish checks, resolving once the request is complete (once a notice is displayed). @@ -371,7 +387,7 @@ resolving once the request is complete (once a notice is displayed). ### saveDraft -[src/index.js#L31-L31](src/index.js#L31-L31) +[src/index.js#L32-L32](src/index.js#L32-L32) Saves the post as a draft, resolving once the request is complete (once the "Saved" indicator is displayed). @@ -382,7 +398,7 @@ Saves the post as a draft, resolving once the request is complete (once the ### searchForBlock -[src/index.js#L32-L32](src/index.js#L32-L32) +[src/index.js#L33-L33](src/index.js#L33-L33) Search for block in the global inserter @@ -392,7 +408,7 @@ Search for block in the global inserter ### selectBlockByClientId -[src/index.js#L33-L33](src/index.js#L33-L33) +[src/index.js#L34-L34](src/index.js#L34-L34) Given the clientId of a block, selects the block on the editor. @@ -402,7 +418,7 @@ Given the clientId of a block, selects the block on the editor. ### setBrowserViewport -[src/index.js#L34-L34](src/index.js#L34-L34) +[src/index.js#L35-L35](src/index.js#L35-L35) Sets browser viewport to specified type. @@ -412,7 +428,7 @@ Sets browser viewport to specified type. ### setPostContent -[src/index.js#L35-L35](src/index.js#L35-L35) +[src/index.js#L36-L36](src/index.js#L36-L36) Sets code editor content @@ -426,7 +442,7 @@ Sets code editor content ### setUpResponseMocking -[src/index.js#L45-L45](src/index.js#L45-L45) +[src/index.js#L46-L46](src/index.js#L46-L46) Sets up mock checks and responses. Accepts a list of mock settings with the following properties: @@ -457,7 +473,7 @@ If none of the mock settings match the request, the request is allowed to contin ### switchEditorModeTo -[src/index.js#L36-L36](src/index.js#L36-L36) +[src/index.js#L37-L37](src/index.js#L37-L37) Switches editor mode. @@ -467,21 +483,21 @@ Switches editor mode. ### switchUserToAdmin -[src/index.js#L37-L37](src/index.js#L37-L37) +[src/index.js#L38-L38](src/index.js#L38-L38) Switches the current user to the admin user (if the user running the test is not already the admin user). ### switchUserToTest -[src/index.js#L38-L38](src/index.js#L38-L38) +[src/index.js#L39-L39](src/index.js#L39-L39) Switches the current user to whichever user we should be running the tests as (if we're not already that user). ### toggleScreenOption -[src/index.js#L39-L39](src/index.js#L39-L39) +[src/index.js#L40-L40](src/index.js#L40-L40) Toggles the screen option with the given label. @@ -492,7 +508,7 @@ Toggles the screen option with the given label. ### transformBlockTo -[src/index.js#L40-L40](src/index.js#L40-L40) +[src/index.js#L41-L41](src/index.js#L41-L41) Converts editor's block type. @@ -502,7 +518,7 @@ Converts editor's block type. ### uninstallPlugin -[src/index.js#L41-L41](src/index.js#L41-L41) +[src/index.js#L42-L42](src/index.js#L42-L42) Uninstalls a plugin. @@ -512,7 +528,7 @@ Uninstalls a plugin. ### visitAdminPage -[src/index.js#L42-L42](src/index.js#L42-L42) +[src/index.js#L43-L43](src/index.js#L43-L43) Visits admin page; if user is not logged in then it logging in it first, then visits admin page. @@ -523,7 +539,7 @@ Visits admin page; if user is not logged in then it logging in it first, then vi ### waitForWindowDimensions -[src/index.js#L43-L43](src/index.js#L43-L43) +[src/index.js#L44-L44](src/index.js#L44-L44) Function that waits until the page viewport has the required dimensions. It is being used to address a problem where after using setViewport the execution may continue, @@ -535,4 +551,7 @@ without the new dimensions being applied. - **width** `number`: Width of the window. - **height** `height`: Height of the window. + +<!-- END TOKEN(Autogenerated API docs) --> + <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/e2e-test-utils/package.json b/packages/e2e-test-utils/package.json index f31d67677475e..0a6871bff1d76 100644 --- a/packages/e2e-test-utils/package.json +++ b/packages/e2e-test-utils/package.json @@ -33,11 +33,17 @@ "lodash": "^4.17.11", "node-fetch": "^1.7.3" }, + "devDependencies": { + "@wordpress/docgen": "file:../docgen" + }, "peerDependencies": { "jest": ">=24", "puppeteer": ">=1.6" }, "publishConfig": { "access": "public" + }, + "scripts": { + "docs:generate": "docgen ./src/index.js --output ./README.md --to-token" } } From 35815e05a17f36f26d99257dbf6bcc8421921b6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Fri, 1 Mar 2019 18:51:34 +0100 Subject: [PATCH 541/691] Update nosolosw notifs (#14196) --- .github/CODEOWNERS | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 945b03b7b08d8..ce69bf49c1b59 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -23,11 +23,12 @@ # Tooling /bin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra -/packages/babel-plugin-import-jsx-pragma @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra +/packages/babel-plugin-import-jsx-pragma @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw /packages/babel-plugin-makepot @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra -/packages/babel-preset-default @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra +/packages/babel-preset-default @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw /packages/browserslist-config @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra /packages/custom-templated-path-webpack-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra +/packages/docgen @nosolosw /packages/e2e-test-utils @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra /packages/e2e-tests @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @talldan /packages/eslint-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw From aff8782e277350ccc8d2d2f0f67c13c3c670de55 Mon Sep 17 00:00:00 2001 From: Andrea Fercia <a.fercia@gmail.com> Date: Fri, 1 Mar 2019 19:34:49 +0100 Subject: [PATCH 542/691] Make IconButton able to be referenced. (#14163) * Make IconButton able to get referenced. * Components: Forward IconButton ref as stateless function * Components: Restore IconButton aria-pressed prop pass-through --- .../src/form-file-upload/test/index.js | 2 +- packages/components/src/icon-button/index.js | 94 ++++++++++--------- .../components/src/icon-button/test/index.js | 21 ++++- .../test/__snapshots__/index.js.snap | 8 +- .../notice/test/__snapshots__/index.js.snap | 2 +- .../test/__snapshots__/index.js.snap | 4 +- .../src/components/block-mover/test/index.js | 4 +- .../test/__snapshots__/index.js.snap | 4 +- .../components/block-switcher/test/index.js | 4 +- .../test/__snapshots__/index.js.snap | 10 +- .../src/components/url-input/test/button.js | 6 +- .../test/__snapshots__/index.js.snap | 4 +- .../dot-tip/test/__snapshots__/index.js.snap | 2 +- .../nux/src/components/dot-tip/test/index.js | 2 +- 14 files changed, 96 insertions(+), 71 deletions(-) diff --git a/packages/components/src/form-file-upload/test/index.js b/packages/components/src/form-file-upload/test/index.js index bbd843a640827..5a81c898d81bb 100644 --- a/packages/components/src/form-file-upload/test/index.js +++ b/packages/components/src/form-file-upload/test/index.js @@ -22,7 +22,7 @@ describe( 'InserterMenu', () => { </FormFileUpload> ); - const iconButton = wrapper.find( 'IconButton' ); + const iconButton = wrapper.find( 'ForwardRef(IconButton)' ); const input = wrapper.find( 'input' ); expect( iconButton.prop( 'children' ) ).toBe( 'My Upload Button' ); expect( input.prop( 'style' ).display ).toBe( 'none' ); diff --git a/packages/components/src/icon-button/index.js b/packages/components/src/icon-button/index.js index 7b0a4ae3541a9..f64d7f6777e93 100644 --- a/packages/components/src/icon-button/index.js +++ b/packages/components/src/icon-button/index.js @@ -7,7 +7,7 @@ import { isArray, isString } from 'lodash'; /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; +import { forwardRef } from '@wordpress/element'; /** * Internal dependencies @@ -16,50 +16,60 @@ import Tooltip from '../tooltip'; import Button from '../button'; import Dashicon from '../dashicon'; -// This is intentionally a Component class, not a function component because it -// is common to apply a ref to the button element (only supported in class) -class IconButton extends Component { - render() { - const { icon, children, label, className, tooltip, shortcut, labelPosition, ...additionalProps } = this.props; - const { 'aria-pressed': ariaPressed } = this.props; - const classes = classnames( 'components-icon-button', className, { - 'has-text': children, - } ); - const tooltipText = tooltip || label; +function IconButton( props, ref ) { + const { + icon, + children, + label, + className, + tooltip, + shortcut, + labelPosition, + ...additionalProps + } = props; + const { 'aria-pressed': ariaPressed } = additionalProps; + const classes = classnames( 'components-icon-button', className, { + 'has-text': children, + } ); + const tooltipText = tooltip || label; - // Should show the tooltip if... - const showTooltip = ! additionalProps.disabled && ( - // an explicit tooltip is passed or... - tooltip || - // there's a shortcut or... - shortcut || - ( - // there's a label and... - !! label && - // the children are empty and... - ( ! children || ( isArray( children ) && ! children.length ) ) && - // the tooltip is not explicitly disabled. - false !== tooltip - ) - ); - - let element = ( - <Button aria-label={ label } { ...additionalProps } className={ classes }> - { isString( icon ) ? <Dashicon icon={ icon } ariaPressed={ ariaPressed } /> : icon } - { children } - </Button> - ); + // Should show the tooltip if... + const showTooltip = ! additionalProps.disabled && ( + // an explicit tooltip is passed or... + tooltip || + // there's a shortcut or... + shortcut || + ( + // there's a label and... + !! label && + // the children are empty and... + ( ! children || ( isArray( children ) && ! children.length ) ) && + // the tooltip is not explicitly disabled. + false !== tooltip + ) + ); - if ( showTooltip ) { - element = ( - <Tooltip text={ tooltipText } shortcut={ shortcut } position={ labelPosition }> - { element } - </Tooltip> - ); - } + let element = ( + <Button + aria-label={ label } + { ...additionalProps } + className={ classes } + ref={ ref } + > + { isString( icon ) ? <Dashicon icon={ icon } ariaPressed={ ariaPressed } /> : icon } + { children } + </Button> + ); - return element; + if ( showTooltip ) { + element = ( + <Tooltip text={ tooltipText } shortcut={ shortcut } position={ labelPosition }> + { element } + </Tooltip> + ); } + + return element; } -export default IconButton; +export default forwardRef( IconButton ); diff --git a/packages/components/src/icon-button/test/index.js b/packages/components/src/icon-button/test/index.js index 133224941c1b5..e824ed1d556ec 100644 --- a/packages/components/src/icon-button/test/index.js +++ b/packages/components/src/icon-button/test/index.js @@ -2,6 +2,12 @@ * External dependencies */ import { shallow } from 'enzyme'; +import TestUtils from 'react-dom/test-utils'; + +/** + * WordPress dependencies + */ +import { createRef } from '@wordpress/element'; /** * Internal dependencies @@ -49,9 +55,11 @@ describe( 'IconButton', () => { expect( iconButton.hasClass( 'test' ) ).toBe( true ); } ); - it( 'should add an additonal prop to the IconButton element', () => { - const iconButton = shallow( <IconButton test="test" /> ); - expect( iconButton.props().test ).toBe( 'test' ); + it( 'should pass additional props to the underlying button', () => { + const iconButton = shallow( <IconButton disabled aria-pressed="true" /> ); + + expect( iconButton.find( 'ForwardRef(Button)' ).prop( 'aria-pressed' ) ).toBe( 'true' ); + expect( iconButton.find( 'ForwardRef(Button)' ).prop( 'disabled' ) ).toBe( true ); } ); it( 'should allow custom tooltip text', () => { @@ -72,5 +80,12 @@ describe( 'IconButton', () => { expect( iconButton.name() ).toBe( 'Tooltip' ); expect( iconButton.prop( 'text' ) ).toBe( 'WordPress' ); } ); + + it( 'forwards ref', () => { + const ref = createRef(); + + TestUtils.renderIntoDocument( <IconButton ref={ ref } /> ); + expect( ref.current.type ).toBe( 'button' ); + } ); } ); } ); diff --git a/packages/components/src/menu-item/test/__snapshots__/index.js.snap b/packages/components/src/menu-item/test/__snapshots__/index.js.snap index caa467a67a66b..a0f00b2889f00 100644 --- a/packages/components/src/menu-item/test/__snapshots__/index.js.snap +++ b/packages/components/src/menu-item/test/__snapshots__/index.js.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`MenuItem should match snapshot when all props provided 1`] = ` -<IconButton +<ForwardRef(IconButton) aria-checked={true} className="components-menu-item__button my-class has-icon" icon="wordpress" @@ -13,7 +13,7 @@ exports[`MenuItem should match snapshot when all props provided 1`] = ` className="components-menu-item__shortcut" shortcut="mod+shift+alt+w" /> -</IconButton> +</ForwardRef(IconButton)> `; exports[`MenuItem should match snapshot when info is provided 1`] = ` @@ -40,7 +40,7 @@ exports[`MenuItem should match snapshot when info is provided 1`] = ` `; exports[`MenuItem should match snapshot when isSelected and role are optionally provided 1`] = ` -<IconButton +<ForwardRef(IconButton) className="components-menu-item__button my-class has-icon" icon="wordpress" onClick={[Function]} @@ -51,7 +51,7 @@ exports[`MenuItem should match snapshot when isSelected and role are optionally className="components-menu-item__shortcut" shortcut="mod+shift+alt+w" /> -</IconButton> +</ForwardRef(IconButton)> `; exports[`MenuItem should match snapshot when only label provided 1`] = ` diff --git a/packages/components/src/notice/test/__snapshots__/index.js.snap b/packages/components/src/notice/test/__snapshots__/index.js.snap index 28b35679ceec6..fe2329681dd54 100644 --- a/packages/components/src/notice/test/__snapshots__/index.js.snap +++ b/packages/components/src/notice/test/__snapshots__/index.js.snap @@ -17,7 +17,7 @@ exports[`Notice should match snapshot 1`] = ` View </ForwardRef(Button)> </div> - <IconButton + <ForwardRef(IconButton) className="components-notice__dismiss" icon="no" label="Dismiss this notice" diff --git a/packages/edit-post/src/components/header/more-menu/test/__snapshots__/index.js.snap b/packages/edit-post/src/components/header/more-menu/test/__snapshots__/index.js.snap index 9e8a474f6f921..78f197fda6035 100644 --- a/packages/edit-post/src/components/header/more-menu/test/__snapshots__/index.js.snap +++ b/packages/edit-post/src/components/header/more-menu/test/__snapshots__/index.js.snap @@ -12,7 +12,7 @@ exports[`MoreMenu should match snapshot 1`] = ` <div className="edit-post-more-menu" > - <IconButton + <ForwardRef(IconButton) aria-expanded={false} icon="ellipsis" label="Show more tools & options" @@ -81,7 +81,7 @@ exports[`MoreMenu should match snapshot 1`] = ` </button> </ForwardRef(Button)> </Tooltip> - </IconButton> + </ForwardRef(IconButton)> </div> </Dropdown> </MoreMenu> diff --git a/packages/editor/src/components/block-mover/test/index.js b/packages/editor/src/components/block-mover/test/index.js index ea13280f4d311..ffb4ef6ab8198 100644 --- a/packages/editor/src/components/block-mover/test/index.js +++ b/packages/editor/src/components/block-mover/test/index.js @@ -37,9 +37,9 @@ describe( 'BlockMover', () => { const moveDown = blockMover.childAt( 2 ); const moveUpDesc = blockMover.childAt( 3 ); const moveDownDesc = blockMover.childAt( 4 ); - expect( moveUp.type().name ).toBe( 'IconButton' ); + expect( moveUp.name() ).toBe( 'ForwardRef(IconButton)' ); expect( drag.type().name ).toBe( 'IconDragHandle' ); - expect( moveDown.type().name ).toBe( 'IconButton' ); + expect( moveDown.name() ).toBe( 'ForwardRef(IconButton)' ); expect( moveUp.props() ).toMatchObject( { className: 'editor-block-mover__control', onClick: undefined, diff --git a/packages/editor/src/components/block-switcher/test/__snapshots__/index.js.snap b/packages/editor/src/components/block-switcher/test/__snapshots__/index.js.snap index 9e03c4e31fb0d..df5a8d415abad 100644 --- a/packages/editor/src/components/block-switcher/test/__snapshots__/index.js.snap +++ b/packages/editor/src/components/block-switcher/test/__snapshots__/index.js.snap @@ -2,7 +2,7 @@ exports[`BlockSwitcher should render disabled block switcher with multi block of different types when no transforms 1`] = ` <Toolbar> - <IconButton + <ForwardRef(IconButton) className="editor-block-switcher__no-switcher-icon" disabled={true} label="Block icon" @@ -11,7 +11,7 @@ exports[`BlockSwitcher should render disabled block switcher with multi block of icon="layout" showColors={true} /> - </IconButton> + </ForwardRef(IconButton)> </Toolbar> `; diff --git a/packages/editor/src/components/block-switcher/test/index.js b/packages/editor/src/components/block-switcher/test/index.js index 47f784240db2b..544d403872206 100644 --- a/packages/editor/src/components/block-switcher/test/index.js +++ b/packages/editor/src/components/block-switcher/test/index.js @@ -171,7 +171,7 @@ describe( 'BlockSwitcher', () => { test( 'should simulate a keydown event, which should call onToggle and open transform toggle.', () => { const toggleClosed = shallow( getDropdown().props().renderToggle( { onToggle: onToggleStub, isOpen: false } ) ); - const iconButtonClosed = toggleClosed.find( 'IconButton' ); + const iconButtonClosed = toggleClosed.find( 'ForwardRef(IconButton)' ); iconButtonClosed.simulate( 'keydown', mockKeyDown ); @@ -180,7 +180,7 @@ describe( 'BlockSwitcher', () => { test( 'should simulate a click event, which should call onToggle.', () => { const toggleOpen = shallow( getDropdown().props().renderToggle( { onToggle: onToggleStub, isOpen: true } ) ); - const iconButtonOpen = toggleOpen.find( 'IconButton' ); + const iconButtonOpen = toggleOpen.find( 'ForwardRef(IconButton)' ); iconButtonOpen.simulate( 'keydown', mockKeyDown ); diff --git a/packages/editor/src/components/post-publish-panel/test/__snapshots__/index.js.snap b/packages/editor/src/components/post-publish-panel/test/__snapshots__/index.js.snap index 574787b931a8d..752b03174a55f 100644 --- a/packages/editor/src/components/post-publish-panel/test/__snapshots__/index.js.snap +++ b/packages/editor/src/components/post-publish-panel/test/__snapshots__/index.js.snap @@ -12,7 +12,7 @@ exports[`PostPublishPanel should render the post-publish panel if the post is pu > Published </div> - <IconButton + <ForwardRef(IconButton) aria-expanded={true} icon="no-alt" label="Close panel" @@ -47,7 +47,7 @@ exports[`PostPublishPanel should render the post-publish panel if the post is sc > Scheduled </div> - <IconButton + <ForwardRef(IconButton) aria-expanded={true} icon="no-alt" label="Close panel" @@ -88,7 +88,7 @@ exports[`PostPublishPanel should render the pre-publish panel if post status is className="editor-post-publish-panel__spacer" /> </div> - <IconButton + <ForwardRef(IconButton) aria-expanded={true} icon="no-alt" label="Close panel" @@ -127,7 +127,7 @@ exports[`PostPublishPanel should render the pre-publish panel if the post is not className="editor-post-publish-panel__spacer" /> </div> - <IconButton + <ForwardRef(IconButton) aria-expanded={true} icon="no-alt" label="Close panel" @@ -166,7 +166,7 @@ exports[`PostPublishPanel should render the spinner if the post is being saved 1 className="editor-post-publish-panel__spacer" /> </div> - <IconButton + <ForwardRef(IconButton) aria-expanded={true} icon="no-alt" label="Close panel" diff --git a/packages/editor/src/components/url-input/test/button.js b/packages/editor/src/components/url-input/test/button.js index 5cf225d17da56..b054a1a6ba14e 100644 --- a/packages/editor/src/components/url-input/test/button.js +++ b/packages/editor/src/components/url-input/test/button.js @@ -12,7 +12,7 @@ import URLInput from '../'; import URLInputButton from '../button'; describe( 'URLInputButton', () => { - const clickEditLink = ( wrapper ) => wrapper.find( 'IconButton.components-toolbar__control' ).simulate( 'click' ); + const clickEditLink = ( wrapper ) => wrapper.find( 'ForwardRef(IconButton).components-toolbar__control' ).simulate( 'click' ); it( 'should have a valid class name in the wrapper tag', () => { const wrapper = shallow( <URLInputButton /> ); @@ -20,11 +20,11 @@ describe( 'URLInputButton', () => { } ); it( 'should not have is-active class when url prop not defined', () => { const wrapper = shallow( <URLInputButton /> ); - expect( wrapper.find( 'IconButton' ).hasClass( 'is-active' ) ).toBe( false ); + expect( wrapper.find( 'ForwardRef(IconButton)' ).hasClass( 'is-active' ) ).toBe( false ); } ); it( 'should have is-active class name if url prop defined', () => { const wrapper = shallow( <URLInputButton url="https://example.com" /> ); - expect( wrapper.find( 'IconButton' ).hasClass( 'is-active' ) ).toBe( true ); + expect( wrapper.find( 'ForwardRef(IconButton)' ).hasClass( 'is-active' ) ).toBe( true ); } ); it( 'should have hidden form by default', () => { const wrapper = shallow( <URLInputButton /> ); diff --git a/packages/editor/src/components/url-popover/test/__snapshots__/index.js.snap b/packages/editor/src/components/url-popover/test/__snapshots__/index.js.snap index f8f0aa9027bdc..ccb4526f87457 100644 --- a/packages/editor/src/components/url-popover/test/__snapshots__/index.js.snap +++ b/packages/editor/src/components/url-popover/test/__snapshots__/index.js.snap @@ -13,7 +13,7 @@ exports[`URLPopover matches the snapshot in its default state 1`] = ` <div> Editor </div> - <IconButton + <ForwardRef(IconButton) aria-expanded={false} className="editor-url-popover__settings-toggle" icon="arrow-down-alt2" @@ -37,7 +37,7 @@ exports[`URLPopover matches the snapshot when the settings are toggled open 1`] <div> Editor </div> - <IconButton + <ForwardRef(IconButton) aria-expanded={true} className="editor-url-popover__settings-toggle" icon="arrow-down-alt2" diff --git a/packages/nux/src/components/dot-tip/test/__snapshots__/index.js.snap b/packages/nux/src/components/dot-tip/test/__snapshots__/index.js.snap index 155eac26c893a..192cfe76fb74e 100644 --- a/packages/nux/src/components/dot-tip/test/__snapshots__/index.js.snap +++ b/packages/nux/src/components/dot-tip/test/__snapshots__/index.js.snap @@ -21,7 +21,7 @@ exports[`DotTip should render correctly 1`] = ` Got it </ForwardRef(Button)> </p> - <IconButton + <ForwardRef(IconButton) className="nux-dot-tip__disable" icon="no-alt" label="Disable tips" diff --git a/packages/nux/src/components/dot-tip/test/index.js b/packages/nux/src/components/dot-tip/test/index.js index 2f4736be1316c..b8821f0215e06 100644 --- a/packages/nux/src/components/dot-tip/test/index.js +++ b/packages/nux/src/components/dot-tip/test/index.js @@ -46,7 +46,7 @@ describe( 'DotTip', () => { It looks like you’re writing a letter. Would you like help? </DotTip> ); - wrapper.find( 'IconButton[label="Disable tips"]' ).first().simulate( 'click' ); + wrapper.find( 'ForwardRef(IconButton)[label="Disable tips"]' ).first().simulate( 'click' ); expect( onDisable ).toHaveBeenCalled(); } ); } ); From 54e34bf5fc566339cfcce3910b52a19e7a77d27f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20Van=C2=A0Durpe?= <iseulde@automattic.com> Date: Fri, 1 Mar 2019 19:59:13 +0100 Subject: [PATCH 543/691] Paste: preserve empty table cells (#14137) --- packages/block-library/src/table/index.js | 6 ++++++ packages/blocks/src/api/raw-handling/utils.js | 10 ++++++++-- test/integration/fixtures/markdown-in.txt | 4 ++++ test/integration/fixtures/markdown-out.html | 4 ++++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/table/index.js b/packages/block-library/src/table/index.js index 6cf466f50dc36..78bc014bd80b0 100644 --- a/packages/block-library/src/table/index.js +++ b/packages/block-library/src/table/index.js @@ -18,11 +18,14 @@ import edit from './edit'; const tableContentPasteSchema = { tr: { + allowEmpty: true, children: { th: { + allowEmpty: true, children: getPhrasingContentSchema(), }, td: { + allowEmpty: true, children: getPhrasingContentSchema(), }, }, @@ -33,12 +36,15 @@ const tablePasteSchema = { table: { children: { thead: { + allowEmpty: true, children: tableContentPasteSchema, }, tfoot: { + allowEmpty: true, children: tableContentPasteSchema, }, tbody: { + allowEmpty: true, children: tableContentPasteSchema, }, }, diff --git a/packages/blocks/src/api/raw-handling/utils.js b/packages/blocks/src/api/raw-handling/utils.js index 4395c82c429dc..896bb6b8d4a5e 100644 --- a/packages/blocks/src/api/raw-handling/utils.js +++ b/packages/blocks/src/api/raw-handling/utils.js @@ -185,11 +185,17 @@ function cleanNodeList( nodeList, doc, schema, inline ) { ( ! schema[ tag ].isMatch || schema[ tag ].isMatch( node ) ) ) { if ( node.nodeType === ELEMENT_NODE ) { - const { attributes = [], classes = [], children, require = [] } = schema[ tag ]; + const { + attributes = [], + classes = [], + children, + require = [], + allowEmpty, + } = schema[ tag ]; // If the node is empty and it's supposed to have children, // remove the node. - if ( isEmpty( node ) && children ) { + if ( children && ! allowEmpty && isEmpty( node ) ) { remove( node ); return; } diff --git a/test/integration/fixtures/markdown-in.txt b/test/integration/fixtures/markdown-in.txt index c985608462247..b636a78143800 100644 --- a/test/integration/fixtures/markdown-in.txt +++ b/test/integration/fixtures/markdown-in.txt @@ -23,6 +23,10 @@ First Header | Second Header Content from cell 1 | Content from cell 2 Content in the first column | Content in the second column +| | | +|---|---| +| | Table with empty cells. | + ## Quote > First diff --git a/test/integration/fixtures/markdown-out.html b/test/integration/fixtures/markdown-out.html index cf83cf40050c4..ba9a33b7172cc 100644 --- a/test/integration/fixtures/markdown-out.html +++ b/test/integration/fixtures/markdown-out.html @@ -31,6 +31,10 @@ <h2>Table</h2> <table class="wp-block-table"><thead><tr><th>First Header</th><th>Second Header</th></tr></thead><tbody><tr><td>Content from cell 1</td><td>Content from cell 2</td></tr><tr><td>Content in the first column</td><td>Content in the second column</td></tr></tbody></table> <!-- /wp:table --> +<!-- wp:table --> +<table class="wp-block-table"><thead><tr><th></th><th></th></tr></thead><tbody><tr><td></td><td>Table with empty cells.</td></tr></tbody></table> +<!-- /wp:table --> + <!-- wp:heading --> <h2>Quote</h2> <!-- /wp:heading --> From 62f81e10f92a1ec2bdd71c4fe0e249c1e5cb8ccd Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Fri, 1 Mar 2019 11:14:45 -0800 Subject: [PATCH 544/691] Update Codeowners, add mkaz to docgen (#14198) --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ce69bf49c1b59..5a0454ca08e56 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -28,7 +28,7 @@ /packages/babel-preset-default @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw /packages/browserslist-config @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra /packages/custom-templated-path-webpack-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra -/packages/docgen @nosolosw +/packages/docgen @nosolosw @mkaz /packages/e2e-test-utils @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra /packages/e2e-tests @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @talldan /packages/eslint-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw From d62a1282672cfe665feab26d500c166815a716e2 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 1 Mar 2019 15:54:23 -0500 Subject: [PATCH 545/691] Plugin: Remove wp-editor-font stylesheet override (#14176) --- lib/client-assets.php | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/lib/client-assets.php b/lib/client-assets.php index dc9fff22a8386..9bd7cdb8be025 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -200,27 +200,6 @@ function gutenberg_register_scripts_and_styles() { // Editor Styles. // This empty stylesheet is defined to ensure backward compatibility. gutenberg_override_style( 'wp-blocks', false ); - $fonts_url = ''; - - /* - * Translators: Use this to specify the proper Google Font name and variants - * to load that is supported by your language. Do not translate. - * Set to 'off' to disable loading. - */ - $font_family = _x( 'Noto Serif:400,400i,700,700i', 'Google Font Name and Variants', 'gutenberg' ); - if ( 'off' !== $font_family ) { - $query_args = array( - 'family' => urlencode( $font_family ), - ); - $fonts_url = esc_url_raw( add_query_arg( $query_args, 'https://fonts.googleapis.com/css' ) ); - } - - gutenberg_override_style( - 'wp-editor-font', - $fonts_url, - array(), - null - ); gutenberg_override_style( 'wp-editor', From 33253abc1a3e7bbd2e23fb6f69cfdf57846cc2db Mon Sep 17 00:00:00 2001 From: Pascal Birchler <pascal.birchler@gmail.com> Date: Fri, 1 Mar 2019 22:08:52 +0100 Subject: [PATCH 546/691] Bring i18n functionality up to date (#12559) * Plugin: Add Text Domain to plugin headers * Bring i18n functionality up to date * Plugin: Set Gutenberg script translations as default domain Filter loading behavior to load from plugin translation files * Testing: Update gutenberg_override_script per localization changes * Plugin: Provide second argument for set script translations Technically optional, only after WP5.1+, which is newer than the current minimum version supported by Gutenberg --- .gitignore | 2 - babel.config.js | 12 --- bin/build-plugin-zip.sh | 4 - .../backward-compatibility/deprecations.md | 6 ++ gutenberg.php | 1 + languages/README.md | 10 --- lib/client-assets.php | 74 +++++++++++++++++-- lib/i18n.php | 11 ++- phpcs.xml.dist | 1 - phpunit/class-i18n-functions-test.php | 32 -------- phpunit/class-override-script-test.php | 21 +++++- 11 files changed, 97 insertions(+), 77 deletions(-) delete mode 100644 languages/README.md delete mode 100644 phpunit/class-i18n-functions-test.php diff --git a/.gitignore b/.gitignore index 5ba04948b2faf..2fb814f9616e0 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,6 @@ build-module build-style node_modules gutenberg.zip -languages/gutenberg.pot -/languages/gutenberg-translations.php # Directories/files that may appear in your environment .DS_Store diff --git a/babel.config.js b/babel.config.js index 4dc16df8337b2..b56ad5b149478 100644 --- a/babel.config.js +++ b/babel.config.js @@ -3,17 +3,5 @@ module.exports = function( api ) { return { presets: [ '@wordpress/babel-preset-default' ], - env: { - production: { - plugins: [ - [ - '@wordpress/babel-plugin-makepot', - { - output: 'languages/gutenberg.pot', - }, - ], - ], - }, - }, }; }; diff --git a/bin/build-plugin-zip.sh b/bin/build-plugin-zip.sh index a95f2ef74b23d..1a6344fa2e67a 100755 --- a/bin/build-plugin-zip.sh +++ b/bin/build-plugin-zip.sh @@ -97,8 +97,6 @@ status "Installing dependencies... 📦" npm install status "Generating build... 👷‍♀️" npm run build -status "Generating PHP file for wordpress.org to parse translations... 👷‍♂️" -npx pot-to-php ./languages/gutenberg.pot ./languages/gutenberg-translations.php gutenberg # Temporarily modify `gutenberg.php` with production constants defined. Use a # temp file because `bin/generate-gutenberg-php.php` reads from `gutenberg.php` @@ -118,8 +116,6 @@ zip -r gutenberg.zip \ post-content.php \ $vendor_scripts \ $build_files \ - languages/gutenberg.pot \ - languages/gutenberg-translations.php \ README.md # Reset `gutenberg.php`. diff --git a/docs/designers-developers/developers/backward-compatibility/deprecations.md b/docs/designers-developers/developers/backward-compatibility/deprecations.md index 966b33d77cafe..8e8f8ee2d3c37 100644 --- a/docs/designers-developers/developers/backward-compatibility/deprecations.md +++ b/docs/designers-developers/developers/backward-compatibility/deprecations.md @@ -2,6 +2,12 @@ The Gutenberg project's deprecation policy is intended to support backward compatibility for releases, when possible. The current deprecations are listed below and are grouped by _the version at which they will be removed completely_. If your plugin depends on these behaviors, you must update to the recommended alternative before the noted version. +## 5.4.0 + +- The PHP function `gutenberg_load_plugin_textdomain` has been removed. +- The PHP function `gutenberg_get_jed_locale_data` has been removed. +- The PHP function `gutenberg_load_locale_data` has been removed. + ## 5.3.0 - The PHP function `gutenberg_redirect_to_classic_editor_when_saving_posts` has been removed. diff --git a/gutenberg.php b/gutenberg.php index db7f70d1cab95..993f9e4566834 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -5,6 +5,7 @@ * Description: Printing since 1440. This is the development plugin for the new block editor in core. * Version: 5.1.1 * Author: Gutenberg Team + * Text Domain: gutenberg * * @package gutenberg */ diff --git a/languages/README.md b/languages/README.md deleted file mode 100644 index 3f3ba1d4478c6..0000000000000 --- a/languages/README.md +++ /dev/null @@ -1,10 +0,0 @@ -Languages -========= - -The generated POT template file is not included in this repository. To create this file locally, follow instructions from [CONTRIBUTING.md](https://github.com/WordPress/gutenberg/blob/master/CONTRIBUTING.md) to install the project, then run the following command: - -``` -npm run build -``` - -After the build completes, you'll find a `gutenberg.pot` strings file in this directory. diff --git a/lib/client-assets.php b/lib/client-assets.php index 9bd7cdb8be025..040557e3cfb11 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -83,8 +83,71 @@ function gutenberg_override_script( $handle, $src, $deps = array(), $ver = false } else { wp_register_script( $handle, $src, $deps, $ver, $in_footer ); } + + /* + * `WP_Dependencies::set_translations` will fall over on itself if setting + * translations on the `wp-i18n` handle, since it internally adds `wp-i18n` + * as a dependency of itself, exhausting memory. The same applies for the + * polyfill script, which is a dependency _of_ `wp-i18n`. + * + * See: https://core.trac.wordpress.org/ticket/46089 + */ + if ( 'wp-i18n' !== $handle && 'wp-polyfill' !== $handle ) { + wp_set_script_translations( $handle, 'default' ); + } } +/** + * Filters the default translation file load behavior to load the Gutenberg + * plugin translation file, if available. + * + * @param string|false $file Path to the translation file to load. False if + * there isn't one. + * @param string $handle Name of the script to register a translation + * domain to. + * + * @return string|false Filtered path to the Gutenberg translation file, if + * available. + */ +function gutenberg_override_translation_file( $file, $handle ) { + if ( ! $file ) { + return $file; + } + + // Only override script handles generated from the Gutenberg plugin. + $packages_dependencies = include dirname( __FILE__ ) . '/packages-dependencies.php'; + if ( ! isset( $packages_dependencies[ $handle ] ) ) { + return $file; + } + + /* + * The default file will be in the plugins language directory, omitting the + * domain since Gutenberg assigns the script translations as the default. + * + * Example: /www/wp-content/languages/plugins/de_DE-07d88e6a803e01276b9bfcc1203e862e.json + * + * The logic of `load_script_textdomain` is such that it will assume to + * search in the plugins language directory, since the assigned source of + * the overridden Gutenberg script originates in the plugins directory. + * + * The plugin translation files each begin with the slug of the plugin, so + * it's a simple matter of prepending the Gutenberg plugin slug. + */ + $path_parts = pathinfo( $file ); + $plugin_translation_file = ( + $path_parts['dirname'] . + '/gutenberg-' . + $path_parts['basename'] + ); + + if ( ! is_readable( $plugin_translation_file ) ) { + return $file; + } + + return $plugin_translation_file; +} +add_filter( 'load_script_translation_file', 'gutenberg_override_translation_file', 10, 2 ); + /** * Registers a style according to `wp_register_style`. Honors this request by * deregistering any style by the same handler before registration. @@ -485,14 +548,11 @@ function gutenberg_get_autosave_newer_than_post_save( $post ) { /** * Loads Gutenberg Locale Data. + * + * @deprecated 5.2.0 */ function gutenberg_load_locale_data() { - // Prepare Jed locale data. - $locale_data = gutenberg_get_jed_locale_data( 'gutenberg' ); - wp_add_inline_script( - 'wp-i18n', - 'wp.i18n.setLocaleData( ' . json_encode( $locale_data ) . ' );' - ); + _deprecated_function( __FUNCTION__, '5.2.0' ); } /** @@ -680,8 +740,6 @@ function gutenberg_editor_scripts_and_styles( $hook ) { $initial_edits = null; } - gutenberg_load_locale_data(); - // Preload server-registered block schemas. wp_add_inline_script( 'wp-blocks', diff --git a/lib/i18n.php b/lib/i18n.php index ab347c8334285..72aadb0598f27 100644 --- a/lib/i18n.php +++ b/lib/i18n.php @@ -13,12 +13,15 @@ * Returns Jed-formatted localization data. * * @since 0.1.0 + * @deprecated 5.2.0 * * @param string $domain Translation domain. * * @return array */ function gutenberg_get_jed_locale_data( $domain ) { + _deprecated_function( __FUNCTION__, '5.2.0' ); + $translations = get_translations_for_domain( $domain ); $locale = array( @@ -43,12 +46,8 @@ function gutenberg_get_jed_locale_data( $domain ) { * Load plugin text domain for translations. * * @since 0.1.0 + * @deprecated 5.2.0 */ function gutenberg_load_plugin_textdomain() { - load_plugin_textdomain( - 'gutenberg', - false, - plugin_basename( gutenberg_dir_path() ) . '/languages/' - ); + _deprecated_function( __FUNCTION__, '5.2.0' ); } -add_action( 'plugins_loaded', 'gutenberg_load_plugin_textdomain' ); diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 85a39fbd82a4a..e328ac8dde3fc 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -32,7 +32,6 @@ <exclude-pattern>./vendor</exclude-pattern> <!-- Exclude generated files --> - <exclude-pattern>./languages/gutenberg-translations.php</exclude-pattern> <exclude-pattern>./packages/block-serialization-spec-parser/parser.php</exclude-pattern> <rule ref="PHPCompatibility.PHP.NewKeywords.t_namespaceFound"> diff --git a/phpunit/class-i18n-functions-test.php b/phpunit/class-i18n-functions-test.php deleted file mode 100644 index db8abe35bb7a7..0000000000000 --- a/phpunit/class-i18n-functions-test.php +++ /dev/null @@ -1,32 +0,0 @@ -<?php -/** - * Tests for i18n functions - * - * @package Gutenberg - */ -class I18n_Functions_Test extends WP_UnitTestCase { - - /** - * @test - * @group #5038 - * @covers gutenberg_load_plugin_textdomain - */ - public function it_should_load_language_files_from_gutenberg_languages() { - if ( 'gutenberg' !== plugin_basename( gutenberg_dir_path() ) ) { - $this->markTestSkipped( 'The path is not matching in docker test environment. Needs further investigation.' ); - return; - } - - add_action( 'load_textdomain', array( $this, 'check_arguments_in_load_textdomaincheck' ), 10, 2 ); - gutenberg_load_plugin_textdomain(); - } - - public function check_arguments_in_load_textdomaincheck( $domain, $mofile ) { - $content_language_file_pattern = 'languages[\\/]plugins[\\/]gutenberg-[a-z]{2}_[A-Z]{2}.mo$'; - $plugin_language_file_pattern = 'gutenberg[\\/]languages[\\/]gutenberg-[a-z]{2}_[A-Z]{2}.mo$'; - $regex_pattern = '#' . $content_language_file_pattern . '|' . $plugin_language_file_pattern . '#'; - - $this->assertEquals( 'gutenberg', $domain ); - $this->assertRegExp( $regex_pattern, $mofile ); - } -} diff --git a/phpunit/class-override-script-test.php b/phpunit/class-override-script-test.php index af9e5d73fff19..034fa2f25a618 100644 --- a/phpunit/class-override-script-test.php +++ b/phpunit/class-override-script-test.php @@ -24,6 +24,23 @@ function tearDown() { wp_deregister_script( 'gutenberg-dummy-script' ); } + /** + * Tests that script is localized. + */ + function test_localizes_script() { + gutenberg_override_script( + 'gutenberg-dummy-script', + 'https://example.com/', + array( 'dependency' ), + 'version', + false + ); + + global $wp_scripts; + $script = $wp_scripts->query( 'gutenberg-dummy-script', 'registered' ); + $this->assertEquals( array( 'dependency', 'wp-i18n' ), $script->deps ); + } + /** * Tests that script properties are overridden. */ @@ -39,7 +56,7 @@ function test_replaces_registered_properties() { global $wp_scripts; $script = $wp_scripts->query( 'gutenberg-dummy-script', 'registered' ); $this->assertEquals( 'https://example.com/updated', $script->src ); - $this->assertEquals( array( 'updated-dependency' ), $script->deps ); + $this->assertEquals( array( 'updated-dependency', 'wp-i18n' ), $script->deps ); $this->assertEquals( 'updated-version', $script->ver ); $this->assertEquals( 1, $script->extra['group'] ); } @@ -59,7 +76,7 @@ function test_registers_new_script() { global $wp_scripts; $script = $wp_scripts->query( 'gutenberg-second-dummy-script', 'registered' ); $this->assertEquals( 'https://example.com/', $script->src ); - $this->assertEquals( array( 'dependency' ), $script->deps ); + $this->assertEquals( array( 'dependency', 'wp-i18n' ), $script->deps ); $this->assertEquals( 'version', $script->ver ); $this->assertEquals( 1, $script->extra['group'] ); } From 8bd00c40e40f33829f488e17c323067292835024 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Fri, 1 Mar 2019 22:04:11 +0000 Subject: [PATCH 547/691] Fix: deleting the last block triggers a focus loss. (#14189) ## Description This PR fixes a problem: if the post contains one block and it is removed the focus is lost. This is a regression that happened when removeBlocks action was refactored to be a generator. We had an effect that inserts the default block during remove blocks action when certain conditions are met, this effect stopped working. This PR removes the effect and makes sure everything is handled by the removeBlocks action creator. End to end test available at https://github.com/WordPress/gutenberg/pull/14191. ## How has this been tested? I created a new post. I wrote something in a paragraph I removed the paragraph using the remove button in the side menu and I verified the default block was added. --- packages/block-editor/src/store/actions.js | 11 +++++++++++ packages/block-editor/src/store/effects.js | 3 --- packages/block-editor/src/store/test/actions.js | 15 ++++++++++++++- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index a94a526f4a5da..07f150f53d21a 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -393,6 +393,17 @@ export function* removeBlocks( clientIds, selectPrevious = true ) { type: 'REMOVE_BLOCKS', clientIds, }; + + const count = yield select( + 'core/block-editor', + 'getBlockCount', + ); + + // To avoid a focus loss when removing the last block, assure there is + // always a default block if the last of the blocks have been removed. + if ( count === 0 ) { + yield insertDefaultBlock(); + } } /** diff --git a/packages/block-editor/src/store/effects.js b/packages/block-editor/src/store/effects.js index f02ef6fbd7231..ca46e1afb8f4a 100644 --- a/packages/block-editor/src/store/effects.js +++ b/packages/block-editor/src/store/effects.js @@ -127,9 +127,6 @@ export default { RESET_BLOCKS: [ validateBlocksToTemplate, ], - REMOVE_BLOCKS: [ - ensureDefaultBlock, - ], REPLACE_BLOCKS: [ ensureDefaultBlock, ], diff --git a/packages/block-editor/src/store/test/actions.js b/packages/block-editor/src/store/test/actions.js index 3ae9039505356..2ca6839dc57e2 100644 --- a/packages/block-editor/src/store/test/actions.js +++ b/packages/block-editor/src/store/test/actions.js @@ -28,6 +28,7 @@ import { toggleBlockMode, updateBlockListSettings, } from '../actions'; +import { select } from '../controls'; describe( 'actions', () => { describe( 'resetBlocks', () => { @@ -218,6 +219,10 @@ describe( 'actions', () => { type: 'REMOVE_BLOCKS', clientIds, }, + select( + 'core/block-editor', + 'getBlockCount', + ), ] ); } ); } ); @@ -234,10 +239,14 @@ describe( 'actions', () => { type: 'REMOVE_BLOCKS', clientIds: [ clientId ], }, + select( + 'core/block-editor', + 'getBlockCount', + ), ] ); } ); - it( 'should return REMOVE_BLOCKS action, opting out of remove previous', () => { + it( 'should return REMOVE_BLOCKS action, opting out of select previous', () => { const clientId = 'myclientid'; const actions = Array.from( removeBlock( clientId, false ) ); @@ -247,6 +256,10 @@ describe( 'actions', () => { type: 'REMOVE_BLOCKS', clientIds: [ clientId ], }, + select( + 'core/block-editor', + 'getBlockCount', + ), ] ); } ); } ); From c29b729a6d107fc5a7ef53efafda126d6fbb68e0 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Mon, 4 Mar 2019 07:42:58 +0100 Subject: [PATCH 548/691] Clarify the block editor settings from the post editor settings (#14082) --- .../developers/data/data-core-block-editor.md | 6 ++-- .../developers/data/data-core-editor.md | 22 +++++++++++++- .../src/components/provider/index.js | 10 +++---- packages/block-editor/src/index.js | 2 ++ packages/block-editor/src/store/actions.js | 6 ++-- packages/block-editor/src/store/defaults.js | 28 +++++++++--------- packages/block-editor/src/store/reducer.js | 6 ++-- packages/block-editor/src/store/selectors.js | 4 +-- .../block-editor/src/store/test/effects.js | 8 ++--- packages/block-library/src/html/edit.js | 4 +-- packages/block-library/src/image/edit.js | 4 +-- packages/block-library/src/paragraph/edit.js | 4 +-- packages/block-library/src/pullquote/index.js | 2 +- .../components/header/header-toolbar/index.js | 2 +- .../components/header/mode-switcher/index.js | 2 +- .../components/keyboard-shortcuts/index.js | 2 +- .../edit-post/src/components/layout/index.js | 2 +- .../options-modal/meta-boxes-section.js | 2 +- .../options/enable-custom-fields.js | 2 +- .../src/components/text-editor/index.js | 2 +- .../src/components/alignment-toolbar/index.js | 4 +-- .../src/components/autosave-monitor/index.js | 3 +- .../block-alignment-toolbar/index.js | 7 +++-- .../editor/src/components/block-list/block.js | 8 ++--- .../src/components/block-list/hover-area.js | 2 +- .../color-palette/with-color-context.js | 2 +- .../src/components/colors/with-colors.js | 2 +- .../default-block-appender/index.js | 4 +-- .../default-block-appender/index.native.js | 4 +-- .../components/font-sizes/font-size-picker.js | 2 +- .../components/font-sizes/with-font-sizes.js | 2 +- .../src/components/media-placeholder/index.js | 4 +-- .../src/components/page-attributes/check.js | 5 +--- .../components/page-attributes/template.js | 3 +- .../src/components/post-format/check.js | 3 +- .../src/components/post-locked-modal/index.js | 5 +--- .../editor/src/components/post-title/index.js | 4 +-- .../editor/src/components/provider/index.js | 29 +++++++++++++++++-- packages/editor/src/hooks/align.js | 4 +-- packages/editor/src/store/actions.js | 15 +++++++++- packages/editor/src/store/defaults.js | 24 +++++++++++++++ packages/editor/src/store/reducer.js | 22 ++++++++++++++ packages/editor/src/store/selectors.js | 12 +++++++- .../editor/src/utils/media-upload/index.js | 5 +--- 44 files changed, 201 insertions(+), 94 deletions(-) diff --git a/docs/designers-developers/developers/data/data-core-block-editor.md b/docs/designers-developers/developers/data/data-core-block-editor.md index 3166400bd3fbf..03929e3671685 100644 --- a/docs/designers-developers/developers/data/data-core-block-editor.md +++ b/docs/designers-developers/developers/data/data-core-block-editor.md @@ -737,7 +737,7 @@ Returns the Block List settings of a block, if any exist. Block settings of the block if set. -### getEditorSettings +### getSettings Returns the editor settings. @@ -1027,9 +1027,9 @@ Returns an action object that changes the nested settings of a given block. being received. * settings: Object with the new settings for the nested block. -### updateEditorSettings +### updateSettings -Returns an action object used in signalling that the editor settings have been updated. +Returns an action object used in signalling that the block editor settings have been updated. *Parameters* diff --git a/docs/designers-developers/developers/data/data-core-editor.md b/docs/designers-developers/developers/data/data-core-editor.md index 3a22e11c90121..cd251dc92d678 100644 --- a/docs/designers-developers/developers/data/data-core-editor.md +++ b/docs/designers-developers/developers/data/data-core-editor.md @@ -718,6 +718,18 @@ Is the editor ready is Ready. +### getEditorSettings + +Returns the post editor settings. + +*Parameters* + + * state: Editor state. + +*Returns* + +The editor settings object. + ## Actions ### setupEditor @@ -967,4 +979,12 @@ Returns an action object used to signal that the blocks have been updated. *Parameters* * blocks: Block Array. - * options: Optional options. \ No newline at end of file + * options: Optional options. + +### updateEditorSettings + +Returns an action object used in signalling that the post editor settings have been updated. + +*Parameters* + + * settings: Updated settings \ No newline at end of file diff --git a/packages/block-editor/src/components/provider/index.js b/packages/block-editor/src/components/provider/index.js index a1d3063962b5d..a8073ace46c5d 100644 --- a/packages/block-editor/src/components/provider/index.js +++ b/packages/block-editor/src/components/provider/index.js @@ -30,7 +30,7 @@ const withRegistry = createHigherOrderComponent( class BlockEditorProvider extends Component { componentDidMount() { - this.props.updateEditorSettings( this.props.settings ); + this.props.updateSettings( this.props.settings ); this.props.resetBlocks( this.props.value ); this.attachChangeObserver( this.props.registry ); } @@ -38,14 +38,14 @@ class BlockEditorProvider extends Component { componentDidUpdate( prevProps ) { const { settings, - updateEditorSettings, + updateSettings, value, resetBlocks, registry, } = this.props; if ( settings !== prevProps.settings ) { - updateEditorSettings( settings ); + updateSettings( settings ); } if ( registry !== prevProps.registry ) { @@ -139,12 +139,12 @@ class BlockEditorProvider extends Component { export default compose( [ withDispatch( ( dispatch ) => { const { - updateEditorSettings, + updateSettings, resetBlocks, } = dispatch( 'core/block-editor' ); return { - updateEditorSettings, + updateSettings, resetBlocks, }; } ), diff --git a/packages/block-editor/src/index.js b/packages/block-editor/src/index.js index 9421db61f16e9..58e920befd5ea 100644 --- a/packages/block-editor/src/index.js +++ b/packages/block-editor/src/index.js @@ -9,3 +9,5 @@ import '@wordpress/blocks'; import './store'; export * from './components'; + +export { SETTINGS_DEFAULTS } from './store/defaults'; diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index 07f150f53d21a..a2a1bc6e6e628 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -514,15 +514,15 @@ export function updateBlockListSettings( clientId, settings ) { } /* - * Returns an action object used in signalling that the editor settings have been updated. + * Returns an action object used in signalling that the block editor settings have been updated. * * @param {Object} settings Updated settings * * @return {Object} Action object */ -export function updateEditorSettings( settings ) { +export function updateSettings( settings ) { return { - type: 'UPDATE_EDITOR_SETTINGS', + type: 'UPDATE_SETTINGS', settings, }; } diff --git a/packages/block-editor/src/store/defaults.js b/packages/block-editor/src/store/defaults.js index 31d1574d6283a..3e61e459c53f6 100644 --- a/packages/block-editor/src/store/defaults.js +++ b/packages/block-editor/src/store/defaults.js @@ -10,17 +10,22 @@ export const PREFERENCES_DEFAULTS = { /** * The default editor settings * - * alignWide boolean Enable/Disable Wide/Full Alignments - * colors Array Palette colors - * fontSizes Array Available font sizes - * imageSizes Array Available image sizes - * maxWidth number Max width to constraint resizing - * blockTypes boolean|Array Allowed block types - * hasFixedToolbar boolean Whether or not the editor toolbar is fixed - * focusMode boolean Whether the focus mode is enabled or not - * richEditingEnabled boolean Whether rich editing is enabled or not + * alignWide boolean Enable/Disable Wide/Full Alignments + * colors Array Palette colors + * disableCustomColors boolean Whether or not the custom colors are disabled + * fontSizes Array Available font sizes + * disableCustomFontSizes boolean Whether or not the custom font sizes are disabled + * imageSizes Array Available image sizes + * maxWidth number Max width to constraint resizing + * blockTypes boolean|Array Allowed block types + * hasFixedToolbar boolean Whether or not the editor toolbar is fixed + * focusMode boolean Whether the focus mode is enabled or not + * styles Array Editor Styles + * isRTL boolean Whether the editor is in RTL mode + * bodyPlaceholder string Empty post placeholder + * titlePlaceholder string Empty title placeholder */ -export const EDITOR_SETTINGS_DEFAULTS = { +export const SETTINGS_DEFAULTS = { alignWide: false, colors: [ { @@ -126,8 +131,5 @@ export const EDITOR_SETTINGS_DEFAULTS = { // List of allowed mime types and file extensions. allowedMimeTypes: null, - - // Whether richs editing is enabled or not. - richEditingEnabled: true, }; diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index bcd8b3295c4bc..007c5f4a8f6d4 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -26,7 +26,7 @@ import { isReusableBlock } from '@wordpress/blocks'; */ import { PREFERENCES_DEFAULTS, - EDITOR_SETTINGS_DEFAULTS, + SETTINGS_DEFAULTS, } from './defaults'; import { insertAt, moveTo } from './array'; @@ -830,9 +830,9 @@ export function template( state = { isValid: true }, action ) { * * @return {Object} Updated state. */ -export function settings( state = EDITOR_SETTINGS_DEFAULTS, action ) { +export function settings( state = SETTINGS_DEFAULTS, action ) { switch ( action.type ) { - case 'UPDATE_EDITOR_SETTINGS': + case 'UPDATE_SETTINGS': return { ...state, ...action.settings, diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index efadb31bf0ff2..d1b142530cff6 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -1034,7 +1034,7 @@ const canInsertBlockTypeUnmemoized = ( state, blockName, rootClientId = null ) = return false; } - const { allowedBlockTypes } = getEditorSettings( state ); + const { allowedBlockTypes } = getSettings( state ); const isBlockAllowedInEditor = checkAllowList( allowedBlockTypes, blockName, true ); if ( ! isBlockAllowedInEditor ) { @@ -1350,7 +1350,7 @@ export function getBlockListSettings( state, clientId ) { * * @return {Object} The editor settings object. */ -export function getEditorSettings( state ) { +export function getSettings( state ) { return state.settings; } diff --git a/packages/block-editor/src/store/test/effects.js b/packages/block-editor/src/store/test/effects.js index 090779cbad0d9..34300ed6d42ff 100644 --- a/packages/block-editor/src/store/test/effects.js +++ b/packages/block-editor/src/store/test/effects.js @@ -18,7 +18,7 @@ import { createRegistry } from '@wordpress/data'; * Internal dependencies */ import actions, { - updateEditorSettings, + updateSettings, mergeBlocks, replaceBlocks, resetBlocks, @@ -234,7 +234,7 @@ describe( 'effects', () => { } ); it( 'should return undefined if invalid but unlocked', () => { - store.dispatch( updateEditorSettings( { + store.dispatch( updateSettings( { template: [ [ 'core/foo', {} ], ], @@ -248,7 +248,7 @@ describe( 'effects', () => { } ); it( 'should return undefined if locked and valid', () => { - store.dispatch( updateEditorSettings( { + store.dispatch( updateSettings( { template: [ [ 'core/test-block' ], ], @@ -263,7 +263,7 @@ describe( 'effects', () => { } ); it( 'should return validity set action if invalid on default state', () => { - store.dispatch( updateEditorSettings( { + store.dispatch( updateSettings( { template: [ [ 'core/foo' ], ], diff --git a/packages/block-library/src/html/edit.js b/packages/block-library/src/html/edit.js index 6dd2bb6f1fd92..0000654785a43 100644 --- a/packages/block-library/src/html/edit.js +++ b/packages/block-library/src/html/edit.js @@ -87,8 +87,8 @@ class HTMLEdit extends Component { } } export default withSelect( ( select ) => { - const { getEditorSettings } = select( 'core/block-editor' ); + const { getSettings } = select( 'core/block-editor' ); return { - styles: getEditorSettings().styles, + styles: getSettings().styles, }; } )( HTMLEdit ); diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index deaeaf79766dc..f8308165923f1 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -705,9 +705,9 @@ class ImageEdit extends Component { export default compose( [ withSelect( ( select, props ) => { const { getMedia } = select( 'core' ); - const { getEditorSettings } = select( 'core/block-editor' ); + const { getSettings } = select( 'core/block-editor' ); const { id } = props.attributes; - const { maxWidth, isRTL, imageSizes } = getEditorSettings(); + const { maxWidth, isRTL, imageSizes } = getSettings(); return { image: id ? getMedia( id ) : null, diff --git a/packages/block-library/src/paragraph/edit.js b/packages/block-library/src/paragraph/edit.js index 2aaad4c8060ff..060080b971e59 100644 --- a/packages/block-library/src/paragraph/edit.js +++ b/packages/block-library/src/paragraph/edit.js @@ -259,10 +259,10 @@ const ParagraphEdit = compose( [ withFontSizes( 'fontSize' ), applyFallbackStyles, withSelect( ( select ) => { - const { getEditorSettings } = select( 'core/block-editor' ); + const { getSettings } = select( 'core/block-editor' ); return { - isRTL: getEditorSettings().isRTL, + isRTL: getSettings().isRTL, }; } ), ] )( ParagraphBlock ); diff --git a/packages/block-library/src/pullquote/index.js b/packages/block-library/src/pullquote/index.js index beaf1d9549aaa..766cb78861158 100644 --- a/packages/block-library/src/pullquote/index.js +++ b/packages/block-library/src/pullquote/index.js @@ -100,7 +100,7 @@ export const settings = { // Is normal style and a named color is being used, we need to retrieve the color value to set the style, // as there is no expectation that themes create classes that set border colors. } else if ( mainColor ) { - const colors = get( select( 'core/block-editor' ).getEditorSettings(), [ 'colors' ], [] ); + const colors = get( select( 'core/block-editor' ).getSettings(), [ 'colors' ], [] ); const colorObject = getColorObjectByAttributeValues( colors, mainColor ); figureStyles = { borderColor: colorObject.color, diff --git a/packages/edit-post/src/components/header/header-toolbar/index.js b/packages/edit-post/src/components/header/header-toolbar/index.js index 7740b3b157a99..7d99c1f889e04 100644 --- a/packages/edit-post/src/components/header/header-toolbar/index.js +++ b/packages/edit-post/src/components/header/header-toolbar/index.js @@ -57,7 +57,7 @@ export default compose( [ withSelect( ( select ) => ( { hasFixedToolbar: select( 'core/edit-post' ).isFeatureActive( 'fixedToolbar' ), // This setting (richEditingEnabled) should not live in the block editor's setting. - showInserter: select( 'core/edit-post' ).getEditorMode() === 'visual' && select( 'core/block-editor' ).getEditorSettings().richEditingEnabled, + showInserter: select( 'core/edit-post' ).getEditorMode() === 'visual' && select( 'core/editor' ).getEditorSettings().richEditingEnabled, isTextModeEnabled: select( 'core/edit-post' ).getEditorMode() === 'text', } ) ), withViewportMatch( { isLargeViewport: 'medium' } ), diff --git a/packages/edit-post/src/components/header/mode-switcher/index.js b/packages/edit-post/src/components/header/mode-switcher/index.js index 058d9c74eeb8c..52ca0b78cfc94 100644 --- a/packages/edit-post/src/components/header/mode-switcher/index.js +++ b/packages/edit-post/src/components/header/mode-switcher/index.js @@ -49,7 +49,7 @@ function ModeSwitcher( { onSwitch, mode } ) { export default compose( [ withSelect( ( select ) => ( { - isRichEditingEnabled: select( 'core/block-editor' ).getEditorSettings().richEditingEnabled, + isRichEditingEnabled: select( 'core/editor' ).getEditorSettings().richEditingEnabled, mode: select( 'core/edit-post' ).getEditorMode(), } ) ), ifCondition( ( { isRichEditingEnabled } ) => isRichEditingEnabled ), diff --git a/packages/edit-post/src/components/keyboard-shortcuts/index.js b/packages/edit-post/src/components/keyboard-shortcuts/index.js index b5c09178efd68..bde16d7431106 100644 --- a/packages/edit-post/src/components/keyboard-shortcuts/index.js +++ b/packages/edit-post/src/components/keyboard-shortcuts/index.js @@ -55,7 +55,7 @@ class EditorModeKeyboardShortcuts extends Component { export default compose( [ withSelect( ( select ) => ( { - isRichEditingEnabled: select( 'core/block-editor' ).getEditorSettings().richEditingEnabled, + isRichEditingEnabled: select( 'core/editor' ).getEditorSettings().richEditingEnabled, mode: select( 'core/edit-post' ).getEditorMode(), isEditorSidebarOpen: select( 'core/edit-post' ).isEditorSidebarOpened(), } ) ), diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index 76c10e8a78032..f0876f4672287 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -137,7 +137,7 @@ export default compose( hasFixedToolbar: select( 'core/edit-post' ).isFeatureActive( 'fixedToolbar' ), hasActiveMetaboxes: select( 'core/edit-post' ).hasMetaBoxes(), isSaving: select( 'core/edit-post' ).isSavingMetaBoxes(), - isRichEditingEnabled: select( 'core/block-editor' ).getEditorSettings().richEditingEnabled, + isRichEditingEnabled: select( 'core/editor' ).getEditorSettings().richEditingEnabled, } ) ), withDispatch( ( dispatch ) => { const { closePublishSidebar, togglePublishSidebar } = dispatch( 'core/edit-post' ); diff --git a/packages/edit-post/src/components/options-modal/meta-boxes-section.js b/packages/edit-post/src/components/options-modal/meta-boxes-section.js index a5b8e0e85ad50..e4ab459a0e41a 100644 --- a/packages/edit-post/src/components/options-modal/meta-boxes-section.js +++ b/packages/edit-post/src/components/options-modal/meta-boxes-section.js @@ -34,7 +34,7 @@ export function MetaBoxesSection( { areCustomFieldsRegistered, metaBoxes, ...sec } export default withSelect( ( select ) => { - const { getEditorSettings } = select( 'core/block-editor' ); + const { getEditorSettings } = select( 'core/editor' ); const { getAllMetaBoxes } = select( 'core/edit-post' ); return { diff --git a/packages/edit-post/src/components/options-modal/options/enable-custom-fields.js b/packages/edit-post/src/components/options-modal/options/enable-custom-fields.js index e9a319efbebfe..140c6f1c46d2d 100644 --- a/packages/edit-post/src/components/options-modal/options/enable-custom-fields.js +++ b/packages/edit-post/src/components/options-modal/options/enable-custom-fields.js @@ -43,5 +43,5 @@ export class EnableCustomFieldsOption extends Component { } export default withSelect( ( select ) => ( { - isChecked: !! select( 'core/block-editor' ).getEditorSettings().enableCustomFields, + isChecked: !! select( 'core/editor' ).getEditorSettings().enableCustomFields, } ) )( EnableCustomFieldsOption ); diff --git a/packages/edit-post/src/components/text-editor/index.js b/packages/edit-post/src/components/text-editor/index.js index 0cebd6e7396ad..882e97dd7e83c 100644 --- a/packages/edit-post/src/components/text-editor/index.js +++ b/packages/edit-post/src/components/text-editor/index.js @@ -38,7 +38,7 @@ function TextEditor( { onExit, isRichEditingEnabled } ) { export default compose( withSelect( ( select ) => ( { - isRichEditingEnabled: select( 'core/block-editor' ).getEditorSettings().richEditingEnabled, + isRichEditingEnabled: select( 'core/editor' ).getEditorSettings().richEditingEnabled, } ) ), withDispatch( ( dispatch ) => { return { diff --git a/packages/editor/src/components/alignment-toolbar/index.js b/packages/editor/src/components/alignment-toolbar/index.js index bf5fe27650e2a..1124f44f033e1 100644 --- a/packages/editor/src/components/alignment-toolbar/index.js +++ b/packages/editor/src/components/alignment-toolbar/index.js @@ -69,10 +69,10 @@ export default compose( } ), withViewportMatch( { isLargeViewport: 'medium' } ), withSelect( ( select, { clientId, isLargeViewport, isCollapsed } ) => { - const { getBlockRootClientId, getEditorSettings } = select( 'core/block-editor' ); + const { getBlockRootClientId, getSettings } = select( 'core/block-editor' ); return { isCollapsed: isCollapsed || ! isLargeViewport || ( - ! getEditorSettings().hasFixedToolbar && + ! getSettings().hasFixedToolbar && getBlockRootClientId( clientId ) ), }; diff --git a/packages/editor/src/components/autosave-monitor/index.js b/packages/editor/src/components/autosave-monitor/index.js index 23cff18b19411..73f15c285cbff 100644 --- a/packages/editor/src/components/autosave-monitor/index.js +++ b/packages/editor/src/components/autosave-monitor/index.js @@ -66,8 +66,7 @@ export default compose( [ isAutosavingPost, } = select( 'core/editor' ); - // This settings should not live in the block editor. - const { autosaveInterval } = select( 'core/block-editor' ).getEditorSettings(); + const { autosaveInterval } = select( 'core/editor' ).getEditorSettings(); return { isDirty: isEditedPostDirty(), diff --git a/packages/editor/src/components/block-alignment-toolbar/index.js b/packages/editor/src/components/block-alignment-toolbar/index.js index 0dcc4bda1412f..adf02cd90b08b 100644 --- a/packages/editor/src/components/block-alignment-toolbar/index.js +++ b/packages/editor/src/components/block-alignment-toolbar/index.js @@ -75,11 +75,12 @@ export default compose( } ), withViewportMatch( { isLargeViewport: 'medium' } ), withSelect( ( select, { clientId, isLargeViewport, isCollapsed } ) => { - const { getBlockRootClientId, getEditorSettings } = select( 'core/block-editor' ); + const { getBlockRootClientId, getSettings } = select( 'core/block-editor' ); + const settings = getSettings(); return { - wideControlsEnabled: select( 'core/block-editor' ).getEditorSettings().alignWide, + wideControlsEnabled: settings.alignWide, isCollapsed: isCollapsed || ! isLargeViewport || ( - ! getEditorSettings().hasFixedToolbar && + ! settings.hasFixedToolbar && getBlockRootClientId( clientId ) ), }; diff --git a/packages/editor/src/components/block-list/block.js b/packages/editor/src/components/block-list/block.js index 4a977903e44c7..2b828a34cdd93 100644 --- a/packages/editor/src/components/block-list/block.js +++ b/packages/editor/src/components/block-list/block.js @@ -633,14 +633,14 @@ const applyWithSelect = withSelect( getBlockMode, isSelectionEnabled, getSelectedBlocksInitialCaretPosition, - getEditorSettings, + getSettings, hasSelectedInnerBlock, getTemplateLock, __unstableGetBlockWithoutInnerBlocks, } = select( 'core/block-editor' ); const block = __unstableGetBlockWithoutInnerBlocks( clientId ); const isSelected = isBlockSelected( clientId ); - const { hasFixedToolbar, focusMode } = getEditorSettings(); + const { hasFixedToolbar, focusMode } = getSettings(); const templateLock = getTemplateLock( rootClientId ); const isParentOfSelectedBlock = hasSelectedInnerBlock( clientId, true ); @@ -748,8 +748,8 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, { select } ) => { replaceBlocks( [ ownProps.clientId ], blocks ); }, onMetaChange( updatedMeta ) { - const { getEditorSettings } = select( 'core/block-editor' ); - const onChangeMeta = getEditorSettings().__experimentalMetaSource.onChange; + const { getSettings } = select( 'core/block-editor' ); + const onChangeMeta = getSettings().__experimentalMetaSource.onChange; onChangeMeta( updatedMeta ); }, onShiftSelection() { diff --git a/packages/editor/src/components/block-list/hover-area.js b/packages/editor/src/components/block-list/hover-area.js index d7091552b4885..a79b0bcd9b088 100644 --- a/packages/editor/src/components/block-list/hover-area.js +++ b/packages/editor/src/components/block-list/hover-area.js @@ -76,7 +76,7 @@ class HoverArea extends Component { export default withSelect( ( select ) => { return { - isRTL: select( 'core/block-editor' ).getEditorSettings().isRTL, + isRTL: select( 'core/block-editor' ).getSettings().isRTL, }; } )( HoverArea ); diff --git a/packages/editor/src/components/color-palette/with-color-context.js b/packages/editor/src/components/color-palette/with-color-context.js index 48eaa8085fca9..1c32141f2a3f1 100644 --- a/packages/editor/src/components/color-palette/with-color-context.js +++ b/packages/editor/src/components/color-palette/with-color-context.js @@ -13,7 +13,7 @@ import { withSelect } from '@wordpress/data'; export default createHigherOrderComponent( withSelect( ( select, ownProps ) => { - const settings = select( 'core/block-editor' ).getEditorSettings(); + const settings = select( 'core/block-editor' ).getSettings(); const colors = ownProps.colors === undefined ? settings.colors : ownProps.colors; diff --git a/packages/editor/src/components/colors/with-colors.js b/packages/editor/src/components/colors/with-colors.js index 11524408b4a29..516a5a023414f 100644 --- a/packages/editor/src/components/colors/with-colors.js +++ b/packages/editor/src/components/colors/with-colors.js @@ -36,7 +36,7 @@ const withCustomColorPalette = ( colorsArray ) => createHigherOrderComponent( ( * @return {function} The higher order component. */ const withEditorColorPalette = () => withSelect( ( select ) => { - const settings = select( 'core/block-editor' ).getEditorSettings(); + const settings = select( 'core/block-editor' ).getSettings(); return { colors: get( settings, [ 'colors' ], DEFAULT_COLORS ), }; diff --git a/packages/editor/src/components/default-block-appender/index.js b/packages/editor/src/components/default-block-appender/index.js index 5a320ba4c105d..183da30afd8b1 100644 --- a/packages/editor/src/components/default-block-appender/index.js +++ b/packages/editor/src/components/default-block-appender/index.js @@ -74,12 +74,12 @@ export function DefaultBlockAppender( { export default compose( withState( { hovered: false } ), withSelect( ( select, ownProps ) => { - const { getBlockCount, getBlockName, isBlockValid, getEditorSettings, getTemplateLock } = select( 'core/block-editor' ); + const { getBlockCount, getBlockName, isBlockValid, getSettings, getTemplateLock } = select( 'core/block-editor' ); const isEmpty = ! getBlockCount( ownProps.rootClientId ); const isLastBlockDefault = getBlockName( ownProps.lastBlockClientId ) === getDefaultBlockName(); const isLastBlockValid = isBlockValid( ownProps.lastBlockClientId ); - const { bodyPlaceholder } = getEditorSettings(); + const { bodyPlaceholder } = getSettings(); return { isVisible: isEmpty || ! isLastBlockDefault || ! isLastBlockValid, diff --git a/packages/editor/src/components/default-block-appender/index.native.js b/packages/editor/src/components/default-block-appender/index.native.js index ab34b5ebbde21..eae900cc70362 100644 --- a/packages/editor/src/components/default-block-appender/index.native.js +++ b/packages/editor/src/components/default-block-appender/index.native.js @@ -49,10 +49,10 @@ export function DefaultBlockAppender( { export default compose( withSelect( ( select, ownProps ) => { - const { getBlockCount, getEditorSettings, getTemplateLock } = select( 'core/block-editor' ); + const { getBlockCount, getSettings, getTemplateLock } = select( 'core/block-editor' ); const isEmpty = ! getBlockCount( ownProps.rootClientId ); - const { bodyPlaceholder } = getEditorSettings(); + const { bodyPlaceholder } = getSettings(); return { isVisible: isEmpty, diff --git a/packages/editor/src/components/font-sizes/font-size-picker.js b/packages/editor/src/components/font-sizes/font-size-picker.js index 6989bb2233669..e2dbe3dba8134 100644 --- a/packages/editor/src/components/font-sizes/font-size-picker.js +++ b/packages/editor/src/components/font-sizes/font-size-picker.js @@ -9,7 +9,7 @@ export default withSelect( const { disableCustomFontSizes, fontSizes, - } = select( 'core/block-editor' ).getEditorSettings(); + } = select( 'core/block-editor' ).getSettings(); return { disableCustomFontSizes, diff --git a/packages/editor/src/components/font-sizes/with-font-sizes.js b/packages/editor/src/components/font-sizes/with-font-sizes.js index 39953baf801f2..23a8c76a11d25 100644 --- a/packages/editor/src/components/font-sizes/with-font-sizes.js +++ b/packages/editor/src/components/font-sizes/with-font-sizes.js @@ -38,7 +38,7 @@ export default ( ...fontSizeNames ) => { return createHigherOrderComponent( compose( [ withSelect( ( select ) => { - const { fontSizes } = select( 'core/block-editor' ).getEditorSettings(); + const { fontSizes } = select( 'core/block-editor' ).getSettings(); return { fontSizes, }; diff --git a/packages/editor/src/components/media-placeholder/index.js b/packages/editor/src/components/media-placeholder/index.js index 33b2792beb65d..2270b87762ea3 100644 --- a/packages/editor/src/components/media-placeholder/index.js +++ b/packages/editor/src/components/media-placeholder/index.js @@ -263,11 +263,11 @@ export class MediaPlaceholder extends Component { const applyWithSelect = withSelect( ( select ) => { const { canUser } = select( 'core' ); - const { getEditorSettings } = select( 'core/block-editor' ); + const { getSettings } = select( 'core/block-editor' ); return { hasUploadPermissions: defaultTo( canUser( 'create', 'media' ), true ), - mediaUpload: getEditorSettings().__experimentalMediaUpload, + mediaUpload: getSettings().__experimentalMediaUpload, }; } ); diff --git a/packages/editor/src/components/page-attributes/check.js b/packages/editor/src/components/page-attributes/check.js index 014b2aa29136a..56be444a04985 100644 --- a/packages/editor/src/components/page-attributes/check.js +++ b/packages/editor/src/components/page-attributes/check.js @@ -20,11 +20,8 @@ export function PageAttributesCheck( { availableTemplates, postType, children } } export default withSelect( ( select ) => { - const { getEditedPostAttribute } = select( 'core/editor' ); - const { getEditorSettings } = select( 'core/block-editor' ); + const { getEditedPostAttribute, getEditorSettings } = select( 'core/editor' ); const { getPostType } = select( 'core' ); - - // This setting should not live in the block-editor module. const { availableTemplates } = getEditorSettings(); return { postType: getPostType( getEditedPostAttribute( 'type' ) ), diff --git a/packages/editor/src/components/page-attributes/template.js b/packages/editor/src/components/page-attributes/template.js index 8e6d9cef689cd..3646a05e3206f 100644 --- a/packages/editor/src/components/page-attributes/template.js +++ b/packages/editor/src/components/page-attributes/template.js @@ -33,8 +33,7 @@ export function PageTemplate( { availableTemplates, selectedTemplate, onUpdate } export default compose( withSelect( ( select ) => { - const { getEditedPostAttribute } = select( 'core/editor' ); - const { getEditorSettings } = select( 'core/block-editor' ); + const { getEditedPostAttribute, getEditorSettings } = select( 'core/editor' ); const { availableTemplates } = getEditorSettings(); return { selectedTemplate: getEditedPostAttribute( 'template' ), diff --git a/packages/editor/src/components/post-format/check.js b/packages/editor/src/components/post-format/check.js index d13e354655e9d..cd124553a7cf1 100644 --- a/packages/editor/src/components/post-format/check.js +++ b/packages/editor/src/components/post-format/check.js @@ -15,8 +15,7 @@ function PostFormatCheck( { disablePostFormats, ...props } ) { export default withSelect( ( select ) => { - // This setting should not live in the block-editor's store. - const editorSettings = select( 'core/block-editor' ).getEditorSettings(); + const editorSettings = select( 'core/editor' ).getEditorSettings(); return { disablePostFormats: editorSettings.disablePostFormats, }; diff --git a/packages/editor/src/components/post-locked-modal/index.js b/packages/editor/src/components/post-locked-modal/index.js index 8a9b88cbc2109..dd681f1f80980 100644 --- a/packages/editor/src/components/post-locked-modal/index.js +++ b/packages/editor/src/components/post-locked-modal/index.js @@ -220,17 +220,14 @@ export default compose( getCurrentPostId, getActivePostLock, getEditedPostAttribute, - } = select( 'core/editor' ); - const { getEditorSettings, - } = select( 'core/block-editor' ); + } = select( 'core/editor' ); const { getPostType } = select( 'core' ); return { isLocked: isPostLocked(), isTakeover: isPostLockTakeover(), user: getPostLockUser(), postId: getCurrentPostId(), - // This setting should not live in the block-editor's store. postLockUtils: getEditorSettings().postLockUtils, activePostLock: getActivePostLock(), postType: getPostType( getEditedPostAttribute( 'type' ) ), diff --git a/packages/editor/src/components/post-title/index.js b/packages/editor/src/components/post-title/index.js index 761fcff095df4..e992e25bae11a 100644 --- a/packages/editor/src/components/post-title/index.js +++ b/packages/editor/src/components/post-title/index.js @@ -150,10 +150,10 @@ class PostTitle extends Component { const applyWithSelect = withSelect( ( select ) => { const { getEditedPostAttribute, isCleanNewPost } = select( 'core/editor' ); - const { getEditorSettings } = select( 'core/block-editor' ); + const { getSettings } = select( 'core/block-editor' ); const { getPostType } = select( 'core' ); const postType = getPostType( getEditedPostAttribute( 'type' ) ); - const { titlePlaceholder, focusMode, hasFixedToolbar } = getEditorSettings(); + const { titlePlaceholder, focusMode, hasFixedToolbar } = getSettings(); return { isCleanNewPost: isCleanNewPost(), diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index fa85e1b1abadd..92e13d137ce54 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { map } from 'lodash'; +import { map, pick } from 'lodash'; import memize from 'memize'; /** @@ -53,7 +53,22 @@ class EditorProvider extends Component { getBlockEditorSettings( settings, meta, onMetaChange, reusableBlocks ) { return { - ...settings, + ...pick( settings, [ + 'alignWide', + 'colors', + 'disableCustomColors', + 'fontSizes', + 'disableCustomFontSizes', + 'imageSizes', + 'maxWidth', + 'blockTypes', + 'hasFixedToolbar', + 'focusMode', + 'styles', + 'isRTL', + 'bodyPlaceholder', + 'titlePlaceholder', + ] ), __experimentalMetaSource: { value: meta, onChange: onMetaChange, @@ -64,6 +79,8 @@ class EditorProvider extends Component { } componentDidMount() { + this.props.updateEditorSettings( this.props.settings ); + if ( ! this.props.settings.styles ) { return; } @@ -79,6 +96,12 @@ class EditorProvider extends Component { } ); } + componentDidUpdate( prevProps ) { + if ( this.props.settings !== prevProps.settings ) { + this.props.updateEditorSettings( this.props.settings ); + } + } + render() { const { children, @@ -134,6 +157,7 @@ export default compose( [ updatePostLock, resetEditorBlocks, editPost, + updateEditorSettings, } = dispatch( 'core/editor' ); const { createWarningNotice } = dispatch( 'core/notices' ); @@ -142,6 +166,7 @@ export default compose( [ updatePostLock, createWarningNotice, resetEditorBlocks, + updateEditorSettings, resetEditorBlocksWithoutUndoLevel( blocks ) { resetEditorBlocks( blocks, { __unstableShouldCreateUndoLevel: false, diff --git a/packages/editor/src/hooks/align.js b/packages/editor/src/hooks/align.js index b9d2b842cef1d..ca0a019d5cc9e 100644 --- a/packages/editor/src/hooks/align.js +++ b/packages/editor/src/hooks/align.js @@ -168,9 +168,9 @@ export const withDataAlign = createHigherOrderComponent( compose( [ withSelect( ( select ) => { - const { getEditorSettings } = select( 'core/block-editor' ); + const { getSettings } = select( 'core/block-editor' ); return { - hasWideEnabled: !! getEditorSettings().alignWide, + hasWideEnabled: !! getSettings().alignWide, }; } ), diff --git a/packages/editor/src/store/actions.js b/packages/editor/src/store/actions.js index f1271ed3d96d1..c0c88d1072fe9 100644 --- a/packages/editor/src/store/actions.js +++ b/packages/editor/src/store/actions.js @@ -699,6 +699,20 @@ export function resetEditorBlocks( blocks, options = {} ) { }; } +/* + * Returns an action object used in signalling that the post editor settings have been updated. + * + * @param {Object} settings Updated settings + * + * @return {Object} Action object + */ +export function updateEditorSettings( settings ) { + return { + type: 'UPDATE_EDITOR_SETTINGS', + settings, + }; +} + /** * Backward compatibility */ @@ -737,4 +751,3 @@ export const enterFormattedText = getBlockEditorAction( 'enterFormattedText' ); export const exitFormattedText = getBlockEditorAction( 'exitFormattedText' ); export const insertDefaultBlock = getBlockEditorAction( 'insertDefaultBlock' ); export const updateBlockListSettings = getBlockEditorAction( 'updateBlockListSettings' ); -export const updateEditorSettings = getBlockEditorAction( 'updateEditorSettings' ); diff --git a/packages/editor/src/store/defaults.js b/packages/editor/src/store/defaults.js index 32fd438302c69..53178b43cb5aa 100644 --- a/packages/editor/src/store/defaults.js +++ b/packages/editor/src/store/defaults.js @@ -1,3 +1,8 @@ +/** + * WordPress dependencies + */ +import { SETTINGS_DEFAULTS } from '@wordpress/block-editor'; + export const PREFERENCES_DEFAULTS = { isPublishSidebarEnabled: true, }; @@ -8,3 +13,22 @@ export const PREFERENCES_DEFAULTS = { * @type {Object} */ export const INITIAL_EDITS_DEFAULTS = {}; + +/** + * The default post editor settings + * + * richEditingEnabled boolean Whether rich editing is enabled or not + * enableCustomFields boolean Whether the WordPress custom fields are enabled or not + * autosaveInterval number Autosave Interval + * availableTemplates array? The available post templates + * disablePostFormats boolean Whether or not the post formats are disabled + * allowedMimeTypes array? List of allowed mime types and file extensions + * maxUploadFileSize number Maximum upload file size + */ +export const EDITOR_SETTINGS_DEFAULTS = { + ...SETTINGS_DEFAULTS, + + richEditingEnabled: true, + enableCustomFields: false, +}; + diff --git a/packages/editor/src/store/reducer.js b/packages/editor/src/store/reducer.js index 68a6624163ae0..b6a4759503ce7 100644 --- a/packages/editor/src/store/reducer.js +++ b/packages/editor/src/store/reducer.js @@ -24,6 +24,7 @@ import { addQueryArgs } from '@wordpress/url'; import { PREFERENCES_DEFAULTS, INITIAL_EDITS_DEFAULTS, + EDITOR_SETTINGS_DEFAULTS, } from './defaults'; import { EDIT_MERGE_PROPERTIES } from './constants'; import withChangeDetection from '../utils/with-change-detection'; @@ -825,6 +826,26 @@ export function isReady( state = false, action ) { return state; } +/** + * Reducer returning the post editor setting. + * + * @param {Object} state Current state. + * @param {Object} action Dispatched action. + * + * @return {Object} Updated state. + */ +export function editorSettings( state = EDITOR_SETTINGS_DEFAULTS, action ) { + switch ( action.type ) { + case 'UPDATE_EDITOR_SETTINGS': + return { + ...state, + ...action.settings, + }; + } + + return state; +} + export default optimist( combineReducers( { editor, initialEdits, @@ -838,4 +859,5 @@ export default optimist( combineReducers( { previewLink, postSavingLock, isReady, + editorSettings, } ) ); diff --git a/packages/editor/src/store/selectors.js b/packages/editor/src/store/selectors.js index c4d97cbd741ba..b840175a5cdd9 100644 --- a/packages/editor/src/store/selectors.js +++ b/packages/editor/src/store/selectors.js @@ -1084,6 +1084,17 @@ export function __unstableIsEditorReady( state ) { return state.isReady; } +/** + * Returns the post editor settings. + * + * @param {Object} state Editor state. + * + * @return {Object} The editor settings object. + */ +export function getEditorSettings( state ) { + return state.editorSettings; +} + /* * Backward compatibility */ @@ -1146,5 +1157,4 @@ export const getTemplateLock = getBlockEditorSelector( 'getTemplateLock' ); export const canInsertBlockType = getBlockEditorSelector( 'canInsertBlockType' ); export const getInserterItems = getBlockEditorSelector( 'getInserterItems' ); export const hasInserterItems = getBlockEditorSelector( 'hasInserterItems' ); -export const getEditorSettings = getBlockEditorSelector( 'getEditorSettings' ); export const getBlockListSettings = getBlockEditorSelector( 'getBlockListSettings' ); diff --git a/packages/editor/src/utils/media-upload/index.js b/packages/editor/src/utils/media-upload/index.js index ced9e70c6b253..f6572e29c6b0c 100644 --- a/packages/editor/src/utils/media-upload/index.js +++ b/packages/editor/src/utils/media-upload/index.js @@ -33,10 +33,7 @@ export default function( { onError = noop, onFileChange, } ) { - const { getCurrentPostId } = select( 'core/editor' ); - const { getEditorSettings } = select( 'core/block-editor' ); - - // These settings should not live in the block editor's store. + const { getCurrentPostId, getEditorSettings } = select( 'core/editor' ); const wpAllowedMimeTypes = getEditorSettings().allowedMimeTypes; maxUploadFileSize = maxUploadFileSize || getEditorSettings().maxUploadFileSize; From c99b7f0c97698b633165eb40ef3100a12de59165 Mon Sep 17 00:00:00 2001 From: Ned Zimmerman <ned@bight.ca> Date: Mon, 4 Mar 2019 05:16:13 -0400 Subject: [PATCH 549/691] Add repository.directory linting rule (fixes #13947) (#14200) * Add repository.directory rule to npm-package-json-lint-config (fix #13947) * Fix lock file (props @aduth) * Fix rule, update changelogs * Add PR reference * Add directory field to packages/docgen * Apply changes suggested by @ntwb * Update CHANGELOG.md --- package-lock.json | 176 +++++++++++------- package.json | 1 + packages/docgen/package.json | 3 +- .../npm-package-json-lint-config/CHANGELOG.md | 8 +- .../npm-package-json-lint-config/index.js | 1 + .../npm-package-json-lint-config/package.json | 2 +- packages/scripts/CHANGELOG.md | 1 + packages/scripts/package.json | 2 +- 8 files changed, 121 insertions(+), 73 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2f0d39cd7a38c..44476fc682168 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2908,60 +2908,6 @@ "enzyme": "^3.9.0", "enzyme-adapter-react-16": "^1.10.0", "enzyme-to-json": "^3.3.5" - }, - "dependencies": { - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "enzyme-adapter-react-16": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.10.0.tgz", - "integrity": "sha512-0QqwEZcBv1xEEla+a3H7FMci+y4ybLia9cZzsdIrId7qcig4MK0kqqf6iiCILH1lsKS6c6AVqL3wGPhCevv5aQ==", - "dev": true, - "requires": { - "enzyme-adapter-utils": "^1.10.0", - "object.assign": "^4.1.0", - "object.values": "^1.1.0", - "prop-types": "^15.6.2", - "react-is": "^16.7.0", - "react-test-renderer": "^16.0.0-0" - } - }, - "object.values": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.0.tgz", - "integrity": "sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.12.0", - "function-bind": "^1.1.1", - "has": "^1.0.3" - } - }, - "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "dev": true, - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - } - }, - "react-is": { - "version": "16.8.3", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.3.tgz", - "integrity": "sha512-Y4rC1ZJmsxxkkPuMLwvKvlL1Zfpbcu+Bf4ZigkHup3v9EfdYhAlWAaVyA19olXq2o2mGn0w+dFKvk3pVVlYcIA==", - "dev": true - } } }, "@wordpress/jest-puppeteer-axe": { @@ -3085,7 +3031,7 @@ "eslint": "^5.12.1", "jest": "^24.1.0", "jest-puppeteer": "^4.0.0", - "npm-package-json-lint": "^3.3.1", + "npm-package-json-lint": "^3.6.0", "puppeteer": "1.6.1", "read-pkg-up": "^1.0.1", "resolve-bin": "^0.4.0", @@ -7400,6 +7346,60 @@ "string.prototype.trim": "^1.1.2" } }, + "enzyme-adapter-react-16": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.10.0.tgz", + "integrity": "sha512-0QqwEZcBv1xEEla+a3H7FMci+y4ybLia9cZzsdIrId7qcig4MK0kqqf6iiCILH1lsKS6c6AVqL3wGPhCevv5aQ==", + "dev": true, + "requires": { + "enzyme-adapter-utils": "^1.10.0", + "object.assign": "^4.1.0", + "object.values": "^1.1.0", + "prop-types": "^15.6.2", + "react-is": "^16.7.0", + "react-test-renderer": "^16.0.0-0" + }, + "dependencies": { + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "object.values": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.0.tgz", + "integrity": "sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.12.0", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "dev": true, + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + }, + "react-is": { + "version": "16.8.3", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.3.tgz", + "integrity": "sha512-Y4rC1ZJmsxxkkPuMLwvKvlL1Zfpbcu+Bf4ZigkHup3v9EfdYhAlWAaVyA19olXq2o2mGn0w+dFKvk3pVVlYcIA==", + "dev": true + } + } + }, "enzyme-adapter-utils": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.10.0.tgz", @@ -14910,29 +14910,30 @@ } }, "npm-package-json-lint": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/npm-package-json-lint/-/npm-package-json-lint-3.3.1.tgz", - "integrity": "sha512-Wz5byxFKbitRcQS66wCTqrje/uCLJPH+jsEvIV4H2G//E4iB/X0utVPLZzbs4hukoeKuLFadHbMcILa6Cr027w==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/npm-package-json-lint/-/npm-package-json-lint-3.6.0.tgz", + "integrity": "sha512-N1y3r0l0oN7mYnMfRzZvYF8+NvjIx+zkskRn3J7ofipJKGH4RDDKdEGP/mV1Crf5W8uUo3201VhJe04Q+v9erw==", "dev": true, "requires": { - "ajv": "^6.5.2", - "chalk": "^2.4.1", - "glob": "^7.1.2", + "ajv": "^6.9.2", + "chalk": "^2.4.2", + "glob": "^7.1.3", + "ignore": "^5.0.5", "is-path-inside": "^2.0.0", "is-plain-obj": "^1.1.0", "is-resolvable": "^1.1.0", "log-symbols": "^2.2.0", "meow": "^5.0.0", "plur": "^3.0.1", - "semver": "^5.5.0", + "semver": "^5.6.0", "strip-json-comments": "^2.0.1", - "validator": "^10.5.0" + "validator": "^10.11.0" }, "dependencies": { "ajv": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.3.tgz", - "integrity": "sha512-LqZ9wY+fx3UMiiPd741yB2pj3hhil+hQc8taf4o2QGRFpWgZ2V5C8HA165DY9sS3fJwsk7uT7ZlFEyC3Ig3lLg==", + "version": "6.9.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.9.2.tgz", + "integrity": "sha512-4UFy0/LgDo7Oa/+wOAlj44tp9K78u38E5/359eSrqEp1Z5PdVfimCcs7SluXMP755RUQu6d2b4AvF0R1C9RZjg==", "dev": true, "requires": { "fast-deep-equal": "^2.0.1", @@ -14941,17 +14942,54 @@ "uri-js": "^4.2.2" } }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, "fast-deep-equal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", "dev": true }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "ignore": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.0.5.tgz", + "integrity": "sha512-kOC8IUb8HSDMVcYrDVezCxpJkzSQWTAzf3olpKM6o9rM5zpojx23O0Fl8Wr4+qJ6ZbPEHqf1fdwev/DS7v7pmA==", + "dev": true + }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true + }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true } } }, @@ -21467,9 +21505,9 @@ } }, "validator": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-10.5.0.tgz", - "integrity": "sha512-6OOi+eV2mOxCFLq0f2cJDrdB6lrtLXEUxabhNRGjgOLT/l3SSll9J49Cl+LIloUqkWWTPraK/mucEQ3dc2jStQ==", + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz", + "integrity": "sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw==", "dev": true }, "vary": { diff --git a/package.json b/package.json index b0965325aa435..e0a8cf46f1384 100644 --- a/package.json +++ b/package.json @@ -132,6 +132,7 @@ } ], "require-publishConfig": "error", + "require-repository-directory": "error", "valid-values-author": [ "error", [ diff --git a/packages/docgen/package.json b/packages/docgen/package.json index a73ed098f3142..dd94f3b8de2ff 100644 --- a/packages/docgen/package.json +++ b/packages/docgen/package.json @@ -12,7 +12,8 @@ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/docgen/README.md", "repository": { "type": "git", - "url": "git+https://github.com/WordPress/gutenberg.git" + "url": "git+https://github.com/WordPress/gutenberg.git", + "directory": "packages/docgen" }, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" diff --git a/packages/npm-package-json-lint-config/CHANGELOG.md b/packages/npm-package-json-lint-config/CHANGELOG.md index d081e98941bc6..1462fbbcd91fc 100644 --- a/packages/npm-package-json-lint-config/CHANGELOG.md +++ b/packages/npm-package-json-lint-config/CHANGELOG.md @@ -1,4 +1,10 @@ -## v1.1.3 (2018-09-05) +## 1.1.4 (Unreleased) + +### Internal + +- Updated `npm-package-json-lint` dependency [#14200](https://github.com/WordPress/gutenberg/pull/14200) + +## 1.1.3 (2018-09-05) ### Bug Fix diff --git a/packages/npm-package-json-lint-config/index.js b/packages/npm-package-json-lint-config/index.js index 8b9e5dc2208a1..5af34472e68b1 100644 --- a/packages/npm-package-json-lint-config/index.js +++ b/packages/npm-package-json-lint-config/index.js @@ -95,6 +95,7 @@ const defaultConfig = { 'require-private': 'off', 'require-publishConfig': 'off', 'require-repository': 'error', + 'require-repository-directory': 'off', 'require-scripts': 'off', 'require-version': 'error', 'scripts-type': 'error', diff --git a/packages/npm-package-json-lint-config/package.json b/packages/npm-package-json-lint-config/package.json index f61270594fd16..d578fc98298f3 100644 --- a/packages/npm-package-json-lint-config/package.json +++ b/packages/npm-package-json-lint-config/package.json @@ -23,7 +23,7 @@ }, "main": "index.js", "peerDependencies": { - "npm-package-json-lint": ">=3.3.1" + "npm-package-json-lint": ">=3.6.0" }, "publishConfig": { "access": "public" diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index 342ada51d8374..4e9ea213d597e 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -10,6 +10,7 @@ - Added support for `build` script ([#12837](https://github.com/WordPress/gutenberg/pull/12837)) - Added support for `start` script ([#12837](https://github.com/WordPress/gutenberg/pull/12837)) +- Updated `npm-package-json-lint` dependency [#14200](https://github.com/WordPress/gutenberg/pull/14200) ### Bug Fix diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 6a2f625c182ab..e59064fc82f38 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -43,7 +43,7 @@ "eslint": "^5.12.1", "jest": "^24.1.0", "jest-puppeteer": "^4.0.0", - "npm-package-json-lint": "^3.3.1", + "npm-package-json-lint": "^3.6.0", "puppeteer": "1.6.1", "read-pkg-up": "^1.0.1", "resolve-bin": "^0.4.0", From 91afecf2ba9284cc7f36d2a080dbc9784674b6cc Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Mon, 4 Mar 2019 04:58:52 -0500 Subject: [PATCH 550/691] Plugin: Remove deprecated `_wpLoadGutenbergEditor`, `gutenberg` theme supports (#14144) * Plugin: Remove deprecated `gutenberg` theme supports * Plugin: Remove deprecated `_wpLoadGutenbergEditor` --- lib/client-assets.php | 40 +++++++++------------------------------- 1 file changed, 9 insertions(+), 31 deletions(-) diff --git a/lib/client-assets.php b/lib/client-assets.php index 040557e3cfb11..f721c3855bc0d 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -760,15 +760,9 @@ function gutenberg_editor_scripts_and_styles( $hook ) { wp_localize_script( 'wp-editor', '_wpMetaBoxUrl', $meta_box_url ); // Initialize the editor. - $gutenberg_theme_support = get_theme_support( 'gutenberg' ); - $align_wide = get_theme_support( 'align-wide' ); - $color_palette = current( (array) get_theme_support( 'editor-color-palette' ) ); - $font_sizes = current( (array) get_theme_support( 'editor-font-sizes' ) ); - - if ( ! empty( $gutenberg_theme_support ) ) { - wp_enqueue_script( 'wp-deprecated' ); - wp_add_inline_script( 'wp-deprecated', 'wp.deprecated( "`gutenberg` theme support", { plugin: "Gutenberg", version: "5.2", alternative: "`align-wide` theme support" } );' ); - } + $align_wide = get_theme_support( 'align-wide' ); + $color_palette = current( (array) get_theme_support( 'editor-color-palette' ) ); + $font_sizes = current( (array) get_theme_support( 'editor-font-sizes' ) ); /** * Filters the allowed block types for the editor, defaulting to true (all @@ -879,7 +873,7 @@ function gutenberg_editor_scripts_and_styles( $hook ) { } $editor_settings = array( - 'alignWide' => $align_wide || ! empty( $gutenberg_theme_support[0]['wide-images'] ), // Backcompat. Use `align-wide` outside of `gutenberg` array. + 'alignWide' => $align_wide, 'availableTemplates' => $available_templates, 'allowedBlockTypes' => $allowed_block_types, 'disableCustomColors' => get_theme_support( 'disable-custom-colors' ), @@ -952,28 +946,12 @@ function gutenberg_editor_scripts_and_styles( $hook ) { $editor_settings = apply_filters( 'block_editor_settings', $editor_settings, $post ); $init_script = <<<JS - ( function() { - window._wpLoadBlockEditor = new Promise( function( resolve ) { - wp.domReady( function() { - resolve( wp.editPost.initializeEditor( 'editor', "%s", %d, %s, %s ) ); - } ); - } ); - - Object.defineProperty( window, '_wpLoadGutenbergEditor', { - get: function() { - // TODO: Hello future maintainer. In removing this deprecation, - // ensure also to check whether `wp-editor`'s dependencies in - // `package-dependencies.php` still require `wp-deprecated`. - wp.deprecated( '`window._wpLoadGutenbergEditor`', { - plugin: 'Gutenberg', - version: '5.2', - alternative: '`window._wpLoadBlockEditor`', - hint: 'This is a private API, not intended for public use. It may be removed in the future.' - } ); - - return window._wpLoadBlockEditor; - } +( function() { + window._wpLoadBlockEditor = new Promise( function( resolve ) { + wp.domReady( function() { + resolve( wp.editPost.initializeEditor( 'editor', "%s", %d, %s, %s ) ); } ); + } ); } )(); JS; From 975727d208268c3b75529ea57fe6bbcd2b9dba32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20Van=C2=A0Durpe?= <iseulde@automattic.com> Date: Mon, 4 Mar 2019 11:47:31 +0100 Subject: [PATCH 551/691] Bump plugin version to 5.2.0-rc.1 (#14210) --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 993f9e4566834..2ff8f5e26254b 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,7 +3,7 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the new block editor in core. - * Version: 5.1.1 + * Version: 5.2.0-rc.1 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index 44476fc682168..3b660b120761b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.1.1", + "version": "5.2.0-rc.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index e0a8cf46f1384..fdd2122723c81 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.1.1", + "version": "5.2.0-rc.1", "private": true, "description": "A new WordPress editor experience", "repository": "git+https://github.com/WordPress/gutenberg.git", From e67de72e8c50d0e9689a97e577ba520320b61edf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Mon, 4 Mar 2019 13:05:02 +0100 Subject: [PATCH 552/691] Packages: Add missing expect-pupeteer dependency to e2e-tests package (#14212) --- package-lock.json | 1 + packages/e2e-tests/package.json | 1 + 2 files changed, 2 insertions(+) diff --git a/package-lock.json b/package-lock.json index 3b660b120761b..38cb849991764 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2742,6 +2742,7 @@ "@wordpress/e2e-test-utils": "file:packages/e2e-test-utils", "@wordpress/jest-console": "file:packages/jest-console", "@wordpress/scripts": "file:packages/scripts", + "expect-puppeteer": "^4.0.0", "lodash": "^4.17.11" } }, diff --git a/packages/e2e-tests/package.json b/packages/e2e-tests/package.json index c7e096b781d76..a10d916e486d3 100644 --- a/packages/e2e-tests/package.json +++ b/packages/e2e-tests/package.json @@ -25,6 +25,7 @@ "@wordpress/e2e-test-utils": "file:../e2e-test-utils", "@wordpress/jest-console": "file:../jest-console", "@wordpress/scripts": "file:../scripts", + "expect-puppeteer": "^4.0.0", "lodash": "^4.17.11" }, "peerDependencies": { From 00454e469955fa699bf7c6e49bd06039ccb5f044 Mon Sep 17 00:00:00 2001 From: Garrett Hyder <garrett@eclipse3sixty.com> Date: Mon, 4 Mar 2019 04:24:23 -0800 Subject: [PATCH 553/691] Lowercase Block Editor to block editor as per the core spelling Best Practices (#14205) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description I've updated all references to Block Editor to be lowercase as they aren't proper nouns. This conforms to the core spelling Best Practices; “block editor” or “block-based editor” | “Block Editor” or “Gutenberg” | When referring to the new editor. Reference - https://make.wordpress.org/core/handbook/best-practices/spelling/ Also ties back to the original discussion around the capitalization convention here; https://github.com/WordPress/gutenberg/pull/12856 Previously opened #14203 to handle the Classic Editor changes. ## How has this been tested? Only tested that it didn't break anything. ## Types of changes Mostly comment changes, there was one string change and a couple translator comment changes. ## Checklist: - [x] My code is tested. - [x] My code follows the WordPress code style. <!-- Check code: `npm run lint`, Guidelines: https://make.wordpress.org/core/handbook/best-practices/coding-standards/javascript/ --> - [x] My code follows the accessibility standards. <!-- Guidelines: https://make.wordpress.org/core/handbook/best-practices/coding-standards/accessibility-coding-standards/ --> - [x] My code has proper inline documentation. <!-- Guidelines: https://make.wordpress.org/core/handbook/best-practices/inline-documentation-standards/javascript/ --> - [x] I've included developer documentation if appropriate. <!-- Handbook: https://wordpress.org/gutenberg/handbook/designers-developers/ --> --- docs/designers-developers/designers/README.md | 2 +- .../javascript/extending-the-block-editor.md | 2 +- .../tutorials/javascript/loading-javascript.md | 4 ++-- .../developers/tutorials/javascript/readme.md | 4 ++-- .../metabox/meta-block-2-register-meta.md | 2 +- .../tutorials/metabox/meta-block-3-add.md | 2 +- .../developers/tutorials/metabox/readme.md | 2 +- .../developers/tutorials/notices/README.md | 18 +++++++++--------- gutenberg.php | 2 +- packages/block-editor/README.md | 2 +- packages/block-editor/package.json | 2 +- 11 files changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/designers-developers/designers/README.md b/docs/designers-developers/designers/README.md index 362cf794885f2..a630847b37661 100644 --- a/docs/designers-developers/designers/README.md +++ b/docs/designers-developers/designers/README.md @@ -1,3 +1,3 @@ # Designer Documentation -For those designing blocks and other Block Editor integrations, this documentation will provide resources for creating beautiful and intuitive layouts. +For those designing blocks and other block editor integrations, this documentation will provide resources for creating beautiful and intuitive layouts. diff --git a/docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md b/docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md index 8d175ef3e6d9b..024b1f50e9367 100644 --- a/docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md +++ b/docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md @@ -32,7 +32,7 @@ The last argument in the `wp_enqueue_script()` function is an array of dependenc See [Packages](/docs/designers-developers/developers/packages.md) for list of available packages and what objects they export. -After you have updated both JavaScript and PHP files, go to the Block Editor and create a new post. +After you have updated both JavaScript and PHP files, go to the block editor and create a new post. Add a quote block, and in the right sidebar under Styles, you will see your new Fancy Quote style listed. Click the Fancy Quote to select and apply that style to your quote block. diff --git a/docs/designers-developers/developers/tutorials/javascript/loading-javascript.md b/docs/designers-developers/developers/tutorials/javascript/loading-javascript.md index 813b8fa3e2bdb..c3805f26845cc 100644 --- a/docs/designers-developers/developers/tutorials/javascript/loading-javascript.md +++ b/docs/designers-developers/developers/tutorials/javascript/loading-javascript.md @@ -30,7 +30,7 @@ If your code is registered and enqueued correctly, you should see a message in y ![Console Log Message Success](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/assets/js-tutorial-console-log-success.png) -**Note for Theme Developers:** The above method of enqueing is used for plugins. If you are extending the Block Editor for your theme there is a minor difference, you will use the `get_template_directory_uri()` function instead of `plugins_url()`. So for a theme, the enqueue example is: +**Note for Theme Developers:** The above method of enqueing is used for plugins. If you are extending the block editor for your theme there is a minor difference, you will use the `get_template_directory_uri()` function instead of `plugins_url()`. So for a theme, the enqueue example is: ```php function myguten_enqueue() { @@ -46,4 +46,4 @@ add_action( 'enqueue_block_editor_assets', 'myguten_enqueue' ); At this point, you have a plugin in the directory `wp-content/plugins/myguten-plugin` with two files: the PHP server-side code in `myguten-plugin.php`, and the JavaScript which runs in the browser in `myguten.js`. -This puts all the initial pieces in place for you to start extending the Block Editor. +This puts all the initial pieces in place for you to start extending the block editor. diff --git a/docs/designers-developers/developers/tutorials/javascript/readme.md b/docs/designers-developers/developers/tutorials/javascript/readme.md index 55628c1cd398e..f425f2726df67 100644 --- a/docs/designers-developers/developers/tutorials/javascript/readme.md +++ b/docs/designers-developers/developers/tutorials/javascript/readme.md @@ -1,12 +1,12 @@ # Getting Started with JavaScript -The purpose of this tutorial is to step through getting started with JavaScript and WordPress, specifically around the new Block Editor. The Gutenberg Handbook documentation contains information on the APIs available for working with the block editor. The goal of this tutorial is get you comfortable on how to use the API reference and snippets of code found within. +The purpose of this tutorial is to step through getting started with JavaScript and WordPress, specifically around the new block editor. The Gutenberg Handbook documentation contains information on the APIs available for working with the block editor. The goal of this tutorial is get you comfortable on how to use the API reference and snippets of code found within. ### What is JavaScript JavaScript is a programming language which is loaded and executed in your web browser; compared to PHP which is run by a web server with the results sent to the browser, typically as HTML. -The Block Editor introduced in WordPress 5.0 is written entirely in JavaScript, with the code run in the browser, and not on the server, this allows for a richer and more dynamic user experience. It also requires to learn how to use JavaScript to extend and enhance the Block Editor. +The block editor introduced in WordPress 5.0 is written entirely in JavaScript, with the code run in the browser, and not on the server, this allows for a richer and more dynamic user experience. It also requires to learn how to use JavaScript to extend and enhance the block editor. ### Table of Contents diff --git a/docs/designers-developers/developers/tutorials/metabox/meta-block-2-register-meta.md b/docs/designers-developers/developers/tutorials/metabox/meta-block-2-register-meta.md index e58889c7599df..795bd722ece29 100644 --- a/docs/designers-developers/developers/tutorials/metabox/meta-block-2-register-meta.md +++ b/docs/designers-developers/developers/tutorials/metabox/meta-block-2-register-meta.md @@ -2,7 +2,7 @@ A post meta field is a WordPress object used to store extra data about a post. You need to first register a new meta field prior to use. See Managing [Post Metadata](https://developer.wordpress.org/plugins/metadata/managing-post-metadata/) to learn more about post meta. -When registering the field, note the `show_in_rest` parameter. This ensures the data will be included in the REST API, which the Block Editor uses to load and save meta data. See the [`register_meta`](https://developer.wordpress.org/reference/functions/register_meta/) function definition for extra information. +When registering the field, note the `show_in_rest` parameter. This ensures the data will be included in the REST API, which the block editor uses to load and save meta data. See the [`register_meta`](https://developer.wordpress.org/reference/functions/register_meta/) function definition for extra information. To register the field, create a PHP plugin file called `myguten-meta-block.php` including: diff --git a/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md b/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md index e1e6749c17ff7..0a173a539a82b 100644 --- a/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md +++ b/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md @@ -6,7 +6,7 @@ For this block, you will use the TextControl component, which is similar to an H Attributes are the information displayed in blocks. As shown in the block tutorial, the source of attributes come from the text or HTML a user writes in the editor. For your meta block, the attribute will come from the post meta field. -By specifying the source of the attributes as `meta`, the Block Editor automatically handles the loading and storing of the data; no REST API or data functions are needed. +By specifying the source of the attributes as `meta`, the block editor automatically handles the loading and storing of the data; no REST API or data functions are needed. Add this code to your JavaScript file (this tutorial will call the file `myguten.js`): diff --git a/docs/designers-developers/developers/tutorials/metabox/readme.md b/docs/designers-developers/developers/tutorials/metabox/readme.md index aa13df05ff580..a1e29fb2beeff 100644 --- a/docs/designers-developers/developers/tutorials/metabox/readme.md +++ b/docs/designers-developers/developers/tutorials/metabox/readme.md @@ -4,7 +4,7 @@ Prior to the block editor, custom meta boxes were used to extend the editor. Wit The new block editor does support most existing meta boxes, see [this backward compatibility article](/docs/designers-developers/developers/backward-compatibility/meta-box.md) for more support details . -Here are two mini-tutorials for creating similar functionality to meta boxes in the Block Editor. +Here are two mini-tutorials for creating similar functionality to meta boxes in the block editor. ## Use Blocks to Store Meta diff --git a/docs/designers-developers/developers/tutorials/notices/README.md b/docs/designers-developers/developers/tutorials/notices/README.md index 63296f41dfb4d..cde3670e8bf00 100644 --- a/docs/designers-developers/developers/tutorials/notices/README.md +++ b/docs/designers-developers/developers/tutorials/notices/README.md @@ -2,7 +2,7 @@ Notices are informational UI displayed near the top of admin pages. WordPress core, themes, and plugins all use notices to indicate the result of an action, or to draw the user's attention to necessary information. -In the Classic Editor, notices hooked onto the `admin_notices` action can render whatever HTML they'd like. In the Block Editor, notices are restricted to a more formal API. +In the Classic Editor, notices hooked onto the `admin_notices` action can render whatever HTML they'd like. In the block editor, notices are restricted to a more formal API. ## Notices in the Classic Editor @@ -33,13 +33,13 @@ function myguten_admin_notice() { add_action( 'admin_notices', 'myguten_admin_notice' ); ``` -Importantly, the `admin_notices` hook allows a developer to render whatever HTML they'd like. One advantage is that the developer has a great amount of flexibility. The key disadvantage is that arbitrary HTML makes future iterations on notices more difficult, if not possible, because the iterations need to accommodate for arbitrary HTML. This is why the Block Editor has a formal API. +Importantly, the `admin_notices` hook allows a developer to render whatever HTML they'd like. One advantage is that the developer has a great amount of flexibility. The key disadvantage is that arbitrary HTML makes future iterations on notices more difficult, if not possible, because the iterations need to accommodate for arbitrary HTML. This is why the block editor has a formal API. ## Notices in the Block Editor -In the Block Editor, here's an example of the "Post published" notice: +In the block editor, here's an example of the "Post published" notice: -![Post published in the Block Editor](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/notices/block-editor-notice.png) +![Post published in the block editor](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/notices/block-editor-notice.png) Producing an equivalent "Post published" notice would require code like this: @@ -67,14 +67,14 @@ You'll want to use this _Notices Data API_ when producing a notice from within t To better understand the specific code example above: * `wp` is WordPress global window variable. -* `wp.data` is an object provided by the Block Editor for accessing the Block Editor data store. -* `wp.data.dispatch('core/notices')` accesses functionality registered to the Block Editor data store by the Notices package. -* `createNotice()` is a function offered by the Notices package to register a new notice. The Block Editor reads from the notice data store in order to know which notices to display. +* `wp.data` is an object provided by the block editor for accessing the block editor data store. +* `wp.data.dispatch('core/notices')` accesses functionality registered to the block editor data store by the Notices package. +* `createNotice()` is a function offered by the Notices package to register a new notice. The block editor reads from the notice data store in order to know which notices to display. -Check out the [_Loading JavaScript_](/docs/designers-developers/developers/tutorials/javascript/loading-javascript.md) tutorial for a primer on how to load your custom JavaScript into the Block Editor. +Check out the [_Loading JavaScript_](/docs/designers-developers/developers/tutorials/javascript/loading-javascript.md) tutorial for a primer on how to load your custom JavaScript into the block editor. ## Learn More -The Block Editor offers a complete API for generating notices. The official documentation is a great place to review what's possible. +The block editor offers a complete API for generating notices. The official documentation is a great place to review what's possible. For a full list of the available actions and selectors, refer to the [Notices Data Handbook](/docs/designers-developers/developers/data/data-core-notices.md) page. diff --git a/gutenberg.php b/gutenberg.php index 2ff8f5e26254b..feb70cd894c8b 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -34,7 +34,7 @@ function the_gutenberg_project() { <?php printf( /* translators: %s: https://wordpress.org/plugins/classic-editor/ */ - __( 'The Block Editor requires JavaScript. Please try the <a href="%s">Classic Editor plugin</a>.', 'gutenberg' ), + __( 'The block editor requires JavaScript. Please try the <a href="%s">Classic Editor plugin</a>.', 'gutenberg' ), __( 'https://wordpress.org/plugins/classic-editor/', 'gutenberg' ) ); ?> diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index 9e07cf10eb2ce..f58305a4d15d1 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -1,6 +1,6 @@ # Block Editor -Generic Block Editor Module. +Generic block editor module. ## Installation diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json index 6f593a1063edf..232ed8a681fcd 100644 --- a/packages/block-editor/package.json +++ b/packages/block-editor/package.json @@ -1,7 +1,7 @@ { "name": "@wordpress/block-editor", "version": "1.0.0-alpha.0", - "description": "Generic Block Editor.", + "description": "Generic block editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", "keywords": [ From ff25bd4ade282053b1c2e71fd8fbd80fcbdb05b4 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Mon, 4 Mar 2019 16:39:23 -0500 Subject: [PATCH 554/691] Babel Preset Default: Avoid disabling regenerator option (#14130) * Babel Preset Default: Remove redundant (defaulted) corejs option * Scripts: Assign Babel runtime regenerator as externals in Webpack config * Babel Preset Default: Avoid disabling regenerator option --- packages/babel-preset-default/CHANGELOG.md | 8 ++++++-- packages/babel-preset-default/index.js | 2 -- .../test/__snapshots__/index.js.snap | 9 +++++---- packages/scripts/config/webpack.config.js | 8 ++++++++ 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/packages/babel-preset-default/CHANGELOG.md b/packages/babel-preset-default/CHANGELOG.md index 3fe7a9f29b100..2f2ce6a633962 100644 --- a/packages/babel-preset-default/CHANGELOG.md +++ b/packages/babel-preset-default/CHANGELOG.md @@ -1,13 +1,17 @@ ## 4.0.0 (Unreleased) -## Breaking Change +### Breaking Change - Removed `babel-core` dependency acting as Babel 7 bridge ([#13922](https://github.com/WordPress/gutenberg/pull/13922). Ensure all references to `babel-core` are replaced with `@babel/core` . - Preset updated to include `@wordpress/babel-plugin-import-jsx-pragma` plugin integration ([#13540](https://github.com/WordPress/gutenberg/pull/13540)). +### Bug Fix + +- The runtime transform no longer disables [the `regenerator` option](https://babeljs.io/docs/en/babel-plugin-transform-runtime#regenerator). This should resolve issues where a file generated using the preset would assume the presence of a `regeneratorRuntime` object in the global scope. While this is not considered a breaking change, you may be mindful to consider that with transformed output now explicitly importing the runtime regenerator, bundle sizes may increase if you do not otherwise mitigate the additional import by either (a) overriding the option in your own Babel configuration extensions or (b) redefining the resolved value of `@babel/runtime/regenerator` using a feature like [Webpack's `externals` option](https://webpack.js.org/configuration/externals/). + ## 3.0.0 (2018-09-30) -## Breaking Change +### Breaking Change - The configured `@babel/preset-env` preset will no longer pass `useBuiltIns: 'usage'` as an option. It is therefore expected that a polyfill serve in its place, if necessary. diff --git a/packages/babel-preset-default/index.js b/packages/babel-preset-default/index.js index 44a0392cd82cb..8a893ee9c5ea7 100644 --- a/packages/babel-preset-default/index.js +++ b/packages/babel-preset-default/index.js @@ -28,9 +28,7 @@ module.exports = function( api ) { } ], '@babel/plugin-proposal-async-generator-functions', ! isTestEnv && [ '@babel/plugin-transform-runtime', { - corejs: false, // We polyfill so we don't need core-js. helpers: true, - regenerator: false, // We polyfill so we don't need regenerator. useESModules: false, } ], ].filter( Boolean ), diff --git a/packages/babel-preset-default/test/__snapshots__/index.js.snap b/packages/babel-preset-default/test/__snapshots__/index.js.snap index 5cf000511103d..ed8a87a05dbfe 100644 --- a/packages/babel-preset-default/test/__snapshots__/index.js.snap +++ b/packages/babel-preset-default/test/__snapshots__/index.js.snap @@ -2,6 +2,7 @@ exports[`Babel preset default transpilation works properly 1`] = ` "import _asyncToGenerator from \\"@babel/runtime/helpers/asyncToGenerator\\"; +import _regeneratorRuntime from \\"@babel/runtime/regenerator\\"; import _awaitAsyncGenerator from \\"@babel/runtime/helpers/awaitAsyncGenerator\\"; import _wrapAsyncGenerator from \\"@babel/runtime/helpers/wrapAsyncGenerator\\"; describe('Babel preset default', function () { @@ -12,8 +13,8 @@ describe('Babel preset default', function () { function _foo() { _foo = _wrapAsyncGenerator( /*#__PURE__*/ - regeneratorRuntime.mark(function _callee() { - return regeneratorRuntime.wrap(function _callee$(_context) { + _regeneratorRuntime.mark(function _callee() { + return _regeneratorRuntime.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: @@ -38,9 +39,9 @@ describe('Babel preset default', function () { /*#__PURE__*/ _asyncToGenerator( /*#__PURE__*/ - regeneratorRuntime.mark(function _callee2() { + _regeneratorRuntime.mark(function _callee2() { var generator; - return regeneratorRuntime.wrap(function _callee2$(_context2) { + return _regeneratorRuntime.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: diff --git a/packages/scripts/config/webpack.config.js b/packages/scripts/config/webpack.config.js index 44d0ff61d9a65..70fed30229ba4 100644 --- a/packages/scripts/config/webpack.config.js +++ b/packages/scripts/config/webpack.config.js @@ -51,6 +51,14 @@ const externals = [ jquery: 'jQuery', lodash: 'lodash', 'lodash-es': 'lodash', + + // Distributed NPM packages may depend on Babel's runtime regenerator. + // In a WordPress context, the regenerator is assigned to the global + // scope via the `wp-polyfill` script. It is reassigned here as an + // externals to reduce the size of generated bundles. + // + // See: https://github.com/WordPress/gutenberg/issues/13890 + '@babel/runtime/regenerator': 'regeneratorRuntime', }, wordpressExternals, ]; From 7c952cf24823b36251512dc433d1041d2c5c50ed Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Tue, 5 Mar 2019 02:43:06 -0500 Subject: [PATCH 555/691] Testing: Await E2E promise interactions (#14219) * Testing: Await E2E promise interactions * Testing: Disable animations on every admin screen * Testing: Refactor reusable block management to compare before/after entry count --- .../e2e-tests/plugins/disable-animations.php | 2 +- .../specs/block-hierarchy-navigation.test.js | 8 ++++---- .../e2e-tests/specs/change-detection.test.js | 4 ++-- packages/e2e-tests/specs/demo.test.js | 2 +- .../specs/manage-reusable-blocks.test.js | 18 ++++++++++++++++-- packages/e2e-tests/specs/taxonomies.test.js | 2 +- 6 files changed, 25 insertions(+), 11 deletions(-) diff --git a/packages/e2e-tests/plugins/disable-animations.php b/packages/e2e-tests/plugins/disable-animations.php index f13c2c9003a2d..4e69a6ba88f89 100644 --- a/packages/e2e-tests/plugins/disable-animations.php +++ b/packages/e2e-tests/plugins/disable-animations.php @@ -15,4 +15,4 @@ function enqueue_disable_animations_stylesheet() { wp_add_inline_style( 'wp-components', $custom_css ); } -add_action( 'enqueue_block_editor_assets', 'enqueue_disable_animations_stylesheet' ); +add_action( 'admin_enqueue_scripts', 'enqueue_disable_animations_stylesheet' ); diff --git a/packages/e2e-tests/specs/block-hierarchy-navigation.test.js b/packages/e2e-tests/specs/block-hierarchy-navigation.test.js index 4745768f812c7..28b5c88b9d2c2 100644 --- a/packages/e2e-tests/specs/block-hierarchy-navigation.test.js +++ b/packages/e2e-tests/specs/block-hierarchy-navigation.test.js @@ -31,10 +31,10 @@ describe( 'Navigating the block hierarchy', () => { // Tweak the columns count. await page.focus( '.edit-post-settings-sidebar__panel-block .components-range-control__number[aria-label="Columns"]' ); - page.keyboard.down( 'Shift' ); - page.keyboard.press( 'ArrowLeft' ); - page.keyboard.up( 'Shift' ); - page.keyboard.type( '3' ); + await page.keyboard.down( 'Shift' ); + await page.keyboard.press( 'ArrowLeft' ); + await page.keyboard.up( 'Shift' ); + await page.keyboard.type( '3' ); // Navigate to the last column block. await page.click( '[aria-label="Block Navigation"]' ); diff --git a/packages/e2e-tests/specs/change-detection.test.js b/packages/e2e-tests/specs/change-detection.test.js index b8f2377ce2c91..0e529a95ca512 100644 --- a/packages/e2e-tests/specs/change-detection.test.js +++ b/packages/e2e-tests/specs/change-detection.test.js @@ -18,9 +18,9 @@ describe( 'Change detection', () => { await createNewPost(); } ); - afterEach( () => { + afterEach( async () => { if ( handleInterceptedRequest ) { - releaseSaveIntercept(); + await releaseSaveIntercept(); } } ); diff --git a/packages/e2e-tests/specs/demo.test.js b/packages/e2e-tests/specs/demo.test.js index 80d3fbfeb2939..73fc77966cf6d 100644 --- a/packages/e2e-tests/specs/demo.test.js +++ b/packages/e2e-tests/specs/demo.test.js @@ -22,7 +22,7 @@ const stripIframeFromEmbed = ( embedObject ) => { describe( 'new editor state', () => { beforeAll( async () => { - setUpResponseMocking( [ + await setUpResponseMocking( [ { match: createURLMatcher( 'oembed%2F1.0%2Fproxy' ), onRequestMatch: mockOrTransform( couldNotBePreviewed, MOCK_VIMEO_RESPONSE, stripIframeFromEmbed ), diff --git a/packages/e2e-tests/specs/manage-reusable-blocks.test.js b/packages/e2e-tests/specs/manage-reusable-blocks.test.js index 05af841189a83..e846de123004d 100644 --- a/packages/e2e-tests/specs/manage-reusable-blocks.test.js +++ b/packages/e2e-tests/specs/manage-reusable-blocks.test.js @@ -9,11 +9,23 @@ import path from 'path'; import { visitAdminPage } from '@wordpress/e2e-test-utils'; describe( 'Managing reusable blocks', () => { + /** + * Returns a Promise which resolves to the number of post list entries on + * the current page. + * + * @return {Promise} Promise resolving to number of post list entries. + */ + async function getNumberOfEntries() { + return page.evaluate( () => document.querySelectorAll( '.entry' ).length ); + } + beforeAll( async () => { await visitAdminPage( 'edit.php', 'post_type=wp_block' ); } ); it( 'Should import reusable blocks', async () => { + const originalEntries = await getNumberOfEntries(); + // Import Reusable block await page.waitForSelector( '.list-reusable-blocks__container' ); const importButton = await page.$( '.list-reusable-blocks__container button' ); @@ -36,7 +48,9 @@ describe( 'Managing reusable blocks', () => { // Refresh the page await visitAdminPage( 'edit.php', 'post_type=wp_block' ); - // The reusable block has been imported - page.waitForXPath( 'div[@class="post_title"][contains(text(), "Greeting")]' ); + const expectedEntries = originalEntries + 1; + const actualEntries = await getNumberOfEntries(); + + expect( actualEntries ).toBe( expectedEntries ); } ); } ); diff --git a/packages/e2e-tests/specs/taxonomies.test.js b/packages/e2e-tests/specs/taxonomies.test.js index 087da816f4d6a..ee3759fe296f0 100644 --- a/packages/e2e-tests/specs/taxonomies.test.js +++ b/packages/e2e-tests/specs/taxonomies.test.js @@ -153,7 +153,7 @@ describe( 'Taxonomies', () => { await page.reload(); // Wait for the tags to load. - page.waitForSelector( '.components-form-token-field__token' ); + await page.waitForSelector( '.components-form-token-field__token' ); tags = await getCurrentTags(); From 087c4aa22fe6c537895d25572b449c6971731eb4 Mon Sep 17 00:00:00 2001 From: Garrett Hyder <garrett@eclipse3sixty.com> Date: Mon, 4 Mar 2019 23:55:12 -0800 Subject: [PATCH 556/691] Lowercase classic editor when not in reference to the plugin. (#14203) * Lowercase classic editor when not in reference to the plugin. * Reverted heading to use capitalized as the heading choice throughout uses full capitalization style on headings so went back to make consistent as @torres126 recommended. Also updated the snap file to resolve the Travis CI error. * Missed saving README.md where the revert on headding capitalization was in my editor. --- .../developers/backward-compatibility/meta-box.md | 2 +- .../developers/filters/editor-filters.md | 2 +- .../developers/tutorials/notices/README.md | 6 +++--- docs/designers-developers/faq.md | 2 +- .../__snapshots__/compatibility-classic-editor.test.js.snap | 2 +- .../e2e-tests/specs/compatibility-classic-editor.test.js | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/designers-developers/developers/backward-compatibility/meta-box.md b/docs/designers-developers/developers/backward-compatibility/meta-box.md index affb67fa65a10..e7350dadba7fb 100644 --- a/docs/designers-developers/developers/backward-compatibility/meta-box.md +++ b/docs/designers-developers/developers/backward-compatibility/meta-box.md @@ -30,7 +30,7 @@ add_meta_box( 'my-meta-box', 'My Meta Box', 'my_meta_box_callback', ); ``` -When Gutenberg is used, this meta box will no longer be displayed in the meta box area, as it now only exists for backward compatibility purposes. It will continue to display correctly in the Classic editor. +When Gutenberg is used, this meta box will no longer be displayed in the meta box area, as it now only exists for backward compatibility purposes. It will continue to display correctly in the classic editor. ### Meta Box Data Collection diff --git a/docs/designers-developers/developers/filters/editor-filters.md b/docs/designers-developers/developers/filters/editor-filters.md index 2ddb34f39f957..9421d7cd68801 100644 --- a/docs/designers-developers/developers/filters/editor-filters.md +++ b/docs/designers-developers/developers/filters/editor-filters.md @@ -4,7 +4,7 @@ To modify the behavior of the editor experience, the following Filters are expos ### `editor.PostFeaturedImage.imageSize` -Used to modify the image size displayed in the Post Featured Image component. It defaults to `'post-thumbnail'`, and will fail back to the `full` image size when the specified image size doesn't exist in the media object. It's modeled after the `admin_post_thumbnail_size` filter in the Classic Editor. +Used to modify the image size displayed in the Post Featured Image component. It defaults to `'post-thumbnail'`, and will fail back to the `full` image size when the specified image size doesn't exist in the media object. It's modeled after the `admin_post_thumbnail_size` filter in the classic editor. _Example:_ diff --git a/docs/designers-developers/developers/tutorials/notices/README.md b/docs/designers-developers/developers/tutorials/notices/README.md index cde3670e8bf00..56950343a13ce 100644 --- a/docs/designers-developers/developers/tutorials/notices/README.md +++ b/docs/designers-developers/developers/tutorials/notices/README.md @@ -2,13 +2,13 @@ Notices are informational UI displayed near the top of admin pages. WordPress core, themes, and plugins all use notices to indicate the result of an action, or to draw the user's attention to necessary information. -In the Classic Editor, notices hooked onto the `admin_notices` action can render whatever HTML they'd like. In the block editor, notices are restricted to a more formal API. +In the classic editor, notices hooked onto the `admin_notices` action can render whatever HTML they'd like. In the block editor, notices are restricted to a more formal API. ## Notices in the Classic Editor -In the Classic Editor, here's an example of the "Post draft updated" notice: +In the classic editor, here's an example of the "Post draft updated" notice: -![Post draft updated in the Classic Editor](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/notices/classic-editor-notice.png) +![Post draft updated in the classic editor](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/notices/classic-editor-notice.png) Producing an equivalent "Post draft updated" notice would require code like this: diff --git a/docs/designers-developers/faq.md b/docs/designers-developers/faq.md index eb4b21b2a1a6c..4917ac8f82e3c 100644 --- a/docs/designers-developers/faq.md +++ b/docs/designers-developers/faq.md @@ -322,7 +322,7 @@ We realize it's a big change. We also think there will be many new opportunities There is a “Classic” block, which is virtually the same as the current editor, except in block form. -There is also the [Classic Editor Plugin](https://wordpress.org/plugins/classic-editor/) which restores the previous editor, see the plugin for more information. The WordPress Core team has committed to supporting the Classic Editor Plugin [until December 2021](https://make.wordpress.org/core/2018/11/07/classic-editor-plugin-support-window/). +There is also the [Classic Editor plugin](https://wordpress.org/plugins/classic-editor/) which restores the previous editor, see the plugin for more information. The WordPress Core team has committed to supporting the Classic Editor plugin [until December 2021](https://make.wordpress.org/core/2018/11/07/classic-editor-plugin-support-window/). ## How will custom TinyMCE buttons work in Gutenberg? diff --git a/packages/e2e-tests/specs/__snapshots__/compatibility-classic-editor.test.js.snap b/packages/e2e-tests/specs/__snapshots__/compatibility-classic-editor.test.js.snap index e310008a220d3..3115d36a3b64c 100644 --- a/packages/e2e-tests/specs/__snapshots__/compatibility-classic-editor.test.js.snap +++ b/packages/e2e-tests/specs/__snapshots__/compatibility-classic-editor.test.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Compatibility with Classic Editor Should not apply autop when rendering blocks 1`] = ` +exports[`Compatibility with classic editor Should not apply autop when rendering blocks 1`] = ` "<a> Random Link </a>" diff --git a/packages/e2e-tests/specs/compatibility-classic-editor.test.js b/packages/e2e-tests/specs/compatibility-classic-editor.test.js index 6134361af73fe..9b311275b7296 100644 --- a/packages/e2e-tests/specs/compatibility-classic-editor.test.js +++ b/packages/e2e-tests/specs/compatibility-classic-editor.test.js @@ -3,7 +3,7 @@ */ import { createNewPost, insertBlock, publishPost } from '@wordpress/e2e-test-utils'; -describe( 'Compatibility with Classic Editor', () => { +describe( 'Compatibility with classic editor', () => { beforeEach( async () => { await createNewPost(); } ); From 44c58b8d72b152c9df94c6e976ff3dfe865e0ea8 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Tue, 5 Mar 2019 09:00:26 +0000 Subject: [PATCH 557/691] Fix: Calendar block: Always show current month for non post types on the editor (#13873) --- packages/block-library/src/calendar/edit.js | 11 +++++++++- packages/block-library/src/calendar/index.php | 22 +++++++++++-------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/packages/block-library/src/calendar/edit.js b/packages/block-library/src/calendar/edit.js index 67d89d99bd457..f884490ec38c8 100644 --- a/packages/block-library/src/calendar/edit.js +++ b/packages/block-library/src/calendar/edit.js @@ -61,7 +61,16 @@ class CalendarEdit extends Component { } export default withSelect( ( select ) => { + const { + getEditedPostAttribute, + } = select( 'core/editor' ); + const postType = getEditedPostAttribute( 'type' ); + // Dates are used to overwrite year and month used on the calendar. + // This overwrite should only happen for 'post' post types. + // For other post types the calendar always displays the current month. return { - date: select( 'core/editor' ).getEditedPostAttribute( 'date' ), + date: postType === 'post' ? + getEditedPostAttribute( 'date' ) : + undefined, }; } )( CalendarEdit ); diff --git a/packages/block-library/src/calendar/index.php b/packages/block-library/src/calendar/index.php index 9177997692f40..8e26626f5f861 100644 --- a/packages/block-library/src/calendar/index.php +++ b/packages/block-library/src/calendar/index.php @@ -13,18 +13,22 @@ * @return string Returns the block content. */ function render_block_core_calendar( $attributes ) { - global $monthnum, $year, $post; + global $monthnum, $year; + $previous_monthnum = $monthnum; $previous_year = $year; - if ( isset( $attributes['month'] ) ) { - // phpcs:ignore WordPress.WP.GlobalVariablesOverride.OverrideProhibited - $monthnum = $attributes['month']; - } - - if ( isset( $attributes['year'] ) ) { - // phpcs:ignore WordPress.WP.GlobalVariablesOverride.OverrideProhibited - $year = $attributes['year']; + if ( isset( $attributes['month'] ) && isset( $attributes['year'] ) ) { + $permalink_structure = get_option( 'permalink_structure' ); + if ( + strpos( $permalink_structure, '%monthnum%' ) !== false && + strpos( $permalink_structure, '%year%' ) !== false + ) { + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.OverrideProhibited + $monthnum = $attributes['month']; + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.OverrideProhibited + $year = $attributes['year']; + } } $custom_class_name = empty( $attributes['className'] ) ? '' : ' ' . $attributes['className']; From 357175ff246352d0aa74a6f1140ed8fd0fb8cb5a Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Tue, 5 Mar 2019 10:10:04 +0100 Subject: [PATCH 558/691] Fix allowed_block_types regression (#14229) --- packages/block-editor/src/store/defaults.js | 2 +- packages/e2e-tests/plugins/allowed-blocks.php | 24 +++++++++++++ .../specs/plugins/allowed-blocks.test.js | 36 +++++++++++++++++++ .../editor/src/components/provider/index.js | 2 +- 4 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 packages/e2e-tests/plugins/allowed-blocks.php create mode 100644 packages/e2e-tests/specs/plugins/allowed-blocks.test.js diff --git a/packages/block-editor/src/store/defaults.js b/packages/block-editor/src/store/defaults.js index 3e61e459c53f6..728b500a51d59 100644 --- a/packages/block-editor/src/store/defaults.js +++ b/packages/block-editor/src/store/defaults.js @@ -17,7 +17,7 @@ export const PREFERENCES_DEFAULTS = { * disableCustomFontSizes boolean Whether or not the custom font sizes are disabled * imageSizes Array Available image sizes * maxWidth number Max width to constraint resizing - * blockTypes boolean|Array Allowed block types + * allowedBlockTypes boolean|Array Allowed block types * hasFixedToolbar boolean Whether or not the editor toolbar is fixed * focusMode boolean Whether the focus mode is enabled or not * styles Array Editor Styles diff --git a/packages/e2e-tests/plugins/allowed-blocks.php b/packages/e2e-tests/plugins/allowed-blocks.php new file mode 100644 index 0000000000000..c3d30cb20c9bd --- /dev/null +++ b/packages/e2e-tests/plugins/allowed-blocks.php @@ -0,0 +1,24 @@ +<?php +/** + * Plugin Name: Gutenberg Test Allowed Blocks + * Plugin URI: https://github.com/WordPress/gutenberg + * Author: Gutenberg Team + * + * @package gutenberg-test-allowed-blocks + */ + +/** + * Restrict the allowed blocks in the editor. + * + * @param Array $allowed_block_types An array of strings containing the previously allowed blocks. + * @param WP_Post $post The current post object. + * @return Array An array of strings containing the new allowed blocks after the filter is applied. + */ +function my_plugin_allowed_block_types( $allowed_block_types, $post ) { + if ( 'post' !== $post->post_type ) { + return $allowed_block_types; + } + return array( 'core/paragraph', 'core/image' ); +} + +add_filter( 'allowed_block_types', 'my_plugin_allowed_block_types', 10, 2 ); diff --git a/packages/e2e-tests/specs/plugins/allowed-blocks.test.js b/packages/e2e-tests/specs/plugins/allowed-blocks.test.js new file mode 100644 index 0000000000000..9e86e4ad01990 --- /dev/null +++ b/packages/e2e-tests/specs/plugins/allowed-blocks.test.js @@ -0,0 +1,36 @@ +/** + * WordPress dependencies + */ +import { + activatePlugin, + createNewPost, + deactivatePlugin, + searchForBlock, +} from '@wordpress/e2e-test-utils'; + +describe( 'Allowed Blocks Filter', () => { + beforeAll( async () => { + await activatePlugin( 'gutenberg-test-allowed-blocks' ); + } ); + + beforeEach( async () => { + await createNewPost(); + } ); + + afterAll( async () => { + await deactivatePlugin( 'gutenberg-test-allowed-blocks' ); + } ); + + it( 'should restrict the allowed blocks in the inserter', async () => { + // The paragraph block is available. + await searchForBlock( 'Paragraph' ); + const paragraphBlock = await page.$( `button[aria-label="Paragraph"]` ); + expect( paragraphBlock ).not.toBeNull(); + await paragraphBlock.click(); + + // The gallery block is not available. + await searchForBlock( 'Gallery' ); + const galleryBlock = await page.$( `button[aria-label="Gallery"]` ); + expect( galleryBlock ).toBeNull(); + } ); +} ); diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index 92e13d137ce54..8817eb5d4be3b 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -61,7 +61,7 @@ class EditorProvider extends Component { 'disableCustomFontSizes', 'imageSizes', 'maxWidth', - 'blockTypes', + 'allowedBlockTypes', 'hasFixedToolbar', 'focusMode', 'styles', From ff5360861a98cb8f526b26492568bf3764120930 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Tue, 5 Mar 2019 09:29:21 +0000 Subject: [PATCH 559/691] Fix: Latest posts: Title is clickable across the full width of the row (#14109) ## Description The title in latest posts block was clickable across the full width of the row, making it easy to click on the title by mistake. This problem only affects the editor on the front end things worked as expected. ## How has this been tested? I added the latest posts block. I verified that the title is only clickable if the mouse is above the title and not above any part of its row. ## Screenshots <!-- if applicable --> Before: ![feb-25-2019 21-49-15](https://user-images.githubusercontent.com/11271197/53371643-b3e3f380-3948-11e9-95f5-a8f235fabab2.gif) After: ![feb-25-2019 21-53-30](https://user-images.githubusercontent.com/11271197/53371657-c2320f80-3948-11e9-8dfd-ab7ae4640955.gif) --- packages/block-library/src/latest-posts/editor.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/block-library/src/latest-posts/editor.scss b/packages/block-library/src/latest-posts/editor.scss index a29b74fd4cc3a..3442630f42486 100644 --- a/packages/block-library/src/latest-posts/editor.scss +++ b/packages/block-library/src/latest-posts/editor.scss @@ -4,3 +4,6 @@ padding-left: 0; } } +.wp-block-latest-posts li a > div { + display: inline; +} From 99f294cd8683283af6550f07bca41c94dacf34d4 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Tue, 5 Mar 2019 04:44:46 -0500 Subject: [PATCH 560/691] Blocks: Regenerate RSS, Search block fixtures (#14122) --- packages/e2e-tests/fixtures/blocks/core__rss.html | 2 +- packages/e2e-tests/fixtures/blocks/core__rss.json | 11 ++++++++++- .../e2e-tests/fixtures/blocks/core__rss.parsed.json | 4 ++-- .../fixtures/blocks/core__rss.serialized.html | 2 +- packages/e2e-tests/fixtures/blocks/core__search.json | 6 +++++- .../fixtures/blocks/core__search__custom-text.json | 6 +++++- .../blocks/core__search__custom-text.parsed.json | 4 ++-- .../blocks/core__search__custom-text.serialized.html | 2 +- test/integration/full-content/server-registered.json | 2 +- 9 files changed, 28 insertions(+), 11 deletions(-) diff --git a/packages/e2e-tests/fixtures/blocks/core__rss.html b/packages/e2e-tests/fixtures/blocks/core__rss.html index ce652528301c6..69b41ce041862 100644 --- a/packages/e2e-tests/fixtures/blocks/core__rss.html +++ b/packages/e2e-tests/fixtures/blocks/core__rss.html @@ -1 +1 @@ -<!-- wp:rss {"postLayout":"grid","feedURL":"https://wordpress.org/news/","postsToShow":4,"displayExcerpt":true,"displayAuthor":true,"displayDate":true,"excerptLength":20} /--> +<!-- wp:rss {"blockLayout":"grid","feedURL":"https://wordpress.org/news/","itemsToShow":4,"displayExcerpt":true,"displayAuthor":true,"displayDate":true,"excerptLength":20} /--> diff --git a/packages/e2e-tests/fixtures/blocks/core__rss.json b/packages/e2e-tests/fixtures/blocks/core__rss.json index 89de06cd2d78d..09f43cf2e1e25 100644 --- a/packages/e2e-tests/fixtures/blocks/core__rss.json +++ b/packages/e2e-tests/fixtures/blocks/core__rss.json @@ -3,7 +3,16 @@ "clientId": "_clientId_0", "name": "core/rss", "isValid": true, - "attributes": {}, + "attributes": { + "columns": 2, + "blockLayout": "grid", + "feedURL": "https://wordpress.org/news/", + "itemsToShow": 4, + "displayExcerpt": true, + "displayAuthor": true, + "displayDate": true, + "excerptLength": 20 + }, "innerBlocks": [], "originalContent": "" } diff --git a/packages/e2e-tests/fixtures/blocks/core__rss.parsed.json b/packages/e2e-tests/fixtures/blocks/core__rss.parsed.json index bdc596b09a5a1..e55306214e219 100644 --- a/packages/e2e-tests/fixtures/blocks/core__rss.parsed.json +++ b/packages/e2e-tests/fixtures/blocks/core__rss.parsed.json @@ -2,9 +2,9 @@ { "blockName": "core/rss", "attrs": { - "postLayout": "grid", + "blockLayout": "grid", "feedURL": "https://wordpress.org/news/", - "postsToShow": 4, + "itemsToShow": 4, "displayExcerpt": true, "displayAuthor": true, "displayDate": true, diff --git a/packages/e2e-tests/fixtures/blocks/core__rss.serialized.html b/packages/e2e-tests/fixtures/blocks/core__rss.serialized.html index c6d9be22646e7..69b41ce041862 100644 --- a/packages/e2e-tests/fixtures/blocks/core__rss.serialized.html +++ b/packages/e2e-tests/fixtures/blocks/core__rss.serialized.html @@ -1 +1 @@ -<!-- wp:rss /--> +<!-- wp:rss {"blockLayout":"grid","feedURL":"https://wordpress.org/news/","itemsToShow":4,"displayExcerpt":true,"displayAuthor":true,"displayDate":true,"excerptLength":20} /--> diff --git a/packages/e2e-tests/fixtures/blocks/core__search.json b/packages/e2e-tests/fixtures/blocks/core__search.json index e1397fdc9a37a..dbf8ea090062e 100644 --- a/packages/e2e-tests/fixtures/blocks/core__search.json +++ b/packages/e2e-tests/fixtures/blocks/core__search.json @@ -3,7 +3,11 @@ "clientId": "_clientId_0", "name": "core/search", "isValid": true, - "attributes": {}, + "attributes": { + "label": "Search", + "placeholder": "", + "buttonText": "Search" + }, "innerBlocks": [], "originalContent": "" } diff --git a/packages/e2e-tests/fixtures/blocks/core__search__custom-text.json b/packages/e2e-tests/fixtures/blocks/core__search__custom-text.json index e1397fdc9a37a..fb91161919c83 100644 --- a/packages/e2e-tests/fixtures/blocks/core__search__custom-text.json +++ b/packages/e2e-tests/fixtures/blocks/core__search__custom-text.json @@ -3,7 +3,11 @@ "clientId": "_clientId_0", "name": "core/search", "isValid": true, - "attributes": {}, + "attributes": { + "label": "Custom label", + "placeholder": "Custom placeholder", + "buttonText": "Custom button text" + }, "innerBlocks": [], "originalContent": "" } diff --git a/packages/e2e-tests/fixtures/blocks/core__search__custom-text.parsed.json b/packages/e2e-tests/fixtures/blocks/core__search__custom-text.parsed.json index fd128b35f93c8..6b67b6d21a472 100644 --- a/packages/e2e-tests/fixtures/blocks/core__search__custom-text.parsed.json +++ b/packages/e2e-tests/fixtures/blocks/core__search__custom-text.parsed.json @@ -2,9 +2,9 @@ { "blockName": "core/search", "attrs": { - "buttonText": "Custom button text", "label": "Custom label", - "placeholder": "Custom placeholder" + "placeholder": "Custom placeholder", + "buttonText": "Custom button text" }, "innerBlocks": [], "innerHTML": "", diff --git a/packages/e2e-tests/fixtures/blocks/core__search__custom-text.serialized.html b/packages/e2e-tests/fixtures/blocks/core__search__custom-text.serialized.html index f62e8a853c2ea..bb6e6a56c9a33 100644 --- a/packages/e2e-tests/fixtures/blocks/core__search__custom-text.serialized.html +++ b/packages/e2e-tests/fixtures/blocks/core__search__custom-text.serialized.html @@ -1 +1 @@ -<!-- wp:search /--> +<!-- wp:search {"label":"Custom label","placeholder":"Custom placeholder","buttonText":"Custom button text"} /--> diff --git a/test/integration/full-content/server-registered.json b/test/integration/full-content/server-registered.json index 080865f39bb10..eeae11cb99e9c 100644 --- a/test/integration/full-content/server-registered.json +++ b/test/integration/full-content/server-registered.json @@ -1 +1 @@ -{"core\/block":{"attributes":{"ref":{"type":"number"}}},"core\/latest-comments":{"attributes":{"className":{"type":"string"},"commentsToShow":{"type":"number","default":5,"minimum":1,"maximum":100},"displayAvatar":{"type":"boolean","default":true},"displayDate":{"type":"boolean","default":true},"displayExcerpt":{"type":"boolean","default":true},"align":{"type":"string","enum":["center","left","right","wide","full",""]}}},"core\/archives":{"attributes":{"align":{"type":"string"},"className":{"type":"string"},"displayAsDropdown":{"type":"boolean","default":false},"showPostCounts":{"type":"boolean","default":false}}},"core\/latest-posts":{"attributes":{"categories":{"type":"string"},"className":{"type":"string"},"postsToShow":{"type":"number","default":5},"displayPostDate":{"type":"boolean","default":false},"postLayout":{"type":"string","default":"list"},"columns":{"type":"number","default":3},"align":{"type":"string"},"order":{"type":"string","default":"desc"},"orderBy":{"type":"string","default":"date"}}},"core\/tag-cloud":{"attributes":{"taxonomy":{"type":"string","default":"tags"},"className":{"type":"string"},"showTagCounts":{"type":"boolean","default":false},"align":{"type":"string"}}}} \ No newline at end of file +{"core\/block":{"attributes":{"ref":{"type":"number"}}},"core\/latest-comments":{"attributes":{"className":{"type":"string"},"commentsToShow":{"type":"number","default":5,"minimum":1,"maximum":100},"displayAvatar":{"type":"boolean","default":true},"displayDate":{"type":"boolean","default":true},"displayExcerpt":{"type":"boolean","default":true},"align":{"type":"string","enum":["center","left","right","wide","full",""]}}},"core\/archives":{"attributes":{"align":{"type":"string"},"className":{"type":"string"},"displayAsDropdown":{"type":"boolean","default":false},"showPostCounts":{"type":"boolean","default":false}}},"core\/calendar":{"attributes":{"align":{"type":"string"},"className":{"type":"string"},"month":{"type":"integer"},"year":{"type":"integer"}}},"core\/latest-posts":{"attributes":{"categories":{"type":"string"},"className":{"type":"string"},"postsToShow":{"type":"number","default":5},"displayPostDate":{"type":"boolean","default":false},"postLayout":{"type":"string","default":"list"},"columns":{"type":"number","default":3},"align":{"type":"string"},"order":{"type":"string","default":"desc"},"orderBy":{"type":"string","default":"date"}}},"core\/rss":{"attributes":{"columns":{"type":"number","default":2},"blockLayout":{"type":"string","default":"list"},"feedURL":{"type":"string","default":""},"itemsToShow":{"type":"number","default":5},"displayExcerpt":{"type":"boolean","default":false},"displayAuthor":{"type":"boolean","default":false},"displayDate":{"type":"boolean","default":false},"excerptLength":{"type":"number","default":55}}},"core\/search":{"attributes":{"label":{"type":"string","default":"Search"},"placeholder":{"type":"string","default":""},"buttonText":{"type":"string","default":"Search"}}},"core\/tag-cloud":{"attributes":{"taxonomy":{"type":"string","default":"post_tag"},"className":{"type":"string"},"showTagCounts":{"type":"boolean","default":false},"align":{"type":"string"}}}} \ No newline at end of file From 8c82d3e2a8b6f553c62d38f38705ceac47e420e9 Mon Sep 17 00:00:00 2001 From: Paul Sealock <psealock@gmail.com> Date: Tue, 5 Mar 2019 22:48:20 +1300 Subject: [PATCH 561/691] Component: date-time: Remove comparison to now when testing getMomentDate (#14230) --- packages/components/src/date-time/test/date.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/components/src/date-time/test/date.js b/packages/components/src/date-time/test/date.js index a12999dbbc784..cd1b1e1e12e2b 100644 --- a/packages/components/src/date-time/test/date.js +++ b/packages/components/src/date-time/test/date.js @@ -29,7 +29,6 @@ describe( 'DatePicker', () => { const wrapper = shallow( <DatePicker /> ); const date = wrapper.children().props().date; expect( moment.isMoment( date ) ).toBe( true ); - expect( date.isSame( moment(), 'second' ) ).toBe( true ); } ); describe( 'getMomentDate', () => { @@ -55,7 +54,6 @@ describe( 'DatePicker', () => { const momentDate = wrapper.instance().getMomentDate(); expect( moment.isMoment( momentDate ) ).toBe( true ); - expect( momentDate.isSame( moment(), 'second' ) ).toBe( true ); } ); } ); From 5ebdc02b30839de857f17902a86c81d5761ac9f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <iseulde@automattic.com> Date: Tue, 5 Mar 2019 11:36:36 +0100 Subject: [PATCH 562/691] RichText: Fix undo after pattern (#13917) * Fix undo after pattern * Update e2e test --- .../blocks/__snapshots__/list.test.js.snap | 2 +- .../editor/src/components/rich-text/index.js | 21 ++++++++++++------- .../src/components/rich-text/patterns.js | 5 +---- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap b/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap index efc3b6d274373..119fce765c5f4 100644 --- a/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap +++ b/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap @@ -76,7 +76,7 @@ exports[`List can be created by using an asterisk at the start of a paragraph bl exports[`List can undo asterisk transform 1`] = ` "<!-- wp:paragraph --> -<p>1.</p> +<p>1. </p> <!-- /wp:paragraph -->" `; diff --git a/packages/editor/src/components/rich-text/index.js b/packages/editor/src/components/rich-text/index.js index d6be0a622307e..3ca992296e4fb 100644 --- a/packages/editor/src/components/rich-text/index.js +++ b/packages/editor/src/components/rich-text/index.js @@ -133,9 +133,7 @@ export class RichText extends Component { this.savedContent = value; this.patterns = getPatterns( { onReplace, - onCreateUndoLevel: this.onCreateUndoLevel, valueToFormat: this.valueToFormat, - onChange: this.onChange, } ); this.enterPatterns = getBlockTransforms( 'from' ) .filter( ( { type } ) => type === 'enter' ); @@ -395,10 +393,7 @@ export class RichText extends Component { } let { selectedFormat } = this.state; - const { formats, text, start, end } = this.patterns.reduce( - ( accumlator, transform ) => transform( accumlator ), - this.createRecord() - ); + const { formats, text, start, end } = this.createRecord(); if ( this.formatPlaceholder ) { formats[ this.state.start ] = formats[ this.state.start ] || []; @@ -420,10 +415,22 @@ export class RichText extends Component { delete formats[ this.state.start ]; } - this.onChange( { formats, text, start, end, selectedFormat }, { + const change = { formats, text, start, end, selectedFormat }; + + this.onChange( change, { withoutHistory: true, } ); + const transformed = this.patterns.reduce( + ( accumlator, transform ) => transform( accumlator ), + change + ); + + if ( transformed !== change ) { + this.onCreateUndoLevel(); + this.onChange( { ...transformed, selectedFormat } ); + } + // Create an undo level when input stops for over a second. this.props.clearTimeout( this.onInput.timeout ); this.onInput.timeout = this.props.setTimeout( this.onCreateUndoLevel, 1000 ); diff --git a/packages/editor/src/components/rich-text/patterns.js b/packages/editor/src/components/rich-text/patterns.js index a9e9a8010149d..a1d5794cca828 100644 --- a/packages/editor/src/components/rich-text/patterns.js +++ b/packages/editor/src/components/rich-text/patterns.js @@ -10,7 +10,7 @@ import { slice, } from '@wordpress/rich-text'; -export function getPatterns( { onReplace, valueToFormat, onCreateUndoLevel, onChange } ) { +export function getPatterns( { onReplace, valueToFormat } ) { const prefixTransforms = getBlockTransforms( 'from' ) .filter( ( { type } ) => type === 'prefix' ); @@ -40,7 +40,6 @@ export function getPatterns( { onReplace, valueToFormat, onCreateUndoLevel, onCh const content = valueToFormat( slice( record, start, text.length ) ); const block = transformation.transform( content ); - onCreateUndoLevel(); onReplace( [ block ] ); return record; @@ -70,8 +69,6 @@ export function getPatterns( { onReplace, valueToFormat, onCreateUndoLevel, onCh return record; } - onChange( record ); - record = remove( record, startIndex, startIndex + 1 ); record = remove( record, endIndex, endIndex + 1 ); record = applyFormat( record, { type: 'code' }, startIndex, endIndex ); From 53e86ee99bb1816470291b9e942cd79224eba749 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <iseulde@automattic.com> Date: Tue, 5 Mar 2019 11:40:39 +0100 Subject: [PATCH 563/691] RichText: don't use DOM to add line padding (#13850) * RichText: don't use DOM to add line padding * Clean up * Keep track of user inserted line breaks * Mark isEditableTree param unstable --- .../__snapshots__/writing-flow.test.js.snap | 4 +- .../blocks/__snapshots__/list.test.js.snap | 4 +- .../editor/src/components/rich-text/index.js | 1 + packages/rich-text/src/create.js | 30 +++++--------- packages/rich-text/src/insert-line-break.js | 24 ++--------- .../src/test/__snapshots__/to-dom.js.snap | 37 +++++++++++++---- packages/rich-text/src/test/helpers/index.js | 8 ++-- packages/rich-text/src/to-dom.js | 37 ----------------- packages/rich-text/src/to-tree.js | 40 ++++++++++++++++++- 9 files changed, 91 insertions(+), 94 deletions(-) diff --git a/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap b/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap index 0d02a8e655bfa..dca0ce4763cdb 100644 --- a/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap +++ b/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap @@ -72,7 +72,7 @@ exports[`adding blocks should create valid paragraph blocks when rapidly pressin exports[`adding blocks should insert line break at end 1`] = ` "<!-- wp:paragraph --> -<p>a<br><br></p> +<p>a<br></p> <!-- /wp:paragraph -->" `; @@ -90,7 +90,7 @@ exports[`adding blocks should insert line break at start 1`] = ` exports[`adding blocks should insert line break in empty container 1`] = ` "<!-- wp:paragraph --> -<p><br><br></p> +<p><br></p> <!-- /wp:paragraph -->" `; diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap b/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap index 119fce765c5f4..b2e8ec4875aac 100644 --- a/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap +++ b/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap @@ -146,13 +146,13 @@ exports[`List should indent and outdent level 2 3`] = ` exports[`List should insert a line break on shift+enter 1`] = ` "<!-- wp:list --> -<ul><li>a<br><br></li></ul> +<ul><li>a<br></li></ul> <!-- /wp:list -->" `; exports[`List should insert a line break on shift+enter in a non trailing list item 1`] = ` "<!-- wp:list --> -<ul><li>a</li><li>b<br><br></li><li>c</li></ul> +<ul><li>a</li><li>b<br></li><li>c</li></ul> <!-- /wp:list -->" `; diff --git a/packages/editor/src/components/rich-text/index.js b/packages/editor/src/components/rich-text/index.js index 3ca992296e4fb..e72a70b61e46c 100644 --- a/packages/editor/src/components/rich-text/index.js +++ b/packages/editor/src/components/rich-text/index.js @@ -193,6 +193,7 @@ export class RichText extends Component { multilineTag: this.multilineTag, multilineWrapperTags: this.multilineWrapperTags, prepareEditableTree: this.props.prepareEditableTree, + __unstableIsEditableTree: true, } ); } diff --git a/packages/rich-text/src/create.js b/packages/rich-text/src/create.js index 90d54c82c4561..82348bbd4fa8a 100644 --- a/packages/rich-text/src/create.js +++ b/packages/rich-text/src/create.js @@ -108,6 +108,7 @@ export function create( { range, multilineTag, multilineWrapperTags, + __unstableIsEditableTree: isEditableTree, } = {} ) { if ( typeof text === 'string' && text.length > 0 ) { return { @@ -128,6 +129,7 @@ export function create( { return createFromElement( { element, range, + isEditableTree, } ); } @@ -136,6 +138,7 @@ export function create( { range, multilineTag, multilineWrapperTags, + isEditableTree, } ); } @@ -257,6 +260,7 @@ function createFromElement( { multilineTag, multilineWrapperTags, currentWrapperTags = [], + isEditableTree, } ) { const accumulator = createEmptyValue(); @@ -291,7 +295,10 @@ function createFromElement( { continue; } - if ( node.getAttribute( 'data-rich-text-padding' ) ) { + if ( + node.getAttribute( 'data-rich-text-padding' ) || + ( isEditableTree && type === 'br' && ! node.getAttribute( 'data-rich-text-line-break' ) ) + ) { accumulateSelection( accumulator, node, range, createEmptyValue() ); continue; } @@ -433,7 +440,7 @@ function createFromMultilineElement( { continue; } - let value = createFromElement( { + const value = createFromElement( { element: node, range, multilineTag, @@ -441,23 +448,6 @@ function createFromMultilineElement( { currentWrapperTags, } ); - // If a line consists of one single line break (invisible), consider the - // line empty, wether this is the browser's doing or not. - if ( value.text === '\n' ) { - const start = value.start; - const end = value.end; - - value = createEmptyValue(); - - if ( start !== undefined ) { - value.start = 0; - } - - if ( end !== undefined ) { - value.end = 0; - } - } - // Multiline value text should be separated by a double line break. if ( index !== 0 || currentWrapperTags.length > 0 ) { const formats = currentWrapperTags.length > 0 ? [ currentWrapperTags ] : [ , ]; @@ -495,7 +485,7 @@ function getAttributes( { element } ) { for ( let i = 0; i < length; i++ ) { const { name, value } = element.attributes[ i ]; - if ( name === 'data-rich-text-format-boundary' ) { + if ( name.indexOf( 'data-rich-text-' ) === 0 ) { continue; } diff --git a/packages/rich-text/src/insert-line-break.js b/packages/rich-text/src/insert-line-break.js index 540214d614f3c..08b0b70b864e6 100644 --- a/packages/rich-text/src/insert-line-break.js +++ b/packages/rich-text/src/insert-line-break.js @@ -3,32 +3,14 @@ */ import { insert } from './insert'; -import { LINE_SEPARATOR } from './special-characters'; /** - * Inserts a line break at the given or selected position. Inserts two line - * breaks if at the end of a line. + * Inserts a line break at the given or selected position. * * @param {Object} value Value to modify. * - * @return {Object} The value with the line break(s) inserted. + * @return {Object} The value with the line break inserted. */ export function insertLineBreak( value ) { - const { text, end } = value; - const length = text.length; - - let toInsert = '\n'; - - // If the caret is at the end of the text, and there is no - // trailing line break or no text at all, we have to insert two - // line breaks in order to create a new line visually and place - // the caret there. - if ( - ( end === length || text[ end ] === LINE_SEPARATOR ) && - ( text[ end - 1 ] !== '\n' || length === 0 ) - ) { - toInsert = '\n\n'; - } - - return insert( value, toInsert ); + return insert( value, '\n' ); } diff --git a/packages/rich-text/src/test/__snapshots__/to-dom.js.snap b/packages/rich-text/src/test/__snapshots__/to-dom.js.snap index 78378090137bd..a68cdab1c911e 100644 --- a/packages/rich-text/src/test/__snapshots__/to-dom.js.snap +++ b/packages/rich-text/src/test/__snapshots__/to-dom.js.snap @@ -132,8 +132,13 @@ exports[`recordToDom should filter format boundary attributes 1`] = ` exports[`recordToDom should handle br 1`] = ` <body> - <br /> + <br + data-rich-text-line-break="true" + /> + <br + data-rich-text-padding="true" + /> </body> `; @@ -143,17 +148,24 @@ exports[`recordToDom should handle br with formatting 1`] = ` data-rich-text-format-boundary="true" > - <br /> + <br + data-rich-text-line-break="true" + /> </em> + <br + data-rich-text-padding="true" + /> </body> `; exports[`recordToDom should handle br with text 1`] = ` <body> te - <br /> + <br + data-rich-text-line-break="true" + /> st </body> `; @@ -161,9 +173,13 @@ exports[`recordToDom should handle br with text 1`] = ` exports[`recordToDom should handle double br 1`] = ` <body> a - <br /> + <br + data-rich-text-line-break="true" + /> - <br /> + <br + data-rich-text-line-break="true" + /> b </body> `; @@ -197,12 +213,14 @@ exports[`recordToDom should handle middle empty list value 1`] = ` <br data-rich-text-padding="true" /> + </li> <li> <br data-rich-text-padding="true" /> + </li> <li> @@ -276,6 +294,7 @@ exports[`recordToDom should handle multiline value with empty 1`] = ` exports[`recordToDom should handle nested empty list value 1`] = ` <body> <li> + <br data-rich-text-padding="true" /> @@ -294,9 +313,13 @@ exports[`recordToDom should handle nested empty list value 1`] = ` exports[`recordToDom should handle selection before br 1`] = ` <body> a - <br /> + <br + data-rich-text-line-break="true" + /> - <br /> + <br + data-rich-text-line-break="true" + /> b </body> `; diff --git a/packages/rich-text/src/test/helpers/index.js b/packages/rich-text/src/test/helpers/index.js index 2828b36718ca7..e12757ad03530 100644 --- a/packages/rich-text/src/test/helpers/index.js +++ b/packages/rich-text/src/test/helpers/index.js @@ -459,8 +459,8 @@ export const spec = [ endOffset: 0, endContainer: element.querySelector( 'ul > li' ), } ), - startPath: [ 0, 0, 0, 0, 0 ], - endPath: [ 0, 0, 0, 0, 0 ], + startPath: [ 0, 2, 0, 0, 0 ], + endPath: [ 0, 2, 0, 0, 0 ], record: { start: 1, end: 1, @@ -479,8 +479,8 @@ export const spec = [ endOffset: 0, endContainer: element.firstChild.nextSibling, } ), - startPath: [ 1, 0, 0 ], - endPath: [ 1, 0, 0 ], + startPath: [ 1, 2, 0 ], + endPath: [ 1, 2, 0 ], record: { start: 1, end: 1, diff --git a/packages/rich-text/src/to-dom.js b/packages/rich-text/src/to-dom.js index 222d06ad80074..987849172bf5f 100644 --- a/packages/rich-text/src/to-dom.js +++ b/packages/rich-text/src/to-dom.js @@ -113,39 +113,6 @@ function remove( node ) { return node.parentNode.removeChild( node ); } -function createLinePadding( doc ) { - const element = doc.createElement( 'br' ); - element.setAttribute( 'data-rich-text-padding', 'true' ); - return element; -} - -function padEmptyLines( { element, multilineWrapperTags } ) { - const length = element.childNodes.length; - const doc = element.ownerDocument; - - for ( let index = 0; index < length; index++ ) { - const child = element.childNodes[ index ]; - - if ( child.nodeType === TEXT_NODE ) { - if ( length === 1 && ! child.nodeValue ) { - // Pad if the only child is an empty text node. - element.appendChild( createLinePadding( doc ) ); - } - } else { - if ( - multilineWrapperTags && - ! child.previousSibling && - multilineWrapperTags.indexOf( child.nodeName.toLowerCase() ) !== -1 - ) { - // Pad the line if there is no content before a nested wrapper. - element.insertBefore( createLinePadding( doc ), child ); - } - - padEmptyLines( { element: child, multilineWrapperTags } ); - } - } -} - function prepareFormats( prepareEditableTree = [], value ) { return prepareEditableTree.reduce( ( accumlator, fn ) => { return fn( accumlator, value.text ); @@ -186,10 +153,6 @@ export function toDom( { isEditableTree, } ); - if ( isEditableTree ) { - padEmptyLines( { element: tree, multilineWrapperTags } ); - } - return { body: tree, selection: { startPath, endPath }, diff --git a/packages/rich-text/src/to-tree.js b/packages/rich-text/src/to-tree.js index 9ce962d540d49..c89bdc94a2d06 100644 --- a/packages/rich-text/src/to-tree.js +++ b/packages/rich-text/src/to-tree.js @@ -80,6 +80,14 @@ function getDeepestActiveFormat( value ) { return activeFormats[ selectedFormat - 1 ]; } +const padding = { + type: 'br', + attributes: { + 'data-rich-text-padding': 'true', + }, + object: true, +}; + export function toTree( { value, multilineTag, @@ -116,6 +124,15 @@ export function toTree( { for ( let i = 0; i < formatsLength; i++ ) { const character = text.charAt( i ); + const shouldInsertPadding = isEditableTree && ( + // Pad the line if the line is empty. + ! lastCharacter || + lastCharacter === LINE_SEPARATOR || + // Pad the line if the previous character is a line break, otherwise + // the line break won't be visible. + lastCharacter === '\n' + ); + let characterFormats = formats[ i ]; // Set multiline tags in queue for building the tree. @@ -136,6 +153,17 @@ export function toTree( { let pointer = getLastChild( tree ); + if ( shouldInsertPadding && character === LINE_SEPARATOR ) { + let node = pointer; + + while ( ! isText( node ) ) { + node = getLastChild( node ); + } + + append( getParent( node ), padding ); + append( getParent( node ), '' ); + } + // Set selection for the start of line. if ( lastCharacter === LINE_SEPARATOR ) { let node = pointer; @@ -214,7 +242,13 @@ export function toTree( { if ( character !== OBJECT_REPLACEMENT_CHARACTER ) { if ( character === '\n' ) { - pointer = append( getParent( pointer ), { type: 'br', object: true } ); + pointer = append( getParent( pointer ), { + type: 'br', + attributes: isEditableTree ? { + 'data-rich-text-line-break': 'true', + } : undefined, + object: true, + } ); // Ensure pointer is text node. pointer = append( getParent( pointer ), '' ); } else if ( ! isText( pointer ) ) { @@ -232,6 +266,10 @@ export function toTree( { onEndIndex( tree, pointer ); } + if ( shouldInsertPadding && i === text.length ) { + append( getParent( pointer ), padding ); + } + lastCharacterFormats = characterFormats; lastCharacter = character; } From 29b3dc5f26bef7b991ed0556ed6bfcc1be754396 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Tue, 5 Mar 2019 12:05:37 +0100 Subject: [PATCH 564/691] Use a central script instead of one per package to generate docs (#14216) --- bin/update-readmes.js | 29 ++++++++++++++ package.json | 8 ++-- packages/docgen/bin/cli.js | 2 +- packages/docgen/src/index.js | 26 +++++++------ packages/docgen/src/markdown/formatter.js | 2 +- .../docgen/src/test/formatter-markdown.js | 2 +- packages/e2e-test-utils/README.md | 38 +++++++++---------- packages/e2e-test-utils/package.json | 6 --- 8 files changed, 70 insertions(+), 43 deletions(-) create mode 100755 bin/update-readmes.js diff --git a/bin/update-readmes.js b/bin/update-readmes.js new file mode 100755 index 0000000000000..c094bc57a5b2b --- /dev/null +++ b/bin/update-readmes.js @@ -0,0 +1,29 @@ +#!/usr/bin/env node + +const path = require( 'path' ); +const childProcess = require( 'child_process' ); + +const packages = [ + 'e2e-test-utils', +]; + +let aggregatedExitCode = 0; +packages.forEach( ( packageName ) => { + const args = [ + `packages/${ packageName }/src/index.js`, + `--output packages/${ packageName }/README.md`, + '--to-token', + ]; + const pathToDocGen = path.join( __dirname, '..', 'node_modules', '.bin', 'docgen' ); + const { status, stderr } = childProcess.spawnSync( + pathToDocGen, + args, + { shell: true }, + ); + if ( status !== 0 ) { + aggregatedExitCode = status; + process.stderr.write( `${ stderr }\n` ); + } +} ); + +process.exit( aggregatedExitCode ); diff --git a/package.json b/package.json index fdd2122723c81..0f3b37a094e24 100644 --- a/package.json +++ b/package.json @@ -162,8 +162,7 @@ "predev": "npm run check-engines", "dev": "npm run build:packages && concurrently \"wp-scripts start\" \"npm run dev:packages\"", "dev:packages": "node ./bin/packages/watch.js", - "docs:build": "node docs/tool", - "docs:generate": "lerna run docs:generate", + "docs:build": "node ./docs/tool && node ./bin/update-readmes", "fixtures:clean": "rimraf \"packages/e2e-tests/fixtures/blocks/*.+(json|serialized.html)\"", "fixtures:server-registered": "docker-compose run -w /var/www/html/wp-content/plugins/gutenberg --rm wordpress ./bin/get-server-blocks.php > test/integration/full-content/server-registered.json", "fixtures:generate": "npm run fixtures:server-registered && cross-env GENERATE_MISSING_FIXTURES=y npm run test-unit", @@ -204,7 +203,10 @@ "wp-scripts lint-js" ], "{docs/{toc.json,tool/*.js},packages/{*/README.md,*/src/{actions,selectors}.js,components/src/*/**/README.md}}": [ - "npm run docs:build" + "node ./docs/tool" + ], + "packages/**/*.js": [ + "node ./bin/update-readmes" ] } } diff --git a/packages/docgen/bin/cli.js b/packages/docgen/bin/cli.js index bcc1a6f1e245d..e5e67d5d89f3c 100755 --- a/packages/docgen/bin/cli.js +++ b/packages/docgen/bin/cli.js @@ -41,4 +41,4 @@ const optionator = require( 'optionator' )( { } ); const options = optionator.parseArgv( process.argv ); -docgen( options._[ 0 ], options ); +process.exit( docgen( options._[ 0 ], options ) ); diff --git a/packages/docgen/src/index.js b/packages/docgen/src/index.js index 1bc2e71a37897..0fef8a2563a59 100644 --- a/packages/docgen/src/index.js +++ b/packages/docgen/src/index.js @@ -28,11 +28,11 @@ const relativeToAbsolute = ( basePath, relativePath ) => { if ( fs.existsSync( targetFile ) ) { return targetFile; } - process.stdout.write( '\nRelative path does not exists.' ); - process.stdout.write( '\n' ); - process.stdout.write( `\nBase: ${ basePath }` ); - process.stdout.write( `\nRelative: ${ relativePath }` ); - process.stdout.write( '\n\n' ); + process.stderr.write( '\nRelative path does not exists.' ); + process.stderr.write( '\n' ); + process.stderr.write( `\nBase: ${ basePath }` ); + process.stderr.write( `\nRelative: ${ relativePath }` ); + process.stderr.write( '\n\n' ); process.exit( 1 ); }; @@ -54,8 +54,8 @@ const processFile = ( rootDir, inputFile ) => { currentFileStack.pop( inputFile ); return result; } catch ( e ) { - process.stdout.write( `\n${ e }` ); - process.stdout.write( '\n\n' ); + process.stderr.write( `\n${ e }` ); + process.stderr.write( '\n\n' ); process.exit( 1 ); } }; @@ -66,8 +66,8 @@ const runCustomFormatter = ( customFormatterFile, rootDir, doc, symbols, heading const output = customFormatter( rootDir, doc, symbols, headingTitle ); fs.writeFileSync( doc, output ); } catch ( e ) { - process.stdout.write( `\n${ e }` ); - process.stdout.write( '\n\n' ); + process.stderr.write( `\n${ e }` ); + process.stderr.write( '\n\n' ); process.exit( 1 ); } return 'custom formatter'; @@ -80,9 +80,9 @@ module.exports = function( sourceFile, options ) { // Input: process CLI args, prepare files, etc const processDir = process.cwd(); if ( sourceFile === undefined ) { - process.stdout.write( '\n' ); - process.stdout.write( 'No source file provided' ); - process.stdout.write( '\n\n' ); + process.stderr.write( '\n' ); + process.stderr.write( 'No source file provided' ); + process.stderr.write( '\n\n' ); process.exit( 1 ); } sourceFile = path.join( processDir, sourceFile ); @@ -123,4 +123,6 @@ module.exports = function( sourceFile, options ) { fs.writeFileSync( tokens, JSON.stringify( result.tokens ) ); fs.writeFileSync( ast, JSON.stringify( result.ast ) ); } + + process.exit( 0 ); }; diff --git a/packages/docgen/src/markdown/formatter.js b/packages/docgen/src/markdown/formatter.js index d9e41536f2f9a..84b09a1b441f5 100644 --- a/packages/docgen/src/markdown/formatter.js +++ b/packages/docgen/src/markdown/formatter.js @@ -113,7 +113,7 @@ module.exports = function( rootDir, docPath, symbols, headingTitle, headingStart formatTag( 'Returns', getTagsByName( symbol.tags, 'return' ), - ( tag ) => `\n\`${ tag.type }\` ${ cleanSpaces( tag.description ) }`, + ( tag ) => `\n\`${ tag.type }\`: ${ cleanSpaces( tag.description ) }`, docs ); docs.push( '\n' ); diff --git a/packages/docgen/src/test/formatter-markdown.js b/packages/docgen/src/test/formatter-markdown.js index cd7e66a12e267..953768eb5c604 100644 --- a/packages/docgen/src/test/formatter-markdown.js +++ b/packages/docgen/src/test/formatter-markdown.js @@ -28,7 +28,7 @@ describe( 'Formatter', () => { lineEnd: 2, } ], 'API docs' ); expect( docs ).toBe( - '# API docs\n\n## myDeclaration\n\n[home/my-path/docs-code.js#L1-L2](home/my-path/docs-code.js#L1-L2)\n\nMy declaration example.\n\n**Parameters**\n\n- **firstParam** `number`: First declaration parameter.\n\n**Returns**\n\n`number` The result of the declaration.\n' + '# API docs\n\n## myDeclaration\n\n[home/my-path/docs-code.js#L1-L2](home/my-path/docs-code.js#L1-L2)\n\nMy declaration example.\n\n**Parameters**\n\n- **firstParam** `number`: First declaration parameter.\n\n**Returns**\n\n`number`: The result of the declaration.\n' ); } ); } ); diff --git a/packages/e2e-test-utils/README.md b/packages/e2e-test-utils/README.md index 4958201be7297..845b6725cf82f 100644 --- a/packages/e2e-test-utils/README.md +++ b/packages/e2e-test-utils/README.md @@ -32,7 +32,7 @@ Verifies if publish checks are enabled. **Returns** -`boolean` Boolean which represents the state of prepublish checks. +`boolean`: Boolean which represents the state of prepublish checks. ### clearLocalStorage @@ -88,7 +88,7 @@ Creates a function to determine if a request is embedding a certain URL. **Returns** -`function` Function that determines if a request is for the embed API, embedding a specific URL. +`function`: Function that determines if a request is for the embed API, embedding a specific URL. ### createJSONResponse @@ -102,7 +102,7 @@ Respond to a request with a JSON response. **Returns** -`Promise` Promise that responds to a request with the mock JSON response. +`Promise`: Promise that responds to a request with the mock JSON response. ### createNewPost @@ -127,7 +127,7 @@ Creates new URL by parsing base URL, WPPath and query string. **Returns** -`string` String which represents full URL. +`string`: String which represents full URL. ### createURLMatcher @@ -141,7 +141,7 @@ Creates a function to determine if a request is calling a URL with the substring **Returns** -`function` Function that determines if a request's URL contains substring. +`function`: Function that determines if a request's URL contains substring. ### deactivatePlugin @@ -180,7 +180,7 @@ Verifies that the edit post sidebar is opened, and if it is not, opens it. **Returns** -`Promise` Promise resolving once the edit post sidebar is opened. +`Promise`: Promise resolving once the edit post sidebar is opened. ### findSidebarPanelToggleButtonWithTitle @@ -194,7 +194,7 @@ Finds a sidebar panel with the provided title. **Returns** -`?ElementHandle` Object that represents an in-page DOM element. +`?ElementHandle`: Object that represents an in-page DOM element. ### findSidebarPanelWithTitle @@ -208,7 +208,7 @@ Finds the button responsible for toggling the sidebar panel with the provided ti **Returns** -`?ElementHandle` Object that represents an in-page DOM element. +`?ElementHandle`: Object that represents an in-page DOM element. ### getAllBlocks @@ -218,7 +218,7 @@ Returns an array with all blocks; Equivalent to calling wp.data.select( 'core/ed **Returns** -`Promise` Promise resolving with an array containing all blocks in the document. +`Promise`: Promise resolving with an array containing all blocks in the document. ### getAvailableBlockTransforms @@ -229,7 +229,7 @@ that the current selected block can be transformed into. **Returns** -`Promise` Promise resolving with an array containing all possible block transforms +`Promise`: Promise resolving with an array containing all possible block transforms ### getEditedPostContent @@ -239,7 +239,7 @@ Returns a promise which resolves with the edited post content (HTML string). **Returns** -`Promise` Promise resolving with post content markup. +`Promise`: Promise resolving with post content markup. ### hasBlockSwitcher @@ -249,7 +249,7 @@ Returns a boolean indicating if the current selected block has a block switcher **Returns** -`Promise` Promise resolving with a boolean. +`Promise`: Promise resolving with a boolean. ### insertBlock @@ -287,7 +287,7 @@ Checks if current URL is a WordPress path. **Returns** -`boolean` Boolean represents whether current URL is or not a WordPress path. +`boolean`: Boolean represents whether current URL is or not a WordPress path. ### loginUser @@ -315,7 +315,7 @@ deserialised JSON response for the request. **Returns** -`Promise` Promise that uses `mockCheck` to see if a request should be mocked with `mock`, and optionally transforms the response with `responseObjectTransform`. +`Promise`: Promise that uses `mockCheck` to see if a request should be mocked with `mock`, and optionally transforms the response with `responseObjectTransform`. ### observeFocusLoss @@ -349,7 +349,7 @@ Presses the given keyboard key a number of times in sequence. **Returns** -`Promise` Promise resolving when key presses complete. +`Promise`: Promise resolving when key presses complete. ### pressKeyWithModifier @@ -372,7 +372,7 @@ is displayed). **Returns** -`Promise` Promise resolving when publish is complete. +`Promise`: Promise resolving when publish is complete. ### publishPostWithPrePublishChecksDisabled @@ -383,7 +383,7 @@ resolving once the request is complete (once a notice is displayed). **Returns** -`Promise` Promise resolving when publish is complete. +`Promise`: Promise resolving when publish is complete. ### saveDraft @@ -394,7 +394,7 @@ Saves the post as a draft, resolving once the request is complete (once the **Returns** -`Promise` Promise resolving when draft save is complete. +`Promise`: Promise resolving when draft save is complete. ### searchForBlock @@ -438,7 +438,7 @@ Sets code editor content **Returns** -`Promise` Promise resolving with an array containing all blocks in the document. +`Promise`: Promise resolving with an array containing all blocks in the document. ### setUpResponseMocking diff --git a/packages/e2e-test-utils/package.json b/packages/e2e-test-utils/package.json index 0a6871bff1d76..f31d67677475e 100644 --- a/packages/e2e-test-utils/package.json +++ b/packages/e2e-test-utils/package.json @@ -33,17 +33,11 @@ "lodash": "^4.17.11", "node-fetch": "^1.7.3" }, - "devDependencies": { - "@wordpress/docgen": "file:../docgen" - }, "peerDependencies": { "jest": ">=24", "puppeteer": ">=1.6" }, "publishConfig": { "access": "public" - }, - "scripts": { - "docs:generate": "docgen ./src/index.js --output ./README.md --to-token" } } From 7eb0853ecdec57e28568e51e527122a02e1a8aa5 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Tue, 5 Mar 2019 21:23:01 +0000 Subject: [PATCH 565/691] Update: Use escape key press instead of mouse movement to show block toolbar (#14247) This PR updates our end to end tests to use escape key press instead of mouse movement to show the block toolbar. The PR follows a suggestion by @aduth in https://github.com/WordPress/gutenberg/pull/14191#discussion_r261715507 ## How has this been tested? We only need to make sure end 2 end tests pass --- packages/e2e-tests/specs/blocks/list.test.js | 12 ++++++------ packages/e2e-tests/specs/invalid-block.test.js | 3 ++- packages/e2e-tests/specs/links.test.js | 16 ++++++---------- .../e2e-tests/specs/plugins/format-api.test.js | 6 ++++-- packages/e2e-tests/specs/reusable-blocks.test.js | 14 ++++---------- packages/e2e-tests/specs/style-variation.test.js | 5 ++--- 6 files changed, 24 insertions(+), 32 deletions(-) diff --git a/packages/e2e-tests/specs/blocks/list.test.js b/packages/e2e-tests/specs/blocks/list.test.js index ca56e691a1050..8432ff5d912d6 100644 --- a/packages/e2e-tests/specs/blocks/list.test.js +++ b/packages/e2e-tests/specs/blocks/list.test.js @@ -126,8 +126,8 @@ describe( 'List', () => { await page.keyboard.press( 'Enter' ); // Pointer device is needed. Shift+Tab won't focus the toolbar. // To do: fix so Shift+Tab works. - await page.mouse.move( 200, 300, { steps: 10 } ); - await page.mouse.move( 250, 350, { steps: 10 } ); + // Press escape to show the block toolbar + await page.keyboard.press( 'Escape' ); await page.click( 'button[aria-label="Indent list item"]' ); await page.keyboard.type( 'two' ); await transformBlockTo( 'Paragraph' ); @@ -200,8 +200,8 @@ describe( 'List', () => { await page.keyboard.press( 'Enter' ); // Pointer device is needed. Shift+Tab won't focus the toolbar. // To do: fix so Shift+Tab works. - await page.mouse.move( 200, 300, { steps: 10 } ); - await page.mouse.move( 250, 350, { steps: 10 } ); + // Press escape to show the block toolbar + await page.keyboard.press( 'Escape' ); await page.click( 'button[aria-label="Indent list item"]' ); await page.keyboard.type( 'two' ); await page.keyboard.press( 'Enter' ); @@ -235,8 +235,8 @@ describe( 'List', () => { // Pointer device is needed. Shift+Tab won't focus the toolbar. // To do: fix so Shift+Tab works. - await page.mouse.move( 200, 300, { steps: 10 } ); - await page.mouse.move( 250, 350, { steps: 10 } ); + // Press escape to show the block toolbar + await page.keyboard.press( 'Escape' ); await page.click( 'button[aria-label="Convert to ordered list"]' ); diff --git a/packages/e2e-tests/specs/invalid-block.test.js b/packages/e2e-tests/specs/invalid-block.test.js index a068781ef42ac..803fed87b6446 100644 --- a/packages/e2e-tests/specs/invalid-block.test.js +++ b/packages/e2e-tests/specs/invalid-block.test.js @@ -16,8 +16,9 @@ describe( 'invalid blocks', () => { await clickBlockAppender(); await page.keyboard.type( 'hello' ); + // Press escape to show the block toolbar + await page.keyboard.press( 'Escape' ); // Click the 'more options' - await page.mouse.move( 200, 300, { steps: 10 } ); await page.click( 'button[aria-label="More options"]' ); // Change to HTML mode and close the options diff --git a/packages/e2e-tests/specs/links.test.js b/packages/e2e-tests/specs/links.test.js index 0899494cef66f..39b6eca2f5810 100644 --- a/packages/e2e-tests/specs/links.test.js +++ b/packages/e2e-tests/specs/links.test.js @@ -24,11 +24,6 @@ describe( 'Links', () => { await page.waitForFunction( () => !! document.activeElement.closest( '.editor-url-input' ) ); }; - const moveMouse = async () => { - await page.mouse.move( 200, 300, { steps: 10 } ); - await page.mouse.move( 250, 350, { steps: 10 } ); - }; - it( 'can be created by selecting text and clicking Link', async () => { // Create a block with some text await clickBlockAppender(); @@ -82,8 +77,8 @@ describe( 'Links', () => { await clickBlockAppender(); await page.keyboard.type( 'This is Gutenberg: ' ); - // Trigger isTyping = false - await moveMouse(); + // Press escape to show the block toolbar + await page.keyboard.press( 'Escape' ); // Press Cmd+K to insert a link await pressKeyWithModifier( 'primary', 'K' ); @@ -224,8 +219,8 @@ describe( 'Links', () => { await clickBlockAppender(); await page.keyboard.type( 'Text' ); - // we need to trigger isTyping = false - await moveMouse(); + // Press escape to show the block toolbar + await page.keyboard.press( 'Escape' ); await page.waitForSelector( 'button[aria-label="Link"]' ); await page.click( 'button[aria-label="Link"]' ); @@ -245,7 +240,8 @@ describe( 'Links', () => { // Make a collapsed selection inside the link await page.keyboard.press( 'ArrowLeft' ); await page.keyboard.press( 'ArrowRight' ); - await moveMouse(); + // Press escape to show the block toolbar + await page.keyboard.press( 'Escape' ); await page.click( 'button[aria-label="Edit"]' ); await waitForAutoFocus(); await page.keyboard.type( '/handbook' ); diff --git a/packages/e2e-tests/specs/plugins/format-api.test.js b/packages/e2e-tests/specs/plugins/format-api.test.js index c8d22a60f356d..203714863c083 100644 --- a/packages/e2e-tests/specs/plugins/format-api.test.js +++ b/packages/e2e-tests/specs/plugins/format-api.test.js @@ -26,7 +26,8 @@ describe( 'Using Format API', () => { it( 'Format toolbar is present in a paragraph block', async () => { await clickBlockAppender(); await page.keyboard.type( 'First paragraph' ); - await page.mouse.move( 200, 300, { steps: 10 } ); + // Press escape to show the block toolbar + await page.keyboard.press( 'Escape' ); expect( await page.$( '[aria-label="Custom Link"]' ) ).not.toBeNull(); } ); @@ -35,7 +36,8 @@ describe( 'Using Format API', () => { await page.keyboard.type( 'First paragraph' ); await pressKeyWithModifier( 'shiftAlt', 'ArrowLeft' ); await pressKeyWithModifier( 'primary', 'A' ); - await page.mouse.move( 200, 300, { steps: 10 } ); + // Press escape to show the block toolbar + await page.keyboard.press( 'Escape' ); await page.click( '[aria-label="Custom Link"]' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); diff --git a/packages/e2e-tests/specs/reusable-blocks.test.js b/packages/e2e-tests/specs/reusable-blocks.test.js index f1ef297f311ba..a444a61cfbb58 100644 --- a/packages/e2e-tests/specs/reusable-blocks.test.js +++ b/packages/e2e-tests/specs/reusable-blocks.test.js @@ -34,9 +34,8 @@ describe( 'Reusable Blocks', () => { await insertBlock( 'Paragraph' ); await page.keyboard.type( 'Hello there!' ); - // Trigger isTyping = false - await page.mouse.move( 200, 300, { steps: 10 } ); - await page.mouse.move( 250, 350, { steps: 10 } ); + // Press escape to show the block toolbar + await page.keyboard.press( 'Escape' ); // Convert block to a reusable block await page.waitForSelector( 'button[aria-label="More options"]' ); @@ -81,9 +80,8 @@ describe( 'Reusable Blocks', () => { await insertBlock( 'Paragraph' ); await page.keyboard.type( 'Hello there!' ); - // Trigger isTyping = false - await page.mouse.move( 200, 300, { steps: 10 } ); - await page.mouse.move( 250, 350, { steps: 10 } ); + // Press escape to show the block toolbar + await page.keyboard.press( 'Escape' ); // Convert block to a reusable block await page.waitForSelector( 'button[aria-label="More options"]' ); @@ -222,10 +220,6 @@ describe( 'Reusable Blocks', () => { await pressKeyWithModifier( 'primary', 'a' ); await pressKeyWithModifier( 'primary', 'a' ); - // Trigger isTyping = false - await page.mouse.move( 200, 300, { steps: 10 } ); - await page.mouse.move( 250, 350, { steps: 10 } ); - // Convert block to a reusable block await page.waitForSelector( 'button[aria-label="More options"]' ); await page.click( 'button[aria-label="More options"]' ); diff --git a/packages/e2e-tests/specs/style-variation.test.js b/packages/e2e-tests/specs/style-variation.test.js index 0d1c2e758b042..2ec32efe89141 100644 --- a/packages/e2e-tests/specs/style-variation.test.js +++ b/packages/e2e-tests/specs/style-variation.test.js @@ -13,9 +13,8 @@ describe( 'adding blocks', () => { await insertBlock( 'Quote' ); await page.keyboard.type( 'Quote content' ); - // we need to trigger isTyping = false - await page.mouse.move( 200, 300, { steps: 10 } ); - await page.mouse.move( 250, 350, { steps: 10 } ); + // Press escape to show the block toolbar + await page.keyboard.press( 'Escape' ); // Use a different style variation await page.waitForSelector( 'button[aria-label="Change block type"]' ); From ecc71fc92bdfa115329f0ea661a95ca58f7aec38 Mon Sep 17 00:00:00 2001 From: Gary Pendergast <gary@pento.net> Date: Wed, 6 Mar 2019 15:04:59 +1100 Subject: [PATCH 566/691] Switch the Travis badge from travis-ci.org to travis-ci.com. (#14250) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4878bf03990a2..861c13ff675a0 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Gutenberg -[![Build Status](https://img.shields.io/travis/WordPress/gutenberg/master.svg)](https://travis-ci.org/WordPress/gutenberg) +[![Build Status](https://img.shields.io/travis/com/WordPress/gutenberg/master.svg)](https://travis-ci.com/WordPress/gutenberg) [![lerna](https://img.shields.io/badge/maintained%20with-lerna-cc00ff.svg)](https://lernajs.io/) ![Screenshot of the Gutenberg Editor, editing a post in WordPress](https://cldup.com/H0oKBfpidk.png) From fc25ff5672574268e47993b5419d678021453d73 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Wed, 6 Mar 2019 11:01:37 +0100 Subject: [PATCH 567/691] Update the npm packages release process (#14136) --- docs/contributors/release.md | 52 ++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/docs/contributors/release.md b/docs/contributors/release.md index 2321e63b70b42..fb31bbb4d6a25 100644 --- a/docs/contributors/release.md +++ b/docs/contributors/release.md @@ -186,25 +186,55 @@ If you don't have access to [make.wordpress.org/core](https://make.wordpress.org ## Packages Releases and WordPress Core Updates -WordPress Core Updates are based on the `g-minor` branch. Releasing packages in order to update WordPress Core involves updating the `g-minor` branch (the workflow depends on whether it's a minor or major WordPress release) and run the package release process. +The Gutenberg repository mirrors the [WordPress SVN repository](https://make.wordpress.org/core/handbook/about/release-cycle/) in terms of branching for each SVN branch, a corresponding Gutenberg `wp/*` branch is created: -### Major WordPress Releases + - The `wp/trunk` branch contains all the packages that are published and used in the `trunk` branch of WordPress. + - A Gutenberg branch targetting a specific WordPress major release (including its further minor increments) is created (example `wp/5.2`) based on the `wp/trunk` Gutenberg branch when the WordPress `trunk` branch is marked as "feature-freezed". (This usually happens when the first `beta` of the next WordPress major version is released). -For major WordPress releases, the last Gutenberg plugin release is merged into `g-minor`. This involves the following steps: +### Synchronizing WordPress Trunk + +For each Gutenberg plugin release, WordPress trunk should be synchronized with this release. This involves the following steps: + +**Note:** The WordPress `trunk` branch can be closed or in "feature-freeze" mode. Usually, this happens between the first `beta` and the first `RC` of the WordPress release cycle. During this period, the Gutenberg plugin releases should not be synchronized with WordPress Core. + +1. Ensure the WordPress `trunk` branch is open for enhancements. +2. Check out the last published Gutenberg release branch `git checkout release/x.x` +3. Create a Pull Request from this branch targetting `wp/trunk`. +4. Merge the Pull Request. + +Now, the branch is ready to be used to publish the npm packages. + +1. Check out the `wp/trunk` branch. +2. Run the [package release process] but when asked for the version numbers to choose for each package, (assuming the package versions are written using this format `major.minor.patch`) make sure to bump at least the `minor` version number. For example, if the CHANGELOG of the package to be released indicates that the next unreleased version is `5.6.1`, choose `5.7.0` as a version. +3. Update the `CHANGELOG.md` files of the published packages with the new released versions and commit to the `wp/trunk` branch. +4. Cherry-pick the "Publish" (created by Lerna) and the CHANGELOG update commits into the `master` branch of Gutenberg. + +Now, the npm packages should be ready and a patch can be created and commited into WordPress `trunk`. -1. Check out the last published Gutenberg release branch `git checkout release/x.x` -2. Create a Pull Request from this branch into `g-minor`. -3. Merge the branch. ### Minor WordPress Releases -For minor releases, the critical fixes targeted for this WordPress Minor release should be cherry-picked into the `g-minor` branch one by one in their chronological order. +The following workflow is needed when bug fixes or security releases need to be backported into WordPress Core. This can happen in a few use-cases: + + - During the `beta` and the `RC` period of the WordPress release cycle. + - For WordPress minor releases and WordPress security releases (example `5.1.1`). + +1. Cherry-pick +2. Check out the last published Gutenberg release branch `git checkout release/x.x` +3. Create a Pull Request from this branch targetting the WordPress related major branch (Example `wp/5.2`). +4. Merge the Pull Request. + +Now, the branch is ready to be used to publish the npm packages. + +1. Check out the WordPress branch used before (Example `wp/5.2`). +2. Run the [package release process] but when asked for the version numbers to choose for each package, (assuming the package versions are written using this format `major.minor.patch`) make sure to bump only the `patch` version number. For example, if the last published package version for this WordPress branch was `5.6.0`, choose `5.6.1` as a version. + +**Note:** For WordPress `5.0` and WordPress `5.1`, a different release process was used. This means that when choosing npm package versions targetting these two releases, you won't be able to use the next `patch` version number as it may have been already used. You should use the "metadata" modifier for these. For example, if the last published package version for this WordPress branch was `5.6.1`, choose `5.6.1+patch.1` as a version. -### Releasing the WordPress packages +3. Update the `CHANGELOG.md` files of the published packages with the new released versions and commit to the corresponding branch (Example `wp/5.2`). +4. Cherry-pick the CHANGELOG update commits into the `master` branch of Gutenberg. -1. Check out the `g-minor` branch. -2. Run the [package release process]. -3. Update the `CHANGELOG.md` files of the published packages with the new released versions and commit to the `g-minor` branch. +Now, the npm packages should be ready and a patch can be created and commited into the corresponding WordPress SVN branch. --------- From bed758f55c0757422af191f09c5beb3ac2607f9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <iseulde@automattic.com> Date: Wed, 6 Mar 2019 11:12:41 +0100 Subject: [PATCH 568/691] Update plugin version to 5.2 (#14255) --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index feb70cd894c8b..6253c9a3bf0e4 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,7 +3,7 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the new block editor in core. - * Version: 5.2.0-rc.1 + * Version: 5.2.0 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index 38cb849991764..5c30b5addb71b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.2.0-rc.1", + "version": "5.2.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 0f3b37a094e24..0700d64f3141b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.2.0-rc.1", + "version": "5.2.0", "private": true, "description": "A new WordPress editor experience", "repository": "git+https://github.com/WordPress/gutenberg.git", From b640895f501202c17dd82a2e521d4287b4b38721 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Wed, 6 Mar 2019 12:07:42 +0100 Subject: [PATCH 569/691] chore(release): publish - @wordpress/a11y@2.1.0 - @wordpress/annotations@1.1.0 - @wordpress/api-fetch@3.0.0 - @wordpress/autop@2.1.0 - @wordpress/babel-plugin-import-jsx-pragma@2.0.0 - @wordpress/babel-plugin-makepot@3.0.0 - @wordpress/babel-preset-default@4.0.0 - @wordpress/blob@2.2.0 - @wordpress/block-editor@1.0.0 - @wordpress/block-library@2.3.0 - @wordpress/block-serialization-default-parser@3.0.0 - @wordpress/block-serialization-spec-parser@3.0.0 - @wordpress/blocks@6.1.0 - @wordpress/browserslist-config@2.3.0 - @wordpress/components@7.1.0 - @wordpress/compose@3.1.0 - @wordpress/core-data@2.1.0 - @wordpress/custom-templated-path-webpack-plugin@1.2.0 - @wordpress/data@4.3.0 - @wordpress/date@3.1.0 - @wordpress/deprecated@2.1.0 - @wordpress/docgen@1.0.0 - @wordpress/dom-ready@2.1.0 - @wordpress/dom@2.1.0 - @wordpress/e2e-test-utils@1.0.0 - @wordpress/e2e-tests@1.0.0 - @wordpress/edit-post@3.2.0 - @wordpress/edit-widgets@0.1.0 - @wordpress/editor@9.1.0 - @wordpress/element@2.2.0 - @wordpress/escape-html@1.1.0 - @wordpress/eslint-plugin@2.0.0 - @wordpress/format-library@1.3.0 - @wordpress/hooks@2.1.0 - @wordpress/html-entities@2.1.0 - @wordpress/i18n@3.2.0 - @wordpress/is-shallow-equal@1.2.0 - @wordpress/jest-console@3.0.0 - @wordpress/jest-preset-default@4.0.0 - @wordpress/jest-puppeteer-axe@1.0.0 - @wordpress/keycodes@2.1.0 - @wordpress/library-export-default-webpack-plugin@1.1.0 - @wordpress/list-reusable-blocks@1.2.0 - @wordpress/notices@1.2.0 - @wordpress/npm-package-json-lint-config@1.2.0 - @wordpress/nux@3.1.0 - @wordpress/plugins@2.1.0 - @wordpress/postcss-themes@2.0.0 - @wordpress/priority-queue@1.0.0 - @wordpress/redux-routine@3.1.0 - @wordpress/rich-text@3.1.0 - @wordpress/scripts@3.0.0 - @wordpress/shortcode@2.1.0 - @wordpress/token-list@1.2.0 - @wordpress/url@2.4.0 - @wordpress/viewport@2.2.0 - @wordpress/wordcount@2.1.0 --- packages/a11y/package.json | 2 +- packages/annotations/package.json | 2 +- packages/api-fetch/package.json | 2 +- packages/autop/package.json | 2 +- packages/babel-plugin-import-jsx-pragma/package.json | 2 +- packages/babel-plugin-makepot/package.json | 2 +- packages/babel-preset-default/package.json | 2 +- packages/blob/package.json | 2 +- packages/block-editor/package.json | 2 +- packages/block-library/package.json | 2 +- packages/block-serialization-default-parser/package.json | 2 +- packages/block-serialization-spec-parser/package.json | 2 +- packages/blocks/package.json | 2 +- packages/browserslist-config/package.json | 2 +- packages/components/package.json | 2 +- packages/compose/package.json | 2 +- packages/core-data/package.json | 2 +- packages/custom-templated-path-webpack-plugin/package.json | 2 +- packages/data/package.json | 2 +- packages/date/package.json | 2 +- packages/deprecated/package.json | 2 +- packages/docgen/package.json | 2 +- packages/dom-ready/package.json | 2 +- packages/dom/package.json | 2 +- packages/e2e-test-utils/package.json | 2 +- packages/e2e-tests/package.json | 2 +- packages/edit-post/package.json | 2 +- packages/edit-widgets/package.json | 2 +- packages/editor/package.json | 2 +- packages/element/package.json | 2 +- packages/escape-html/package.json | 2 +- packages/eslint-plugin/package.json | 2 +- packages/format-library/package.json | 2 +- packages/hooks/package.json | 2 +- packages/html-entities/package.json | 2 +- packages/i18n/package.json | 2 +- packages/is-shallow-equal/package.json | 2 +- packages/jest-console/package.json | 2 +- packages/jest-preset-default/package.json | 2 +- packages/jest-puppeteer-axe/package.json | 2 +- packages/keycodes/package.json | 2 +- packages/library-export-default-webpack-plugin/package.json | 2 +- packages/list-reusable-blocks/package.json | 2 +- packages/notices/package.json | 2 +- packages/npm-package-json-lint-config/package.json | 2 +- packages/nux/package.json | 2 +- packages/plugins/package.json | 2 +- packages/postcss-themes/package.json | 2 +- packages/priority-queue/package.json | 2 +- packages/redux-routine/package.json | 2 +- packages/rich-text/package.json | 2 +- packages/scripts/package.json | 2 +- packages/shortcode/package.json | 2 +- packages/token-list/package.json | 2 +- packages/url/package.json | 2 +- packages/viewport/package.json | 2 +- packages/wordcount/package.json | 2 +- 57 files changed, 57 insertions(+), 57 deletions(-) diff --git a/packages/a11y/package.json b/packages/a11y/package.json index dac861cbc1869..bd564572960a6 100644 --- a/packages/a11y/package.json +++ b/packages/a11y/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/a11y", - "version": "2.0.2", + "version": "2.1.0", "description": "Accessibility (a11y) utilities for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/annotations/package.json b/packages/annotations/package.json index af891a4376c52..34c055841844f 100644 --- a/packages/annotations/package.json +++ b/packages/annotations/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/annotations", - "version": "1.0.8", + "version": "1.1.0", "description": "Annotate content in the Gutenberg editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/api-fetch/package.json b/packages/api-fetch/package.json index d5d4b55da93f7..67f99e6907d7a 100644 --- a/packages/api-fetch/package.json +++ b/packages/api-fetch/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/api-fetch", - "version": "2.2.8", + "version": "3.0.0", "description": "Utility to make WordPress REST API requests.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/autop/package.json b/packages/autop/package.json index b89416ed15b2f..4f1441ff2024f 100644 --- a/packages/autop/package.json +++ b/packages/autop/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/autop", - "version": "2.0.2", + "version": "2.1.0", "description": "WordPress's automatic paragraph functions `autop` and `removep`.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/babel-plugin-import-jsx-pragma/package.json b/packages/babel-plugin-import-jsx-pragma/package.json index c06f76a26f3bb..5b0126c4903bf 100644 --- a/packages/babel-plugin-import-jsx-pragma/package.json +++ b/packages/babel-plugin-import-jsx-pragma/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/babel-plugin-import-jsx-pragma", - "version": "1.1.3", + "version": "2.0.0", "description": "Babel transform plugin for automatically injecting an import to be used as the pragma for the React JSX Transform plugin.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/babel-plugin-makepot/package.json b/packages/babel-plugin-makepot/package.json index 10b4d4eb90d02..973c4162a48dc 100644 --- a/packages/babel-plugin-makepot/package.json +++ b/packages/babel-plugin-makepot/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/babel-plugin-makepot", - "version": "2.1.3", + "version": "3.0.0", "description": "WordPress Babel internationalization (i18n) plugin.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/babel-preset-default/package.json b/packages/babel-preset-default/package.json index 1bf66c0c54e6c..e3096fa847d45 100644 --- a/packages/babel-preset-default/package.json +++ b/packages/babel-preset-default/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/babel-preset-default", - "version": "3.0.2", + "version": "4.0.0", "description": "Default Babel preset for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/blob/package.json b/packages/blob/package.json index 39bdfcf6377b2..a4032f11d80d5 100644 --- a/packages/blob/package.json +++ b/packages/blob/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/blob", - "version": "2.1.0", + "version": "2.2.0", "description": "Blob utilities for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json index 232ed8a681fcd..17cd7064411ec 100644 --- a/packages/block-editor/package.json +++ b/packages/block-editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-editor", - "version": "1.0.0-alpha.0", + "version": "1.0.0", "description": "Generic block editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-library/package.json b/packages/block-library/package.json index 5847272862360..8c5ebfa5cb6dc 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-library", - "version": "2.2.16", + "version": "2.3.0", "description": "Block library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-serialization-default-parser/package.json b/packages/block-serialization-default-parser/package.json index 28e9bf0f6c54b..4cd7de76fcd50 100644 --- a/packages/block-serialization-default-parser/package.json +++ b/packages/block-serialization-default-parser/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-serialization-default-parser", - "version": "2.0.5", + "version": "3.0.0", "description": "Block serialization specification parser for WordPress posts.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-serialization-spec-parser/package.json b/packages/block-serialization-spec-parser/package.json index 335d245be2b9a..3761d551ae6d5 100644 --- a/packages/block-serialization-spec-parser/package.json +++ b/packages/block-serialization-spec-parser/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-serialization-spec-parser", - "version": "2.0.3", + "version": "3.0.0", "description": "Block serialization specification parser for WordPress posts.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/blocks/package.json b/packages/blocks/package.json index eea81e9985e50..071cfff3baa0d 100644 --- a/packages/blocks/package.json +++ b/packages/blocks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/blocks", - "version": "6.0.7", + "version": "6.1.0", "description": "Block API for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/browserslist-config/package.json b/packages/browserslist-config/package.json index 84c51f2345659..4b5e80b54bee4 100644 --- a/packages/browserslist-config/package.json +++ b/packages/browserslist-config/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/browserslist-config", - "version": "2.2.3", + "version": "2.3.0", "description": "WordPress Browserslist shared configuration.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/components/package.json b/packages/components/package.json index 9613e850a722d..c8ac194c2ebfd 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/components", - "version": "7.0.8", + "version": "7.1.0", "description": "UI components for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/compose/package.json b/packages/compose/package.json index 875dbf0852d35..e054a0bedff96 100644 --- a/packages/compose/package.json +++ b/packages/compose/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/compose", - "version": "3.0.1", + "version": "3.1.0", "description": "WordPress higher-order components (HOCs).", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/core-data/package.json b/packages/core-data/package.json index b2173586318f5..dc1d6678a00dc 100644 --- a/packages/core-data/package.json +++ b/packages/core-data/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/core-data", - "version": "2.0.17", + "version": "2.1.0", "description": "Access to and manipulation of core WordPress entities.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/custom-templated-path-webpack-plugin/package.json b/packages/custom-templated-path-webpack-plugin/package.json index 02cd1d4c4d7c9..b1f0832b34ab8 100644 --- a/packages/custom-templated-path-webpack-plugin/package.json +++ b/packages/custom-templated-path-webpack-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/custom-templated-path-webpack-plugin", - "version": "1.1.6", + "version": "1.2.0", "description": "Webpack plugin for creating custom path template tags.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/data/package.json b/packages/data/package.json index c351248f049a9..98c243c145b44 100644 --- a/packages/data/package.json +++ b/packages/data/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/data", - "version": "4.2.1", + "version": "4.3.0", "description": "Data module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/date/package.json b/packages/date/package.json index becb695d02ebd..7acabbc90bf7c 100644 --- a/packages/date/package.json +++ b/packages/date/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/date", - "version": "3.0.1", + "version": "3.1.0", "description": "Date module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/deprecated/package.json b/packages/deprecated/package.json index b8584a7e4f266..82c1876fb09ea 100644 --- a/packages/deprecated/package.json +++ b/packages/deprecated/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/deprecated", - "version": "2.0.5", + "version": "2.1.0", "description": "Deprecation utility for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/docgen/package.json b/packages/docgen/package.json index dd94f3b8de2ff..a02ca5104c87d 100644 --- a/packages/docgen/package.json +++ b/packages/docgen/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/docgen", - "version": "1.0.0-beta.0", + "version": "1.0.0", "description": "Autogenerate public API documentation from exports and JSDoc comments.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/dom-ready/package.json b/packages/dom-ready/package.json index 22f70e8341189..8d88781a9e4fb 100644 --- a/packages/dom-ready/package.json +++ b/packages/dom-ready/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/dom-ready", - "version": "2.0.2", + "version": "2.1.0", "description": "Execute callback after the DOM is loaded.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/dom/package.json b/packages/dom/package.json index 75289e1e27ce4..007f809ebe221 100644 --- a/packages/dom/package.json +++ b/packages/dom/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/dom", - "version": "2.0.8", + "version": "2.1.0", "description": "DOM utilities module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/e2e-test-utils/package.json b/packages/e2e-test-utils/package.json index f31d67677475e..33404a2d3d956 100644 --- a/packages/e2e-test-utils/package.json +++ b/packages/e2e-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/e2e-test-utils", - "version": "1.0.0-alpha.0", + "version": "1.0.0", "description": "End-To-End (E2E) test utils for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/e2e-tests/package.json b/packages/e2e-tests/package.json index a10d916e486d3..fc4093cd68903 100644 --- a/packages/e2e-tests/package.json +++ b/packages/e2e-tests/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/e2e-tests", - "version": "1.0.0-alpha.0", + "version": "1.0.0", "description": "End-To-End (E2E) tests for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json index 74756043aec27..4f366d9decb3a 100644 --- a/packages/edit-post/package.json +++ b/packages/edit-post/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-post", - "version": "3.1.11", + "version": "3.2.0", "description": "Edit Post module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/edit-widgets/package.json b/packages/edit-widgets/package.json index 3adbdad1f19a1..66805000fbf06 100644 --- a/packages/edit-widgets/package.json +++ b/packages/edit-widgets/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-widgets", - "version": "0.0.1-alpha.0", + "version": "0.1.0", "private": true, "description": "Widgets Page module for WordPress..", "author": "The WordPress Contributors", diff --git a/packages/editor/package.json b/packages/editor/package.json index 8893dbf4fea7c..97935dd04b406 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/editor", - "version": "9.0.11", + "version": "9.1.0", "description": "Building blocks for WordPress editors.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/element/package.json b/packages/element/package.json index ce76fa344232c..3702a0b559a4d 100644 --- a/packages/element/package.json +++ b/packages/element/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/element", - "version": "2.1.9", + "version": "2.2.0", "description": "Element React module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/escape-html/package.json b/packages/escape-html/package.json index 5e60de55ab031..909f2bd4ec1f3 100644 --- a/packages/escape-html/package.json +++ b/packages/escape-html/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/escape-html", - "version": "1.0.1", + "version": "1.1.0", "description": "Escape HTML utils.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index f115d81c5c41e..3d79f48f78fb7 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/eslint-plugin", - "version": "1.0.1", + "version": "2.0.0", "description": "ESLint plugin for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/format-library/package.json b/packages/format-library/package.json index ec4e8a02a90be..32f10182c632f 100644 --- a/packages/format-library/package.json +++ b/packages/format-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/format-library", - "version": "1.2.14", + "version": "1.3.0", "description": "Format library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/hooks/package.json b/packages/hooks/package.json index 3469fc00f451e..ed5e1800079ff 100644 --- a/packages/hooks/package.json +++ b/packages/hooks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/hooks", - "version": "2.0.5", + "version": "2.1.0", "description": "WordPress hooks library.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/html-entities/package.json b/packages/html-entities/package.json index 06c7a9d25d59c..6b6d7ec47910d 100644 --- a/packages/html-entities/package.json +++ b/packages/html-entities/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/html-entities", - "version": "2.0.4", + "version": "2.1.0", "description": "HTML entity utilities for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/i18n/package.json b/packages/i18n/package.json index 91e9e20fd3ea0..9734f9970e04d 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/i18n", - "version": "3.1.1", + "version": "3.2.0", "description": "WordPress internationalization (i18n) library.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/is-shallow-equal/package.json b/packages/is-shallow-equal/package.json index 06c38ebf63cd2..55239ed32ba70 100644 --- a/packages/is-shallow-equal/package.json +++ b/packages/is-shallow-equal/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/is-shallow-equal", - "version": "1.1.5", + "version": "1.2.0", "description": "Test for shallow equality between two objects or arrays.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/jest-console/package.json b/packages/jest-console/package.json index 2b54b68bb8ff8..7c249c4f0a9a4 100644 --- a/packages/jest-console/package.json +++ b/packages/jest-console/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/jest-console", - "version": "2.0.7", + "version": "3.0.0", "description": "Custom Jest matchers for the Console object.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/jest-preset-default/package.json b/packages/jest-preset-default/package.json index e8bbd45dd0b8b..17a83cb24602d 100644 --- a/packages/jest-preset-default/package.json +++ b/packages/jest-preset-default/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/jest-preset-default", - "version": "3.0.3", + "version": "4.0.0", "description": "Default Jest preset for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/jest-puppeteer-axe/package.json b/packages/jest-puppeteer-axe/package.json index c8a534ec613d4..1ce6c86de38d2 100644 --- a/packages/jest-puppeteer-axe/package.json +++ b/packages/jest-puppeteer-axe/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/jest-puppeteer-axe", - "version": "1.0.0-alpha.0", + "version": "1.0.0", "description": "Axe API integration with Jest and Puppeteer.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/keycodes/package.json b/packages/keycodes/package.json index d062bf6f75a36..82fb2b06fc04e 100644 --- a/packages/keycodes/package.json +++ b/packages/keycodes/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/keycodes", - "version": "2.0.6", + "version": "2.1.0", "description": "Keycodes utilities for WordPress. Used to check for keyboard events across browsers/operating systems.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/library-export-default-webpack-plugin/package.json b/packages/library-export-default-webpack-plugin/package.json index c64a2f3bee854..04f921c654af8 100644 --- a/packages/library-export-default-webpack-plugin/package.json +++ b/packages/library-export-default-webpack-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/library-export-default-webpack-plugin", - "version": "1.0.5", + "version": "1.1.0", "description": "Webpack plugin for exporting default property for selected libraries.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/list-reusable-blocks/package.json b/packages/list-reusable-blocks/package.json index 9501833c972fb..f27ad18a42a0b 100644 --- a/packages/list-reusable-blocks/package.json +++ b/packages/list-reusable-blocks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/list-reusable-blocks", - "version": "1.1.21", + "version": "1.2.0", "description": "Adding Export/Import support to the reusable blocks listing.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/notices/package.json b/packages/notices/package.json index 93f5e16eac4b1..929afd3efd5ab 100644 --- a/packages/notices/package.json +++ b/packages/notices/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/notices", - "version": "1.1.3", + "version": "1.2.0", "description": "State management for notices.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/npm-package-json-lint-config/package.json b/packages/npm-package-json-lint-config/package.json index d578fc98298f3..5e3a1a1af40c6 100644 --- a/packages/npm-package-json-lint-config/package.json +++ b/packages/npm-package-json-lint-config/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/npm-package-json-lint-config", - "version": "1.1.6", + "version": "1.2.0", "description": "WordPress npm-package-json-lint shareable configuration.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/nux/package.json b/packages/nux/package.json index 1b9ebbe5f7e4d..5bd88f3b1d9c2 100644 --- a/packages/nux/package.json +++ b/packages/nux/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/nux", - "version": "3.0.9", + "version": "3.1.0", "description": "NUX (New User eXperience) module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/plugins/package.json b/packages/plugins/package.json index 5f433e110f6b8..4aff74a1df83d 100644 --- a/packages/plugins/package.json +++ b/packages/plugins/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/plugins", - "version": "2.0.11", + "version": "2.1.0", "description": "Plugins module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/postcss-themes/package.json b/packages/postcss-themes/package.json index 8a089ee2c8ccf..a23e92af7a41d 100644 --- a/packages/postcss-themes/package.json +++ b/packages/postcss-themes/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/postcss-themes", - "version": "1.0.5", + "version": "2.0.0", "description": "PostCSS plugin to generate theme colors.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/priority-queue/package.json b/packages/priority-queue/package.json index afa5df6e857ec..70ebca39073bb 100644 --- a/packages/priority-queue/package.json +++ b/packages/priority-queue/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/priority-queue", - "version": "1.0.0-alpha.0", + "version": "1.0.0", "description": "Generic browser priority queue.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/redux-routine/package.json b/packages/redux-routine/package.json index 3ee1016b84b68..bbeb56eca1cb4 100644 --- a/packages/redux-routine/package.json +++ b/packages/redux-routine/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/redux-routine", - "version": "3.0.4", + "version": "3.1.0", "description": "Redux middleware for generator coroutines.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/rich-text/package.json b/packages/rich-text/package.json index 029e2056d3a5c..824ce28ef1d18 100644 --- a/packages/rich-text/package.json +++ b/packages/rich-text/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/rich-text", - "version": "3.0.7", + "version": "3.1.0", "description": "Rich text value and manipulation API.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/scripts/package.json b/packages/scripts/package.json index e59064fc82f38..21f71aa6861e3 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/scripts", - "version": "2.5.0", + "version": "3.0.0", "description": "Collection of reusable scripts for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/shortcode/package.json b/packages/shortcode/package.json index 5fd63da90e79e..acda28244caca 100644 --- a/packages/shortcode/package.json +++ b/packages/shortcode/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/shortcode", - "version": "2.0.2", + "version": "2.1.0", "description": "Shortcode module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/token-list/package.json b/packages/token-list/package.json index 5c04a6bcb92f3..3213d1504290b 100644 --- a/packages/token-list/package.json +++ b/packages/token-list/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/token-list", - "version": "1.1.0", + "version": "1.2.0", "description": "Constructable, plain JavaScript DOMTokenList implementation, supporting non-browser runtimes.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/url/package.json b/packages/url/package.json index 2ae9d56532c18..244925c0c4eaf 100644 --- a/packages/url/package.json +++ b/packages/url/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/url", - "version": "2.3.3", + "version": "2.4.0", "description": "WordPress URL utilities.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/viewport/package.json b/packages/viewport/package.json index 106bd96b57f3b..a532f6299dc69 100644 --- a/packages/viewport/package.json +++ b/packages/viewport/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/viewport", - "version": "2.1.1", + "version": "2.2.0", "description": "Viewport module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/wordcount/package.json b/packages/wordcount/package.json index a5ec368498b1e..74f2637f58eed 100644 --- a/packages/wordcount/package.json +++ b/packages/wordcount/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/wordcount", - "version": "2.0.3", + "version": "2.1.0", "description": "WordPress word count utility.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", From 40d5cd6ff70723808340e6fc322bd40b711e2a64 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Wed, 6 Mar 2019 12:13:31 +0100 Subject: [PATCH 570/691] Update the package changelogs after the npm release --- packages/api-fetch/CHANGELOG.md | 2 +- packages/autop/CHANGELOG.md | 2 +- packages/babel-plugin-import-jsx-pragma/CHANGELOG.md | 2 +- packages/babel-plugin-makepot/CHANGELOG.md | 2 +- packages/babel-preset-default/CHANGELOG.md | 2 +- packages/block-editor/CHANGELOG.md | 2 +- packages/block-library/CHANGELOG.md | 2 +- packages/block-serialization-spec-parser/CHANGELOG.md | 2 +- packages/blocks/CHANGELOG.md | 2 +- packages/components/CHANGELOG.md | 2 +- packages/data/CHANGELOG.md | 2 +- packages/docgen/CHANGELOG.md | 2 +- packages/dom/CHANGELOG.md | 2 +- packages/e2e-test-utils/CHANGELOG.md | 2 +- packages/e2e-tests/CHANGELOG.md | 2 +- packages/edit-widgets/CHANGELOG.md | 2 +- packages/editor/CHANGELOG.md | 2 +- packages/eslint-plugin/CHANGELOG.md | 2 +- packages/is-shallow-equal/CHANGELOG.md | 2 +- packages/jest-console/CHANGELOG.md | 2 +- packages/jest-preset-default/CHANGELOG.md | 2 +- packages/jest-puppeteer-axe/CHANGELOG.md | 2 +- packages/npm-package-json-lint-config/CHANGELOG.md | 2 +- packages/postcss-themes/CHANGELOG.md | 2 +- packages/priority-queue/CHANGELOG.md | 2 +- packages/redux-routine/CHANGELOG.md | 2 +- packages/rich-text/CHANGELOG.md | 2 +- packages/scripts/CHANGELOG.md | 2 +- 28 files changed, 28 insertions(+), 28 deletions(-) diff --git a/packages/api-fetch/CHANGELOG.md b/packages/api-fetch/CHANGELOG.md index 1727529b88366..dc7dcdeb3defe 100644 --- a/packages/api-fetch/CHANGELOG.md +++ b/packages/api-fetch/CHANGELOG.md @@ -1,4 +1,4 @@ -## 3.0.0 (Unreleased) +## 3.0.0 (2019-03-06) ### Breaking Changes diff --git a/packages/autop/CHANGELOG.md b/packages/autop/CHANGELOG.md index 184ca54078a38..53373272a2459 100644 --- a/packages/autop/CHANGELOG.md +++ b/packages/autop/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.0.1 (Unreleased) +## 2.1.0 (2019-03-06) ### Bug Fix diff --git a/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md b/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md index 7a7c426e6ead9..69c48331c9b90 100644 --- a/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md +++ b/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.0.0 (Unreleased) +## 2.0.0 (2019-03-06) ### Breaking Change diff --git a/packages/babel-plugin-makepot/CHANGELOG.md b/packages/babel-plugin-makepot/CHANGELOG.md index e48eeff4bca3e..e6518a0d859e4 100644 --- a/packages/babel-plugin-makepot/CHANGELOG.md +++ b/packages/babel-plugin-makepot/CHANGELOG.md @@ -1,4 +1,4 @@ -## v2.1.1 (Unreleased) +## v2.2.0 (2019-03-06) ### Bug Fix diff --git a/packages/babel-preset-default/CHANGELOG.md b/packages/babel-preset-default/CHANGELOG.md index 2f2ce6a633962..7ff815a2a7db0 100644 --- a/packages/babel-preset-default/CHANGELOG.md +++ b/packages/babel-preset-default/CHANGELOG.md @@ -1,4 +1,4 @@ -## 4.0.0 (Unreleased) +## 4.0.0 (2019-03-06) ### Breaking Change diff --git a/packages/block-editor/CHANGELOG.md b/packages/block-editor/CHANGELOG.md index b5527e39e4e7c..5c4cae61450ac 100644 --- a/packages/block-editor/CHANGELOG.md +++ b/packages/block-editor/CHANGELOG.md @@ -1,4 +1,4 @@ -## 1.0.0 (Unreleased) +## 1.0.0 (2019-03-06) ### New Features diff --git a/packages/block-library/CHANGELOG.md b/packages/block-library/CHANGELOG.md index 427d55ba22412..f46b47f579afe 100644 --- a/packages/block-library/CHANGELOG.md +++ b/packages/block-library/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.3.0 (Unreleased) +## 2.3.0 (2019-03-06) ### New Feature diff --git a/packages/block-serialization-spec-parser/CHANGELOG.md b/packages/block-serialization-spec-parser/CHANGELOG.md index 7ba44f0cd9a96..59a1acaa802a1 100644 --- a/packages/block-serialization-spec-parser/CHANGELOG.md +++ b/packages/block-serialization-spec-parser/CHANGELOG.md @@ -1,4 +1,4 @@ -## 3.0.0 (Unreleased) +## 3.0.0 (2019-03-06) ## Breaking Change diff --git a/packages/blocks/CHANGELOG.md b/packages/blocks/CHANGELOG.md index e4a1b5977bcc0..96d8125c985e1 100644 --- a/packages/blocks/CHANGELOG.md +++ b/packages/blocks/CHANGELOG.md @@ -1,4 +1,4 @@ -## 6.1.0 (Unreleased) +## 6.1.0 (2019-03-06) ### New Feature diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index f8ad35ad5c426..b596c09199849 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -1,4 +1,4 @@ -## 7.1.0 (Unreleased) +## 7.1.0 (2019-03-06) ### New Features diff --git a/packages/data/CHANGELOG.md b/packages/data/CHANGELOG.md index 1a05bfd4891ca..15a723006d351 100644 --- a/packages/data/CHANGELOG.md +++ b/packages/data/CHANGELOG.md @@ -1,4 +1,4 @@ -## 4.3.0 (Unreleased) +## 4.3.0 (2019-03-06) ### Enhancements diff --git a/packages/docgen/CHANGELOG.md b/packages/docgen/CHANGELOG.md index ae69187866b19..9c2240b06686f 100644 --- a/packages/docgen/CHANGELOG.md +++ b/packages/docgen/CHANGELOG.md @@ -1,3 +1,3 @@ -## 1.0.0 (Unreleased) +## 1.0.0 (2019-03-06) - Initial release diff --git a/packages/dom/CHANGELOG.md b/packages/dom/CHANGELOG.md index 3ec9b7a1bf70c..eeca6c818528d 100644 --- a/packages/dom/CHANGELOG.md +++ b/packages/dom/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.0.9 (Unreleased) +## 2.1.0 (2019-03-06) ### Bug Fix diff --git a/packages/e2e-test-utils/CHANGELOG.md b/packages/e2e-test-utils/CHANGELOG.md index e2467d78c92eb..75b6278875283 100644 --- a/packages/e2e-test-utils/CHANGELOG.md +++ b/packages/e2e-test-utils/CHANGELOG.md @@ -1,3 +1,3 @@ -## 1.0.0 (Unreleased) +## 1.0.0 (2019-03-06) - Initial release. diff --git a/packages/e2e-tests/CHANGELOG.md b/packages/e2e-tests/CHANGELOG.md index e2467d78c92eb..75b6278875283 100644 --- a/packages/e2e-tests/CHANGELOG.md +++ b/packages/e2e-tests/CHANGELOG.md @@ -1,3 +1,3 @@ -## 1.0.0 (Unreleased) +## 1.0.0 (2019-03-06) - Initial release. diff --git a/packages/edit-widgets/CHANGELOG.md b/packages/edit-widgets/CHANGELOG.md index 6718bf307cadd..749b9d3b391bb 100644 --- a/packages/edit-widgets/CHANGELOG.md +++ b/packages/edit-widgets/CHANGELOG.md @@ -1,4 +1,4 @@ -## 1.0.0 (Unreleased) +## 0.1.0 (2019-03-06) ### New Features diff --git a/packages/editor/CHANGELOG.md b/packages/editor/CHANGELOG.md index 7c9eb8c623ed9..ce396bfa582ec 100644 --- a/packages/editor/CHANGELOG.md +++ b/packages/editor/CHANGELOG.md @@ -1,4 +1,4 @@ -## 9.1.0 (Unreleased) +## 9.1.0 (2019-03-06) ### New Features diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 0d563005b9613..faa561924f1d1 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.0.0 (Unreleased) +## 2.0.0 (2019-03-06) ### Breaking Changes diff --git a/packages/is-shallow-equal/CHANGELOG.md b/packages/is-shallow-equal/CHANGELOG.md index 876fcc4044e97..76e2a7317c224 100644 --- a/packages/is-shallow-equal/CHANGELOG.md +++ b/packages/is-shallow-equal/CHANGELOG.md @@ -1,4 +1,4 @@ -## 1.2.0 (Unreleased) +## 1.2.0 (2019-03-06) ### New Feature diff --git a/packages/jest-console/CHANGELOG.md b/packages/jest-console/CHANGELOG.md index 3561c61387c0e..3533278048ea4 100644 --- a/packages/jest-console/CHANGELOG.md +++ b/packages/jest-console/CHANGELOG.md @@ -1,4 +1,4 @@ -## 3.0.0 (Unreleased) +## 3.0.0 (2019-03-06) ### Breaking Changes diff --git a/packages/jest-preset-default/CHANGELOG.md b/packages/jest-preset-default/CHANGELOG.md index 04eb1446a749d..0fd0e34112251 100644 --- a/packages/jest-preset-default/CHANGELOG.md +++ b/packages/jest-preset-default/CHANGELOG.md @@ -1,4 +1,4 @@ -## 4.0.0 (Unreleased) +## 4.0.0 (2019-03-06) ### Breaking Changes diff --git a/packages/jest-puppeteer-axe/CHANGELOG.md b/packages/jest-puppeteer-axe/CHANGELOG.md index 16c97e5a1bf32..2256a6340e4fc 100644 --- a/packages/jest-puppeteer-axe/CHANGELOG.md +++ b/packages/jest-puppeteer-axe/CHANGELOG.md @@ -1,3 +1,3 @@ -## 1.0.0 (Unreleased) +## 1.0.0 (2019-03-06) - Initial release. diff --git a/packages/npm-package-json-lint-config/CHANGELOG.md b/packages/npm-package-json-lint-config/CHANGELOG.md index 1462fbbcd91fc..11259b591308d 100644 --- a/packages/npm-package-json-lint-config/CHANGELOG.md +++ b/packages/npm-package-json-lint-config/CHANGELOG.md @@ -1,4 +1,4 @@ -## 1.1.4 (Unreleased) +## 1.2.0 (2019-03-06) ### Internal diff --git a/packages/postcss-themes/CHANGELOG.md b/packages/postcss-themes/CHANGELOG.md index 23a814422fd86..d1e71c6d4bfae 100644 --- a/packages/postcss-themes/CHANGELOG.md +++ b/packages/postcss-themes/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.0.0 (Unreleased) +## 2.0.0 (2019-03-06) ### Breaking change diff --git a/packages/priority-queue/CHANGELOG.md b/packages/priority-queue/CHANGELOG.md index 2297c60f404ea..5c161096b79f1 100644 --- a/packages/priority-queue/CHANGELOG.md +++ b/packages/priority-queue/CHANGELOG.md @@ -1,3 +1,3 @@ -### 1.0.0 (Unreleased) +### 1.0.0 (2019-03-06) Initial release. diff --git a/packages/redux-routine/CHANGELOG.md b/packages/redux-routine/CHANGELOG.md index 1bec9149f6099..cebc412d999b7 100644 --- a/packages/redux-routine/CHANGELOG.md +++ b/packages/redux-routine/CHANGELOG.md @@ -1,4 +1,4 @@ -## 3.0.4 (Unreleased) +## 3.1.0 (2019-03-06) ### Bug Fixes diff --git a/packages/rich-text/CHANGELOG.md b/packages/rich-text/CHANGELOG.md index cd141e933a9b8..f3bb924d1898d 100644 --- a/packages/rich-text/CHANGELOG.md +++ b/packages/rich-text/CHANGELOG.md @@ -1,4 +1,4 @@ -## 3.1.0 (Unreleased) +## 3.1.0 (2019-03-06) ### Enhancement diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index 4e9ea213d597e..c9373b3025f38 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -1,4 +1,4 @@ -## 3.0.0 (Unreleased) +## 3.0.0 (2019-03-06) ### Breaking Changes From 7e1c1ed09b3da740a51fa892cbcbc0b6692b63c1 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Wed, 6 Mar 2019 13:12:06 +0000 Subject: [PATCH 571/691] Add clickBlockToolbarButton end to end test util (#14254) This PR adds an end 2 end test util that allows clicking in a block toolbar button and refactors existing code to use it. ## How has this been tested? We only need to verify that the end 2 end tests pass. --- packages/e2e-test-utils/README.md | 100 ++++++++++-------- .../src/click-block-toolbar-button.js | 15 +++ packages/e2e-test-utils/src/index.js | 1 + packages/e2e-tests/specs/blocks/list.test.js | 20 +--- .../e2e-tests/specs/invalid-block.test.js | 6 +- packages/e2e-tests/specs/links.test.js | 6 +- .../specs/plugins/format-api.test.js | 9 +- .../e2e-tests/specs/reusable-blocks.test.js | 13 +-- .../e2e-tests/specs/style-variation.test.js | 13 +-- 9 files changed, 92 insertions(+), 91 deletions(-) create mode 100644 packages/e2e-test-utils/src/click-block-toolbar-button.js diff --git a/packages/e2e-test-utils/README.md b/packages/e2e-test-utils/README.md index 845b6725cf82f..0b1c589738236 100644 --- a/packages/e2e-test-utils/README.md +++ b/packages/e2e-test-utils/README.md @@ -46,10 +46,20 @@ Clears the local storage. Clicks the default block appender. -### clickButton +### clickBlockToolbarButton [src/index.js#L5-L5](src/index.js#L5-L5) +Clicks a block toolbar button. + +**Parameters** + +- **buttonAriaLabel** `string`: The aria label of the button to click. + +### clickButton + +[src/index.js#L6-L6](src/index.js#L6-L6) + Clicks a button based on the text on the button. **Parameters** @@ -58,7 +68,7 @@ Clicks a button based on the text on the button. ### clickOnCloseModalButton -[src/index.js#L6-L6](src/index.js#L6-L6) +[src/index.js#L7-L7](src/index.js#L7-L7) Click on the close button of an open modal. @@ -68,7 +78,7 @@ Click on the close button of an open modal. ### clickOnMoreMenuItem -[src/index.js#L7-L7](src/index.js#L7-L7) +[src/index.js#L8-L8](src/index.js#L8-L8) Clicks on More Menu item, searches for the button with the text provided and clicks it. @@ -78,7 +88,7 @@ Clicks on More Menu item, searches for the button with the text provided and cli ### createEmbeddingMatcher -[src/index.js#L46-L46](src/index.js#L46-L46) +[src/index.js#L47-L47](src/index.js#L47-L47) Creates a function to determine if a request is embedding a certain URL. @@ -92,7 +102,7 @@ Creates a function to determine if a request is embedding a certain URL. ### createJSONResponse -[src/index.js#L46-L46](src/index.js#L46-L46) +[src/index.js#L47-L47](src/index.js#L47-L47) Respond to a request with a JSON response. @@ -106,7 +116,7 @@ Respond to a request with a JSON response. ### createNewPost -[src/index.js#L8-L8](src/index.js#L8-L8) +[src/index.js#L9-L9](src/index.js#L9-L9) Creates new post. @@ -116,7 +126,7 @@ Creates new post. ### createURL -[src/index.js#L9-L9](src/index.js#L9-L9) +[src/index.js#L10-L10](src/index.js#L10-L10) Creates new URL by parsing base URL, WPPath and query string. @@ -131,7 +141,7 @@ Creates new URL by parsing base URL, WPPath and query string. ### createURLMatcher -[src/index.js#L46-L46](src/index.js#L46-L46) +[src/index.js#L47-L47](src/index.js#L47-L47) Creates a function to determine if a request is calling a URL with the substring present. @@ -145,7 +155,7 @@ Creates a function to determine if a request is calling a URL with the substring ### deactivatePlugin -[src/index.js#L10-L10](src/index.js#L10-L10) +[src/index.js#L11-L11](src/index.js#L11-L11) Deactivates an active plugin. @@ -155,26 +165,26 @@ Deactivates an active plugin. ### disablePrePublishChecks -[src/index.js#L11-L11](src/index.js#L11-L11) +[src/index.js#L12-L12](src/index.js#L12-L12) Disables Pre-publish checks. ### enablePageDialogAccept -[src/index.js#L12-L12](src/index.js#L12-L12) +[src/index.js#L13-L13](src/index.js#L13-L13) Enables even listener which accepts a page dialog which may appear when navigating away from Gutenberg. ### enablePrePublishChecks -[src/index.js#L13-L13](src/index.js#L13-L13) +[src/index.js#L14-L14](src/index.js#L14-L14) Enables Pre-publish checks. ### ensureSidebarOpened -[src/index.js#L14-L14](src/index.js#L14-L14) +[src/index.js#L15-L15](src/index.js#L15-L15) Verifies that the edit post sidebar is opened, and if it is not, opens it. @@ -184,7 +194,7 @@ Verifies that the edit post sidebar is opened, and if it is not, opens it. ### findSidebarPanelToggleButtonWithTitle -[src/index.js#L15-L15](src/index.js#L15-L15) +[src/index.js#L16-L16](src/index.js#L16-L16) Finds a sidebar panel with the provided title. @@ -198,7 +208,7 @@ Finds a sidebar panel with the provided title. ### findSidebarPanelWithTitle -[src/index.js#L16-L16](src/index.js#L16-L16) +[src/index.js#L17-L17](src/index.js#L17-L17) Finds the button responsible for toggling the sidebar panel with the provided title. @@ -212,7 +222,7 @@ Finds the button responsible for toggling the sidebar panel with the provided ti ### getAllBlocks -[src/index.js#L17-L17](src/index.js#L17-L17) +[src/index.js#L18-L18](src/index.js#L18-L18) Returns an array with all blocks; Equivalent to calling wp.data.select( 'core/editor' ).getBlocks(); @@ -222,7 +232,7 @@ Returns an array with all blocks; Equivalent to calling wp.data.select( 'core/ed ### getAvailableBlockTransforms -[src/index.js#L18-L18](src/index.js#L18-L18) +[src/index.js#L19-L19](src/index.js#L19-L19) Returns an array of strings with all block titles, that the current selected block can be transformed into. @@ -233,7 +243,7 @@ that the current selected block can be transformed into. ### getEditedPostContent -[src/index.js#L19-L19](src/index.js#L19-L19) +[src/index.js#L20-L20](src/index.js#L20-L20) Returns a promise which resolves with the edited post content (HTML string). @@ -243,7 +253,7 @@ Returns a promise which resolves with the edited post content (HTML string). ### hasBlockSwitcher -[src/index.js#L20-L20](src/index.js#L20-L20) +[src/index.js#L21-L21](src/index.js#L21-L21) Returns a boolean indicating if the current selected block has a block switcher or not. @@ -253,7 +263,7 @@ Returns a boolean indicating if the current selected block has a block switcher ### insertBlock -[src/index.js#L21-L21](src/index.js#L21-L21) +[src/index.js#L22-L22](src/index.js#L22-L22) Opens the inserter, searches for the given term, then selects the first result that appears. @@ -265,7 +275,7 @@ result that appears. ### installPlugin -[src/index.js#L22-L22](src/index.js#L22-L22) +[src/index.js#L23-L23](src/index.js#L23-L23) Installs a plugin from the WP.org repository. @@ -276,7 +286,7 @@ Installs a plugin from the WP.org repository. ### isCurrentURL -[src/index.js#L23-L23](src/index.js#L23-L23) +[src/index.js#L24-L24](src/index.js#L24-L24) Checks if current URL is a WordPress path. @@ -291,7 +301,7 @@ Checks if current URL is a WordPress path. ### loginUser -[src/index.js#L24-L24](src/index.js#L24-L24) +[src/index.js#L25-L25](src/index.js#L25-L25) Performs log in with specified username and password. @@ -302,7 +312,7 @@ Performs log in with specified username and password. ### mockOrTransform -[src/index.js#L46-L46](src/index.js#L46-L46) +[src/index.js#L47-L47](src/index.js#L47-L47) Mocks a request with the supplied mock object, or allows it to run with an optional transform, based on the deserialised JSON response for the request. @@ -319,26 +329,26 @@ deserialised JSON response for the request. ### observeFocusLoss -[src/index.js#L25-L25](src/index.js#L25-L25) +[src/index.js#L26-L26](src/index.js#L26-L26) Binds to the document on page load which throws an error if a `focusout` event occurs without a related target (i.e. focus loss). ### openDocumentSettingsSidebar -[src/index.js#L26-L26](src/index.js#L26-L26) +[src/index.js#L27-L27](src/index.js#L27-L27) Clicks on the button in the header which opens Document Settings sidebar when it is closed. ### openPublishPanel -[src/index.js#L27-L27](src/index.js#L27-L27) +[src/index.js#L28-L28](src/index.js#L28-L28) Opens the publish panel. ### pressKeyTimes -[src/index.js#L28-L28](src/index.js#L28-L28) +[src/index.js#L29-L29](src/index.js#L29-L29) Presses the given keyboard key a number of times in sequence. @@ -353,7 +363,7 @@ Presses the given keyboard key a number of times in sequence. ### pressKeyWithModifier -[src/index.js#L29-L29](src/index.js#L29-L29) +[src/index.js#L30-L30](src/index.js#L30-L30) Performs a key press with modifier (Shift, Control, Meta, Alt), where each modifier is normalized to platform-specific modifier. @@ -365,7 +375,7 @@ is normalized to platform-specific modifier. ### publishPost -[src/index.js#L30-L30](src/index.js#L30-L30) +[src/index.js#L31-L31](src/index.js#L31-L31) Publishes the post, resolving once the request is complete (once a notice is displayed). @@ -376,7 +386,7 @@ is displayed). ### publishPostWithPrePublishChecksDisabled -[src/index.js#L31-L31](src/index.js#L31-L31) +[src/index.js#L32-L32](src/index.js#L32-L32) Publishes the post without the pre-publish checks, resolving once the request is complete (once a notice is displayed). @@ -387,7 +397,7 @@ resolving once the request is complete (once a notice is displayed). ### saveDraft -[src/index.js#L32-L32](src/index.js#L32-L32) +[src/index.js#L33-L33](src/index.js#L33-L33) Saves the post as a draft, resolving once the request is complete (once the "Saved" indicator is displayed). @@ -398,7 +408,7 @@ Saves the post as a draft, resolving once the request is complete (once the ### searchForBlock -[src/index.js#L33-L33](src/index.js#L33-L33) +[src/index.js#L34-L34](src/index.js#L34-L34) Search for block in the global inserter @@ -408,7 +418,7 @@ Search for block in the global inserter ### selectBlockByClientId -[src/index.js#L34-L34](src/index.js#L34-L34) +[src/index.js#L35-L35](src/index.js#L35-L35) Given the clientId of a block, selects the block on the editor. @@ -418,7 +428,7 @@ Given the clientId of a block, selects the block on the editor. ### setBrowserViewport -[src/index.js#L35-L35](src/index.js#L35-L35) +[src/index.js#L36-L36](src/index.js#L36-L36) Sets browser viewport to specified type. @@ -428,7 +438,7 @@ Sets browser viewport to specified type. ### setPostContent -[src/index.js#L36-L36](src/index.js#L36-L36) +[src/index.js#L37-L37](src/index.js#L37-L37) Sets code editor content @@ -442,7 +452,7 @@ Sets code editor content ### setUpResponseMocking -[src/index.js#L46-L46](src/index.js#L46-L46) +[src/index.js#L47-L47](src/index.js#L47-L47) Sets up mock checks and responses. Accepts a list of mock settings with the following properties: @@ -473,7 +483,7 @@ If none of the mock settings match the request, the request is allowed to contin ### switchEditorModeTo -[src/index.js#L37-L37](src/index.js#L37-L37) +[src/index.js#L38-L38](src/index.js#L38-L38) Switches editor mode. @@ -483,21 +493,21 @@ Switches editor mode. ### switchUserToAdmin -[src/index.js#L38-L38](src/index.js#L38-L38) +[src/index.js#L39-L39](src/index.js#L39-L39) Switches the current user to the admin user (if the user running the test is not already the admin user). ### switchUserToTest -[src/index.js#L39-L39](src/index.js#L39-L39) +[src/index.js#L40-L40](src/index.js#L40-L40) Switches the current user to whichever user we should be running the tests as (if we're not already that user). ### toggleScreenOption -[src/index.js#L40-L40](src/index.js#L40-L40) +[src/index.js#L41-L41](src/index.js#L41-L41) Toggles the screen option with the given label. @@ -508,7 +518,7 @@ Toggles the screen option with the given label. ### transformBlockTo -[src/index.js#L41-L41](src/index.js#L41-L41) +[src/index.js#L42-L42](src/index.js#L42-L42) Converts editor's block type. @@ -518,7 +528,7 @@ Converts editor's block type. ### uninstallPlugin -[src/index.js#L42-L42](src/index.js#L42-L42) +[src/index.js#L43-L43](src/index.js#L43-L43) Uninstalls a plugin. @@ -528,7 +538,7 @@ Uninstalls a plugin. ### visitAdminPage -[src/index.js#L43-L43](src/index.js#L43-L43) +[src/index.js#L44-L44](src/index.js#L44-L44) Visits admin page; if user is not logged in then it logging in it first, then visits admin page. @@ -539,7 +549,7 @@ Visits admin page; if user is not logged in then it logging in it first, then vi ### waitForWindowDimensions -[src/index.js#L44-L44](src/index.js#L44-L44) +[src/index.js#L45-L45](src/index.js#L45-L45) Function that waits until the page viewport has the required dimensions. It is being used to address a problem where after using setViewport the execution may continue, diff --git a/packages/e2e-test-utils/src/click-block-toolbar-button.js b/packages/e2e-test-utils/src/click-block-toolbar-button.js new file mode 100644 index 0000000000000..511b300866673 --- /dev/null +++ b/packages/e2e-test-utils/src/click-block-toolbar-button.js @@ -0,0 +1,15 @@ +/** + * Clicks a block toolbar button. + * + * @param {string} buttonAriaLabel The aria label of the button to click. + */ +export async function clickBlockToolbarButton( buttonAriaLabel ) { + const BLOCK_TOOLBAR_SELECTOR = '.editor-block-toolbar'; + const BUTTON_SELECTOR = `${ BLOCK_TOOLBAR_SELECTOR } button[aria-label="${ buttonAriaLabel }"]`; + if ( await page.$( BLOCK_TOOLBAR_SELECTOR ) === null ) { + // Press escape to show the block toolbar + await page.keyboard.press( 'Escape' ); + } + await page.waitForSelector( BUTTON_SELECTOR ); + await page.click( BUTTON_SELECTOR ); +} diff --git a/packages/e2e-test-utils/src/index.js b/packages/e2e-test-utils/src/index.js index 6f48ab320a206..2d9e5b6af6ffc 100644 --- a/packages/e2e-test-utils/src/index.js +++ b/packages/e2e-test-utils/src/index.js @@ -2,6 +2,7 @@ export { activatePlugin } from './activate-plugin'; export { arePrePublishChecksEnabled } from './are-pre-publish-checks-enabled'; export { clearLocalStorage } from './clear-local-storage'; export { clickBlockAppender } from './click-block-appender'; +export { clickBlockToolbarButton } from './click-block-toolbar-button'; export { clickButton } from './click-button'; export { clickOnCloseModalButton } from './click-on-close-modal-button'; export { clickOnMoreMenuItem } from './click-on-more-menu-item'; diff --git a/packages/e2e-tests/specs/blocks/list.test.js b/packages/e2e-tests/specs/blocks/list.test.js index 8432ff5d912d6..23d02e0f10cb7 100644 --- a/packages/e2e-tests/specs/blocks/list.test.js +++ b/packages/e2e-tests/specs/blocks/list.test.js @@ -3,6 +3,7 @@ */ import { clickBlockAppender, + clickBlockToolbarButton, getEditedPostContent, createNewPost, pressKeyTimes, @@ -124,11 +125,7 @@ describe( 'List', () => { await insertBlock( 'List' ); await page.keyboard.type( 'one' ); await page.keyboard.press( 'Enter' ); - // Pointer device is needed. Shift+Tab won't focus the toolbar. - // To do: fix so Shift+Tab works. - // Press escape to show the block toolbar - await page.keyboard.press( 'Escape' ); - await page.click( 'button[aria-label="Indent list item"]' ); + await clickBlockToolbarButton( 'Indent list item' ); await page.keyboard.type( 'two' ); await transformBlockTo( 'Paragraph' ); @@ -198,11 +195,7 @@ describe( 'List', () => { await insertBlock( 'List' ); await page.keyboard.type( 'one' ); await page.keyboard.press( 'Enter' ); - // Pointer device is needed. Shift+Tab won't focus the toolbar. - // To do: fix so Shift+Tab works. - // Press escape to show the block toolbar - await page.keyboard.press( 'Escape' ); - await page.click( 'button[aria-label="Indent list item"]' ); + await clickBlockToolbarButton( 'Indent list item' ); await page.keyboard.type( 'two' ); await page.keyboard.press( 'Enter' ); await page.keyboard.type( 'three' ); @@ -233,12 +226,7 @@ describe( 'List', () => { await pressKeyWithModifier( 'primary', 'm' ); await page.keyboard.type( '1' ); - // Pointer device is needed. Shift+Tab won't focus the toolbar. - // To do: fix so Shift+Tab works. - // Press escape to show the block toolbar - await page.keyboard.press( 'Escape' ); - - await page.click( 'button[aria-label="Convert to ordered list"]' ); + await clickBlockToolbarButton( 'Convert to ordered list' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); diff --git a/packages/e2e-tests/specs/invalid-block.test.js b/packages/e2e-tests/specs/invalid-block.test.js index 803fed87b6446..007912aafe317 100644 --- a/packages/e2e-tests/specs/invalid-block.test.js +++ b/packages/e2e-tests/specs/invalid-block.test.js @@ -4,6 +4,7 @@ import { createNewPost, clickBlockAppender, + clickBlockToolbarButton, } from '@wordpress/e2e-test-utils'; describe( 'invalid blocks', () => { @@ -16,10 +17,7 @@ describe( 'invalid blocks', () => { await clickBlockAppender(); await page.keyboard.type( 'hello' ); - // Press escape to show the block toolbar - await page.keyboard.press( 'Escape' ); - // Click the 'more options' - await page.click( 'button[aria-label="More options"]' ); + await clickBlockToolbarButton( 'More options' ); // Change to HTML mode and close the options const changeModeButton = await page.waitForXPath( '//button[text()="Edit as HTML"]' ); diff --git a/packages/e2e-tests/specs/links.test.js b/packages/e2e-tests/specs/links.test.js index 39b6eca2f5810..b4be3dbfc53a6 100644 --- a/packages/e2e-tests/specs/links.test.js +++ b/packages/e2e-tests/specs/links.test.js @@ -3,6 +3,7 @@ */ import { clickBlockAppender, + clickBlockToolbarButton, getEditedPostContent, createNewPost, pressKeyWithModifier, @@ -219,10 +220,7 @@ describe( 'Links', () => { await clickBlockAppender(); await page.keyboard.type( 'Text' ); - // Press escape to show the block toolbar - await page.keyboard.press( 'Escape' ); - await page.waitForSelector( 'button[aria-label="Link"]' ); - await page.click( 'button[aria-label="Link"]' ); + await clickBlockToolbarButton( 'Link' ); // Typing "left" should not close the dialog await page.keyboard.press( 'ArrowLeft' ); diff --git a/packages/e2e-tests/specs/plugins/format-api.test.js b/packages/e2e-tests/specs/plugins/format-api.test.js index 203714863c083..25f56f5a39c77 100644 --- a/packages/e2e-tests/specs/plugins/format-api.test.js +++ b/packages/e2e-tests/specs/plugins/format-api.test.js @@ -4,6 +4,7 @@ import { activatePlugin, clickBlockAppender, + clickBlockToolbarButton, createNewPost, deactivatePlugin, getEditedPostContent, @@ -26,9 +27,7 @@ describe( 'Using Format API', () => { it( 'Format toolbar is present in a paragraph block', async () => { await clickBlockAppender(); await page.keyboard.type( 'First paragraph' ); - // Press escape to show the block toolbar - await page.keyboard.press( 'Escape' ); - expect( await page.$( '[aria-label="Custom Link"]' ) ).not.toBeNull(); + await clickBlockToolbarButton( 'Custom Link' ); } ); it( 'Clicking the control wraps the selected text properly with HTML code', async () => { @@ -36,9 +35,7 @@ describe( 'Using Format API', () => { await page.keyboard.type( 'First paragraph' ); await pressKeyWithModifier( 'shiftAlt', 'ArrowLeft' ); await pressKeyWithModifier( 'primary', 'A' ); - // Press escape to show the block toolbar - await page.keyboard.press( 'Escape' ); - await page.click( '[aria-label="Custom Link"]' ); + await clickBlockToolbarButton( 'Custom Link' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); } ); diff --git a/packages/e2e-tests/specs/reusable-blocks.test.js b/packages/e2e-tests/specs/reusable-blocks.test.js index a444a61cfbb58..982762b63dfec 100644 --- a/packages/e2e-tests/specs/reusable-blocks.test.js +++ b/packages/e2e-tests/specs/reusable-blocks.test.js @@ -4,6 +4,7 @@ import { insertBlock, createNewPost, + clickBlockToolbarButton, pressKeyWithModifier, searchForBlock, getEditedPostContent, @@ -34,12 +35,8 @@ describe( 'Reusable Blocks', () => { await insertBlock( 'Paragraph' ); await page.keyboard.type( 'Hello there!' ); - // Press escape to show the block toolbar - await page.keyboard.press( 'Escape' ); + await clickBlockToolbarButton( 'More options' ); - // Convert block to a reusable block - await page.waitForSelector( 'button[aria-label="More options"]' ); - await page.click( 'button[aria-label="More options"]' ); const convertButton = await page.waitForXPath( '//button[text()="Add to Reusable Blocks"]' ); await convertButton.click(); @@ -80,12 +77,8 @@ describe( 'Reusable Blocks', () => { await insertBlock( 'Paragraph' ); await page.keyboard.type( 'Hello there!' ); - // Press escape to show the block toolbar - await page.keyboard.press( 'Escape' ); + await clickBlockToolbarButton( 'More options' ); - // Convert block to a reusable block - await page.waitForSelector( 'button[aria-label="More options"]' ); - await page.click( 'button[aria-label="More options"]' ); const convertButton = await page.waitForXPath( '//button[text()="Add to Reusable Blocks"]' ); await convertButton.click(); diff --git a/packages/e2e-tests/specs/style-variation.test.js b/packages/e2e-tests/specs/style-variation.test.js index 2ec32efe89141..33ddcc3c2f809 100644 --- a/packages/e2e-tests/specs/style-variation.test.js +++ b/packages/e2e-tests/specs/style-variation.test.js @@ -1,7 +1,12 @@ /** * WordPress dependencies */ -import { createNewPost, insertBlock, getEditedPostContent } from '@wordpress/e2e-test-utils'; +import { + createNewPost, + clickBlockToolbarButton, + insertBlock, + getEditedPostContent, +} from '@wordpress/e2e-test-utils'; describe( 'adding blocks', () => { beforeAll( async () => { @@ -13,12 +18,8 @@ describe( 'adding blocks', () => { await insertBlock( 'Quote' ); await page.keyboard.type( 'Quote content' ); - // Press escape to show the block toolbar - await page.keyboard.press( 'Escape' ); + await clickBlockToolbarButton( 'Change block type' ); - // Use a different style variation - await page.waitForSelector( 'button[aria-label="Change block type"]' ); - await page.click( 'button[aria-label="Change block type"]' ); const styleVariations = await page.$$( '.editor-block-styles__item' ); await styleVariations[ 1 ].click(); From d3616912b53381ef038df92fe8fbe93afc3f462a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Wed, 6 Mar 2019 17:36:56 +0100 Subject: [PATCH 572/691] element: set up autogenerated API docs (#14269) --- bin/update-readmes.js | 1 + packages/element/README.md | 259 ++++++++++++++++++++++++++++++++-- packages/element/src/react.js | 6 + 3 files changed, 258 insertions(+), 8 deletions(-) diff --git a/bin/update-readmes.js b/bin/update-readmes.js index c094bc57a5b2b..c4bd256f0a8ed 100755 --- a/bin/update-readmes.js +++ b/bin/update-readmes.js @@ -5,6 +5,7 @@ const childProcess = require( 'child_process' ); const packages = [ 'e2e-test-utils', + 'element', ]; let aggregatedExitCode = 0; diff --git a/packages/element/README.md b/packages/element/README.md index cca88dff95240..ae28a29cf6ec2 100755 --- a/packages/element/README.md +++ b/packages/element/README.md @@ -4,14 +4,14 @@ Element is, quite simply, an abstraction layer atop [React](https://reactjs.org/ You may find yourself asking, "Why an abstraction layer?". For a few reasons: -- In many applications, especially those extended by a rich plugin ecosystem as is the case with WordPress, it's wise to create interfaces to underlying third-party code. The thinking is that if ever a need arises to change or even replace the underlying implementation, it can be done without catastrophic rippling effects to dependent code, so long as the interface stays the same. -- It provides a mechanism to shield implementers by omitting features with uncertain futures (`createClass`, `PropTypes`). -- It helps avoid incompatibilities between versions by ensuring that every plugin operates on a single centralized version of the code. +- In many applications, especially those extended by a rich plugin ecosystem as is the case with WordPress, it's wise to create interfaces to underlying third-party code. The thinking is that if ever a need arises to change or even replace the underlying implementation, it can be done without catastrophic rippling effects to dependent code, so long as the interface stays the same. +- It provides a mechanism to shield implementers by omitting features with uncertain futures (`createClass`, `PropTypes`). +- It helps avoid incompatibilities between versions by ensuring that every plugin operates on a single centralized version of the code. On the `wp.element` global object, you will find the following, ordered roughly by the likelihood you'll encounter it in your code: -- [`createElement`](https://reactjs.org/docs/react-api.html#createelement) -- [`render`](https://reactjs.org/docs/react-dom.html#render) +- [`createElement`](https://reactjs.org/docs/react-api.html#createelement) +- [`render`](https://reactjs.org/docs/react-dom.html#render) ## Installation @@ -49,10 +49,10 @@ Refer to the [official React Quick Start guide](https://reactjs.org/docs/hello-w At the risk of igniting debate surrounding any single "best" front-end framework, the choice to use any tool should be motivated specifically to serve the requirements of the system. In modeling the concept of a [block](/packages/blocks/README.md), we observe the following technical requirements: -- An understanding of a block in terms of its underlying values (in the [random image example](/packages/blocks/README.md#example), a category) -- A means to describe the UI of a block given these values +- An understanding of a block in terms of its underlying values (in the [random image example](/packages/blocks/README.md#example), a category) +- A means to describe the UI of a block given these values -At its most basic, React provides a simple input / output mechanism. __Given a set of inputs ("props"), a developer describes the output to be shown on the page.__ This is most elegantly observed in its [function components](https://reactjs.org/docs/components-and-props.html#functional-and-class-components). React serves the role of reconciling the desired output with the current state of the page. +At its most basic, React provides a simple input / output mechanism. **Given a set of inputs ("props"), a developer describes the output to be shown on the page.** This is most elegantly observed in its [function components](https://reactjs.org/docs/components-and-props.html#functional-and-class-components). React serves the role of reconciling the desired output with the current state of the page. The offerings of any framework necessarily become more complex as these requirements increase; many front-end frameworks prescribe ideas around page routing, retrieving and updating data, and managing layout. React is not immune to this, but the introduced complexity is rarely caused by React itself, but instead managing an arrangement of supporting tools. By moving these concerns out of sight to the internals of the system (WordPress core code), we can minimize the responsibilities of plugin authors to a small, clear set of touch points. @@ -74,4 +74,247 @@ If you've configured [Babel](http://babeljs.io/) for your project, you can opt i This assumes that you will import the `createElement` function in any file where you use JSX. Alternatively, consider using the [`@wordpress/babel-plugin-import-jsx-pragma` Babel plugin](https://www.npmjs.com/package/@wordpress/babel-plugin-import-jsx-pragma) to automate the import of this function. +## API + +<!-- START TOKEN(Autogenerated API docs) --> + +### Children + +[src/index.js#L1-L1](src/index.js#L1-L1) + +Object that provides utilities for dealing with React children. + +### cloneElement + +[src/index.js#L1-L1](src/index.js#L1-L1) + +Creates a copy of an element with extended props. + +**Parameters** + +- **element** `WPElement`: Element +- **props** `?Object`: Props to apply to cloned element + +**Returns** + +`WPElement`: Cloned element. + +### Component + +[src/index.js#L1-L1](src/index.js#L1-L1) + +A base class to create WordPress Components (Refs, state and lifecycle hooks) + +### concatChildren + +[src/index.js#L1-L1](src/index.js#L1-L1) + +Concatenate two or more React children objects. + +**Parameters** + +- **childrenArguments** `...?Object`: Array of children arguments (array of arrays/strings/objects) to concatenate. + +**Returns** + +`Array`: The concatenated value. + +### createContext + +[src/index.js#L1-L1](src/index.js#L1-L1) + +Creates a context object containing two components: a provider and consumer. + +**Parameters** + +- **defaultValue** `Object`: A default data stored in the context. + +**Returns** + +`Object`: Context object. + +### createElement + +[src/index.js#L1-L1](src/index.js#L1-L1) + +Returns a new element of given type. Type can be either a string tag name or +another function which itself returns an element. + +**Parameters** + +- **type** `?(string|Function)`: Tag name or element creator +- **props** `Object`: Element properties, either attribute set to apply to DOM node or values to pass through to element creator +- **children** `...WPElement`: Descendant elements + +**Returns** + +`WPElement`: Element. + +### createPortal + +[src/index.js#L2-L2](src/index.js#L2-L2) + +Creates a portal into which a component can be rendered. + +**Related** + +- <https://github.com/facebook/react/issues/10309#issuecomment-318433235> + +**Parameters** + +- **component** `Component`: Component +- **target** `Element`: DOM node into which element should be rendered + +### createRef + +[src/index.js#L1-L1](src/index.js#L1-L1) + +Returns an object tracking a reference to a rendered element via its +`current` property as either a DOMElement or Element, dependent upon the +type of element rendered with the ref attribute. + +**Returns** + +`Object`: Ref object. + +### findDOMNode + +[src/index.js#L2-L2](src/index.js#L2-L2) + +Finds the dom node of a React component + +**Parameters** + +- **component** `Component`: component's instance +- **target** `Element`: DOM node into which element should be rendered + +### forwardRef + +[src/index.js#L1-L1](src/index.js#L1-L1) + +Component enhancer used to enable passing a ref to its wrapped component. +Pass a function argument which receives `props` and `ref` as its arguments, +returning an element using the forwarded ref. The return value is a new +component which forwards its ref. + +**Parameters** + +- **forwarder** `Function`: Function passed `props` and `ref`, expected to return an element. + +**Returns** + +`WPComponent`: Enhanced component. + +### Fragment + +[src/index.js#L1-L1](src/index.js#L1-L1) + +A component which renders its children without any wrapping element. + +### isEmptyElement + +[src/index.js#L3-L3](src/index.js#L3-L3) + +Checks if the provided WP element is empty. + +**Parameters** + +- **element** `*`: WP element to check. + +**Returns** + +`boolean`: True when an element is considered empty. + +### isValidElement + +[src/index.js#L1-L1](src/index.js#L1-L1) + +Checks if an object is a valid WPElement + +**Parameters** + +- **objectToCheck** `Object`: The object to be checked. + +**Returns** + +`boolean`: true if objectToTest is a valid WPElement and false otherwise. + +### RawHTML + +[src/index.js#L5-L5](src/index.js#L5-L5) + +Component used as equivalent of Fragment with unescaped HTML, in cases where +it is desirable to render dangerous HTML without needing a wrapper element. +To preserve additional props, a `div` wrapper _will_ be created if any props +aside from `children` are passed. + +**Parameters** + +- **props.children** `string`: HTML to render. + +**Returns** + +`WPElement`: Dangerously-rendering element. + +### render + +[src/index.js#L2-L2](src/index.js#L2-L2) + +Renders a given element into the target DOM node. + +**Parameters** + +- **element** `WPElement`: Element to render +- **target** `Element`: DOM node into which element should be rendered + +### renderToString + +[src/index.js#L4-L4](src/index.js#L4-L4) + +Serializes a React element to string. + +**Parameters** + +- **element** `WPElement`: Element to serialize. +- **context** `?Object`: Context object. +- **legacyContext** `?Object`: Legacy context object. + +**Returns** + +`string`: Serialized element. + +### StrictMode + +[src/index.js#L1-L1](src/index.js#L1-L1) + +Component that activates additional checks and warnings for its descendants. + +### switchChildrenNodeName + +[src/index.js#L1-L1](src/index.js#L1-L1) + +Switches the nodeName of all the elements in the children object. + +**Parameters** + +- **children** `?Object`: Children object. +- **nodeName** `string`: Node name. + +**Returns** + +`?Object`: The updated children object. + +### unmountComponentAtNode + +[src/index.js#L2-L2](src/index.js#L2-L2) + +Removes any mounted element from the target DOM node. + +**Parameters** + +- **target** `Element`: DOM node in which element is to be removed + + +<!-- END TOKEN(Autogenerated API docs) --> + <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/element/src/react.js b/packages/element/src/react.js index 4d42fae21c1f7..703a0455d0a16 100644 --- a/packages/element/src/react.js +++ b/packages/element/src/react.js @@ -15,6 +15,9 @@ import { } from 'react'; import { isString } from 'lodash'; +/** + * Object that provides utilities for dealing with React children. + */ export { Children }; /** @@ -91,6 +94,9 @@ export { Fragment }; */ export { isValidElement }; +/** + * Component that activates additional checks and warnings for its descendants. + */ export { StrictMode }; /** From fa4874ea152f7a62ad50c6594cb7e56856df11a7 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Wed, 6 Mar 2019 11:42:05 -0500 Subject: [PATCH 573/691] Testing: Trash existing posts as admin user (#14244) --- packages/e2e-tests/config/setup-test-framework.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/e2e-tests/config/setup-test-framework.js b/packages/e2e-tests/config/setup-test-framework.js index a3129c4d82c48..4800da23a860f 100644 --- a/packages/e2e-tests/config/setup-test-framework.js +++ b/packages/e2e-tests/config/setup-test-framework.js @@ -13,6 +13,8 @@ import { setBrowserViewport, visitAdminPage, activatePlugin, + switchUserToAdmin, + switchUserToTest, } from '@wordpress/e2e-test-utils'; /** @@ -54,6 +56,7 @@ async function setupBrowser() { * @return {Promise} Promise resolving once posts have been trashed. */ async function trashExistingPosts() { + await switchUserToAdmin(); // Visit `/wp-admin/edit.php` so we can see a list of posts and delete them. await visitAdminPage( 'edit.php' ); @@ -70,9 +73,10 @@ async function trashExistingPosts() { await page.select( '#bulk-action-selector-top', 'trash' ); // Submit the form to send all draft/scheduled/published posts to the trash. await page.click( '#doaction' ); - return page.waitForXPath( + await page.waitForXPath( '//*[contains(@class, "updated notice")]/p[contains(text(), "moved to the Trash.")]' ); + await switchUserToTest(); } /** From 52355c49c7e1bba2a0b538c92e4e1455ba11880a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Wed, 6 Mar 2019 18:46:02 +0100 Subject: [PATCH 574/691] escape-html: set up autogenerated API docs (#14268) --- bin/update-readmes.js | 27 ++++++++ packages/escape-html/README.md | 114 +++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) diff --git a/bin/update-readmes.js b/bin/update-readmes.js index c4bd256f0a8ed..ce9d702bbbba7 100755 --- a/bin/update-readmes.js +++ b/bin/update-readmes.js @@ -4,8 +4,34 @@ const path = require( 'path' ); const childProcess = require( 'child_process' ); const packages = [ + //'a11y', + //'autop', + //'blob', + //'block-editor', + //'block-library', + //'block-serialization-default-parser', + //'blocks', + //'compose', + //'data', + //'date', + //'deprecated', + //'dom', + //'dom-ready', 'e2e-test-utils', + //'edit-post', 'element', + 'escape-html', + //'html-entities', + //'i18n', + //'keycodes', + //'plugins', + //'priority-queue', + //'redux-routine', + //'rich-text', + //'shortcode', + //'url', + //'viewport', + //'wordcount', ]; let aggregatedExitCode = 0; @@ -14,6 +40,7 @@ packages.forEach( ( packageName ) => { `packages/${ packageName }/src/index.js`, `--output packages/${ packageName }/README.md`, '--to-token', + '--ignore "unstable|experimental"', ]; const pathToDocGen = path.join( __dirname, '..', 'node_modules', '.bin', 'docgen' ); const { status, stderr } = childProcess.spawnSync( diff --git a/packages/escape-html/README.md b/packages/escape-html/README.md index 310034d07cd8e..d144be1b7de9f 100644 --- a/packages/escape-html/README.md +++ b/packages/escape-html/README.md @@ -12,4 +12,118 @@ npm install @wordpress/escape-html _This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. Learn more about it in [Babel docs](https://babeljs.io/docs/en/next/caveats)._ +## API + +<!-- START TOKEN(Autogenerated API docs) --> + +### escapeAmpersand + +[src/index.js#L28-L30](src/index.js#L28-L30) + +Returns a string with ampersands escaped. Note that this is an imperfect +implementation, where only ampersands which do not appear as a pattern of +named, decimal, or hexadecimal character references are escaped. Invalid +named references (i.e. ambiguous ampersand) are are still permitted. + +**Related** + +- <https://w3c.github.io/html/syntax.html#character-references> +- <https://w3c.github.io/html/syntax.html#ambiguous-ampersand> +- <https://w3c.github.io/html/syntax.html#named-character-references> + +**Parameters** + +- **value** `string`: Original string. + +**Returns** + +`string`: Escaped string. + +### escapeAttribute + +[src/index.js#L66-L68](src/index.js#L66-L68) + +Returns an escaped attribute value. + +**Related** + +- <https://w3c.github.io/html/syntax.html#elements-attributes> + +"[...] the text cannot contain an ambiguous ampersand [...] must not contain +any literal U+0022 QUOTATION MARK characters (")" + +**Parameters** + +- **value** `string`: Attribute value. + +**Returns** + +`string`: Escaped attribute value. + +### escapeHTML + +[src/index.js#L82-L84](src/index.js#L82-L84) + +Returns an escaped HTML element value. + +**Related** + +- <https://w3c.github.io/html/syntax.html#writing-html-documents-elements> + +"the text must not contain the character U+003C LESS-THAN SIGN (\<) or an +ambiguous ampersand." + +**Parameters** + +- **value** `string`: Element value. + +**Returns** + +`string`: Escaped HTML element value. + +### escapeLessThan + +[src/index.js#L50-L52](src/index.js#L50-L52) + +Returns a string with less-than sign replaced. + +**Parameters** + +- **value** `string`: Original string. + +**Returns** + +`string`: Escaped string. + +### escapeQuotationMark + +[src/index.js#L39-L41](src/index.js#L39-L41) + +Returns a string with quotation marks replaced. + +**Parameters** + +- **value** `string`: Original string. + +**Returns** + +`string`: Escaped string. + +### isValidAttributeName + +[src/index.js#L93-L95](src/index.js#L93-L95) + +Returns true if the given attribute name is valid, or false otherwise. + +**Parameters** + +- **name** `string`: Attribute name to test. + +**Returns** + +`boolean`: Whether attribute is valid. + + +<!-- END TOKEN(Autogenerated API docs) --> + <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> From 7d986580b463f927c62f65209f850a61c54bc8bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Wed, 6 Mar 2019 19:04:42 +0100 Subject: [PATCH 575/691] html-entities: set up auto-generated API docs (#14267) --- bin/update-readmes.js | 2 +- packages/html-entities/README.md | 28 ++++++++++++++++++++++++++++ packages/html-entities/src/index.js | 13 +++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/bin/update-readmes.js b/bin/update-readmes.js index ce9d702bbbba7..038cfa34c6213 100755 --- a/bin/update-readmes.js +++ b/bin/update-readmes.js @@ -21,7 +21,7 @@ const packages = [ //'edit-post', 'element', 'escape-html', - //'html-entities', + 'html-entities', //'i18n', //'keycodes', //'plugins', diff --git a/packages/html-entities/README.md b/packages/html-entities/README.md index f86def2a5dcc9..2d13431fa13b4 100644 --- a/packages/html-entities/README.md +++ b/packages/html-entities/README.md @@ -12,4 +12,32 @@ npm install @wordpress/html-entities --save _This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. Learn more about it in [Babel docs](https://babeljs.io/docs/en/next/caveats)._ +## API + +<!-- START TOKEN(Autogenerated API docs) --> + +### decodeEntities + +[src/index.js#L16-L35](src/index.js#L16-L35) + +Decodes the HTML entities from a given string. + +**Usage** + +```js +const result = decodeEntities( '&aacute;' ); +console.log( result ); // result will be "á" +``` + +**Parameters** + +- **html** `string`: String that contain HTML entities. + +**Returns** + +`string`: The decoded string. + + +<!-- END TOKEN(Autogenerated API docs) --> + <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/html-entities/src/index.js b/packages/html-entities/src/index.js index 7fe93882223c5..15c04fe51268a 100644 --- a/packages/html-entities/src/index.js +++ b/packages/html-entities/src/index.js @@ -1,5 +1,18 @@ let _decodeTextArea; +/** + * Decodes the HTML entities from a given string. + * + * @param {string} html String that contain HTML entities. + * + * @example + * ```js + * const result = decodeEntities( '&aacute;' ); + * console.log( result ); // result will be "á" + * ``` + * + * @return {string} The decoded string. + */ export function decodeEntities( html ) { // not a string, or no entities to decode if ( 'string' !== typeof html || -1 === html.indexOf( '&' ) ) { From 078041bd1f78b688eac133fb17838704ca0bc53c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Wed, 6 Mar 2019 19:22:46 +0100 Subject: [PATCH 576/691] keycodes: set up auto-generated API docs (#14265) --- bin/update-readmes.js | 2 +- packages/keycodes/README.md | 169 +++++++++++++++++++++++++++++++++ packages/keycodes/src/index.js | 62 +++++++++++- 3 files changed, 230 insertions(+), 3 deletions(-) diff --git a/bin/update-readmes.js b/bin/update-readmes.js index 038cfa34c6213..f57bca0a0e089 100755 --- a/bin/update-readmes.js +++ b/bin/update-readmes.js @@ -23,7 +23,7 @@ const packages = [ 'escape-html', 'html-entities', //'i18n', - //'keycodes', + 'keycodes', //'plugins', //'priority-queue', //'redux-routine', diff --git a/packages/keycodes/README.md b/packages/keycodes/README.md index cb7f5cc6e1ebb..d99081ed5823c 100644 --- a/packages/keycodes/README.md +++ b/packages/keycodes/README.md @@ -34,4 +34,173 @@ onKeyDown( event ) { } ``` +## API + +<!-- START TOKEN(Autogenerated API docs) --> + +### ALT + +[src/index.js#L74-L74](src/index.js#L74-L74) + +Keycode for ALT key. + +### BACKSPACE + +[src/index.js#L30-L30](src/index.js#L30-L30) + +Keycode for BACKSPACE key. + +### COMMAND + +[src/index.js#L82-L82](src/index.js#L82-L82) + +Keycode for COMMAND/META key. + +### CTRL + +[src/index.js#L78-L78](src/index.js#L78-L78) + +Keycode for CTRL key. + +### DELETE + +[src/index.js#L66-L66](src/index.js#L66-L66) + +Keycode for DELETE key. + +### displayShortcut + +[src/index.js#L166-L168](src/index.js#L166-L168) + +An object that contains functions to display shortcuts. +E.g. displayShortcut.primary( 'm' ) will return '⌘M' on Mac. + +**Type** + +`Object` Keyed map of functions to display shortcuts. + +### displayShortcutList + +[src/index.js#L135-L158](src/index.js#L135-L158) + +Return an array of the parts of a keyboard shortcut chord for display +E.g displayShortcutList.primary( 'm' ) will return [ '⌘', 'M' ] on Mac. + +**Type** + +`Object` keyed map of functions to shortcut sequences + +### DOWN + +[src/index.js#L62-L62](src/index.js#L62-L62) + +Keycode for DOWN key. + +### ENTER + +[src/index.js#L38-L38](src/index.js#L38-L38) + +Keycode for ENTER key. + +### ESCAPE + +[src/index.js#L42-L42](src/index.js#L42-L42) + +Keycode for ESCAPE key. + +### F10 + +[src/index.js#L70-L70](src/index.js#L70-L70) + +Keycode for F10 key. + +### isKeyboardEvent + +[src/index.js#L204-L218](src/index.js#L204-L218) + +An object that contains functions to check if a keyboard event matches a +predefined shortcut combination. +E.g. isKeyboardEvent.primary( event, 'm' ) will return true if the event +signals pressing ⌘M. + +**Type** + +`Object` Keyed map of functions to match events. + +### LEFT + +[src/index.js#L50-L50](src/index.js#L50-L50) + +Keycode for LEFT key. + +### modifiers + +[src/index.js#L103-L114](src/index.js#L103-L114) + +Object that contains functions that return the available modifier +depending on platform. + +- `primary`: takes a isApple function as a parameter. +- `primaryShift`: takes a isApple function as a parameter. +- `primaryAlt`: takes a isApple function as a parameter. +- `secondary`: takes a isApple function as a parameter. +- `access`: takes a isApple function as a parameter. +- `ctrl` +- `alt` +- `ctrlShift` +- `shift` +- `shiftAlt` + +### rawShortcut + +[src/index.js#L123-L127](src/index.js#L123-L127) + +An object that contains functions to get raw shortcuts. +E.g. rawShortcut.primary( 'm' ) will return 'meta+m' on Mac. +These are intended for user with the KeyboardShortcuts component or TinyMCE. + +**Type** + +`Object` Keyed map of functions to raw shortcuts. + +### RIGHT + +[src/index.js#L58-L58](src/index.js#L58-L58) + +Keycode for RIGHT key. + +### SHIFT + +[src/index.js#L86-L86](src/index.js#L86-L86) + +Keycode for SHIFT key. + +### shortcutAriaLabel + +[src/index.js#L174-L194](src/index.js#L174-L194) + +An object that contains functions to return an aria label for a keyboard shortcut. +E.g. shortcutAriaLabel.primary( '.' ) will return 'Command + Period' on Mac. + +### SPACE + +[src/index.js#L46-L46](src/index.js#L46-L46) + +Keycode for SPACE key. + +### TAB + +[src/index.js#L34-L34](src/index.js#L34-L34) + +Keycode for TAB key. + +### UP + +[src/index.js#L54-L54](src/index.js#L54-L54) + +Keycode for UP key. + + +<!-- END TOKEN(Autogenerated API docs) --> + <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/keycodes/src/index.js b/packages/keycodes/src/index.js index 5465b9df19184..ea0047a6596f4 100644 --- a/packages/keycodes/src/index.js +++ b/packages/keycodes/src/index.js @@ -24,24 +24,82 @@ import { __ } from '@wordpress/i18n'; */ import { isAppleOS } from './platform'; +/** + * Keycode for BACKSPACE key. + */ export const BACKSPACE = 8; +/** + * Keycode for TAB key. + */ export const TAB = 9; +/** + * Keycode for ENTER key. + */ export const ENTER = 13; +/** + * Keycode for ESCAPE key. + */ export const ESCAPE = 27; +/** + * Keycode for SPACE key. + */ export const SPACE = 32; +/** + * Keycode for LEFT key. + */ export const LEFT = 37; +/** + * Keycode for UP key. + */ export const UP = 38; +/** + * Keycode for RIGHT key. + */ export const RIGHT = 39; +/** + * Keycode for DOWN key. + */ export const DOWN = 40; +/** + * Keycode for DELETE key. + */ export const DELETE = 46; - +/** + * Keycode for F10 key. + */ export const F10 = 121; - +/** + * Keycode for ALT key. + */ export const ALT = 'alt'; +/** + * Keycode for CTRL key. + */ export const CTRL = 'ctrl'; +/** + * Keycode for COMMAND/META key. + */ export const COMMAND = 'meta'; +/** + * Keycode for SHIFT key. + */ export const SHIFT = 'shift'; +/** + * Object that contains functions that return the available modifier + * depending on platform. + * + * - `primary`: takes a isApple function as a parameter. + * - `primaryShift`: takes a isApple function as a parameter. + * - `primaryAlt`: takes a isApple function as a parameter. + * - `secondary`: takes a isApple function as a parameter. + * - `access`: takes a isApple function as a parameter. + * - `ctrl` + * - `alt` + * - `ctrlShift` + * - `shift` + * - `shiftAlt` + */ export const modifiers = { primary: ( _isApple ) => _isApple() ? [ COMMAND ] : [ CTRL ], primaryShift: ( _isApple ) => _isApple() ? [ SHIFT, COMMAND ] : [ CTRL, SHIFT ], From 1ebd6b3ce90c3c82b66d05aa049ccf90ad5b5bd3 Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.p.richards@gmail.com> Date: Thu, 7 Mar 2019 09:13:07 +0800 Subject: [PATCH 577/691] =?UTF-8?q?Use=20currentColor=20for=20fill=20of=20?= =?UTF-8?q?placeholder=20icon=E2=80=94ensures=20icon=20contrasts=20with=20?= =?UTF-8?q?background=20color=20of=20block=20(#14257)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/components/src/placeholder/style.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/components/src/placeholder/style.scss b/packages/components/src/placeholder/style.scss index 14dbef2a0f4eb..c9779153c0415 100644 --- a/packages/components/src/placeholder/style.scss +++ b/packages/components/src/placeholder/style.scss @@ -28,6 +28,7 @@ .dashicon, .editor-block-icon { + fill: currentColor; margin-right: 1ch; } } From a273f7d0933362493d83579989f020f3fe7a5c63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Thu, 7 Mar 2019 08:59:56 +0100 Subject: [PATCH 578/691] a11y: set up auto-generated API docs (#14288) --- bin/update-readmes.js | 2 +- packages/a11y/README.md | 45 ++++++++++++++++++++++++++++---------- packages/a11y/src/index.js | 14 +++++++++++- 3 files changed, 48 insertions(+), 13 deletions(-) diff --git a/bin/update-readmes.js b/bin/update-readmes.js index f57bca0a0e089..9bb32bd7c9e08 100755 --- a/bin/update-readmes.js +++ b/bin/update-readmes.js @@ -4,7 +4,7 @@ const path = require( 'path' ); const childProcess = require( 'child_process' ); const packages = [ - //'a11y', + 'a11y', //'autop', //'blob', //'block-editor', diff --git a/packages/a11y/README.md b/packages/a11y/README.md index 37cd55e9c5d92..9ce27b254d5c9 100644 --- a/packages/a11y/README.md +++ b/packages/a11y/README.md @@ -12,15 +12,26 @@ npm install @wordpress/a11y --save _This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. Learn more about it in [Babel docs](https://babeljs.io/docs/en/next/caveats)._ -## speak +## API -Allows you to easily announce dynamic interface updates to screen readers using ARIA live regions. This module is inspired by the `speak` function in wp-a11y.js +<!-- START TOKEN(Autogenerated API docs) --> -### Usage +### setup -To make the `wp.a11y.speak` functionality more universally available, we've decided to create a dedicated JS module for it, called `speak`. Usage is very simple: +[src/index.js#L16-L26](src/index.js#L16-L26) -```JS +Create the live regions. + +### speak + +[src/index.js#L52-L66](src/index.js#L52-L66) + +Allows you to easily announce dynamic interface updates to screen readers using ARIA live regions. +This module is inspired by the `speak` function in wp-a11y.js + +**Usage** + +```js import { speak } from '@wordpress/a11y'; // For polite messages that shouldn't interrupt what screen readers are currently announcing. @@ -30,26 +41,38 @@ speak( 'The message you want to send to the ARIA live region' ); speak( 'The message you want to send to the ARIA live region', 'assertive' ); ``` +**Parameters** + +- **message** `string`: The message to be announced by Assistive Technologies. +- **ariaLive** `string`: Optional. The politeness level for aria-live. Possible values: polite or assertive. Default polite. + + +<!-- END TOKEN(Autogenerated API docs) --> + ### Background + For context I'll quote [this article on WordPress.org](https://make.wordpress.org/accessibility/2015/04/15/let-wordpress-speak-new-in-wordpress-4-2/) by [@joedolson](https://github.com/joedolson): > #### Why. +> > In modern web development, updating discrete regions of a screen with JavaScript is common. The use of AJAX responses in modern JavaScript-based User Interfaces allows web developers to create beautiful interfaces similar to Desktop applications that don’t require pages to reload or refresh. - +> > JavaScript can create great usability improvements for most users – but when content is updated dynamically, it has the potential to introduce accessibility issues. This is one of the steps you can take to handle that problem. - +> > #### What. +> > When a portion of a page is updated with JavaScript, the update is usually highlighted with animation and bright colors, and is easy to see. But if you don’t have the ability to see the screen, you don’t know this has happened, unless the updated region is marked as an ARIA-live region. - +> > If this isn’t marked, there’s no notification for screen readers. But it’s also possible that all the region content will be announced after an update, if the ARIA live region is too large. You want to provide users with just a simple, concise message. - +> > #### How. +> > That’s what `wp.a11y.speak()` is meant for. It’s a simple tool that creates and appends an ARIA live notifications area to the <body> element where developers can dispatch text messages. Assistive technologies will automatically announce any text change in this area. This ARIA live region has an ARIA role of “status” so it has an implicit aria-live value of polite and an implicit aria-atomic value of true. - +> > This means assistive technologies will notify users of updates but generally do not interrupt the current task, and updates take low priority. If you’re creating an application with higher priority updates (such as a notification that their current session is about to expire, for example), then you’ll want to create your own method with an aria-live value of assertive. ## Browser support -See https://make.wordpress.org/design/handbook/design-guide/browser-support/ +See <https://make.wordpress.org/design/handbook/design-guide/browser-support/> <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/a11y/src/index.js b/packages/a11y/src/index.js index e082e15ca6fb7..525ea63b87968 100644 --- a/packages/a11y/src/index.js +++ b/packages/a11y/src/index.js @@ -31,11 +31,23 @@ export const setup = function() { domReady( setup ); /** - * Update the ARIA live notification area text node. + * Allows you to easily announce dynamic interface updates to screen readers using ARIA live regions. + * This module is inspired by the `speak` function in wp-a11y.js * * @param {string} message The message to be announced by Assistive Technologies. * @param {string} ariaLive Optional. The politeness level for aria-live. Possible values: * polite or assertive. Default polite. + * + * @example + * ```js + * import { speak } from '@wordpress/a11y'; + * + * // For polite messages that shouldn't interrupt what screen readers are currently announcing. + * speak( 'The message you want to send to the ARIA live region' ); + * + * // For assertive messages that should interrupt what screen readers are currently announcing. + * speak( 'The message you want to send to the ARIA live region', 'assertive' ); + * ``` */ export const speak = function( message, ariaLive ) { // Clear previous messages to allow repeated strings being read out. From a2761c8c67de6b90db44d954ea8d85d886669d88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Thu, 7 Mar 2019 09:00:57 +0100 Subject: [PATCH 579/691] blob: set up auto-generated API docs (#14286) --- bin/update-readmes.js | 2 +- packages/blob/README.md | 61 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/bin/update-readmes.js b/bin/update-readmes.js index 9bb32bd7c9e08..91be89cd2912c 100755 --- a/bin/update-readmes.js +++ b/bin/update-readmes.js @@ -6,7 +6,7 @@ const childProcess = require( 'child_process' ); const packages = [ 'a11y', //'autop', - //'blob', + 'blob', //'block-editor', //'block-library', //'block-serialization-default-parser', diff --git a/packages/blob/README.md b/packages/blob/README.md index d8f1b1b359aa4..c0aed95f3b17b 100644 --- a/packages/blob/README.md +++ b/packages/blob/README.md @@ -10,4 +10,65 @@ Install the module npm install @wordpress/blob --save ``` +## API + +<!-- START TOKEN(Autogenerated API docs) --> + +### createBlobURL + +[src/index.js#L15-L21](src/index.js#L15-L21) + +Create a blob URL from a file. + +**Parameters** + +- **file** `File`: The file to create a blob URL for. + +**Returns** + +`string`: The blob URL. + +### getBlobByURL + +[src/index.js#L32-L34](src/index.js#L32-L34) + +Retrieve a file based on a blob URL. The file must have been created by +`createBlobURL` and not removed by `revokeBlobURL`, otherwise it will return +`undefined`. + +**Parameters** + +- **url** `string`: The blob URL. + +**Returns** + +`?File`: The file for the blob URL. + +### isBlobURL + +[src/index.js#L56-L61](src/index.js#L56-L61) + +Check whether a url is a blob url. + +**Parameters** + +- **url** `string`: The URL. + +**Returns** + +`boolean`: Is the url a blob url? + +### revokeBlobURL + +[src/index.js#L41-L47](src/index.js#L41-L47) + +Remove the resource and file cache from memory. + +**Parameters** + +- **url** `string`: The blob URL. + + +<!-- END TOKEN(Autogenerated API docs) --> + <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> From 6493a3ce2f7fbc26e17fa7c4ff63c105159c40e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Thu, 7 Mar 2019 09:03:33 +0100 Subject: [PATCH 580/691] block-library: set up auto-generated API docs (#14282) --- bin/update-readmes.js | 2 +- packages/block-library/README.md | 15 ++++++++++++++- packages/block-library/src/index.js | 10 ++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/bin/update-readmes.js b/bin/update-readmes.js index 91be89cd2912c..9e71248c08e06 100755 --- a/bin/update-readmes.js +++ b/bin/update-readmes.js @@ -8,7 +8,7 @@ const packages = [ //'autop', 'blob', //'block-editor', - //'block-library', + 'block-library', //'block-serialization-default-parser', //'blocks', //'compose', diff --git a/packages/block-library/README.md b/packages/block-library/README.md index d9f50a3432a55..f21ffbea41303 100644 --- a/packages/block-library/README.md +++ b/packages/block-library/README.md @@ -12,7 +12,17 @@ npm install @wordpress/block-library --save _This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. Learn more about it in [Babel docs](https://babeljs.io/docs/en/next/caveats)._ -## Usage +## API + +<!-- START TOKEN(Autogenerated API docs) --> + +### registerCoreBlocks + +[src/index.js#L69-L130](src/index.js#L69-L130) + +Function to register core blocks provided by the block editor. + +**Usage** ```js import { registerCoreBlocks } from '@wordpress/block-library'; @@ -20,4 +30,7 @@ import { registerCoreBlocks } from '@wordpress/block-library'; registerCoreBlocks(); ``` + +<!-- END TOKEN(Autogenerated API docs) --> + <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/block-library/src/index.js b/packages/block-library/src/index.js index eb6cd561d887a..1fc00265bc875 100644 --- a/packages/block-library/src/index.js +++ b/packages/block-library/src/index.js @@ -56,6 +56,16 @@ import * as tagCloud from './tag-cloud'; import * as classic from './classic'; +/** + * Function to register core blocks provided by the block editor. + * + * @example + * ```js + * import { registerCoreBlocks } from '@wordpress/block-library'; + * + * registerCoreBlocks(); + * ``` + */ export const registerCoreBlocks = () => { [ // Common blocks are grouped at the top to prioritize their display From 88751b1e18c856c62b9a0575c8239c053df6a86b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Thu, 7 Mar 2019 09:04:32 +0100 Subject: [PATCH 581/691] compose: set up auto-generated API docs (#14278) --- bin/update-readmes.js | 2 +- packages/compose/README.md | 128 +++++++++++++++++++++++++++++++++---- 2 files changed, 118 insertions(+), 12 deletions(-) diff --git a/bin/update-readmes.js b/bin/update-readmes.js index 9e71248c08e06..8fe454956da43 100755 --- a/bin/update-readmes.js +++ b/bin/update-readmes.js @@ -11,7 +11,7 @@ const packages = [ 'block-library', //'block-serialization-default-parser', //'blocks', - //'compose', + 'compose', //'data', //'date', //'deprecated', diff --git a/packages/compose/README.md b/packages/compose/README.md index f0f8e25c14f38..cbb4be0f20642 100644 --- a/packages/compose/README.md +++ b/packages/compose/README.md @@ -47,7 +47,7 @@ export default withPluginContext( ) ) ); - +``` ## Installation @@ -59,20 +59,126 @@ npm install @wordpress/compose --save _This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. Learn more about it in [Babel docs](https://babeljs.io/docs/en/next/caveats)._ -## Usage +## API -An example using the HOC `withInstanceId` from the compose package: +For more details, you can refer to each Higher Order Component's README file. [Available components are located here.](https://github.com/WordPress/gutenberg/tree/master/packages/compose/src) -```js -import { withInstanceId } from '@wordpress/compose'; +<!-- START TOKEN(Autogenerated API docs) --> -function WrappedComponent( props ) { - return props.instanceId; -} +### compose -const ComponentWithInstanceIdProp = withInstanceId( WrappedComponent ); -``` +[src/index.js#L22-L22](src/index.js#L22-L22) -For more details, you can refer to each Higher Order Component's README file. [Available components are located here.](https://github.com/WordPress/gutenberg/tree/master/packages/compose/src) +Composes multiple higher-order components into a single higher-order component. Performs right-to-left function +composition, where each successive invocation is supplied the return value of the previous. + +**Parameters** + +- **hocs** `...Function`: The HOC functions to invoke. + +**Returns** + +`Function`: Returns the new composite function. + +### createHigherOrderComponent + +[src/index.js#L6-L6](src/index.js#L6-L6) + +Given a function mapping a component to an enhanced component and modifier +name, returns the enhanced component augmented with a generated displayName. + +**Parameters** + +- **mapComponentToEnhancedComponent** `Function`: Function mapping component to enhanced component. +- **modifierName** `string`: Seed name from which to generated display name. + +**Returns** + +`WPComponent`: Component class with generated display name assigned. + +### ifCondition + +[src/index.js#L7-L7](src/index.js#L7-L7) + +Higher-order component creator, creating a new component which renders if +the given condition is satisfied or with the given optional prop name. + +**Parameters** + +- **predicate** `Function`: Function to test condition. + +**Returns** + +`Function`: Higher-order component. + +### pure + +[src/index.js#L8-L8](src/index.js#L8-L8) + +Given a component returns the enhanced component augmented with a component +only rerendering when its props/state change + +**Parameters** + +- **mapComponentToEnhancedComponent** `Function`: Function mapping component to enhanced component. +- **modifierName** `string`: Seed name from which to generated display name. + +**Returns** + +`WPComponent`: Component class with generated display name assigned. + +### withGlobalEvents + +[src/index.js#L9-L9](src/index.js#L9-L9) + +Undocumented declaration. + +### withInstanceId + +[src/index.js#L10-L10](src/index.js#L10-L10) + +A Higher Order Component used to be provide a unique instance ID by +component. + +**Parameters** + +- **WrappedComponent** `WPElement`: The wrapped component. + +**Returns** + +`Component`: Component with an instanceId prop. + +### withSafeTimeout + +[src/index.js#L11-L11](src/index.js#L11-L11) + +A higher-order component used to provide and manage delayed function calls +that ought to be bound to a component's lifecycle. + +**Parameters** + +- **OriginalComponent** `Component`: Component requiring setTimeout + +**Returns** + +`Component`: Wrapped component. + +### withState + +[src/index.js#L12-L12](src/index.js#L12-L12) + +A Higher Order Component used to provide and manage internal component state +via props. + +**Parameters** + +- **initialState** `?Object`: Optional initial state of the component. + +**Returns** + +`Component`: Wrapped component. + + +<!-- END TOKEN(Autogenerated API docs) --> <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> From b674ec433d40a02181dd59a723c6ba0fd57e6bb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Thu, 7 Mar 2019 09:05:20 +0100 Subject: [PATCH 582/691] dom: set up auto-generated API docs (#14273) --- bin/update-readmes.js | 2 +- packages/dom/README.md | 258 ++++++++++++++++++++++++++++++++++++++ packages/dom/src/index.js | 4 + 3 files changed, 263 insertions(+), 1 deletion(-) diff --git a/bin/update-readmes.js b/bin/update-readmes.js index 8fe454956da43..af7a9b622d8f7 100755 --- a/bin/update-readmes.js +++ b/bin/update-readmes.js @@ -15,7 +15,7 @@ const packages = [ //'data', //'date', //'deprecated', - //'dom', + 'dom', //'dom-ready', 'e2e-test-utils', //'edit-post', diff --git a/packages/dom/README.md b/packages/dom/README.md index f5e890dd7d06e..bf572a4741b50 100644 --- a/packages/dom/README.md +++ b/packages/dom/README.md @@ -10,4 +10,262 @@ Install the module npm install @wordpress/dom --save ``` +## API + +<!-- START TOKEN(Autogenerated API docs) --> + +### computeCaretRect + +[src/index.js#L13-L13](src/index.js#L13-L13) + +Get the rectangle for the selection in a container. + +**Parameters** + +- **container** `Element`: Editable container. + +**Returns** + +`?DOMRect`: The rectangle. + +### documentHasSelection + +[src/index.js#L13-L13](src/index.js#L13-L13) + +Check wether the current document has a selection. +This checks both for focus in an input field and general text selection. + +**Returns** + +`boolean`: True if there is selection, false if not. + +### focus + +[src/index.js#L11-L11](src/index.js#L11-L11) + +Object grouping `focusable` and `tabbable` utils +under the keys with the same name. + +### getOffsetParent + +[src/index.js#L13-L13](src/index.js#L13-L13) + +Returns the closest positioned element, or null under any of the conditions +of the offsetParent specification. Unlike offsetParent, this function is not +limited to HTMLElement and accepts any Node (e.g. Node.TEXT_NODE). + +**Related** + +- <https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsetparent> + +**Parameters** + +- **node** `Node`: Node from which to find offset parent. + +**Returns** + +`?Node`: Offset parent. + +### getRectangleFromRange + +[src/index.js#L13-L13](src/index.js#L13-L13) + +Get the rectangle of a given Range. + +**Parameters** + +- **range** `Range`: The range. + +**Returns** + +`DOMRect`: The rectangle. + +### getScrollContainer + +[src/index.js#L13-L13](src/index.js#L13-L13) + +Given a DOM node, finds the closest scrollable container node. + +**Parameters** + +- **node** `Element`: Node from which to start. + +**Returns** + +`?Element`: Scrollable container node, if found. + +### insertAfter + +[src/index.js#L13-L13](src/index.js#L13-L13) + +Given two DOM nodes, inserts the former in the DOM as the next sibling of +the latter. + +**Parameters** + +- **newNode** `Element`: Node to be inserted. +- **referenceNode** `Element`: Node after which to perform the insertion. + +**Returns** + +`void`: + +### isEntirelySelected + +[src/index.js#L13-L13](src/index.js#L13-L13) + +Check whether the contents of the element have been entirely selected. +Returns true if there is no possibility of selection. + +**Parameters** + +- **element** `Element`: The element to check. + +**Returns** + +`boolean`: True if entirely selected, false if not. + +### isHorizontalEdge + +[src/index.js#L13-L13](src/index.js#L13-L13) + +Check whether the selection is horizontally at the edge of the container. + +**Parameters** + +- **container** `Element`: Focusable element. +- **isReverse** `boolean`: Set to true to check left, false for right. + +**Returns** + +`boolean`: True if at the horizontal edge, false if not. + +### isTextField + +[src/index.js#L13-L13](src/index.js#L13-L13) + +Check whether the given element is a text field, where text field is defined +by the ability to select within the input, or that it is contenteditable. + +See: <https://html.spec.whatwg.org/#textFieldSelection> + +**Parameters** + +- **element** `HTMLElement`: The HTML element. + +**Returns** + +`boolean`: True if the element is an text field, false if not. + +### isVerticalEdge + +[src/index.js#L13-L13](src/index.js#L13-L13) + +Check whether the selection is vertically at the edge of the container. + +**Parameters** + +- **container** `Element`: Focusable element. +- **isReverse** `boolean`: Set to true to check top, false for bottom. + +**Returns** + +`boolean`: True if at the edge, false if not. + +### placeCaretAtHorizontalEdge + +[src/index.js#L13-L13](src/index.js#L13-L13) + +Places the caret at start or end of a given element. + +**Parameters** + +- **container** `Element`: Focusable element. +- **isReverse** `boolean`: True for end, false for start. + +### placeCaretAtVerticalEdge + +[src/index.js#L13-L13](src/index.js#L13-L13) + +Places the caret at the top or bottom of a given element. + +**Parameters** + +- **container** `Element`: Focusable element. +- **isReverse** `boolean`: True for bottom, false for top. +- **rect** `[DOMRect]`: The rectangle to position the caret with. +- **mayUseScroll** `[boolean]`: True to allow scrolling, false to disallow. + +### remove + +[src/index.js#L13-L13](src/index.js#L13-L13) + +Given a DOM node, removes it from the DOM. + +**Parameters** + +- **node** `Element`: Node to be removed. + +**Returns** + +`void`: + +### replace + +[src/index.js#L13-L13](src/index.js#L13-L13) + +Given two DOM nodes, replaces the former with the latter in the DOM. + +**Parameters** + +- **processedNode** `Element`: Node to be removed. +- **newNode** `Element`: Node to be inserted in its place. + +**Returns** + +`void`: + +### replaceTag + +[src/index.js#L13-L13](src/index.js#L13-L13) + +Replaces the given node with a new node with the given tag name. + +**Parameters** + +- **node** `Element`: The node to replace +- **tagName** `string`: The new tag name. + +**Returns** + +`Element`: The new node. + +### unwrap + +[src/index.js#L13-L13](src/index.js#L13-L13) + +Unwrap the given node. This means any child nodes are moved to the parent. + +**Parameters** + +- **node** `Node`: The node to unwrap. + +**Returns** + +`void`: + +### wrap + +[src/index.js#L13-L13](src/index.js#L13-L13) + +Wraps the given node with a new node with the given tag name. + +**Parameters** + +- **newNode** `Element`: The node to insert. +- **referenceNode** `Element`: The node to wrap. + + +<!-- END TOKEN(Autogenerated API docs) --> + <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/dom/src/index.js b/packages/dom/src/index.js index 9fde374193ad5..7d514005846c2 100644 --- a/packages/dom/src/index.js +++ b/packages/dom/src/index.js @@ -4,6 +4,10 @@ import * as focusable from './focusable'; import * as tabbable from './tabbable'; +/** + * Object grouping `focusable` and `tabbable` utils + * under the keys with the same name. + */ export const focus = { focusable, tabbable }; export * from './dom'; From f9349318dc49b8c247345d692f5580594b8f887f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Thu, 7 Mar 2019 09:06:26 +0100 Subject: [PATCH 583/691] i18n: set up auto-generated API docs (#14266) --- bin/update-readmes.js | 2 +- packages/i18n/README.md | 125 ++++++++++++++++++++++++++++++++----- packages/i18n/src/index.js | 2 +- 3 files changed, 110 insertions(+), 19 deletions(-) diff --git a/bin/update-readmes.js b/bin/update-readmes.js index af7a9b622d8f7..98c831386475d 100755 --- a/bin/update-readmes.js +++ b/bin/update-readmes.js @@ -22,7 +22,7 @@ const packages = [ 'element', 'escape-html', 'html-entities', - //'i18n', + 'i18n', 'keycodes', //'plugins', //'priority-queue', diff --git a/packages/i18n/README.md b/packages/i18n/README.md index 5e57b0f4cb806..6779e866f2453 100644 --- a/packages/i18n/README.md +++ b/packages/i18n/README.md @@ -2,7 +2,7 @@ Internationalization utilities for client-side localization. -https://codex.wordpress.org/I18n_for_WordPress_Developers +<https://codex.wordpress.org/I18n_for_WordPress_Developers> ## Installation @@ -37,38 +37,129 @@ npx pot-to-php languages/myplugin.pot languages/myplugin-translations.php text-d ## API -`__( text: string, domain: string ): string` +<!-- START TOKEN(Autogenerated API docs) --> -Retrieve the translation of text. +### setLocaleData + +[src/index.js#L45-L58](src/index.js#L45-L58) + +Merges locale data into the Tannin instance by domain. Accepts data in a +Jed-formatted JSON object shape. + +**Related** + +- <http://messageformat.github.io/Jed/> + +**Parameters** + +- **data** `?Object`: Locale data configuration. +- **domain** `?string`: Domain for which configuration applies. + +### sprintf + +[src/index.js#L159-L167](src/index.js#L159-L167) + +Returns a formatted string. If an error occurs in applying the format, the +original format string is returned. + +**Related** + +- <http://www.diveintojavascript.com/projects/javascript-sprintf> + +**Parameters** + +- **format** `string`: The format of the string to generate. +- **args** `...string`: Arguments to apply to the format. + +**Returns** + +`string`: The formatted string. + +### \_n + +[src/index.js#L125-L127](src/index.js#L125-L127) + +Translates and retrieves the singular or plural form based on the supplied +number. + +**Related** -See: https://developer.wordpress.org/reference/functions/__/ +- <https://developer.wordpress.org/reference/functions/_n/> -`_x( text: string, context: string, domain: string ): string` +**Parameters** + +- **single** `string`: The text to be used if the number is singular. +- **plural** `string`: The text to be used if the number is plural. +- **number** `number`: The number to compare against to use either the singular or plural form. +- **domain** `?string`: Domain to retrieve the translated text. + +**Returns** + +`string`: The translated singular or plural form. + +### \_nx + +[src/index.js#L144-L146](src/index.js#L144-L146) + +Translates and retrieves the singular or plural form based on the supplied +number, with gettext context. + +**Related** + +- <https://developer.wordpress.org/reference/functions/_nx/> + +**Parameters** + +- **single** `string`: The text to be used if the number is singular. +- **plural** `string`: The text to be used if the number is plural. +- **number** `number`: The number to compare against to use either the singular or plural form. +- **context** `string`: Context information for the translators. +- **domain** `?string`: Domain to retrieve the translated text. + +**Returns** + +`string`: The translated singular or plural form. + +### \_x + +[src/index.js#L107-L109](src/index.js#L107-L109) Retrieve translated string with gettext context. -See: https://developer.wordpress.org/reference/functions/_x/ +**Related** + +- <https://developer.wordpress.org/reference/functions/_x/> -`_n( single: string, plural: string, number: Number, domain: string ): string` +**Parameters** -Translates and retrieves the singular or plural form based on the supplied number. +- **text** `string`: Text to translate. +- **context** `string`: Context information for the translators. +- **domain** `?string`: Domain to retrieve the translated text. -See: https://developer.wordpress.org/reference/functions/_n/ +**Returns** + +`string`: Translated context string without pipe. + +### \_\_ + +[src/index.js#L92-L94](src/index.js#L92-L94) + +Retrieve the translation of text. -`_nx( single: string, plural: string, number: Number, context: string, domain: string ): string` +**Related** -Translates and retrieves the singular or plural form based on the supplied number, with gettext context. +- <https://developer.wordpress.org/reference/functions/__/> -See: https://developer.wordpress.org/reference/functions/_nx/ +**Parameters** -`sprintf( format: string, ...args: mixed[] ): string` +- **text** `string`: Text to translate. +- **domain** `?string`: Domain to retrieve the translated text. -Returns a formatted string. +**Returns** -See: http://www.diveintojavascript.com/projects/javascript-sprintf +`string`: Translated text. -`setLocaleData( data: Object, domain: string )` -Creates a new Jed instance with specified locale data configuration. +<!-- END TOKEN(Autogenerated API docs) --> <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/i18n/src/index.js b/packages/i18n/src/index.js index 42266da0c9ff9..50869f49efd39 100644 --- a/packages/i18n/src/index.js +++ b/packages/i18n/src/index.js @@ -150,7 +150,7 @@ export function _nx( single, plural, number, context, domain ) { * original format string is returned. * * @param {string} format The format of the string to generate. - * @param {string[]} ...args Arguments to apply to the format. + * @param {...string} args Arguments to apply to the format. * * @see http://www.diveintojavascript.com/projects/javascript-sprintf * From 6ead0992ddf21ad1a8c29720d6ff00191f300cdc Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Thu, 7 Mar 2019 09:22:37 +0100 Subject: [PATCH 584/691] Clarify that we should rebase the branch when preparing the npm release branches (#14260) --- docs/contributors/release.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/contributors/release.md b/docs/contributors/release.md index fb31bbb4d6a25..d19f1664dc12f 100644 --- a/docs/contributors/release.md +++ b/docs/contributors/release.md @@ -200,7 +200,7 @@ For each Gutenberg plugin release, WordPress trunk should be synchronized with t 1. Ensure the WordPress `trunk` branch is open for enhancements. 2. Check out the last published Gutenberg release branch `git checkout release/x.x` 3. Create a Pull Request from this branch targetting `wp/trunk`. -4. Merge the Pull Request. +4. Merge the Pull Request using the "Rebase and Merge" button to keep the history of the commits. Now, the branch is ready to be used to publish the npm packages. @@ -222,7 +222,7 @@ The following workflow is needed when bug fixes or security releases need to be 1. Cherry-pick 2. Check out the last published Gutenberg release branch `git checkout release/x.x` 3. Create a Pull Request from this branch targetting the WordPress related major branch (Example `wp/5.2`). -4. Merge the Pull Request. +4. Merge the Pull Request using the "Rebase and Merge" button to keep the history of the commits. Now, the branch is ready to be used to publish the npm packages. From 032d96e2b077095b941ef8d9e6ad727dfa72f67b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Thu, 7 Mar 2019 09:35:40 +0100 Subject: [PATCH 585/691] autop: set up auto-generated API docs (#14287) --- bin/update-readmes.js | 2 +- packages/autop/README.md | 58 ++++++++++++++++++++++++++++++------- packages/autop/src/index.js | 14 +++++++++ 3 files changed, 63 insertions(+), 11 deletions(-) diff --git a/bin/update-readmes.js b/bin/update-readmes.js index 98c831386475d..da84d066eef75 100755 --- a/bin/update-readmes.js +++ b/bin/update-readmes.js @@ -5,7 +5,7 @@ const childProcess = require( 'child_process' ); const packages = [ 'a11y', - //'autop', + 'autop', 'blob', //'block-editor', 'block-library', diff --git a/packages/autop/README.md b/packages/autop/README.md index 4aecd9cafa3de..ffb9168b63f20 100644 --- a/packages/autop/README.md +++ b/packages/autop/README.md @@ -12,23 +12,61 @@ npm install @wordpress/autop --save _This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. Learn more about it in [Babel docs](https://babeljs.io/docs/en/next/caveats)._ -### Usage +### API -Import the desired function(s) from `@wordpress/autop`: +<!-- START TOKEN(Autogenerated API docs) --> + +#### autop + +[src/index.js#L129-L285](src/index.js#L129-L285) + +Replaces double line-breaks with paragraph elements. + +A group of regex replaces used to identify text formatted with newlines and +replace double line-breaks with HTML paragraph tags. The remaining line- +breaks after conversion become `<br />` tags, unless br is set to 'false'. + +**Usage** ```js -import { autop, removep } from '@wordpress/autop'; +import { autop } from '@wordpress/autop'; +autop( 'my text' ); // "<p>my text</p>" +``` + +**Parameters** + +- **text** `string`: The text which has to be formatted. +- **br** `boolean`: Optional. If set, will convert all remaining line- breaks after paragraphing. Default true. + +**Returns** + +`string`: Text which has been converted into paragraph tags. -autop( 'my text' ); -// "<p>my text</p>" +#### removep -removep( '<p>my text</p>' ); -// "my text" +[src/index.js#L303-L426](src/index.js#L303-L426) + +Replaces `<p>` tags with two line breaks. "Opposite" of autop(). + +Replaces `<p>` tags with two line breaks except where the `<p>` has attributes. +Unifies whitespace. Indents `<li>`, `<dt>` and `<dd>` for better readability. + +**Usage** + +```js +import { removep } from '@wordpress/autop'; +removep( '<p>my text</p>' ); // "my text" ``` -### API Usage +**Parameters** + +- **html** `string`: The content from the editor. + +**Returns** + +`string`: The content with stripped paragraph tags. + -* `autop( text: string ): string` -* `removep( text: string ): string` +<!-- END TOKEN(Autogenerated API docs) --> <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/autop/src/index.js b/packages/autop/src/index.js index 916f56a4be5f2..809a66b412d69 100644 --- a/packages/autop/src/index.js +++ b/packages/autop/src/index.js @@ -117,6 +117,13 @@ function replaceInHtmlTags( haystack, replacePairs ) { * @param {string} text The text which has to be formatted. * @param {boolean} br Optional. If set, will convert all remaining line- * breaks after paragraphing. Default true. + * + * @example + *```js + * import { autop } from '@wordpress/autop'; + * autop( 'my text' ); // "<p>my text</p>" + * ``` + * * @return {string} Text which has been converted into paragraph tags. */ export function autop( text, br = true ) { @@ -284,6 +291,13 @@ export function autop( text, br = true ) { * Unifies whitespace. Indents `<li>`, `<dt>` and `<dd>` for better readability. * * @param {string} html The content from the editor. + * + * @example + * ```js + * import { removep } from '@wordpress/autop'; + * removep( '<p>my text</p>' ); // "my text" + * ``` + * * @return {string} The content with stripped paragraph tags. */ export function removep( html ) { From 55d3cff3004c0ab7f16024f45d49d1c7f67ddd92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Thu, 7 Mar 2019 09:54:46 +0100 Subject: [PATCH 586/691] dom-ready: set up autogenerated API docs (#14272) --- bin/update-readmes.js | 2 +- packages/dom-ready/README.md | 27 ++++++++++++++++++++++++--- packages/dom-ready/src/index.js | 9 +++++++++ 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/bin/update-readmes.js b/bin/update-readmes.js index da84d066eef75..f40eee3d8129e 100755 --- a/bin/update-readmes.js +++ b/bin/update-readmes.js @@ -16,7 +16,7 @@ const packages = [ //'date', //'deprecated', 'dom', - //'dom-ready', + 'dom-ready', 'e2e-test-utils', //'edit-post', 'element', diff --git a/packages/dom-ready/README.md b/packages/dom-ready/README.md index c262e04a93a62..0c8515e57f3b1 100644 --- a/packages/dom-ready/README.md +++ b/packages/dom-ready/README.md @@ -12,9 +12,19 @@ npm install @wordpress/dom-ready --save _This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. Learn more about it in [Babel docs](https://babeljs.io/docs/en/next/caveats)._ -### Usage +## API -```JS +<!-- START TOKEN(Autogenerated API docs) --> + +### default + +[src/index.js#L29-L29](src/index.js#L29-L29) + +Specify a function to execute when the DOM is fully loaded. + +**Usage** + +```js import domReady from '@wordpress/dom-ready'; domReady( function() { @@ -22,8 +32,19 @@ domReady( function() { } ); ``` +**Parameters** + +- **callback** `Function`: A function to execute after the DOM is ready. + +**Returns** + +`void`: + + +<!-- END TOKEN(Autogenerated API docs) --> + ## Browser support -See https://make.wordpress.org/design/handbook/design-guide/browser-support/ +See <https://make.wordpress.org/design/handbook/design-guide/browser-support/> <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/dom-ready/src/index.js b/packages/dom-ready/src/index.js index f19b862e0e28c..82c0ed781d5e1 100644 --- a/packages/dom-ready/src/index.js +++ b/packages/dom-ready/src/index.js @@ -3,6 +3,15 @@ * * @param {Function} callback A function to execute after the DOM is ready. * + * @example + * ```js + * import domReady from '@wordpress/dom-ready'; + * + * domReady( function() { + * //do something after DOM loads. + * } ); + * ``` + * * @return {void} */ const domReady = function( callback ) { From e14c421589991eda96decf29bf32db3708e23cf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Thu, 7 Mar 2019 11:12:00 +0100 Subject: [PATCH 587/691] block-editor: set up auto-generated API docs (#14285) --- bin/update-readmes.js | 2 +- packages/block-editor/README.md | 36 +++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/bin/update-readmes.js b/bin/update-readmes.js index f40eee3d8129e..6f1efc62ff5a8 100755 --- a/bin/update-readmes.js +++ b/bin/update-readmes.js @@ -7,7 +7,7 @@ const packages = [ 'a11y', 'autop', 'blob', - //'block-editor', + 'block-editor', 'block-library', //'block-serialization-default-parser', //'blocks', diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index f58305a4d15d1..3172cb260f2a8 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -11,3 +11,39 @@ npm install @wordpress/block-editor --save ``` _This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. Learn more about it in [Babel docs](https://babeljs.io/docs/en/next/caveats)._ + +## API + +<!-- START TOKEN(Autogenerated API docs) --> + +### BlockEditorProvider + +[src/index.js#L11-L11](src/index.js#L11-L11) + +Undocumented declaration. + +### SETTINGS_DEFAULTS + +[src/index.js#L13-L13](src/index.js#L13-L13) + +The default editor settings + + alignWide boolean Enable/Disable Wide/Full Alignments + colors Array Palette colors + disableCustomColors boolean Whether or not the custom colors are disabled + fontSizes Array Available font sizes + disableCustomFontSizes boolean Whether or not the custom font sizes are disabled + imageSizes Array Available image sizes + maxWidth number Max width to constraint resizing + allowedBlockTypes boolean|Array Allowed block types + hasFixedToolbar boolean Whether or not the editor toolbar is fixed + focusMode boolean Whether the focus mode is enabled or not + styles Array Editor Styles + isRTL boolean Whether the editor is in RTL mode + bodyPlaceholder string Empty post placeholder + titlePlaceholder string Empty title placeholder + + +<!-- END TOKEN(Autogenerated API docs) --> + +<br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> From 5e4c2aebe2e9aa7b7f2b4f92a307110d2f12d471 Mon Sep 17 00:00:00 2001 From: TJ Nicolaides <tjnicolaides@gmail.com> Date: Thu, 7 Mar 2019 05:38:56 -0500 Subject: [PATCH 588/691] Adding an e2e test verifying simple keyboard navigation through blocks (#13455) * Adding an e2e test verifying simple keyboard navigation through blocks (Issue #12392) using bug resolved in #11773 as the basis for the steps * Moving `navigateToContentEditorTop`, `tabThroughParagraphBlock`, `tabThroughBlockMoverControl` and `tabThroughBlockToolbar` to the parent scope. Using pressKeyWithModifier within navigateToContentEditorTop. --- .../specs/keyboard-navigable-blocks.test.js | 167 ++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 packages/e2e-tests/specs/keyboard-navigable-blocks.test.js diff --git a/packages/e2e-tests/specs/keyboard-navigable-blocks.test.js b/packages/e2e-tests/specs/keyboard-navigable-blocks.test.js new file mode 100644 index 0000000000000..a35afb34e522b --- /dev/null +++ b/packages/e2e-tests/specs/keyboard-navigable-blocks.test.js @@ -0,0 +1,167 @@ +/** + * WordPress dependencies + */ +import { + createNewPost, + insertBlock, + pressKeyWithModifier, +} from '@wordpress/e2e-test-utils'; + +const navigateToContentEditorTop = async () => { + // Use 'Ctrl+`' to return to the top of the editor + await pressKeyWithModifier( 'ctrl', '`' ); + await pressKeyWithModifier( 'ctrl', '`' ); + + // Tab into the Title block + await page.keyboard.press( 'Tab' ); +}; + +const tabThroughParagraphBlock = async ( paragraphText ) => { + // Tab to the next paragraph block + await page.keyboard.press( 'Tab' ); + + // The block external focusable wrapper has focus + const isFocusedParagraphBlock = await page.evaluate( + () => document.activeElement.dataset.type + ); + await expect( isFocusedParagraphBlock ).toEqual( 'core/paragraph' ); + + // Tab causes 'add block' button to receive focus + await page.keyboard.press( 'Tab' ); + const isFocusedParagraphInserterToggle = await page.evaluate( () => + document.activeElement.classList.contains( 'editor-inserter__toggle' ) + ); + await expect( isFocusedParagraphInserterToggle ).toBe( true ); + + await tabThroughBlockMoverControl(); + await tabThroughBlockToolbar(); + + // Tab causes the paragraph content to receive focus + await page.keyboard.press( 'Tab' ); + const isFocusedParagraphContent = await page.evaluate( + () => document.activeElement.contentEditable + ); + // The value of 'contentEditable' should be the string 'true' + await expect( isFocusedParagraphContent ).toBe( 'true' ); + + const paragraphEditableContent = await page.evaluate( + () => document.activeElement.innerHTML + ); + await expect( paragraphEditableContent ).toBe( paragraphText ); +}; + +const tabThroughBlockMoverControl = async () => { + // Tab to focus on the 'move up' control + await page.keyboard.press( 'Tab' ); + const isFocusedMoveUpControl = await page.evaluate( () => + document.activeElement.classList.contains( 'editor-block-mover__control' ) + ); + await expect( isFocusedMoveUpControl ).toBe( true ); + + // Tab to focus on the 'move down' control + await page.keyboard.press( 'Tab' ); + const isFocusedMoveDownControl = await page.evaluate( () => + document.activeElement.classList.contains( 'editor-block-mover__control' ) + ); + await expect( isFocusedMoveDownControl ).toBe( true ); +}; + +const tabThroughBlockToolbar = async () => { + // Tab to focus on the 'block switcher' control + await page.keyboard.press( 'Tab' ); + const isFocusedBlockSwitcherControl = await page.evaluate( () => + document.activeElement.classList.contains( + 'editor-block-switcher__toggle' + ) + ); + await expect( isFocusedBlockSwitcherControl ).toBe( true ); + + // Tab to focus on the 'left paragraph alignment' dropdown control + await page.keyboard.press( 'Tab' ); + const isFocusedLeftAlignmentControl = await page.evaluate( () => + document.activeElement.classList.contains( 'components-toolbar__control' ) + ); + await expect( isFocusedLeftAlignmentControl ).toBe( true ); + + // Tab to focus on the 'center paragraph alignment' dropdown control + await page.keyboard.press( 'Tab' ); + const isFocusedCenterAlignmentControl = await page.evaluate( () => + document.activeElement.classList.contains( 'components-toolbar__control' ) + ); + await expect( isFocusedCenterAlignmentControl ).toBe( true ); + + // Tab to focus on the 'right paragraph alignment' dropdown control + await page.keyboard.press( 'Tab' ); + const isFocusedRightAlignmentControl = await page.evaluate( () => + document.activeElement.classList.contains( 'components-toolbar__control' ) + ); + await expect( isFocusedRightAlignmentControl ).toBe( true ); + + // Tab to focus on the 'Bold' formatting button + await page.keyboard.press( 'Tab' ); + const isFocusedBoldFormattingButton = await page.evaluate( () => + document.activeElement.classList.contains( 'components-toolbar__control' ) + ); + await expect( isFocusedBoldFormattingButton ).toBe( true ); + + // Tab to focus on the 'Italic' formatting button + await page.keyboard.press( 'Tab' ); + const isFocusedItalicFormattingButton = await page.evaluate( () => + document.activeElement.classList.contains( 'components-toolbar__control' ) + ); + await expect( isFocusedItalicFormattingButton ).toBe( true ); + + // Tab to focus on the 'Hyperlink' formatting button + await page.keyboard.press( 'Tab' ); + const isFocusedHyperlinkFormattingButton = await page.evaluate( () => + document.activeElement.classList.contains( 'components-toolbar__control' ) + ); + await expect( isFocusedHyperlinkFormattingButton ).toBe( true ); + + // Tab to focus on the 'Strikethrough' formatting button + await page.keyboard.press( 'Tab' ); + const isFocusedStrikethroughFormattingButton = await page.evaluate( () => + document.activeElement.classList.contains( 'components-toolbar__control' ) + ); + await expect( isFocusedStrikethroughFormattingButton ).toBe( true ); + + // Tab to focus on the 'More formatting' dropdown toggle + await page.keyboard.press( 'Tab' ); + const isFocusedMoreFormattingDropdown = await page.evaluate( () => + document.activeElement.classList.contains( + 'editor-block-settings-menu__toggle' + ) + ); + await expect( isFocusedMoreFormattingDropdown ).toBe( true ); +}; + +describe( 'Order of block keyboard navigation', () => { + beforeEach( async () => { + await createNewPost(); + } ); + + it( 'permits tabbing through paragraph blocks in the expected order', async () => { + const paragraphBlocks = [ 'Paragraph 0', 'Paragraph 1', 'Paragraph 2' ]; + + // create 3 paragraphs blocks with some content + for ( const paragraphBlock of paragraphBlocks ) { + await insertBlock( 'Paragraph' ); + await page.keyboard.type( paragraphBlock ); + await page.keyboard.press( 'Enter' ); + } + + await navigateToContentEditorTop(); + + for ( const paragraphBlock of paragraphBlocks ) { + await tabThroughParagraphBlock( paragraphBlock ); + } + + // Repeat the same steps to ensure that there is no change introduced in how the focus is handled. + // This prevents the previous regression explained in: https://github.com/WordPress/gutenberg/issues/11773. + await navigateToContentEditorTop(); + + for ( const paragraphBlock of paragraphBlocks ) { + await tabThroughParagraphBlock( paragraphBlock ); + } + } ); +} ); From 73ccbe48ae369e788e7c540bdcf76482af69dc78 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Thu, 7 Mar 2019 11:40:23 +0000 Subject: [PATCH 589/691] Add nested blocks inside cover block (#13822) --- assets/stylesheets/_z-index.scss | 1 + package-lock.json | 6 + packages/block-library/package.json | 1 + packages/block-library/src/cover/edit.js | 367 +++++++++++++++++ packages/block-library/src/cover/editor.scss | 22 +- packages/block-library/src/cover/index.js | 385 +++++------------- packages/block-library/src/cover/style.scss | 17 + .../fixtures/blocks/core__cover.html | 17 +- .../fixtures/blocks/core__cover.json | 21 +- .../fixtures/blocks/core__cover.parsed.json | 22 +- .../blocks/core__cover.serialized.html | 6 +- .../blocks/core__cover__video-overlay.html | 22 +- .../blocks/core__cover__video-overlay.json | 21 +- .../core__cover__video-overlay.parsed.json | 22 +- ...core__cover__video-overlay.serialized.html | 6 +- .../fixtures/blocks/core__cover__video.html | 21 +- .../fixtures/blocks/core__cover__video.json | 21 +- .../blocks/core__cover__video.parsed.json | 22 +- .../blocks/core__cover__video.serialized.html | 6 +- .../src/components/block-mover/style.scss | 6 +- post-content.php | 6 +- 21 files changed, 701 insertions(+), 317 deletions(-) create mode 100644 packages/block-library/src/cover/edit.js diff --git a/assets/stylesheets/_z-index.scss b/assets/stylesheets/_z-index.scss index 8efd108ba73d0..87bc9ea5575de 100644 --- a/assets/stylesheets/_z-index.scss +++ b/assets/stylesheets/_z-index.scss @@ -28,6 +28,7 @@ $z-layers: ( ".edit-post-header": 30, ".block-library-button__inline-link .editor-url-input__suggestions": 6, // URL suggestions for button block above sibling inserter ".block-library-image__resize-handlers": 1, // Resize handlers above sibling inserter + ".wp-block-cover__inner-container": 1, // InnerBlocks area inside cover image block ".wp-block-cover.has-background-dim::before": 1, // Overlay area inside block cover need to be higher than the video background. ".wp-block-cover__video-background": 0, // Video background inside cover block. diff --git a/package-lock.json b/package-lock.json index 5c30b5addb71b..82560b53523e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2563,6 +2563,7 @@ "@wordpress/keycodes": "file:packages/keycodes", "@wordpress/viewport": "file:packages/viewport", "classnames": "^2.2.5", + "fast-average-color": "4.3.0", "lodash": "^4.17.11", "memize": "^1.0.5", "url": "^0.11.0" @@ -8190,6 +8191,11 @@ "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", "dev": true }, + "fast-average-color": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/fast-average-color/-/fast-average-color-4.3.0.tgz", + "integrity": "sha512-k8FXd6+JeXoItmdNqB3hMwFgArryjdYBLuzEM8fRY/oztd/051yhSHU6GUrMOfIQU9dDHyFDcIAkGrQKlYtpDA==" + }, "fast-deep-equal": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", diff --git a/packages/block-library/package.json b/packages/block-library/package.json index 8c5ebfa5cb6dc..5a497ce617376 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -38,6 +38,7 @@ "@wordpress/keycodes": "file:../keycodes", "@wordpress/viewport": "file:../viewport", "classnames": "^2.2.5", + "fast-average-color": "4.3.0", "lodash": "^4.17.11", "memize": "^1.0.5", "url": "^0.11.0" diff --git a/packages/block-library/src/cover/edit.js b/packages/block-library/src/cover/edit.js new file mode 100644 index 0000000000000..398e842d9eafb --- /dev/null +++ b/packages/block-library/src/cover/edit.js @@ -0,0 +1,367 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; +import FastAverageColor from 'fast-average-color'; +import tinycolor from 'tinycolor2'; + +/** + * WordPress dependencies + */ +import { + FocalPointPicker, + IconButton, + PanelBody, + RangeControl, + ToggleControl, + Toolbar, + withNotices, +} from '@wordpress/components'; +import { compose } from '@wordpress/compose'; +import { + BlockControls, + BlockIcon, + InnerBlocks, + InspectorControls, + MediaPlaceholder, + MediaUpload, + MediaUploadCheck, + PanelColorSettings, + withColors, +} from '@wordpress/editor'; +import { Component, createRef, Fragment } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import icon from './icon'; + +/** + * Module Constants + */ +export const IMAGE_BACKGROUND_TYPE = 'image'; +export const VIDEO_BACKGROUND_TYPE = 'video'; +const ALLOWED_MEDIA_TYPES = [ 'image', 'video' ]; +const INNER_BLOCKS_TEMPLATE = [ + [ 'core/paragraph', { + align: 'center', + fontSize: 'large', + placeholder: __( 'Write title…' ), + } ], +]; +const INNER_BLOCKS_ALLOWED_BLOCKS = [ 'core/button', 'core/heading', 'core/paragraph' ]; + +function retrieveFastAverageColor() { + if ( ! retrieveFastAverageColor.fastAverageColor ) { + retrieveFastAverageColor.fastAverageColor = new FastAverageColor(); + } + return retrieveFastAverageColor.fastAverageColor; +} + +export function backgroundImageStyles( url ) { + return url ? + { backgroundImage: `url(${ url })` } : + {}; +} + +export function dimRatioToClass( ratio ) { + return ( ratio === 0 || ratio === 50 ) ? + null : + 'has-background-dim-' + ( 10 * Math.round( ratio / 10 ) ); +} + +class CoverEdit extends Component { + constructor() { + super( ...arguments ); + this.state = { + isDark: false, + }; + this.imageRef = createRef(); + this.videoRef = createRef(); + this.changeIsDarkIfRequired = this.changeIsDarkIfRequired.bind( this ); + } + + componentDidMount() { + this.handleBackgroundMode(); + } + + componentDidUpdate( prevProps ) { + this.handleBackgroundMode( prevProps ); + } + + render() { + const { + attributes, + setAttributes, + className, + noticeOperations, + noticeUI, + overlayColor, + setOverlayColor, + } = this.props; + const { + backgroundType, + dimRatio, + focalPoint, + hasParallax, + id, + url, + } = attributes; + const onSelectMedia = ( media ) => { + if ( ! media || ! media.url ) { + setAttributes( { url: undefined, id: undefined } ); + return; + } + let mediaType; + // for media selections originated from a file upload. + if ( media.media_type ) { + if ( media.media_type === IMAGE_BACKGROUND_TYPE ) { + mediaType = IMAGE_BACKGROUND_TYPE; + } else { + // only images and videos are accepted so if the media_type is not an image we can assume it is a video. + // Videos contain the media type of 'file' in the object returned from the rest api. + mediaType = VIDEO_BACKGROUND_TYPE; + } + } else { // for media selections originated from existing files in the media library. + if ( + media.type !== IMAGE_BACKGROUND_TYPE && + media.type !== VIDEO_BACKGROUND_TYPE + ) { + return; + } + mediaType = media.type; + } + + setAttributes( { + url: media.url, + id: media.id, + backgroundType: mediaType, + } ); + }; + const toggleParallax = () => setAttributes( { hasParallax: ! hasParallax } ); + const setDimRatio = ( ratio ) => setAttributes( { dimRatio: ratio } ); + + const style = { + ...( + backgroundType === IMAGE_BACKGROUND_TYPE ? + backgroundImageStyles( url ) : + {} + ), + backgroundColor: overlayColor.color, + }; + + if ( focalPoint ) { + style.backgroundPosition = `${ focalPoint.x * 100 }% ${ focalPoint.y * 100 }%`; + } + + const controls = ( + <Fragment> + <BlockControls> + { !! url && ( + <Fragment> + <MediaUploadCheck> + <Toolbar> + <MediaUpload + onSelect={ onSelectMedia } + allowedTypes={ ALLOWED_MEDIA_TYPES } + value={ id } + render={ ( { open } ) => ( + <IconButton + className="components-toolbar__control" + label={ __( 'Edit media' ) } + icon="edit" + onClick={ open } + /> + ) } + /> + </Toolbar> + </MediaUploadCheck> + </Fragment> + ) } + </BlockControls> + { !! url && ( + <InspectorControls> + <PanelBody title={ __( 'Cover Settings' ) }> + { IMAGE_BACKGROUND_TYPE === backgroundType && ( + <ToggleControl + label={ __( 'Fixed Background' ) } + checked={ hasParallax } + onChange={ toggleParallax } + /> + ) } + { IMAGE_BACKGROUND_TYPE === backgroundType && ! hasParallax && ( + <FocalPointPicker + label={ __( 'Focal Point Picker' ) } + url={ url } + value={ focalPoint } + onChange={ ( value ) => setAttributes( { focalPoint: value } ) } + /> + ) } + <PanelColorSettings + title={ __( 'Overlay' ) } + initialOpen={ true } + colorSettings={ [ { + value: overlayColor.color, + onChange: setOverlayColor, + label: __( 'Overlay Color' ), + } ] } + > + <RangeControl + label={ __( 'Background Opacity' ) } + value={ dimRatio } + onChange={ setDimRatio } + min={ 0 } + max={ 100 } + step={ 10 } + /> + </PanelColorSettings> + </PanelBody> + </InspectorControls> + ) } + </Fragment> + ); + + if ( ! url ) { + const placeholderIcon = <BlockIcon icon={ icon } />; + const label = __( 'Cover' ); + + return ( + <Fragment> + { controls } + <MediaPlaceholder + icon={ placeholderIcon } + className={ className } + labels={ { + title: label, + instructions: __( 'Drag an image or a video, upload a new one or select a file from your library.' ), + } } + onSelect={ onSelectMedia } + accept="image/*,video/*" + allowedTypes={ ALLOWED_MEDIA_TYPES } + notices={ noticeUI } + onError={ noticeOperations.createErrorNotice } + /> + </Fragment> + ); + } + + const classes = classnames( + className, + dimRatioToClass( dimRatio ), + { + 'is-dark-theme': this.state.isDark, + 'has-background-dim': dimRatio !== 0, + 'has-parallax': hasParallax, + } + ); + + return ( + <Fragment> + { controls } + <div + data-url={ url } + style={ style } + className={ classes } + > + { IMAGE_BACKGROUND_TYPE === backgroundType && ( + // Used only to programmatically check if the image is dark or not + <img + ref={ this.imageRef } + aria-hidden + alt="" + style={ { + display: 'none', + } } + src={ url } + /> + ) } + { VIDEO_BACKGROUND_TYPE === backgroundType && ( + <video + ref={ this.videoRef } + className="wp-block-cover__video-background" + autoPlay + muted + loop + src={ url } + /> + ) } + <div className="wp-block-cover__inner-container"> + <InnerBlocks + template={ INNER_BLOCKS_TEMPLATE } + allowedBlocks={ INNER_BLOCKS_ALLOWED_BLOCKS } + /> + </div> + </div> + </Fragment> + ); + } + + handleBackgroundMode( prevProps ) { + const { attributes, overlayColor } = this.props; + const { dimRatio, url } = attributes; + // If opacity is greater than 50 the dominant color is the overlay color, + // so use that color for the dark mode computation. + if ( dimRatio > 50 ) { + if ( + prevProps && + prevProps.attributes.dimRatio > 50 && + prevProps.overlayColor.color === overlayColor.color + ) { + // No relevant prop changes happened there is no need to apply any change. + return; + } + if ( ! overlayColor.color ) { + // If no overlay color exists the overlay color is black (isDark ) + this.changeIsDarkIfRequired( true ); + return; + } + this.changeIsDarkIfRequired( + tinycolor( overlayColor.color ).isDark() + ); + return; + } + // If opacity is lower than 50 the dominant color is the image or video color, + // so use that color for the dark mode computation. + + if ( + prevProps && + prevProps.attributes.dimRatio <= 50 && + prevProps.attributes.url === url + ) { + // No relevant prop changes happened there is no need to apply any change. + return; + } + const { backgroundType } = attributes; + + let element; + + switch ( backgroundType ) { + case IMAGE_BACKGROUND_TYPE: + element = this.imageRef.current; + break; + case VIDEO_BACKGROUND_TYPE: + element = this.videoRef.current; + break; + } + if ( ! element ) { + return; + } + retrieveFastAverageColor().getColorAsync( element, ( color ) => { + this.changeIsDarkIfRequired( color.isDark ); + } ); + } + + changeIsDarkIfRequired( newIsDark ) { + if ( this.state.isDark !== newIsDark ) { + this.setState( { + isDark: newIsDark, + } ); + } + } +} + +export default compose( [ + withColors( { overlayColor: 'background-color' } ), + withNotices, +] )( CoverEdit ); diff --git a/packages/block-library/src/cover/editor.scss b/packages/block-library/src/cover/editor.scss index 00d3fed4c8fca..6e84c3a4837eb 100644 --- a/packages/block-library/src/cover/editor.scss +++ b/packages/block-library/src/cover/editor.scss @@ -9,12 +9,28 @@ color: inherit; } + &.has-right-content .editor-rich-text__inline-toolbar, &.has-left-content .editor-rich-text__inline-toolbar { - justify-content: flex-start; + display: inline-block; } - &.has-right-content .editor-rich-text__inline-toolbar { - justify-content: flex-end; + .editor-block-list__layout { + width: 100%; + } + + .editor-block-list__block { + color: $light-gray-100; + } + + // wp-block-cover class is used just to increase selector specificity + .wp-block-cover__inner-container { + // avoid text align inherit from cover image align. + text-align: left; + } + + .wp-block-cover__inner-container > .editor-inner-blocks > .editor-block-list__layout { + margin-left: 0; + margin-right: 0; } &.components-placeholder { diff --git a/packages/block-library/src/cover/index.js b/packages/block-library/src/cover/index.js index 3ad3e1c8546e7..cd39eaf9e496a 100644 --- a/packages/block-library/src/cover/index.js +++ b/packages/block-library/src/cover/index.js @@ -1,6 +1,7 @@ /** * External dependencies */ +import { omit } from 'lodash'; import classnames from 'classnames'; /** @@ -8,49 +9,28 @@ import classnames from 'classnames'; */ import { createBlock } from '@wordpress/blocks'; import { - FocalPointPicker, - IconButton, - PanelBody, - RangeControl, - ToggleControl, - Toolbar, - withNotices, -} from '@wordpress/components'; -import { compose } from '@wordpress/compose'; -import { - AlignmentToolbar, - BlockControls, - BlockIcon, - InspectorControls, - MediaPlaceholder, - MediaUpload, - MediaUploadCheck, - PanelColorSettings, + InnerBlocks, RichText, getColorClassName, - withColors, } from '@wordpress/editor'; -import { Fragment } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ import icon from './icon'; +import { + default as CoverEdit, + IMAGE_BACKGROUND_TYPE, + VIDEO_BACKGROUND_TYPE, + backgroundImageStyles, + dimRatioToClass, +} from './edit'; const blockAttributes = { - title: { - type: 'string', - source: 'html', - selector: 'p', - }, url: { type: 'string', }, - contentAlign: { - type: 'string', - default: 'center', - }, id: { type: 'number', }, @@ -79,10 +59,6 @@ const blockAttributes = { export const name = 'core/cover'; -const ALLOWED_MEDIA_TYPES = [ 'image', 'video' ]; -const IMAGE_BACKGROUND_TYPE = 'image'; -const VIDEO_BACKGROUND_TYPE = 'video'; - export const settings = { title: __( 'Cover' ), @@ -100,13 +76,6 @@ export const settings = { transforms: { from: [ - { - type: 'block', - blocks: [ 'core/heading' ], - transform: ( { content } ) => ( - createBlock( 'core/cover', { title: content } ) - ), - }, { type: 'block', blocks: [ 'core/image' ], @@ -134,13 +103,6 @@ export const settings = { }, ], to: [ - { - type: 'block', - blocks: [ 'core/heading' ], - transform: ( { title } ) => ( - createBlock( 'core/heading', { content: title } ) - ), - }, { type: 'block', blocks: [ 'core/image' ], @@ -174,227 +136,14 @@ export const settings = { ], }, - edit: compose( [ - withColors( { overlayColor: 'background-color' } ), - withNotices, - ] )( - ( { attributes, setAttributes, isSelected, className, noticeOperations, noticeUI, overlayColor, setOverlayColor } ) => { - const { - backgroundType, - contentAlign, - dimRatio, - focalPoint, - hasParallax, - id, - title, - url, - } = attributes; - const onSelectMedia = ( media ) => { - if ( ! media || ! media.url ) { - setAttributes( { url: undefined, id: undefined } ); - return; - } - let mediaType; - // for media selections originated from a file upload. - if ( media.media_type ) { - if ( media.media_type === IMAGE_BACKGROUND_TYPE ) { - mediaType = IMAGE_BACKGROUND_TYPE; - } else { - // only images and videos are accepted so if the media_type is not an image we can assume it is a video. - // Videos contain the media type of 'file' in the object returned from the rest api. - mediaType = VIDEO_BACKGROUND_TYPE; - } - } else { // for media selections originated from existing files in the media library. - if ( - media.type !== IMAGE_BACKGROUND_TYPE && - media.type !== VIDEO_BACKGROUND_TYPE - ) { - return; - } - mediaType = media.type; - } - - setAttributes( { - url: media.url, - id: media.id, - backgroundType: mediaType, - } ); - }; - const toggleParallax = () => setAttributes( { hasParallax: ! hasParallax } ); - const setDimRatio = ( ratio ) => setAttributes( { dimRatio: ratio } ); - const setTitle = ( newTitle ) => setAttributes( { title: newTitle } ); - - const style = { - ...( - backgroundType === IMAGE_BACKGROUND_TYPE ? - backgroundImageStyles( url ) : - {} - ), - backgroundColor: overlayColor.color, - }; - - if ( focalPoint ) { - style.backgroundPosition = `${ focalPoint.x * 100 }% ${ focalPoint.y * 100 }%`; - } - - const controls = ( - <Fragment> - <BlockControls> - { !! url && ( - <Fragment> - <AlignmentToolbar - value={ contentAlign } - onChange={ ( nextAlign ) => { - setAttributes( { contentAlign: nextAlign } ); - } } - /> - <MediaUploadCheck> - <Toolbar> - <MediaUpload - onSelect={ onSelectMedia } - allowedTypes={ ALLOWED_MEDIA_TYPES } - value={ id } - render={ ( { open } ) => ( - <IconButton - className="components-toolbar__control" - label={ __( 'Edit media' ) } - icon="edit" - onClick={ open } - /> - ) } - /> - </Toolbar> - </MediaUploadCheck> - </Fragment> - ) } - </BlockControls> - { !! url && ( - <InspectorControls> - <PanelBody title={ __( 'Cover Settings' ) }> - { IMAGE_BACKGROUND_TYPE === backgroundType && ( - <ToggleControl - label={ __( 'Fixed Background' ) } - checked={ hasParallax } - onChange={ toggleParallax } - /> - ) } - { IMAGE_BACKGROUND_TYPE === backgroundType && ! hasParallax && ( - <FocalPointPicker - label={ __( 'Focal Point Picker' ) } - url={ url } - value={ focalPoint } - onChange={ ( value ) => setAttributes( { focalPoint: value } ) } - /> - ) } - <PanelColorSettings - title={ __( 'Overlay' ) } - initialOpen={ true } - colorSettings={ [ { - value: overlayColor.color, - onChange: setOverlayColor, - label: __( 'Overlay Color' ), - } ] } - > - <RangeControl - label={ __( 'Background Opacity' ) } - value={ dimRatio } - onChange={ setDimRatio } - min={ 0 } - max={ 100 } - step={ 10 } - /> - </PanelColorSettings> - </PanelBody> - </InspectorControls> - ) } - </Fragment> - ); - - if ( ! url ) { - const hasTitle = ! RichText.isEmpty( title ); - const placeholderIcon = hasTitle ? undefined : <BlockIcon icon={ icon } />; - const label = hasTitle ? ( - <RichText - tagName="h2" - value={ title } - onChange={ setTitle } - inlineToolbar - /> - ) : __( 'Cover' ); - - return ( - <Fragment> - { controls } - <MediaPlaceholder - icon={ placeholderIcon } - className={ className } - labels={ { - title: label, - instructions: __( 'Drag an image or a video, upload a new one or select a file from your library.' ), - } } - onSelect={ onSelectMedia } - accept="image/*,video/*" - allowedTypes={ ALLOWED_MEDIA_TYPES } - notices={ noticeUI } - onError={ noticeOperations.createErrorNotice } - /> - </Fragment> - ); - } - - const classes = classnames( - className, - contentAlign !== 'center' && `has-${ contentAlign }-content`, - dimRatioToClass( dimRatio ), - { - 'has-background-dim': dimRatio !== 0, - 'has-parallax': hasParallax, - } - ); - - return ( - <Fragment> - { controls } - <div - data-url={ url } - style={ style } - className={ classes } - > - { VIDEO_BACKGROUND_TYPE === backgroundType && ( - <video - className="wp-block-cover__video-background" - autoPlay - muted - loop - src={ url } - /> - ) } - { ( ! RichText.isEmpty( title ) || isSelected ) && ( - <RichText - tagName="p" - className="wp-block-cover-text" - placeholder={ __( 'Write title…' ) } - value={ title } - onChange={ setTitle } - inlineToolbar - /> - ) } - </div> - </Fragment> - ); - } - ), - save( { attributes } ) { const { backgroundType, - contentAlign, customOverlayColor, dimRatio, focalPoint, hasParallax, overlayColor, - title, url, } = attributes; const overlayColorClass = getColorClassName( 'background-color', overlayColor ); @@ -414,7 +163,6 @@ export const settings = { { 'has-background-dim': dimRatio !== 0, 'has-parallax': hasParallax, - [ `has-${ contentAlign }-content` ]: contentAlign !== 'center', }, ); @@ -427,16 +175,109 @@ export const settings = { loop src={ url } /> ) } - { ! RichText.isEmpty( title ) && ( - <RichText.Content tagName="p" className="wp-block-cover-text" value={ title } /> - ) } + <div className="wp-block-cover__inner-container"> + <InnerBlocks.Content /> + </div> </div> ); }, + edit: CoverEdit, deprecated: [ { attributes: { ...blockAttributes, + title: { + type: 'string', + source: 'html', + selector: 'p', + }, + contentAlign: { + type: 'string', + default: 'center', + }, + }, + + supports: { + align: true, + }, + + save( { attributes } ) { + const { + backgroundType, + contentAlign, + customOverlayColor, + dimRatio, + focalPoint, + hasParallax, + overlayColor, + title, + url, + } = attributes; + const overlayColorClass = getColorClassName( 'background-color', overlayColor ); + const style = backgroundType === IMAGE_BACKGROUND_TYPE ? + backgroundImageStyles( url ) : + {}; + if ( ! overlayColorClass ) { + style.backgroundColor = customOverlayColor; + } + if ( focalPoint && ! hasParallax ) { + style.backgroundPosition = `${ focalPoint.x * 100 }% ${ focalPoint.y * 100 }%`; + } + + const classes = classnames( + dimRatioToClass( dimRatio ), + overlayColorClass, + { + 'has-background-dim': dimRatio !== 0, + 'has-parallax': hasParallax, + [ `has-${ contentAlign }-content` ]: contentAlign !== 'center', + }, + ); + + return ( + <div className={ classes } style={ style }> + { VIDEO_BACKGROUND_TYPE === backgroundType && url && ( <video + className="wp-block-cover__video-background" + autoPlay + muted + loop + src={ url } + /> ) } + { ! RichText.isEmpty( title ) && ( + <RichText.Content tagName="p" className="wp-block-cover-text" value={ title } /> + ) } + </div> + ); + }, + + migrate( attributes ) { + return [ + omit( attributes, [ 'title', 'contentAlign' ] ), + [ + createBlock( + 'core/paragraph', + { + content: attributes.title, + align: attributes.contentAlign, + fontSize: 'large', + placeholder: __( 'Write title…' ), + } + ), + ], + ]; + }, + }, { + attributes: { + ...blockAttributes, + title: { + type: 'string', + source: 'html', + selector: 'p', + }, + contentAlign: { + type: 'string', + default: 'center', + }, align: { type: 'string', }, @@ -485,6 +326,10 @@ export const settings = { source: 'html', selector: 'h2', }, + contentAlign: { + type: 'string', + default: 'center', + }, }, save( { attributes } ) { @@ -507,15 +352,3 @@ export const settings = { }, } ], }; - -function dimRatioToClass( ratio ) { - return ( ratio === 0 || ratio === 50 ) ? - null : - 'has-background-dim-' + ( 10 * Math.round( ratio / 10 ) ); -} - -function backgroundImageStyles( url ) { - return url ? - { backgroundImage: `url(${ url })` } : - {}; -} diff --git a/packages/block-library/src/cover/style.scss b/packages/block-library/src/cover/style.scss index 5e35c6d8ff809..c11243504016a 100644 --- a/packages/block-library/src/cover/style.scss +++ b/packages/block-library/src/cover/style.scss @@ -110,6 +110,23 @@ &.alignright { display: flex; } + + .wp-block-cover__inner-container { + width: calc(100% - 70px); + z-index: z-index(".wp-block-cover__inner-container"); + color: $light-gray-100; + } + + p, + h1, + h2, + h3, + h4, + h5, + h6, + .wp-block-subhead { + color: inherit; + } } .wp-block-cover__video-background { diff --git a/packages/e2e-tests/fixtures/blocks/core__cover.html b/packages/e2e-tests/fixtures/blocks/core__cover.html index ae26d922c2644..a95fdd0d43f78 100644 --- a/packages/e2e-tests/fixtures/blocks/core__cover.html +++ b/packages/e2e-tests/fixtures/blocks/core__cover.html @@ -1,5 +1,14 @@ -<!-- wp:core/cover {"url":"https://cldup.com/uuUqE_dXzy.jpg","dimRatio":40} --> -<div class="wp-block-cover has-background-dim-40 has-background-dim" style="background-image:url(https://cldup.com/uuUqE_dXzy.jpg)"> - <p class="wp-block-cover-text">Guten Berg!</p> +<!-- wp:cover {"url":"https://cldup.com/uuUqE_dXzy.jpg","dimRatio":40} --> +<div + class="wp-block-cover has-background-dim-40 has-background-dim" + style="background-image:url(https://cldup.com/uuUqE_dXzy.jpg)" +> + <div class="wp-block-cover__inner-container"> + <!-- wp:paragraph {"align":"center","placeholder":"Write title…","fontSize":"large"} --> + <p style="text-align:center" class="has-large-font-size"> + Guten Berg! + </p> + <!-- /wp:paragraph --> + </div> </div> -<!-- /wp:core/cover --> +<!-- /wp:cover --> diff --git a/packages/e2e-tests/fixtures/blocks/core__cover.json b/packages/e2e-tests/fixtures/blocks/core__cover.json index dea09d92c11c8..8dfaaf80485a8 100644 --- a/packages/e2e-tests/fixtures/blocks/core__cover.json +++ b/packages/e2e-tests/fixtures/blocks/core__cover.json @@ -4,14 +4,27 @@ "name": "core/cover", "isValid": true, "attributes": { - "title": "Guten Berg!", "url": "https://cldup.com/uuUqE_dXzy.jpg", - "contentAlign": "center", "hasParallax": false, "dimRatio": 40, "backgroundType": "image" }, - "innerBlocks": [], - "originalContent": "<div class=\"wp-block-cover has-background-dim-40 has-background-dim\" style=\"background-image:url(https://cldup.com/uuUqE_dXzy.jpg)\">\n <p class=\"wp-block-cover-text\">Guten Berg!</p>\n</div>" + "innerBlocks": [ + { + "clientId": "_clientId_0", + "name": "core/paragraph", + "isValid": true, + "attributes": { + "content": "\n\t\t\tGuten Berg!\n\t\t", + "align": "center", + "dropCap": false, + "placeholder": "Write title…", + "fontSize": "large" + }, + "innerBlocks": [], + "originalContent": "<p style=\"text-align:center\" class=\"has-large-font-size\">\n\t\t\tGuten Berg!\n\t\t</p>" + } + ], + "originalContent": "<div\n\tclass=\"wp-block-cover has-background-dim-40 has-background-dim\"\n\tstyle=\"background-image:url(https://cldup.com/uuUqE_dXzy.jpg)\"\n>\n\t<div class=\"wp-block-cover__inner-container\">\n\t\t\n\t</div>\n</div>" } ] diff --git a/packages/e2e-tests/fixtures/blocks/core__cover.parsed.json b/packages/e2e-tests/fixtures/blocks/core__cover.parsed.json index 14f37b617067b..116012372ec34 100644 --- a/packages/e2e-tests/fixtures/blocks/core__cover.parsed.json +++ b/packages/e2e-tests/fixtures/blocks/core__cover.parsed.json @@ -5,10 +5,26 @@ "url": "https://cldup.com/uuUqE_dXzy.jpg", "dimRatio": 40 }, - "innerBlocks": [], - "innerHTML": "\n<div class=\"wp-block-cover has-background-dim-40 has-background-dim\" style=\"background-image:url(https://cldup.com/uuUqE_dXzy.jpg)\">\n <p class=\"wp-block-cover-text\">Guten Berg!</p>\n</div>\n", + "innerBlocks": [ + { + "blockName": "core/paragraph", + "attrs": { + "align": "center", + "placeholder": "Write title…", + "fontSize": "large" + }, + "innerBlocks": [], + "innerHTML": "\n\t\t<p style=\"text-align:center\" class=\"has-large-font-size\">\n\t\t\tGuten Berg!\n\t\t</p>\n\t\t", + "innerContent": [ + "\n\t\t<p style=\"text-align:center\" class=\"has-large-font-size\">\n\t\t\tGuten Berg!\n\t\t</p>\n\t\t" + ] + } + ], + "innerHTML": "\n<div\n\tclass=\"wp-block-cover has-background-dim-40 has-background-dim\"\n\tstyle=\"background-image:url(https://cldup.com/uuUqE_dXzy.jpg)\"\n>\n\t<div class=\"wp-block-cover__inner-container\">\n\t\t\n\t</div>\n</div>\n", "innerContent": [ - "\n<div class=\"wp-block-cover has-background-dim-40 has-background-dim\" style=\"background-image:url(https://cldup.com/uuUqE_dXzy.jpg)\">\n <p class=\"wp-block-cover-text\">Guten Berg!</p>\n</div>\n" + "\n<div\n\tclass=\"wp-block-cover has-background-dim-40 has-background-dim\"\n\tstyle=\"background-image:url(https://cldup.com/uuUqE_dXzy.jpg)\"\n>\n\t<div class=\"wp-block-cover__inner-container\">\n\t\t", + null, + "\n\t</div>\n</div>\n" ] }, { diff --git a/packages/e2e-tests/fixtures/blocks/core__cover.serialized.html b/packages/e2e-tests/fixtures/blocks/core__cover.serialized.html index a6d795bb56175..306a5a740160a 100644 --- a/packages/e2e-tests/fixtures/blocks/core__cover.serialized.html +++ b/packages/e2e-tests/fixtures/blocks/core__cover.serialized.html @@ -1,3 +1,7 @@ <!-- wp:cover {"url":"https://cldup.com/uuUqE_dXzy.jpg","dimRatio":40} --> -<div class="wp-block-cover has-background-dim-40 has-background-dim" style="background-image:url(https://cldup.com/uuUqE_dXzy.jpg)"><p class="wp-block-cover-text">Guten Berg!</p></div> +<div class="wp-block-cover has-background-dim-40 has-background-dim" style="background-image:url(https://cldup.com/uuUqE_dXzy.jpg)"><div class="wp-block-cover__inner-container"><!-- wp:paragraph {"align":"center","placeholder":"Write title…","fontSize":"large"} --> +<p style="text-align:center" class="has-large-font-size"> + Guten Berg! + </p> +<!-- /wp:paragraph --></div></div> <!-- /wp:cover --> diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.html b/packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.html index 2a3530954f29b..1eb26c9730be6 100644 --- a/packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.html +++ b/packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.html @@ -1,6 +1,22 @@ <!-- wp:cover {"url":"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=","dimRatio":10,"customOverlayColor":"#3615d9","backgroundType":"video"} --> -<div class="wp-block-cover has-background-dim-10 has-background-dim" style="background-color:#3615d9"> - <video class="wp-block-cover__video-background" autoplay muted loop src="data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE="></video> - <p class="wp-block-cover-text">Guten Berg!</p> +<div + class="wp-block-cover has-background-dim-10 has-background-dim" + style="background-color:#3615d9" +> + <video + class="wp-block-cover__video-background" + autoplay + muted + loop + src="data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=" + > + </video> + <div class="wp-block-cover__inner-container"> + <!-- wp:paragraph {"align":"center","placeholder":"Write title…","fontSize":"large"} --> + <p style="text-align:center" class="has-large-font-size"> + Guten Berg! + </p> + <!-- /wp:paragraph --> + </div> </div> <!-- /wp:cover --> diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.json b/packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.json index 08f0de4eb4c15..36298998345e8 100644 --- a/packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.json +++ b/packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.json @@ -4,15 +4,28 @@ "name": "core/cover", "isValid": true, "attributes": { - "title": "Guten Berg!", "url": "data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=", - "contentAlign": "center", "hasParallax": false, "dimRatio": 10, "customOverlayColor": "#3615d9", "backgroundType": "video" }, - "innerBlocks": [], - "originalContent": "<div class=\"wp-block-cover has-background-dim-10 has-background-dim\" style=\"background-color:#3615d9\">\n\t<video class=\"wp-block-cover__video-background\" autoplay muted loop src=\"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=\"></video>\n\t<p class=\"wp-block-cover-text\">Guten Berg!</p>\n</div>" + "innerBlocks": [ + { + "clientId": "_clientId_0", + "name": "core/paragraph", + "isValid": true, + "attributes": { + "content": "\n\t\t\tGuten Berg!\n\t\t", + "align": "center", + "dropCap": false, + "placeholder": "Write title…", + "fontSize": "large" + }, + "innerBlocks": [], + "originalContent": "<p style=\"text-align:center\" class=\"has-large-font-size\">\n\t\t\tGuten Berg!\n\t\t</p>" + } + ], + "originalContent": "<div\n\tclass=\"wp-block-cover has-background-dim-10 has-background-dim\"\n\tstyle=\"background-color:#3615d9\"\n>\n\t<video\n\t\tclass=\"wp-block-cover__video-background\"\n\t\tautoplay\n\t\tmuted\n\t\tloop\n\t\tsrc=\"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=\"\n\t>\n\t</video>\n\t<div class=\"wp-block-cover__inner-container\">\n\t\t\n\t</div>\n</div>" } ] diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.parsed.json b/packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.parsed.json index a2a3ccc94a713..4b72e0d4148e7 100644 --- a/packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.parsed.json +++ b/packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.parsed.json @@ -7,10 +7,26 @@ "customOverlayColor": "#3615d9", "backgroundType": "video" }, - "innerBlocks": [], - "innerHTML": "\n<div class=\"wp-block-cover has-background-dim-10 has-background-dim\" style=\"background-color:#3615d9\">\n\t<video class=\"wp-block-cover__video-background\" autoplay muted loop src=\"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=\"></video>\n\t<p class=\"wp-block-cover-text\">Guten Berg!</p>\n</div>\n", + "innerBlocks": [ + { + "blockName": "core/paragraph", + "attrs": { + "align": "center", + "placeholder": "Write title…", + "fontSize": "large" + }, + "innerBlocks": [], + "innerHTML": "\n\t\t<p style=\"text-align:center\" class=\"has-large-font-size\">\n\t\t\tGuten Berg!\n\t\t</p>\n\t\t", + "innerContent": [ + "\n\t\t<p style=\"text-align:center\" class=\"has-large-font-size\">\n\t\t\tGuten Berg!\n\t\t</p>\n\t\t" + ] + } + ], + "innerHTML": "\n<div\n\tclass=\"wp-block-cover has-background-dim-10 has-background-dim\"\n\tstyle=\"background-color:#3615d9\"\n>\n\t<video\n\t\tclass=\"wp-block-cover__video-background\"\n\t\tautoplay\n\t\tmuted\n\t\tloop\n\t\tsrc=\"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=\"\n\t>\n\t</video>\n\t<div class=\"wp-block-cover__inner-container\">\n\t\t\n\t</div>\n</div>\n", "innerContent": [ - "\n<div class=\"wp-block-cover has-background-dim-10 has-background-dim\" style=\"background-color:#3615d9\">\n\t<video class=\"wp-block-cover__video-background\" autoplay muted loop src=\"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=\"></video>\n\t<p class=\"wp-block-cover-text\">Guten Berg!</p>\n</div>\n" + "\n<div\n\tclass=\"wp-block-cover has-background-dim-10 has-background-dim\"\n\tstyle=\"background-color:#3615d9\"\n>\n\t<video\n\t\tclass=\"wp-block-cover__video-background\"\n\t\tautoplay\n\t\tmuted\n\t\tloop\n\t\tsrc=\"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=\"\n\t>\n\t</video>\n\t<div class=\"wp-block-cover__inner-container\">\n\t\t", + null, + "\n\t</div>\n</div>\n" ] }, { diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.serialized.html b/packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.serialized.html index 14786a0f0d189..831388b0182b8 100644 --- a/packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.serialized.html +++ b/packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.serialized.html @@ -1,3 +1,7 @@ <!-- wp:cover {"url":"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=","dimRatio":10,"customOverlayColor":"#3615d9","backgroundType":"video"} --> -<div class="wp-block-cover has-background-dim-10 has-background-dim" style="background-color:#3615d9"><video class="wp-block-cover__video-background" autoplay muted loop src="data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE="></video><p class="wp-block-cover-text">Guten Berg!</p></div> +<div class="wp-block-cover has-background-dim-10 has-background-dim" style="background-color:#3615d9"><video class="wp-block-cover__video-background" autoplay muted loop src="data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE="></video><div class="wp-block-cover__inner-container"><!-- wp:paragraph {"align":"center","placeholder":"Write title…","fontSize":"large"} --> +<p style="text-align:center" class="has-large-font-size"> + Guten Berg! + </p> +<!-- /wp:paragraph --></div></div> <!-- /wp:cover --> diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__video.html b/packages/e2e-tests/fixtures/blocks/core__cover__video.html index 0ab2fd73ccdf8..eb50abcb28cf3 100644 --- a/packages/e2e-tests/fixtures/blocks/core__cover__video.html +++ b/packages/e2e-tests/fixtures/blocks/core__cover__video.html @@ -1,6 +1,21 @@ <!-- wp:cover {"url":"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=","dimRatio":40,"backgroundType":"video"} --> -<div class="wp-block-cover has-background-dim-40 has-background-dim"> - <video class="wp-block-cover__video-background" autoplay muted loop src="data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE="></video> - <p class="wp-block-cover-text">Guten Berg!</p> +<div + class="wp-block-cover has-background-dim-40 has-background-dim" +> + <video + class="wp-block-cover__video-background" + autoplay + muted + loop + src="data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=" + > + </video> + <div class="wp-block-cover__inner-container"> + <!-- wp:paragraph {"align":"center","placeholder":"Write title…","fontSize":"large"} --> + <p style="text-align:center" class="has-large-font-size"> + Guten Berg! + </p> + <!-- /wp:paragraph --> + </div> </div> <!-- /wp:cover --> diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__video.json b/packages/e2e-tests/fixtures/blocks/core__cover__video.json index 1fa4d25d66cda..4c05b5f2f0c1f 100644 --- a/packages/e2e-tests/fixtures/blocks/core__cover__video.json +++ b/packages/e2e-tests/fixtures/blocks/core__cover__video.json @@ -4,14 +4,27 @@ "name": "core/cover", "isValid": true, "attributes": { - "title": "Guten Berg!", "url": "data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=", - "contentAlign": "center", "hasParallax": false, "dimRatio": 40, "backgroundType": "video" }, - "innerBlocks": [], - "originalContent": "<div class=\"wp-block-cover has-background-dim-40 has-background-dim\">\n\t<video class=\"wp-block-cover__video-background\" autoplay muted loop src=\"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=\"></video>\n\t<p class=\"wp-block-cover-text\">Guten Berg!</p>\n</div>" + "innerBlocks": [ + { + "clientId": "_clientId_0", + "name": "core/paragraph", + "isValid": true, + "attributes": { + "content": "\n\t\t\tGuten Berg!\n\t\t", + "align": "center", + "dropCap": false, + "placeholder": "Write title…", + "fontSize": "large" + }, + "innerBlocks": [], + "originalContent": "<p style=\"text-align:center\" class=\"has-large-font-size\">\n\t\t\tGuten Berg!\n\t\t</p>" + } + ], + "originalContent": "<div\n\tclass=\"wp-block-cover has-background-dim-40 has-background-dim\"\n>\n\t<video\n\t\tclass=\"wp-block-cover__video-background\"\n\t\tautoplay\n\t\tmuted\n\t\tloop\n\t\tsrc=\"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=\"\n\t>\n\t</video>\n\t<div class=\"wp-block-cover__inner-container\">\n\t\t\n\t</div>\n</div>" } ] diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__video.parsed.json b/packages/e2e-tests/fixtures/blocks/core__cover__video.parsed.json index a864b5fea344e..c48b676a2f1c5 100644 --- a/packages/e2e-tests/fixtures/blocks/core__cover__video.parsed.json +++ b/packages/e2e-tests/fixtures/blocks/core__cover__video.parsed.json @@ -6,10 +6,26 @@ "dimRatio": 40, "backgroundType": "video" }, - "innerBlocks": [], - "innerHTML": "\n<div class=\"wp-block-cover has-background-dim-40 has-background-dim\">\n\t<video class=\"wp-block-cover__video-background\" autoplay muted loop src=\"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=\"></video>\n\t<p class=\"wp-block-cover-text\">Guten Berg!</p>\n</div>\n", + "innerBlocks": [ + { + "blockName": "core/paragraph", + "attrs": { + "align": "center", + "placeholder": "Write title…", + "fontSize": "large" + }, + "innerBlocks": [], + "innerHTML": "\n\t\t<p style=\"text-align:center\" class=\"has-large-font-size\">\n\t\t\tGuten Berg!\n\t\t</p>\n\t\t", + "innerContent": [ + "\n\t\t<p style=\"text-align:center\" class=\"has-large-font-size\">\n\t\t\tGuten Berg!\n\t\t</p>\n\t\t" + ] + } + ], + "innerHTML": "\n<div\n\tclass=\"wp-block-cover has-background-dim-40 has-background-dim\"\n>\n\t<video\n\t\tclass=\"wp-block-cover__video-background\"\n\t\tautoplay\n\t\tmuted\n\t\tloop\n\t\tsrc=\"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=\"\n\t>\n\t</video>\n\t<div class=\"wp-block-cover__inner-container\">\n\t\t\n\t</div>\n</div>\n", "innerContent": [ - "\n<div class=\"wp-block-cover has-background-dim-40 has-background-dim\">\n\t<video class=\"wp-block-cover__video-background\" autoplay muted loop src=\"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=\"></video>\n\t<p class=\"wp-block-cover-text\">Guten Berg!</p>\n</div>\n" + "\n<div\n\tclass=\"wp-block-cover has-background-dim-40 has-background-dim\"\n>\n\t<video\n\t\tclass=\"wp-block-cover__video-background\"\n\t\tautoplay\n\t\tmuted\n\t\tloop\n\t\tsrc=\"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=\"\n\t>\n\t</video>\n\t<div class=\"wp-block-cover__inner-container\">\n\t\t", + null, + "\n\t</div>\n</div>\n" ] }, { diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__video.serialized.html b/packages/e2e-tests/fixtures/blocks/core__cover__video.serialized.html index 03398311e20f6..5f23cddf2fa1c 100644 --- a/packages/e2e-tests/fixtures/blocks/core__cover__video.serialized.html +++ b/packages/e2e-tests/fixtures/blocks/core__cover__video.serialized.html @@ -1,3 +1,7 @@ <!-- wp:cover {"url":"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=","dimRatio":40,"backgroundType":"video"} --> -<div class="wp-block-cover has-background-dim-40 has-background-dim"><video class="wp-block-cover__video-background" autoplay muted loop src="data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE="></video><p class="wp-block-cover-text">Guten Berg!</p></div> +<div class="wp-block-cover has-background-dim-40 has-background-dim"><video class="wp-block-cover__video-background" autoplay muted loop src="data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE="></video><div class="wp-block-cover__inner-container"><!-- wp:paragraph {"align":"center","placeholder":"Write title…","fontSize":"large"} --> +<p style="text-align:center" class="has-large-font-size"> + Guten Berg! + </p> +<!-- /wp:paragraph --></div></div> <!-- /wp:cover --> diff --git a/packages/editor/src/components/block-mover/style.scss b/packages/editor/src/components/block-mover/style.scss index 39a908806ad5b..be9ddeab9c1b1 100644 --- a/packages/editor/src/components/block-mover/style.scss +++ b/packages/editor/src/components/block-mover/style.scss @@ -43,7 +43,8 @@ } // Nested movers have a background, so don't invert the colors there. - .is-dark-theme .wp-block .wp-block & { + .is-dark-theme .wp-block .wp-block &, + .wp-block .is-dark-theme .wp-block & { color: $dark-opacity-300; } @@ -79,7 +80,8 @@ } // Nested movers have a background, so don't invert the colors there. - .is-dark-theme .wp-block .wp-block & { + .is-dark-theme .wp-block .wp-block &, + .wp-block .is-dark-theme .wp-block & { color: $dark-opacity-500; } } diff --git a/post-content.php b/post-content.php index 45684cb6b0d2a..032c4ad10be6a 100644 --- a/post-content.php +++ b/post-content.php @@ -6,8 +6,10 @@ */ ?> -<!-- wp:cover {"url":"https://cldup.com/Fz-ASbo2s3.jpg","align":"wide"} --> -<div class="wp-block-cover has-background-dim alignwide" style="background-image:url(https://cldup.com/Fz-ASbo2s3.jpg)"><p class="wp-block-cover-text"><?php _e( 'Of Mountains &amp; Printing Presses', 'gutenberg' ); ?></p></div> +<!-- wp:cover {"url":"https://cldup.com/Fz-ASbo2s3.jpg","className":"alignwide"} --> +<div class="wp-block-cover has-background-dim alignwide" style="background-image:url(https://cldup.com/Fz-ASbo2s3.jpg)"><div class="wp-block-cover__inner-container"><!-- wp:paragraph {"align":"center","placeholder":"Write title…","fontSize":"large"} --> +<p style="text-align:center" class="has-large-font-size"><?php _e( 'Of Mountains &amp; Printing Presses', 'gutenberg' ); ?></p> +<!-- /wp:paragraph --></div></div> <!-- /wp:cover --> <!-- wp:paragraph --> From 29203bf380b8604df99a86b160f4f4cbf5026476 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Thu, 7 Mar 2019 14:38:09 +0100 Subject: [PATCH 590/691] rich-text: set up autogenerated API docs (#14220) --- bin/update-readmes.js | 28 +- packages/rich-text/README.md | 388 +++++++++++++----- packages/rich-text/src/apply-format.js | 8 +- packages/rich-text/src/char-at.js | 2 +- packages/rich-text/src/create.js | 14 +- packages/rich-text/src/get-active-format.js | 2 +- packages/rich-text/src/get-selection-end.js | 2 +- packages/rich-text/src/get-selection-start.js | 2 +- packages/rich-text/src/indent-list-items.js | 2 +- .../rich-text/src/insert-line-separator.js | 6 +- packages/rich-text/src/insert-object.js | 4 +- packages/rich-text/src/insert.js | 8 +- packages/rich-text/src/is-collapsed.js | 4 +- packages/rich-text/src/join.js | 4 +- .../rich-text/src/register-format-type.js | 12 +- packages/rich-text/src/remove-format.js | 8 +- packages/rich-text/src/remove.js | 6 +- packages/rich-text/src/slice.js | 6 +- packages/rich-text/src/special-characters.js | 3 + packages/rich-text/src/split.js | 6 +- packages/rich-text/src/to-dom.js | 9 +- packages/rich-text/src/to-html-string.js | 10 +- packages/rich-text/src/toggle-format.js | 4 +- .../rich-text/src/unregister-format-type.js | 4 +- 24 files changed, 370 insertions(+), 172 deletions(-) diff --git a/bin/update-readmes.js b/bin/update-readmes.js index 6f1efc62ff5a8..fb1be92aa4a34 100755 --- a/bin/update-readmes.js +++ b/bin/update-readmes.js @@ -27,21 +27,35 @@ const packages = [ //'plugins', //'priority-queue', //'redux-routine', - //'rich-text', + 'rich-text', //'shortcode', //'url', //'viewport', //'wordcount', ]; +const getArgsForPackage = ( packageName ) => { + switch ( packageName ) { + case 'rich-text': + return [ + `packages/${ packageName }/src/index.js`, + `--output packages/${ packageName }/README.md`, + '--to-token', + '--ignore "unstable|experimental|^apply$|^changeListType$|^charAt$|^getSelectionStart$|^getSelectionEnd$|^indentListItems$|^insertLineBreak$|^insertLineSeparator$|^isEmptyLine$|^LINE_SEPARATOR$|^outdentListItems$"', + ]; + default: + return [ + `packages/${ packageName }/src/index.js`, + `--output packages/${ packageName }/README.md`, + '--to-token', + '--ignore "unstable|experimental"', + ]; + } +}; + let aggregatedExitCode = 0; packages.forEach( ( packageName ) => { - const args = [ - `packages/${ packageName }/src/index.js`, - `--output packages/${ packageName }/README.md`, - '--to-token', - '--ignore "unstable|experimental"', - ]; + const args = getArgsForPackage( packageName ); const pathToDocGen = path.join( __dirname, '..', 'node_modules', '.bin', 'docgen' ); const { status, stderr } = childProcess.spawnSync( pathToDocGen, diff --git a/packages/rich-text/README.md b/packages/rich-text/README.md index be3d703b2678f..0ac85d85b640d 100644 --- a/packages/rich-text/README.md +++ b/packages/rich-text/README.md @@ -12,171 +12,347 @@ npm install @wordpress/rich-text _This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. Learn more about it in [Babel docs](https://babeljs.io/docs/en/next/caveats)._ -## Usage +## API + +<!-- START TOKEN(Autogenerated API docs) --> + +### applyFormat + +[src/index.js#L6-L6](src/index.js#L6-L6) + +Apply a format object to a Rich Text value from the given `startIndex` to the +given `endIndex`. Indices are retrieved from the selection if none are +provided. + +**Parameters** + +- **value** `Object`: Value to modify. +- **format** `Object`: Format to apply. +- **startIndex** `[number]`: Start index. +- **endIndex** `[number]`: End index. + +**Returns** + +`Object`: A new value with the format applied. + +### concat + +[src/index.js#L8-L8](src/index.js#L8-L8) + +Combine all Rich Text values into one. This is similar to +`String.prototype.concat`. + +**Parameters** + +- **values** `...Object`: Objects to combine. + +**Returns** + +`Object`: A new value combining all given records. ### create -```js -create( { - ?element: Element, - ?text: string, - ?html: string, - ?range: Range, - ?multilineTag: string, - ?multilineWrapperTags: Array, -} ): Object -``` +[src/index.js#L9-L9](src/index.js#L9-L9) -Create a RichText value from an `Element` tree (DOM), an HTML string or a plain text string, with optionally a `Range` object to set the selection. If called without any arguments, an empty value will be created. If `multilineTag` is provided, any content of direct children whose type matches `multilineTag` will be separated by a line separator. +Create a RichText value from an `Element` tree (DOM), an HTML string or a +plain text string, with optionally a `Range` object to set the selection. If +called without any input, an empty value will be created. If +`multilineTag` is provided, any content of direct children whose type matches +`multilineTag` will be separated by two newlines. The optional functions can +be used to filter out content. -### toHTMLString +**Parameters** -```js -toHTMLString( { - value: Object, - ?multilineTag: string, - ?multilineWrapperTags: Array, -} ): string -``` +- **$1** `[Object]`: Optional named arguments. +- **$1.element** `[Element]`: Element to create value from. +- **$1.text** `[string]`: Text to create value from. +- **$1.html** `[string]`: HTML to create value from. +- **$1.range** `[Range]`: Range to create value from. +- **$1.multilineTag** `[string]`: Multiline tag if the structure is multiline. +- **$1.multilineWrapperTags** `[Array]`: Tags where lines can be found if nesting is possible. -Create an HTML string from a Rich Text value. If a `multilineTag` is provided, text separated by a line separator will be wrapped in it. +**Returns** -### apply +`Object`: A rich text value. -```js -apply( { - value: Object, - current: Element, - ?multilineTag: string - ?multilineWrapperTags: Array, -} ): void -``` +### getActiveFormat + +[src/index.js#L10-L10](src/index.js#L10-L10) + +Gets the format object by type at the start of the selection. This can be +used to get e.g. the URL of a link format at the current selection, but also +to check if a format is active at the selection. Returns undefined if there +is no format at the selection. + +**Parameters** + +- **value** `Object`: Value to inspect. +- **formatType** `string`: Format type to look for. + +**Returns** + +`(Object|undefined)`: Active format object of the specified type, or undefined. + +### getTextContent + +[src/index.js#L13-L13](src/index.js#L13-L13) -Create an `Element` tree from a Rich Text value and applies the difference to the `Element` tree contained by `current`. If a `multilineTag` is provided, text separated by two new lines will be wrapped in an `Element` of that type. +Get the textual content of a Rich Text value. This is similar to +`Element.textContent`. + +**Parameters** + +- **value** `Object`: Value to use. + +**Returns** + +`string`: The text content. + +### insert + +[src/index.js#L21-L21](src/index.js#L21-L21) + +Insert a Rich Text value, an HTML string, or a plain text string, into a +Rich Text value at the given `startIndex`. Any content between `startIndex` +and `endIndex` will be removed. Indices are retrieved from the selection if +none are provided. + +**Parameters** + +- **value** `Object`: Value to modify. +- **valueToInsert** `(Object|string)`: Value to insert. +- **startIndex** `[number]`: Start index. +- **endIndex** `[number]`: End index. + +**Returns** + +`Object`: A new value with the value inserted. + +### insertObject + +[src/index.js#L24-L24](src/index.js#L24-L24) + +Insert a format as an object into a Rich Text value at the given +`startIndex`. Any content between `startIndex` and `endIndex` will be +removed. Indices are retrieved from the selection if none are provided. + +**Parameters** + +- **value** `Object`: Value to modify. +- **formatToInsert** `Object`: Format to insert as object. +- **startIndex** `[number]`: Start index. +- **endIndex** `[number]`: End index. + +**Returns** + +`Object`: A new value with the object inserted. ### isCollapsed -```js -isCollapsed( value: Object ): ?boolean -``` +[src/index.js#L14-L14](src/index.js#L14-L14) + +Check if the selection of a Rich Text value is collapsed or not. Collapsed +means that no characters are selected, but there is a caret present. If there +is no selection, `undefined` will be returned. This is similar to +`window.getSelection().isCollapsed()`. + +**Parameters** + +- **value** `Object`: The rich text value to check. + +**Returns** -Check if the selection of a Rich Text value is collapsed or not. Collapsed means that no characters are selected, but there is a caret present. If there is no selection, `undefined` will be returned. This is similar to `window.getSelection().isCollapsed()`. +`(boolean|undefined)`: True if the selection is collapsed, false if not, undefined if there is no selection. ### isEmpty -```js -isEmpty( value: Object ): boolean -``` +[src/index.js#L15-L15](src/index.js#L15-L15) -Check if a Rich Text value is Empty, meaning it contains no text or any objects (such as images). +Check if a Rich Text value is Empty, meaning it contains no text or any +objects (such as images). -### applyFormat +**Parameters** -```js -applyFormat( value: Object, format: Object, ?startIndex: number, ?endIndex: number ): Object -``` +- **value** `Object`: Value to use. -Apply a format object to a Rich Text value from the given `startIndex` to the given `endIndex`. Indices are retrieved from the selection if none are provided. +**Returns** -### removeFormat +`boolean`: True if the value is empty, false if not. -```js -removeFormat( value: Object, formatType: string, ?startIndex: number, ?endIndex: number ): Object -``` +### join -Remove any format object from a Rich Text value by type from the given `startIndex` to the given `endIndex`. Indices are retrieved from the selection if none are provided. +[src/index.js#L16-L16](src/index.js#L16-L16) -### toggleFormat +Combine an array of Rich Text values into one, optionally separated by +`separator`, which can be a Rich Text value, HTML string, or plain text +string. This is similar to `Array.prototype.join`. -```js -toggleFormat( value: Object, format: Object ): Object -``` +**Parameters** -Toggles a format object to a Rich Text value at the current selection, and returns a new value with the format applied or removed. +- **values** `Array<Object>`: An array of values to join. +- **separator** `[(string|Object)]`: Separator string or value. -### getActiveFormat +**Returns** -```js -getActiveFormat( value: Object, formatType: string ): ?Object -``` +`Object`: A new combined value. -Get any format object by type at the start of the selection. This can be used to get e.g. the URL of a link format at the current selection, but also to check if a format is active at the selection. Returns undefined if there is no format at the selection. +### registerFormatType -### getTextContent +[src/index.js#L17-L17](src/index.js#L17-L17) -```js -getTextContent( value: Object ): string -``` +Registers a new format provided a unique name and an object defining its +behavior. -Get the textual content of a Rich Text value. This is similar to `Element.textContent`. +**Parameters** -### slice +- **name** `string`: Format name. +- **settings** `Object`: Format settings. +- **settings.tagName** `string`: The HTML tag this format will wrap the selection with. +- **settings.className** `[string]`: A class to match the format. +- **settings.title** `string`: Name of the format. +- **settings.edit** `Function`: Should return a component for the user to interact with the new registered format. -```js -slice( value: Object, ?startIndex: number, ?endIndex: number ): Object -``` +**Returns** + +`(WPFormat|undefined)`: The format, if it has been successfully registered; otherwise `undefined`. + +### remove + +[src/index.js#L19-L19](src/index.js#L19-L19) + +Remove content from a Rich Text value between the given `startIndex` and +`endIndex`. Indices are retrieved from the selection if none are provided. + +**Parameters** + +- **value** `Object`: Value to modify. +- **startIndex** `[number]`: Start index. +- **endIndex** `[number]`: End index. + +**Returns** -Slice a Rich Text value from `startIndex` to `endIndex`. Indices are retrieved from the selection if none are provided. This is similar to `String.prototype.slice`. +`Object`: A new value with the content removed. + +### removeFormat + +[src/index.js#L18-L18](src/index.js#L18-L18) + +Remove any format object from a Rich Text value by type from the given +`startIndex` to the given `endIndex`. Indices are retrieved from the +selection if none are provided. + +**Parameters** + +- **value** `Object`: Value to modify. +- **formatType** `string`: Format type to remove. +- **startIndex** `[number]`: Start index. +- **endIndex** `[number]`: End index. + +**Returns** + +`Object`: A new value with the format applied. ### replace -```js -replace( value: Object, pattern: RegExp, replacement: Object | string ): Object -``` +[src/index.js#L20-L20](src/index.js#L20-L20) -Search a Rich Text value and replace the match(es) with `replacement`. This is similar to `String.prototype.replace`. +Search a Rich Text value and replace the match(es) with `replacement`. This +is similar to `String.prototype.replace`. -### insert +**Parameters** -```js -insert( value: Object, valueToInsert: Object | string, ?startIndex: number, ?endIndex: number ): Object -``` +- **value** `Object`: The value to modify. +- **pattern** `(RegExp|string)`: A RegExp object or literal. Can also be a string. It is treated as a verbatim string and is not interpreted as a regular expression. Only the first occurrence will be replaced. +- **replacement** `(Function|string)`: The match or matches are replaced with the specified or the value returned by the specified function. -Insert a Rich Text value, an HTML string, or a plain text string, into a Rich Text value at the given `startIndex`. Any content between `startIndex` and `endIndex` will be removed. Indices are retrieved from the selection if none are provided. +**Returns** -### registerFormatType +`Object`: A new value with replacements applied. -```js -registerFormatType( name: String, settings: Object ): ?WPformat -``` +### slice -Registers a new format provided a unique name and an object defining its behavior. Settings object: +[src/index.js#L25-L25](src/index.js#L25-L25) -- `tagName`: String. The HTML tag this format will wrap the selection with. -- `className`: String || null. A class to match the format. -- `title`: String. Name of the format. -- `edit`: function. Should return a component for the user to interact with the new registered format. +Slice a Rich Text value from `startIndex` to `endIndex`. Indices are +retrieved from the selection if none are provided. This is similar to +`String.prototype.slice`. -### remove +**Parameters** -```js -remove( value: Object, ?startIndex: number, ?endIndex: number ): Object -``` +- **value** `Object`: Value to modify. +- **startIndex** `[number]`: Start index. +- **endIndex** `[number]`: End index. + +**Returns** -Remove content from a Rich Text value between the given `startIndex` and `endIndex`. Indices are retrieved from the selection if none are provided. +`Object`: A new extracted value. ### split -```js -split( value: Object, ?startIndex: number | string | RegExp, ?endIndex: number ): Array<Object> -``` +[src/index.js#L26-L26](src/index.js#L26-L26) -Split a Rich Text value in two at the given `startIndex` and `endIndex`, or split at the given separator. This is similar to `String.prototype.split`. Indices are retrieved from the selection if none are provided. +Split a Rich Text value in two at the given `startIndex` and `endIndex`, or +split at the given separator. This is similar to `String.prototype.split`. +Indices are retrieved from the selection if none are provided. -### join +**Parameters** -```js -join( values: Array<Object>, ?separator: Object | string ): Object -``` +- **value** `Object`: Value to modify. +- **string** `[(number|string)]`: Start index, or string at which to split. +- **endStr** `[number]`: End index. -Combine an array of Rich Text values into one, optionally separated by `separator`, which can be a Rich Text value, HTML string, or plain text string. This is similar to `Array.prototype.join`. +**Returns** -### concat +`Array`: An array of new values. + +### toggleFormat + +[src/index.js#L29-L29](src/index.js#L29-L29) + +Toggles a format object to a Rich Text value at the current selection. + +**Parameters** + +- **value** `Object`: Value to modify. +- **format** `Object`: Format to apply or remove. + +**Returns** + +`Object`: A new value with the format applied or removed. + +### toHTMLString + +[src/index.js#L28-L28](src/index.js#L28-L28) + +Create an HTML string from a Rich Text value. If a `multilineTag` is +provided, text separated by a line separator will be wrapped in it. + +**Parameters** + +- **$1** `Object`: Named argements. +- **$1.value** `Object`: Rich text value. +- **$1.multilineTag** `[string]`: Multiline tag. +- **$1.multilineWrapperTags** `[Array]`: Tags where lines can be found if nesting is possible. + +**Returns** + +`string`: HTML string. + +### unregisterFormatType + +[src/index.js#L31-L31](src/index.js#L31-L31) + +Unregisters a format. + +**Parameters** + +- **name** `string`: Format name. + +**Returns** + +`(WPFormat|undefined)`: The previous format value, if it has been successfully unregistered; otherwise `undefined`. -```js -concat( ...values: Array<Object> ): Object -``` -Combine all Rich Text values into one. This is similar to `String.prototype.concat`. +<!-- END TOKEN(Autogenerated API docs) --> <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/rich-text/src/apply-format.js b/packages/rich-text/src/apply-format.js index 271be1176931d..8134cd8d742a9 100644 --- a/packages/rich-text/src/apply-format.js +++ b/packages/rich-text/src/apply-format.js @@ -15,10 +15,10 @@ import { normaliseFormats } from './normalise-formats'; * given `endIndex`. Indices are retrieved from the selection if none are * provided. * - * @param {Object} value Value to modify. - * @param {Object} format Format to apply. - * @param {number} startIndex Start index. - * @param {number} endIndex End index. + * @param {Object} value Value to modify. + * @param {Object} format Format to apply. + * @param {number} [startIndex] Start index. + * @param {number} [endIndex] End index. * * @return {Object} A new value with the format applied. */ diff --git a/packages/rich-text/src/char-at.js b/packages/rich-text/src/char-at.js index 6f04c2e2ac1aa..e68d3b6a5a5ac 100644 --- a/packages/rich-text/src/char-at.js +++ b/packages/rich-text/src/char-at.js @@ -5,7 +5,7 @@ * @param {Object} value Value to get the character from. * @param {string} index Index to use. * - * @return {?string} A one character long string, or undefined. + * @return {string|undefined} A one character long string, or undefined. */ export function charAt( { text }, index ) { return text[ index ]; diff --git a/packages/rich-text/src/create.js b/packages/rich-text/src/create.js index 82348bbd4fa8a..9fa677828209d 100644 --- a/packages/rich-text/src/create.js +++ b/packages/rich-text/src/create.js @@ -89,14 +89,14 @@ function toFormat( { type, attributes } ) { * `multilineTag` will be separated by two newlines. The optional functions can * be used to filter out content. * - * @param {?Object} $1 Optional named argements. - * @param {?Element} $1.element Element to create value from. - * @param {?string} $1.text Text to create value from. - * @param {?string} $1.html HTML to create value from. - * @param {?Range} $1.range Range to create value from. - * @param {?string} $1.multilineTag Multiline tag if the structure is + * @param {Object} [$1] Optional named arguments. + * @param {Element} [$1.element] Element to create value from. + * @param {string} [$1.text] Text to create value from. + * @param {string} [$1.html] HTML to create value from. + * @param {Range} [$1.range] Range to create value from. + * @param {string} [$1.multilineTag] Multiline tag if the structure is * multiline. - * @param {?Array} $1.multilineWrapperTags Tags where lines can be found if + * @param {Array} [$1.multilineWrapperTags] Tags where lines can be found if * nesting is possible. * * @return {Object} A rich text value. diff --git a/packages/rich-text/src/get-active-format.js b/packages/rich-text/src/get-active-format.js index 89fcb5fc7711c..fd658bf6adc27 100644 --- a/packages/rich-text/src/get-active-format.js +++ b/packages/rich-text/src/get-active-format.js @@ -19,7 +19,7 @@ import { getActiveFormats } from './get-active-formats'; * @param {Object} value Value to inspect. * @param {string} formatType Format type to look for. * - * @return {?Object} Active format object of the specified type, or undefined. + * @return {Object|undefined} Active format object of the specified type, or undefined. */ export function getActiveFormat( value, formatType ) { return find( getActiveFormats( value ), { type: formatType } ); diff --git a/packages/rich-text/src/get-selection-end.js b/packages/rich-text/src/get-selection-end.js index 90dee341391c1..8154a48b451fb 100644 --- a/packages/rich-text/src/get-selection-end.js +++ b/packages/rich-text/src/get-selection-end.js @@ -5,7 +5,7 @@ * * @param {Object} value Value to get the selection from. * - * @return {?number} Index where the selection ends. + * @return {number|undefined} Index where the selection ends. */ export function getSelectionEnd( { end } ) { return end; diff --git a/packages/rich-text/src/get-selection-start.js b/packages/rich-text/src/get-selection-start.js index 948d380052314..442d84d308691 100644 --- a/packages/rich-text/src/get-selection-start.js +++ b/packages/rich-text/src/get-selection-start.js @@ -5,7 +5,7 @@ * * @param {Object} value Value to get the selection from. * - * @return {?number} Index where the selection starts. + * @return {number|undefined} Index where the selection starts. */ export function getSelectionStart( { start } ) { return start; diff --git a/packages/rich-text/src/indent-list-items.js b/packages/rich-text/src/indent-list-items.js index 516b3c85f96ea..1a61bd7db1536 100644 --- a/packages/rich-text/src/indent-list-items.js +++ b/packages/rich-text/src/indent-list-items.js @@ -40,7 +40,7 @@ function getTargetLevelLineIndex( { text, formats }, lineIndex ) { * Indents any selected list items if possible. * * @param {Object} value Value to change. - * @param {Object} rootFormat + * @param {Object} rootFormat Root format. * * @return {Object} The changed value. */ diff --git a/packages/rich-text/src/insert-line-separator.js b/packages/rich-text/src/insert-line-separator.js index 24174668d25cb..e273c9eea9ac4 100644 --- a/packages/rich-text/src/insert-line-separator.js +++ b/packages/rich-text/src/insert-line-separator.js @@ -11,9 +11,9 @@ import { LINE_SEPARATOR } from './special-characters'; * `startIndex`. Any content between `startIndex` and `endIndex` will be * removed. Indices are retrieved from the selection if none are provided. * - * @param {Object} value Value to modify. - * @param {number} startIndex Start index. - * @param {number} endIndex End index. + * @param {Object} value Value to modify. + * @param {number} [startIndex] Start index. + * @param {number} [endIndex] End index. * * @return {Object} A new value with the value inserted. */ diff --git a/packages/rich-text/src/insert-object.js b/packages/rich-text/src/insert-object.js index bf67ac3913b73..fcdfc6f897c2d 100644 --- a/packages/rich-text/src/insert-object.js +++ b/packages/rich-text/src/insert-object.js @@ -13,8 +13,8 @@ const OBJECT_REPLACEMENT_CHARACTER = '\ufffc'; * * @param {Object} value Value to modify. * @param {Object} formatToInsert Format to insert as object. - * @param {number} startIndex Start index. - * @param {number} endIndex End index. + * @param {number} [startIndex] Start index. + * @param {number} [endIndex] End index. * * @return {Object} A new value with the object inserted. */ diff --git a/packages/rich-text/src/insert.js b/packages/rich-text/src/insert.js index 10c2ec0e9621a..cf46305240fb7 100644 --- a/packages/rich-text/src/insert.js +++ b/packages/rich-text/src/insert.js @@ -11,10 +11,10 @@ import { normaliseFormats } from './normalise-formats'; * and `endIndex` will be removed. Indices are retrieved from the selection if * none are provided. * - * @param {Object} value Value to modify. - * @param {string} valueToInsert Value to insert. - * @param {number} startIndex Start index. - * @param {number} endIndex End index. + * @param {Object} value Value to modify. + * @param {Object|string} valueToInsert Value to insert. + * @param {number} [startIndex] Start index. + * @param {number} [endIndex] End index. * * @return {Object} A new value with the value inserted. */ diff --git a/packages/rich-text/src/is-collapsed.js b/packages/rich-text/src/is-collapsed.js index 7c1e49b5a7cd4..7014d510377c2 100644 --- a/packages/rich-text/src/is-collapsed.js +++ b/packages/rich-text/src/is-collapsed.js @@ -6,8 +6,8 @@ * * @param {Object} value The rich text value to check. * - * @return {?boolean} True if the selection is collapsed, false if not, - * undefined if there is no selection. + * @return {boolean|undefined} True if the selection is collapsed, false if not, + * undefined if there is no selection. */ export function isCollapsed( { start, end } ) { if ( start === undefined || end === undefined ) { diff --git a/packages/rich-text/src/join.js b/packages/rich-text/src/join.js index c2e3c103b0674..7784b5962ca53 100644 --- a/packages/rich-text/src/join.js +++ b/packages/rich-text/src/join.js @@ -10,8 +10,8 @@ import { normaliseFormats } from './normalise-formats'; * `separator`, which can be a Rich Text value, HTML string, or plain text * string. This is similar to `Array.prototype.join`. * - * @param {Array} values An array of values to join. - * @param {string|Object} separator Separator string or value. + * @param {Array<Object>} values An array of values to join. + * @param {string|Object} [separator] Separator string or value. * * @return {Object} A new combined value. */ diff --git a/packages/rich-text/src/register-format-type.js b/packages/rich-text/src/register-format-type.js index 262812a22b248..8b07559541984 100644 --- a/packages/rich-text/src/register-format-type.js +++ b/packages/rich-text/src/register-format-type.js @@ -26,11 +26,15 @@ const EMPTY_ARRAY = []; * Registers a new format provided a unique name and an object defining its * behavior. * - * @param {string} name Format name. - * @param {Object} settings Format settings. + * @param {string} name Format name. + * @param {Object} settings Format settings. + * @param {string} settings.tagName The HTML tag this format will wrap the selection with. + * @param {string} [settings.className] A class to match the format. + * @param {string} settings.title Name of the format. + * @param {Function} settings.edit Should return a component for the user to interact with the new registered format. * - * @return {?WPFormat} The format, if it has been successfully registered; - * otherwise `undefined`. + * @return {WPFormat|undefined} The format, if it has been successfully registered; + * otherwise `undefined`. */ export function registerFormatType( name, settings ) { settings = { diff --git a/packages/rich-text/src/remove-format.js b/packages/rich-text/src/remove-format.js index d6f2890f3afaf..be1c92ace7fd4 100644 --- a/packages/rich-text/src/remove-format.js +++ b/packages/rich-text/src/remove-format.js @@ -15,10 +15,10 @@ import { normaliseFormats } from './normalise-formats'; * `startIndex` to the given `endIndex`. Indices are retrieved from the * selection if none are provided. * - * @param {Object} value Value to modify. - * @param {string} formatType Format type to remove. - * @param {number} startIndex Start index. - * @param {number} endIndex End index. + * @param {Object} value Value to modify. + * @param {string} formatType Format type to remove. + * @param {number} [startIndex] Start index. + * @param {number} [endIndex] End index. * * @return {Object} A new value with the format applied. */ diff --git a/packages/rich-text/src/remove.js b/packages/rich-text/src/remove.js index de4241ad301e6..c3af472167baa 100644 --- a/packages/rich-text/src/remove.js +++ b/packages/rich-text/src/remove.js @@ -9,9 +9,9 @@ import { create } from './create'; * Remove content from a Rich Text value between the given `startIndex` and * `endIndex`. Indices are retrieved from the selection if none are provided. * - * @param {Object} value Value to modify. - * @param {number} startIndex Start index. - * @param {number} endIndex End index. + * @param {Object} value Value to modify. + * @param {number} [startIndex] Start index. + * @param {number} [endIndex] End index. * * @return {Object} A new value with the content removed. */ diff --git a/packages/rich-text/src/slice.js b/packages/rich-text/src/slice.js index 3a54642b98cc7..bb4313dd61309 100644 --- a/packages/rich-text/src/slice.js +++ b/packages/rich-text/src/slice.js @@ -3,9 +3,9 @@ * retrieved from the selection if none are provided. This is similar to * `String.prototype.slice`. * - * @param {Object} value Value to modify. - * @param {number} startIndex Start index. - * @param {number} endIndex End index. + * @param {Object} value Value to modify. + * @param {number} [startIndex] Start index. + * @param {number} [endIndex] End index. * * @return {Object} A new extracted value. */ diff --git a/packages/rich-text/src/special-characters.js b/packages/rich-text/src/special-characters.js index 611869f8dbe56..5695c40c745b9 100644 --- a/packages/rich-text/src/special-characters.js +++ b/packages/rich-text/src/special-characters.js @@ -1,2 +1,5 @@ +/** + * Line separator character. + */ export const LINE_SEPARATOR = '\u2028'; export const OBJECT_REPLACEMENT_CHARACTER = '\ufffc'; diff --git a/packages/rich-text/src/split.js b/packages/rich-text/src/split.js index d8b40b5aafb75..a300c3ccbcca4 100644 --- a/packages/rich-text/src/split.js +++ b/packages/rich-text/src/split.js @@ -9,9 +9,9 @@ import { replace } from './replace'; * split at the given separator. This is similar to `String.prototype.split`. * Indices are retrieved from the selection if none are provided. * - * @param {Object} value Value to modify. - * @param {number|string} string Start index, or string at which to split. - * @param {number} end End index. + * @param {Object} value Value to modify. + * @param {number|string} [string] Start index, or string at which to split. + * @param {number} [endStr] End index. * * @return {Array} An array of new values. */ diff --git a/packages/rich-text/src/to-dom.js b/packages/rich-text/src/to-dom.js index 987849172bf5f..b81c518468444 100644 --- a/packages/rich-text/src/to-dom.js +++ b/packages/rich-text/src/to-dom.js @@ -164,10 +164,11 @@ export function toDom( { * the `Element` tree contained by `current`. If a `multilineTag` is provided, * text separated by two new lines will be wrapped in an `Element` of that type. * - * @param {Object} value Value to apply. - * @param {HTMLElement} current The live root node to apply the element - * tree to. - * @param {string} multilineTag Multiline tag. + * @param {Object} $1 Named arguments. + * @param {Object} $1.value Value to apply. + * @param {HTMLElement} $1.current The live root node to apply the element tree to. + * @param {string} [$1.multilineTag] Multiline tag. + * @param {Array} [$1.multilineWrapperTags] Tags where lines can be found if nesting is possible. */ export function apply( { value, diff --git a/packages/rich-text/src/to-html-string.js b/packages/rich-text/src/to-html-string.js index 324dd2d65d287..0ba36e62510a3 100644 --- a/packages/rich-text/src/to-html-string.js +++ b/packages/rich-text/src/to-html-string.js @@ -18,11 +18,11 @@ import { toTree } from './to-tree'; * Create an HTML string from a Rich Text value. If a `multilineTag` is * provided, text separated by a line separator will be wrapped in it. * - * @param {Object} $1 Named argements. - * @param {Object} $1.value Rich text value. - * @param {string} $1.multilineTag Multiline tag. - * @param {Array} $1.multilineWrapperTags Tags where lines can be found if - * nesting is possible. + * @param {Object} $1 Named argements. + * @param {Object} $1.value Rich text value. + * @param {string} [$1.multilineTag] Multiline tag. + * @param {Array} [$1.multilineWrapperTags] Tags where lines can be found if + * nesting is possible. * * @return {string} HTML string. */ diff --git a/packages/rich-text/src/toggle-format.js b/packages/rich-text/src/toggle-format.js index 147d9524df246..6e7854dcaa662 100644 --- a/packages/rich-text/src/toggle-format.js +++ b/packages/rich-text/src/toggle-format.js @@ -9,8 +9,8 @@ import { applyFormat } from './apply-format'; /** * Toggles a format object to a Rich Text value at the current selection. * - * @param {Object} value Value to modify. - * @param {Object} format Format to apply or remove. + * @param {Object} value Value to modify. + * @param {Object} format Format to apply or remove. * * @return {Object} A new value with the format applied or removed. */ diff --git a/packages/rich-text/src/unregister-format-type.js b/packages/rich-text/src/unregister-format-type.js index cffaa3d025b94..6f6741183ee16 100644 --- a/packages/rich-text/src/unregister-format-type.js +++ b/packages/rich-text/src/unregister-format-type.js @@ -9,8 +9,8 @@ import { removeFilter } from '@wordpress/hooks'; * * @param {string} name Format name. * - * @return {?WPFormat} The previous format value, if it has been successfully - * unregistered; otherwise `undefined`. + * @return {WPFormat|undefined} The previous format value, if it has been successfully + * unregistered; otherwise `undefined`. */ export function unregisterFormatType( name ) { const oldFormat = select( 'core/rich-text' ).getFormatType( name ); From adb10c1da473db4eb9435716166bead739b6b436 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Thu, 7 Mar 2019 06:43:05 -0800 Subject: [PATCH 591/691] Add link to WordPress Support documentation (#14316) --- docs/readme.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/readme.md b/docs/readme.md index a95a1b0ea5abf..20f6d15625388 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -10,7 +10,9 @@ Start with [the Designer & Developer Handbook](/docs/designers-developers/readme ## User Handbook -Discover the new features Gutenberg offers, learn how your site will be affected by the new editor and how to keep using the old interface, and tips for creating beautiful posts and pages. +Discover the new features WordPress offers, learn how your site will be affected by the new editor and how to keep using the old interface, and tips for creating beautiful posts and pages. + +See [the WordPress Editor](https://wordpress.org/support/article/wordpress-editor/) support documentation. ## Contributor Handbook From 9e91923f58003b4113850e78fa356f800b92ef9f Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Thu, 7 Mar 2019 07:04:42 -0800 Subject: [PATCH 592/691] Remove users documentation (#14318) --- docs/users/readme.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 docs/users/readme.md diff --git a/docs/users/readme.md b/docs/users/readme.md deleted file mode 100644 index e69de29bb2d1d..0000000000000 From 075580ab2d85e976c39243c72174a965630dee9d Mon Sep 17 00:00:00 2001 From: Thiago Locks <thiago@zira.com.br> Date: Thu, 7 Mar 2019 13:49:01 -0300 Subject: [PATCH 593/691] Fix the double dash issue (#14321) The double dash was being converted to a single dash (&#8211;) --- .../developers/tutorials/javascript/js-build-setup.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md b/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md index 4be9fcc1efbd1..067e99e5790f2 100644 --- a/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md +++ b/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md @@ -127,7 +127,7 @@ module.exports = { Next, you need to install babel, the webpack loader, and the JSX plugin using: -> npm install --save-dev --save-exact babel-loader @babel/core @babel/plugin-transform-react-jsx +`npm install --save-dev --save-exact babel-loader @babel/core @babel/plugin-transform-react-jsx` You configure babel by creating a `.babelrc` file: From 94a1068f5c84fa505c12038fbf0ad96591b3f25b Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Thu, 7 Mar 2019 16:13:58 -0500 Subject: [PATCH 594/691] docgen: Generate package docs in parallel (#14295) --- bin/update-readmes.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/bin/update-readmes.js b/bin/update-readmes.js index fb1be92aa4a34..02d1fa8a20563 100755 --- a/bin/update-readmes.js +++ b/bin/update-readmes.js @@ -1,7 +1,8 @@ #!/usr/bin/env node const path = require( 'path' ); -const childProcess = require( 'child_process' ); +const { promisify } = require( 'util' ); +const spawn = promisify( require( 'child_process' ).spawn ); const packages = [ 'a11y', @@ -53,19 +54,18 @@ const getArgsForPackage = ( packageName ) => { } }; -let aggregatedExitCode = 0; -packages.forEach( ( packageName ) => { +Promise.all( packages.map( async ( packageName ) => { const args = getArgsForPackage( packageName ); const pathToDocGen = path.join( __dirname, '..', 'node_modules', '.bin', 'docgen' ); - const { status, stderr } = childProcess.spawnSync( + const { status, stderr } = await spawn( pathToDocGen, args, { shell: true }, ); if ( status !== 0 ) { - aggregatedExitCode = status; - process.stderr.write( `${ stderr }\n` ); + throw stderr.toString(); } +} ) ).catch( ( error ) => { + process.stderr.write( `${ error }\n` ); + process.exit( 1 ); } ); - -process.exit( aggregatedExitCode ); From d965c6ed876062284056b187186b63b148009308 Mon Sep 17 00:00:00 2001 From: Darren Ethier <darren@roughsmootheng.in> Date: Fri, 8 Mar 2019 01:10:38 -0500 Subject: [PATCH 595/691] Add new actions for invalidating resolution caches (#14225) * add new actions for invalidating resolution caches * use path array for omit * clarify logic --- packages/data/CHANGELOG.md | 2 + packages/data/src/store/actions.js | 36 ++++++++++++++++++ packages/data/src/store/reducer.js | 42 +++++++++++++++++++-- packages/data/src/store/test/reducer.js | 49 +++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 4 deletions(-) diff --git a/packages/data/CHANGELOG.md b/packages/data/CHANGELOG.md index 15a723006d351..34cfc2e0096e1 100644 --- a/packages/data/CHANGELOG.md +++ b/packages/data/CHANGELOG.md @@ -3,6 +3,8 @@ ### Enhancements - The `registerStore` function now accepts an optional `initialState` option value. +- Introduce new `invalidateResolutionForStore` dispatch action for signalling to invalidate the resolution cache for an entire given store. +- Introduce new `invalidateResolutionForStoreSelector` dispatch action for signalling to invalidate the resolution cache for a store selector (and all variations of arguments on that selector). ### Bug Fix diff --git a/packages/data/src/store/actions.js b/packages/data/src/store/actions.js index e4d887ee3e58a..b7bd9aa805738 100644 --- a/packages/data/src/store/actions.js +++ b/packages/data/src/store/actions.js @@ -53,3 +53,39 @@ export function invalidateResolution( reducerKey, selectorName, args ) { args, }; } + +/** + * Returns an action object used in signalling that the resolution cache for a + * given reducerKey should be invalidated. + * + * @param {string} reducerKey Registered store reducer key. + * + * @return {Object} Action object. + */ +export function invalidateResolutionForStore( reducerKey ) { + return { + type: 'INVALIDATE_RESOLUTION_FOR_STORE', + reducerKey, + }; +} + +/** + * Returns an action object used in signalling that the resolution cache for a + * given reducerKey and selectorName should be invalidated. + * + * @param {string} reducerKey Registered store reducer key. + * @param {string} selectorName Name of selector for which all resolvers should + * be invalidated. + * + * @return {Object} Action object. + */ +export function invalidateResolutionForStoreSelector( + reducerKey, + selectorName +) { + return { + type: 'INVALIDATE_RESOLUTION_FOR_STORE_SELECTOR', + reducerKey, + selectorName, + }; +} diff --git a/packages/data/src/store/reducer.js b/packages/data/src/store/reducer.js index 69cd865766234..cd043f753f833 100644 --- a/packages/data/src/store/reducer.js +++ b/packages/data/src/store/reducer.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { flowRight } from 'lodash'; +import { flowRight, omit, has } from 'lodash'; import EquivalentKeyMap from 'equivalent-key-map'; /** @@ -10,7 +10,8 @@ import EquivalentKeyMap from 'equivalent-key-map'; import { onSubKey } from './utils'; /** - * Reducer function returning next state for selector resolution, object form: + * Reducer function returning next state for selector resolution of + * subkeys, object form: * * reducerKey -> selectorName -> EquivalentKeyMap<Array,boolean> * @@ -19,7 +20,7 @@ import { onSubKey } from './utils'; * * @returns {Object} Next state. */ -const isResolved = flowRight( [ +const subKeysIsResolved = flowRight( [ onSubKey( 'reducerKey' ), onSubKey( 'selectorName' ), ] )( ( state = new EquivalentKeyMap(), action ) => { @@ -37,8 +38,41 @@ const isResolved = flowRight( [ return nextState; } } - return state; } ); +/** + * Reducer function returning next state for selector resolution, object form: + * + * reducerKey -> selectorName -> EquivalentKeyMap<Array, boolean> + * + * @param {Object} state Current state. + * @param {Object} action Dispatched action. + * + * @return {Object} Next state. + */ +const isResolved = ( state = {}, action ) => { + switch ( action.type ) { + case 'INVALIDATE_RESOLUTION_FOR_STORE': + return has( state, action.reducerKey ) ? + omit( state, [ action.reducerKey ] ) : + state; + case 'INVALIDATE_RESOLUTION_FOR_STORE_SELECTOR': + return has( state, [ action.reducerKey, action.selectorName ] ) ? + { + ...state, + [ action.reducerKey ]: omit( + state[ action.reducerKey ], + [ action.selectorName ] + ), + } : + state; + case 'START_RESOLUTION': + case 'FINISH_RESOLUTION': + case 'INVALIDATE_RESOLUTION': + return subKeysIsResolved( state, action ); + } + return state; +}; + export default isResolved; diff --git a/packages/data/src/store/test/reducer.js b/packages/data/src/store/test/reducer.js index 186703c7738a2..eab156e89a71e 100644 --- a/packages/data/src/store/test/reducer.js +++ b/packages/data/src/store/test/reducer.js @@ -93,4 +93,53 @@ describe( 'reducer', () => { expect( state.test.getFoo.get( [ 'post' ] ) ).toBe( false ); expect( state.test.getFoo.get( [ 'block' ] ) ).toBe( true ); } ); + + it( 'should remove invalidation for store level and leave others ' + + 'intact', () => { + const original = reducer( undefined, { + type: 'FINISH_RESOLUTION', + reducerKey: 'testA', + selectorName: 'getFoo', + args: [ 'post' ], + } ); + let state = reducer( deepFreeze( original ), { + type: 'FINISH_RESOLUTION', + reducerKey: 'testB', + selectorName: 'getBar', + args: [ 'postBar' ], + } ); + state = reducer( deepFreeze( state ), { + type: 'INVALIDATE_RESOLUTION_FOR_STORE', + reducerKey: 'testA', + } ); + + expect( state.testA ).toBeUndefined(); + // { testB: { getBar: EquivalentKeyMap( [] => false ) } } + expect( state.testB.getBar.get( [ 'postBar' ] ) ).toBe( false ); + } ); + + it( 'should remove invalidation for store and selector name level and ' + + 'leave other selectors at store level intact', () => { + const original = reducer( undefined, { + type: 'FINISH_RESOLUTION', + reducerKey: 'test', + selectorName: 'getFoo', + args: [ 'post' ], + } ); + let state = reducer( deepFreeze( original ), { + type: 'FINISH_RESOLUTION', + reducerKey: 'test', + selectorName: 'getBar', + args: [ 'postBar' ], + } ); + state = reducer( deepFreeze( state ), { + type: 'INVALIDATE_RESOLUTION_FOR_STORE_SELECTOR', + reducerKey: 'test', + selectorName: 'getBar', + } ); + + expect( state.test.getBar ).toBeUndefined(); + // { test: { getFoo: EquivalentKeyMap( [] => false ) } } + expect( state.test.getFoo.get( [ 'post' ] ) ).toBe( false ); + } ); } ); From 0b84ace2b77c36a5fa0e5cbbf6237b9007714122 Mon Sep 17 00:00:00 2001 From: Tim Elsass <timelsass@users.noreply.github.com> Date: Fri, 8 Mar 2019 01:20:57 -0500 Subject: [PATCH 596/691] CC-BY-3.0 is not GPLv2 compatible. (#14329) --- packages/scripts/scripts/check-licenses.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/scripts/scripts/check-licenses.js b/packages/scripts/scripts/check-licenses.js index 2bc95d51e0913..54b906f30e37b 100644 --- a/packages/scripts/scripts/check-licenses.js +++ b/packages/scripts/scripts/check-licenses.js @@ -47,7 +47,6 @@ const gpl2CompatibleLicenses = [ 'BSD-3-Clause', 'BSD-3-Clause-W3C', 'BSD-like', - 'CC-BY-3.0', 'CC-BY-4.0', 'CC0-1.0', 'GPL-2.0', @@ -79,6 +78,7 @@ const otherOssLicenses = [ 'Apache 2.0', 'Apache License, Version 2.0', 'Apache version 2.0', + 'CC-BY-3.0', ]; const licenses = [ From 5d1d1dedc1c10e2845e955af7c5984dfe6d2a633 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Fri, 8 Mar 2019 07:41:00 +0100 Subject: [PATCH 597/691] blocks: set up auto-generated API docs (#14279) --- bin/update-readmes.js | 2 +- packages/blocks/README.md | 751 +++++++++++++++++++++++++++++++++++--- 2 files changed, 692 insertions(+), 61 deletions(-) diff --git a/bin/update-readmes.js b/bin/update-readmes.js index 02d1fa8a20563..355703c8810e1 100755 --- a/bin/update-readmes.js +++ b/bin/update-readmes.js @@ -11,7 +11,7 @@ const packages = [ 'block-editor', 'block-library', //'block-serialization-default-parser', - //'blocks', + 'blocks', 'compose', //'data', //'date', diff --git a/packages/blocks/README.md b/packages/blocks/README.md index 380ea9b9b9da4..e2074fa23bacc 100644 --- a/packages/blocks/README.md +++ b/packages/blocks/README.md @@ -36,7 +36,7 @@ function myplugin_enqueue_block_editor_assets() { add_action( 'enqueue_block_editor_assets', 'myplugin_enqueue_block_editor_assets' ); ``` -The `enqueue_block_editor_assets` hook is only run in the Gutenberg editor context when the editor is ready to receive additional scripts and stylesheets. There is also an `enqueue_block_assets` hook which is run under __both__ the editor and front-end contexts. This should be used to enqueue stylesheets common to the front-end and the editor. (The rules can be overridden in the editor-specific stylesheet if necessary.) +The `enqueue_block_editor_assets` hook is only run in the Gutenberg editor context when the editor is ready to receive additional scripts and stylesheets. There is also an `enqueue_block_assets` hook which is run under **both** the editor and front-end contexts. This should be used to enqueue stylesheets common to the front-end and the editor. (The rules can be overridden in the editor-specific stylesheet if necessary.) The following sections will describe what you'll need to include in `block.js` to describe the behavior of your custom block. @@ -48,20 +48,20 @@ Let's imagine you wanted to define a block to show a randomly generated image in Take a step back and consider the ideal workflow for adding a new random image: -- Insert the block. It should be shown in some empty state, with an option to choose a category in a select dropdown. -- Upon confirming my selection, a preview of the image should be shown next to the dropdown. +- Insert the block. It should be shown in some empty state, with an option to choose a category in a select dropdown. +- Upon confirming my selection, a preview of the image should be shown next to the dropdown. At this point, you might realize that while you'd want some controls to be shown when editing content, the markup included in the published post might not appear the same (your visitors should not see a dropdown field when reading your content). This leads to the first requirement of describing a block: -__You will need to provide implementations both for what's to be shown in an editor and what's to be saved with the published content__. +**You will need to provide implementations both for what's to be shown in an editor and what's to be saved with the published content**. To eliminate redundant effort here, share common behaviors by splitting your code up into [components](/packages/element/README.md). -Now that we've considered user interaction, you should think about the underlying values that determine the markup generated by your block. In our example, the output is affected only when the category changes. Put another way: __the output of a block is a function of its attributes__. +Now that we've considered user interaction, you should think about the underlying values that determine the markup generated by your block. In our example, the output is affected only when the category changes. Put another way: **the output of a block is a function of its attributes**. -The category, a simple string, is the only thing we require to be able to generate the image we want to include in the published content. We call these underlying values of a block instance its __attributes__. +The category, a simple string, is the only thing we require to be able to generate the image we want to include in the published content. We call these underlying values of a block instance its **attributes**. With these concepts in mind, let's explore an implementation of our random image block: @@ -151,67 +151,698 @@ _[(Example in ES2015+, JSX)](https://gist.github.com/aduth/fb1cc9a2296110a62b963 Let's briefly review a few items you might observe in the implementation: -- When registering a new block, you must prefix its name with a namespace for - your plugin. This helps prevent conflicts when more than one plugin registers - a block with the same name. -- You will use `createElement` to describe the structure of your block's - markup. See the [Element documentation](/packages/element/README.md) for more - information. -- Extracting `RandomImage` to a separate function allows us to reuse it in both - the editor-specific interface and the published content. -- The `edit` function should handle any case where an attribute is unset, as in - the case of the block being newly inserted. -- We only change the attributes of a block by calling the `setAttributes` - helper. Never assign a value on the attributes object directly. -- React provides conveniences for working with `select` element with - [`value` and `onChange` props](https://facebook.github.io/react/docs/forms.html#the-select-tag). +- When registering a new block, you must prefix its name with a namespace for + your plugin. This helps prevent conflicts when more than one plugin registers + a block with the same name. +- You will use `createElement` to describe the structure of your block's + markup. See the [Element documentation](/packages/element/README.md) for more + information. +- Extracting `RandomImage` to a separate function allows us to reuse it in both + the editor-specific interface and the published content. +- The `edit` function should handle any case where an attribute is unset, as in + the case of the block being newly inserted. +- We only change the attributes of a block by calling the `setAttributes` + helper. Never assign a value on the attributes object directly. +- React provides conveniences for working with `select` element with + [`value` and `onChange` props](https://facebook.github.io/react/docs/forms.html#the-select-tag). By concerning yourself only with describing the markup of a block given its attributes, you need not worry about maintaining the state of the page, or how your block interacts in the context of the surrounding editor. -But how does the markup become an object of attributes? We need a pattern for encoding the values into the published post's markup, and then retrieving them the next time the post is edited. This is the motivation for the block's `attributes` property. The shape of this object matches that of the attributes object we'd like to receive, where each value is a [__source__](http://github.com/aduth/hpq) which tries to find the desired value from the markup of the block. +But how does the markup become an object of attributes? We need a pattern for encoding the values into the published post's markup, and then retrieving them the next time the post is edited. This is the motivation for the block's `attributes` property. The shape of this object matches that of the attributes object we'd like to receive, where each value is a [**source**](http://github.com/aduth/hpq) which tries to find the desired value from the markup of the block. In the random image block above, we've given the `alt` attribute of the image a secondary responsibility of tracking the selected category. There are a few other ways we could have achieved this, but the category value happens to work well as an `alt` descriptor. In the `attributes` property, we define an object with a key of `category` whose value tries to find this `alt` attribute of the markup. If it's successful, the category's value in our `edit` and `save` functions will be assigned. In the case of a new block or invalid markup, this value would be `undefined`, so we adjust our return value accordingly. ## API -### `wp.blocks.registerBlockType( name: string, typeDefinition: Object )` - -Registers a new block provided a unique name and an object defining its behavior. Once registered, the block is made available as an option to any editor interface where blocks are implemented. - -- `title: string` - A human-readable - [localized](https://codex.wordpress.org/I18n_for_WordPress_Developers#Handling_JavaScript_files) - label for the block. Shown in the block inserter. -- `icon: string | WPElement | Function | Object` - Slug of the - [Dashicon](https://developer.wordpress.org/resource/dashicons/#awards) - to be shown in the control's button, or an element (or function returning an - element) if you choose to render your own SVG. - An object can also be passed, in this case, icon, as specified above, should be included in the src property. - Besides src the object can contain background and foreground colors, this colors will appear with the icon - when they are applicable e.g.: in the inserter. -- `attributes: Object` - An object of attribute schemas, where the - keys of the object define the shape of attributes, and each value an object - schema describing the `type`, `default` (optional), and - [`source`](https://wordpress.org/gutenberg/handbook/block-api/attributes/) - (optional) of the attribute. If `source` is omitted, the attribute is - serialized into the block's comment delimiters. -- `category: string` - Slug of the block's category. The category is used to - organize the blocks in the block inserter. -- `edit( { attributes: Object, setAttributes: Function } ): WPElement` - - Returns an element describing the markup of a block to be shown in the - editor. A block can update its own state in response to events using the - `setAttributes` function, passing an object of properties to be applied as a - partial update. -- `save( { attributes: Object } ): WPElement` - Returns an element describing - the markup of a block to be saved in the published content. This function is - called before save and when switching to an editor's HTML view. -- `keywords` - An optional array of keywords used to filter the block list. - -### `wp.blocks.getBlockType( name: string )` - -Returns type definitions associated with a registered block. - -### `wp.blocks.getControlSettings( name: string )` - -Returns settings associated with a registered control. +<!-- START TOKEN(Autogenerated API docs) --> + +### children + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Undocumented declaration. + +### cloneBlock + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Given a block object, returns a copy of the block object, optionally merging +new attributes and/or replacing its inner blocks. + +**Parameters** + +- **block** `Object`: Block instance. +- **mergeAttributes** `Object`: Block attributes. +- **newInnerBlocks** `?Array`: Nested blocks. + +**Returns** + +`Object`: A cloned block. + +### createBlock + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Returns a block object given its type and attributes. + +**Parameters** + +- **name** `string`: Block name. +- **attributes** `Object`: Block attributes. +- **innerBlocks** `?Array`: Nested blocks. + +**Returns** + +`Object`: Block object. + +### doBlocksMatchTemplate + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Checks whether a list of blocks matches a template by comparing the block names. + +**Parameters** + +- **blocks** `Array`: Block list. +- **template** `Array`: Block template. + +**Returns** + +`boolean`: Whether the list of blocks matches a templates + +### findTransform + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Given an array of transforms, returns the highest-priority transform where +the predicate function returns a truthy value. A higher-priority transform +is one with a lower priority value (i.e. first in priority order). Returns +null if the transforms set is empty or the predicate function returns a +falsey value for all entries. + +**Parameters** + +- **transforms** `Array<Object>`: Transforms to search. +- **predicate** `Function`: Function returning true on matching transform. + +**Returns** + +`?Object`: Highest-priority transform candidate. + +### getBlockAttributes + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Returns the block attributes of a registered block node given its type. + +**Parameters** + +- **blockTypeOrName** `(string|Object)`: Block type or name. +- **innerHTML** `string`: Raw block content. +- **attributes** `?Object`: Known block attributes (from delimiters). + +**Returns** + +`Object`: All block attributes. + +### getBlockContent + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Given a block object, returns the Block's Inner HTML markup. + +**Parameters** + +- **block** `Object`: Block instance. + +**Returns** + +`string`: HTML. + +### getBlockDefaultClassName + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Returns the block's default classname from its name. + +**Parameters** + +- **blockName** `string`: The block name. + +**Returns** + +`string`: The block's default class. + +### getBlockMenuDefaultClassName + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Returns the block's default menu item classname from its name. + +**Parameters** + +- **blockName** `string`: The block name. + +**Returns** + +`string`: The block's default menu item class. + +### getBlockSupport + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Returns the block support value for a feature, if defined. + +**Parameters** + +- **nameOrType** `(string|Object)`: Block name or type object +- **feature** `string`: Feature to retrieve +- **defaultSupports** `*`: Default value to return if not explicitly defined + +**Returns** + +`?*`: Block support value + +### getBlockTransforms + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Returns normal block transforms for a given transform direction, optionally +for a specific block by name, or an empty array if there are no transforms. +If no block name is provided, returns transforms for all blocks. A normal +transform object includes `blockName` as a property. + +**Parameters** + +- **direction** `string`: Transform direction ("to", "from"). +- **blockTypeOrName** `(string|Object)`: Block type or name. + +**Returns** + +`Array`: Block transforms for direction. + +### getBlockType + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Returns a registered block type. + +**Parameters** + +- **name** `string`: Block name. + +**Returns** + +`?Object`: Block type. + +### getBlockTypes + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Returns all registered blocks. + +**Returns** + +`Array`: Block settings. + +### getCategories + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Returns all the block categories. + +**Returns** + +`Array<Object>`: Block categories. + +### getChildBlockNames + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Returns an array with the child blocks of a given block. + +**Parameters** + +- **blockName** `string`: Name of block (example: “latest-posts”). + +**Returns** + +`Array`: Array of child block names. + +### getDefaultBlockName + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Retrieves the default block name. + +**Returns** + +`?string`: Block name. + +### getFreeformContentHandlerName + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Retrieves name of block handling non-block content, or undefined if no +handler has been defined. + +**Returns** + +`?string`: Blog name. + +### getPhrasingContentSchema + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Get schema of possible paths for phrasing content. + +**Related** + +- <https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Phrasing_content> + +**Returns** + +`Object`: Schema. + +### getPossibleBlockTransformations + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Returns an array of block types that the set of blocks received as argument +can be transformed into. + +**Parameters** + +- **blocks** `Array`: Blocks array. + +**Returns** + +`Array`: Block types that the blocks argument can be transformed to. + +### getSaveContent + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Given a block type containing a save render implementation and attributes, returns the +static markup to be saved. + +**Parameters** + +- **blockTypeOrName** `(string|Object)`: Block type or name. +- **attributes** `Object`: Block attributes. +- **innerBlocks** `?Array`: Nested blocks. + +**Returns** + +`string`: Save content. + +### getSaveElement + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Given a block type containing a save render implementation and attributes, returns the +enhanced element to be saved or string when raw HTML expected. + +**Parameters** + +- **blockTypeOrName** `(string|Object)`: Block type or name. +- **attributes** `Object`: Block attributes. +- **innerBlocks** `?Array`: Nested blocks. + +**Returns** + +`(Object|string)`: Save element or raw HTML string. + +### getUnregisteredTypeHandlerName + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Retrieves name of block handling unregistered block types, or undefined if no +handler has been defined. + +**Returns** + +`?string`: Blog name. + +### hasBlockSupport + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Returns true if the block defines support for a feature, or false otherwise. + +**Parameters** + +- **nameOrType** `(string|Object)`: Block name or type object. +- **feature** `string`: Feature to test. +- **defaultSupports** `boolean`: Whether feature is supported by default if not explicitly defined. + +**Returns** + +`boolean`: Whether block supports feature. + +### hasChildBlocks + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Returns a boolean indicating if a block has child blocks or not. + +**Parameters** + +- **blockName** `string`: Name of block (example: “latest-posts”). + +**Returns** + +`boolean`: True if a block contains child blocks and false otherwise. + +### hasChildBlocksWithInserterSupport + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Returns a boolean indicating if a block has at least one child block with inserter support. + +**Parameters** + +- **blockName** `string`: Block type name. + +**Returns** + +`boolean`: True if a block contains at least one child blocks with inserter support and false otherwise. + +### isReusableBlock + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Determines whether or not the given block is a reusable block. This is a +special block type that is used to point to a global block stored via the +API. + +**Parameters** + +- **blockOrType** `Object`: Block or Block Type to test. + +**Returns** + +`boolean`: Whether the given block is a reusable block. + +### isUnmodifiedDefaultBlock + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Determines whether the block is a default block +and its attributes are equal to the default attributes +which means the block is unmodified. + +**Parameters** + +- **block** `WPBlock`: Block Object + +**Returns** + +`boolean`: Whether the block is an unmodified default block + +### isValidBlockContent + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Returns true if the parsed block is valid given the input content. A block +is considered valid if, when serialized with assumed attributes, the content +matches the original value. + +Logs to console in development environments when invalid. + +**Parameters** + +- **blockTypeOrName** `(string|Object)`: Block type. +- **attributes** `Object`: Parsed block attributes. +- **innerHTML** `string`: Original block content. + +**Returns** + +`boolean`: Whether block is valid. + +### isValidIcon + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Function that checks if the parameter is a valid icon. + +**Parameters** + +- **icon** `*`: Parameter to be checked. + +**Returns** + +`boolean`: True if the parameter is a valid icon and false otherwise. + +### node + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Undocumented declaration. + +### normalizeIconObject + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Function that receives an icon as set by the blocks during the registration +and returns a new icon object that is normalized so we can rely on just on possible icon structure +in the codebase. + +**Parameters** + +- **icon** `(Object|string|WPElement)`: Slug of the Dashicon to be shown as the icon for the block in the inserter, or element or an object describing the icon. + +**Returns** + +`Object`: Object describing the icon. + +### parse + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Parses the post content with a PegJS grammar and returns a list of blocks. + +**Parameters** + +- **content** `string`: The post content. + +**Returns** + +`Array`: Block list. + +### parseWithAttributeSchema + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Given a block's raw content and an attribute's schema returns the attribute's +value depending on its source. + +**Parameters** + +- **innerHTML** `string`: Block's raw content. +- **attributeSchema** `Object`: Attribute's schema. + +**Returns** + +`*`: Attribute value. + +### pasteHandler + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Converts an HTML string to known blocks. Strips everything else. + +**Parameters** + +- **options.HTML** `[string]`: The HTML to convert. +- **options.plainText** `[string]`: Plain text version. +- **options.mode** `[string]`: Handle content as blocks or inline content. _ 'AUTO': Decide based on the content passed. _ 'INLINE': Always handle as inline content, and return string. \* 'BLOCKS': Always handle as blocks, and return array of blocks. +- **options.tagName** `[Array]`: The tag into which content will be inserted. +- **options.canUserUseUnfilteredHTML** `[boolean]`: Whether or not the user can use unfiltered HTML. + +**Returns** + +`(Array|string)`: A list of blocks or a string, depending on `handlerMode`. + +### rawHandler + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Converts an HTML string to known blocks. + +**Parameters** + +- **$1.HTML** `string`: The HTML to convert. + +**Returns** + +`Array`: A list of blocks. + +### registerBlockStyle + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Registers a new block style variation for the given block. + +**Parameters** + +- **blockName** `string`: Name of block (example: “core/latest-posts”). +- **styleVariation** `Object`: Object containing `name` which is the class name applied to the block and `label` which identifies the variation to the user. + +### registerBlockType + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Registers a new block provided a unique name and an object defining its +behavior. Once registered, the block is made available as an option to any +editor interface where blocks are implemented. + +**Parameters** + +- **name** `string`: Block name. +- **settings** `Object`: Block settings. + +**Returns** + +`?WPBlock`: The block, if it has been successfully registered; otherwise `undefined`. + +### serialize + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Takes a block or set of blocks and returns the serialized post content. + +**Parameters** + +- **blocks** `Array`: Block(s) to serialize. + +**Returns** + +`string`: The post content. + +### setCategories + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Sets the block categories. + +**Parameters** + +- **categories** `Array<Object>`: Block categories. + +### setDefaultBlockName + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Assigns the default block name. + +**Parameters** + +- **name** `string`: Block name. + +### setFreeformContentHandlerName + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Assigns name of block for handling non-block content. + +**Parameters** + +- **blockName** `string`: Block name. + +### setUnregisteredTypeHandlerName + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Assigns name of block handling unregistered block types. + +**Parameters** + +- **blockName** `string`: Block name. + +### switchToBlockType + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Switch one or more blocks into one or more blocks of the new block type. + +**Parameters** + +- **blocks** `(Array|Object)`: Blocks array or block object. +- **name** `string`: Block name. + +**Returns** + +`Array`: Array of blocks. + +### synchronizeBlocksWithTemplate + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Synchronize a block list with a block template. + +Synchronizing a block list with a block template means that we loop over the blocks +keep the block as is if it matches the block at the same position in the template +(If it has the same name) and if doesn't match, we create a new block based on the template. +Extra blocks not present in the template are removed. + +**Parameters** + +- **blocks** `Array`: Block list. +- **template** `Array`: Block template. + +**Returns** + +`Array`: Updated Block list. + +### unregisterBlockStyle + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Unregisters a block style variation for the given block. + +**Parameters** + +- **blockName** `string`: Name of block (example: “core/latest-posts”). +- **styleVariationName** `string`: Name of class applied to the block. + +### unregisterBlockType + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Unregisters a block. + +**Parameters** + +- **name** `string`: Block name. + +**Returns** + +`?WPBlock`: The previous block value, if it has been successfully unregistered; otherwise `undefined`. + +### updateCategory + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Updates a category. + +**Parameters** + +- **slug** `string`: Block category slug. +- **category** `Object`: Object containing the category properties that should be updated. + +### withBlockContentContext + +[src/index.js#L17-L17](src/index.js#L17-L17) + +A Higher Order Component used to inject BlockContent using context to the +wrapped component. + +**Returns** + +`Component`: Enhanced component with injected BlockContent as prop. + + +<!-- END TOKEN(Autogenerated API docs) --> <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> From f7d90b9e43fc90b4dc9c47e4670836106b2387ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Fri, 8 Mar 2019 07:42:29 +0100 Subject: [PATCH 598/691] deprecated: set up auto-generated API docs (#14275) --- bin/update-readmes.js | 2 +- packages/deprecated/README.md | 56 +++++++++++++++++++++++++------- packages/deprecated/src/index.js | 14 ++++++++ 3 files changed, 60 insertions(+), 12 deletions(-) diff --git a/bin/update-readmes.js b/bin/update-readmes.js index 355703c8810e1..8db9ee9e61e6c 100755 --- a/bin/update-readmes.js +++ b/bin/update-readmes.js @@ -15,7 +15,7 @@ const packages = [ 'compose', //'data', //'date', - //'deprecated', + 'deprecated', 'dom', 'dom-ready', 'e2e-test-utils', diff --git a/packages/deprecated/README.md b/packages/deprecated/README.md index 84c44f08d71d1..8f03c2ec0b3cd 100644 --- a/packages/deprecated/README.md +++ b/packages/deprecated/README.md @@ -12,7 +12,33 @@ npm install @wordpress/deprecated --save _This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. Learn more about it in [Babel docs](https://babeljs.io/docs/en/next/caveats)._ -## Usage +## Hook + +The `deprecated` action is fired with three parameters: the name of the deprecated feature, the options object passed to deprecated, and the message sent to the console. + +_Example:_ + +```js +import { addAction } from '@wordpress/hooks'; + +function addDeprecationAlert( message, { version } ) { + alert( `Deprecation: ${ message }. Version: ${ version }` ); +} + +addAction( 'deprecated', 'my-plugin/add-deprecation-alert', addDeprecationAlert ); +``` + +## API + +<!-- START TOKEN(Autogenerated API docs) --> + +### default + +[src/index.js#L39-L78](src/index.js#L39-L78) + +Logs a message to notify developers about a deprecated feature. + +**Usage** ```js import deprecated from '@wordpress/deprecated'; @@ -27,20 +53,28 @@ deprecated( 'Eating meat', { // Logs: 'Eating meat is deprecated and will be removed from the earth in the future. Please use vegetables instead. Note: You may find it beneficial to transition gradually.' ``` -## Hook +**Parameters** -The `deprecated` action is fired with three parameters: the name of the deprecated feature, the options object passed to deprecated, and the message sent to the console. +- **feature** `string`: Name of the deprecated feature. +- **options** `?Object`: Personalisation options +- **options.version** `?string`: Version in which the feature will be removed. +- **options.alternative** `?string`: Feature to use instead +- **options.plugin** `?string`: Plugin name if it's a plugin feature +- **options.link** `?string`: Link to documentation +- **options.hint** `?string`: Additional message to help transition away from the deprecated feature. -_Example:_ +### logged -```js -import { addAction } from '@wordpress/hooks'; +[src/index.js#L12-L12](src/index.js#L12-L12) -function addDeprecationAlert( message, { version } ) { - alert( `Deprecation: ${ message }. Version: ${ version }` ); -} +Object map tracking messages which have been logged, for use in ensuring a +message is only logged once. -addAction( 'deprecated', 'my-plugin/add-deprecation-alert', addDeprecationAlert ); -``` +**Type** + +`Object` + + +<!-- END TOKEN(Autogenerated API docs) --> <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/deprecated/src/index.js b/packages/deprecated/src/index.js index 10ebc176c8704..840c694994a17 100644 --- a/packages/deprecated/src/index.js +++ b/packages/deprecated/src/index.js @@ -21,6 +21,20 @@ export const logged = Object.create( null ); * @param {?string} options.plugin Plugin name if it's a plugin feature * @param {?string} options.link Link to documentation * @param {?string} options.hint Additional message to help transition away from the deprecated feature. + * + * @example + * ```js + * import deprecated from '@wordpress/deprecated'; + * + * deprecated( 'Eating meat', { + * version: 'the future', + * alternative: 'vegetables', + * plugin: 'the earth', + * hint: 'You may find it beneficial to transition gradually.', + * } ); + * + * // Logs: 'Eating meat is deprecated and will be removed from the earth in the future. Please use vegetables instead. Note: You may find it beneficial to transition gradually.' + * ``` */ export default function deprecated( feature, options = {} ) { const { From 5cf68afc6a1a97b3e7670aa8ec4ce4560de61427 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Fri, 8 Mar 2019 07:43:29 +0100 Subject: [PATCH 599/691] priority-queue: set-up auto-generated API docs (#14262) --- bin/update-readmes.js | 2 +- packages/priority-queue/README.md | 24 +++++++++++++++++++++--- packages/priority-queue/src/index.js | 22 ++++++++++++++++++++++ 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/bin/update-readmes.js b/bin/update-readmes.js index 8db9ee9e61e6c..be50b22a6b061 100755 --- a/bin/update-readmes.js +++ b/bin/update-readmes.js @@ -26,7 +26,7 @@ const packages = [ 'i18n', 'keycodes', //'plugins', - //'priority-queue', + 'priority-queue', //'redux-routine', 'rich-text', //'shortcode', diff --git a/packages/priority-queue/README.md b/packages/priority-queue/README.md index 2405c6fbd6c99..d9f3477034436 100644 --- a/packages/priority-queue/README.md +++ b/packages/priority-queue/README.md @@ -10,9 +10,20 @@ Install the module npm install @wordpress/priority-queue --save ``` -_This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. Learn more about it in [Babel docs](https://babeljs.io/docs/en/next/caveats). +_This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. Learn more about it in [Babel docs](https://babeljs.io/docs/en/next/caveats)._ -## Usage +## API + +<!-- START TOKEN(Autogenerated API docs) --> + +### createQueue + +[src/index.js#L25-L72](src/index.js#L25-L72) + +Creates a context-aware queue that only executes +the last task of a given context. + +**Usage** ```js import { createQueue } from '@wordpress/priority-queue'; @@ -21,7 +32,7 @@ const queue = createQueue(); // Context objects. const ctx1 = {}; -const ctx2 = {}; +const ctx2 = {}; // For a given context in the queue, only the last callback is executed. queue.add( ctx1, () => console.log( 'This will be printed first' ) ); @@ -29,4 +40,11 @@ queue.add( ctx2, () => console.log( 'This won\'t be printed' ) ); queue.add( ctx2, () => console.log( 'This will be printed second' ) ); ``` +**Returns** + +`Object`: Queue object with `add` and `flush` methods. + + +<!-- END TOKEN(Autogenerated API docs) --> + <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/priority-queue/src/index.js b/packages/priority-queue/src/index.js index 5934fe74875ef..50b1befba01a9 100644 --- a/packages/priority-queue/src/index.js +++ b/packages/priority-queue/src/index.js @@ -1,5 +1,27 @@ const requestIdleCallback = window.requestIdleCallback ? window.requestIdleCallback : window.requestAnimationFrame; +/** + * Creates a context-aware queue that only executes + * the last task of a given context. + * + * @example + *```js + * import { createQueue } from '@wordpress/priority-queue'; + * + * const queue = createQueue(); + * + * // Context objects. + * const ctx1 = {}; + * const ctx2 = {}; + * + * // For a given context in the queue, only the last callback is executed. + * queue.add( ctx1, () => console.log( 'This will be printed first' ) ); + * queue.add( ctx2, () => console.log( 'This won\'t be printed' ) ); + * queue.add( ctx2, () => console.log( 'This will be printed second' ) ); + *``` + * + * @return {Object} Queue object with `add` and `flush` methods. + */ export const createQueue = () => { const waitingList = []; const elementsMap = new WeakMap(); From 25ee3df3eec0a1e3da0588550b8b78bd806bcb22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Fri, 8 Mar 2019 07:44:18 +0100 Subject: [PATCH 600/691] shortcode: set up autogenerated API docs (#14218) --- bin/update-readmes.js | 2 +- packages/shortcode/README.md | 147 +++++++++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+), 1 deletion(-) diff --git a/bin/update-readmes.js b/bin/update-readmes.js index be50b22a6b061..dafdf7d474303 100755 --- a/bin/update-readmes.js +++ b/bin/update-readmes.js @@ -29,7 +29,7 @@ const packages = [ 'priority-queue', //'redux-routine', 'rich-text', - //'shortcode', + 'shortcode', //'url', //'viewport', //'wordcount', diff --git a/packages/shortcode/README.md b/packages/shortcode/README.md index 4887cf2fdcbf7..4fc494b224206 100644 --- a/packages/shortcode/README.md +++ b/packages/shortcode/README.md @@ -12,4 +12,151 @@ npm install @wordpress/shortcode --save _This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. Learn more about it in [Babel docs](https://babeljs.io/docs/en/next/caveats)._ +## API + +<!-- START TOKEN(Autogenerated API docs) --> + +### attrs + +[src/index.js#L167-L210](src/index.js#L167-L210) + +Parse shortcode attributes. + +Shortcodes accept many types of attributes. These can chiefly be divided into +named and numeric attributes: + +Named attributes are assigned on a key/value basis, while numeric attributes +are treated as an array. + +Named attributes can be formatted as either `name="value"`, `name='value'`, +or `name=value`. Numeric attributes can be formatted as `"value"` or just +`value`. + +**Parameters** + +- **text** `string`: Serialised shortcode attributes. + +**Returns** + +`WPShortcodeAttrs`: Parsed shortcode attributes. + +### default + +[src/index.js#L363-L363](src/index.js#L363-L363) + +Creates a shortcode instance. + +To access a raw representation of a shortcode, pass an `options` object, +containing a `tag` string, a string or object of `attrs`, a string indicating +the `type` of the shortcode ('single', 'self-closing', or 'closed'), and a +`content` string. + +**Parameters** + +- **options** `Object`: Options as described. + +**Returns** + +`WPShortcode`: Shortcode instance. + +### fromMatch + +[src/index.js#L223-L240](src/index.js#L223-L240) + +Generate a Shortcode Object from a RegExp match. + +Accepts a `match` object from calling `regexp.exec()` on a `RegExp` generated +by `regexp()`. `match` can also be set to the `arguments` from a callback +passed to `regexp.replace()`. + +**Parameters** + +- **match** `Array`: Match array. + +**Returns** + +`WPShortcode`: Shortcode instance. + +### next + +[src/index.js#L45-L80](src/index.js#L45-L80) + +Find the next matching shortcode. + +**Parameters** + +- **tag** `string`: Shortcode tag. +- **text** `string`: Text to search. +- **index** `number`: Index to start search from. + +**Returns** + +`?WPShortcodeMatch`: Matched information. + +### regexp + +[src/index.js#L146-L148](src/index.js#L146-L148) + +Generate a RegExp to identify a shortcode. + +The base regex is functionally equivalent to the one found in +`get_shortcode_regex()` in `wp-includes/shortcodes.php`. + +Capture groups: + +1. An extra `[` to allow for escaping shortcodes with double `[[]]` +2. The shortcode name +3. The shortcode argument list +4. The self closing `/` +5. The content of a shortcode when it wraps some content. +6. The closing tag. +7. An extra `]` to allow for escaping shortcodes with double `[[]]` + +**Parameters** + +- **tag** `string`: Shortcode tag. + +**Returns** + +`RegExp`: Shortcode RegExp. + +### replace + +[src/index.js#L92-L107](src/index.js#L92-L107) + +Replace matching shortcodes in a block of text. + +**Parameters** + +- **tag** `string`: Shortcode tag. +- **text** `string`: Text to search. +- **callback** `Function`: Function to process the match and return replacement string. + +**Returns** + +`string`: Text with shortcodes replaced. + +### string + +[src/index.js#L122-L124](src/index.js#L122-L124) + +Generate a string from shortcode parameters. + +Creates a shortcode instance and returns a string. + +Accepts the same `options` as the `shortcode()` constructor, containing a +`tag` string, a string or object of `attrs`, a boolean indicating whether to +format the shortcode using a `single` tag, and a `content` string. + +**Parameters** + +- **options** `Object`: + +**Returns** + +`string`: String representation of the shortcode. + + +<!-- END TOKEN(Autogenerated API docs) --> + <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> From b5fbc47d41075ac581699a58322d93bdd7f3aa2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Fri, 8 Mar 2019 07:44:58 +0100 Subject: [PATCH 601/691] viewport: set up autogenerated API docs in README (#14214) --- bin/update-readmes.js | 2 +- packages/viewport/README.md | 72 +++++++++++++++----- packages/viewport/src/if-viewport-matches.js | 12 +++- packages/viewport/src/with-viewport-match.js | 14 +++- 4 files changed, 81 insertions(+), 19 deletions(-) diff --git a/bin/update-readmes.js b/bin/update-readmes.js index dafdf7d474303..c3cbef224c0f4 100755 --- a/bin/update-readmes.js +++ b/bin/update-readmes.js @@ -31,7 +31,7 @@ const packages = [ 'rich-text', 'shortcode', //'url', - //'viewport', + 'viewport', //'wordcount', ]; diff --git a/packages/viewport/README.md b/packages/viewport/README.md index 3431d99b216de..c11fd9172d10d 100644 --- a/packages/viewport/README.md +++ b/packages/viewport/README.md @@ -12,20 +12,20 @@ npm install @wordpress/viewport --save _This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. Learn more about it in [Babel docs](https://babeljs.io/docs/en/next/caveats)._ -## Breakpoints +## Usage The standard set of breakpoint thresholds is as follows: -Name|Pixel Width ----|--- -`huge`|1440 -`wide`|1280 -`large`|960 -`medium`|782 -`small`|600 -`mobile`|480 +| Name | Pixel Width | +| -------- | ----------- | +| `huge` | 1440 | +| `wide` | 1280 | +| `large` | 960 | +| `medium` | 782 | +| `small` | 600 | +| `mobile` | 480 | -## Data Module +### Data Module The Viewport module registers itself under the `core/viewport` data namespace. @@ -43,11 +43,24 @@ const isWideOrHuge = isViewportMatch( '>= wide' ); // const isWideOrHuge = isViewportMatch( 'wide' ); ``` -## `ifViewportMatches` Higher-Order Component +### Higher-Order Components -If you are authoring a component which should only be shown under a specific viewport condition, you can leverage the `ifViewportMatches` higher-order component to achieve this requirement. +This package provides a set of HOCs to author components whose behavior should vary depending on the viewport. -Pass a viewport query to render the component only when the query is matched: +<!-- START TOKEN(Autogenerated API docs) --> + +#### ifViewportMatches + +[src/index.js#L16-L16](src/index.js#L16-L16) + +Higher-order component creator, creating a new component which renders if +the viewport query is satisfied. + +**Related** + +- withViewportMatches + +**Usage** ```jsx function MyMobileComponent() { @@ -57,11 +70,27 @@ function MyMobileComponent() { MyMobileComponent = ifViewportMatches( '< small' )( MyMobileComponent ); ``` -## `withViewportMatch` Higher-Order Component +**Parameters** + +- **query** `string`: Viewport query. + +**Returns** + +`Function`: Higher-order component. + +#### withViewportMatch -If you are authoring a component which should vary its rendering behavior depending upon the matching viewport, you can leverage the `withViewportMatch` higher-order component to achieve this requirement. +[src/index.js#L17-L17](src/index.js#L17-L17) -Pass an object, where each key is a prop name and its value a viewport query. The component will be rendered with your prop(s) assigned with the result(s) of the query match: +Higher-order component creator, creating a new component which renders with +the given prop names, where the value passed to the underlying component is +the result of the query assigned as the object's value. + +**Related** + +- isViewportMatch + +**Usage** ```jsx function MyComponent( { isMobile } ) { @@ -73,4 +102,15 @@ function MyComponent( { isMobile } ) { MyComponent = withViewportMatch( { isMobile: '< small' } )( MyComponent ); ``` +**Parameters** + +- **queries** `Object`: Object of prop name to viewport query. + +**Returns** + +`Function`: Higher-order component. + + +<!-- END TOKEN(Autogenerated API docs) --> + <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/viewport/src/if-viewport-matches.js b/packages/viewport/src/if-viewport-matches.js index 97cf7fb355411..76ea3c5650b56 100644 --- a/packages/viewport/src/if-viewport-matches.js +++ b/packages/viewport/src/if-viewport-matches.js @@ -12,9 +12,19 @@ import withViewportMatch from './with-viewport-match'; * Higher-order component creator, creating a new component which renders if * the viewport query is satisfied. * + * @see withViewportMatches + * * @param {string} query Viewport query. * - * @see withViewportMatches + * @example + * + * ```jsx + * function MyMobileComponent() { + * return <div>I'm only rendered on mobile viewports!</div>; + * } + * + * MyMobileComponent = ifViewportMatches( '< small' )( MyMobileComponent ); + * ``` * * @return {Function} Higher-order component. */ diff --git a/packages/viewport/src/with-viewport-match.js b/packages/viewport/src/with-viewport-match.js index 1da962f1f1a01..3398fda7e4f3f 100644 --- a/packages/viewport/src/with-viewport-match.js +++ b/packages/viewport/src/with-viewport-match.js @@ -14,9 +14,21 @@ import { withSelect } from '@wordpress/data'; * the given prop names, where the value passed to the underlying component is * the result of the query assigned as the object's value. * + * @see isViewportMatch + * * @param {Object} queries Object of prop name to viewport query. * - * @see isViewportMatch + * @example + * + * ```jsx + * function MyComponent( { isMobile } ) { + * return ( + * <div>Currently: { isMobile ? 'Mobile' : 'Not Mobile' }</div> + * ); + * } + * + * MyComponent = withViewportMatch( { isMobile: '< small' } )( MyComponent ); + * ``` * * @return {Function} Higher-order component. */ From 34240bc296d612d1ab44b8768f1304988249c130 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Fri, 8 Mar 2019 08:28:24 +0100 Subject: [PATCH 602/691] url: set up autogenerated API docs (#14217) --- bin/update-readmes.js | 2 +- packages/url/README.md | 332 ++++++++++++++++++++++++++++++++------ packages/url/src/index.js | 100 ++++++++++++ 3 files changed, 381 insertions(+), 53 deletions(-) diff --git a/bin/update-readmes.js b/bin/update-readmes.js index c3cbef224c0f4..7cbaa4274da27 100755 --- a/bin/update-readmes.js +++ b/bin/update-readmes.js @@ -30,7 +30,7 @@ const packages = [ //'redux-routine', 'rich-text', 'shortcode', - //'url', + 'url', 'viewport', //'wordcount', ]; diff --git a/packages/url/README.md b/packages/url/README.md index fb78d8808bb2b..611efcddfc3f3 100644 --- a/packages/url/README.md +++ b/packages/url/README.md @@ -14,173 +14,401 @@ _This package assumes that your code will run in an **ES2015+** environment. If ## Usage -### isURL +<!-- START TOKEN(Autogenerated API docs) --> -```js -const isURL = isURL( 'https://wordpress.org' ); // true -``` +### addQueryArgs -Checks whether the URL is an HTTP or HTTPS URL. +[src/index.js#L242-L264](src/index.js#L242-L264) +Appends arguments as querystring to the provided URL. If the URL already +includes query arguments, the arguments are merged with (and take precedent +over) the existing set. -### getProtocol +**Usage** ```js -const protocol1 = getProtocol( 'tel:012345678' ); // 'tel:' -const protocol2 = getProtocol( 'https://wordpress.org' ); // 'https:' +const newURL = addQueryArgs( 'https://google.com', { q: 'test' } ); // https://google.com/?q=test ``` -Returns the protocol part of the provided URL. +**Parameters** +- **url** `?string`: URL to which arguments should be appended. If omitted, only the resulting querystring is returned. +- **args** `Object`: Query arguments to apply to URL. -### isValidProtocol +**Returns** + +`string`: URL with arguments applied. + +### filterURLForDisplay + +[src/index.js#L379-L389](src/index.js#L379-L389) + +Returns a URL for display. + +**Usage** ```js -const isValid = isValidProtocol( 'https:' ); // true -const isNotValid = isValidProtocol( 'https :' ); // false +const displayUrl = filterURLForDisplay( 'https://www.wordpress.org/gutenberg/' ); // wordpress.org/gutenberg ``` -Returns true if the provided protocol is valid. Returns false if the protocol contains invalid characters. +**Parameters** + +- **url** `string`: Original URL. + +**Returns** +`string`: Displayed URL. ### getAuthority +[src/index.js#L79-L84](src/index.js#L79-L84) + +Returns the authority part of the URL. + +**Usage** + ```js const authority1 = getAuthority( 'https://wordpress.org/help/' ); // 'wordpress.org' const authority2 = getAuthority( 'https://localhost:8080/test/' ); // 'localhost:8080' ``` -Returns the authority part of the provided URL. +**Parameters** +- **url** `string`: The full URL. -### isValidAuthority +**Returns** + +`?string`: The authority part of the URL. + +### getFragment + +[src/index.js#L199-L204](src/index.js#L199-L204) + +Returns the fragment part of the URL. + +**Usage** ```js -const isValid = isValidAuthority( 'wordpress.org' ); // true -const isNotValid = isValidAuthority( 'wordpress#org' ); // false +const fragment1 = getFragment( 'http://localhost:8080/this/is/a/test?query=true#fragment' ); // '#fragment' +const fragment2 = getFragment( 'https://wordpress.org#another-fragment?query=true' ); // '#another-fragment' ``` -Returns true if the provided authority is valid. Returns false if the protocol contains invalid characters. +**Parameters** + +- **url** `string`: The full URL + +**Returns** +`?string`: The fragment part of the URL. ### getPath +[src/index.js#L119-L124](src/index.js#L119-L124) + +Returns the path part of the URL. + +**Usage** + ```js const path1 = getPath( 'http://localhost:8080/this/is/a/test?query=true' ); // 'this/is/a/test' const path2 = getPath( 'https://wordpress.org/help/faq/' ); // 'help/faq' ``` -Returns the path part of the provided URL. +**Parameters** +- **url** `string`: The full URL. -### isValidPath +**Returns** + +`?string`: The path part of the URL. + +### getProtocol + +[src/index.js#L39-L44](src/index.js#L39-L44) + +Returns the protocol part of the URL. + +**Usage** ```js -const isValid = isValidPath( 'test/path/' ); // true -const isNotValid = isValidPath( '/invalid?test/path/' ); // false +const protocol1 = getProtocol( 'tel:012345678' ); // 'tel:' +const protocol2 = getProtocol( 'https://wordpress.org' ); // 'https:' ``` -Returns true if the provided path is valid. Returns false if the path contains invalid characters. +**Parameters** + +- **url** `string`: The full URL. + +**Returns** + +`?string`: The protocol part of the URL. + +### getQueryArg + +[src/index.js#L279-L284](src/index.js#L279-L284) + +Returns a single query argument of the url + +**Usage** + +```js +const foo = getQueryArg( 'https://wordpress.org?foo=bar&bar=baz', 'foo' ); // bar +``` + +**Parameters** + +- **url** `string`: URL +- **arg** `string`: Query arg name +**Returns** + +`(Array|string)`: Query arg value. ### getQueryString +[src/index.js#L159-L164](src/index.js#L159-L164) + +Returns the query string part of the URL. + +**Usage** + ```js const queryString1 = getQueryString( 'http://localhost:8080/this/is/a/test?query=true#fragment' ); // 'query=true' const queryString2 = getQueryString( 'https://wordpress.org#fragment?query=false&search=hello' ); // 'query=false&search=hello' ``` -Returns the query string part of the provided URL. +**Parameters** +- **url** `string`: The full URL. -### isValidQueryString +**Returns** + +`?string`: The query string part of the URL. + +### hasQueryArg + +[src/index.js#L299-L301](src/index.js#L299-L301) + +Determines whether the URL contains a given query arg. + +**Usage** ```js -const isValid = isValidQueryString( 'query=true&another=false' ); // true -const isNotValid = isValidQueryString( 'query=true?another=false' ); // false +const hasBar = hasQueryArg( 'https://wordpress.org?foo=bar&bar=baz', 'bar' ); // true ``` -Returns true if the provided query string is valid. Returns false if the query string contains invalid characters. +**Parameters** +- **url** `string`: URL +- **arg** `string`: Query arg name -### getFragment +**Returns** + +`boolean`: Whether or not the URL contains the query arg. + +### isURL + +[src/index.js#L22-L24](src/index.js#L22-L24) + +Determines whether the given string looks like a URL. + +**Usage** ```js -const fragment1 = getFragment( 'http://localhost:8080/this/is/a/test?query=true#fragment' ); // '#fragment' -const fragment2 = getFragment( 'https://wordpress.org#another-fragment?query=true' ); // '#another-fragment' +const isURL = isURL( 'https://wordpress.org' ); // true ``` -Returns the fragment part of the provided URL. +**Parameters** +- **url** `string`: The string to scrutinise. + +**Returns** + +`boolean`: Whether or not it looks like a URL. + +### isValidAuthority + +[src/index.js#L99-L104](src/index.js#L99-L104) + +Checks for invalid characters within the provided authority. + +**Usage** + +```js +const isValid = isValidAuthority( 'wordpress.org' ); // true +const isNotValid = isValidAuthority( 'wordpress#org' ); // false +``` + +**Parameters** + +- **authority** `string`: A string containing the URL authority. + +**Returns** + +`boolean`: True if the argument contains a valid authority. ### isValidFragment +[src/index.js#L219-L224](src/index.js#L219-L224) + +Checks for invalid characters within the provided fragment. + +**Usage** + ```js const isValid = isValidFragment( '#valid-fragment' ); // true const isNotValid = isValidFragment( '#invalid-#fragment' ); // false ``` -Returns true if the provided fragment is valid. Returns false if the fragment contains invalid characters. +**Parameters** +- **fragment** `string`: The url fragment. -### addQueryArgs +**Returns** + +`boolean`: True if the argument contains a valid fragment. + +### isValidPath + +[src/index.js#L139-L144](src/index.js#L139-L144) + +Checks for invalid characters within the provided path. + +**Usage** ```js -const newURL = addQueryArgs( 'https://google.com', { q: 'test' } ); // https://google.com/?q=test +const isValid = isValidPath( 'test/path/' ); // true +const isNotValid = isValidPath( '/invalid?test/path/' ); // false ``` -Adds a query string argument to the provided URL. +**Parameters** +- **path** `string`: The URL path. -### prependHTTP +**Returns** + +`boolean`: True if the argument contains a valid path + +### isValidProtocol + +[src/index.js#L59-L64](src/index.js#L59-L64) + +Tests if a url protocol is valid. + +**Usage** ```js -const actualURL = prependHTTP( 'wordpress.org' ); // http://wordpress.org +const isValid = isValidProtocol( 'https:' ); // true +const isNotValid = isValidProtocol( 'https :' ); // false ``` -Prepends the provided partial URL with the http:// protocol. +**Parameters** -### getQueryArg +- **protocol** `string`: The url protocol. + +**Returns** + +`boolean`: True if the argument is a valid protocol (e.g. http\:, tel:). + +### isValidQueryString + +[src/index.js#L179-L184](src/index.js#L179-L184) + +Checks for invalid characters within the provided query string. + +**Usage** ```js -const foo = getQueryArg( 'https://wordpress.org?foo=bar&bar=baz', 'foo' ); // bar +const isValid = isValidQueryString( 'query=true&another=false' ); // true +const isNotValid = isValidQueryString( 'query=true?another=false' ); // false ``` -Retrieve a query string argument from the provided URL. +**Parameters** +- **queryString** `string`: The query string. -### hasQueryArg +**Returns** + +`boolean`: True if the argument contains a valid query string. + +### prependHTTP + +[src/index.js#L338-L344](src/index.js#L338-L344) + +Prepends "http\://" to a url, if it looks like something that is meant to be a TLD. + +**Usage** ```js -const hasBar = hasQueryArg( 'https://wordpress.org?foo=bar&bar=baz', 'bar' ); // true +const actualURL = prependHTTP( 'wordpress.org' ); // http://wordpress.org ``` -Checks whether a URL contains the given query string argument. +**Parameters** + +- **url** `string`: The URL to test + +**Returns** +`string`: The updated URL ### removeQueryArgs +[src/index.js#L316-L324](src/index.js#L316-L324) + +Removes arguments from the query string of the url + +**Usage** + ```js const newUrl = removeQueryArgs( 'https://wordpress.org?foo=bar&bar=baz&baz=foobar', 'foo', 'bar' ); // https://wordpress.org?baz=foobar ``` -Removes one or more query string arguments from the given URL. +**Parameters** +- **url** `string`: URL +- **args** `...string`: Query Args + +**Returns** + +`string`: Updated URL ### safeDecodeURI +[src/index.js#L359-L365](src/index.js#L359-L365) + +Safely decodes a URI with `decodeURI`. Returns the URI unmodified if +`decodeURI` throws an error. + +**Usage** + ```js const badUri = safeDecodeURI( '%z' ); // does not throw an Error, simply returns '%z' ``` -Safely decodes a URI with `decodeURI`. Returns the URI unmodified if `decodeURI` throws an Error. +**Parameters** -### filterURLForDisplay +- **uri** `string`: URI to decode. + +**Returns** + +`string`: Decoded URI if possible. + +### safeDecodeURIComponent + +[src/index.js#L399-L405](src/index.js#L399-L405) + +Safely decodes a URI component with `decodeURIComponent`. Returns the URI component unmodified if +`decodeURIComponent` throws an error. + +**Parameters** + +- **uriComponent** `string`: URI component to decode. + +**Returns** + +`string`: Decoded URI component if possible. -```js -const displayUrl = filterURLForDisplay( 'https://www.wordpress.org/gutenberg/' ); // wordpress.org/gutenberg -``` -Returns a URL for display, without protocol, www subdomain, or trailing slash. +<!-- END TOKEN(Autogenerated API docs) --> <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/url/src/index.js b/packages/url/src/index.js index d060f3539d76e..c5037cdf41730 100644 --- a/packages/url/src/index.js +++ b/packages/url/src/index.js @@ -12,6 +12,11 @@ const USABLE_HREF_REGEXP = /^(?:[a-z]+:|#|\?|\.|\/)/i; * * @param {string} url The string to scrutinise. * + * @example + * ```js + * const isURL = isURL( 'https://wordpress.org' ); // true + * ``` + * * @return {boolean} Whether or not it looks like a URL. */ export function isURL( url ) { @@ -23,6 +28,12 @@ export function isURL( url ) { * * @param {string} url The full URL. * + * @example + * ```js + * const protocol1 = getProtocol( 'tel:012345678' ); // 'tel:' + * const protocol2 = getProtocol( 'https://wordpress.org' ); // 'https:' + * ``` + * * @return {?string} The protocol part of the URL. */ export function getProtocol( url ) { @@ -37,6 +48,12 @@ export function getProtocol( url ) { * * @param {string} protocol The url protocol. * + * @example + * ```js + * const isValid = isValidProtocol( 'https:' ); // true + * const isNotValid = isValidProtocol( 'https :' ); // false + * ``` + * * @return {boolean} True if the argument is a valid protocol (e.g. http:, tel:). */ export function isValidProtocol( protocol ) { @@ -51,6 +68,12 @@ export function isValidProtocol( protocol ) { * * @param {string} url The full URL. * + * @example + * ```js + * const authority1 = getAuthority( 'https://wordpress.org/help/' ); // 'wordpress.org' + * const authority2 = getAuthority( 'https://localhost:8080/test/' ); // 'localhost:8080' + * ``` + * * @return {?string} The authority part of the URL. */ export function getAuthority( url ) { @@ -65,6 +88,12 @@ export function getAuthority( url ) { * * @param {string} authority A string containing the URL authority. * + * @example + * ```js + * const isValid = isValidAuthority( 'wordpress.org' ); // true + * const isNotValid = isValidAuthority( 'wordpress#org' ); // false + * ``` + * * @return {boolean} True if the argument contains a valid authority. */ export function isValidAuthority( authority ) { @@ -79,6 +108,12 @@ export function isValidAuthority( authority ) { * * @param {string} url The full URL. * + * @example + * ```js + * const path1 = getPath( 'http://localhost:8080/this/is/a/test?query=true' ); // 'this/is/a/test' + * const path2 = getPath( 'https://wordpress.org/help/faq/' ); // 'help/faq' + * ``` + * * @return {?string} The path part of the URL. */ export function getPath( url ) { @@ -93,6 +128,12 @@ export function getPath( url ) { * * @param {string} path The URL path. * + * @example + * ```js + * const isValid = isValidPath( 'test/path/' ); // true + * const isNotValid = isValidPath( '/invalid?test/path/' ); // false + * ``` + * * @return {boolean} True if the argument contains a valid path */ export function isValidPath( path ) { @@ -107,6 +148,12 @@ export function isValidPath( path ) { * * @param {string} url The full URL. * + * @example + * ```js + * const queryString1 = getQueryString( 'http://localhost:8080/this/is/a/test?query=true#fragment' ); // 'query=true' + * const queryString2 = getQueryString( 'https://wordpress.org#fragment?query=false&search=hello' ); // 'query=false&search=hello' + * ``` + * * @return {?string} The query string part of the URL. */ export function getQueryString( url ) { @@ -121,6 +168,12 @@ export function getQueryString( url ) { * * @param {string} queryString The query string. * + * @example + * ```js + * const isValid = isValidQueryString( 'query=true&another=false' ); // true + * const isNotValid = isValidQueryString( 'query=true?another=false' ); // false + * ``` + * * @return {boolean} True if the argument contains a valid query string. */ export function isValidQueryString( queryString ) { @@ -135,6 +188,12 @@ export function isValidQueryString( queryString ) { * * @param {string} url The full URL * + * @example + * ```js + * const fragment1 = getFragment( 'http://localhost:8080/this/is/a/test?query=true#fragment' ); // '#fragment' + * const fragment2 = getFragment( 'https://wordpress.org#another-fragment?query=true' ); // '#another-fragment' + * ``` + * * @return {?string} The fragment part of the URL. */ export function getFragment( url ) { @@ -149,6 +208,12 @@ export function getFragment( url ) { * * @param {string} fragment The url fragment. * + * @example + * ```js + * const isValid = isValidFragment( '#valid-fragment' ); // true + * const isNotValid = isValidFragment( '#invalid-#fragment' ); // false + * ``` + * * @return {boolean} True if the argument contains a valid fragment. */ export function isValidFragment( fragment ) { @@ -167,6 +232,11 @@ export function isValidFragment( fragment ) { * only the resulting querystring is returned. * @param {Object} args Query arguments to apply to URL. * + * @example + * ```js + * const newURL = addQueryArgs( 'https://google.com', { q: 'test' } ); // https://google.com/?q=test + * ``` + * * @return {string} URL with arguments applied. */ export function addQueryArgs( url = '', args ) { @@ -199,6 +269,11 @@ export function addQueryArgs( url = '', args ) { * @param {string} url URL * @param {string} arg Query arg name * + * @example + * ```js + * const foo = getQueryArg( 'https://wordpress.org?foo=bar&bar=baz', 'foo' ); // bar + * ``` + * * @return {Array|string} Query arg value. */ export function getQueryArg( url, arg ) { @@ -214,6 +289,11 @@ export function getQueryArg( url, arg ) { * @param {string} url URL * @param {string} arg Query arg name * + * @example + * ```js + * const hasBar = hasQueryArg( 'https://wordpress.org?foo=bar&bar=baz', 'bar' ); // true + * ``` + * * @return {boolean} Whether or not the URL contains the query arg. */ export function hasQueryArg( url, arg ) { @@ -226,6 +306,11 @@ export function hasQueryArg( url, arg ) { * @param {string} url URL * @param {...string} args Query Args * + * @example + * ```js + * const newUrl = removeQueryArgs( 'https://wordpress.org?foo=bar&bar=baz&baz=foobar', 'foo', 'bar' ); // https://wordpress.org?baz=foobar + * ``` + * * @return {string} Updated URL */ export function removeQueryArgs( url, ...args ) { @@ -243,6 +328,11 @@ export function removeQueryArgs( url, ...args ) { * * @param {string} url The URL to test * + * @example + * ```js + * const actualURL = prependHTTP( 'wordpress.org' ); // http://wordpress.org + * ``` + * * @return {string} The updated URL */ export function prependHTTP( url ) { @@ -259,6 +349,11 @@ export function prependHTTP( url ) { * * @param {string} uri URI to decode. * + * @example + * ```js + * const badUri = safeDecodeURI( '%z' ); // does not throw an Error, simply returns '%z' + * ``` + * * @return {string} Decoded URI if possible. */ export function safeDecodeURI( uri ) { @@ -274,6 +369,11 @@ export function safeDecodeURI( uri ) { * * @param {string} url Original URL. * + * @example + * ```js + * const displayUrl = filterURLForDisplay( 'https://www.wordpress.org/gutenberg/' ); // wordpress.org/gutenberg + * ``` + * * @return {string} Displayed URL. */ export function filterURLForDisplay( url ) { From d67cf526b62dfb1b11e36cad1f0dd8150cc9aecf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Fri, 8 Mar 2019 08:30:03 +0100 Subject: [PATCH 603/691] redux-routine: set up autogenerated API docs (#14228) --- bin/update-readmes.js | 2 +- packages/redux-routine/README.md | 30 +++++++++++++++++++++++++----- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/bin/update-readmes.js b/bin/update-readmes.js index 7cbaa4274da27..a227c6373570c 100755 --- a/bin/update-readmes.js +++ b/bin/update-readmes.js @@ -27,7 +27,7 @@ const packages = [ 'keycodes', //'plugins', 'priority-queue', - //'redux-routine', + 'redux-routine', 'rich-text', 'shortcode', 'url', diff --git a/packages/redux-routine/README.md b/packages/redux-routine/README.md index 5484b7ec35c57..46c9b299b3104 100644 --- a/packages/redux-routine/README.md +++ b/packages/redux-routine/README.md @@ -57,9 +57,29 @@ request has completed does the action creator procede to return the `SET_TEMPERA ## API -### `createMiddleware( controls: ?Object )` +<!-- START TOKEN(Autogenerated API docs) --> -Create a Redux middleware, given an object of controls where each key is an action type for which to act upon, the value a function which returns either a promise which is to resolve when evaluation of the action should continue, or a value. The value or resolved promise value is assigned on the return value of the yield assignment. If the control handler returns undefined, the execution is not continued. +### default + +[src/index.js#L19-L30](src/index.js#L19-L30) + +Creates a Redux middleware, given an object of controls where each key is an +action type for which to act upon, the value a function which returns either +a promise which is to resolve when evaluation of the action should continue, +or a value. The value or resolved promise value is assigned on the return +value of the yield assignment. If the control handler returns undefined, the +execution is not continued. + +**Parameters** + +- **controls** `Object`: Object of control handlers. + +**Returns** + +`Function`: Co-routine runtime + + +<!-- END TOKEN(Autogenerated API docs) --> ## Motivation @@ -67,9 +87,9 @@ Create a Redux middleware, given an object of controls where each key is an acti The primary motivations include, among others: -- **Testability**: Since an action creator yields plain action objects, the behavior of their resolution can be easily substituted in tests. -- **Controlled flexibility**: Control flows can be implemented without sacrificing the expressiveness and intentionality of an action type. Other solutions like thunks or promises promote ultimate flexibility, but at the expense of maintainability manifested through deep coupling between action types and incidental implementation. -- A **common domain language** for expressing data flows: Since controls are centrally defined, it requires the conscious decision on the part of a development team to decide when and how new control handlers are added. +- **Testability**: Since an action creator yields plain action objects, the behavior of their resolution can be easily substituted in tests. +- **Controlled flexibility**: Control flows can be implemented without sacrificing the expressiveness and intentionality of an action type. Other solutions like thunks or promises promote ultimate flexibility, but at the expense of maintainability manifested through deep coupling between action types and incidental implementation. +- A **common domain language** for expressing data flows: Since controls are centrally defined, it requires the conscious decision on the part of a development team to decide when and how new control handlers are added. ## Testing From bf1dd41cffb8b4c7719ea310e6b9b94b0bd5f0c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Fri, 8 Mar 2019 08:30:28 +0100 Subject: [PATCH 604/691] date: set up auto-generated API docs (#14276) --- bin/update-readmes.js | 2 +- packages/date/README.md | 106 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 1 deletion(-) diff --git a/bin/update-readmes.js b/bin/update-readmes.js index a227c6373570c..a9acb6c9a1de6 100755 --- a/bin/update-readmes.js +++ b/bin/update-readmes.js @@ -14,7 +14,7 @@ const packages = [ 'blocks', 'compose', //'data', - //'date', + 'date', 'deprecated', 'dom', 'dom-ready', diff --git a/packages/date/README.md b/packages/date/README.md index 245540b78632c..fb201db6395cb 100644 --- a/packages/date/README.md +++ b/packages/date/README.md @@ -12,4 +12,110 @@ npm install @wordpress/date --save _This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. Learn more about it in [Babel docs](https://babeljs.io/docs/en/next/caveats)._ +## API + +<!-- START TOKEN(Autogenerated API docs) --> + +### date + +[src/index.js#L323-L327](src/index.js#L323-L327) + +Formats a date (like `date()` in PHP), in the site's timezone. + +**Parameters** + +- **dateFormat** `string`: PHP-style formatting string. See php.net/date. +- **dateValue** `(Date|string|moment|null)`: Date object or string, parsable by moment.js. + +**Returns** + +`string`: Formatted date. + +### dateI18n + +[src/index.js#L356-L366](src/index.js#L356-L366) + +Formats a date (like `dateI18n()` in PHP). + +**Parameters** + +- **dateFormat** `string`: PHP-style formatting string. See php.net/date. +- **dateValue** `(Date|string|moment|null)`: Date object or string, parsable by moment.js. +- **gmt** `boolean`: True for GMT/UTC, false for site's timezone. + +**Returns** + +`string`: Formatted date. + +### format + +[src/index.js#L282-L311](src/index.js#L282-L311) + +Formats a date. Does not alter the date's timezone. + +**Parameters** + +- **dateFormat** `string`: PHP-style formatting string. See php.net/date. +- **dateValue** `(Date|string|moment|null)`: Date object or string, parsable by moment.js. + +**Returns** + +`string`: Formatted date. + +### getDate + +[src/index.js#L389-L395](src/index.js#L389-L395) + +Create and return a JavaScript Date Object from a date string in the WP timezone. + +**Parameters** + +- **dateString** `?string`: Date formatted in the WP timezone. + +**Returns** + +`Date`: Date + +### gmdate + +[src/index.js#L339-L342](src/index.js#L339-L342) + +Formats a date (like `date()` in PHP), in the UTC timezone. + +**Parameters** + +- **dateFormat** `string`: PHP-style formatting string. See php.net/date. +- **dateValue** `(Date|string|moment|null)`: Date object or string, parsable by moment.js. + +**Returns** + +`string`: Formatted date. + +### isInTheFuture + +[src/index.js#L375-L380](src/index.js#L375-L380) + +Check whether a date is considered in the future according to the WordPress settings. + +**Parameters** + +- **dateValue** `string`: Date String or Date object in the Defined WP Timezone. + +**Returns** + +`boolean`: Is in the future. + +### setSettings + +[src/index.js#L36-L83](src/index.js#L36-L83) + +Adds a locale to moment, using the format supplied by `wp_localize_script()`. + +**Parameters** + +- **dateSettings** `Object`: Settings, including locale data. + + +<!-- END TOKEN(Autogenerated API docs) --> + <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> From e66738bc9fe3a21d7aea613a0ba536ccd09f7df3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Fri, 8 Mar 2019 08:31:34 +0100 Subject: [PATCH 605/691] block-serialization-default-parser: set up auto-generated API docs (#14280) --- bin/update-readmes.js | 2 +- .../README.md | 45 ++++++++--- .../src/index.js | 77 +++++++++++++++++++ 3 files changed, 112 insertions(+), 12 deletions(-) diff --git a/bin/update-readmes.js b/bin/update-readmes.js index a9acb6c9a1de6..b43b4fbd515e0 100755 --- a/bin/update-readmes.js +++ b/bin/update-readmes.js @@ -10,7 +10,7 @@ const packages = [ 'blob', 'block-editor', 'block-library', - //'block-serialization-default-parser', + 'block-serialization-default-parser', 'blocks', 'compose', //'data', diff --git a/packages/block-serialization-default-parser/README.md b/packages/block-serialization-default-parser/README.md index 254d8f7bdcd19..d64533b36b341 100644 --- a/packages/block-serialization-default-parser/README.md +++ b/packages/block-serialization-default-parser/README.md @@ -12,9 +12,20 @@ npm install @wordpress/block-serialization-default-parser --save _This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. Learn more about it in [Babel docs](https://babeljs.io/docs/en/next/caveats)._ -## Usage +## API + +<!-- START TOKEN(Autogenerated API docs) --> + +### parse + +[src/index.js#L150-L162](src/index.js#L150-L162) + +Parser function, that converts input HTML into a block based structure. + +**Usage** Input post: + ```html <!-- wp:columns {"columns":3} --> <div class="wp-block-columns has-3-columns"><!-- wp:column --> @@ -36,6 +47,7 @@ Input post: ``` Parsing code: + ```js import { parse } from '@wordpress/block-serialization-default-parser'; @@ -84,6 +96,17 @@ parse( post ) === [ ]; ``` +**Parameters** + +- **doc** `string`: The HTML document to parse. + +**Returns** + +`Array`: A block-based representation of the input HTML. + + +<!-- END TOKEN(Autogenerated API docs) --> + ## Theory ### What is different about this one from the spec-parser? @@ -98,26 +121,26 @@ Every serialized Gutenberg document is nominally an HTML document which, in addi This parser attempts to create a state-machine around the transitions triggered from those delimiters -- the "tokens" of the grammar. Every time we find one we should only be doing either of: - - enter a new block; - - exit out of a block. +- enter a new block; +- exit out of a block. Those actions have different effects depending on the context; for instance, when we exit a block we either need to add it to the output block list _or_ we need to append it as the next `innerBlock` on the parent block below it in the block stack (the place where we track open blocks). The details are documented below. The biggest challenge in this parser is making the right accounting of indices required to construct the `innerHTML` values for each block at every level of nesting depth. We take a simple approach: - - Start each newly opened block with an empty `innerHTML`. - - Whenever we push a first block into the `innerBlocks` list, add the content from where the content of the parent block started to where this inner block starts. - - Whenever we push another block into the `innerBlocks` list, add the content from where the previous inner block ended to where this inner block starts. - - When we close out an open block, add the content from where the last inner block ended to where the closing block delimiter starts. - - If there are no inner blocks then we take the entire content between the opening and closing block comment delimiters as the `innerHTML`. +- Start each newly opened block with an empty `innerHTML`. +- Whenever we push a first block into the `innerBlocks` list, add the content from where the content of the parent block started to where this inner block starts. +- Whenever we push another block into the `innerBlocks` list, add the content from where the previous inner block ended to where this inner block starts. +- When we close out an open block, add the content from where the last inner block ended to where the closing block delimiter starts. +- If there are no inner blocks then we take the entire content between the opening and closing block comment delimiters as the `innerHTML`. ### I meant, how does it perform? This parser operates much faster than the generated parser from the specification. Because we know more about the parsing than the PEG does we can take advantage of several tricks to improve our speed and memory usage: - - We only have one or two distinct tokens, depending on how you look at it, and they are all readily matched via a regular expression. Instead of parsing on a character-per-character basis we can allow the PCRE RegExp engine to skip over large swaths of the document for us in order to find those tokens. - - Since `preg_match()` takes an `offset` parameter we can crawl through the input without passing copies of the input text on every step. We can track our position in the string and only pass a number instead. - - Not copying all those strings means that we'll also skip many memory allocations. +- We only have one or two distinct tokens, depending on how you look at it, and they are all readily matched via a regular expression. Instead of parsing on a character-per-character basis we can allow the PCRE RegExp engine to skip over large swaths of the document for us in order to find those tokens. +- Since `preg_match()` takes an `offset` parameter we can crawl through the input without passing copies of the input text on every step. We can track our position in the string and only pass a number instead. +- Not copying all those strings means that we'll also skip many memory allocations. Further, tokenizing with a RegExp brings an additional advantage. The parser generated by the PEG provides predictable performance characteristics in exchange for control over tokenization rules -- it doesn't allow us to define RegExp patterns in the rules so as to guard against _e.g._ cataclysmic backtracking that would break the PEG guarantees. diff --git a/packages/block-serialization-default-parser/src/index.js b/packages/block-serialization-default-parser/src/index.js index 489f1f76bb1bb..e885e57af6269 100644 --- a/packages/block-serialization-default-parser/src/index.js +++ b/packages/block-serialization-default-parser/src/index.js @@ -70,6 +70,83 @@ function Frame( block, tokenStart, tokenLength, prevOffset, leadingHtmlStart ) { }; } +/** + * Parser function, that converts input HTML into a block based structure. + * + * @param {string} doc The HTML document to parse. + * + * @example + * Input post: + * ```html + * <!-- wp:columns {"columns":3} --> + * <div class="wp-block-columns has-3-columns"><!-- wp:column --> + * <div class="wp-block-column"><!-- wp:paragraph --> + * <p>Left</p> + * <!-- /wp:paragraph --></div> + * <!-- /wp:column --> + * + * <!-- wp:column --> + * <div class="wp-block-column"><!-- wp:paragraph --> + * <p><strong>Middle</strong></p> + * <!-- /wp:paragraph --></div> + * <!-- /wp:column --> + * + * <!-- wp:column --> + * <div class="wp-block-column"></div> + * <!-- /wp:column --></div> + * <!-- /wp:columns --> + * ``` + * + * Parsing code: + * ```js + * import { parse } from '@wordpress/block-serialization-default-parser'; + * + * parse( post ) === [ + * { + * blockName: "core/columns", + * attrs: { + * columns: 3 + * }, + * innerBlocks: [ + * { + * blockName: "core/column", + * attrs: null, + * innerBlocks: [ + * { + * blockName: "core/paragraph", + * attrs: null, + * innerBlocks: [], + * innerHTML: "\n<p>Left</p>\n" + * } + * ], + * innerHTML: '\n<div class="wp-block-column"></div>\n' + * }, + * { + * blockName: "core/column", + * attrs: null, + * innerBlocks: [ + * { + * blockName: "core/paragraph", + * attrs: null, + * innerBlocks: [], + * innerHTML: "\n<p><strong>Middle</strong></p>\n" + * } + * ], + * innerHTML: '\n<div class="wp-block-column"></div>\n' + * }, + * { + * blockName: "core/column", + * attrs: null, + * innerBlocks: [], + * innerHTML: '\n<div class="wp-block-column"></div>\n' + * } + * ], + * innerHTML: '\n<div class="wp-block-columns has-3-columns">\n\n\n\n</div>\n' + * } + * ]; + * ``` + * @return {Array} A block-based representation of the input HTML. + */ export const parse = ( doc ) => { document = doc; offset = 0; From 9e7a851b06a074402eab911d87f85261590fadcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <iseulde@automattic.com> Date: Fri, 8 Mar 2019 10:45:21 +0100 Subject: [PATCH 606/691] RichText: collapse toolbar (#14233) * RichText: collapse toolbar * Move inline image * Add balanced margin * Add tooltip * Update e2e test * Update e2e test * Remove RichTextInserterItem * Ensure there are fills before rendering * Make toggle button bigger * Use clickBlockToolbarButton * Fix e2e test --- .../components/src/dropdown-menu/index.js | 2 + .../specs/adding-inline-tokens.test.js | 6 ++- .../specs/keyboard-navigable-blocks.test.js | 10 ++--- .../__snapshots__/format-api.test.js.snap | 2 +- .../specs/plugins/format-api.test.js | 11 ++---- packages/editor/src/components/index.js | 1 - .../components/inserter/inline-elements.js | 35 ----------------- .../editor/src/components/inserter/menu.js | 3 -- .../rich-text/format-toolbar/index.js | 20 +++++++++- .../rich-text/format-toolbar/style.scss | 4 ++ .../editor/src/components/rich-text/index.js | 1 - .../rich-text/inserter-list-item.js | 38 ------------------- packages/format-library/src/code/index.js | 25 ++++++++---- packages/format-library/src/image/index.js | 6 +-- .../format-library/src/strikethrough/index.js | 1 - 15 files changed, 58 insertions(+), 107 deletions(-) delete mode 100644 packages/editor/src/components/inserter/inline-elements.js delete mode 100644 packages/editor/src/components/rich-text/inserter-list-item.js diff --git a/packages/components/src/dropdown-menu/index.js b/packages/components/src/dropdown-menu/index.js index 9434eab2098d7..e301935a0ac60 100644 --- a/packages/components/src/dropdown-menu/index.js +++ b/packages/components/src/dropdown-menu/index.js @@ -22,6 +22,7 @@ function DropdownMenu( { menuLabel, controls, className, + position, } ) { if ( ! controls || ! controls.length ) { return null; @@ -37,6 +38,7 @@ function DropdownMenu( { <Dropdown className={ classnames( 'components-dropdown-menu', className ) } contentClassName="components-dropdown-menu__popover" + position={ position } renderToggle={ ( { isOpen, onToggle } ) => { const openOnArrowDown = ( event ) => { if ( ! isOpen && event.keyCode === DOWN ) { diff --git a/packages/e2e-tests/specs/adding-inline-tokens.test.js b/packages/e2e-tests/specs/adding-inline-tokens.test.js index 6fe5d42e84912..5bcd55498489c 100644 --- a/packages/e2e-tests/specs/adding-inline-tokens.test.js +++ b/packages/e2e-tests/specs/adding-inline-tokens.test.js @@ -12,8 +12,9 @@ import uuid from 'uuid/v4'; import { clickBlockAppender, getEditedPostContent, - insertBlock, createNewPost, + clickBlockToolbarButton, + clickButton, } from '@wordpress/e2e-test-utils'; describe( 'adding inline tokens', () => { @@ -26,7 +27,8 @@ describe( 'adding inline tokens', () => { await clickBlockAppender(); await page.keyboard.type( 'a ' ); - await insertBlock( 'Inline Image', 'Inline Elements' ); + await clickBlockToolbarButton( 'More Rich Text Controls' ); + await clickButton( 'Inline Image' ); // Wait for media modal to appear and upload image. await page.waitForSelector( '.media-modal input[type=file]' ); diff --git a/packages/e2e-tests/specs/keyboard-navigable-blocks.test.js b/packages/e2e-tests/specs/keyboard-navigable-blocks.test.js index a35afb34e522b..e9db2951cc5f6 100644 --- a/packages/e2e-tests/specs/keyboard-navigable-blocks.test.js +++ b/packages/e2e-tests/specs/keyboard-navigable-blocks.test.js @@ -120,19 +120,19 @@ const tabThroughBlockToolbar = async () => { // Tab to focus on the 'Strikethrough' formatting button await page.keyboard.press( 'Tab' ); - const isFocusedStrikethroughFormattingButton = await page.evaluate( () => - document.activeElement.classList.contains( 'components-toolbar__control' ) + const isFocusedMoreFormattingDropdown = await page.evaluate( () => + document.activeElement.classList.contains( 'components-dropdown-menu__toggle' ) ); - await expect( isFocusedStrikethroughFormattingButton ).toBe( true ); + await expect( isFocusedMoreFormattingDropdown ).toBe( true ); // Tab to focus on the 'More formatting' dropdown toggle await page.keyboard.press( 'Tab' ); - const isFocusedMoreFormattingDropdown = await page.evaluate( () => + const isFocusedBlockSettingsDropdown = await page.evaluate( () => document.activeElement.classList.contains( 'editor-block-settings-menu__toggle' ) ); - await expect( isFocusedMoreFormattingDropdown ).toBe( true ); + await expect( isFocusedBlockSettingsDropdown ).toBe( true ); }; describe( 'Order of block keyboard navigation', () => { diff --git a/packages/e2e-tests/specs/plugins/__snapshots__/format-api.test.js.snap b/packages/e2e-tests/specs/plugins/__snapshots__/format-api.test.js.snap index 8dbbc915521be..20a79ccc33efb 100644 --- a/packages/e2e-tests/specs/plugins/__snapshots__/format-api.test.js.snap +++ b/packages/e2e-tests/specs/plugins/__snapshots__/format-api.test.js.snap @@ -2,6 +2,6 @@ exports[`Using Format API Clicking the control wraps the selected text properly with HTML code 1`] = ` "<!-- wp:paragraph --> -<p><a href=\\"#test\\" class=\\"my-plugin-link\\">First paragraph</a></p> +<p>First <a href=\\"#test\\" class=\\"my-plugin-link\\">paragraph</a></p> <!-- /wp:paragraph -->" `; diff --git a/packages/e2e-tests/specs/plugins/format-api.test.js b/packages/e2e-tests/specs/plugins/format-api.test.js index 25f56f5a39c77..814ace8fd842a 100644 --- a/packages/e2e-tests/specs/plugins/format-api.test.js +++ b/packages/e2e-tests/specs/plugins/format-api.test.js @@ -9,6 +9,7 @@ import { deactivatePlugin, getEditedPostContent, pressKeyWithModifier, + clickButton, } from '@wordpress/e2e-test-utils'; describe( 'Using Format API', () => { @@ -24,18 +25,12 @@ describe( 'Using Format API', () => { await createNewPost(); } ); - it( 'Format toolbar is present in a paragraph block', async () => { - await clickBlockAppender(); - await page.keyboard.type( 'First paragraph' ); - await clickBlockToolbarButton( 'Custom Link' ); - } ); - it( 'Clicking the control wraps the selected text properly with HTML code', async () => { await clickBlockAppender(); await page.keyboard.type( 'First paragraph' ); await pressKeyWithModifier( 'shiftAlt', 'ArrowLeft' ); - await pressKeyWithModifier( 'primary', 'A' ); - await clickBlockToolbarButton( 'Custom Link' ); + await clickBlockToolbarButton( 'More Rich Text Controls' ); + await clickButton( 'Custom Link' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); } ); diff --git a/packages/editor/src/components/index.js b/packages/editor/src/components/index.js index 94cadfc7ddc24..9ebb2dacc8640 100644 --- a/packages/editor/src/components/index.js +++ b/packages/editor/src/components/index.js @@ -22,7 +22,6 @@ export { default as RichText, RichTextShortcut, RichTextToolbarButton, - RichTextInserterItem, UnstableRichTextInputEvent, } from './rich-text'; export { default as ServerSideRender } from './server-side-render'; diff --git a/packages/editor/src/components/inserter/inline-elements.js b/packages/editor/src/components/inserter/inline-elements.js deleted file mode 100644 index ee45d0ea3536d..0000000000000 --- a/packages/editor/src/components/inserter/inline-elements.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * External dependencies - */ -import { isEmpty } from 'lodash'; - -/** - * WordPress dependencies - */ -import { __ } from '@wordpress/i18n'; -import { PanelBody, Slot } from '@wordpress/components'; - -/** - * Internal dependencies - */ -import BlockTypesList from '../block-types-list'; - -const InserterInlineElements = ( { filterValue } ) => { - return ( - <Slot name="Inserter.InlineElements" fillProps={ { filterValue } }> - { ( fills ) => ! isEmpty( fills ) && ( - <PanelBody - title={ __( 'Inline Elements' ) } - initialOpen={ false } - className="editor-inserter__inline-elements" - > - <BlockTypesList> - { fills } - </BlockTypesList> - </PanelBody> - ) } - </Slot> - ); -}; - -export default InserterInlineElements; diff --git a/packages/editor/src/components/inserter/menu.js b/packages/editor/src/components/inserter/menu.js index 023a4b1102fe5..92253b32cfc1d 100644 --- a/packages/editor/src/components/inserter/menu.js +++ b/packages/editor/src/components/inserter/menu.js @@ -40,7 +40,6 @@ import { addQueryArgs } from '@wordpress/url'; import BlockPreview from '../block-preview'; import BlockTypesList from '../block-types-list'; import ChildBlocks from './child-blocks'; -import InserterInlineElements from './inline-elements'; const MAX_SUGGESTED_ITEMS = 9; @@ -291,8 +290,6 @@ export class InserterMenu extends Component { </PanelBody> } - <InserterInlineElements filterValue={ filterValue } /> - { map( getCategories(), ( category ) => { const categoryItems = itemsPerCategory[ category.slug ]; if ( ! categoryItems || ! categoryItems.length ) { diff --git a/packages/editor/src/components/rich-text/format-toolbar/index.js b/packages/editor/src/components/rich-text/format-toolbar/index.js index 23ee381380f66..c980119c2ebe7 100644 --- a/packages/editor/src/components/rich-text/format-toolbar/index.js +++ b/packages/editor/src/components/rich-text/format-toolbar/index.js @@ -1,8 +1,15 @@ +/** + * External dependencies + */ + +import { orderBy } from 'lodash'; + /** * WordPress dependencies */ -import { Toolbar, Slot } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { Toolbar, Slot, DropdownMenu } from '@wordpress/components'; const FormatToolbar = ( { controls } ) => { return ( @@ -11,7 +18,16 @@ const FormatToolbar = ( { controls } ) => { { controls.map( ( format ) => <Slot name={ `RichText.ToolbarControls.${ format }` } key={ format } /> ) } - <Slot name="RichText.ToolbarControls" /> + <Slot name="RichText.ToolbarControls"> + { ( fills ) => fills.length && + <DropdownMenu + icon={ false } + position="bottom left" + label={ __( 'More Rich Text Controls' ) } + controls={ orderBy( fills.map( ( [ { props } ] ) => props ), 'title' ) } + /> + } + </Slot> </Toolbar> </div> ); diff --git a/packages/editor/src/components/rich-text/format-toolbar/style.scss b/packages/editor/src/components/rich-text/format-toolbar/style.scss index 2b6fa1e5c07b5..c9f2d0de0cc91 100644 --- a/packages/editor/src/components/rich-text/format-toolbar/style.scss +++ b/packages/editor/src/components/rich-text/format-toolbar/style.scss @@ -7,3 +7,7 @@ position: absolute; transform: translateX(-50%); } + +.editor-format-toolbar .components-dropdown-menu__toggle .components-dropdown-menu__indicator::after { + margin: 7px; +} diff --git a/packages/editor/src/components/rich-text/index.js b/packages/editor/src/components/rich-text/index.js index e72a70b61e46c..5b91674b9a673 100644 --- a/packages/editor/src/components/rich-text/index.js +++ b/packages/editor/src/components/rich-text/index.js @@ -1191,5 +1191,4 @@ RichTextContainer.Content.defaultProps = { export default RichTextContainer; export { RichTextShortcut } from './shortcut'; export { RichTextToolbarButton } from './toolbar-button'; -export { RichTextInserterItem } from './inserter-list-item'; export { UnstableRichTextInputEvent } from './input-event'; diff --git a/packages/editor/src/components/rich-text/inserter-list-item.js b/packages/editor/src/components/rich-text/inserter-list-item.js deleted file mode 100644 index 82ff6d8b8b446..0000000000000 --- a/packages/editor/src/components/rich-text/inserter-list-item.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * WordPress dependencies - */ -import { normalizeIconObject } from '@wordpress/blocks'; -import { Fill } from '@wordpress/components'; -import { withSelect } from '@wordpress/data'; - -/** - * Internal dependencies - */ -import InserterListItem from '../inserter-list-item'; -import { normalizeTerm } from '../inserter/menu'; - -function isResult( keywords, filterValue ) { - return keywords.some( ( string ) => - normalizeTerm( string ).indexOf( normalizeTerm( filterValue ) ) !== -1 - ); -} - -export const RichTextInserterItem = withSelect( ( select, { name } ) => ( { - formatType: select( 'core/rich-text' ).getFormatType( name ), -} ) )( ( props ) => { - return ( - <Fill name="Inserter.InlineElements"> - { ( { filterValue } ) => { - const { keywords = [], title } = props.formatType; - - keywords.push( title, props.title ); - - if ( filterValue && ! isResult( keywords, filterValue ) ) { - return null; - } - - return <InserterListItem { ...props } icon={ normalizeIconObject( props.icon ) } />; - } } - </Fill> - ); -} ); diff --git a/packages/format-library/src/code/index.js b/packages/format-library/src/code/index.js index d5e0b2705f245..f5bf96bb32a2e 100644 --- a/packages/format-library/src/code/index.js +++ b/packages/format-library/src/code/index.js @@ -2,8 +2,9 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; +import { Fragment } from '@wordpress/element'; import { toggleFormat } from '@wordpress/rich-text'; -import { RichTextShortcut } from '@wordpress/editor'; +import { RichTextShortcut, RichTextToolbarButton } from '@wordpress/editor'; const name = 'core/code'; @@ -12,15 +13,25 @@ export const code = { title: __( 'Code' ), tagName: 'code', className: null, - edit( { value, onChange } ) { + edit( { value, onChange, isActive } ) { const onToggle = () => onChange( toggleFormat( value, { type: name } ) ); return ( - <RichTextShortcut - type="access" - character="x" - onUse={ onToggle } - /> + <Fragment> + <RichTextShortcut + type="access" + character="x" + onUse={ onToggle } + /> + <RichTextToolbarButton + icon="editor-code" + title={ __( 'Code' ) } + onClick={ onToggle } + isActive={ isActive } + shortcutType="access" + shortcutCharacter="x" + /> + </Fragment> ); }, }; diff --git a/packages/format-library/src/image/index.js b/packages/format-library/src/image/index.js index b44d6ddc57c62..b7a3db3b1d4c3 100644 --- a/packages/format-library/src/image/index.js +++ b/packages/format-library/src/image/index.js @@ -5,7 +5,7 @@ import { Path, SVG, TextControl, Popover, IconButton, PositionedAtSelection } fr import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; import { insertObject } from '@wordpress/rich-text'; -import { MediaUpload, RichTextInserterItem, MediaUploadCheck } from '@wordpress/editor'; +import { MediaUpload, RichTextToolbarButton, MediaUploadCheck } from '@wordpress/editor'; import { LEFT, RIGHT, UP, DOWN, BACKSPACE, ENTER } from '@wordpress/keycodes'; const ALLOWED_MEDIA_TYPES = [ 'image' ]; @@ -87,11 +87,11 @@ export const image = { return ( <MediaUploadCheck> - <RichTextInserterItem - name={ name } + <RichTextToolbarButton icon={ <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><Path d="M4 16h10c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v9c0 1.1.9 2 2 2zM4 5h10v9H4V5zm14 9v2h4v-2h-4zM2 20h20v-2H2v2zm6.4-8.8L7 9.4 5 12h8l-2.6-3.4-2 2.6z" /></SVG> } title={ __( 'Inline Image' ) } onClick={ this.openModal } + isActive={ isActive } /> { this.state.modal && <MediaUpload allowedTypes={ ALLOWED_MEDIA_TYPES } diff --git a/packages/format-library/src/strikethrough/index.js b/packages/format-library/src/strikethrough/index.js index eba22e7a64f32..7caa96781730b 100644 --- a/packages/format-library/src/strikethrough/index.js +++ b/packages/format-library/src/strikethrough/index.js @@ -24,7 +24,6 @@ export const strikethrough = { onUse={ onToggle } /> <RichTextToolbarButton - name="strikethrough" icon="editor-strikethrough" title={ __( 'Strikethrough' ) } onClick={ onToggle } From f145bd89a3fdc023de8ef2c302e9b16e4a1bb3a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <iseulde@automattic.com> Date: Fri, 8 Mar 2019 10:45:48 +0100 Subject: [PATCH 607/691] RichText: fix br padding in multiline and nesting (#14315) * RichText: fix br padding in multiline and nesting * Add e2e test --- .../specs/__snapshots__/writing-flow.test.js.snap | 6 ++++++ packages/e2e-tests/specs/writing-flow.test.js | 8 ++++++++ packages/rich-text/src/create.js | 4 ++++ 3 files changed, 18 insertions(+) diff --git a/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap b/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap index dca0ce4763cdb..41f6fe04a2b53 100644 --- a/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap +++ b/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap @@ -126,6 +126,12 @@ exports[`adding blocks should navigate around nested inline boundaries 2`] = ` <!-- /wp:paragraph -->" `; +exports[`adding blocks should not create extra line breaks in multiline value 1`] = ` +"<!-- wp:quote --> +<blockquote class=\\"wp-block-quote\\"><p></p></blockquote> +<!-- /wp:quote -->" +`; + exports[`adding blocks should not delete surrounding space when deleting a selected word 1`] = ` "<!-- wp:paragraph --> <p>alpha gamma</p> diff --git a/packages/e2e-tests/specs/writing-flow.test.js b/packages/e2e-tests/specs/writing-flow.test.js index d0b46970c87f4..62ece15801640 100644 --- a/packages/e2e-tests/specs/writing-flow.test.js +++ b/packages/e2e-tests/specs/writing-flow.test.js @@ -7,6 +7,7 @@ import { createNewPost, pressKeyTimes, pressKeyWithModifier, + insertBlock, } from '@wordpress/e2e-test-utils'; describe( 'adding blocks', () => { @@ -208,6 +209,13 @@ describe( 'adding blocks', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + it( 'should not create extra line breaks in multiline value', async () => { + await insertBlock( 'Quote' ); + await page.keyboard.type( 'a' ); + await page.keyboard.press( 'Backspace' ); + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + it( 'should navigate native inputs vertically, not horizontally', async () => { // See: https://github.com/WordPress/gutenberg/issues/9626 diff --git a/packages/rich-text/src/create.js b/packages/rich-text/src/create.js index 9fa677828209d..939b56fb9a8ed 100644 --- a/packages/rich-text/src/create.js +++ b/packages/rich-text/src/create.js @@ -337,6 +337,7 @@ function createFromElement( { multilineTag, multilineWrapperTags, currentWrapperTags: [ ...currentWrapperTags, format ], + isEditableTree, } ); format = undefined; } else { @@ -345,6 +346,7 @@ function createFromElement( { range, multilineTag, multilineWrapperTags, + isEditableTree, } ); } @@ -423,6 +425,7 @@ function createFromMultilineElement( { multilineTag, multilineWrapperTags, currentWrapperTags = [], + isEditableTree, } ) { const accumulator = createEmptyValue(); @@ -446,6 +449,7 @@ function createFromMultilineElement( { multilineTag, multilineWrapperTags, currentWrapperTags, + isEditableTree, } ); // Multiline value text should be separated by a double line break. From f638d647b5fc3175a0b2a0c169fe877a32e70fc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <iseulde@automattic.com> Date: Fri, 8 Mar 2019 10:46:26 +0100 Subject: [PATCH 608/691] Docs: Release: Suggest lighter SVN checkout (#14259) --- docs/contributors/release.md | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/docs/contributors/release.md b/docs/contributors/release.md index d19f1664dc12f..cee20e5cc0219 100644 --- a/docs/contributors/release.md +++ b/docs/contributors/release.md @@ -87,7 +87,7 @@ If a bug is found in a release candidate and a fix is committed to `master`, we 5. Tag the RC version. `git tag vx.x.0-rc.2` from the release branch. 6. Push the tag `git push --tags`. 7. Merge the version bump pull request and avoid removing the release branch. -8. Follow the steps in [build the plugin](#build-the-plugin) and [publish the release on GitHub](#publish-the-release-on-github). +8. Follow the steps in [build the plugin](#build-the-plugin) and [publish the release on GitHub](#publish-the-release-on-github). You can copy the existing changelog from the previous release candidate. Let other contributors know that a new release candidate has been released in the [`#core-editor` channel](https://wordpress.slack.com/messages/C02QB2JS7) and the call for testing post. @@ -144,11 +144,11 @@ Creating a release involves: You'll need to use Subversion to publish the plugin to WordPress.org. -1. Do an SVN checkout of `https://wordpress.org/plugins/gutenberg/`: - * If this is your first checkout, run: `svn checkout https://plugins.svn.wordpress.org/gutenberg` +1. Do an SVN checkout of `https://wordpress.org/plugins/gutenberg/trunk`: + * If this is your first checkout, run: `svn checkout https://plugins.svn.wordpress.org/gutenberg/trunk` * If you already have a copy, run: `svn up` -2. Delete the contents of `trunk` except for the `readme.txt` and `changelog.txt` files (these files don’t exist in the `git` repo, only in Subversion). -3. Extract the contents of the zip file to `trunk`. +2. Delete the contents except for the `readme.txt` and `changelog.txt` files (these files don’t exist in the `git` repo, only in Subversion). +3. Extract the contents of the zip file. 4. Edit `readme.txt`, replacing the changelog for the previous version with the current release's changelog. 5. Add the changelog for the current release to `changelog.txt`. 6. Add new files/remove deleted files from the repository: @@ -158,17 +158,16 @@ svn st | grep '^\?' | awk '{print $2}' | xargs svn add # Delete old files: svn st | grep '^!' | awk '{print $2}' | xargs svn rm ``` -7. Commit the new version to `trunk`: +7. Commit the new version: ```bash -# Replace vX.X.X with your version: -svn ci -m "Committing Gutenberg version vX.X.X" +# Replace X.X.X with your version: +svn ci -m "Committing Gutenberg version X.X.X" ``` -8. Tag the new version. Make sure you're in the root directory of `gutenberg`, then run: +8. Tag the new version: ```bash -svn cp trunk tags/X.X.X -svn ci -m "Tagging Gutenberg version X.X.X" +svn cp https://plugins.svn.wordpress.org/gutenberg/trunk https://plugins.svn.wordpress.org/gutenberg/tags/X.X.X -m "Tagging Gutenberg version X.X.X" ``` -9. Edit `trunk/readme.txt` to point to the new tag. The **Stable version** header in `readme.txt` should be updated to match the new release version number. After updating and committing that, the new version should be released: +9. Edit `readme.txt` to point to the new tag. The **Stable version** header in `readme.txt` should be updated to match the new release version number. After updating and committing that, the new version should be released: ```bash svn ci -m "Releasing Gutenberg version X.X.X" ``` From 6b56436088621a5e341fb3df4850a11a72b5c4e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Fri, 8 Mar 2019 11:15:08 +0100 Subject: [PATCH 609/691] plugins: set up auto-generated API docs (#14263) --- bin/update-readmes.js | 2 +- packages/plugins/README.md | 158 +++++++++++------- packages/plugins/src/api/index.js | 88 +++++++++- .../src/components/plugin-area/index.js | 29 ++++ 4 files changed, 217 insertions(+), 60 deletions(-) diff --git a/bin/update-readmes.js b/bin/update-readmes.js index b43b4fbd515e0..b9a6dd6d2a92a 100755 --- a/bin/update-readmes.js +++ b/bin/update-readmes.js @@ -25,7 +25,7 @@ const packages = [ 'html-entities', 'i18n', 'keycodes', - //'plugins', + 'plugins', 'priority-queue', 'redux-routine', 'rich-text', diff --git a/packages/plugins/README.md b/packages/plugins/README.md index 4c19538984892..1dfa17b3fed22 100644 --- a/packages/plugins/README.md +++ b/packages/plugins/README.md @@ -14,27 +14,81 @@ _This package assumes that your code will run in an **ES2015+** environment. If ### Plugins API -The plugins API contains the following methods: +<!-- START TOKEN(Autogenerated API docs) --> -#### `wp.plugins.registerPlugin( name: string, settings: Object )` +#### getPlugin -This method registers a new plugin. +[src/index.js#L2-L2](src/index.js#L2-L2) -This method takes two arguments: +Returns a registered plugin settings. -1. `name`: A string identifying the plugin. Must be unique across all registered plugins. -2. `settings`: An object containing the following data: - - `icon: string | WPElement | Function` - An icon to be shown in the UI. It can be a slug - of the [Dashicon](https://developer.wordpress.org/resource/dashicons/#awards), - or an element (or function returning an element) if you choose to render your own SVG. - - `render`: A component containing the UI elements to be rendered. +**Parameters** -See [the edit-post module documentation](/packages/edit-post/README.md) for available components. +- **name** `string`: Plugin name. + +**Returns** + +`?Object`: Plugin setting. + +#### getPlugins + +[src/index.js#L2-L2](src/index.js#L2-L2) + +Returns all registered plugins. + +**Returns** + +`Array`: Plugin settings. + +#### PluginArea + +[src/index.js#L1-L1](src/index.js#L1-L1) + +A component that renders all plugin fills in a hidden div. + +**Usage** + +```js +// Using ES5 syntax +var el = wp.element.createElement; +var PluginArea = wp.plugins.PluginArea; + +function Layout() { + return el( + 'div', + {}, + 'Content of the page', + PluginArea + ); +} +``` + +```js +// Using ESNext syntax +const { PluginArea } = wp.plugins; + +const Layout = () => ( + <div> + Content of the page + <PluginArea /> + </div> +); +``` + +**Returns** + +`WPElement`: Plugin area. + +#### registerPlugin + +[src/index.js#L2-L2](src/index.js#L2-L2) + +Registers a plugin to the editor. + +**Usage** -_Example:_ -{% codetabs %} -{% ES5 %} ```js +// Using ES5 syntax var el = wp.element.createElement; var Fragment = wp.element.Fragment; var PluginSidebar = wp.editPost.PluginSidebar; @@ -68,8 +122,8 @@ registerPlugin( 'plugin-name', { } ); ``` -{% ESNext %} -```jsx +```js +// Using ESNext syntax const { Fragment } = wp.element; const { PluginSidebar, PluginSidebarMoreMenuItem } = wp.editPost; const { registerPlugin } = wp.plugins; @@ -95,72 +149,64 @@ registerPlugin( 'plugin-name', { render: Component, } ); ``` -{% end %} -#### `wp.plugins.unregisterPlugin( name: string )` +**Parameters** + +- **name** `string`: A string identifying the plugin. Must be unique across all registered plugins. +- **settings** `Object`: The settings for this plugin. +- **settings.icon** `(string|WPElement|Function)`: An icon to be shown in the UI. It can be a slug of the Dashicon, or an element (or function returning an element) if you choose to render your own SVG. +- **settings.render** `Function`: A component containing the UI elements to be rendered. -This method unregisters an existing plugin. +**Returns** -This method takes one argument: +`Object`: The final plugin settings object. -1. `name`: A string identifying the plugin. +#### unregisterPlugin -_Example:_ +[src/index.js#L2-L2](src/index.js#L2-L2) -{% codetabs %} +Unregisters a plugin by name. + +**Usage** -{% ES5 %} ```js +// Using ES5 syntax var unregisterPlugin = wp.plugins.unregisterPlugin; unregisterPlugin( 'plugin-name' ); ``` -{% ESNext %} - ```js +// Using ESNext syntax const { unregisterPlugin } = wp.plugins; unregisterPlugin( 'plugin-name' ); ``` -{% end %} -### Components +**Parameters** -#### `PluginArea` +- **name** `string`: Plugin name. -A component that renders all registered plugins in a hidden div. +**Returns** -_Example:_ -{% codetabs %} +`?WPPlugin`: The previous plugin settings object, if it has been successfully unregistered; otherwise `undefined`. -{% ES5 %} -```js -var el = wp.element.createElement; -var PluginArea = wp.plugins.PluginArea; +#### withPluginContext -function Layout() { - return el( - 'div', - {}, - 'Content of the page', - PluginArea - ); -} -``` +[src/index.js#L1-L1](src/index.js#L1-L1) -{% ESNext %} +A Higher Order Component used to inject Plugin context to the +wrapped component. -```jsx -const { PluginArea } = wp.plugins; +**Parameters** -const Layout = () => ( - <div> - Content of the page - <PluginArea /> - </div> -); -``` -{% end %} +- **mapContextToProps** `Function`: Function called on every context change, expected to return object of props to merge with the component's own props. + +**Returns** + +`Component`: Enhanced component with injected context as props. + + +<!-- END TOKEN(Autogenerated API docs) --> <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/plugins/src/api/index.js b/packages/plugins/src/api/index.js index bb5873ad01bf3..afcd733db880e 100644 --- a/packages/plugins/src/api/index.js +++ b/packages/plugins/src/api/index.js @@ -20,10 +20,76 @@ const plugins = {}; /** * Registers a plugin to the editor. * - * @param {string} name The name of the plugin. + * @param {string} name A string identifying the plugin. Must be unique across all registered plugins. * @param {Object} settings The settings for this plugin. - * @param {Function} settings.render The function that renders the plugin. - * @param {string|WPElement|Function} settings.icon An icon to be shown in the UI. + * @param {string|WPElement|Function} settings.icon An icon to be shown in the UI. It can be a slug of the Dashicon, + * or an element (or function returning an element) if you choose to render your own SVG. + * @param {Function} settings.render A component containing the UI elements to be rendered. + * + * @example <caption>ES5</caption> + * ```js + * // Using ES5 syntax + * var el = wp.element.createElement; + * var Fragment = wp.element.Fragment; + * var PluginSidebar = wp.editPost.PluginSidebar; + * var PluginSidebarMoreMenuItem = wp.editPost.PluginSidebarMoreMenuItem; + * var registerPlugin = wp.plugins.registerPlugin; + * + * function Component() { + * return el( + * Fragment, + * {}, + * el( + * PluginSidebarMoreMenuItem, + * { + * target: 'sidebar-name', + * }, + * 'My Sidebar' + * ), + * el( + * PluginSidebar, + * { + * name: 'sidebar-name', + * title: 'My Sidebar', + * }, + * 'Content of the sidebar' + * ) + * ); + * } + * registerPlugin( 'plugin-name', { + * icon: 'smiley', + * render: Component, + * } ); + * ``` + * + * @example <caption>ESNext</caption> + * ```js + * // Using ESNext syntax + * const { Fragment } = wp.element; + * const { PluginSidebar, PluginSidebarMoreMenuItem } = wp.editPost; + * const { registerPlugin } = wp.plugins; + * + * const Component = () => ( + * <Fragment> + * <PluginSidebarMoreMenuItem + * target="sidebar-name" + * > + * My Sidebar + * </PluginSidebarMoreMenuItem> + * <PluginSidebar + * name="sidebar-name" + * title="My Sidebar" + * > + * Content of the sidebar + * </PluginSidebar> + * </Fragment> + * ); + * + * registerPlugin( 'plugin-name', { + * icon: 'smiley', + * render: Component, + * } ); + * ``` * * @return {Object} The final plugin settings object. */ @@ -77,6 +143,22 @@ export function registerPlugin( name, settings ) { * * @param {string} name Plugin name. * + * @example <caption>ES5</caption> + * ```js + * // Using ES5 syntax + * var unregisterPlugin = wp.plugins.unregisterPlugin; + * + * unregisterPlugin( 'plugin-name' ); + * ``` + * + * @example <caption>ESNext</caption> + * ```js + * // Using ESNext syntax + * const { unregisterPlugin } = wp.plugins; + * + * unregisterPlugin( 'plugin-name' ); + * ``` + * * @return {?WPPlugin} The previous plugin settings object, if it has been * successfully unregistered; otherwise `undefined`. */ diff --git a/packages/plugins/src/components/plugin-area/index.js b/packages/plugins/src/components/plugin-area/index.js index 4bfd74176991f..1cf6f15b52879 100644 --- a/packages/plugins/src/components/plugin-area/index.js +++ b/packages/plugins/src/components/plugin-area/index.js @@ -18,6 +18,35 @@ import { getPlugins } from '../../api'; /** * A component that renders all plugin fills in a hidden div. * + * @example <caption>ES5</caption> + * ```js + * // Using ES5 syntax + * var el = wp.element.createElement; + * var PluginArea = wp.plugins.PluginArea; + * + * function Layout() { + * return el( + * 'div', + * {}, + * 'Content of the page', + * PluginArea + * ); + * } + * ``` + * + * @example <caption>ESNext</caption> + * ```js + * // Using ESNext syntax + * const { PluginArea } = wp.plugins; + * + * const Layout = () => ( + * <div> + * Content of the page + * <PluginArea /> + * </div> + * ); + * ``` + * * @return {WPElement} Plugin area. */ class PluginArea extends Component { From 359858da0675943d8a759a0a7c03e7b3846536f5 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Fri, 8 Mar 2019 11:16:49 +0100 Subject: [PATCH 610/691] Move the block components to the block editor module (#14112) --- .../filters/autocomplete-filters.md | 2 +- .../developers/packages.md | 6 +- lib/client-assets.php | 14 +- lib/packages-dependencies.php | 14 + package-lock.json | 26 +- packages/block-editor/README.md | 439 +++++++++++++++++- packages/block-editor/package.json | 19 +- .../src/components/alignment-toolbar/index.js | 0 .../test/__snapshots__/index.js.snap | 0 .../alignment-toolbar/test/index.js | 0 .../src/components/autocomplete/README.md | 0 .../src/components/autocomplete/index.js | 0 .../src/components/autocomplete/test/index.js | 0 .../src/components/block-actions/index.js | 0 .../block-alignment-toolbar/index.js | 0 .../test/__snapshots__/index.js.snap | 0 .../block-alignment-toolbar/test/index.js | 0 .../src/components/block-compare/README.md | 0 .../components/block-compare/block-view.js | 0 .../src/components/block-compare/index.js | 0 .../src/components/block-compare/style.scss | 0 .../test/__snapshots__/block-view.js.snap | 0 .../block-compare/test/block-view.js | 0 .../src/components/block-controls/index.js | 0 .../test/__snapshots__/index.js.snap | 0 .../components/block-controls/test/index.js | 0 .../src/components/block-draggable/index.js | 0 .../src/components/block-drop-zone/README.md | 0 .../src/components/block-drop-zone/index.js | 0 .../src/components/block-drop-zone/style.scss | 0 .../src/components/block-edit/context.js | 0 .../src/components/block-edit/edit.js | 0 .../src/components/block-edit/edit.native.js | 0 .../src/components/block-edit/index.js | 0 .../src/components/block-edit/test/edit.js | 0 .../block-editor-keyboard-shortcuts/index.js} | 0 .../components/block-format-controls/index.js | 0 .../src/components/block-icon/index.js | 0 .../src/components/block-icon/style.scss | 0 .../src/components/block-icon/test/index.js | 0 .../src/components/block-inspector/index.js | 0 .../src/components/block-inspector/style.scss | 0 .../components/block-list-appender/index.js | 0 .../components/block-list-appender/style.scss | 0 .../block-list/block-contextual-toolbar.js | 0 .../block-list/block-crash-boundary.js | 0 .../block-list/block-crash-warning.js | 0 .../src/components/block-list/block-html.js | 0 .../block-list/block-invalid-warning.js | 0 .../block-list/block-mobile-toolbar.js | 0 .../src/components/block-list/block.js | 0 .../src/components/block-list/breadcrumb.js | 0 .../src/components/block-list/hover-area.js | 0 .../src/components/block-list/index.js | 0 .../components/block-list/insertion-point.js | 0 .../components/block-list/multi-controls.js | 0 .../src/components/block-list/style.scss | 0 .../components/block-list/test/block-html.js | 0 .../src/components/block-mover/drag-handle.js | 0 .../src/components/block-mover/icons.js | 0 .../src/components/block-mover/index.js | 0 .../block-mover/mover-description.js | 0 .../src/components/block-mover/style.scss | 0 .../src/components/block-mover/test/index.js | 0 .../block-mover/test/mover-description.js | 0 .../components/block-navigation/dropdown.js | 0 .../src/components/block-navigation/index.js | 0 .../components/block-navigation/style.scss | 0 .../src/components/block-preview/index.js | 0 .../src/components/block-preview/style.scss | 0 .../block-selection-clearer/index.js | 0 .../block-convert-button.js | 0 .../block-html-convert-button.js | 0 .../block-settings-menu/block-mode-toggle.js | 0 .../block-settings-menu-first-item.js | 0 .../block-settings-menu-plugins-extension.js | 0 .../block-unknown-convert-button.js | 0 .../components/block-settings-menu/index.js | 2 +- .../reusable-block-convert-button.js | 0 .../reusable-block-delete-button.js | 0 .../components/block-settings-menu/style.scss | 0 .../reusable-block-delete-button.js.snap | 0 .../test/block-mode-toggle.js | 0 .../test/reusable-block-convert-button.js | 0 .../test/reusable-block-delete-button.js | 0 .../src/components/block-styles/index.js | 0 .../src/components/block-styles/style.scss | 0 .../src/components/block-styles/test/index.js | 0 .../src/components/block-switcher/index.js | 0 .../block-switcher/multi-blocks-switcher.js | 0 .../src/components/block-switcher/style.scss | 0 .../test/__snapshots__/index.js.snap | 0 .../multi-blocks-switcher.js.snap | 0 .../components/block-switcher/test/index.js | 0 .../test/multi-blocks-switcher.js | 0 .../src/components/block-title/README.md | 0 .../src/components/block-title/index.js | 0 .../src/components/block-title/test/index.js | 0 .../src/components/block-toolbar/index.js | 0 .../src/components/block-toolbar/style.scss | 0 .../src/components/block-types-list/index.js | 0 .../components/block-types-list/style.scss | 0 .../src/components/color-palette/control.js | 0 .../src/components/color-palette/control.scss | 0 .../src/components/color-palette/index.js | 0 .../test/__snapshots__/control.js.snap | 0 .../components/color-palette/test/control.js | 0 .../color-palette/with-color-context.js | 0 .../src/components/colors/index.js | 0 .../test/__snapshots__/with-colors.js.snap | 0 .../src/components/colors/test/with-colors.js | 0 .../src/components/colors/utils.js | 0 .../src/components/colors/with-colors.js | 0 .../src/components/contrast-checker/index.js | 0 .../components/contrast-checker/style.scss | 0 .../test/__snapshots__/index.js.snap | 0 .../components/contrast-checker/test/index.js | 0 .../src/components/copy-handler/index.js | 0 .../default-block-appender/index.js | 0 .../default-block-appender/index.native.js | 0 .../default-block-appender/style.native.scss | 0 .../default-block-appender/style.scss | 0 .../test/__snapshots__/index.js.snap | 0 .../default-block-appender/test/index.js | 0 .../components/font-sizes/font-size-picker.js | 0 .../src/components/font-sizes/index.js | 0 .../src/components/font-sizes/index.native.js | 0 .../src/components/font-sizes/style.scss | 0 .../src/components/font-sizes/utils.js | 0 .../components/font-sizes/with-font-sizes.js | 0 .../components/ignore-nested-events/index.js | 0 .../ignore-nested-events/test/index.js | 0 packages/block-editor/src/components/index.js | 56 +++ .../src/components/inner-blocks/README.md | 2 +- .../src/components/inner-blocks/index.js | 0 .../src/components/inner-blocks/style.scss | 0 .../test/__snapshots__/index.js.snap | 0 .../src/components/inner-blocks/test/index.js | 0 .../components/inserter-list-item/index.js | 0 .../components/inserter-list-item/style.scss | 0 .../inserter-with-shortcuts/index.js | 0 .../inserter-with-shortcuts/style.scss | 0 .../src/components/inserter/child-blocks.js | 0 .../src/components/inserter/index.js | 0 .../src/components/inserter/menu.js | 0 .../src/components/inserter/style.scss | 0 .../src/components/inserter/test/menu.js | 0 .../inspector-advanced-controls/index.js | 0 .../components/inspector-controls/README.md | 0 .../components/inspector-controls/index.js | 0 .../components/media-placeholder/README.md | 0 .../src/components/media-placeholder/index.js | 0 .../media-placeholder/index.native.js | 0 .../components/media-placeholder/style.scss | 0 .../media-placeholder/styles.native.scss | 0 .../media-placeholder/test/index.js | 0 .../src/components/media-upload/README.md | 2 +- .../src/components/media-upload/check.js | 0 .../src/components/media-upload/index.js | 0 .../multi-select-scroll-into-view/index.js | 0 .../multi-selection-inspector/index.js | 0 .../multi-selection-inspector/style.scss | 0 .../src/components/navigable-toolbar/index.js | 0 .../src/components/observe-typing/README.md | 0 .../src/components/observe-typing/index.js | 0 .../components/panel-color-settings/index.js | 0 .../panel-color-settings/style.scss | 0 .../test/__snapshots__/index.js.snap | 0 .../panel-color-settings/test/index.js | 0 .../src/components/plain-text/README.md | 0 .../src/components/plain-text/index.js | 0 .../src/components/plain-text/index.native.js | 0 .../components/plain-text/style.native.scss | 0 .../src/components/plain-text/style.scss | 0 .../preserve-scroll-in-reorder/index.js | 0 .../src/components/rich-text/README.md | 0 .../src/components/rich-text/aria.js | 0 .../src/components/rich-text/editable.js | 0 .../src/components/rich-text/format-edit.js | 0 .../rich-text/format-toolbar/index.js | 0 .../rich-text/format-toolbar/index.native.js | 0 .../rich-text/format-toolbar/style.scss | 0 .../src/components/rich-text/index.js | 0 .../src/components/rich-text/index.native.js | 2 +- .../src/components/rich-text/input-event.js | 0 .../rich-text/input-event.native.js | 0 .../src/components/rich-text/list-edit.js | 0 .../src/components/rich-text/patterns.js | 0 .../rich-text/remove-browser-shortcuts.js | 0 .../src/components/rich-text/shortcut.js | 0 .../components/rich-text/shortcut.native.js | 0 .../components/rich-text/style.native.scss | 0 .../src/components/rich-text/style.scss | 0 .../src/components/rich-text/test/index.js | 0 .../components/rich-text/toolbar-button.js | 0 .../skip-to-selected-block/index.js | 0 .../skip-to-selected-block/style.scss | 0 .../src/components/url-input/README.md | 0 .../src/components/url-input/button.js | 0 .../src/components/url-input/index.js | 0 .../src/components/url-input/index.native.js | 0 .../src/components/url-input/style.scss | 0 .../src/components/url-input/test/button.js | 0 .../src/components/url-popover/README.md | 2 +- .../src/components/url-popover/index.js | 0 .../src/components/url-popover/style.scss | 0 .../test/__snapshots__/index.js.snap | 0 .../src/components/url-popover/test/index.js | 0 .../src/components/warning/index.js | 0 .../src/components/warning/style.scss | 0 .../warning/test/__snapshots__/index.js.snap | 0 .../src/components/warning/test/index.js | 0 .../src/components/writing-flow/index.js | 0 .../src/components/writing-flow/style.scss | 0 .../src/components/writing-flow/test/index.js | 0 .../src/hooks/align.js | 0 .../src/hooks/anchor.js | 0 .../src/hooks/custom-class-name.js | 0 .../src/hooks/custom-class-name.native.js | 0 .../src/hooks/generated-class-name.js | 0 packages/block-editor/src/hooks/index.js | 7 + .../block-editor/src/hooks/index.native.js | 5 + .../src/hooks/test/align.js | 0 .../src/hooks/test/anchor.js | 0 .../src/hooks/test/custom-class-name.js | 0 .../src/hooks/test/generated-class-name.js | 0 packages/block-editor/src/index.js | 4 + packages/block-editor/src/style.scss | 33 ++ .../{editor => block-editor}/src/utils/dom.js | 0 .../src/utils/test/dom.js | 0 packages/block-library/src/archives/edit.js | 8 +- packages/block-library/src/audio/edit.js | 4 +- packages/block-library/src/audio/index.js | 2 +- packages/block-library/src/block/edit.js | 2 +- packages/block-library/src/button/edit.js | 2 +- packages/block-library/src/button/index.js | 2 +- packages/block-library/src/categories/edit.js | 2 +- packages/block-library/src/code/edit.js | 2 +- .../block-library/src/code/edit.native.js | 2 +- packages/block-library/src/columns/column.js | 2 +- packages/block-library/src/columns/index.js | 2 +- packages/block-library/src/cover/index.js | 2 +- .../block-library/src/embed/embed-controls.js | 2 +- .../src/embed/embed-placeholder.js | 2 +- .../block-library/src/embed/embed-preview.js | 2 +- packages/block-library/src/embed/settings.js | 2 +- packages/block-library/src/file/edit.js | 4 +- packages/block-library/src/file/index.js | 2 +- packages/block-library/src/file/inspector.js | 2 +- packages/block-library/src/gallery/edit.js | 4 +- .../src/gallery/gallery-image.js | 2 +- packages/block-library/src/gallery/index.js | 3 +- packages/block-library/src/heading/edit.js | 7 +- .../block-library/src/heading/edit.native.js | 2 +- packages/block-library/src/heading/index.js | 2 +- packages/block-library/src/html/edit.js | 3 +- packages/block-library/src/image/edit.js | 4 +- .../block-library/src/image/edit.native.js | 2 + packages/block-library/src/image/index.js | 2 +- .../block-library/src/latest-comments/edit.js | 4 +- .../block-library/src/latest-posts/edit.js | 2 +- packages/block-library/src/list/index.js | 2 +- packages/block-library/src/media-text/edit.js | 2 +- .../block-library/src/media-text/index.js | 2 +- .../src/media-text/media-container.js | 2 +- packages/block-library/src/missing/index.js | 2 +- packages/block-library/src/more/edit.js | 2 +- .../block-library/src/more/edit.native.js | 2 +- packages/block-library/src/paragraph/edit.js | 2 +- .../src/paragraph/edit.native.js | 2 +- packages/block-library/src/paragraph/index.js | 2 +- .../block-library/src/preformatted/index.js | 2 +- packages/block-library/src/pullquote/edit.js | 2 +- packages/block-library/src/pullquote/index.js | 2 +- packages/block-library/src/quote/index.js | 2 +- packages/block-library/src/rss/edit.js | 2 +- packages/block-library/src/search/edit.js | 2 +- packages/block-library/src/shortcode/index.js | 2 +- packages/block-library/src/spacer/index.js | 2 +- packages/block-library/src/subhead/index.js | 2 +- packages/block-library/src/table/edit.js | 2 +- packages/block-library/src/table/index.js | 2 +- packages/block-library/src/tag-cloud/edit.js | 2 +- packages/block-library/src/template/index.js | 2 +- .../block-library/src/test/helpers/index.js | 3 +- .../block-library/src/text-columns/index.js | 2 +- packages/block-library/src/verse/index.js | 2 +- packages/block-library/src/video/edit.js | 4 +- packages/block-library/src/video/index.js | 2 +- .../__snapshots__/block-deletion.test.js.snap | 4 +- .../e2e-tests/specs/block-deletion.test.js | 2 +- .../components/header/header-toolbar/index.js | 6 +- .../edit-post/src/components/layout/index.js | 2 +- .../src/components/options-modal/index.js | 8 +- .../sidebar/settings-sidebar/index.js | 2 +- .../src/components/visual-editor/index.js | 10 +- .../src/hooks/validate-multiple-use/index.js | 2 +- packages/editor/package.json | 6 - .../src/components/autocompleters/block.js | 6 +- packages/editor/src/components/deprecated.js | 119 +++++ .../src/components/document-outline/item.js | 6 +- .../components/document-outline/test/index.js | 4 +- .../src/components/error-boundary/index.js | 6 +- .../visual-editor-shortcuts.js | 2 +- packages/editor/src/components/index.js | 56 +-- .../components/post-featured-image/index.js | 3 +- .../src/components/post-title/index.native.js | 2 +- packages/editor/src/hooks/index.js | 4 - packages/editor/src/hooks/index.native.js | 5 - packages/editor/src/store/test/selectors.js | 3 + packages/editor/src/style.scss | 33 -- packages/format-library/package.json | 1 + packages/format-library/src/bold/index.js | 2 +- packages/format-library/src/code/index.js | 2 +- packages/format-library/src/image/index.js | 2 +- packages/format-library/src/italic/index.js | 2 +- packages/format-library/src/link/index.js | 2 +- .../format-library/src/link/index.native.js | 2 +- packages/format-library/src/link/inline.js | 2 +- .../format-library/src/strikethrough/index.js | 2 +- .../format-library/src/underline/index.js | 2 +- 321 files changed, 855 insertions(+), 234 deletions(-) rename packages/{editor => block-editor}/src/components/alignment-toolbar/index.js (100%) rename packages/{editor => block-editor}/src/components/alignment-toolbar/test/__snapshots__/index.js.snap (100%) rename packages/{editor => block-editor}/src/components/alignment-toolbar/test/index.js (100%) rename packages/{editor => block-editor}/src/components/autocomplete/README.md (100%) rename packages/{editor => block-editor}/src/components/autocomplete/index.js (100%) rename packages/{editor => block-editor}/src/components/autocomplete/test/index.js (100%) rename packages/{editor => block-editor}/src/components/block-actions/index.js (100%) rename packages/{editor => block-editor}/src/components/block-alignment-toolbar/index.js (100%) rename packages/{editor => block-editor}/src/components/block-alignment-toolbar/test/__snapshots__/index.js.snap (100%) rename packages/{editor => block-editor}/src/components/block-alignment-toolbar/test/index.js (100%) rename packages/{editor => block-editor}/src/components/block-compare/README.md (100%) rename packages/{editor => block-editor}/src/components/block-compare/block-view.js (100%) rename packages/{editor => block-editor}/src/components/block-compare/index.js (100%) rename packages/{editor => block-editor}/src/components/block-compare/style.scss (100%) rename packages/{editor => block-editor}/src/components/block-compare/test/__snapshots__/block-view.js.snap (100%) rename packages/{editor => block-editor}/src/components/block-compare/test/block-view.js (100%) rename packages/{editor => block-editor}/src/components/block-controls/index.js (100%) rename packages/{editor => block-editor}/src/components/block-controls/test/__snapshots__/index.js.snap (100%) rename packages/{editor => block-editor}/src/components/block-controls/test/index.js (100%) rename packages/{editor => block-editor}/src/components/block-draggable/index.js (100%) rename packages/{editor => block-editor}/src/components/block-drop-zone/README.md (100%) rename packages/{editor => block-editor}/src/components/block-drop-zone/index.js (100%) rename packages/{editor => block-editor}/src/components/block-drop-zone/style.scss (100%) rename packages/{editor => block-editor}/src/components/block-edit/context.js (100%) rename packages/{editor => block-editor}/src/components/block-edit/edit.js (100%) rename packages/{editor => block-editor}/src/components/block-edit/edit.native.js (100%) rename packages/{editor => block-editor}/src/components/block-edit/index.js (100%) rename packages/{editor => block-editor}/src/components/block-edit/test/edit.js (100%) rename packages/{editor/src/components/global-keyboard-shortcuts/block-editor-shortcuts.js => block-editor/src/components/block-editor-keyboard-shortcuts/index.js} (100%) rename packages/{editor => block-editor}/src/components/block-format-controls/index.js (100%) rename packages/{editor => block-editor}/src/components/block-icon/index.js (100%) rename packages/{editor => block-editor}/src/components/block-icon/style.scss (100%) rename packages/{editor => block-editor}/src/components/block-icon/test/index.js (100%) rename packages/{editor => block-editor}/src/components/block-inspector/index.js (100%) rename packages/{editor => block-editor}/src/components/block-inspector/style.scss (100%) rename packages/{editor => block-editor}/src/components/block-list-appender/index.js (100%) rename packages/{editor => block-editor}/src/components/block-list-appender/style.scss (100%) rename packages/{editor => block-editor}/src/components/block-list/block-contextual-toolbar.js (100%) rename packages/{editor => block-editor}/src/components/block-list/block-crash-boundary.js (100%) rename packages/{editor => block-editor}/src/components/block-list/block-crash-warning.js (100%) rename packages/{editor => block-editor}/src/components/block-list/block-html.js (100%) rename packages/{editor => block-editor}/src/components/block-list/block-invalid-warning.js (100%) rename packages/{editor => block-editor}/src/components/block-list/block-mobile-toolbar.js (100%) rename packages/{editor => block-editor}/src/components/block-list/block.js (100%) rename packages/{editor => block-editor}/src/components/block-list/breadcrumb.js (100%) rename packages/{editor => block-editor}/src/components/block-list/hover-area.js (100%) rename packages/{editor => block-editor}/src/components/block-list/index.js (100%) rename packages/{editor => block-editor}/src/components/block-list/insertion-point.js (100%) rename packages/{editor => block-editor}/src/components/block-list/multi-controls.js (100%) rename packages/{editor => block-editor}/src/components/block-list/style.scss (100%) rename packages/{editor => block-editor}/src/components/block-list/test/block-html.js (100%) rename packages/{editor => block-editor}/src/components/block-mover/drag-handle.js (100%) rename packages/{editor => block-editor}/src/components/block-mover/icons.js (100%) rename packages/{editor => block-editor}/src/components/block-mover/index.js (100%) rename packages/{editor => block-editor}/src/components/block-mover/mover-description.js (100%) rename packages/{editor => block-editor}/src/components/block-mover/style.scss (100%) rename packages/{editor => block-editor}/src/components/block-mover/test/index.js (100%) rename packages/{editor => block-editor}/src/components/block-mover/test/mover-description.js (100%) rename packages/{editor => block-editor}/src/components/block-navigation/dropdown.js (100%) rename packages/{editor => block-editor}/src/components/block-navigation/index.js (100%) rename packages/{editor => block-editor}/src/components/block-navigation/style.scss (100%) rename packages/{editor => block-editor}/src/components/block-preview/index.js (100%) rename packages/{editor => block-editor}/src/components/block-preview/style.scss (100%) rename packages/{editor => block-editor}/src/components/block-selection-clearer/index.js (100%) rename packages/{editor => block-editor}/src/components/block-settings-menu/block-convert-button.js (100%) rename packages/{editor => block-editor}/src/components/block-settings-menu/block-html-convert-button.js (100%) rename packages/{editor => block-editor}/src/components/block-settings-menu/block-mode-toggle.js (100%) rename packages/{editor => block-editor}/src/components/block-settings-menu/block-settings-menu-first-item.js (100%) rename packages/{editor => block-editor}/src/components/block-settings-menu/block-settings-menu-plugins-extension.js (100%) rename packages/{editor => block-editor}/src/components/block-settings-menu/block-unknown-convert-button.js (100%) rename packages/{editor => block-editor}/src/components/block-settings-menu/index.js (98%) rename packages/{editor => block-editor}/src/components/block-settings-menu/reusable-block-convert-button.js (100%) rename packages/{editor => block-editor}/src/components/block-settings-menu/reusable-block-delete-button.js (100%) rename packages/{editor => block-editor}/src/components/block-settings-menu/style.scss (100%) rename packages/{editor => block-editor}/src/components/block-settings-menu/test/__snapshots__/reusable-block-delete-button.js.snap (100%) rename packages/{editor => block-editor}/src/components/block-settings-menu/test/block-mode-toggle.js (100%) rename packages/{editor => block-editor}/src/components/block-settings-menu/test/reusable-block-convert-button.js (100%) rename packages/{editor => block-editor}/src/components/block-settings-menu/test/reusable-block-delete-button.js (100%) rename packages/{editor => block-editor}/src/components/block-styles/index.js (100%) rename packages/{editor => block-editor}/src/components/block-styles/style.scss (100%) rename packages/{editor => block-editor}/src/components/block-styles/test/index.js (100%) rename packages/{editor => block-editor}/src/components/block-switcher/index.js (100%) rename packages/{editor => block-editor}/src/components/block-switcher/multi-blocks-switcher.js (100%) rename packages/{editor => block-editor}/src/components/block-switcher/style.scss (100%) rename packages/{editor => block-editor}/src/components/block-switcher/test/__snapshots__/index.js.snap (100%) rename packages/{editor => block-editor}/src/components/block-switcher/test/__snapshots__/multi-blocks-switcher.js.snap (100%) rename packages/{editor => block-editor}/src/components/block-switcher/test/index.js (100%) rename packages/{editor => block-editor}/src/components/block-switcher/test/multi-blocks-switcher.js (100%) rename packages/{editor => block-editor}/src/components/block-title/README.md (100%) rename packages/{editor => block-editor}/src/components/block-title/index.js (100%) rename packages/{editor => block-editor}/src/components/block-title/test/index.js (100%) rename packages/{editor => block-editor}/src/components/block-toolbar/index.js (100%) rename packages/{editor => block-editor}/src/components/block-toolbar/style.scss (100%) rename packages/{editor => block-editor}/src/components/block-types-list/index.js (100%) rename packages/{editor => block-editor}/src/components/block-types-list/style.scss (100%) rename packages/{editor => block-editor}/src/components/color-palette/control.js (100%) rename packages/{editor => block-editor}/src/components/color-palette/control.scss (100%) rename packages/{editor => block-editor}/src/components/color-palette/index.js (100%) rename packages/{editor => block-editor}/src/components/color-palette/test/__snapshots__/control.js.snap (100%) rename packages/{editor => block-editor}/src/components/color-palette/test/control.js (100%) rename packages/{editor => block-editor}/src/components/color-palette/with-color-context.js (100%) rename packages/{editor => block-editor}/src/components/colors/index.js (100%) rename packages/{editor => block-editor}/src/components/colors/test/__snapshots__/with-colors.js.snap (100%) rename packages/{editor => block-editor}/src/components/colors/test/with-colors.js (100%) rename packages/{editor => block-editor}/src/components/colors/utils.js (100%) rename packages/{editor => block-editor}/src/components/colors/with-colors.js (100%) rename packages/{editor => block-editor}/src/components/contrast-checker/index.js (100%) rename packages/{editor => block-editor}/src/components/contrast-checker/style.scss (100%) rename packages/{editor => block-editor}/src/components/contrast-checker/test/__snapshots__/index.js.snap (100%) rename packages/{editor => block-editor}/src/components/contrast-checker/test/index.js (100%) rename packages/{editor => block-editor}/src/components/copy-handler/index.js (100%) rename packages/{editor => block-editor}/src/components/default-block-appender/index.js (100%) rename packages/{editor => block-editor}/src/components/default-block-appender/index.native.js (100%) rename packages/{editor => block-editor}/src/components/default-block-appender/style.native.scss (100%) rename packages/{editor => block-editor}/src/components/default-block-appender/style.scss (100%) rename packages/{editor => block-editor}/src/components/default-block-appender/test/__snapshots__/index.js.snap (100%) rename packages/{editor => block-editor}/src/components/default-block-appender/test/index.js (100%) rename packages/{editor => block-editor}/src/components/font-sizes/font-size-picker.js (100%) rename packages/{editor => block-editor}/src/components/font-sizes/index.js (100%) rename packages/{editor => block-editor}/src/components/font-sizes/index.native.js (100%) rename packages/{editor => block-editor}/src/components/font-sizes/style.scss (100%) rename packages/{editor => block-editor}/src/components/font-sizes/utils.js (100%) rename packages/{editor => block-editor}/src/components/font-sizes/with-font-sizes.js (100%) rename packages/{editor => block-editor}/src/components/ignore-nested-events/index.js (100%) rename packages/{editor => block-editor}/src/components/ignore-nested-events/test/index.js (100%) rename packages/{editor => block-editor}/src/components/inner-blocks/README.md (98%) rename packages/{editor => block-editor}/src/components/inner-blocks/index.js (100%) rename packages/{editor => block-editor}/src/components/inner-blocks/style.scss (100%) rename packages/{editor => block-editor}/src/components/inner-blocks/test/__snapshots__/index.js.snap (100%) rename packages/{editor => block-editor}/src/components/inner-blocks/test/index.js (100%) rename packages/{editor => block-editor}/src/components/inserter-list-item/index.js (100%) rename packages/{editor => block-editor}/src/components/inserter-list-item/style.scss (100%) rename packages/{editor => block-editor}/src/components/inserter-with-shortcuts/index.js (100%) rename packages/{editor => block-editor}/src/components/inserter-with-shortcuts/style.scss (100%) rename packages/{editor => block-editor}/src/components/inserter/child-blocks.js (100%) rename packages/{editor => block-editor}/src/components/inserter/index.js (100%) rename packages/{editor => block-editor}/src/components/inserter/menu.js (100%) rename packages/{editor => block-editor}/src/components/inserter/style.scss (100%) rename packages/{editor => block-editor}/src/components/inserter/test/menu.js (100%) rename packages/{editor => block-editor}/src/components/inspector-advanced-controls/index.js (100%) rename packages/{editor => block-editor}/src/components/inspector-controls/README.md (100%) rename packages/{editor => block-editor}/src/components/inspector-controls/index.js (100%) rename packages/{editor => block-editor}/src/components/media-placeholder/README.md (100%) rename packages/{editor => block-editor}/src/components/media-placeholder/index.js (100%) rename packages/{editor => block-editor}/src/components/media-placeholder/index.native.js (100%) rename packages/{editor => block-editor}/src/components/media-placeholder/style.scss (100%) rename packages/{editor => block-editor}/src/components/media-placeholder/styles.native.scss (100%) rename packages/{editor => block-editor}/src/components/media-placeholder/test/index.js (100%) rename packages/{editor => block-editor}/src/components/media-upload/README.md (97%) rename packages/{editor => block-editor}/src/components/media-upload/check.js (100%) rename packages/{editor => block-editor}/src/components/media-upload/index.js (100%) rename packages/{editor => block-editor}/src/components/multi-select-scroll-into-view/index.js (100%) rename packages/{editor => block-editor}/src/components/multi-selection-inspector/index.js (100%) rename packages/{editor => block-editor}/src/components/multi-selection-inspector/style.scss (100%) rename packages/{editor => block-editor}/src/components/navigable-toolbar/index.js (100%) rename packages/{editor => block-editor}/src/components/observe-typing/README.md (100%) rename packages/{editor => block-editor}/src/components/observe-typing/index.js (100%) rename packages/{editor => block-editor}/src/components/panel-color-settings/index.js (100%) rename packages/{editor => block-editor}/src/components/panel-color-settings/style.scss (100%) rename packages/{editor => block-editor}/src/components/panel-color-settings/test/__snapshots__/index.js.snap (100%) rename packages/{editor => block-editor}/src/components/panel-color-settings/test/index.js (100%) rename packages/{editor => block-editor}/src/components/plain-text/README.md (100%) rename packages/{editor => block-editor}/src/components/plain-text/index.js (100%) rename packages/{editor => block-editor}/src/components/plain-text/index.native.js (100%) rename packages/{editor => block-editor}/src/components/plain-text/style.native.scss (100%) rename packages/{editor => block-editor}/src/components/plain-text/style.scss (100%) rename packages/{editor => block-editor}/src/components/preserve-scroll-in-reorder/index.js (100%) rename packages/{editor => block-editor}/src/components/rich-text/README.md (100%) rename packages/{editor => block-editor}/src/components/rich-text/aria.js (100%) rename packages/{editor => block-editor}/src/components/rich-text/editable.js (100%) rename packages/{editor => block-editor}/src/components/rich-text/format-edit.js (100%) rename packages/{editor => block-editor}/src/components/rich-text/format-toolbar/index.js (100%) rename packages/{editor => block-editor}/src/components/rich-text/format-toolbar/index.native.js (100%) rename packages/{editor => block-editor}/src/components/rich-text/format-toolbar/style.scss (100%) rename packages/{editor => block-editor}/src/components/rich-text/index.js (100%) rename packages/{editor => block-editor}/src/components/rich-text/index.native.js (99%) rename packages/{editor => block-editor}/src/components/rich-text/input-event.js (100%) rename packages/{editor => block-editor}/src/components/rich-text/input-event.native.js (100%) rename packages/{editor => block-editor}/src/components/rich-text/list-edit.js (100%) rename packages/{editor => block-editor}/src/components/rich-text/patterns.js (100%) rename packages/{editor => block-editor}/src/components/rich-text/remove-browser-shortcuts.js (100%) rename packages/{editor => block-editor}/src/components/rich-text/shortcut.js (100%) rename packages/{editor => block-editor}/src/components/rich-text/shortcut.native.js (100%) rename packages/{editor => block-editor}/src/components/rich-text/style.native.scss (100%) rename packages/{editor => block-editor}/src/components/rich-text/style.scss (100%) rename packages/{editor => block-editor}/src/components/rich-text/test/index.js (100%) rename packages/{editor => block-editor}/src/components/rich-text/toolbar-button.js (100%) rename packages/{editor => block-editor}/src/components/skip-to-selected-block/index.js (100%) rename packages/{editor => block-editor}/src/components/skip-to-selected-block/style.scss (100%) rename packages/{editor => block-editor}/src/components/url-input/README.md (100%) rename packages/{editor => block-editor}/src/components/url-input/button.js (100%) rename packages/{editor => block-editor}/src/components/url-input/index.js (100%) rename packages/{editor => block-editor}/src/components/url-input/index.native.js (100%) rename packages/{editor => block-editor}/src/components/url-input/style.scss (100%) rename packages/{editor => block-editor}/src/components/url-input/test/button.js (100%) rename packages/{editor => block-editor}/src/components/url-popover/README.md (98%) rename packages/{editor => block-editor}/src/components/url-popover/index.js (100%) rename packages/{editor => block-editor}/src/components/url-popover/style.scss (100%) rename packages/{editor => block-editor}/src/components/url-popover/test/__snapshots__/index.js.snap (100%) rename packages/{editor => block-editor}/src/components/url-popover/test/index.js (100%) rename packages/{editor => block-editor}/src/components/warning/index.js (100%) rename packages/{editor => block-editor}/src/components/warning/style.scss (100%) rename packages/{editor => block-editor}/src/components/warning/test/__snapshots__/index.js.snap (100%) rename packages/{editor => block-editor}/src/components/warning/test/index.js (100%) rename packages/{editor => block-editor}/src/components/writing-flow/index.js (100%) rename packages/{editor => block-editor}/src/components/writing-flow/style.scss (100%) rename packages/{editor => block-editor}/src/components/writing-flow/test/index.js (100%) rename packages/{editor => block-editor}/src/hooks/align.js (100%) rename packages/{editor => block-editor}/src/hooks/anchor.js (100%) rename packages/{editor => block-editor}/src/hooks/custom-class-name.js (100%) rename packages/{editor => block-editor}/src/hooks/custom-class-name.native.js (100%) rename packages/{editor => block-editor}/src/hooks/generated-class-name.js (100%) create mode 100644 packages/block-editor/src/hooks/index.js create mode 100644 packages/block-editor/src/hooks/index.native.js rename packages/{editor => block-editor}/src/hooks/test/align.js (100%) rename packages/{editor => block-editor}/src/hooks/test/anchor.js (100%) rename packages/{editor => block-editor}/src/hooks/test/custom-class-name.js (100%) rename packages/{editor => block-editor}/src/hooks/test/generated-class-name.js (100%) create mode 100644 packages/block-editor/src/style.scss rename packages/{editor => block-editor}/src/utils/dom.js (100%) rename packages/{editor => block-editor}/src/utils/test/dom.js (100%) create mode 100644 packages/editor/src/components/deprecated.js diff --git a/docs/designers-developers/developers/filters/autocomplete-filters.md b/docs/designers-developers/developers/filters/autocomplete-filters.md index 4086139c60a19..1707080e867e8 100644 --- a/docs/designers-developers/developers/filters/autocomplete-filters.md +++ b/docs/designers-developers/developers/filters/autocomplete-filters.md @@ -2,7 +2,7 @@ The `editor.Autocomplete.completers` filter is for extending and overriding the list of autocompleters used by blocks. -The `Autocomplete` component found in `@wordpress/editor` applies this filter. The `@wordpress/components` package provides the foundational `Autocomplete` component that does not apply such a filter, but blocks should generally use the component provided by `@wordpress/editor`. +The `Autocomplete` component found in `@wordpress/block-editor` applies this filter. The `@wordpress/components` package provides the foundational `Autocomplete` component that does not apply such a filter, but blocks should generally use the component provided by `@wordpress/block-editor`. ### Example diff --git a/docs/designers-developers/developers/packages.md b/docs/designers-developers/developers/packages.md index e2d5a97a48211..b5ef06c18782a 100644 --- a/docs/designers-developers/developers/packages.md +++ b/docs/designers-developers/developers/packages.md @@ -26,15 +26,15 @@ const { PlainText } = wp.editor; All the packages are also available on [npm](https://www.npmjs.com/org/wordpress) if you want to bundle them in your code. -Using the same `PlainText` example, you would install the editor module with npm: +Using the same `PlainText` example, you would install the block editor module with npm: ```bash -npm install @wordpress/editor --save +npm install @wordpress/block-editor --save ``` Once installed, you can access the component in your code using: ```js -import { PlainText } from '@wordpress/editor'; +import { PlainText } from '@wordpress/block-editor'; ``` diff --git a/lib/client-assets.php b/lib/client-assets.php index f721c3855bc0d..7839df9dbb3ea 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -264,10 +264,18 @@ function gutenberg_register_scripts_and_styles() { // This empty stylesheet is defined to ensure backward compatibility. gutenberg_override_style( 'wp-blocks', false ); + gutenberg_override_style( + 'wp-block-editor', + gutenberg_url( 'build/block-editor/style.css' ), + array( 'wp-components', 'wp-editor-font' ), + filemtime( gutenberg_dir_path() . 'build/editor/style.css' ) + ); + wp_style_add_data( 'wp-block-editor', 'rtl', 'replace' ); + gutenberg_override_style( 'wp-editor', gutenberg_url( 'build/editor/style.css' ), - array( 'wp-components', 'wp-editor-font', 'wp-nux' ), + array( 'wp-components', 'wp-block-editor', 'wp-nux' ), filemtime( gutenberg_dir_path() . 'build/editor/style.css' ) ); wp_style_add_data( 'wp-editor', 'rtl', 'replace' ); @@ -275,7 +283,7 @@ function gutenberg_register_scripts_and_styles() { gutenberg_override_style( 'wp-edit-post', gutenberg_url( 'build/edit-post/style.css' ), - array( 'wp-components', 'wp-editor', 'wp-edit-blocks', 'wp-block-library', 'wp-nux' ), + array( 'wp-components', 'wp-block-editor', 'wp-editor', 'wp-edit-blocks', 'wp-block-library', 'wp-nux' ), filemtime( gutenberg_dir_path() . 'build/edit-post/style.css' ) ); wp_style_add_data( 'wp-edit-post', 'rtl', 'replace' ); @@ -299,7 +307,7 @@ function gutenberg_register_scripts_and_styles() { gutenberg_override_style( 'wp-format-library', gutenberg_url( 'build/format-library/style.css' ), - array(), + array( 'wp-block-editor', 'wp-components' ), filemtime( gutenberg_dir_path() . 'build/format-library/style.css' ) ); wp_style_add_data( 'wp-format-library', 'rtl', 'replace' ); diff --git a/lib/packages-dependencies.php b/lib/packages-dependencies.php index 47c7d15422940..3a59c5de5f282 100644 --- a/lib/packages-dependencies.php +++ b/lib/packages-dependencies.php @@ -47,12 +47,25 @@ 'wp-block-serialization-spec-parser' => array(), 'wp-block-editor' => array( 'lodash', + 'wp-a11y', + 'wp-blob', 'wp-blocks', 'wp-compose', 'wp-components', + 'wp-core-data', 'wp-data', + 'wp-dom', 'wp-element', + 'wp-hooks', + 'wp-html-entities', 'wp-i18n', + 'wp-is-shallow-equal', + 'wp-keycodes', + 'wp-rich-text', + 'wp-token-list', + 'wp-url', + 'wp-viewport', + 'wp-wordcount', ), 'wp-blocks' => array( 'lodash', @@ -180,6 +193,7 @@ ), 'wp-escape-html' => array(), 'wp-format-library' => array( + 'wp-block-editor', 'wp-components', 'wp-editor', 'wp-element', diff --git a/package-lock.json b/package-lock.json index 82560b53523e7..9d1ac8494ae02 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2532,15 +2532,32 @@ "version": "file:packages/block-editor", "requires": { "@babel/runtime": "^7.0.0", + "@wordpress/a11y": "file:packages/a11y", + "@wordpress/blob": "file:packages/blob", "@wordpress/blocks": "file:packages/blocks", "@wordpress/components": "file:packages/components", "@wordpress/compose": "file:packages/compose", + "@wordpress/core-data": "file:packages/core-data", "@wordpress/data": "file:packages/data", + "@wordpress/dom": "file:packages/dom", "@wordpress/element": "file:packages/element", + "@wordpress/hooks": "file:packages/hooks", + "@wordpress/html-entities": "file:packages/html-entities", "@wordpress/i18n": "file:packages/i18n", + "@wordpress/is-shallow-equal": "file:packages/is-shallow-equal", + "@wordpress/keycodes": "file:packages/keycodes", + "@wordpress/rich-text": "file:packages/rich-text", + "@wordpress/token-list": "file:packages/token-list", + "@wordpress/url": "file:packages/url", + "@wordpress/viewport": "file:packages/viewport", + "@wordpress/wordcount": "file:packages/wordcount", + "classnames": "^2.2.5", + "dom-scroll-into-view": "^1.2.1", "lodash": "^4.17.10", + "redux-multi": "^0.1.12", "refx": "^3.0.0", - "rememo": "^3.0.0" + "rememo": "^3.0.0", + "tinycolor2": "^1.4.1" } }, "@wordpress/block-library": { @@ -2786,7 +2803,6 @@ "version": "file:packages/editor", "requires": { "@babel/runtime": "^7.3.1", - "@wordpress/a11y": "file:packages/a11y", "@wordpress/api-fetch": "file:packages/api-fetch", "@wordpress/blob": "file:packages/blob", "@wordpress/block-editor": "file:packages/block-editor", @@ -2797,21 +2813,17 @@ "@wordpress/data": "file:packages/data", "@wordpress/date": "file:packages/date", "@wordpress/deprecated": "file:packages/deprecated", - "@wordpress/dom": "file:packages/dom", "@wordpress/element": "file:packages/element", "@wordpress/hooks": "file:packages/hooks", "@wordpress/html-entities": "file:packages/html-entities", "@wordpress/i18n": "file:packages/i18n", - "@wordpress/is-shallow-equal": "file:packages/is-shallow-equal", "@wordpress/keycodes": "file:packages/keycodes", "@wordpress/notices": "file:packages/notices", "@wordpress/nux": "file:packages/nux", - "@wordpress/token-list": "file:packages/token-list", "@wordpress/url": "file:packages/url", "@wordpress/viewport": "file:packages/viewport", "@wordpress/wordcount": "file:packages/wordcount", "classnames": "^2.2.5", - "dom-scroll-into-view": "^1.2.1", "inherits": "^2.0.3", "lodash": "^4.17.11", "memize": "^1.0.5", @@ -2820,7 +2832,6 @@ "redux-optimist": "^1.0.0", "refx": "^3.0.0", "rememo": "^3.0.0", - "tinycolor2": "^1.4.1", "traverse": "^0.6.6" } }, @@ -2854,6 +2865,7 @@ "version": "file:packages/format-library", "requires": { "@babel/runtime": "^7.3.1", + "@wordpress/block-editor": "file:packages/block-editor", "@wordpress/components": "file:packages/components", "@wordpress/editor": "file:packages/editor", "@wordpress/element": "file:packages/element", diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index 3172cb260f2a8..7686f0fba4f18 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -16,15 +16,350 @@ _This package assumes that your code will run in an **ES2015+** environment. If <!-- START TOKEN(Autogenerated API docs) --> +### AlignmentToolbar + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### Autocomplete + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### BlockAlignmentToolbar + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### BlockControls + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### BlockEdit + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### BlockEditorKeyboardShortcuts + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + ### BlockEditorProvider -[src/index.js#L11-L11](src/index.js#L11-L11) +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### BlockFormatControls + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### BlockIcon + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### BlockInspector + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### BlockList + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### BlockMover + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### BlockNavigationDropdown + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### BlockSelectionClearer + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### BlockSettingsMenu + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### BlockTitle + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### BlockToolbar + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### ColorPalette + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### ContrastChecker + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### CopyHandler + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### createCustomColorsHOC + +[src/index.js#L15-L15](src/index.js#L15-L15) + +A higher-order component factory for creating a 'withCustomColors' HOC, which handles color logic +for class generation color value, retrieval and color attribute setting. + +Use this higher-order component to work with a custom set of colors. + +**Usage** + +```jsx +const CUSTOM_COLORS = [ { name: 'Red', slug: 'red', color: '#ff0000' }, { name: 'Blue', slug: 'blue', color: '#0000ff' } ]; +const withCustomColors = createCustomColorsHOC( CUSTOM_COLORS ); +// ... +export default compose( + withCustomColors( 'backgroundColor', 'borderColor' ), + MyColorfulComponent, +); +``` + +**Parameters** + +- **colorsArray** `Array`: The array of color objects (name, slug, color, etc... ). + +**Returns** + +`Function`: Higher-order component. + +### DefaultBlockAppender + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### FontSizePicker + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### getColorClassName + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Returns a class based on the context a color is being used and its slug. + +**Parameters** + +- **colorContextName** `string`: Context/place where color is being used e.g: background, text etc... +- **colorSlug** `string`: Slug of the color. + +**Returns** + +`string`: String with the class corresponding to the color in the provided context. + +### getColorObjectByAttributeValues + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Provided an array of color objects as set by the theme or by the editor defaults, +and the values of the defined color or custom color returns a color object describing the color. + +**Parameters** + +- **colors** `Array`: Array of color objects as set by the theme or by the editor defaults. +- **definedColor** `?string`: A string containing the color slug. +- **customColor** `?string`: A string containing the customColor value. + +**Returns** + +`?string`: If definedColor is passed and the name is found in colors, the color object exactly as set by the theme or editor defaults is returned. Otherwise, an object that just sets the color is defined. + +### getColorObjectByColorValue + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Provided an array of color objects as set by the theme or by the editor defaults, and a color value returns the color object matching that value or undefined. + +**Parameters** + +- **colors** `Array`: Array of color objects as set by the theme or by the editor defaults. +- **colorValue** `?string`: A string containing the color value. + +**Returns** + +`?string`: Returns the color object included in the colors array whose color property equals colorValue. Returns undefined if no color object matches this requirement. + +### getFontSize + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Returns the font size object based on an array of named font sizes and the namedFontSize and customFontSize values. + If namedFontSize is undefined or not found in fontSizes an object with just the size value based on customFontSize is returned. + +**Parameters** + +- **fontSizes** `Array`: Array of font size objects containing at least the "name" and "size" values as properties. +- **fontSizeAttribute** `?string`: Content of the font size attribute (slug). +- **customFontSizeAttribute** `?number`: Contents of the custom font size attribute (value). + +**Returns** + +`?string`: If fontSizeAttribute is set and an equal slug is found in fontSizes it returns the font size object for that slug. Otherwise, an object with just the size value based on customFontSize is returned. + +### getFontSizeClass + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Returns a class based on fontSizeName. + +**Parameters** + +- **fontSizeSlug** `string`: Slug of the fontSize. + +**Returns** + +`string`: String with the class corresponding to the fontSize passed. The class is generated by appending 'has-' followed by fontSizeSlug in kebabCase and ending with '-font-size'. + +### InnerBlocks + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### Inserter + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### InspectorAdvancedControls + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### InspectorControls + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### MediaPlaceholder + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### MediaUpload + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### MediaUploadCheck + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### MultiBlocksSwitcher + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### MultiSelectScrollIntoView + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### NavigableToolbar + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### ObserveTyping + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### PanelColorSettings + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### PlainText + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### PreserveScrollInReorder + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### RichText + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### RichTextShortcut + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### RichTextToolbarButton + +[src/index.js#L15-L15](src/index.js#L15-L15) Undocumented declaration. ### SETTINGS_DEFAULTS -[src/index.js#L13-L13](src/index.js#L13-L13) +[src/index.js#L17-L17](src/index.js#L17-L17) The default editor settings @@ -43,6 +378,106 @@ The default editor settings bodyPlaceholder string Empty post placeholder titlePlaceholder string Empty title placeholder +### SkipToSelectedBlock + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### UnstableRichTextInputEvent + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### URLInput + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### URLInputButton + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### URLPopover + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### Warning + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### withColorContext + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### withColors + +[src/index.js#L15-L15](src/index.js#L15-L15) + +A higher-order component, which handles color logic for class generation color value, retrieval and color attribute setting. + +For use with the default editor/theme color palette. + +**Usage** + +```jsx +export default compose( + withColors( 'backgroundColor', { textColor: 'color' } ), + MyColorfulComponent, +); +``` + +**Parameters** + +- **colorTypes** `...(object|string)`: The arguments can be strings or objects. If the argument is an object, it should contain the color attribute name as key and the color context as value. If the argument is a string the value should be the color attribute name, the color context is computed by applying a kebab case transform to the value. Color context represents the context/place where the color is going to be used. The class name of the color is generated using 'has' followed by the color name and ending with the color context all in kebab case e.g: has-green-background-color. + +**Returns** + +`Function`: Higher-order component. + +### withFontSizes + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Higher-order component, which handles font size logic for class generation, +font size value retrieval, and font size change handling. + +**Parameters** + +- **args** `...(object|string)`: The arguments should all be strings Each string contains the font size attribute name e.g: 'fontSize'. + +**Returns** + +`Function`: Higher-order component. + +### WritingFlow + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### \_BlockSettingsMenuFirstItem + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + +### \_BlockSettingsMenuPluginsExtension + +[src/index.js#L15-L15](src/index.js#L15-L15) + +Undocumented declaration. + <!-- END TOKEN(Autogenerated API docs) --> diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json index 17cd7064411ec..b6f19c50b0f15 100644 --- a/packages/block-editor/package.json +++ b/packages/block-editor/package.json @@ -23,15 +23,32 @@ "react-native": "src/index", "dependencies": { "@babel/runtime": "^7.0.0", + "@wordpress/a11y": "file:../a11y", + "@wordpress/blob": "file:../blob", "@wordpress/blocks": "file:../blocks", "@wordpress/components": "file:../components", "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", "@wordpress/data": "file:../data", + "@wordpress/dom": "file:../dom", "@wordpress/element": "file:../element", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", "@wordpress/i18n": "file:../i18n", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/rich-text": "file:../rich-text", + "@wordpress/token-list": "file:../token-list", + "@wordpress/url": "file:../url", + "@wordpress/viewport": "file:../viewport", + "@wordpress/wordcount": "file:../wordcount", + "classnames": "^2.2.5", + "dom-scroll-into-view": "^1.2.1", "lodash": "^4.17.10", + "redux-multi": "^0.1.12", "refx": "^3.0.0", - "rememo": "^3.0.0" + "rememo": "^3.0.0", + "tinycolor2": "^1.4.1" }, "publishConfig": { "access": "public" diff --git a/packages/editor/src/components/alignment-toolbar/index.js b/packages/block-editor/src/components/alignment-toolbar/index.js similarity index 100% rename from packages/editor/src/components/alignment-toolbar/index.js rename to packages/block-editor/src/components/alignment-toolbar/index.js diff --git a/packages/editor/src/components/alignment-toolbar/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/alignment-toolbar/test/__snapshots__/index.js.snap similarity index 100% rename from packages/editor/src/components/alignment-toolbar/test/__snapshots__/index.js.snap rename to packages/block-editor/src/components/alignment-toolbar/test/__snapshots__/index.js.snap diff --git a/packages/editor/src/components/alignment-toolbar/test/index.js b/packages/block-editor/src/components/alignment-toolbar/test/index.js similarity index 100% rename from packages/editor/src/components/alignment-toolbar/test/index.js rename to packages/block-editor/src/components/alignment-toolbar/test/index.js diff --git a/packages/editor/src/components/autocomplete/README.md b/packages/block-editor/src/components/autocomplete/README.md similarity index 100% rename from packages/editor/src/components/autocomplete/README.md rename to packages/block-editor/src/components/autocomplete/README.md diff --git a/packages/editor/src/components/autocomplete/index.js b/packages/block-editor/src/components/autocomplete/index.js similarity index 100% rename from packages/editor/src/components/autocomplete/index.js rename to packages/block-editor/src/components/autocomplete/index.js diff --git a/packages/editor/src/components/autocomplete/test/index.js b/packages/block-editor/src/components/autocomplete/test/index.js similarity index 100% rename from packages/editor/src/components/autocomplete/test/index.js rename to packages/block-editor/src/components/autocomplete/test/index.js diff --git a/packages/editor/src/components/block-actions/index.js b/packages/block-editor/src/components/block-actions/index.js similarity index 100% rename from packages/editor/src/components/block-actions/index.js rename to packages/block-editor/src/components/block-actions/index.js diff --git a/packages/editor/src/components/block-alignment-toolbar/index.js b/packages/block-editor/src/components/block-alignment-toolbar/index.js similarity index 100% rename from packages/editor/src/components/block-alignment-toolbar/index.js rename to packages/block-editor/src/components/block-alignment-toolbar/index.js diff --git a/packages/editor/src/components/block-alignment-toolbar/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/block-alignment-toolbar/test/__snapshots__/index.js.snap similarity index 100% rename from packages/editor/src/components/block-alignment-toolbar/test/__snapshots__/index.js.snap rename to packages/block-editor/src/components/block-alignment-toolbar/test/__snapshots__/index.js.snap diff --git a/packages/editor/src/components/block-alignment-toolbar/test/index.js b/packages/block-editor/src/components/block-alignment-toolbar/test/index.js similarity index 100% rename from packages/editor/src/components/block-alignment-toolbar/test/index.js rename to packages/block-editor/src/components/block-alignment-toolbar/test/index.js diff --git a/packages/editor/src/components/block-compare/README.md b/packages/block-editor/src/components/block-compare/README.md similarity index 100% rename from packages/editor/src/components/block-compare/README.md rename to packages/block-editor/src/components/block-compare/README.md diff --git a/packages/editor/src/components/block-compare/block-view.js b/packages/block-editor/src/components/block-compare/block-view.js similarity index 100% rename from packages/editor/src/components/block-compare/block-view.js rename to packages/block-editor/src/components/block-compare/block-view.js diff --git a/packages/editor/src/components/block-compare/index.js b/packages/block-editor/src/components/block-compare/index.js similarity index 100% rename from packages/editor/src/components/block-compare/index.js rename to packages/block-editor/src/components/block-compare/index.js diff --git a/packages/editor/src/components/block-compare/style.scss b/packages/block-editor/src/components/block-compare/style.scss similarity index 100% rename from packages/editor/src/components/block-compare/style.scss rename to packages/block-editor/src/components/block-compare/style.scss diff --git a/packages/editor/src/components/block-compare/test/__snapshots__/block-view.js.snap b/packages/block-editor/src/components/block-compare/test/__snapshots__/block-view.js.snap similarity index 100% rename from packages/editor/src/components/block-compare/test/__snapshots__/block-view.js.snap rename to packages/block-editor/src/components/block-compare/test/__snapshots__/block-view.js.snap diff --git a/packages/editor/src/components/block-compare/test/block-view.js b/packages/block-editor/src/components/block-compare/test/block-view.js similarity index 100% rename from packages/editor/src/components/block-compare/test/block-view.js rename to packages/block-editor/src/components/block-compare/test/block-view.js diff --git a/packages/editor/src/components/block-controls/index.js b/packages/block-editor/src/components/block-controls/index.js similarity index 100% rename from packages/editor/src/components/block-controls/index.js rename to packages/block-editor/src/components/block-controls/index.js diff --git a/packages/editor/src/components/block-controls/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/block-controls/test/__snapshots__/index.js.snap similarity index 100% rename from packages/editor/src/components/block-controls/test/__snapshots__/index.js.snap rename to packages/block-editor/src/components/block-controls/test/__snapshots__/index.js.snap diff --git a/packages/editor/src/components/block-controls/test/index.js b/packages/block-editor/src/components/block-controls/test/index.js similarity index 100% rename from packages/editor/src/components/block-controls/test/index.js rename to packages/block-editor/src/components/block-controls/test/index.js diff --git a/packages/editor/src/components/block-draggable/index.js b/packages/block-editor/src/components/block-draggable/index.js similarity index 100% rename from packages/editor/src/components/block-draggable/index.js rename to packages/block-editor/src/components/block-draggable/index.js diff --git a/packages/editor/src/components/block-drop-zone/README.md b/packages/block-editor/src/components/block-drop-zone/README.md similarity index 100% rename from packages/editor/src/components/block-drop-zone/README.md rename to packages/block-editor/src/components/block-drop-zone/README.md diff --git a/packages/editor/src/components/block-drop-zone/index.js b/packages/block-editor/src/components/block-drop-zone/index.js similarity index 100% rename from packages/editor/src/components/block-drop-zone/index.js rename to packages/block-editor/src/components/block-drop-zone/index.js diff --git a/packages/editor/src/components/block-drop-zone/style.scss b/packages/block-editor/src/components/block-drop-zone/style.scss similarity index 100% rename from packages/editor/src/components/block-drop-zone/style.scss rename to packages/block-editor/src/components/block-drop-zone/style.scss diff --git a/packages/editor/src/components/block-edit/context.js b/packages/block-editor/src/components/block-edit/context.js similarity index 100% rename from packages/editor/src/components/block-edit/context.js rename to packages/block-editor/src/components/block-edit/context.js diff --git a/packages/editor/src/components/block-edit/edit.js b/packages/block-editor/src/components/block-edit/edit.js similarity index 100% rename from packages/editor/src/components/block-edit/edit.js rename to packages/block-editor/src/components/block-edit/edit.js diff --git a/packages/editor/src/components/block-edit/edit.native.js b/packages/block-editor/src/components/block-edit/edit.native.js similarity index 100% rename from packages/editor/src/components/block-edit/edit.native.js rename to packages/block-editor/src/components/block-edit/edit.native.js diff --git a/packages/editor/src/components/block-edit/index.js b/packages/block-editor/src/components/block-edit/index.js similarity index 100% rename from packages/editor/src/components/block-edit/index.js rename to packages/block-editor/src/components/block-edit/index.js diff --git a/packages/editor/src/components/block-edit/test/edit.js b/packages/block-editor/src/components/block-edit/test/edit.js similarity index 100% rename from packages/editor/src/components/block-edit/test/edit.js rename to packages/block-editor/src/components/block-edit/test/edit.js diff --git a/packages/editor/src/components/global-keyboard-shortcuts/block-editor-shortcuts.js b/packages/block-editor/src/components/block-editor-keyboard-shortcuts/index.js similarity index 100% rename from packages/editor/src/components/global-keyboard-shortcuts/block-editor-shortcuts.js rename to packages/block-editor/src/components/block-editor-keyboard-shortcuts/index.js diff --git a/packages/editor/src/components/block-format-controls/index.js b/packages/block-editor/src/components/block-format-controls/index.js similarity index 100% rename from packages/editor/src/components/block-format-controls/index.js rename to packages/block-editor/src/components/block-format-controls/index.js diff --git a/packages/editor/src/components/block-icon/index.js b/packages/block-editor/src/components/block-icon/index.js similarity index 100% rename from packages/editor/src/components/block-icon/index.js rename to packages/block-editor/src/components/block-icon/index.js diff --git a/packages/editor/src/components/block-icon/style.scss b/packages/block-editor/src/components/block-icon/style.scss similarity index 100% rename from packages/editor/src/components/block-icon/style.scss rename to packages/block-editor/src/components/block-icon/style.scss diff --git a/packages/editor/src/components/block-icon/test/index.js b/packages/block-editor/src/components/block-icon/test/index.js similarity index 100% rename from packages/editor/src/components/block-icon/test/index.js rename to packages/block-editor/src/components/block-icon/test/index.js diff --git a/packages/editor/src/components/block-inspector/index.js b/packages/block-editor/src/components/block-inspector/index.js similarity index 100% rename from packages/editor/src/components/block-inspector/index.js rename to packages/block-editor/src/components/block-inspector/index.js diff --git a/packages/editor/src/components/block-inspector/style.scss b/packages/block-editor/src/components/block-inspector/style.scss similarity index 100% rename from packages/editor/src/components/block-inspector/style.scss rename to packages/block-editor/src/components/block-inspector/style.scss diff --git a/packages/editor/src/components/block-list-appender/index.js b/packages/block-editor/src/components/block-list-appender/index.js similarity index 100% rename from packages/editor/src/components/block-list-appender/index.js rename to packages/block-editor/src/components/block-list-appender/index.js diff --git a/packages/editor/src/components/block-list-appender/style.scss b/packages/block-editor/src/components/block-list-appender/style.scss similarity index 100% rename from packages/editor/src/components/block-list-appender/style.scss rename to packages/block-editor/src/components/block-list-appender/style.scss diff --git a/packages/editor/src/components/block-list/block-contextual-toolbar.js b/packages/block-editor/src/components/block-list/block-contextual-toolbar.js similarity index 100% rename from packages/editor/src/components/block-list/block-contextual-toolbar.js rename to packages/block-editor/src/components/block-list/block-contextual-toolbar.js diff --git a/packages/editor/src/components/block-list/block-crash-boundary.js b/packages/block-editor/src/components/block-list/block-crash-boundary.js similarity index 100% rename from packages/editor/src/components/block-list/block-crash-boundary.js rename to packages/block-editor/src/components/block-list/block-crash-boundary.js diff --git a/packages/editor/src/components/block-list/block-crash-warning.js b/packages/block-editor/src/components/block-list/block-crash-warning.js similarity index 100% rename from packages/editor/src/components/block-list/block-crash-warning.js rename to packages/block-editor/src/components/block-list/block-crash-warning.js diff --git a/packages/editor/src/components/block-list/block-html.js b/packages/block-editor/src/components/block-list/block-html.js similarity index 100% rename from packages/editor/src/components/block-list/block-html.js rename to packages/block-editor/src/components/block-list/block-html.js diff --git a/packages/editor/src/components/block-list/block-invalid-warning.js b/packages/block-editor/src/components/block-list/block-invalid-warning.js similarity index 100% rename from packages/editor/src/components/block-list/block-invalid-warning.js rename to packages/block-editor/src/components/block-list/block-invalid-warning.js diff --git a/packages/editor/src/components/block-list/block-mobile-toolbar.js b/packages/block-editor/src/components/block-list/block-mobile-toolbar.js similarity index 100% rename from packages/editor/src/components/block-list/block-mobile-toolbar.js rename to packages/block-editor/src/components/block-list/block-mobile-toolbar.js diff --git a/packages/editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js similarity index 100% rename from packages/editor/src/components/block-list/block.js rename to packages/block-editor/src/components/block-list/block.js diff --git a/packages/editor/src/components/block-list/breadcrumb.js b/packages/block-editor/src/components/block-list/breadcrumb.js similarity index 100% rename from packages/editor/src/components/block-list/breadcrumb.js rename to packages/block-editor/src/components/block-list/breadcrumb.js diff --git a/packages/editor/src/components/block-list/hover-area.js b/packages/block-editor/src/components/block-list/hover-area.js similarity index 100% rename from packages/editor/src/components/block-list/hover-area.js rename to packages/block-editor/src/components/block-list/hover-area.js diff --git a/packages/editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js similarity index 100% rename from packages/editor/src/components/block-list/index.js rename to packages/block-editor/src/components/block-list/index.js diff --git a/packages/editor/src/components/block-list/insertion-point.js b/packages/block-editor/src/components/block-list/insertion-point.js similarity index 100% rename from packages/editor/src/components/block-list/insertion-point.js rename to packages/block-editor/src/components/block-list/insertion-point.js diff --git a/packages/editor/src/components/block-list/multi-controls.js b/packages/block-editor/src/components/block-list/multi-controls.js similarity index 100% rename from packages/editor/src/components/block-list/multi-controls.js rename to packages/block-editor/src/components/block-list/multi-controls.js diff --git a/packages/editor/src/components/block-list/style.scss b/packages/block-editor/src/components/block-list/style.scss similarity index 100% rename from packages/editor/src/components/block-list/style.scss rename to packages/block-editor/src/components/block-list/style.scss diff --git a/packages/editor/src/components/block-list/test/block-html.js b/packages/block-editor/src/components/block-list/test/block-html.js similarity index 100% rename from packages/editor/src/components/block-list/test/block-html.js rename to packages/block-editor/src/components/block-list/test/block-html.js diff --git a/packages/editor/src/components/block-mover/drag-handle.js b/packages/block-editor/src/components/block-mover/drag-handle.js similarity index 100% rename from packages/editor/src/components/block-mover/drag-handle.js rename to packages/block-editor/src/components/block-mover/drag-handle.js diff --git a/packages/editor/src/components/block-mover/icons.js b/packages/block-editor/src/components/block-mover/icons.js similarity index 100% rename from packages/editor/src/components/block-mover/icons.js rename to packages/block-editor/src/components/block-mover/icons.js diff --git a/packages/editor/src/components/block-mover/index.js b/packages/block-editor/src/components/block-mover/index.js similarity index 100% rename from packages/editor/src/components/block-mover/index.js rename to packages/block-editor/src/components/block-mover/index.js diff --git a/packages/editor/src/components/block-mover/mover-description.js b/packages/block-editor/src/components/block-mover/mover-description.js similarity index 100% rename from packages/editor/src/components/block-mover/mover-description.js rename to packages/block-editor/src/components/block-mover/mover-description.js diff --git a/packages/editor/src/components/block-mover/style.scss b/packages/block-editor/src/components/block-mover/style.scss similarity index 100% rename from packages/editor/src/components/block-mover/style.scss rename to packages/block-editor/src/components/block-mover/style.scss diff --git a/packages/editor/src/components/block-mover/test/index.js b/packages/block-editor/src/components/block-mover/test/index.js similarity index 100% rename from packages/editor/src/components/block-mover/test/index.js rename to packages/block-editor/src/components/block-mover/test/index.js diff --git a/packages/editor/src/components/block-mover/test/mover-description.js b/packages/block-editor/src/components/block-mover/test/mover-description.js similarity index 100% rename from packages/editor/src/components/block-mover/test/mover-description.js rename to packages/block-editor/src/components/block-mover/test/mover-description.js diff --git a/packages/editor/src/components/block-navigation/dropdown.js b/packages/block-editor/src/components/block-navigation/dropdown.js similarity index 100% rename from packages/editor/src/components/block-navigation/dropdown.js rename to packages/block-editor/src/components/block-navigation/dropdown.js diff --git a/packages/editor/src/components/block-navigation/index.js b/packages/block-editor/src/components/block-navigation/index.js similarity index 100% rename from packages/editor/src/components/block-navigation/index.js rename to packages/block-editor/src/components/block-navigation/index.js diff --git a/packages/editor/src/components/block-navigation/style.scss b/packages/block-editor/src/components/block-navigation/style.scss similarity index 100% rename from packages/editor/src/components/block-navigation/style.scss rename to packages/block-editor/src/components/block-navigation/style.scss diff --git a/packages/editor/src/components/block-preview/index.js b/packages/block-editor/src/components/block-preview/index.js similarity index 100% rename from packages/editor/src/components/block-preview/index.js rename to packages/block-editor/src/components/block-preview/index.js diff --git a/packages/editor/src/components/block-preview/style.scss b/packages/block-editor/src/components/block-preview/style.scss similarity index 100% rename from packages/editor/src/components/block-preview/style.scss rename to packages/block-editor/src/components/block-preview/style.scss diff --git a/packages/editor/src/components/block-selection-clearer/index.js b/packages/block-editor/src/components/block-selection-clearer/index.js similarity index 100% rename from packages/editor/src/components/block-selection-clearer/index.js rename to packages/block-editor/src/components/block-selection-clearer/index.js diff --git a/packages/editor/src/components/block-settings-menu/block-convert-button.js b/packages/block-editor/src/components/block-settings-menu/block-convert-button.js similarity index 100% rename from packages/editor/src/components/block-settings-menu/block-convert-button.js rename to packages/block-editor/src/components/block-settings-menu/block-convert-button.js diff --git a/packages/editor/src/components/block-settings-menu/block-html-convert-button.js b/packages/block-editor/src/components/block-settings-menu/block-html-convert-button.js similarity index 100% rename from packages/editor/src/components/block-settings-menu/block-html-convert-button.js rename to packages/block-editor/src/components/block-settings-menu/block-html-convert-button.js diff --git a/packages/editor/src/components/block-settings-menu/block-mode-toggle.js b/packages/block-editor/src/components/block-settings-menu/block-mode-toggle.js similarity index 100% rename from packages/editor/src/components/block-settings-menu/block-mode-toggle.js rename to packages/block-editor/src/components/block-settings-menu/block-mode-toggle.js diff --git a/packages/editor/src/components/block-settings-menu/block-settings-menu-first-item.js b/packages/block-editor/src/components/block-settings-menu/block-settings-menu-first-item.js similarity index 100% rename from packages/editor/src/components/block-settings-menu/block-settings-menu-first-item.js rename to packages/block-editor/src/components/block-settings-menu/block-settings-menu-first-item.js diff --git a/packages/editor/src/components/block-settings-menu/block-settings-menu-plugins-extension.js b/packages/block-editor/src/components/block-settings-menu/block-settings-menu-plugins-extension.js similarity index 100% rename from packages/editor/src/components/block-settings-menu/block-settings-menu-plugins-extension.js rename to packages/block-editor/src/components/block-settings-menu/block-settings-menu-plugins-extension.js diff --git a/packages/editor/src/components/block-settings-menu/block-unknown-convert-button.js b/packages/block-editor/src/components/block-settings-menu/block-unknown-convert-button.js similarity index 100% rename from packages/editor/src/components/block-settings-menu/block-unknown-convert-button.js rename to packages/block-editor/src/components/block-settings-menu/block-unknown-convert-button.js diff --git a/packages/editor/src/components/block-settings-menu/index.js b/packages/block-editor/src/components/block-settings-menu/index.js similarity index 98% rename from packages/editor/src/components/block-settings-menu/index.js rename to packages/block-editor/src/components/block-settings-menu/index.js index c15bc11c63b29..6ab78e2b70c20 100644 --- a/packages/editor/src/components/block-settings-menu/index.js +++ b/packages/block-editor/src/components/block-settings-menu/index.js @@ -15,7 +15,7 @@ import { withDispatch } from '@wordpress/data'; /** * Internal dependencies */ -import { shortcuts } from '../global-keyboard-shortcuts/block-editor-shortcuts'; +import { shortcuts } from '../block-editor-keyboard-shortcuts'; import BlockActions from '../block-actions'; import BlockModeToggle from './block-mode-toggle'; import ReusableBlockConvertButton from './reusable-block-convert-button'; diff --git a/packages/editor/src/components/block-settings-menu/reusable-block-convert-button.js b/packages/block-editor/src/components/block-settings-menu/reusable-block-convert-button.js similarity index 100% rename from packages/editor/src/components/block-settings-menu/reusable-block-convert-button.js rename to packages/block-editor/src/components/block-settings-menu/reusable-block-convert-button.js diff --git a/packages/editor/src/components/block-settings-menu/reusable-block-delete-button.js b/packages/block-editor/src/components/block-settings-menu/reusable-block-delete-button.js similarity index 100% rename from packages/editor/src/components/block-settings-menu/reusable-block-delete-button.js rename to packages/block-editor/src/components/block-settings-menu/reusable-block-delete-button.js diff --git a/packages/editor/src/components/block-settings-menu/style.scss b/packages/block-editor/src/components/block-settings-menu/style.scss similarity index 100% rename from packages/editor/src/components/block-settings-menu/style.scss rename to packages/block-editor/src/components/block-settings-menu/style.scss diff --git a/packages/editor/src/components/block-settings-menu/test/__snapshots__/reusable-block-delete-button.js.snap b/packages/block-editor/src/components/block-settings-menu/test/__snapshots__/reusable-block-delete-button.js.snap similarity index 100% rename from packages/editor/src/components/block-settings-menu/test/__snapshots__/reusable-block-delete-button.js.snap rename to packages/block-editor/src/components/block-settings-menu/test/__snapshots__/reusable-block-delete-button.js.snap diff --git a/packages/editor/src/components/block-settings-menu/test/block-mode-toggle.js b/packages/block-editor/src/components/block-settings-menu/test/block-mode-toggle.js similarity index 100% rename from packages/editor/src/components/block-settings-menu/test/block-mode-toggle.js rename to packages/block-editor/src/components/block-settings-menu/test/block-mode-toggle.js diff --git a/packages/editor/src/components/block-settings-menu/test/reusable-block-convert-button.js b/packages/block-editor/src/components/block-settings-menu/test/reusable-block-convert-button.js similarity index 100% rename from packages/editor/src/components/block-settings-menu/test/reusable-block-convert-button.js rename to packages/block-editor/src/components/block-settings-menu/test/reusable-block-convert-button.js diff --git a/packages/editor/src/components/block-settings-menu/test/reusable-block-delete-button.js b/packages/block-editor/src/components/block-settings-menu/test/reusable-block-delete-button.js similarity index 100% rename from packages/editor/src/components/block-settings-menu/test/reusable-block-delete-button.js rename to packages/block-editor/src/components/block-settings-menu/test/reusable-block-delete-button.js diff --git a/packages/editor/src/components/block-styles/index.js b/packages/block-editor/src/components/block-styles/index.js similarity index 100% rename from packages/editor/src/components/block-styles/index.js rename to packages/block-editor/src/components/block-styles/index.js diff --git a/packages/editor/src/components/block-styles/style.scss b/packages/block-editor/src/components/block-styles/style.scss similarity index 100% rename from packages/editor/src/components/block-styles/style.scss rename to packages/block-editor/src/components/block-styles/style.scss diff --git a/packages/editor/src/components/block-styles/test/index.js b/packages/block-editor/src/components/block-styles/test/index.js similarity index 100% rename from packages/editor/src/components/block-styles/test/index.js rename to packages/block-editor/src/components/block-styles/test/index.js diff --git a/packages/editor/src/components/block-switcher/index.js b/packages/block-editor/src/components/block-switcher/index.js similarity index 100% rename from packages/editor/src/components/block-switcher/index.js rename to packages/block-editor/src/components/block-switcher/index.js diff --git a/packages/editor/src/components/block-switcher/multi-blocks-switcher.js b/packages/block-editor/src/components/block-switcher/multi-blocks-switcher.js similarity index 100% rename from packages/editor/src/components/block-switcher/multi-blocks-switcher.js rename to packages/block-editor/src/components/block-switcher/multi-blocks-switcher.js diff --git a/packages/editor/src/components/block-switcher/style.scss b/packages/block-editor/src/components/block-switcher/style.scss similarity index 100% rename from packages/editor/src/components/block-switcher/style.scss rename to packages/block-editor/src/components/block-switcher/style.scss diff --git a/packages/editor/src/components/block-switcher/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/block-switcher/test/__snapshots__/index.js.snap similarity index 100% rename from packages/editor/src/components/block-switcher/test/__snapshots__/index.js.snap rename to packages/block-editor/src/components/block-switcher/test/__snapshots__/index.js.snap diff --git a/packages/editor/src/components/block-switcher/test/__snapshots__/multi-blocks-switcher.js.snap b/packages/block-editor/src/components/block-switcher/test/__snapshots__/multi-blocks-switcher.js.snap similarity index 100% rename from packages/editor/src/components/block-switcher/test/__snapshots__/multi-blocks-switcher.js.snap rename to packages/block-editor/src/components/block-switcher/test/__snapshots__/multi-blocks-switcher.js.snap diff --git a/packages/editor/src/components/block-switcher/test/index.js b/packages/block-editor/src/components/block-switcher/test/index.js similarity index 100% rename from packages/editor/src/components/block-switcher/test/index.js rename to packages/block-editor/src/components/block-switcher/test/index.js diff --git a/packages/editor/src/components/block-switcher/test/multi-blocks-switcher.js b/packages/block-editor/src/components/block-switcher/test/multi-blocks-switcher.js similarity index 100% rename from packages/editor/src/components/block-switcher/test/multi-blocks-switcher.js rename to packages/block-editor/src/components/block-switcher/test/multi-blocks-switcher.js diff --git a/packages/editor/src/components/block-title/README.md b/packages/block-editor/src/components/block-title/README.md similarity index 100% rename from packages/editor/src/components/block-title/README.md rename to packages/block-editor/src/components/block-title/README.md diff --git a/packages/editor/src/components/block-title/index.js b/packages/block-editor/src/components/block-title/index.js similarity index 100% rename from packages/editor/src/components/block-title/index.js rename to packages/block-editor/src/components/block-title/index.js diff --git a/packages/editor/src/components/block-title/test/index.js b/packages/block-editor/src/components/block-title/test/index.js similarity index 100% rename from packages/editor/src/components/block-title/test/index.js rename to packages/block-editor/src/components/block-title/test/index.js diff --git a/packages/editor/src/components/block-toolbar/index.js b/packages/block-editor/src/components/block-toolbar/index.js similarity index 100% rename from packages/editor/src/components/block-toolbar/index.js rename to packages/block-editor/src/components/block-toolbar/index.js diff --git a/packages/editor/src/components/block-toolbar/style.scss b/packages/block-editor/src/components/block-toolbar/style.scss similarity index 100% rename from packages/editor/src/components/block-toolbar/style.scss rename to packages/block-editor/src/components/block-toolbar/style.scss diff --git a/packages/editor/src/components/block-types-list/index.js b/packages/block-editor/src/components/block-types-list/index.js similarity index 100% rename from packages/editor/src/components/block-types-list/index.js rename to packages/block-editor/src/components/block-types-list/index.js diff --git a/packages/editor/src/components/block-types-list/style.scss b/packages/block-editor/src/components/block-types-list/style.scss similarity index 100% rename from packages/editor/src/components/block-types-list/style.scss rename to packages/block-editor/src/components/block-types-list/style.scss diff --git a/packages/editor/src/components/color-palette/control.js b/packages/block-editor/src/components/color-palette/control.js similarity index 100% rename from packages/editor/src/components/color-palette/control.js rename to packages/block-editor/src/components/color-palette/control.js diff --git a/packages/editor/src/components/color-palette/control.scss b/packages/block-editor/src/components/color-palette/control.scss similarity index 100% rename from packages/editor/src/components/color-palette/control.scss rename to packages/block-editor/src/components/color-palette/control.scss diff --git a/packages/editor/src/components/color-palette/index.js b/packages/block-editor/src/components/color-palette/index.js similarity index 100% rename from packages/editor/src/components/color-palette/index.js rename to packages/block-editor/src/components/color-palette/index.js diff --git a/packages/editor/src/components/color-palette/test/__snapshots__/control.js.snap b/packages/block-editor/src/components/color-palette/test/__snapshots__/control.js.snap similarity index 100% rename from packages/editor/src/components/color-palette/test/__snapshots__/control.js.snap rename to packages/block-editor/src/components/color-palette/test/__snapshots__/control.js.snap diff --git a/packages/editor/src/components/color-palette/test/control.js b/packages/block-editor/src/components/color-palette/test/control.js similarity index 100% rename from packages/editor/src/components/color-palette/test/control.js rename to packages/block-editor/src/components/color-palette/test/control.js diff --git a/packages/editor/src/components/color-palette/with-color-context.js b/packages/block-editor/src/components/color-palette/with-color-context.js similarity index 100% rename from packages/editor/src/components/color-palette/with-color-context.js rename to packages/block-editor/src/components/color-palette/with-color-context.js diff --git a/packages/editor/src/components/colors/index.js b/packages/block-editor/src/components/colors/index.js similarity index 100% rename from packages/editor/src/components/colors/index.js rename to packages/block-editor/src/components/colors/index.js diff --git a/packages/editor/src/components/colors/test/__snapshots__/with-colors.js.snap b/packages/block-editor/src/components/colors/test/__snapshots__/with-colors.js.snap similarity index 100% rename from packages/editor/src/components/colors/test/__snapshots__/with-colors.js.snap rename to packages/block-editor/src/components/colors/test/__snapshots__/with-colors.js.snap diff --git a/packages/editor/src/components/colors/test/with-colors.js b/packages/block-editor/src/components/colors/test/with-colors.js similarity index 100% rename from packages/editor/src/components/colors/test/with-colors.js rename to packages/block-editor/src/components/colors/test/with-colors.js diff --git a/packages/editor/src/components/colors/utils.js b/packages/block-editor/src/components/colors/utils.js similarity index 100% rename from packages/editor/src/components/colors/utils.js rename to packages/block-editor/src/components/colors/utils.js diff --git a/packages/editor/src/components/colors/with-colors.js b/packages/block-editor/src/components/colors/with-colors.js similarity index 100% rename from packages/editor/src/components/colors/with-colors.js rename to packages/block-editor/src/components/colors/with-colors.js diff --git a/packages/editor/src/components/contrast-checker/index.js b/packages/block-editor/src/components/contrast-checker/index.js similarity index 100% rename from packages/editor/src/components/contrast-checker/index.js rename to packages/block-editor/src/components/contrast-checker/index.js diff --git a/packages/editor/src/components/contrast-checker/style.scss b/packages/block-editor/src/components/contrast-checker/style.scss similarity index 100% rename from packages/editor/src/components/contrast-checker/style.scss rename to packages/block-editor/src/components/contrast-checker/style.scss diff --git a/packages/editor/src/components/contrast-checker/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/contrast-checker/test/__snapshots__/index.js.snap similarity index 100% rename from packages/editor/src/components/contrast-checker/test/__snapshots__/index.js.snap rename to packages/block-editor/src/components/contrast-checker/test/__snapshots__/index.js.snap diff --git a/packages/editor/src/components/contrast-checker/test/index.js b/packages/block-editor/src/components/contrast-checker/test/index.js similarity index 100% rename from packages/editor/src/components/contrast-checker/test/index.js rename to packages/block-editor/src/components/contrast-checker/test/index.js diff --git a/packages/editor/src/components/copy-handler/index.js b/packages/block-editor/src/components/copy-handler/index.js similarity index 100% rename from packages/editor/src/components/copy-handler/index.js rename to packages/block-editor/src/components/copy-handler/index.js diff --git a/packages/editor/src/components/default-block-appender/index.js b/packages/block-editor/src/components/default-block-appender/index.js similarity index 100% rename from packages/editor/src/components/default-block-appender/index.js rename to packages/block-editor/src/components/default-block-appender/index.js diff --git a/packages/editor/src/components/default-block-appender/index.native.js b/packages/block-editor/src/components/default-block-appender/index.native.js similarity index 100% rename from packages/editor/src/components/default-block-appender/index.native.js rename to packages/block-editor/src/components/default-block-appender/index.native.js diff --git a/packages/editor/src/components/default-block-appender/style.native.scss b/packages/block-editor/src/components/default-block-appender/style.native.scss similarity index 100% rename from packages/editor/src/components/default-block-appender/style.native.scss rename to packages/block-editor/src/components/default-block-appender/style.native.scss diff --git a/packages/editor/src/components/default-block-appender/style.scss b/packages/block-editor/src/components/default-block-appender/style.scss similarity index 100% rename from packages/editor/src/components/default-block-appender/style.scss rename to packages/block-editor/src/components/default-block-appender/style.scss diff --git a/packages/editor/src/components/default-block-appender/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/default-block-appender/test/__snapshots__/index.js.snap similarity index 100% rename from packages/editor/src/components/default-block-appender/test/__snapshots__/index.js.snap rename to packages/block-editor/src/components/default-block-appender/test/__snapshots__/index.js.snap diff --git a/packages/editor/src/components/default-block-appender/test/index.js b/packages/block-editor/src/components/default-block-appender/test/index.js similarity index 100% rename from packages/editor/src/components/default-block-appender/test/index.js rename to packages/block-editor/src/components/default-block-appender/test/index.js diff --git a/packages/editor/src/components/font-sizes/font-size-picker.js b/packages/block-editor/src/components/font-sizes/font-size-picker.js similarity index 100% rename from packages/editor/src/components/font-sizes/font-size-picker.js rename to packages/block-editor/src/components/font-sizes/font-size-picker.js diff --git a/packages/editor/src/components/font-sizes/index.js b/packages/block-editor/src/components/font-sizes/index.js similarity index 100% rename from packages/editor/src/components/font-sizes/index.js rename to packages/block-editor/src/components/font-sizes/index.js diff --git a/packages/editor/src/components/font-sizes/index.native.js b/packages/block-editor/src/components/font-sizes/index.native.js similarity index 100% rename from packages/editor/src/components/font-sizes/index.native.js rename to packages/block-editor/src/components/font-sizes/index.native.js diff --git a/packages/editor/src/components/font-sizes/style.scss b/packages/block-editor/src/components/font-sizes/style.scss similarity index 100% rename from packages/editor/src/components/font-sizes/style.scss rename to packages/block-editor/src/components/font-sizes/style.scss diff --git a/packages/editor/src/components/font-sizes/utils.js b/packages/block-editor/src/components/font-sizes/utils.js similarity index 100% rename from packages/editor/src/components/font-sizes/utils.js rename to packages/block-editor/src/components/font-sizes/utils.js diff --git a/packages/editor/src/components/font-sizes/with-font-sizes.js b/packages/block-editor/src/components/font-sizes/with-font-sizes.js similarity index 100% rename from packages/editor/src/components/font-sizes/with-font-sizes.js rename to packages/block-editor/src/components/font-sizes/with-font-sizes.js diff --git a/packages/editor/src/components/ignore-nested-events/index.js b/packages/block-editor/src/components/ignore-nested-events/index.js similarity index 100% rename from packages/editor/src/components/ignore-nested-events/index.js rename to packages/block-editor/src/components/ignore-nested-events/index.js diff --git a/packages/editor/src/components/ignore-nested-events/test/index.js b/packages/block-editor/src/components/ignore-nested-events/test/index.js similarity index 100% rename from packages/editor/src/components/ignore-nested-events/test/index.js rename to packages/block-editor/src/components/ignore-nested-events/test/index.js diff --git a/packages/block-editor/src/components/index.js b/packages/block-editor/src/components/index.js index cb2cca8f110ba..a43c53ea54f12 100644 --- a/packages/block-editor/src/components/index.js +++ b/packages/block-editor/src/components/index.js @@ -1 +1,57 @@ +// Block Creation Components +export { default as Autocomplete } from './autocomplete'; +export { default as AlignmentToolbar } from './alignment-toolbar'; +export { default as BlockAlignmentToolbar } from './block-alignment-toolbar'; +export { default as BlockControls } from './block-controls'; +export { default as BlockEdit } from './block-edit'; +export { default as BlockFormatControls } from './block-format-controls'; +export { default as BlockNavigationDropdown } from './block-navigation/dropdown'; +export { default as BlockIcon } from './block-icon'; +export { default as ColorPalette } from './color-palette'; +export { default as withColorContext } from './color-palette/with-color-context'; +export * from './colors'; +export { default as ContrastChecker } from './contrast-checker'; +export * from './font-sizes'; +export { default as InnerBlocks } from './inner-blocks'; +export { default as InspectorAdvancedControls } from './inspector-advanced-controls'; +export { default as InspectorControls } from './inspector-controls'; +export { default as PanelColorSettings } from './panel-color-settings'; +export { default as PlainText } from './plain-text'; +export { + default as RichText, + RichTextShortcut, + RichTextToolbarButton, + UnstableRichTextInputEvent, +} from './rich-text'; +export { default as MediaPlaceholder } from './media-placeholder'; +export { default as MediaUpload } from './media-upload'; +export { default as MediaUploadCheck } from './media-upload/check'; +export { default as URLInput } from './url-input'; +export { default as URLInputButton } from './url-input/button'; +export { default as URLPopover } from './url-popover'; + +// Content Related Components +export { default as BlockEditorKeyboardShortcuts } from './block-editor-keyboard-shortcuts'; +export { default as BlockInspector } from './block-inspector'; +export { default as BlockList } from './block-list'; +export { default as BlockMover } from './block-mover'; +export { default as BlockSelectionClearer } from './block-selection-clearer'; +export { default as BlockSettingsMenu } from './block-settings-menu'; +export { default as _BlockSettingsMenuFirstItem } from './block-settings-menu/block-settings-menu-first-item'; +export { default as _BlockSettingsMenuPluginsExtension } from './block-settings-menu/block-settings-menu-plugins-extension'; +export { default as BlockTitle } from './block-title'; +export { default as BlockToolbar } from './block-toolbar'; +export { default as CopyHandler } from './copy-handler'; +export { default as DefaultBlockAppender } from './default-block-appender'; +export { default as Inserter } from './inserter'; +export { default as MultiBlocksSwitcher } from './block-switcher/multi-blocks-switcher'; +export { default as MultiSelectScrollIntoView } from './multi-select-scroll-into-view'; +export { default as NavigableToolbar } from './navigable-toolbar'; +export { default as ObserveTyping } from './observe-typing'; +export { default as PreserveScrollInReorder } from './preserve-scroll-in-reorder'; +export { default as SkipToSelectedBlock } from './skip-to-selected-block'; +export { default as Warning } from './warning'; +export { default as WritingFlow } from './writing-flow'; + +// State Related Components export { default as BlockEditorProvider } from './provider'; diff --git a/packages/editor/src/components/inner-blocks/README.md b/packages/block-editor/src/components/inner-blocks/README.md similarity index 98% rename from packages/editor/src/components/inner-blocks/README.md rename to packages/block-editor/src/components/inner-blocks/README.md index 040b0d463861c..8904d4b5cceba 100644 --- a/packages/editor/src/components/inner-blocks/README.md +++ b/packages/block-editor/src/components/inner-blocks/README.md @@ -11,7 +11,7 @@ In a block's `edit` implementation, render `InnerBlocks`. Then, in the `save` im ```jsx import { registerBlockType } from '@wordpress/blocks'; -import { InnerBlocks } from '@wordpress/editor'; +import { InnerBlocks } from '@wordpress/block-editor'; registerBlockType( 'my-plugin/my-block', { // ... diff --git a/packages/editor/src/components/inner-blocks/index.js b/packages/block-editor/src/components/inner-blocks/index.js similarity index 100% rename from packages/editor/src/components/inner-blocks/index.js rename to packages/block-editor/src/components/inner-blocks/index.js diff --git a/packages/editor/src/components/inner-blocks/style.scss b/packages/block-editor/src/components/inner-blocks/style.scss similarity index 100% rename from packages/editor/src/components/inner-blocks/style.scss rename to packages/block-editor/src/components/inner-blocks/style.scss diff --git a/packages/editor/src/components/inner-blocks/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/inner-blocks/test/__snapshots__/index.js.snap similarity index 100% rename from packages/editor/src/components/inner-blocks/test/__snapshots__/index.js.snap rename to packages/block-editor/src/components/inner-blocks/test/__snapshots__/index.js.snap diff --git a/packages/editor/src/components/inner-blocks/test/index.js b/packages/block-editor/src/components/inner-blocks/test/index.js similarity index 100% rename from packages/editor/src/components/inner-blocks/test/index.js rename to packages/block-editor/src/components/inner-blocks/test/index.js diff --git a/packages/editor/src/components/inserter-list-item/index.js b/packages/block-editor/src/components/inserter-list-item/index.js similarity index 100% rename from packages/editor/src/components/inserter-list-item/index.js rename to packages/block-editor/src/components/inserter-list-item/index.js diff --git a/packages/editor/src/components/inserter-list-item/style.scss b/packages/block-editor/src/components/inserter-list-item/style.scss similarity index 100% rename from packages/editor/src/components/inserter-list-item/style.scss rename to packages/block-editor/src/components/inserter-list-item/style.scss diff --git a/packages/editor/src/components/inserter-with-shortcuts/index.js b/packages/block-editor/src/components/inserter-with-shortcuts/index.js similarity index 100% rename from packages/editor/src/components/inserter-with-shortcuts/index.js rename to packages/block-editor/src/components/inserter-with-shortcuts/index.js diff --git a/packages/editor/src/components/inserter-with-shortcuts/style.scss b/packages/block-editor/src/components/inserter-with-shortcuts/style.scss similarity index 100% rename from packages/editor/src/components/inserter-with-shortcuts/style.scss rename to packages/block-editor/src/components/inserter-with-shortcuts/style.scss diff --git a/packages/editor/src/components/inserter/child-blocks.js b/packages/block-editor/src/components/inserter/child-blocks.js similarity index 100% rename from packages/editor/src/components/inserter/child-blocks.js rename to packages/block-editor/src/components/inserter/child-blocks.js diff --git a/packages/editor/src/components/inserter/index.js b/packages/block-editor/src/components/inserter/index.js similarity index 100% rename from packages/editor/src/components/inserter/index.js rename to packages/block-editor/src/components/inserter/index.js diff --git a/packages/editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js similarity index 100% rename from packages/editor/src/components/inserter/menu.js rename to packages/block-editor/src/components/inserter/menu.js diff --git a/packages/editor/src/components/inserter/style.scss b/packages/block-editor/src/components/inserter/style.scss similarity index 100% rename from packages/editor/src/components/inserter/style.scss rename to packages/block-editor/src/components/inserter/style.scss diff --git a/packages/editor/src/components/inserter/test/menu.js b/packages/block-editor/src/components/inserter/test/menu.js similarity index 100% rename from packages/editor/src/components/inserter/test/menu.js rename to packages/block-editor/src/components/inserter/test/menu.js diff --git a/packages/editor/src/components/inspector-advanced-controls/index.js b/packages/block-editor/src/components/inspector-advanced-controls/index.js similarity index 100% rename from packages/editor/src/components/inspector-advanced-controls/index.js rename to packages/block-editor/src/components/inspector-advanced-controls/index.js diff --git a/packages/editor/src/components/inspector-controls/README.md b/packages/block-editor/src/components/inspector-controls/README.md similarity index 100% rename from packages/editor/src/components/inspector-controls/README.md rename to packages/block-editor/src/components/inspector-controls/README.md diff --git a/packages/editor/src/components/inspector-controls/index.js b/packages/block-editor/src/components/inspector-controls/index.js similarity index 100% rename from packages/editor/src/components/inspector-controls/index.js rename to packages/block-editor/src/components/inspector-controls/index.js diff --git a/packages/editor/src/components/media-placeholder/README.md b/packages/block-editor/src/components/media-placeholder/README.md similarity index 100% rename from packages/editor/src/components/media-placeholder/README.md rename to packages/block-editor/src/components/media-placeholder/README.md diff --git a/packages/editor/src/components/media-placeholder/index.js b/packages/block-editor/src/components/media-placeholder/index.js similarity index 100% rename from packages/editor/src/components/media-placeholder/index.js rename to packages/block-editor/src/components/media-placeholder/index.js diff --git a/packages/editor/src/components/media-placeholder/index.native.js b/packages/block-editor/src/components/media-placeholder/index.native.js similarity index 100% rename from packages/editor/src/components/media-placeholder/index.native.js rename to packages/block-editor/src/components/media-placeholder/index.native.js diff --git a/packages/editor/src/components/media-placeholder/style.scss b/packages/block-editor/src/components/media-placeholder/style.scss similarity index 100% rename from packages/editor/src/components/media-placeholder/style.scss rename to packages/block-editor/src/components/media-placeholder/style.scss diff --git a/packages/editor/src/components/media-placeholder/styles.native.scss b/packages/block-editor/src/components/media-placeholder/styles.native.scss similarity index 100% rename from packages/editor/src/components/media-placeholder/styles.native.scss rename to packages/block-editor/src/components/media-placeholder/styles.native.scss diff --git a/packages/editor/src/components/media-placeholder/test/index.js b/packages/block-editor/src/components/media-placeholder/test/index.js similarity index 100% rename from packages/editor/src/components/media-placeholder/test/index.js rename to packages/block-editor/src/components/media-placeholder/test/index.js diff --git a/packages/editor/src/components/media-upload/README.md b/packages/block-editor/src/components/media-upload/README.md similarity index 97% rename from packages/editor/src/components/media-upload/README.md rename to packages/block-editor/src/components/media-upload/README.md index 74567c8694205..eb2f75cbdd8af 100644 --- a/packages/editor/src/components/media-upload/README.md +++ b/packages/block-editor/src/components/media-upload/README.md @@ -28,7 +28,7 @@ To make sure the current user has Upload permissions, you need to wrap the Media ```jsx import { Button } from '@wordpress/components'; -import { MediaUpload, MediaUploadCheck } from '@wordpress/editor'; +import { MediaUpload, MediaUploadCheck } from '@wordpress/block-editor'; const ALLOWED_MEDIA_TYPES = [ 'audio' ]; diff --git a/packages/editor/src/components/media-upload/check.js b/packages/block-editor/src/components/media-upload/check.js similarity index 100% rename from packages/editor/src/components/media-upload/check.js rename to packages/block-editor/src/components/media-upload/check.js diff --git a/packages/editor/src/components/media-upload/index.js b/packages/block-editor/src/components/media-upload/index.js similarity index 100% rename from packages/editor/src/components/media-upload/index.js rename to packages/block-editor/src/components/media-upload/index.js diff --git a/packages/editor/src/components/multi-select-scroll-into-view/index.js b/packages/block-editor/src/components/multi-select-scroll-into-view/index.js similarity index 100% rename from packages/editor/src/components/multi-select-scroll-into-view/index.js rename to packages/block-editor/src/components/multi-select-scroll-into-view/index.js diff --git a/packages/editor/src/components/multi-selection-inspector/index.js b/packages/block-editor/src/components/multi-selection-inspector/index.js similarity index 100% rename from packages/editor/src/components/multi-selection-inspector/index.js rename to packages/block-editor/src/components/multi-selection-inspector/index.js diff --git a/packages/editor/src/components/multi-selection-inspector/style.scss b/packages/block-editor/src/components/multi-selection-inspector/style.scss similarity index 100% rename from packages/editor/src/components/multi-selection-inspector/style.scss rename to packages/block-editor/src/components/multi-selection-inspector/style.scss diff --git a/packages/editor/src/components/navigable-toolbar/index.js b/packages/block-editor/src/components/navigable-toolbar/index.js similarity index 100% rename from packages/editor/src/components/navigable-toolbar/index.js rename to packages/block-editor/src/components/navigable-toolbar/index.js diff --git a/packages/editor/src/components/observe-typing/README.md b/packages/block-editor/src/components/observe-typing/README.md similarity index 100% rename from packages/editor/src/components/observe-typing/README.md rename to packages/block-editor/src/components/observe-typing/README.md diff --git a/packages/editor/src/components/observe-typing/index.js b/packages/block-editor/src/components/observe-typing/index.js similarity index 100% rename from packages/editor/src/components/observe-typing/index.js rename to packages/block-editor/src/components/observe-typing/index.js diff --git a/packages/editor/src/components/panel-color-settings/index.js b/packages/block-editor/src/components/panel-color-settings/index.js similarity index 100% rename from packages/editor/src/components/panel-color-settings/index.js rename to packages/block-editor/src/components/panel-color-settings/index.js diff --git a/packages/editor/src/components/panel-color-settings/style.scss b/packages/block-editor/src/components/panel-color-settings/style.scss similarity index 100% rename from packages/editor/src/components/panel-color-settings/style.scss rename to packages/block-editor/src/components/panel-color-settings/style.scss diff --git a/packages/editor/src/components/panel-color-settings/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/panel-color-settings/test/__snapshots__/index.js.snap similarity index 100% rename from packages/editor/src/components/panel-color-settings/test/__snapshots__/index.js.snap rename to packages/block-editor/src/components/panel-color-settings/test/__snapshots__/index.js.snap diff --git a/packages/editor/src/components/panel-color-settings/test/index.js b/packages/block-editor/src/components/panel-color-settings/test/index.js similarity index 100% rename from packages/editor/src/components/panel-color-settings/test/index.js rename to packages/block-editor/src/components/panel-color-settings/test/index.js diff --git a/packages/editor/src/components/plain-text/README.md b/packages/block-editor/src/components/plain-text/README.md similarity index 100% rename from packages/editor/src/components/plain-text/README.md rename to packages/block-editor/src/components/plain-text/README.md diff --git a/packages/editor/src/components/plain-text/index.js b/packages/block-editor/src/components/plain-text/index.js similarity index 100% rename from packages/editor/src/components/plain-text/index.js rename to packages/block-editor/src/components/plain-text/index.js diff --git a/packages/editor/src/components/plain-text/index.native.js b/packages/block-editor/src/components/plain-text/index.native.js similarity index 100% rename from packages/editor/src/components/plain-text/index.native.js rename to packages/block-editor/src/components/plain-text/index.native.js diff --git a/packages/editor/src/components/plain-text/style.native.scss b/packages/block-editor/src/components/plain-text/style.native.scss similarity index 100% rename from packages/editor/src/components/plain-text/style.native.scss rename to packages/block-editor/src/components/plain-text/style.native.scss diff --git a/packages/editor/src/components/plain-text/style.scss b/packages/block-editor/src/components/plain-text/style.scss similarity index 100% rename from packages/editor/src/components/plain-text/style.scss rename to packages/block-editor/src/components/plain-text/style.scss diff --git a/packages/editor/src/components/preserve-scroll-in-reorder/index.js b/packages/block-editor/src/components/preserve-scroll-in-reorder/index.js similarity index 100% rename from packages/editor/src/components/preserve-scroll-in-reorder/index.js rename to packages/block-editor/src/components/preserve-scroll-in-reorder/index.js diff --git a/packages/editor/src/components/rich-text/README.md b/packages/block-editor/src/components/rich-text/README.md similarity index 100% rename from packages/editor/src/components/rich-text/README.md rename to packages/block-editor/src/components/rich-text/README.md diff --git a/packages/editor/src/components/rich-text/aria.js b/packages/block-editor/src/components/rich-text/aria.js similarity index 100% rename from packages/editor/src/components/rich-text/aria.js rename to packages/block-editor/src/components/rich-text/aria.js diff --git a/packages/editor/src/components/rich-text/editable.js b/packages/block-editor/src/components/rich-text/editable.js similarity index 100% rename from packages/editor/src/components/rich-text/editable.js rename to packages/block-editor/src/components/rich-text/editable.js diff --git a/packages/editor/src/components/rich-text/format-edit.js b/packages/block-editor/src/components/rich-text/format-edit.js similarity index 100% rename from packages/editor/src/components/rich-text/format-edit.js rename to packages/block-editor/src/components/rich-text/format-edit.js diff --git a/packages/editor/src/components/rich-text/format-toolbar/index.js b/packages/block-editor/src/components/rich-text/format-toolbar/index.js similarity index 100% rename from packages/editor/src/components/rich-text/format-toolbar/index.js rename to packages/block-editor/src/components/rich-text/format-toolbar/index.js diff --git a/packages/editor/src/components/rich-text/format-toolbar/index.native.js b/packages/block-editor/src/components/rich-text/format-toolbar/index.native.js similarity index 100% rename from packages/editor/src/components/rich-text/format-toolbar/index.native.js rename to packages/block-editor/src/components/rich-text/format-toolbar/index.native.js diff --git a/packages/editor/src/components/rich-text/format-toolbar/style.scss b/packages/block-editor/src/components/rich-text/format-toolbar/style.scss similarity index 100% rename from packages/editor/src/components/rich-text/format-toolbar/style.scss rename to packages/block-editor/src/components/rich-text/format-toolbar/style.scss diff --git a/packages/editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js similarity index 100% rename from packages/editor/src/components/rich-text/index.js rename to packages/block-editor/src/components/rich-text/index.js diff --git a/packages/editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js similarity index 99% rename from packages/editor/src/components/rich-text/index.native.js rename to packages/block-editor/src/components/rich-text/index.native.js index 1e3de4f168086..55bf4580e5e70 100644 --- a/packages/editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -9,7 +9,7 @@ import { View, Platform } from 'react-native'; */ import { Component, RawHTML } from '@wordpress/element'; import { withInstanceId, compose } from '@wordpress/compose'; -import { BlockFormatControls } from '@wordpress/editor'; +import { BlockFormatControls } from '@wordpress/block-editor'; import { withSelect } from '@wordpress/data'; import { applyFormat, diff --git a/packages/editor/src/components/rich-text/input-event.js b/packages/block-editor/src/components/rich-text/input-event.js similarity index 100% rename from packages/editor/src/components/rich-text/input-event.js rename to packages/block-editor/src/components/rich-text/input-event.js diff --git a/packages/editor/src/components/rich-text/input-event.native.js b/packages/block-editor/src/components/rich-text/input-event.native.js similarity index 100% rename from packages/editor/src/components/rich-text/input-event.native.js rename to packages/block-editor/src/components/rich-text/input-event.native.js diff --git a/packages/editor/src/components/rich-text/list-edit.js b/packages/block-editor/src/components/rich-text/list-edit.js similarity index 100% rename from packages/editor/src/components/rich-text/list-edit.js rename to packages/block-editor/src/components/rich-text/list-edit.js diff --git a/packages/editor/src/components/rich-text/patterns.js b/packages/block-editor/src/components/rich-text/patterns.js similarity index 100% rename from packages/editor/src/components/rich-text/patterns.js rename to packages/block-editor/src/components/rich-text/patterns.js diff --git a/packages/editor/src/components/rich-text/remove-browser-shortcuts.js b/packages/block-editor/src/components/rich-text/remove-browser-shortcuts.js similarity index 100% rename from packages/editor/src/components/rich-text/remove-browser-shortcuts.js rename to packages/block-editor/src/components/rich-text/remove-browser-shortcuts.js diff --git a/packages/editor/src/components/rich-text/shortcut.js b/packages/block-editor/src/components/rich-text/shortcut.js similarity index 100% rename from packages/editor/src/components/rich-text/shortcut.js rename to packages/block-editor/src/components/rich-text/shortcut.js diff --git a/packages/editor/src/components/rich-text/shortcut.native.js b/packages/block-editor/src/components/rich-text/shortcut.native.js similarity index 100% rename from packages/editor/src/components/rich-text/shortcut.native.js rename to packages/block-editor/src/components/rich-text/shortcut.native.js diff --git a/packages/editor/src/components/rich-text/style.native.scss b/packages/block-editor/src/components/rich-text/style.native.scss similarity index 100% rename from packages/editor/src/components/rich-text/style.native.scss rename to packages/block-editor/src/components/rich-text/style.native.scss diff --git a/packages/editor/src/components/rich-text/style.scss b/packages/block-editor/src/components/rich-text/style.scss similarity index 100% rename from packages/editor/src/components/rich-text/style.scss rename to packages/block-editor/src/components/rich-text/style.scss diff --git a/packages/editor/src/components/rich-text/test/index.js b/packages/block-editor/src/components/rich-text/test/index.js similarity index 100% rename from packages/editor/src/components/rich-text/test/index.js rename to packages/block-editor/src/components/rich-text/test/index.js diff --git a/packages/editor/src/components/rich-text/toolbar-button.js b/packages/block-editor/src/components/rich-text/toolbar-button.js similarity index 100% rename from packages/editor/src/components/rich-text/toolbar-button.js rename to packages/block-editor/src/components/rich-text/toolbar-button.js diff --git a/packages/editor/src/components/skip-to-selected-block/index.js b/packages/block-editor/src/components/skip-to-selected-block/index.js similarity index 100% rename from packages/editor/src/components/skip-to-selected-block/index.js rename to packages/block-editor/src/components/skip-to-selected-block/index.js diff --git a/packages/editor/src/components/skip-to-selected-block/style.scss b/packages/block-editor/src/components/skip-to-selected-block/style.scss similarity index 100% rename from packages/editor/src/components/skip-to-selected-block/style.scss rename to packages/block-editor/src/components/skip-to-selected-block/style.scss diff --git a/packages/editor/src/components/url-input/README.md b/packages/block-editor/src/components/url-input/README.md similarity index 100% rename from packages/editor/src/components/url-input/README.md rename to packages/block-editor/src/components/url-input/README.md diff --git a/packages/editor/src/components/url-input/button.js b/packages/block-editor/src/components/url-input/button.js similarity index 100% rename from packages/editor/src/components/url-input/button.js rename to packages/block-editor/src/components/url-input/button.js diff --git a/packages/editor/src/components/url-input/index.js b/packages/block-editor/src/components/url-input/index.js similarity index 100% rename from packages/editor/src/components/url-input/index.js rename to packages/block-editor/src/components/url-input/index.js diff --git a/packages/editor/src/components/url-input/index.native.js b/packages/block-editor/src/components/url-input/index.native.js similarity index 100% rename from packages/editor/src/components/url-input/index.native.js rename to packages/block-editor/src/components/url-input/index.native.js diff --git a/packages/editor/src/components/url-input/style.scss b/packages/block-editor/src/components/url-input/style.scss similarity index 100% rename from packages/editor/src/components/url-input/style.scss rename to packages/block-editor/src/components/url-input/style.scss diff --git a/packages/editor/src/components/url-input/test/button.js b/packages/block-editor/src/components/url-input/test/button.js similarity index 100% rename from packages/editor/src/components/url-input/test/button.js rename to packages/block-editor/src/components/url-input/test/button.js diff --git a/packages/editor/src/components/url-popover/README.md b/packages/block-editor/src/components/url-popover/README.md similarity index 98% rename from packages/editor/src/components/url-popover/README.md rename to packages/block-editor/src/components/url-popover/README.md index da0fc4dbefb21..4705ecadfe437 100644 --- a/packages/editor/src/components/url-popover/README.md +++ b/packages/block-editor/src/components/url-popover/README.md @@ -10,7 +10,7 @@ The component will be rendered adjacent to its parent. ```jsx import { Fragment } from '@wordpress/elements'; import { ToggleControl, IconButton, Button } from '@wordpress/components'; -import { URLPopover } from '@wordpress/editor'; +import { URLPopover } from '@wordpress/block-editor'; class MyURLPopover extends Component { constructor() { diff --git a/packages/editor/src/components/url-popover/index.js b/packages/block-editor/src/components/url-popover/index.js similarity index 100% rename from packages/editor/src/components/url-popover/index.js rename to packages/block-editor/src/components/url-popover/index.js diff --git a/packages/editor/src/components/url-popover/style.scss b/packages/block-editor/src/components/url-popover/style.scss similarity index 100% rename from packages/editor/src/components/url-popover/style.scss rename to packages/block-editor/src/components/url-popover/style.scss diff --git a/packages/editor/src/components/url-popover/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/url-popover/test/__snapshots__/index.js.snap similarity index 100% rename from packages/editor/src/components/url-popover/test/__snapshots__/index.js.snap rename to packages/block-editor/src/components/url-popover/test/__snapshots__/index.js.snap diff --git a/packages/editor/src/components/url-popover/test/index.js b/packages/block-editor/src/components/url-popover/test/index.js similarity index 100% rename from packages/editor/src/components/url-popover/test/index.js rename to packages/block-editor/src/components/url-popover/test/index.js diff --git a/packages/editor/src/components/warning/index.js b/packages/block-editor/src/components/warning/index.js similarity index 100% rename from packages/editor/src/components/warning/index.js rename to packages/block-editor/src/components/warning/index.js diff --git a/packages/editor/src/components/warning/style.scss b/packages/block-editor/src/components/warning/style.scss similarity index 100% rename from packages/editor/src/components/warning/style.scss rename to packages/block-editor/src/components/warning/style.scss diff --git a/packages/editor/src/components/warning/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/warning/test/__snapshots__/index.js.snap similarity index 100% rename from packages/editor/src/components/warning/test/__snapshots__/index.js.snap rename to packages/block-editor/src/components/warning/test/__snapshots__/index.js.snap diff --git a/packages/editor/src/components/warning/test/index.js b/packages/block-editor/src/components/warning/test/index.js similarity index 100% rename from packages/editor/src/components/warning/test/index.js rename to packages/block-editor/src/components/warning/test/index.js diff --git a/packages/editor/src/components/writing-flow/index.js b/packages/block-editor/src/components/writing-flow/index.js similarity index 100% rename from packages/editor/src/components/writing-flow/index.js rename to packages/block-editor/src/components/writing-flow/index.js diff --git a/packages/editor/src/components/writing-flow/style.scss b/packages/block-editor/src/components/writing-flow/style.scss similarity index 100% rename from packages/editor/src/components/writing-flow/style.scss rename to packages/block-editor/src/components/writing-flow/style.scss diff --git a/packages/editor/src/components/writing-flow/test/index.js b/packages/block-editor/src/components/writing-flow/test/index.js similarity index 100% rename from packages/editor/src/components/writing-flow/test/index.js rename to packages/block-editor/src/components/writing-flow/test/index.js diff --git a/packages/editor/src/hooks/align.js b/packages/block-editor/src/hooks/align.js similarity index 100% rename from packages/editor/src/hooks/align.js rename to packages/block-editor/src/hooks/align.js diff --git a/packages/editor/src/hooks/anchor.js b/packages/block-editor/src/hooks/anchor.js similarity index 100% rename from packages/editor/src/hooks/anchor.js rename to packages/block-editor/src/hooks/anchor.js diff --git a/packages/editor/src/hooks/custom-class-name.js b/packages/block-editor/src/hooks/custom-class-name.js similarity index 100% rename from packages/editor/src/hooks/custom-class-name.js rename to packages/block-editor/src/hooks/custom-class-name.js diff --git a/packages/editor/src/hooks/custom-class-name.native.js b/packages/block-editor/src/hooks/custom-class-name.native.js similarity index 100% rename from packages/editor/src/hooks/custom-class-name.native.js rename to packages/block-editor/src/hooks/custom-class-name.native.js diff --git a/packages/editor/src/hooks/generated-class-name.js b/packages/block-editor/src/hooks/generated-class-name.js similarity index 100% rename from packages/editor/src/hooks/generated-class-name.js rename to packages/block-editor/src/hooks/generated-class-name.js diff --git a/packages/block-editor/src/hooks/index.js b/packages/block-editor/src/hooks/index.js new file mode 100644 index 0000000000000..7bd9f390390c4 --- /dev/null +++ b/packages/block-editor/src/hooks/index.js @@ -0,0 +1,7 @@ +/** + * Internal dependencies + */ +import './align'; +import './anchor'; +import './custom-class-name'; +import './generated-class-name'; diff --git a/packages/block-editor/src/hooks/index.native.js b/packages/block-editor/src/hooks/index.native.js new file mode 100644 index 0000000000000..d85f61d596d93 --- /dev/null +++ b/packages/block-editor/src/hooks/index.native.js @@ -0,0 +1,5 @@ +/** + * Internal dependencies + */ +import './custom-class-name'; +import './generated-class-name'; diff --git a/packages/editor/src/hooks/test/align.js b/packages/block-editor/src/hooks/test/align.js similarity index 100% rename from packages/editor/src/hooks/test/align.js rename to packages/block-editor/src/hooks/test/align.js diff --git a/packages/editor/src/hooks/test/anchor.js b/packages/block-editor/src/hooks/test/anchor.js similarity index 100% rename from packages/editor/src/hooks/test/anchor.js rename to packages/block-editor/src/hooks/test/anchor.js diff --git a/packages/editor/src/hooks/test/custom-class-name.js b/packages/block-editor/src/hooks/test/custom-class-name.js similarity index 100% rename from packages/editor/src/hooks/test/custom-class-name.js rename to packages/block-editor/src/hooks/test/custom-class-name.js diff --git a/packages/editor/src/hooks/test/generated-class-name.js b/packages/block-editor/src/hooks/test/generated-class-name.js similarity index 100% rename from packages/editor/src/hooks/test/generated-class-name.js rename to packages/block-editor/src/hooks/test/generated-class-name.js diff --git a/packages/block-editor/src/index.js b/packages/block-editor/src/index.js index 58e920befd5ea..fe526a2352a42 100644 --- a/packages/block-editor/src/index.js +++ b/packages/block-editor/src/index.js @@ -2,11 +2,15 @@ * WordPress dependencies */ import '@wordpress/blocks'; +import '@wordpress/core-data'; +import '@wordpress/rich-text'; +import '@wordpress/viewport'; /** * Internal dependencies */ import './store'; +import './hooks'; export * from './components'; diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss new file mode 100644 index 0000000000000..deedea8be7ec1 --- /dev/null +++ b/packages/block-editor/src/style.scss @@ -0,0 +1,33 @@ +@import "./components/block-drop-zone/style.scss"; +@import "./components/block-icon/style.scss"; +@import "./components/block-inspector/style.scss"; +@import "./components/block-list/style.scss"; +@import "./components/block-list-appender/style.scss"; +@import "./components/block-compare/style.scss"; +@import "./components/block-mover/style.scss"; +@import "./components/block-navigation/style.scss"; +@import "./components/block-preview/style.scss"; +@import "./components/block-settings-menu/style.scss"; +@import "./components/block-styles/style.scss"; +@import "./components/block-switcher/style.scss"; +@import "./components/block-toolbar/style.scss"; +@import "./components/block-types-list/style.scss"; +@import "./components/color-palette/control.scss"; +@import "./components/contrast-checker/style.scss"; +@import "./components/default-block-appender/style.scss"; +@import "./components/font-sizes/style.scss"; +@import "./components/inner-blocks/style.scss"; +@import "./components/inserter-with-shortcuts/style.scss"; +@import "./components/inserter/style.scss"; +@import "./components/inserter-list-item/style.scss"; +@import "./components/media-placeholder/style.scss"; +@import "./components/multi-selection-inspector/style.scss"; +@import "./components/panel-color-settings/style.scss"; +@import "./components/plain-text/style.scss"; +@import "./components/rich-text/format-toolbar/style.scss"; +@import "./components/rich-text/style.scss"; +@import "./components/skip-to-selected-block/style.scss"; +@import "./components/url-input/style.scss"; +@import "./components/url-popover/style.scss"; +@import "./components/warning/style.scss"; +@import "./components/writing-flow/style.scss"; diff --git a/packages/editor/src/utils/dom.js b/packages/block-editor/src/utils/dom.js similarity index 100% rename from packages/editor/src/utils/dom.js rename to packages/block-editor/src/utils/dom.js diff --git a/packages/editor/src/utils/test/dom.js b/packages/block-editor/src/utils/test/dom.js similarity index 100% rename from packages/editor/src/utils/test/dom.js rename to packages/block-editor/src/utils/test/dom.js diff --git a/packages/block-library/src/archives/edit.js b/packages/block-library/src/archives/edit.js index b59f4dd04d930..51f9df96f147f 100644 --- a/packages/block-library/src/archives/edit.js +++ b/packages/block-library/src/archives/edit.js @@ -8,16 +8,12 @@ import { Disabled, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ import { InspectorControls, BlockAlignmentToolbar, BlockControls, - ServerSideRender, -} from '@wordpress/editor'; +} from '@wordpress/block-editor'; +import { ServerSideRender } from '@wordpress/editor'; export default function ArchivesEdit( { attributes, setAttributes } ) { const { align, showPostCounts, displayAsDropdown } = attributes; diff --git a/packages/block-library/src/audio/edit.js b/packages/block-library/src/audio/edit.js index 9e85cea5de22d..d222b42ae6a64 100644 --- a/packages/block-library/src/audio/edit.js +++ b/packages/block-library/src/audio/edit.js @@ -17,8 +17,8 @@ import { InspectorControls, MediaPlaceholder, RichText, - mediaUpload, -} from '@wordpress/editor'; +} from '@wordpress/block-editor'; +import { mediaUpload } from '@wordpress/editor'; import { Component, Fragment } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; diff --git a/packages/block-library/src/audio/index.js b/packages/block-library/src/audio/index.js index 1e3d95da29b45..7528ac483311e 100644 --- a/packages/block-library/src/audio/index.js +++ b/packages/block-library/src/audio/index.js @@ -3,7 +3,7 @@ */ import { createBlobURL } from '@wordpress/blob'; import { createBlock } from '@wordpress/blocks'; -import { RichText } from '@wordpress/editor'; +import { RichText } from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; /** diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js index 5db7bda7c7f60..722aad97c9bfc 100644 --- a/packages/block-library/src/block/edit.js +++ b/packages/block-library/src/block/edit.js @@ -10,7 +10,7 @@ import { Component, Fragment } from '@wordpress/element'; import { Placeholder, Spinner, Disabled } from '@wordpress/components'; import { withSelect, withDispatch } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; -import { BlockEdit } from '@wordpress/editor'; +import { BlockEdit } from '@wordpress/block-editor'; import { compose } from '@wordpress/compose'; /** diff --git a/packages/block-library/src/button/edit.js b/packages/block-library/src/button/edit.js index 860d6a1fb64b6..1cf0c1e43d049 100644 --- a/packages/block-library/src/button/edit.js +++ b/packages/block-library/src/button/edit.js @@ -24,7 +24,7 @@ import { InspectorControls, withColors, PanelColorSettings, -} from '@wordpress/editor'; +} from '@wordpress/block-editor'; const { getComputedStyle } = window; diff --git a/packages/block-library/src/button/index.js b/packages/block-library/src/button/index.js index cbc60718bb716..d736c954b9982 100644 --- a/packages/block-library/src/button/index.js +++ b/packages/block-library/src/button/index.js @@ -12,7 +12,7 @@ import { __, _x } from '@wordpress/i18n'; import { RichText, getColorClassName, -} from '@wordpress/editor'; +} from '@wordpress/block-editor'; /** * Internal dependencies diff --git a/packages/block-library/src/categories/edit.js b/packages/block-library/src/categories/edit.js index aee240e7384e6..69e70a2596f25 100644 --- a/packages/block-library/src/categories/edit.js +++ b/packages/block-library/src/categories/edit.js @@ -9,7 +9,7 @@ import { times, unescape } from 'lodash'; import { PanelBody, Placeholder, Spinner, ToggleControl } from '@wordpress/components'; import { compose, withInstanceId } from '@wordpress/compose'; import { withSelect } from '@wordpress/data'; -import { InspectorControls } from '@wordpress/editor'; +import { InspectorControls } from '@wordpress/block-editor'; import { Component, Fragment } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; diff --git a/packages/block-library/src/code/edit.js b/packages/block-library/src/code/edit.js index b41ba7327f15d..5aa4b337bd3f1 100644 --- a/packages/block-library/src/code/edit.js +++ b/packages/block-library/src/code/edit.js @@ -6,7 +6,7 @@ import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import { PlainText } from '@wordpress/editor'; +import { PlainText } from '@wordpress/block-editor'; export default function CodeEdit( { attributes, setAttributes, className } ) { return ( diff --git a/packages/block-library/src/code/edit.native.js b/packages/block-library/src/code/edit.native.js index c072c01df5004..264814f760dee 100644 --- a/packages/block-library/src/code/edit.native.js +++ b/packages/block-library/src/code/edit.native.js @@ -11,7 +11,7 @@ import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import { PlainText } from '@wordpress/editor'; +import { PlainText } from '@wordpress/block-editor'; /** * Block code style diff --git a/packages/block-library/src/columns/column.js b/packages/block-library/src/columns/column.js index 802e2c15082b4..b593f3ee06134 100644 --- a/packages/block-library/src/columns/column.js +++ b/packages/block-library/src/columns/column.js @@ -3,7 +3,7 @@ */ import { Path, SVG } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { InnerBlocks } from '@wordpress/editor'; +import { InnerBlocks } from '@wordpress/block-editor'; export const name = 'core/column'; diff --git a/packages/block-library/src/columns/index.js b/packages/block-library/src/columns/index.js index 374650b9ab13b..7d28794e8a776 100644 --- a/packages/block-library/src/columns/index.js +++ b/packages/block-library/src/columns/index.js @@ -15,7 +15,7 @@ import { createBlock } from '@wordpress/blocks'; import { InspectorControls, InnerBlocks, -} from '@wordpress/editor'; +} from '@wordpress/block-editor'; /** * Allowed blocks constant is passed to InnerBlocks precisely as specified here. diff --git a/packages/block-library/src/cover/index.js b/packages/block-library/src/cover/index.js index cd39eaf9e496a..de5a34699e414 100644 --- a/packages/block-library/src/cover/index.js +++ b/packages/block-library/src/cover/index.js @@ -12,7 +12,7 @@ import { InnerBlocks, RichText, getColorClassName, -} from '@wordpress/editor'; +} from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; /** diff --git a/packages/block-library/src/embed/embed-controls.js b/packages/block-library/src/embed/embed-controls.js index e16b91ba7552f..f768f3fa52903 100644 --- a/packages/block-library/src/embed/embed-controls.js +++ b/packages/block-library/src/embed/embed-controls.js @@ -4,7 +4,7 @@ import { __ } from '@wordpress/i18n'; import { Fragment } from '@wordpress/element'; import { IconButton, Toolbar, PanelBody, ToggleControl } from '@wordpress/components'; -import { BlockControls, InspectorControls } from '@wordpress/editor'; +import { BlockControls, InspectorControls } from '@wordpress/block-editor'; const EmbedControls = ( props ) => { const { diff --git a/packages/block-library/src/embed/embed-placeholder.js b/packages/block-library/src/embed/embed-placeholder.js index 9b2176d5ccf3c..b532d7b1a84a7 100644 --- a/packages/block-library/src/embed/embed-placeholder.js +++ b/packages/block-library/src/embed/embed-placeholder.js @@ -3,7 +3,7 @@ */ import { __, _x } from '@wordpress/i18n'; import { Button, Placeholder } from '@wordpress/components'; -import { BlockIcon } from '@wordpress/editor'; +import { BlockIcon } from '@wordpress/block-editor'; const EmbedPlaceholder = ( props ) => { const { icon, label, value, onSubmit, onChange, cannotEmbed, fallback, tryAgain } = props; diff --git a/packages/block-library/src/embed/embed-preview.js b/packages/block-library/src/embed/embed-preview.js index 1b1f2dfb93787..eeda8d33f9115 100644 --- a/packages/block-library/src/embed/embed-preview.js +++ b/packages/block-library/src/embed/embed-preview.js @@ -16,7 +16,7 @@ import classnames from 'classnames/dedupe'; */ import { __, sprintf } from '@wordpress/i18n'; import { Placeholder, SandBox } from '@wordpress/components'; -import { RichText, BlockIcon } from '@wordpress/editor'; +import { RichText, BlockIcon } from '@wordpress/block-editor'; import { Component } from '@wordpress/element'; /** diff --git a/packages/block-library/src/embed/settings.js b/packages/block-library/src/embed/settings.js index 7995939c325a3..17d97cee43da6 100644 --- a/packages/block-library/src/embed/settings.js +++ b/packages/block-library/src/embed/settings.js @@ -13,7 +13,7 @@ import classnames from 'classnames/dedupe'; */ import { __ } from '@wordpress/i18n'; import { compose } from '@wordpress/compose'; -import { RichText } from '@wordpress/editor'; +import { RichText } from '@wordpress/block-editor'; import { withSelect, withDispatch } from '@wordpress/data'; const embedAttributes = { diff --git a/packages/block-library/src/file/edit.js b/packages/block-library/src/file/edit.js index 9dca21987bc8c..3ce7211eceb85 100644 --- a/packages/block-library/src/file/edit.js +++ b/packages/block-library/src/file/edit.js @@ -26,8 +26,8 @@ import { MediaUploadCheck, MediaPlaceholder, RichText, - mediaUpload, -} from '@wordpress/editor'; +} from '@wordpress/block-editor'; +import { mediaUpload } from '@wordpress/editor'; import { Component, Fragment } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; diff --git a/packages/block-library/src/file/index.js b/packages/block-library/src/file/index.js index 9ce871bcfa277..7a58074192e21 100644 --- a/packages/block-library/src/file/index.js +++ b/packages/block-library/src/file/index.js @@ -10,7 +10,7 @@ import { __, _x } from '@wordpress/i18n'; import { createBlobURL } from '@wordpress/blob'; import { createBlock } from '@wordpress/blocks'; import { select } from '@wordpress/data'; -import { RichText } from '@wordpress/editor'; +import { RichText } from '@wordpress/block-editor'; /** * Internal dependencies diff --git a/packages/block-library/src/file/inspector.js b/packages/block-library/src/file/inspector.js index 4c686ad5afe6d..6ed6747f4455c 100644 --- a/packages/block-library/src/file/inspector.js +++ b/packages/block-library/src/file/inspector.js @@ -8,7 +8,7 @@ import { ToggleControl, } from '@wordpress/components'; import { Fragment } from '@wordpress/element'; -import { InspectorControls } from '@wordpress/editor'; +import { InspectorControls } from '@wordpress/block-editor'; export default function FileBlockInspector( { hrefs, diff --git a/packages/block-library/src/gallery/edit.js b/packages/block-library/src/gallery/edit.js index 203403023abd2..868a6a083723d 100644 --- a/packages/block-library/src/gallery/edit.js +++ b/packages/block-library/src/gallery/edit.js @@ -24,8 +24,8 @@ import { MediaPlaceholder, MediaUpload, InspectorControls, - mediaUpload, -} from '@wordpress/editor'; +} from '@wordpress/block-editor'; +import { mediaUpload } from '@wordpress/editor'; import { Component, Fragment } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; diff --git a/packages/block-library/src/gallery/gallery-image.js b/packages/block-library/src/gallery/gallery-image.js index ff95dd1cc49cb..de18796926500 100644 --- a/packages/block-library/src/gallery/gallery-image.js +++ b/packages/block-library/src/gallery/gallery-image.js @@ -11,7 +11,7 @@ import { IconButton, Spinner } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { BACKSPACE, DELETE } from '@wordpress/keycodes'; import { withSelect } from '@wordpress/data'; -import { RichText } from '@wordpress/editor'; +import { RichText } from '@wordpress/block-editor'; import { isBlobURL } from '@wordpress/blob'; class GalleryImage extends Component { diff --git a/packages/block-library/src/gallery/index.js b/packages/block-library/src/gallery/index.js index 9e616efa3684f..4387f9a5e7083 100644 --- a/packages/block-library/src/gallery/index.js +++ b/packages/block-library/src/gallery/index.js @@ -8,7 +8,8 @@ import { filter, every, map, some } from 'lodash'; */ import { __ } from '@wordpress/i18n'; import { createBlock } from '@wordpress/blocks'; -import { RichText, mediaUpload } from '@wordpress/editor'; +import { RichText } from '@wordpress/block-editor'; +import { mediaUpload } from '@wordpress/editor'; import { createBlobURL } from '@wordpress/blob'; /** diff --git a/packages/block-library/src/heading/edit.js b/packages/block-library/src/heading/edit.js index 95b810c90c529..cf723896a55a0 100644 --- a/packages/block-library/src/heading/edit.js +++ b/packages/block-library/src/heading/edit.js @@ -10,7 +10,12 @@ import { __ } from '@wordpress/i18n'; import { Fragment } from '@wordpress/element'; import { PanelBody } from '@wordpress/components'; import { createBlock } from '@wordpress/blocks'; -import { RichText, BlockControls, InspectorControls, AlignmentToolbar } from '@wordpress/editor'; +import { + RichText, + BlockControls, + InspectorControls, + AlignmentToolbar, +} from '@wordpress/block-editor'; export default function HeadingEdit( { attributes, diff --git a/packages/block-library/src/heading/edit.native.js b/packages/block-library/src/heading/edit.native.js index d28021a0bbd99..7ead0d8bf074b 100644 --- a/packages/block-library/src/heading/edit.native.js +++ b/packages/block-library/src/heading/edit.native.js @@ -13,7 +13,7 @@ import { View } from 'react-native'; */ import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; -import { RichText, BlockControls } from '@wordpress/editor'; +import { RichText, BlockControls } from '@wordpress/block-editor'; import { createBlock } from '@wordpress/blocks'; /** diff --git a/packages/block-library/src/heading/index.js b/packages/block-library/src/heading/index.js index 288a72d35bc1b..f576098578568 100644 --- a/packages/block-library/src/heading/index.js +++ b/packages/block-library/src/heading/index.js @@ -12,7 +12,7 @@ import { getPhrasingContentSchema, getBlockAttributes, } from '@wordpress/blocks'; -import { RichText } from '@wordpress/editor'; +import { RichText } from '@wordpress/block-editor'; import { Path, SVG, diff --git a/packages/block-library/src/html/edit.js b/packages/block-library/src/html/edit.js index 0000654785a43..99df666bb55ec 100644 --- a/packages/block-library/src/html/edit.js +++ b/packages/block-library/src/html/edit.js @@ -3,7 +3,8 @@ */ import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; -import { BlockControls, PlainText, transformStyles } from '@wordpress/editor'; +import { BlockControls, PlainText } from '@wordpress/block-editor'; +import { transformStyles } from '@wordpress/editor'; import { Disabled, SandBox } from '@wordpress/components'; import { withSelect } from '@wordpress/data'; diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index f8308165923f1..51d2587591eda 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -40,8 +40,8 @@ import { MediaUpload, MediaUploadCheck, RichText, - mediaUpload, -} from '@wordpress/editor'; +} from '@wordpress/block-editor'; +import { mediaUpload } from '@wordpress/editor'; import { Component, Fragment } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; import { getPath } from '@wordpress/url'; diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index eabcdc9941a26..95eb712a83f8e 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -27,6 +27,8 @@ import { RichText, BlockControls, InspectorControls, +} from '@wordpress/block-editor'; +import { BottomSheet, Picker, } from '@wordpress/editor'; diff --git a/packages/block-library/src/image/index.js b/packages/block-library/src/image/index.js index 9c8d519425dfd..92b9af7ec9a42 100644 --- a/packages/block-library/src/image/index.js +++ b/packages/block-library/src/image/index.js @@ -12,7 +12,7 @@ import { getBlockAttributes, getPhrasingContentSchema, } from '@wordpress/blocks'; -import { RichText } from '@wordpress/editor'; +import { RichText } from '@wordpress/block-editor'; import { Fragment } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; diff --git a/packages/block-library/src/latest-comments/edit.js b/packages/block-library/src/latest-comments/edit.js index 7d4e65913f991..0f44f30b8ac5a 100644 --- a/packages/block-library/src/latest-comments/edit.js +++ b/packages/block-library/src/latest-comments/edit.js @@ -13,8 +13,8 @@ import { InspectorControls, BlockAlignmentToolbar, BlockControls, - ServerSideRender, -} from '@wordpress/editor'; +} from '@wordpress/block-editor'; +import { ServerSideRender } from '@wordpress/editor'; /** * Minimum number of comments a user can show using this block. diff --git a/packages/block-library/src/latest-posts/edit.js b/packages/block-library/src/latest-posts/edit.js index a275afda148ab..f5f8bc5d121d0 100644 --- a/packages/block-library/src/latest-posts/edit.js +++ b/packages/block-library/src/latest-posts/edit.js @@ -29,7 +29,7 @@ import { InspectorControls, BlockAlignmentToolbar, BlockControls, -} from '@wordpress/editor'; +} from '@wordpress/block-editor'; import { withSelect } from '@wordpress/data'; /** diff --git a/packages/block-library/src/list/index.js b/packages/block-library/src/list/index.js index 4137dcdf3a673..961d57535d044 100644 --- a/packages/block-library/src/list/index.js +++ b/packages/block-library/src/list/index.js @@ -12,7 +12,7 @@ import { getPhrasingContentSchema, getBlockAttributes, } from '@wordpress/blocks'; -import { RichText } from '@wordpress/editor'; +import { RichText } from '@wordpress/block-editor'; import { replace, join, split, create, toHTMLString, LINE_SEPARATOR } from '@wordpress/rich-text'; import { G, Path, SVG } from '@wordpress/components'; diff --git a/packages/block-library/src/media-text/edit.js b/packages/block-library/src/media-text/edit.js index 9f9b626f837fd..061d36ccf6c69 100644 --- a/packages/block-library/src/media-text/edit.js +++ b/packages/block-library/src/media-text/edit.js @@ -14,7 +14,7 @@ import { InspectorControls, PanelColorSettings, withColors, -} from '@wordpress/editor'; +} from '@wordpress/block-editor'; import { Component, Fragment } from '@wordpress/element'; import { PanelBody, diff --git a/packages/block-library/src/media-text/index.js b/packages/block-library/src/media-text/index.js index 01182b306a642..6809ba48ad435 100644 --- a/packages/block-library/src/media-text/index.js +++ b/packages/block-library/src/media-text/index.js @@ -11,7 +11,7 @@ import { createBlock } from '@wordpress/blocks'; import { InnerBlocks, getColorClassName, -} from '@wordpress/editor'; +} from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; /** diff --git a/packages/block-library/src/media-text/media-container.js b/packages/block-library/src/media-text/media-container.js index 6f0ce32685f63..a255e37dc6538 100644 --- a/packages/block-library/src/media-text/media-container.js +++ b/packages/block-library/src/media-text/media-container.js @@ -7,7 +7,7 @@ import { BlockIcon, MediaPlaceholder, MediaUpload, -} from '@wordpress/editor'; +} from '@wordpress/block-editor'; import { Component, Fragment } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; diff --git a/packages/block-library/src/missing/index.js b/packages/block-library/src/missing/index.js index 62ad7ed96b999..bee4d275fa85b 100644 --- a/packages/block-library/src/missing/index.js +++ b/packages/block-library/src/missing/index.js @@ -6,7 +6,7 @@ import { RawHTML, Fragment } from '@wordpress/element'; import { Button } from '@wordpress/components'; import { getBlockType, createBlock } from '@wordpress/blocks'; import { withDispatch } from '@wordpress/data'; -import { Warning } from '@wordpress/editor'; +import { Warning } from '@wordpress/block-editor'; function MissingBlockWarning( { attributes, convertToHTML } ) { const { originalName, originalUndelimitedContent } = attributes; diff --git a/packages/block-library/src/more/edit.js b/packages/block-library/src/more/edit.js index 54347bbdbc6fd..3840182a80d84 100644 --- a/packages/block-library/src/more/edit.js +++ b/packages/block-library/src/more/edit.js @@ -4,7 +4,7 @@ import { __ } from '@wordpress/i18n'; import { PanelBody, ToggleControl } from '@wordpress/components'; import { Component, Fragment } from '@wordpress/element'; -import { InspectorControls } from '@wordpress/editor'; +import { InspectorControls } from '@wordpress/block-editor'; import { ENTER } from '@wordpress/keycodes'; import { getDefaultBlockName, diff --git a/packages/block-library/src/more/edit.native.js b/packages/block-library/src/more/edit.native.js index 6a73a44c73b6a..e6d519c26b304 100644 --- a/packages/block-library/src/more/edit.native.js +++ b/packages/block-library/src/more/edit.native.js @@ -16,7 +16,7 @@ import { /** * Internal dependencies */ -import { PlainText } from '@wordpress/editor'; +import { PlainText } from '@wordpress/block-editor'; import styles from './editor.scss'; export default class MoreEdit extends Component { diff --git a/packages/block-library/src/paragraph/edit.js b/packages/block-library/src/paragraph/edit.js index 060080b971e59..0ed9c5c91b582 100644 --- a/packages/block-library/src/paragraph/edit.js +++ b/packages/block-library/src/paragraph/edit.js @@ -27,7 +27,7 @@ import { PanelColorSettings, RichText, withFontSizes, -} from '@wordpress/editor'; +} from '@wordpress/block-editor'; import { createBlock } from '@wordpress/blocks'; import { compose } from '@wordpress/compose'; import { withSelect } from '@wordpress/data'; diff --git a/packages/block-library/src/paragraph/edit.native.js b/packages/block-library/src/paragraph/edit.native.js index 4b303d489557d..aae8912194725 100644 --- a/packages/block-library/src/paragraph/edit.native.js +++ b/packages/block-library/src/paragraph/edit.native.js @@ -9,7 +9,7 @@ import { View } from 'react-native'; import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; import { createBlock } from '@wordpress/blocks'; -import { RichText } from '@wordpress/editor'; +import { RichText } from '@wordpress/block-editor'; /** * Internal dependencies diff --git a/packages/block-library/src/paragraph/index.js b/packages/block-library/src/paragraph/index.js index 934efe8dc1c90..460bb680d3dcf 100644 --- a/packages/block-library/src/paragraph/index.js +++ b/packages/block-library/src/paragraph/index.js @@ -15,7 +15,7 @@ import { getColorClassName, getFontSizeClass, RichText, -} from '@wordpress/editor'; +} from '@wordpress/block-editor'; import { getPhrasingContentSchema } from '@wordpress/blocks'; import { Path, diff --git a/packages/block-library/src/preformatted/index.js b/packages/block-library/src/preformatted/index.js index 25144b04abcfe..7d90224f9639c 100644 --- a/packages/block-library/src/preformatted/index.js +++ b/packages/block-library/src/preformatted/index.js @@ -3,7 +3,7 @@ */ import { __ } from '@wordpress/i18n'; import { createBlock, getPhrasingContentSchema } from '@wordpress/blocks'; -import { RichText } from '@wordpress/editor'; +import { RichText } from '@wordpress/block-editor'; import { Path, Rect, SVG } from '@wordpress/components'; export const name = 'core/preformatted'; diff --git a/packages/block-library/src/pullquote/edit.js b/packages/block-library/src/pullquote/edit.js index eb7b2b4e0bbd8..45acc1f67463d 100644 --- a/packages/block-library/src/pullquote/edit.js +++ b/packages/block-library/src/pullquote/edit.js @@ -18,7 +18,7 @@ import { InspectorControls, withColors, PanelColorSettings, -} from '@wordpress/editor'; +} from '@wordpress/block-editor'; export const SOLID_COLOR_STYLE_NAME = 'solid-color'; export const SOLID_COLOR_CLASS = `is-style-${ SOLID_COLOR_STYLE_NAME }`; diff --git a/packages/block-library/src/pullquote/index.js b/packages/block-library/src/pullquote/index.js index 766cb78861158..308f230c94dd9 100644 --- a/packages/block-library/src/pullquote/index.js +++ b/packages/block-library/src/pullquote/index.js @@ -12,7 +12,7 @@ import { getColorClassName, RichText, getColorObjectByAttributeValues, -} from '@wordpress/editor'; +} from '@wordpress/block-editor'; import { select, } from '@wordpress/data'; diff --git a/packages/block-library/src/quote/index.js b/packages/block-library/src/quote/index.js index 43c380be09f3a..535dd29e4d8b5 100644 --- a/packages/block-library/src/quote/index.js +++ b/packages/block-library/src/quote/index.js @@ -13,7 +13,7 @@ import { BlockControls, AlignmentToolbar, RichText, -} from '@wordpress/editor'; +} from '@wordpress/block-editor'; import { join, split, create, toHTMLString } from '@wordpress/rich-text'; import { Path, SVG } from '@wordpress/components'; diff --git a/packages/block-library/src/rss/edit.js b/packages/block-library/src/rss/edit.js index a08e7fc8fe4d5..e7262e11b2bc1 100644 --- a/packages/block-library/src/rss/edit.js +++ b/packages/block-library/src/rss/edit.js @@ -17,7 +17,7 @@ import { __ } from '@wordpress/i18n'; import { BlockControls, InspectorControls, -} from '@wordpress/editor'; +} from '@wordpress/block-editor'; const DEFAULT_MIN_ITEMS = 1; const DEFAULT_MAX_ITEMS = 10; diff --git a/packages/block-library/src/search/edit.js b/packages/block-library/src/search/edit.js index f5a5acd20c498..6525f5a82408f 100644 --- a/packages/block-library/src/search/edit.js +++ b/packages/block-library/src/search/edit.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { RichText } from '@wordpress/editor'; +import { RichText } from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; export default function SearchEdit( { className, attributes, setAttributes } ) { diff --git a/packages/block-library/src/shortcode/index.js b/packages/block-library/src/shortcode/index.js index 7204cd1eadf43..6d497a0e29aca 100644 --- a/packages/block-library/src/shortcode/index.js +++ b/packages/block-library/src/shortcode/index.js @@ -5,7 +5,7 @@ import { removep, autop } from '@wordpress/autop'; import { RawHTML } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { Dashicon, SVG, Path } from '@wordpress/components'; -import { PlainText } from '@wordpress/editor'; +import { PlainText } from '@wordpress/block-editor'; import { withInstanceId } from '@wordpress/compose'; export const name = 'core/shortcode'; diff --git a/packages/block-library/src/spacer/index.js b/packages/block-library/src/spacer/index.js index d671a24b994c0..fd83148315454 100644 --- a/packages/block-library/src/spacer/index.js +++ b/packages/block-library/src/spacer/index.js @@ -8,7 +8,7 @@ import classnames from 'classnames'; */ import { Fragment } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { InspectorControls } from '@wordpress/editor'; +import { InspectorControls } from '@wordpress/block-editor'; import { BaseControl, PanelBody, ResizableBox, G, SVG, Path } from '@wordpress/components'; import { withInstanceId } from '@wordpress/compose'; diff --git a/packages/block-library/src/subhead/index.js b/packages/block-library/src/subhead/index.js index 82fa6536fc366..d35847e4edce1 100644 --- a/packages/block-library/src/subhead/index.js +++ b/packages/block-library/src/subhead/index.js @@ -9,7 +9,7 @@ import { RichText, BlockControls, AlignmentToolbar, -} from '@wordpress/editor'; +} from '@wordpress/block-editor'; import { SVG, Path } from '@wordpress/components'; export const name = 'core/subhead'; diff --git a/packages/block-library/src/table/edit.js b/packages/block-library/src/table/edit.js index 175de8277d071..a54b3e9d1789d 100644 --- a/packages/block-library/src/table/edit.js +++ b/packages/block-library/src/table/edit.js @@ -13,7 +13,7 @@ import { RichText, PanelColorSettings, createCustomColorsHOC, -} from '@wordpress/editor'; +} from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; import { PanelBody, diff --git a/packages/block-library/src/table/index.js b/packages/block-library/src/table/index.js index 78bc014bd80b0..35a37c52f00d5 100644 --- a/packages/block-library/src/table/index.js +++ b/packages/block-library/src/table/index.js @@ -9,7 +9,7 @@ import classnames from 'classnames'; import { __, _x } from '@wordpress/i18n'; import { getPhrasingContentSchema } from '@wordpress/blocks'; import { G, Path, SVG } from '@wordpress/components'; -import { RichText, getColorClassName } from '@wordpress/editor'; +import { RichText, getColorClassName } from '@wordpress/block-editor'; /** * Internal dependencies diff --git a/packages/block-library/src/tag-cloud/edit.js b/packages/block-library/src/tag-cloud/edit.js index 2fb7179e2ca22..9a58c64de87e1 100644 --- a/packages/block-library/src/tag-cloud/edit.js +++ b/packages/block-library/src/tag-cloud/edit.js @@ -15,7 +15,7 @@ import { } from '@wordpress/components'; import { withSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; -import { InspectorControls } from '@wordpress/editor'; +import { InspectorControls } from '@wordpress/block-editor'; class TagCloudEdit extends Component { constructor() { diff --git a/packages/block-library/src/template/index.js b/packages/block-library/src/template/index.js index 831f6bfeee5d6..6da60730b2596 100644 --- a/packages/block-library/src/template/index.js +++ b/packages/block-library/src/template/index.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { InnerBlocks } from '@wordpress/editor'; +import { InnerBlocks } from '@wordpress/block-editor'; import { G, Path, Rect, SVG } from '@wordpress/components'; export const name = 'core/template'; diff --git a/packages/block-library/src/test/helpers/index.js b/packages/block-library/src/test/helpers/index.js index 789eff443d7cc..2f672f2090bd3 100644 --- a/packages/block-library/src/test/helpers/index.js +++ b/packages/block-library/src/test/helpers/index.js @@ -12,7 +12,8 @@ import { getBlockType, registerBlockType, } from '@wordpress/blocks'; -import { BlockEdit } from '@wordpress/editor'; +import { BlockEdit } from '@wordpress/block-editor'; +import '@wordpress/editor'; export const blockEditRender = ( name, settings ) => { if ( ! getBlockType( name ) ) { diff --git a/packages/block-library/src/text-columns/index.js b/packages/block-library/src/text-columns/index.js index 60f93e61c60fd..5bcf5cc3b6a79 100644 --- a/packages/block-library/src/text-columns/index.js +++ b/packages/block-library/src/text-columns/index.js @@ -15,7 +15,7 @@ import { BlockAlignmentToolbar, InspectorControls, RichText, -} from '@wordpress/editor'; +} from '@wordpress/block-editor'; import deprecated from '@wordpress/deprecated'; export const name = 'core/text-columns'; diff --git a/packages/block-library/src/verse/index.js b/packages/block-library/src/verse/index.js index 17306b3284f00..e3832d1f6d8b4 100644 --- a/packages/block-library/src/verse/index.js +++ b/packages/block-library/src/verse/index.js @@ -8,7 +8,7 @@ import { RichText, BlockControls, AlignmentToolbar, -} from '@wordpress/editor'; +} from '@wordpress/block-editor'; import { SVG, Path } from '@wordpress/components'; export const name = 'core/verse'; diff --git a/packages/block-library/src/video/edit.js b/packages/block-library/src/video/edit.js index e22f35d0bf41e..6661db034b75c 100644 --- a/packages/block-library/src/video/edit.js +++ b/packages/block-library/src/video/edit.js @@ -21,8 +21,8 @@ import { MediaUpload, MediaUploadCheck, RichText, - mediaUpload, -} from '@wordpress/editor'; +} from '@wordpress/block-editor'; +import { mediaUpload } from '@wordpress/editor'; import { Component, Fragment, createRef } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; diff --git a/packages/block-library/src/video/index.js b/packages/block-library/src/video/index.js index bba5819300228..e56d99127d588 100644 --- a/packages/block-library/src/video/index.js +++ b/packages/block-library/src/video/index.js @@ -3,7 +3,7 @@ */ import { createBlobURL } from '@wordpress/blob'; import { createBlock } from '@wordpress/blocks'; -import { RichText } from '@wordpress/editor'; +import { RichText } from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; /** diff --git a/packages/e2e-tests/specs/__snapshots__/block-deletion.test.js.snap b/packages/e2e-tests/specs/__snapshots__/block-deletion.test.js.snap index 9758679af968f..0eadd8df91767 100644 --- a/packages/e2e-tests/specs/__snapshots__/block-deletion.test.js.snap +++ b/packages/e2e-tests/specs/__snapshots__/block-deletion.test.js.snap @@ -80,7 +80,7 @@ exports[`block deletion - deleting the third block using the Remove Block shortc <!-- /wp:paragraph -->" `; -exports[`block deletion - deleting third third and fourth blocks using backspace with multi-block selection results in two remaining blocks and positions the caret at the end of the second block 1`] = ` +exports[`block deletion - deleting the third and fourth blocks using backspace with multi-block selection results in two remaining blocks and positions the caret at the end of the second block 1`] = ` "<!-- wp:paragraph --> <p>First paragraph</p> <!-- /wp:paragraph --> @@ -90,7 +90,7 @@ exports[`block deletion - deleting third third and fourth blocks using backspace <!-- /wp:paragraph -->" `; -exports[`block deletion - deleting third third and fourth blocks using backspace with multi-block selection results in two remaining blocks and positions the caret at the end of the second block 2`] = ` +exports[`block deletion - deleting the third and fourth blocks using backspace with multi-block selection results in two remaining blocks and positions the caret at the end of the second block 2`] = ` "<!-- wp:paragraph --> <p>First paragraph</p> <!-- /wp:paragraph --> diff --git a/packages/e2e-tests/specs/block-deletion.test.js b/packages/e2e-tests/specs/block-deletion.test.js index b08eacd1f6d62..24550537beff4 100644 --- a/packages/e2e-tests/specs/block-deletion.test.js +++ b/packages/e2e-tests/specs/block-deletion.test.js @@ -90,7 +90,7 @@ describe( 'block deletion -', () => { } ); } ); - describe( 'deleting third third and fourth blocks using backspace with multi-block selection', () => { + describe( 'deleting the third and fourth blocks using backspace with multi-block selection', () => { it( 'results in two remaining blocks and positions the caret at the end of the second block', async () => { // Add a third paragraph for this test. await page.keyboard.type( 'Third paragraph' ); diff --git a/packages/edit-post/src/components/header/header-toolbar/index.js b/packages/edit-post/src/components/header/header-toolbar/index.js index 7d99c1f889e04..56f8dd7d5fdb2 100644 --- a/packages/edit-post/src/components/header/header-toolbar/index.js +++ b/packages/edit-post/src/components/header/header-toolbar/index.js @@ -9,11 +9,13 @@ import { __ } from '@wordpress/i18n'; import { Inserter, BlockToolbar, + NavigableToolbar, + BlockNavigationDropdown, +} from '@wordpress/block-editor'; +import { TableOfContents, EditorHistoryRedo, EditorHistoryUndo, - NavigableToolbar, - BlockNavigationDropdown, } from '@wordpress/editor'; /** diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index f0876f4672287..2274ff9ffa3ec 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -8,12 +8,12 @@ import classnames from 'classnames'; */ import { Button, Popover, ScrollLock, navigateRegions } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; +import { PreserveScrollInReorder } from '@wordpress/block-editor'; import { AutosaveMonitor, UnsavedChangesWarning, EditorNotices, PostPublishPanel, - PreserveScrollInReorder, } from '@wordpress/editor'; import { withDispatch, withSelect } from '@wordpress/data'; import { Fragment } from '@wordpress/element'; diff --git a/packages/edit-post/src/components/options-modal/index.js b/packages/edit-post/src/components/options-modal/index.js index 76cf40a74cd92..a7e2cc2f395cc 100644 --- a/packages/edit-post/src/components/options-modal/index.js +++ b/packages/edit-post/src/components/options-modal/index.js @@ -10,7 +10,13 @@ import { Modal } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { withSelect, withDispatch } from '@wordpress/data'; import { compose } from '@wordpress/compose'; -import { PostTaxonomies, PostExcerptCheck, PageAttributesCheck, PostFeaturedImageCheck, PostTypeSupportCheck } from '@wordpress/editor'; +import { + PostTaxonomies, + PostExcerptCheck, + PageAttributesCheck, + PostFeaturedImageCheck, + PostTypeSupportCheck, +} from '@wordpress/editor'; /** * Internal dependencies diff --git a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js index d6874aa100f04..c43856376a389 100644 --- a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js +++ b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js @@ -4,7 +4,7 @@ import { Panel, PanelBody } from '@wordpress/components'; import { compose, ifCondition } from '@wordpress/compose'; import { withSelect } from '@wordpress/data'; -import { BlockInspector } from '@wordpress/editor'; +import { BlockInspector } from '@wordpress/block-editor'; import { Fragment } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; /** diff --git a/packages/edit-post/src/components/visual-editor/index.js b/packages/edit-post/src/components/visual-editor/index.js index a8cdc7e06f69d..eadf8a5fc461d 100644 --- a/packages/edit-post/src/components/visual-editor/index.js +++ b/packages/edit-post/src/components/visual-editor/index.js @@ -2,17 +2,19 @@ * WordPress dependencies */ import { - BlockList, - CopyHandler, PostTitle, + VisualEditorGlobalKeyboardShortcuts, +} from '@wordpress/editor'; +import { WritingFlow, ObserveTyping, - VisualEditorGlobalKeyboardShortcuts, + BlockList, + CopyHandler, BlockSelectionClearer, MultiSelectScrollIntoView, _BlockSettingsMenuFirstItem, _BlockSettingsMenuPluginsExtension, -} from '@wordpress/editor'; +} from '@wordpress/block-editor'; /** * Internal dependencies diff --git a/packages/edit-post/src/hooks/validate-multiple-use/index.js b/packages/edit-post/src/hooks/validate-multiple-use/index.js index 8a9f15a2c1f49..ea2958949f7e2 100644 --- a/packages/edit-post/src/hooks/validate-multiple-use/index.js +++ b/packages/edit-post/src/hooks/validate-multiple-use/index.js @@ -15,7 +15,7 @@ import { } from '@wordpress/blocks'; import { Button } from '@wordpress/components'; import { withSelect, withDispatch } from '@wordpress/data'; -import { Warning } from '@wordpress/editor'; +import { Warning } from '@wordpress/block-editor'; import { addFilter } from '@wordpress/hooks'; import { __ } from '@wordpress/i18n'; import { compose, createHigherOrderComponent } from '@wordpress/compose'; diff --git a/packages/editor/package.json b/packages/editor/package.json index 97935dd04b406..36b5da996439e 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -22,7 +22,6 @@ "react-native": "src/index", "dependencies": { "@babel/runtime": "^7.3.1", - "@wordpress/a11y": "file:../a11y", "@wordpress/api-fetch": "file:../api-fetch", "@wordpress/blob": "file:../blob", "@wordpress/block-editor": "file:../block-editor", @@ -33,21 +32,17 @@ "@wordpress/data": "file:../data", "@wordpress/date": "file:../date", "@wordpress/deprecated": "file:../deprecated", - "@wordpress/dom": "file:../dom", "@wordpress/element": "file:../element", "@wordpress/hooks": "file:../hooks", "@wordpress/html-entities": "file:../html-entities", "@wordpress/i18n": "file:../i18n", - "@wordpress/is-shallow-equal": "file:../is-shallow-equal", "@wordpress/keycodes": "file:../keycodes", "@wordpress/notices": "file:../notices", "@wordpress/nux": "file:../nux", - "@wordpress/token-list": "file:../token-list", "@wordpress/url": "file:../url", "@wordpress/viewport": "file:../viewport", "@wordpress/wordcount": "file:../wordcount", "classnames": "^2.2.5", - "dom-scroll-into-view": "^1.2.1", "inherits": "^2.0.3", "lodash": "^4.17.11", "memize": "^1.0.5", @@ -56,7 +51,6 @@ "redux-optimist": "^1.0.0", "refx": "^3.0.0", "rememo": "^3.0.0", - "tinycolor2": "^1.4.1", "traverse": "^0.6.6" }, "publishConfig": { diff --git a/packages/editor/src/components/autocompleters/block.js b/packages/editor/src/components/autocompleters/block.js index 2945ea4322786..746928ea7752e 100644 --- a/packages/editor/src/components/autocompleters/block.js +++ b/packages/editor/src/components/autocompleters/block.js @@ -3,11 +3,7 @@ */ import { select } from '@wordpress/data'; import { createBlock } from '@wordpress/blocks'; - -/** - * Internal dependencies - */ -import BlockIcon from '../block-icon'; +import { BlockIcon } from '@wordpress/block-editor'; /** * Returns the client ID of the parent where a newly inserted block would be diff --git a/packages/editor/src/components/deprecated.js b/packages/editor/src/components/deprecated.js new file mode 100644 index 0000000000000..80004056ec311 --- /dev/null +++ b/packages/editor/src/components/deprecated.js @@ -0,0 +1,119 @@ +// Block Creation Components +/** + * WordPress dependencies + */ +import { + Autocomplete, + AlignmentToolbar, + BlockAlignmentToolbar, + BlockControls, + BlockEdit, + BlockEditorKeyboardShortcuts, + BlockFormatControls, + BlockIcon, + BlockInspector, + BlockList, + BlockMover, + BlockNavigationDropdown, + BlockSelectionClearer, + BlockSettingsMenu, + BlockTitle, + BlockToolbar, + ColorPalette, + ContrastChecker, + CopyHandler, + createCustomColorsHOC, + DefaultBlockAppender, + FontSizePicker, + getColorClassName, + getColorObjectByAttributeValues, + getColorObjectByColorValue, + getFontSize, + getFontSizeClass, + Inserter, + InnerBlocks, + InspectorAdvancedControls, + InspectorControls, + PanelColorSettings, + PlainText, + RichText, + RichTextShortcut, + RichTextToolbarButton, + RichTextInserterItem, + UnstableRichTextInputEvent, + MediaPlaceholder, + MediaUpload, + MediaUploadCheck, + MultiBlocksSwitcher, + MultiSelectScrollIntoView, + NavigableToolbar, + ObserveTyping, + PreserveScrollInReorder, + SkipToSelectedBlock, + URLInput, + URLInputButton, + URLPopover, + Warning, + WritingFlow, + withColorContext, + withColors, + withFontSizes, +} from '@wordpress/block-editor'; + +export { + Autocomplete, + AlignmentToolbar, + BlockAlignmentToolbar, + BlockControls, + BlockEdit, + BlockEditorKeyboardShortcuts, + BlockFormatControls, + BlockIcon, + BlockInspector, + BlockList, + BlockMover, + BlockNavigationDropdown, + BlockSelectionClearer, + BlockSettingsMenu, + BlockTitle, + BlockToolbar, + ColorPalette, + ContrastChecker, + CopyHandler, + createCustomColorsHOC, + DefaultBlockAppender, + FontSizePicker, + getColorClassName, + getColorObjectByAttributeValues, + getColorObjectByColorValue, + getFontSize, + getFontSizeClass, + Inserter, + InnerBlocks, + InspectorAdvancedControls, + InspectorControls, + PanelColorSettings, + PlainText, + RichText, + RichTextShortcut, + RichTextToolbarButton, + RichTextInserterItem, + UnstableRichTextInputEvent, + MediaPlaceholder, + MediaUpload, + MediaUploadCheck, + MultiBlocksSwitcher, + MultiSelectScrollIntoView, + NavigableToolbar, + ObserveTyping, + PreserveScrollInReorder, + SkipToSelectedBlock, + URLInput, + URLInputButton, + URLPopover, + Warning, + WritingFlow, + withColorContext, + withColors, + withFontSizes, +}; diff --git a/packages/editor/src/components/document-outline/item.js b/packages/editor/src/components/document-outline/item.js index eb6b147056f67..c0638100d25f1 100644 --- a/packages/editor/src/components/document-outline/item.js +++ b/packages/editor/src/components/document-outline/item.js @@ -7,11 +7,7 @@ import classnames from 'classnames'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import BlockTitle from '../block-title'; +import { BlockTitle } from '@wordpress/block-editor'; const TableOfContentsItem = ( { children, diff --git a/packages/editor/src/components/document-outline/test/index.js b/packages/editor/src/components/document-outline/test/index.js index a3fe67f9eab94..024cb615beeeb 100644 --- a/packages/editor/src/components/document-outline/test/index.js +++ b/packages/editor/src/components/document-outline/test/index.js @@ -13,7 +13,9 @@ import { createBlock, registerBlockType, unregisterBlockType } from '@wordpress/ */ import { DocumentOutline } from '../'; -jest.mock( '../../block-title', () => () => 'Block Title' ); +jest.mock( '@wordpress/block-editor', () => ( { + BlockTitle: () => 'Block Title', +} ) ); describe( 'DocumentOutline', () => { let paragraph, headingH1, headingParent, headingChild, nestedHeading; diff --git a/packages/editor/src/components/error-boundary/index.js b/packages/editor/src/components/error-boundary/index.js index 2218494072125..ce74c05857777 100644 --- a/packages/editor/src/components/error-boundary/index.js +++ b/packages/editor/src/components/error-boundary/index.js @@ -5,11 +5,7 @@ import { Component } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { Button, ClipboardButton } from '@wordpress/components'; import { select } from '@wordpress/data'; - -/** - * Internal dependencies - */ -import { Warning } from '../'; +import { Warning } from '@wordpress/block-editor'; class ErrorBoundary extends Component { constructor() { diff --git a/packages/editor/src/components/global-keyboard-shortcuts/visual-editor-shortcuts.js b/packages/editor/src/components/global-keyboard-shortcuts/visual-editor-shortcuts.js index 4a0cd7f8fd4f7..6298496b87f47 100644 --- a/packages/editor/src/components/global-keyboard-shortcuts/visual-editor-shortcuts.js +++ b/packages/editor/src/components/global-keyboard-shortcuts/visual-editor-shortcuts.js @@ -6,12 +6,12 @@ import { KeyboardShortcuts } from '@wordpress/components'; import { withDispatch } from '@wordpress/data'; import { rawShortcut } from '@wordpress/keycodes'; import deprecated from '@wordpress/deprecated'; +import { BlockEditorKeyboardShortcuts } from '@wordpress/block-editor'; /** * Internal dependencies */ import SaveShortcut from './save-shortcut'; -import BlockEditorKeyboardShortcuts from './block-editor-shortcuts'; class VisualEditorGlobalKeyboardShortcuts extends Component { constructor() { diff --git a/packages/editor/src/components/index.js b/packages/editor/src/components/index.js index 9ebb2dacc8640..9d12344c50dc2 100644 --- a/packages/editor/src/components/index.js +++ b/packages/editor/src/components/index.js @@ -1,36 +1,6 @@ // Block Creation Components -export { default as Autocomplete } from './autocomplete'; export * from './autocompleters'; -export { default as AlignmentToolbar } from './alignment-toolbar'; -export { default as BlockAlignmentToolbar } from './block-alignment-toolbar'; -export { default as BlockControls } from './block-controls'; -export { default as BlockEdit } from './block-edit'; -export { default as BlockFormatControls } from './block-format-controls'; -export { default as BlockNavigationDropdown } from './block-navigation/dropdown'; -export { default as BlockIcon } from './block-icon'; -export { default as ColorPalette } from './color-palette'; -export { default as withColorContext } from './color-palette/with-color-context'; -export * from './colors'; -export { default as ContrastChecker } from './contrast-checker'; -export * from './font-sizes'; -export { default as InnerBlocks } from './inner-blocks'; -export { default as InspectorAdvancedControls } from './inspector-advanced-controls'; -export { default as InspectorControls } from './inspector-controls'; -export { default as PanelColorSettings } from './panel-color-settings'; -export { default as PlainText } from './plain-text'; -export { - default as RichText, - RichTextShortcut, - RichTextToolbarButton, - UnstableRichTextInputEvent, -} from './rich-text'; export { default as ServerSideRender } from './server-side-render'; -export { default as MediaPlaceholder } from './media-placeholder'; -export { default as MediaUpload } from './media-upload'; -export { default as MediaUploadCheck } from './media-upload/check'; -export { default as URLInput } from './url-input'; -export { default as URLInputButton } from './url-input/button'; -export { default as URLPopover } from './url-popover'; // Post Related Components export { default as AutosaveMonitor } from './autosave-monitor'; @@ -44,6 +14,7 @@ export { default as TextEditorGlobalKeyboardShortcuts } from './global-keyboard- export { default as EditorHistoryRedo } from './editor-history/redo'; export { default as EditorHistoryUndo } from './editor-history/undo'; export { default as EditorNotices } from './editor-notices'; +export { default as ErrorBoundary } from './error-boundary'; export { default as PageAttributesCheck } from './page-attributes/check'; export { default as PageAttributesOrder } from './page-attributes/order'; export { default as PageAttributesParent } from './page-attributes/parent'; @@ -88,28 +59,7 @@ export { default as TableOfContents } from './table-of-contents'; export { default as UnsavedChangesWarning } from './unsaved-changes-warning'; export { default as WordCount } from './word-count'; -// Content Related Components -export { default as BlockInspector } from './block-inspector'; -export { default as BlockList } from './block-list'; -export { default as BlockMover } from './block-mover'; -export { default as BlockSelectionClearer } from './block-selection-clearer'; -export { default as BlockSettingsMenu } from './block-settings-menu'; -export { default as _BlockSettingsMenuFirstItem } from './block-settings-menu/block-settings-menu-first-item'; -export { default as _BlockSettingsMenuPluginsExtension } from './block-settings-menu/block-settings-menu-plugins-extension'; -export { default as BlockTitle } from './block-title'; -export { default as BlockToolbar } from './block-toolbar'; -export { default as CopyHandler } from './copy-handler'; -export { default as DefaultBlockAppender } from './default-block-appender'; -export { default as ErrorBoundary } from './error-boundary'; -export { default as Inserter } from './inserter'; -export { default as MultiBlocksSwitcher } from './block-switcher/multi-blocks-switcher'; -export { default as MultiSelectScrollIntoView } from './multi-select-scroll-into-view'; -export { default as NavigableToolbar } from './navigable-toolbar'; -export { default as ObserveTyping } from './observe-typing'; -export { default as PreserveScrollInReorder } from './preserve-scroll-in-reorder'; -export { default as SkipToSelectedBlock } from './skip-to-selected-block'; -export { default as Warning } from './warning'; -export { default as WritingFlow } from './writing-flow'; - // State Related Components export { default as EditorProvider } from './provider'; + +export * from './deprecated'; diff --git a/packages/editor/src/components/post-featured-image/index.js b/packages/editor/src/components/post-featured-image/index.js index 0c77b6dbc6944..05e74ae5a2ace 100644 --- a/packages/editor/src/components/post-featured-image/index.js +++ b/packages/editor/src/components/post-featured-image/index.js @@ -11,13 +11,12 @@ import { applyFilters } from '@wordpress/hooks'; import { Button, Spinner, ResponsiveWrapper, withFilters } from '@wordpress/components'; import { compose } from '@wordpress/compose'; import { withSelect, withDispatch } from '@wordpress/data'; +import { MediaUpload, MediaUploadCheck } from '@wordpress/block-editor'; /** * Internal dependencies */ import PostFeaturedImageCheck from './check'; -import MediaUpload from '../media-upload'; -import MediaUploadCheck from '../media-upload/check'; const ALLOWED_MEDIA_TYPES = [ 'image' ]; diff --git a/packages/editor/src/components/post-title/index.native.js b/packages/editor/src/components/post-title/index.native.js index 55e56d03a101d..95384a6aca6d7 100644 --- a/packages/editor/src/components/post-title/index.native.js +++ b/packages/editor/src/components/post-title/index.native.js @@ -7,7 +7,7 @@ import { View } from 'react-native'; * WordPress dependencies */ import { Component } from '@wordpress/element'; -import { RichText } from '@wordpress/editor'; +import { RichText } from '@wordpress/block-editor'; import { decodeEntities } from '@wordpress/html-entities'; import { withDispatch } from '@wordpress/data'; import { withFocusOutside } from '@wordpress/components'; diff --git a/packages/editor/src/hooks/index.js b/packages/editor/src/hooks/index.js index e0a1ee626d672..2c8a61d980252 100644 --- a/packages/editor/src/hooks/index.js +++ b/packages/editor/src/hooks/index.js @@ -1,8 +1,4 @@ /** * Internal dependencies */ -import './align'; -import './anchor'; -import './custom-class-name'; import './default-autocompleters'; -import './generated-class-name'; diff --git a/packages/editor/src/hooks/index.native.js b/packages/editor/src/hooks/index.native.js index d85f61d596d93..e69de29bb2d1d 100644 --- a/packages/editor/src/hooks/index.native.js +++ b/packages/editor/src/hooks/index.native.js @@ -1,5 +0,0 @@ -/** - * Internal dependencies - */ -import './custom-class-name'; -import './generated-class-name'; diff --git a/packages/editor/src/store/test/selectors.js b/packages/editor/src/store/test/selectors.js index 01f2d09199e52..1740302fe73d6 100644 --- a/packages/editor/src/store/test/selectors.js +++ b/packages/editor/src/store/test/selectors.js @@ -122,6 +122,9 @@ describe( 'selectors', () => { category: 'common', title: 'Test Freeform Content Handler', icon: 'test', + supports: { + className: false, + }, attributes: { content: { type: 'string', diff --git a/packages/editor/src/style.scss b/packages/editor/src/style.scss index 028e9094d629b..63f0e18c9974d 100644 --- a/packages/editor/src/style.scss +++ b/packages/editor/src/style.scss @@ -1,33 +1,7 @@ @import "./components/autocompleters/style.scss"; -@import "./components/block-drop-zone/style.scss"; -@import "./components/block-icon/style.scss"; -@import "./components/block-inspector/style.scss"; -@import "./components/block-list/style.scss"; -@import "./components/block-list-appender/style.scss"; -@import "./components/block-compare/style.scss"; -@import "./components/block-mover/style.scss"; -@import "./components/block-navigation/style.scss"; -@import "./components/block-preview/style.scss"; -@import "./components/block-settings-menu/style.scss"; -@import "./components/block-styles/style.scss"; -@import "./components/block-switcher/style.scss"; -@import "./components/block-toolbar/style.scss"; -@import "./components/block-types-list/style.scss"; -@import "./components/color-palette/control.scss"; -@import "./components/contrast-checker/style.scss"; -@import "./components/default-block-appender/style.scss"; @import "./components/document-outline/style.scss"; @import "./components/error-boundary/style.scss"; -@import "./components/font-sizes/style.scss"; -@import "./components/inner-blocks/style.scss"; -@import "./components/inserter-with-shortcuts/style.scss"; -@import "./components/inserter/style.scss"; -@import "./components/inserter-list-item/style.scss"; -@import "./components/media-placeholder/style.scss"; -@import "./components/multi-selection-inspector/style.scss"; @import "./components/page-attributes/style.scss"; -@import "./components/panel-color-settings/style.scss"; -@import "./components/plain-text/style.scss"; @import "./components/post-excerpt/style.scss"; @import "./components/post-featured-image/style.scss"; @import "./components/post-format/style.scss"; @@ -41,12 +15,5 @@ @import "./components/post-visibility/style.scss"; @import "./components/post-title/style.scss"; @import "./components/post-trash/style.scss"; -@import "./components/rich-text/format-toolbar/style.scss"; -@import "./components/rich-text/style.scss"; -@import "./components/skip-to-selected-block/style.scss"; @import "./components/table-of-contents/style.scss"; @import "./components/template-validation-notice/style.scss"; -@import "./components/url-input/style.scss"; -@import "./components/url-popover/style.scss"; -@import "./components/warning/style.scss"; -@import "./components/writing-flow/style.scss"; diff --git a/packages/format-library/package.json b/packages/format-library/package.json index 32f10182c632f..c2824cc5ec0dc 100644 --- a/packages/format-library/package.json +++ b/packages/format-library/package.json @@ -22,6 +22,7 @@ "react-native": "src/index", "dependencies": { "@babel/runtime": "^7.3.1", + "@wordpress/block-editor": "file:../block-editor", "@wordpress/components": "file:../components", "@wordpress/editor": "file:../editor", "@wordpress/element": "file:../element", diff --git a/packages/format-library/src/bold/index.js b/packages/format-library/src/bold/index.js index 15c64261fbfde..3026b749025eb 100644 --- a/packages/format-library/src/bold/index.js +++ b/packages/format-library/src/bold/index.js @@ -4,7 +4,7 @@ import { __ } from '@wordpress/i18n'; import { Fragment } from '@wordpress/element'; import { toggleFormat } from '@wordpress/rich-text'; -import { RichTextToolbarButton, RichTextShortcut, UnstableRichTextInputEvent } from '@wordpress/editor'; +import { RichTextToolbarButton, RichTextShortcut, UnstableRichTextInputEvent } from '@wordpress/block-editor'; const name = 'core/bold'; diff --git a/packages/format-library/src/code/index.js b/packages/format-library/src/code/index.js index f5bf96bb32a2e..7e3a7b0dbf151 100644 --- a/packages/format-library/src/code/index.js +++ b/packages/format-library/src/code/index.js @@ -4,7 +4,7 @@ import { __ } from '@wordpress/i18n'; import { Fragment } from '@wordpress/element'; import { toggleFormat } from '@wordpress/rich-text'; -import { RichTextShortcut, RichTextToolbarButton } from '@wordpress/editor'; +import { RichTextShortcut, RichTextToolbarButton } from '@wordpress/block-editor'; const name = 'core/code'; diff --git a/packages/format-library/src/image/index.js b/packages/format-library/src/image/index.js index b7a3db3b1d4c3..2dca1c41506b6 100644 --- a/packages/format-library/src/image/index.js +++ b/packages/format-library/src/image/index.js @@ -5,7 +5,7 @@ import { Path, SVG, TextControl, Popover, IconButton, PositionedAtSelection } fr import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; import { insertObject } from '@wordpress/rich-text'; -import { MediaUpload, RichTextToolbarButton, MediaUploadCheck } from '@wordpress/editor'; +import { MediaUpload, RichTextToolbarButton, MediaUploadCheck } from '@wordpress/block-editor'; import { LEFT, RIGHT, UP, DOWN, BACKSPACE, ENTER } from '@wordpress/keycodes'; const ALLOWED_MEDIA_TYPES = [ 'image' ]; diff --git a/packages/format-library/src/italic/index.js b/packages/format-library/src/italic/index.js index 1acadaf6f4826..36974eb1c0fc0 100644 --- a/packages/format-library/src/italic/index.js +++ b/packages/format-library/src/italic/index.js @@ -4,7 +4,7 @@ import { __ } from '@wordpress/i18n'; import { Fragment } from '@wordpress/element'; import { toggleFormat } from '@wordpress/rich-text'; -import { RichTextToolbarButton, RichTextShortcut, UnstableRichTextInputEvent } from '@wordpress/editor'; +import { RichTextToolbarButton, RichTextShortcut, UnstableRichTextInputEvent } from '@wordpress/block-editor'; const name = 'core/italic'; diff --git a/packages/format-library/src/link/index.js b/packages/format-library/src/link/index.js index a3ffc2c6b27f0..c4e3c67348b16 100644 --- a/packages/format-library/src/link/index.js +++ b/packages/format-library/src/link/index.js @@ -11,7 +11,7 @@ import { slice, } from '@wordpress/rich-text'; import { isURL } from '@wordpress/url'; -import { RichTextToolbarButton, RichTextShortcut } from '@wordpress/editor'; +import { RichTextToolbarButton, RichTextShortcut } from '@wordpress/block-editor'; /** * Internal dependencies diff --git a/packages/format-library/src/link/index.native.js b/packages/format-library/src/link/index.native.js index 4ea3ee0e30934..99713b911a62f 100644 --- a/packages/format-library/src/link/index.native.js +++ b/packages/format-library/src/link/index.native.js @@ -9,7 +9,7 @@ import { find } from 'lodash'; import { __ } from '@wordpress/i18n'; import { Component, Fragment } from '@wordpress/element'; import { withSpokenMessages } from '@wordpress/components'; -import { RichTextToolbarButton } from '@wordpress/editor'; +import { RichTextToolbarButton } from '@wordpress/block-editor'; import { applyFormat, getActiveFormat, diff --git a/packages/format-library/src/link/inline.js b/packages/format-library/src/link/inline.js index c700ad2ef3dc5..4f32382bebaa9 100644 --- a/packages/format-library/src/link/inline.js +++ b/packages/format-library/src/link/inline.js @@ -25,7 +25,7 @@ import { getTextContent, slice, } from '@wordpress/rich-text'; -import { URLInput, URLPopover } from '@wordpress/editor'; +import { URLInput, URLPopover } from '@wordpress/block-editor'; /** * Internal dependencies diff --git a/packages/format-library/src/strikethrough/index.js b/packages/format-library/src/strikethrough/index.js index 7caa96781730b..636b1b1d9122c 100644 --- a/packages/format-library/src/strikethrough/index.js +++ b/packages/format-library/src/strikethrough/index.js @@ -4,7 +4,7 @@ import { __ } from '@wordpress/i18n'; import { Fragment } from '@wordpress/element'; import { toggleFormat } from '@wordpress/rich-text'; -import { RichTextToolbarButton, RichTextShortcut } from '@wordpress/editor'; +import { RichTextToolbarButton, RichTextShortcut } from '@wordpress/block-editor'; const name = 'core/strikethrough'; diff --git a/packages/format-library/src/underline/index.js b/packages/format-library/src/underline/index.js index 0777e83e1f717..c049a0e8a3fba 100644 --- a/packages/format-library/src/underline/index.js +++ b/packages/format-library/src/underline/index.js @@ -4,7 +4,7 @@ import { __ } from '@wordpress/i18n'; import { Fragment } from '@wordpress/element'; import { toggleFormat } from '@wordpress/rich-text'; -import { RichTextShortcut, UnstableRichTextInputEvent } from '@wordpress/editor'; +import { RichTextShortcut, UnstableRichTextInputEvent } from '@wordpress/block-editor'; const name = 'core/underline'; From cc70e27e597f17ad38759876807e23c22304bf8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Fri, 8 Mar 2019 11:37:47 +0100 Subject: [PATCH 611/691] wordcount: set up autogenerated API docs (#14213) --- bin/update-readmes.js | 2 +- packages/wordcount/README.md | 33 +++++++++++++++++++++++---------- packages/wordcount/src/index.js | 6 ++++++ 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/bin/update-readmes.js b/bin/update-readmes.js index b9a6dd6d2a92a..f15bedfdd0b64 100755 --- a/bin/update-readmes.js +++ b/bin/update-readmes.js @@ -32,7 +32,7 @@ const packages = [ 'shortcode', 'url', 'viewport', - //'wordcount', + 'wordcount', ]; const getArgsForPackage = ( packageName ) => { diff --git a/packages/wordcount/README.md b/packages/wordcount/README.md index 076a25ac81421..ecd1de4a1ed81 100644 --- a/packages/wordcount/README.md +++ b/packages/wordcount/README.md @@ -12,21 +12,34 @@ npm install @wordpress/wordcount --save _This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. Learn more about it in [Babel docs](https://babeljs.io/docs/en/next/caveats)._ -## Accepted Parameters +## API -```JS -count( text, type, userSettings ) -```` -count accepts three parameters: -1. text: A string containing the words/characters to be counted. -2. type: A string that represents the type of count. The current implementation accepts the strings 'words', 'characters_excluding_spaces', or 'characters_including_spaces'. -3. userSettings: An object that contains the list of regular expressions that will be used to count. See defaultSettings.js for the defaults. +<!-- START TOKEN(Autogenerated API docs) --> -## Usage +### count -```JS +[src/index.js#L107-L121](src/index.js#L107-L121) + +Count some words. + +**Usage** + +```js import { count } from '@wordpress/wordcount'; const numberOfWords = count( 'Words to count', 'words', {} ) ``` +**Parameters** + +- **text** `String`: The text being processed +- **type** `String`: The type of count. Accepts ;words', 'characters_excluding_spaces', or 'characters_including_spaces'. +- **userSettings** `Object`: Custom settings object. + +**Returns** + +`Number`: The word or character count. + + +<!-- END TOKEN(Autogenerated API docs) --> + <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/wordcount/src/index.js b/packages/wordcount/src/index.js index 8d70df55f0a27..9099e523574ce 100644 --- a/packages/wordcount/src/index.js +++ b/packages/wordcount/src/index.js @@ -95,6 +95,12 @@ function matchCharacters( text, regex, settings ) { * @param {String} type The type of count. Accepts ;words', 'characters_excluding_spaces', or 'characters_including_spaces'. * @param {Object} userSettings Custom settings object. * + * @example + * ```js + * import { count } from '@wordpress/wordcount'; + * const numberOfWords = count( 'Words to count', 'words', {} ) + * ``` + * * @return {Number} The word or character count. */ From 887803a219118b178a9e2d367660e7787917bd5c Mon Sep 17 00:00:00 2001 From: Pascal Birchler <pascal.birchler@gmail.com> Date: Fri, 8 Mar 2019 12:47:40 +0200 Subject: [PATCH 612/691] Update packages used by eslint plugin to remove warnings (#14077) * Update packages used by eslint plugin to remove warnings * Manually modify package-lock.json Props @gziolo. * Specify React version in eslint-plugin-react settings * Fix react/jsx-no-target lint errors --- package-lock.json | 122 ++++++++++-------- packages/block-library/src/categories/edit.js | 2 +- .../block-library/src/latest-posts/edit.js | 2 +- .../components/src/external-link/index.js | 2 +- packages/eslint-plugin/configs/custom.js | 5 + packages/eslint-plugin/package.json | 4 +- 6 files changed, 80 insertions(+), 57 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9d1ac8494ae02..93f1ca6a19331 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2856,8 +2856,8 @@ "dev": true, "requires": { "babel-eslint": "^8.0.3", - "eslint-plugin-jsx-a11y": "6.0.2", - "eslint-plugin-react": "7.7.0", + "eslint-plugin-jsx-a11y": "^6.2.1", + "eslint-plugin-react": "^7.12.4", "requireindex": "^1.2.0" } }, @@ -3323,6 +3323,16 @@ } } }, + "aria-query": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz", + "integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=", + "dev": true, + "requires": { + "ast-types-flow": "0.0.7", + "commander": "^2.11.0" + } + }, "arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", @@ -3610,6 +3620,15 @@ "axe-core": "^3.1.2" } }, + "axobject-query": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz", + "integrity": "sha512-MCeek8ZH7hKyO1rWUbKNQBbl4l2eY0ntk7OGi+q0RlafrCnfPxC06WZA+uebCfmYp4mNU9jRBP1AhGyf8+W3ww==", + "dev": true, + "requires": { + "ast-types-flow": "0.0.7" + } + }, "babel-eslint": { "version": "8.0.3", "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-8.0.3.tgz", @@ -7280,9 +7299,9 @@ } }, "emoji-regex": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.5.1.tgz", - "integrity": "sha512-PAHp6TxrCy7MGMFidro8uikr+zlJJKJ/Q6mm2ExZ7HwkyR9lSVFfE3kt36qcwa24BQL7y0G9axycGjK1A/0uNQ==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true }, "emojis-list": { @@ -7722,67 +7741,66 @@ "dev": true }, "eslint-plugin-jsx-a11y": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.0.2.tgz", - "integrity": "sha1-ZZJ3p1iwNsMFp+ShMFfDAc075z8=", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.1.tgz", + "integrity": "sha512-cjN2ObWrRz0TTw7vEcGQrx+YltMvZoOEx4hWU8eEERDnBIU00OTq7Vr+jA7DFKxiwLNv4tTh5Pq2GUNEa8b6+w==", "dev": true, "requires": { - "aria-query": "^0.7.0", + "aria-query": "^3.0.0", "array-includes": "^3.0.3", - "ast-types-flow": "0.0.7", - "axobject-query": "^0.1.0", - "damerau-levenshtein": "^1.0.0", - "emoji-regex": "^6.1.0", - "jsx-ast-utils": "^1.4.0" - }, - "dependencies": { - "aria-query": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-0.7.1.tgz", - "integrity": "sha1-Jsu1r/ZBRLCoJb4YRuCxbPoAsR4=", - "dev": true, - "requires": { - "ast-types-flow": "0.0.7", - "commander": "^2.11.0" - } - }, - "axobject-query": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-0.1.0.tgz", - "integrity": "sha1-YvWdvFnJ+SQnWco0mWDnov48NsA=", - "dev": true, - "requires": { - "ast-types-flow": "0.0.7" - } - }, - "jsx-ast-utils": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz", - "integrity": "sha1-OGchPo3Xm/Ho8jAMDPwe+xgsDfE=", - "dev": true - } + "ast-types-flow": "^0.0.7", + "axobject-query": "^2.0.2", + "damerau-levenshtein": "^1.0.4", + "emoji-regex": "^7.0.2", + "has": "^1.0.3", + "jsx-ast-utils": "^2.0.1" } }, "eslint-plugin-react": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.7.0.tgz", - "integrity": "sha512-KC7Snr4YsWZD5flu6A5c0AcIZidzW3Exbqp7OT67OaD2AppJtlBr/GuPrW/vaQM/yfZotEvKAdrxrO+v8vwYJA==", + "version": "7.12.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.12.4.tgz", + "integrity": "sha512-1puHJkXJY+oS1t467MjbqjvX53uQ05HXwjqDgdbGBqf5j9eeydI54G3KwiJmWciQ0HTBacIKw2jgwSBSH3yfgQ==", "dev": true, "requires": { - "doctrine": "^2.0.2", - "has": "^1.0.1", + "array-includes": "^3.0.3", + "doctrine": "^2.1.0", + "has": "^1.0.3", "jsx-ast-utils": "^2.0.1", - "prop-types": "^15.6.0" + "object.fromentries": "^2.0.0", + "prop-types": "^15.6.2", + "resolve": "^1.9.0" }, "dependencies": { + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, "prop-types": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", - "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", "dev": true, "requires": { - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + }, + "react-is": { + "version": "16.8.3", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.3.tgz", + "integrity": "sha512-Y4rC1ZJmsxxkkPuMLwvKvlL1Zfpbcu+Bf4ZigkHup3v9EfdYhAlWAaVyA19olXq2o2mGn0w+dFKvk3pVVlYcIA==", + "dev": true + }, + "resolve": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", + "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" } } } diff --git a/packages/block-library/src/categories/edit.js b/packages/block-library/src/categories/edit.js index 69e70a2596f25..faee47015394a 100644 --- a/packages/block-library/src/categories/edit.js +++ b/packages/block-library/src/categories/edit.js @@ -86,7 +86,7 @@ class CategoriesEdit extends Component { return ( <li key={ category.id }> - <a href={ category.link } target="_blank">{ this.renderCategoryName( category ) }</a> + <a href={ category.link } target="_blank" rel="noreferrer noopener">{ this.renderCategoryName( category ) }</a> { showPostCounts && <span className="wp-block-categories__post-count"> { ' ' }({ category.count }) diff --git a/packages/block-library/src/latest-posts/edit.js b/packages/block-library/src/latest-posts/edit.js index f5f8bc5d121d0..2e0f00e32eea3 100644 --- a/packages/block-library/src/latest-posts/edit.js +++ b/packages/block-library/src/latest-posts/edit.js @@ -178,7 +178,7 @@ class LatestPostsEdit extends Component { const titleTrimmed = post.title.rendered.trim(); return ( <li key={ i }> - <a href={ post.link } target="_blank"> + <a href={ post.link } target="_blank" rel="noreferrer noopener"> { titleTrimmed ? ( <RawHTML> { titleTrimmed } diff --git a/packages/components/src/external-link/index.js b/packages/components/src/external-link/index.js index 89ad1d80160a1..6b57f3978fd5f 100644 --- a/packages/components/src/external-link/index.js +++ b/packages/components/src/external-link/index.js @@ -24,7 +24,7 @@ export function ExternalLink( { href, children, className, rel = '', ...addition ] ) ).join( ' ' ); const classes = classnames( 'components-external-link', className ); return ( - <a { ...additionalProps } className={ classes } href={ href } target="_blank" rel={ rel } ref={ ref }> + <a { ...additionalProps } className={ classes } href={ href } target="_blank" rel={ rel } ref={ ref }> { /* eslint-disable-line react/jsx-no-target-blank */ } { children } <span className="screen-reader-text"> { diff --git a/packages/eslint-plugin/configs/custom.js b/packages/eslint-plugin/configs/custom.js index 82ba39265eb43..d11c4ef232190 100644 --- a/packages/eslint-plugin/configs/custom.js +++ b/packages/eslint-plugin/configs/custom.js @@ -23,4 +23,9 @@ module.exports = { }, ], }, + settings: { + react: { + version: '16.6', + }, + }, }; diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 3d79f48f78fb7..a9892892b634a 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -19,8 +19,8 @@ }, "dependencies": { "babel-eslint": "^8.0.3", - "eslint-plugin-jsx-a11y": "6.0.2", - "eslint-plugin-react": "7.7.0", + "eslint-plugin-jsx-a11y": "^6.2.1", + "eslint-plugin-react": "^7.12.4", "requireindex": "^1.2.0" }, "publishConfig": { From b421fc9574a7f437fb5068f8b6100db8f2926956 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Fri, 8 Mar 2019 11:15:00 +0000 Subject: [PATCH 613/691] Try Legacy widget block (#13511) ## Description Implements: https://github.com/WordPress/gutenberg/issues/4770 This PR is a **proof of concept** of a legacy widget block. A block that allows existing WordPress widgets to be added as Gutenberg blocks. The design is similar to the one proposed by @melchoyce in https://github.com/WordPress/gutenberg/issues/4770#issuecomment-383995334 (option 1). Although it seems option two was preferred, it would require to expose each widget as a block similar to what embeds do, and it would increase the technical complexity and make testing/debugging harder, so I preferred to use option 1 for now. I will gladly iterate on the UX after this proof of concept gets more stable. ## Some technical details The available widgets are preloaded to Gutenberg similar to what happens with page templates. ### REST-API user story I want to able to pass a widget identifier to an endpoint, pass the existing widget attributes and the changes the user is making if any, and receive from the rest API, the sanitized new widget attributes ready to save and an HTML form that allows the user to edit the widget in the new state. ### REST-API endpoint A very simple REST-API endpoint was implemented. The endpoint receives the previous instance of a widget (previous attributes) the changed instance of a widget (changed attributes if any) and returns the new instance of the widget and the HTML form that allows editing this widget. There are two ajax-admin endpoints save-widget and update-widget. It looks like each one has specificities that make using it here complex. The ajax admin code would probably require some changes to be used here. Our use case is straightforward from the backend perspective as the widget does not need to be saved anywhere, and the widget is not associated with any widget area. The most straightforward approach seemed to be using a very simple endpoint. If I missed something and adapting existing endpoints is simpler feel free to comment, and I will have a look. ### Block Architecture The edit component of the block handles the start placeholder that allows selecting a widget, and the tab mechanism that allows switching between edit and preview. The preview is done using the ServerSideRender component. The edit is done using two components: **WidgetEditHandler:** Is responsible for server communication using the endpoint we created, and for keeping the required local state for the edition. Renders an update button, when pressed we retrieve from the dom the changed fields (using a method provided by WidgetEditDomManager) issues a request to the server and updates the legacy widget instance attribute with the server answer. **WidgetEditDomManager:** Component responsible for rendering the starting dom for a widget. After the first render React never rerenders the component again, the content rendered by this component is then managed by the scripts the widgets may implement. When a new instance of the form HTML is received we manually update the dom changing .widget-content (like the customizer and the widget screen) do. This component provides a method that returns an object with the widget changed attributes. When this component is mounted it triggers a widget-added event when a new update happens and the dom is changed by the component widget-update jQuery event is triggered. On front end widget are rendered with a simple call to [the_widget](https://codex.wordpress.org/Function_Reference/the_widget). ## Screenshots <!-- if applicable --> ![jan-25-2019 19-37-44](https://user-images.githubusercontent.com/11271197/51768637-c3b5b100-20d8-11e9-941f-00adb4c7b0a1.gif) ![jan-25-2019 18-58-49](https://user-images.githubusercontent.com/11271197/51768645-cb755580-20d8-11e9-89e7-1aa9ba7256c3.gif) ## Known problems - The block is not aware of any change inside the widget until the update button is pressed. This replicates the save button on the widgets screen. But it is annoying if we change something on the widget and go to the widget preview right away our changes are not reflected in the preview. Having an explicit update, makes testing and debugging easier, we may than explore other approaches e.g.: also save when preview happens, save on blur events, etc. - The text widget that contains TinyMCE crashes and fails to init. It calls wp.editor.initialize to reference TinyMCE and on Gutenberg, wp.editor is our editor module. This problem may have happened with meta boxes if it was solved probably the same approach may be applied. - The widget design may be affected by CSS that exists in Gutenberg, so the design of the widgets does not look the same. Ideally, Gutenberg CSS would not affect the widgets but as they are on the same page that's not the case. - Some third-party widgets using don't initialize correctly. That happens because the dom of the editor is not equal to the dom of the customizer and/or the widget screen. Some JS widgets use click events on the widgets screen to initialize while on Gutenberg these events don't happen, some check if they are on the customizer page (body contains customizer classes) before handling widget-updated events. Normally adapting a widget that does not initialize correctly is a matter of changing a very simple condition on the plugin. --- gutenberg.php | 26 +++ ...lass-wp-rest-widget-updater-controller.php | 186 +++++++++++++++++ lib/client-assets.php | 85 ++++++-- lib/load.php | 21 ++ lib/rest-api.php | 19 ++ packages/block-editor/README.md | 30 +-- packages/block-editor/src/store/defaults.js | 33 +-- packages/block-library/README.md | 2 +- packages/block-library/src/editor.scss | 1 + packages/block-library/src/index.js | 2 + .../src/legacy-widget/WidgetEditDomManager.js | 143 +++++++++++++ .../src/legacy-widget/WidgetEditHandler.js | 122 +++++++++++ .../block-library/src/legacy-widget/edit.js | 195 ++++++++++++++++++ .../src/legacy-widget/editor.scss | 25 +++ .../block-library/src/legacy-widget/index.js | 33 +++ .../block-library/src/legacy-widget/index.php | 78 +++++++ .../fixtures/blocks/core__legacy-widget.html | 1 + .../fixtures/blocks/core__legacy-widget.json | 10 + .../blocks/core__legacy-widget.parsed.json | 18 ++ .../core__legacy-widget.serialized.html | 1 + .../editor/src/components/provider/index.js | 2 + 21 files changed, 986 insertions(+), 47 deletions(-) create mode 100644 lib/class-wp-rest-widget-updater-controller.php create mode 100644 packages/block-library/src/legacy-widget/WidgetEditDomManager.js create mode 100644 packages/block-library/src/legacy-widget/WidgetEditHandler.js create mode 100644 packages/block-library/src/legacy-widget/edit.js create mode 100644 packages/block-library/src/legacy-widget/editor.scss create mode 100644 packages/block-library/src/legacy-widget/index.js create mode 100644 packages/block-library/src/legacy-widget/index.php create mode 100644 packages/e2e-tests/fixtures/blocks/core__legacy-widget.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__legacy-widget.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__legacy-widget.parsed.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__legacy-widget.serialized.html diff --git a/gutenberg.php b/gutenberg.php index 6253c9a3bf0e4..cbb0375d2a760 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -46,6 +46,21 @@ function the_gutenberg_project() { <div id="metaboxes" class="hidden"> <?php the_block_editor_meta_boxes(); ?> </div> + <?php + /** + * Start: Include for phase 2 + */ + /** This action is documented in wp-admin/admin-footer.php */ + // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores + do_action( 'admin_print_footer_scripts-widgets.php' ); + + /** This action is documented in wp-admin/admin-footer.php */ + // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores + do_action( 'admin_footer-widgets.php' ); + /** + * End: Include for phase 2 + */ + ?> </div> <?php } @@ -223,6 +238,17 @@ function gutenberg_init( $return, $post ) { */ remove_action( 'admin_print_scripts', 'print_emoji_detection_script' ); + /** + * Start: Include for phase 2 + */ + // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores + do_action( 'admin_print_styles-widgets.php' ); + // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores + do_action( 'admin_print_scripts-widgets.php' ); + /** + * End: Include for phase 2 + */ + /* * Ensure meta box functions are available to third-party code; * includes/meta-boxes is typically loaded from edit-form-advanced.php. diff --git a/lib/class-wp-rest-widget-updater-controller.php b/lib/class-wp-rest-widget-updater-controller.php new file mode 100644 index 0000000000000..23c46887ba573 --- /dev/null +++ b/lib/class-wp-rest-widget-updater-controller.php @@ -0,0 +1,186 @@ +<?php +/** + * Start: Include for phase 2 + * Widget Updater REST API: WP_REST_Widget_Updater_Controller class + * + * @package gutenberg + * @since 5.2.0 + */ + +/** + * Controller which provides REST endpoint for updating a widget. + * + * @since 5.2.0 + * + * @see WP_REST_Controller + */ +class WP_REST_Widget_Updater_Controller extends WP_REST_Controller { + + /** + * Constructs the controller. + * + * @access public + */ + public function __construct() { + $this->namespace = 'wp/v2'; + $this->rest_base = 'widgets'; + } + + /** + * Registers the necessary REST API route. + * + * @access public + */ + public function register_routes() { + register_rest_route( + $this->namespace, + // Regex representing a PHP class extracted from http://php.net/manual/en/language.oop5.basic.php. + '/' . $this->rest_base . '/(?P<identifier>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)/', + array( + 'args' => array( + 'identifier' => array( + 'description' => __( 'Class name of the widget.', 'gutenberg' ), + 'type' => 'string', + ), + ), + array( + 'methods' => WP_REST_Server::EDITABLE, + 'permission_callback' => array( $this, 'compute_new_widget_permissions_check' ), + 'callback' => array( $this, 'compute_new_widget' ), + ), + ) + ); + } + + /** + * Checks if the user has permissions to make the request. + * + * @since 5.2.0 + * @access public + * + * @return true|WP_Error True if the request has read access, WP_Error object otherwise. + */ + public function compute_new_widget_permissions_check() { + // Verify if the current user has edit_theme_options capability. + // This capability is required to access the widgets screen. + if ( ! current_user_can( 'edit_theme_options' ) ) { + return new WP_Error( + 'widgets_cannot_access', + __( 'Sorry, you are not allowed to access widgets on this site.', 'gutenberg' ), + array( + 'status' => rest_authorization_required_code(), + ) + ); + } + return true; + } + + /** + * Returns the new widget instance and the form that represents it. + * + * @since 5.2.0 + * @access public + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ + public function compute_new_widget( $request ) { + $url_params = $request->get_url_params(); + + $widget = $request->get_param( 'identifier' ); + + global $wp_widget_factory; + + if ( + null === $widget || + ! isset( $wp_widget_factory->widgets[ $widget ] ) || + ! ( $wp_widget_factory->widgets[ $widget ] instanceof WP_Widget ) + ) { + return new WP_Error( + 'widget_invalid', + __( 'Invalid widget.', 'gutenberg' ), + array( + 'status' => 404, + ) + ); + } + + $widget_obj = $wp_widget_factory->widgets[ $widget ]; + + $instance = $request->get_param( 'instance' ); + if ( null === $instance ) { + $instance = array(); + } + $id_to_use = $request->get_param( 'id_to_use' ); + if ( null === $id_to_use ) { + $id_to_use = -1; + } + + $widget_obj->_set( $id_to_use ); + ob_start(); + + $instance_changes = $request->get_param( 'instance_changes' ); + if ( null !== $instance_changes ) { + $old_instance = $instance; + $instance = $widget_obj->update( $instance_changes, $old_instance ); + /** + * Filters a widget's settings before saving. + * + * Returning false will effectively short-circuit the widget's ability + * to update settings. The old setting will be returned. + * + * @since 5.2.0 + * + * @param array $instance The current widget instance's settings. + * @param array $instance_changes Array of new widget settings. + * @param array $old_instance Array of old widget settings. + * @param WP_Widget $widget_ob The widget instance. + */ + $instance = apply_filters( 'widget_update_callback', $instance, $instance_changes, $old_instance, $widget_obj ); + if ( false === $instance ) { + $instance = $old_instance; + } + } + + $instance = apply_filters( 'widget_form_callback', $instance, $widget_obj ); + + $return = null; + if ( false !== $instance ) { + $return = $widget_obj->form( $instance ); + + /** + * Fires at the end of the widget control form. + * + * Use this hook to add extra fields to the widget form. The hook + * is only fired if the value passed to the 'widget_form_callback' + * hook is not false. + * + * Note: If the widget has no form, the text echoed from the default + * form method can be hidden using CSS. + * + * @since 5.2.0 + * + * @param WP_Widget $widget_obj The widget instance (passed by reference). + * @param null $return Return null if new fields are added. + * @param array $instance An array of the widget's settings. + */ + do_action_ref_array( 'in_widget_form', array( &$widget_obj, &$return, $instance ) ); + } + + $id_base = $widget_obj->id_base; + $id = $widget_obj->id; + $form = ob_get_clean(); + + return rest_ensure_response( + array( + 'instance' => $instance, + 'form' => $form, + 'id_base' => $id_base, + 'id' => $id, + ) + ); + } +} +/** + * End: Include for phase 2 + */ diff --git a/lib/client-assets.php b/lib/client-assets.php index 7839df9dbb3ea..801aefe8c9177 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -880,26 +880,75 @@ function gutenberg_editor_scripts_and_styles( $hook ) { ); } + /** + * Start: Include for phase 2 + */ + + /** + * Todo: The hardcoded array should be replaced with a mechanisms that allows core blocks + * and third party blocks to specify they already have equivalent blocks, and maybe even allow them + * to have a migration function. + */ + $core_widgets = array( 'WP_Widget_Pages', 'WP_Widget_Calendar', 'WP_Widget_Archives', 'WP_Widget_Media_Audio', 'WP_Widget_Media_Image', 'WP_Widget_Media_Gallery', 'WP_Widget_Media_Video', 'WP_Widget_Meta', 'WP_Widget_Search', 'WP_Widget_Text', 'WP_Widget_Categories', 'WP_Widget_Recent_Posts', 'WP_Widget_Recent_Comments', 'WP_Widget_RSS', 'WP_Widget_Tag_Cloud', 'WP_Nav_Menu_Widget', 'WP_Widget_Custom_HTML' ); + + $has_permissions_to_manage_widgets = current_user_can( 'edit_theme_options' ); + $available_legacy_widgets = array(); + global $wp_widget_factory, $wp_registered_widgets; + foreach ( $wp_widget_factory->widgets as $class => $widget_obj ) { + if ( ! in_array( $class, $core_widgets ) ) { + $available_legacy_widgets[ $class ] = array( + 'name' => html_entity_decode( $widget_obj->name ), + 'description' => html_entity_decode( $widget_obj->widget_options['description'] ), + 'isCallbackWidget' => false, + ); + } + } + foreach ( $wp_registered_widgets as $widget_id => $widget_obj ) { + if ( + is_array( $widget_obj['callback'] ) && + isset( $widget_obj['callback'][0] ) && + ( $widget_obj['callback'][0] instanceof WP_Widget ) + ) { + continue; + } + $available_legacy_widgets[ $widget_id ] = array( + 'name' => html_entity_decode( $widget_obj['name'] ), + 'description' => null, + 'isCallbackWidget' => true, + ); + } + /** + * End: Include for phase 2 + */ + $editor_settings = array( - 'alignWide' => $align_wide, - 'availableTemplates' => $available_templates, - 'allowedBlockTypes' => $allowed_block_types, - 'disableCustomColors' => get_theme_support( 'disable-custom-colors' ), - 'disableCustomFontSizes' => get_theme_support( 'disable-custom-font-sizes' ), - 'disablePostFormats' => ! current_theme_supports( 'post-formats' ), - 'titlePlaceholder' => apply_filters( 'enter_title_here', __( 'Add title', 'gutenberg' ), $post ), - 'bodyPlaceholder' => apply_filters( 'write_your_story', __( 'Start writing or type / to choose a block', 'gutenberg' ), $post ), - 'isRTL' => is_rtl(), - 'autosaveInterval' => 10, - 'maxUploadFileSize' => $max_upload_size, - 'allowedMimeTypes' => get_allowed_mime_types(), - 'styles' => $styles, - 'imageSizes' => gutenberg_get_available_image_sizes(), - 'richEditingEnabled' => user_can_richedit(), + 'alignWide' => $align_wide, + 'availableTemplates' => $available_templates, + /** + * Start: Include for phase 2 + */ + 'hasPermissionsToManageWidgets' => $has_permissions_to_manage_widgets, + 'availableLegacyWidgets' => $available_legacy_widgets, + /** + * End: Include for phase 2 + */ + 'allowedBlockTypes' => $allowed_block_types, + 'disableCustomColors' => get_theme_support( 'disable-custom-colors' ), + 'disableCustomFontSizes' => get_theme_support( 'disable-custom-font-sizes' ), + 'disablePostFormats' => ! current_theme_supports( 'post-formats' ), + 'titlePlaceholder' => apply_filters( 'enter_title_here', __( 'Add title', 'gutenberg' ), $post ), + 'bodyPlaceholder' => apply_filters( 'write_your_story', __( 'Start writing or type / to choose a block', 'gutenberg' ), $post ), + 'isRTL' => is_rtl(), + 'autosaveInterval' => 10, + 'maxUploadFileSize' => $max_upload_size, + 'allowedMimeTypes' => get_allowed_mime_types(), + 'styles' => $styles, + 'imageSizes' => gutenberg_get_available_image_sizes(), + 'richEditingEnabled' => user_can_richedit(), // Ideally, we'd remove this and rely on a REST API endpoint. - 'postLock' => $lock_details, - 'postLockUtils' => array( + 'postLock' => $lock_details, + 'postLockUtils' => array( 'nonce' => wp_create_nonce( 'lock-post_' . $post->ID ), 'unlockNonce' => wp_create_nonce( 'update-post_' . $post->ID ), 'ajaxUrl' => admin_url( 'admin-ajax.php' ), @@ -907,7 +956,7 @@ function gutenberg_editor_scripts_and_styles( $hook ) { // Whether or not to load the 'postcustom' meta box is stored as a user meta // field so that we're not always loading its assets. - 'enableCustomFields' => (bool) get_user_meta( get_current_user_id(), 'enable_custom_fields', true ), + 'enableCustomFields' => (bool) get_user_meta( get_current_user_id(), 'enable_custom_fields', true ), ); $post_autosave = gutenberg_get_autosave_newer_than_post_save( $post ); diff --git a/lib/load.php b/lib/load.php index 999523ed52ff6..cb6f3c5cee300 100644 --- a/lib/load.php +++ b/lib/load.php @@ -12,6 +12,15 @@ // These files only need to be loaded if within a rest server instance // which this class will exist if that is the case. if ( class_exists( 'WP_REST_Controller' ) ) { + /** + * Start: Include for phase 2 + */ + if ( ! class_exists( 'WP_REST_Widget_Updater_Controller' ) ) { + require dirname( __FILE__ ) . '/class-wp-rest-widget-updater-controller.php'; + } + /** + * End: Include for phase 2 + */ require dirname( __FILE__ ) . '/rest-api.php'; } @@ -43,6 +52,18 @@ if ( ! function_exists( 'render_block_core_latest_posts' ) ) { require dirname( __FILE__ ) . '/../packages/block-library/src/latest-posts/index.php'; } + + +/** + * Start: Include for phase 2 + */ +if ( ! function_exists( 'render_block_legacy_widget' ) ) { + require dirname( __FILE__ ) . '/../packages/block-library/src/legacy-widget/index.php'; +} +/** + * End: Include for phase 2 + */ + if ( ! function_exists( 'render_block_core_rss' ) ) { require dirname( __FILE__ ) . '/../packages/block-library/src/rss/index.php'; } diff --git a/lib/rest-api.php b/lib/rest-api.php index 1b36ba7df9b38..eaccc09c61978 100644 --- a/lib/rest-api.php +++ b/lib/rest-api.php @@ -51,3 +51,22 @@ function gutenberg_filter_oembed_result( $response, $handler, $request ) { ); } add_filter( 'rest_request_after_callbacks', 'gutenberg_filter_oembed_result', 10, 3 ); + + + +/** + * Start: Include for phase 2 + */ +/** + * Registers the REST API routes needed by the legacy widget block. + * + * @since 5.0.0 + */ +function gutenberg_register_rest_widget_updater_routes() { + $widgets_controller = new WP_REST_Widget_Updater_Controller(); + $widgets_controller->register_routes(); +} +add_action( 'rest_api_init', 'gutenberg_register_rest_widget_updater_routes' ); +/** + * End: Include for phase 2 + */ diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index 7686f0fba4f18..1d1089561ed2a 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -363,20 +363,22 @@ Undocumented declaration. The default editor settings - alignWide boolean Enable/Disable Wide/Full Alignments - colors Array Palette colors - disableCustomColors boolean Whether or not the custom colors are disabled - fontSizes Array Available font sizes - disableCustomFontSizes boolean Whether or not the custom font sizes are disabled - imageSizes Array Available image sizes - maxWidth number Max width to constraint resizing - allowedBlockTypes boolean|Array Allowed block types - hasFixedToolbar boolean Whether or not the editor toolbar is fixed - focusMode boolean Whether the focus mode is enabled or not - styles Array Editor Styles - isRTL boolean Whether the editor is in RTL mode - bodyPlaceholder string Empty post placeholder - titlePlaceholder string Empty title placeholder + alignWide boolean Enable/Disable Wide/Full Alignments + availableLegacyWidgets Array Array of objects representing the legacy widgets available. + colors Array Palette colors + disableCustomColors boolean Whether or not the custom colors are disabled + fontSizes Array Available font sizes + disableCustomFontSizes boolean Whether or not the custom font sizes are disabled + imageSizes Array Available image sizes + maxWidth number Max width to constraint resizing + allowedBlockTypes boolean|Array Allowed block types + hasFixedToolbar boolean Whether or not the editor toolbar is fixed + hasPermissionsToManageWidgets boolean Whether or not the user is able to manage widgets. + focusMode boolean Whether the focus mode is enabled or not + styles Array Editor Styles + isRTL boolean Whether the editor is in RTL mode + bodyPlaceholder string Empty post placeholder + titlePlaceholder string Empty title placeholder ### SkipToSelectedBlock diff --git a/packages/block-editor/src/store/defaults.js b/packages/block-editor/src/store/defaults.js index 728b500a51d59..14c114216f12a 100644 --- a/packages/block-editor/src/store/defaults.js +++ b/packages/block-editor/src/store/defaults.js @@ -10,20 +10,22 @@ export const PREFERENCES_DEFAULTS = { /** * The default editor settings * - * alignWide boolean Enable/Disable Wide/Full Alignments - * colors Array Palette colors - * disableCustomColors boolean Whether or not the custom colors are disabled - * fontSizes Array Available font sizes - * disableCustomFontSizes boolean Whether or not the custom font sizes are disabled - * imageSizes Array Available image sizes - * maxWidth number Max width to constraint resizing - * allowedBlockTypes boolean|Array Allowed block types - * hasFixedToolbar boolean Whether or not the editor toolbar is fixed - * focusMode boolean Whether the focus mode is enabled or not - * styles Array Editor Styles - * isRTL boolean Whether the editor is in RTL mode - * bodyPlaceholder string Empty post placeholder - * titlePlaceholder string Empty title placeholder + * alignWide boolean Enable/Disable Wide/Full Alignments + * availableLegacyWidgets Array Array of objects representing the legacy widgets available. + * colors Array Palette colors + * disableCustomColors boolean Whether or not the custom colors are disabled + * fontSizes Array Available font sizes + * disableCustomFontSizes boolean Whether or not the custom font sizes are disabled + * imageSizes Array Available image sizes + * maxWidth number Max width to constraint resizing + * allowedBlockTypes boolean|Array Allowed block types + * hasFixedToolbar boolean Whether or not the editor toolbar is fixed + * hasPermissionsToManageWidgets boolean Whether or not the user is able to manage widgets. + * focusMode boolean Whether the focus mode is enabled or not + * styles Array Editor Styles + * isRTL boolean Whether the editor is in RTL mode + * bodyPlaceholder string Empty post placeholder + * titlePlaceholder string Empty title placeholder */ export const SETTINGS_DEFAULTS = { alignWide: false, @@ -131,5 +133,8 @@ export const SETTINGS_DEFAULTS = { // List of allowed mime types and file extensions. allowedMimeTypes: null, + + availableLegacyWidgets: {}, + hasPermissionsToManageWidgets: false, }; diff --git a/packages/block-library/README.md b/packages/block-library/README.md index f21ffbea41303..78573bbf753e7 100644 --- a/packages/block-library/README.md +++ b/packages/block-library/README.md @@ -18,7 +18,7 @@ _This package assumes that your code will run in an **ES2015+** environment. If ### registerCoreBlocks -[src/index.js#L69-L130](src/index.js#L69-L130) +[src/index.js#L70-L132](src/index.js#L70-L132) Function to register core blocks provided by the block editor. diff --git a/packages/block-library/src/editor.scss b/packages/block-library/src/editor.scss index 517218be5e715..8422d2c82571d 100644 --- a/packages/block-library/src/editor.scss +++ b/packages/block-library/src/editor.scss @@ -14,6 +14,7 @@ @import "./image/editor.scss"; @import "./latest-comments/editor.scss"; @import "./latest-posts/editor.scss"; +@import "./legacy-widget/editor.scss"; @import "./media-text/editor.scss"; @import "./list/editor.scss"; @import "./more/editor.scss"; diff --git a/packages/block-library/src/index.js b/packages/block-library/src/index.js index 1fc00265bc875..81a0e9bec632b 100644 --- a/packages/block-library/src/index.js +++ b/packages/block-library/src/index.js @@ -34,6 +34,7 @@ import * as html from './html'; import * as mediaText from './media-text'; import * as latestComments from './latest-comments'; import * as latestPosts from './latest-posts'; +import * as legacyWidget from './legacy-widget'; import * as list from './list'; import * as missing from './missing'; import * as more from './more'; @@ -97,6 +98,7 @@ export const registerCoreBlocks = () => { mediaText, latestComments, latestPosts, + process.env.GUTENBERG_PHASE === 2 ? legacyWidget : null, missing, more, nextpage, diff --git a/packages/block-library/src/legacy-widget/WidgetEditDomManager.js b/packages/block-library/src/legacy-widget/WidgetEditDomManager.js new file mode 100644 index 0000000000000..30ded7a7a7a6f --- /dev/null +++ b/packages/block-library/src/legacy-widget/WidgetEditDomManager.js @@ -0,0 +1,143 @@ +/** + * External dependencies + */ +import { includes } from 'lodash'; + +/** + * WordPress dependencies + */ +import { Component, createRef } from '@wordpress/element'; +import isShallowEqual from '@wordpress/is-shallow-equal'; + +class WidgetEditDomManager extends Component { + constructor() { + super( ...arguments ); + + this.containerRef = createRef(); + this.formRef = createRef(); + this.widgetContentRef = createRef(); + this.triggerWidgetEvent = this.triggerWidgetEvent.bind( this ); + } + + componentDidMount() { + this.triggerWidgetEvent( 'widget-added' ); + this.previousFormData = new window.FormData( + this.formRef.current + ); + } + + shouldComponentUpdate( nextProps ) { + // We can not leverage react render otherwise we would destroy dom changes applied by the plugins. + // We manually update the required dom node replicating what the widget screen and the customizer do. + if ( nextProps.form !== this.props.form && this.widgetContentRef.current ) { + const widgetContent = this.widgetContentRef.current; + widgetContent.innerHTML = nextProps.form; + this.triggerWidgetEvent( 'widget-updated' ); + this.previousFormData = new window.FormData( + this.formRef.current + ); + } + return false; + } + + render() { + const { id, idBase, widgetNumber, form } = this.props; + return ( + <div className="widget open" ref={ this.containerRef }> + <div className="widget-inside"> + <form + ref={ this.formRef } + method="post" + onBlur={ () => { + if ( this.shouldTriggerInstanceUpdate() ) { + this.props.onInstanceChange( + this.retrieveUpdatedInstance() + ); + } + } } + > + <div + ref={ this.widgetContentRef } + className="widget-content" + dangerouslySetInnerHTML={ { __html: form } } + /> + <input type="hidden" name="widget-id" className="widget-id" value={ id } /> + <input type="hidden" name="id_base" className="id_base" value={ idBase } /> + <input type="hidden" name="widget_number" className="widget_number" value={ widgetNumber } /> + <input type="hidden" name="multi_number" className="multi_number" value="" /> + <input type="hidden" name="add_new" className="add_new" value="" /> + </form> + </div> + </div> + ); + } + + shouldTriggerInstanceUpdate() { + if ( ! this.formRef.current ) { + return false; + } + if ( ! this.previousFormData ) { + return true; + } + const currentFormData = new window.FormData( + this.formRef.current + ); + const currentFormDataKeys = Array.from( currentFormData.keys() ); + const previousFormDataKeys = Array.from( this.previousFormData.keys() ); + if ( + currentFormDataKeys.length !== previousFormDataKeys.length + ) { + return true; + } + for ( const rawKey of currentFormDataKeys ) { + if ( ! isShallowEqual( + currentFormData.getAll( rawKey ), + this.previousFormData.getAll( rawKey ) + ) ) { + this.previousFormData = currentFormData; + return true; + } + } + return false; + } + + triggerWidgetEvent( event ) { + window.$( window.document ).trigger( + event, + [ window.$( this.containerRef.current ) ] + ); + } + + retrieveUpdatedInstance() { + if ( this.formRef.current ) { + const { idBase, widgetNumber } = this.props; + const form = this.formRef.current; + const formData = new window.FormData( form ); + const updatedInstance = {}; + const keyPrefixLength = `widget-${ idBase }[${ widgetNumber }][`.length; + const keySuffixLength = `]`.length; + for ( const rawKey of formData.keys() ) { + // This fields are added to the form because the widget JavaScript code may use this values. + // They are not relevant for the update mechanism. + if ( includes( + [ 'widget-id', 'id_base', 'widget_number', 'multi_number', 'add_new' ], + rawKey, + ) ) { + continue; + } + const keyParsed = rawKey.substring( keyPrefixLength, rawKey.length - keySuffixLength ); + + const value = formData.getAll( rawKey ); + if ( value.length > 1 ) { + updatedInstance[ keyParsed ] = value; + } else { + updatedInstance[ keyParsed ] = value[ 0 ]; + } + } + return updatedInstance; + } + } +} + +export default WidgetEditDomManager; + diff --git a/packages/block-library/src/legacy-widget/WidgetEditHandler.js b/packages/block-library/src/legacy-widget/WidgetEditHandler.js new file mode 100644 index 0000000000000..527e864c90037 --- /dev/null +++ b/packages/block-library/src/legacy-widget/WidgetEditHandler.js @@ -0,0 +1,122 @@ +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import apiFetch from '@wordpress/api-fetch'; +import { withInstanceId } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import WidgetEditDomManager from './WidgetEditDomManager'; + +class WidgetEditHandler extends Component { + constructor() { + super( ...arguments ); + this.state = { + form: null, + idBase: null, + }; + this.instanceUpdating = null; + this.onInstanceChange = this.onInstanceChange.bind( this ); + this.requestWidgetUpdater = this.requestWidgetUpdater.bind( this ); + } + + componentDidMount() { + this.isStillMounted = true; + this.requestWidgetUpdater(); + } + + componentDidUpdate( prevProps ) { + if ( + prevProps.instance !== this.props.instance && + this.instanceUpdating !== this.props.instance + ) { + this.requestWidgetUpdater(); + } + if ( this.instanceUpdating === this.props.instance ) { + this.instanceUpdating = null; + } + } + + componentWillUnmount() { + this.isStillMounted = false; + } + + render() { + const { instanceId, identifier } = this.props; + const { id, idBase, form } = this.state; + if ( ! identifier ) { + return __( 'Not a valid widget.' ); + } + if ( ! form ) { + return null; + } + return ( + <div + className="wp-block-legacy-widget__edit-container" + // Display none is used because when we switch from edit to preview, + // we don't want to unmount the component. + // Otherwise when we went back to edit we wound need to trigger + // all widgets events again and some scripts may not deal well with this. + style={ { + display: this.props.isVisible ? 'block' : 'none', + } } + > + <WidgetEditDomManager + ref={ ( ref ) => { + this.widgetEditDomManagerRef = ref; + } } + onInstanceChange={ this.onInstanceChange } + widgetNumber={ instanceId * -1 } + id={ id } + idBase={ idBase } + form={ form } + /> + </div> + ); + } + + onInstanceChange( instanceChanges ) { + this.requestWidgetUpdater( instanceChanges, ( response ) => { + this.instanceUpdating = response.instance; + this.props.onInstanceChange( response.instance ); + } ); + } + + requestWidgetUpdater( instanceChanges, callback ) { + const { identifier, instanceId, instance } = this.props; + if ( ! identifier ) { + return; + } + + apiFetch( { + path: `/wp/v2/widgets/${ identifier }/`, + data: { + identifier, + instance, + // use negative ids to make sure the id does not exist on the database. + id_to_use: instanceId * -1, + instance_changes: instanceChanges, + }, + method: 'POST', + } ).then( + ( response ) => { + if ( this.isStillMounted ) { + this.setState( { + form: response.form, + idBase: response.id_base, + id: response.id, + } ); + if ( callback ) { + callback( response ); + } + } + } + ); + } +} + +export default withInstanceId( WidgetEditHandler ); + diff --git a/packages/block-library/src/legacy-widget/edit.js b/packages/block-library/src/legacy-widget/edit.js new file mode 100644 index 0000000000000..e2f2a4c4cf240 --- /dev/null +++ b/packages/block-library/src/legacy-widget/edit.js @@ -0,0 +1,195 @@ +/** + * External dependencies + */ +import { map } from 'lodash'; + +/** + * WordPress dependencies + */ +import { Component, Fragment } from '@wordpress/element'; +import { + Button, + IconButton, + PanelBody, + Placeholder, + SelectControl, + Toolbar, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { withSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { + BlockControls, + BlockIcon, + InspectorControls, + ServerSideRender, +} from '@wordpress/editor'; + +import WidgetEditHandler from './WidgetEditHandler'; + +class LegacyWidgetEdit extends Component { + constructor() { + super( ...arguments ); + this.state = { + isPreview: false, + }; + this.switchToEdit = this.switchToEdit.bind( this ); + this.switchToPreview = this.switchToPreview.bind( this ); + this.changeWidget = this.changeWidget.bind( this ); + } + + render() { + const { + attributes, + availableLegacyWidgets, + hasPermissionsToManageWidgets, + setAttributes, + } = this.props; + const { isPreview } = this.state; + const { identifier, isCallbackWidget } = attributes; + const widgetObject = identifier && availableLegacyWidgets[ identifier ]; + if ( ! widgetObject ) { + let placeholderContent; + + if ( ! hasPermissionsToManageWidgets ) { + placeholderContent = __( 'You don\'t have permissions to use widgets on this site.' ); + } else if ( availableLegacyWidgets.length === 0 ) { + placeholderContent = __( 'There are no widgets available.' ); + } else { + placeholderContent = ( + <SelectControl + label={ __( 'Select a legacy widget to display:' ) } + value={ identifier || 'none' } + onChange={ ( value ) => setAttributes( { + instance: {}, + identifier: value, + isCallbackWidget: availableLegacyWidgets[ value ].isCallbackWidget, + } ) } + options={ [ { value: 'none', label: 'Select widget' } ].concat( + map( availableLegacyWidgets, ( widget, key ) => { + return { + value: key, + label: widget.name, + }; + } ) + ) } + /> + ); + } + + return ( + <Placeholder + icon={ <BlockIcon icon="admin-customizer" /> } + label={ __( 'Legacy Widget' ) } + > + { placeholderContent } + </Placeholder> + ); + } + + const inspectorControls = ( + <InspectorControls> + <PanelBody title={ widgetObject.name }> + { widgetObject.description } + </PanelBody> + </InspectorControls> + ); + if ( ! hasPermissionsToManageWidgets ) { + return ( + <Fragment> + { inspectorControls } + { this.renderWidgetPreview() } + </Fragment> + ); + } + + return ( + <Fragment> + <BlockControls> + <Toolbar> + <IconButton + onClick={ this.changeWidget } + label={ __( 'Change widget' ) } + icon="update" + > + </IconButton> + { ! isCallbackWidget && ( + <Fragment> + <Button + className={ `components-tab-button ${ ! isPreview ? 'is-active' : '' }` } + onClick={ this.switchToEdit } + > + <span>{ __( 'Edit' ) }</span> + </Button> + <Button + className={ `components-tab-button ${ isPreview ? 'is-active' : '' }` } + onClick={ this.switchToPreview } + > + <span>{ __( 'Preview' ) }</span> + </Button> + </Fragment> + ) } + </Toolbar> + </BlockControls> + { inspectorControls } + { ! isCallbackWidget && ( + <WidgetEditHandler + isVisible={ ! isPreview } + identifier={ attributes.identifier } + instance={ attributes.instance } + onInstanceChange={ + ( newInstance ) => { + this.props.setAttributes( { + instance: newInstance, + } ); + } + } + /> + ) } + { ( isPreview || isCallbackWidget ) && this.renderWidgetPreview() } + </Fragment> + ); + } + + changeWidget() { + this.switchToEdit(); + this.props.setAttributes( { + instance: {}, + identifier: undefined, + } ); + } + + switchToEdit() { + this.setState( { isPreview: false } ); + } + + switchToPreview() { + this.setState( { isPreview: true } ); + } + + renderWidgetPreview() { + const { attributes } = this.props; + return ( + <ServerSideRender + className="wp-block-legacy-widget__preview" + block="core/legacy-widget" + attributes={ attributes } + /> + ); + } +} + +export default withSelect( ( select ) => { + const editorSettings = select( 'core/block-editor' ).getSettings(); + const { + availableLegacyWidgets, + hasPermissionsToManageWidgets, + } = editorSettings; + return { + hasPermissionsToManageWidgets, + availableLegacyWidgets, + }; +} )( LegacyWidgetEdit ); diff --git a/packages/block-library/src/legacy-widget/editor.scss b/packages/block-library/src/legacy-widget/editor.scss new file mode 100644 index 0000000000000..f3b8b13b1e1f9 --- /dev/null +++ b/packages/block-library/src/legacy-widget/editor.scss @@ -0,0 +1,25 @@ +.wp-block-legacy-widget__edit-container, +.wp-block-legacy-widget__preview { + padding-left: 2.5em; + padding-right: 2.5em; +} + +.wp-block-legacy-widget__edit-container { + .widget-inside { + border: none; + display: block; + } +} + +.wp-block-legacy-widget__update-button { + margin-left: auto; + display: block; +} + +.wp-block-legacy-widget__edit-container .widget-inside { + box-shadow: none; +} + +.wp-block-legacy-widget__preview { + overflow: auto; +} diff --git a/packages/block-library/src/legacy-widget/index.js b/packages/block-library/src/legacy-widget/index.js new file mode 100644 index 0000000000000..43142ec5ca130 --- /dev/null +++ b/packages/block-library/src/legacy-widget/index.js @@ -0,0 +1,33 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { G, Path, SVG } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import edit from './edit'; + +export const name = 'core/legacy-widget'; + +export const settings = { + title: __( 'Legacy Widget (Experimental)' ), + + description: __( 'Display a legacy widget.' ), + + icon: <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><G><Path d="M7 11h2v2H7v-2zm14-5v14l-2 2H5l-2-2V6l2-2h1V2h2v2h8V2h2v2h1l2 2zM5 8h14V6H5v2zm14 12V10H5v10h14zm-4-7h2v-2h-2v2zm-4 0h2v-2h-2v2z" /></G></SVG>, + + category: 'widgets', + + supports: { + html: false, + }, + + edit, + + save() { + // Handled by PHP. + return null; + }, +}; diff --git a/packages/block-library/src/legacy-widget/index.php b/packages/block-library/src/legacy-widget/index.php new file mode 100644 index 0000000000000..b812f8e3c710f --- /dev/null +++ b/packages/block-library/src/legacy-widget/index.php @@ -0,0 +1,78 @@ +<?php +/** + * Server-side rendering of the `core/legacy-widget` block. + * + * @package WordPress + */ + +/** + * Renders the `core/legacy-widget` block on server. + * + * @see WP_Widget + * + * @param array $attributes The block attributes. + * + * @return string Returns the post content with the legacy widget added. + */ +function render_block_legacy_widget( $attributes ) { + if ( ! isset( $attributes['identifier'] ) ) { + return ''; + } + $identifier = $attributes['identifier']; + if ( + isset( $attributes['isCallbackWidget'] ) && + $attributes['isCallbackWidget'] + ) { + global $wp_registered_widgets; + if ( ! isset( $wp_registered_widgets[ $identifier ] ) ) { + return ''; + } + $widget = $wp_registered_widgets[ $identifier ]; + $params = array_merge( + array( + 'widget_id' => $identifier, + 'widget_name' => $widget['name'], + ), + (array) $wp_registered_widgets[ $identifier ]['params'] + ); + $params = apply_filters( 'dynamic_sidebar_params', $params ); + + $callback = $widget['callback']; + + if ( is_callable( $callback ) ) { + ob_start(); + call_user_func_array( $callback, $params ); + return ob_get_clean(); + } + return ''; + } + ob_start(); + the_widget( $identifier, $attributes['instance'] ); + return ob_get_clean(); + +} + +/** + * Register legacy widget block. + */ +function register_block_core_legacy_widget() { + register_block_type( + 'core/legacy-widget', + array( + 'attributes' => array( + 'identifier' => array( + 'type' => 'string', + ), + 'instance' => array( + 'type' => 'object', + ), + 'isCallbackWidget' => array( + 'type' => 'boolean', + ), + ), + 'render_callback' => 'render_block_legacy_widget', + ) + ); +} + +add_action( 'init', 'register_block_core_legacy_widget' ); diff --git a/packages/e2e-tests/fixtures/blocks/core__legacy-widget.html b/packages/e2e-tests/fixtures/blocks/core__legacy-widget.html new file mode 100644 index 0000000000000..a94242f7e4e6e --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__legacy-widget.html @@ -0,0 +1 @@ +<!-- wp:legacy-widget /--> diff --git a/packages/e2e-tests/fixtures/blocks/core__legacy-widget.json b/packages/e2e-tests/fixtures/blocks/core__legacy-widget.json new file mode 100644 index 0000000000000..c9e750dbaa066 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__legacy-widget.json @@ -0,0 +1,10 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/legacy-widget", + "isValid": true, + "attributes": {}, + "innerBlocks": [], + "originalContent": "" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__legacy-widget.parsed.json b/packages/e2e-tests/fixtures/blocks/core__legacy-widget.parsed.json new file mode 100644 index 0000000000000..93ea4bba8f175 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__legacy-widget.parsed.json @@ -0,0 +1,18 @@ +[ + { + "blockName": "core/legacy-widget", + "attrs": {}, + "innerBlocks": [], + "innerHTML": "", + "innerContent": [] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__legacy-widget.serialized.html b/packages/e2e-tests/fixtures/blocks/core__legacy-widget.serialized.html new file mode 100644 index 0000000000000..a94242f7e4e6e --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__legacy-widget.serialized.html @@ -0,0 +1 @@ +<!-- wp:legacy-widget /--> diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index 8817eb5d4be3b..14ead7919501f 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -55,6 +55,7 @@ class EditorProvider extends Component { return { ...pick( settings, [ 'alignWide', + 'availableLegacyWidgets', 'colors', 'disableCustomColors', 'fontSizes', @@ -63,6 +64,7 @@ class EditorProvider extends Component { 'maxWidth', 'allowedBlockTypes', 'hasFixedToolbar', + 'hasPermissionsToManageWidgets', 'focusMode', 'styles', 'isRTL', From 0dc006267782cb4825d8acccfdd0ff57d2ed1fb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Fri, 8 Mar 2019 14:26:52 +0100 Subject: [PATCH 614/691] Update changelog for esling-plugin (#14339) --- packages/eslint-plugin/CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index faa561924f1d1..5970f24e2a90e 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -1,3 +1,10 @@ +## 2.1.0 (Unreleased) + +### New Features + +- The bundled `eslint-plugin-jsx-a11y` dependency has been updated from requiring `^6.0.2` to requiring `^6.2.1` (see new features added in [6.2.0](https://github.com/evcohen/eslint-plugin-jsx-a11y/releases/tag/v6.2.0) and [6.1.0](https://github.com/evcohen/eslint-plugin-jsx-a11y/releases/tag/v6.1.0)). +- The bundled `eslint-plugin-react` dependency has been updated from requiring `7.7.0` to requiring `^7.12.4` (see new features added in [7.12.0](https://github.com/yannickcr/eslint-plugin-react/releases/tag/v7.12.0), [7.11.0](https://github.com/yannickcr/eslint-plugin-react/releases/tag/v7.11.0), [7.10.0](https://github.com/yannickcr/eslint-plugin-react/releases/tag/v7.10.0), [7.9.0](https://github.com/yannickcr/eslint-plugin-react/releases/tag/v7.9.0) and [7.8.0](https://github.com/yannickcr/eslint-plugin-react/releases/tag/v7.8.0)). + ## 2.0.0 (2019-03-06) ### Breaking Changes From efb232c9d9469e01e290ff64c80cdefb06257d29 Mon Sep 17 00:00:00 2001 From: "Nahid F. Mohit" <nfmohit49@gmail.com> Date: Sat, 9 Mar 2019 00:00:07 +0800 Subject: [PATCH 615/691] Add ability to transform [video] shortcodes to video block (#14042) --- packages/block-library/src/video/index.js | 36 +++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/packages/block-library/src/video/index.js b/packages/block-library/src/video/index.js index e56d99127d588..46592062ea41e 100644 --- a/packages/block-library/src/video/index.js +++ b/packages/block-library/src/video/index.js @@ -98,6 +98,42 @@ export const settings = { return block; }, }, + { + type: 'shortcode', + tag: 'video', + attributes: { + src: { + type: 'string', + shortcode: ( { named: { src } } ) => { + return src; + }, + }, + poster: { + type: 'string', + shortcode: ( { named: { poster } } ) => { + return poster; + }, + }, + loop: { + type: 'string', + shortcode: ( { named: { loop } } ) => { + return loop; + }, + }, + autoplay: { + type: 'string', + shortcode: ( { named: { autoplay } } ) => { + return autoplay; + }, + }, + preload: { + type: 'string', + shortcode: ( { named: { preload } } ) => { + return preload; + }, + }, + }, + }, ], }, From 744d0c7ac8abda61ae2f1ad4dadeb934647c56fb Mon Sep 17 00:00:00 2001 From: andrei draganescu <me@andreidraganescu.info> Date: Fri, 8 Mar 2019 18:01:00 +0200 Subject: [PATCH 616/691] CSS to remove the clipping of the toolbar in RTL languages (#14088) --- packages/block-library/src/classic/editor.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/block-library/src/classic/editor.scss b/packages/block-library/src/classic/editor.scss index 1cda8777992fa..134eeea0c5855 100644 --- a/packages/block-library/src/classic/editor.scss +++ b/packages/block-library/src/classic/editor.scss @@ -187,6 +187,12 @@ color: $dark-gray-800; } + // Prevent toolbar clipping on heading style in RTL languages + .mce-rtl .mce-flow-layout-item.mce-last { + margin-right: 0; + margin-left: 8px; + } + // Prevent i tags in buttons from picking up theme editor styles. .mce-btn i { font-style: normal; From 7f93f5edf45e3ecb6195dfc70fea8ccd20110cdf Mon Sep 17 00:00:00 2001 From: andrei draganescu <me@andreidraganescu.info> Date: Fri, 8 Mar 2019 18:03:14 +0200 Subject: [PATCH 617/691] added the more tag via the old editor's image (#14173) --- packages/block-library/src/classic/editor.scss | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/classic/editor.scss b/packages/block-library/src/classic/editor.scss index 134eeea0c5855..1fe580ef581c9 100644 --- a/packages/block-library/src/classic/editor.scss +++ b/packages/block-library/src/classic/editor.scss @@ -109,12 +109,15 @@ .wp-more-tag { width: 96%; - height: 0; + height: 20px; display: block; margin: 15px auto; outline: 0; cursor: default; - border: 2px dashed rgb(186, 186, 186); + background-image: url(/wp-includes/js/tinymce/skins/wordpress/images/more-2x.png); + background-size: 1900px 20px; + background-repeat: no-repeat; + background-position: center; } /** From 3254c34bbb6d7fad93e75b44ad02305af169df4c Mon Sep 17 00:00:00 2001 From: Andrea Fercia <a.fercia@gmail.com> Date: Fri, 8 Mar 2019 20:44:52 +0100 Subject: [PATCH 618/691] Seimplify hierarchical term selector strings. (#13938) --- .../hierarchical-term-selector.js | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/packages/editor/src/components/post-taxonomies/hierarchical-term-selector.js b/packages/editor/src/components/post-taxonomies/hierarchical-term-selector.js index 419af0dd4f5c9..bc36ab0617058 100644 --- a/packages/editor/src/components/post-taxonomies/hierarchical-term-selector.js +++ b/packages/editor/src/components/post-taxonomies/hierarchical-term-selector.js @@ -370,21 +370,15 @@ class HierarchicalTermSelector extends Component { const newTermSubmitLabel = newTermButtonLabel; const inputId = `editor-post-taxonomies__hierarchical-terms-input-${ instanceId }`; const filterInputId = `editor-post-taxonomies__hierarchical-terms-filter-${ instanceId }`; - const filterLabel = sprintf( - _x( 'Search %s', 'term' ), - get( - this.props.taxonomy, - [ 'name' ], - slug === 'category' ? __( 'Categories' ) : __( 'Terms' ) - ) + const filterLabel = get( + this.props.taxonomy, + [ 'labels', 'search_items' ], + __( 'Search Terms' ) ); - const groupLabel = sprintf( - _x( 'Available %s', 'term' ), - get( - this.props.taxonomy, - [ 'name' ], - slug === 'category' ? __( 'Categories' ) : __( 'Terms' ) - ) + const groupLabel = get( + this.props.taxonomy, + [ 'name' ], + __( 'Terms' ) ); const showFilter = availableTerms.length >= MIN_TERMS_COUNT_FOR_FILTER; From 313974f998ea27090432838ffd7b8de0e5bf031c Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Fri, 8 Mar 2019 19:46:05 +0000 Subject: [PATCH 619/691] Add: End to end test to make sure when all blocks get removed the default block appender gets inserter and selected (#14191) --- packages/e2e-test-utils/README.md | 62 +++++++++++-------- packages/e2e-test-utils/src/index.js | 1 + .../e2e-test-utils/src/is-in-default-block.js | 22 +++++++ .../e2e-tests/specs/block-deletion.test.js | 22 +++++++ .../e2e-tests/specs/splitting-merging.test.js | 12 +--- 5 files changed, 83 insertions(+), 36 deletions(-) create mode 100644 packages/e2e-test-utils/src/is-in-default-block.js diff --git a/packages/e2e-test-utils/README.md b/packages/e2e-test-utils/README.md index 0b1c589738236..e987e7002839b 100644 --- a/packages/e2e-test-utils/README.md +++ b/packages/e2e-test-utils/README.md @@ -88,7 +88,7 @@ Clicks on More Menu item, searches for the button with the text provided and cli ### createEmbeddingMatcher -[src/index.js#L47-L47](src/index.js#L47-L47) +[src/index.js#L48-L48](src/index.js#L48-L48) Creates a function to determine if a request is embedding a certain URL. @@ -102,7 +102,7 @@ Creates a function to determine if a request is embedding a certain URL. ### createJSONResponse -[src/index.js#L47-L47](src/index.js#L47-L47) +[src/index.js#L48-L48](src/index.js#L48-L48) Respond to a request with a JSON response. @@ -141,7 +141,7 @@ Creates new URL by parsing base URL, WPPath and query string. ### createURLMatcher -[src/index.js#L47-L47](src/index.js#L47-L47) +[src/index.js#L48-L48](src/index.js#L48-L48) Creates a function to determine if a request is calling a URL with the substring present. @@ -299,10 +299,20 @@ Checks if current URL is a WordPress path. `boolean`: Boolean represents whether current URL is or not a WordPress path. -### loginUser +### isInDefaultBlock [src/index.js#L25-L25](src/index.js#L25-L25) +Checks if the block that is focused is the default block. + +**Returns** + +`Promise`: Promise resolving with a boolean indicating if the focused block is the default block. + +### loginUser + +[src/index.js#L26-L26](src/index.js#L26-L26) + Performs log in with specified username and password. **Parameters** @@ -312,7 +322,7 @@ Performs log in with specified username and password. ### mockOrTransform -[src/index.js#L47-L47](src/index.js#L47-L47) +[src/index.js#L48-L48](src/index.js#L48-L48) Mocks a request with the supplied mock object, or allows it to run with an optional transform, based on the deserialised JSON response for the request. @@ -329,26 +339,26 @@ deserialised JSON response for the request. ### observeFocusLoss -[src/index.js#L26-L26](src/index.js#L26-L26) +[src/index.js#L27-L27](src/index.js#L27-L27) Binds to the document on page load which throws an error if a `focusout` event occurs without a related target (i.e. focus loss). ### openDocumentSettingsSidebar -[src/index.js#L27-L27](src/index.js#L27-L27) +[src/index.js#L28-L28](src/index.js#L28-L28) Clicks on the button in the header which opens Document Settings sidebar when it is closed. ### openPublishPanel -[src/index.js#L28-L28](src/index.js#L28-L28) +[src/index.js#L29-L29](src/index.js#L29-L29) Opens the publish panel. ### pressKeyTimes -[src/index.js#L29-L29](src/index.js#L29-L29) +[src/index.js#L30-L30](src/index.js#L30-L30) Presses the given keyboard key a number of times in sequence. @@ -363,7 +373,7 @@ Presses the given keyboard key a number of times in sequence. ### pressKeyWithModifier -[src/index.js#L30-L30](src/index.js#L30-L30) +[src/index.js#L31-L31](src/index.js#L31-L31) Performs a key press with modifier (Shift, Control, Meta, Alt), where each modifier is normalized to platform-specific modifier. @@ -375,7 +385,7 @@ is normalized to platform-specific modifier. ### publishPost -[src/index.js#L31-L31](src/index.js#L31-L31) +[src/index.js#L32-L32](src/index.js#L32-L32) Publishes the post, resolving once the request is complete (once a notice is displayed). @@ -386,7 +396,7 @@ is displayed). ### publishPostWithPrePublishChecksDisabled -[src/index.js#L32-L32](src/index.js#L32-L32) +[src/index.js#L33-L33](src/index.js#L33-L33) Publishes the post without the pre-publish checks, resolving once the request is complete (once a notice is displayed). @@ -397,7 +407,7 @@ resolving once the request is complete (once a notice is displayed). ### saveDraft -[src/index.js#L33-L33](src/index.js#L33-L33) +[src/index.js#L34-L34](src/index.js#L34-L34) Saves the post as a draft, resolving once the request is complete (once the "Saved" indicator is displayed). @@ -408,7 +418,7 @@ Saves the post as a draft, resolving once the request is complete (once the ### searchForBlock -[src/index.js#L34-L34](src/index.js#L34-L34) +[src/index.js#L35-L35](src/index.js#L35-L35) Search for block in the global inserter @@ -418,7 +428,7 @@ Search for block in the global inserter ### selectBlockByClientId -[src/index.js#L35-L35](src/index.js#L35-L35) +[src/index.js#L36-L36](src/index.js#L36-L36) Given the clientId of a block, selects the block on the editor. @@ -428,7 +438,7 @@ Given the clientId of a block, selects the block on the editor. ### setBrowserViewport -[src/index.js#L36-L36](src/index.js#L36-L36) +[src/index.js#L37-L37](src/index.js#L37-L37) Sets browser viewport to specified type. @@ -438,7 +448,7 @@ Sets browser viewport to specified type. ### setPostContent -[src/index.js#L37-L37](src/index.js#L37-L37) +[src/index.js#L38-L38](src/index.js#L38-L38) Sets code editor content @@ -452,7 +462,7 @@ Sets code editor content ### setUpResponseMocking -[src/index.js#L47-L47](src/index.js#L47-L47) +[src/index.js#L48-L48](src/index.js#L48-L48) Sets up mock checks and responses. Accepts a list of mock settings with the following properties: @@ -483,7 +493,7 @@ If none of the mock settings match the request, the request is allowed to contin ### switchEditorModeTo -[src/index.js#L38-L38](src/index.js#L38-L38) +[src/index.js#L39-L39](src/index.js#L39-L39) Switches editor mode. @@ -493,21 +503,21 @@ Switches editor mode. ### switchUserToAdmin -[src/index.js#L39-L39](src/index.js#L39-L39) +[src/index.js#L40-L40](src/index.js#L40-L40) Switches the current user to the admin user (if the user running the test is not already the admin user). ### switchUserToTest -[src/index.js#L40-L40](src/index.js#L40-L40) +[src/index.js#L41-L41](src/index.js#L41-L41) Switches the current user to whichever user we should be running the tests as (if we're not already that user). ### toggleScreenOption -[src/index.js#L41-L41](src/index.js#L41-L41) +[src/index.js#L42-L42](src/index.js#L42-L42) Toggles the screen option with the given label. @@ -518,7 +528,7 @@ Toggles the screen option with the given label. ### transformBlockTo -[src/index.js#L42-L42](src/index.js#L42-L42) +[src/index.js#L43-L43](src/index.js#L43-L43) Converts editor's block type. @@ -528,7 +538,7 @@ Converts editor's block type. ### uninstallPlugin -[src/index.js#L43-L43](src/index.js#L43-L43) +[src/index.js#L44-L44](src/index.js#L44-L44) Uninstalls a plugin. @@ -538,7 +548,7 @@ Uninstalls a plugin. ### visitAdminPage -[src/index.js#L44-L44](src/index.js#L44-L44) +[src/index.js#L45-L45](src/index.js#L45-L45) Visits admin page; if user is not logged in then it logging in it first, then visits admin page. @@ -549,7 +559,7 @@ Visits admin page; if user is not logged in then it logging in it first, then vi ### waitForWindowDimensions -[src/index.js#L45-L45](src/index.js#L45-L45) +[src/index.js#L46-L46](src/index.js#L46-L46) Function that waits until the page viewport has the required dimensions. It is being used to address a problem where after using setViewport the execution may continue, diff --git a/packages/e2e-test-utils/src/index.js b/packages/e2e-test-utils/src/index.js index 2d9e5b6af6ffc..09e11bf9ec0ed 100644 --- a/packages/e2e-test-utils/src/index.js +++ b/packages/e2e-test-utils/src/index.js @@ -22,6 +22,7 @@ export { hasBlockSwitcher } from './has-block-switcher'; export { insertBlock } from './insert-block'; export { installPlugin } from './install-plugin'; export { isCurrentURL } from './is-current-url'; +export { isInDefaultBlock } from './is-in-default-block'; export { loginUser } from './login-user'; export { observeFocusLoss } from './observe-focus-loss'; export { openDocumentSettingsSidebar } from './open-document-settings-sidebar'; diff --git a/packages/e2e-test-utils/src/is-in-default-block.js b/packages/e2e-test-utils/src/is-in-default-block.js new file mode 100644 index 0000000000000..7558a7b0767e3 --- /dev/null +++ b/packages/e2e-test-utils/src/is-in-default-block.js @@ -0,0 +1,22 @@ +/** + * Checks if the block that is focused is the default block. + * + * @return {Promise} Promise resolving with a boolean indicating if the focused block is the default block. + */ +export function isInDefaultBlock() { + return page.evaluate( () => { + const activeElement = document.activeElement; + // activeElement may be null in that case we should return false + if ( ! activeElement ) { + return false; + } + const closestElementWithDataTpe = activeElement.closest( '[data-type]' ); + if ( ! closestElementWithDataTpe ) { + return false; + } + const activeBlockName = closestElementWithDataTpe.getAttribute( 'data-type' ); + const defaultBlockName = window.wp.blocks.getDefaultBlockName(); + + return activeBlockName === defaultBlockName; + } ); +} diff --git a/packages/e2e-tests/specs/block-deletion.test.js b/packages/e2e-tests/specs/block-deletion.test.js index 24550537beff4..b9013b3572f9e 100644 --- a/packages/e2e-tests/specs/block-deletion.test.js +++ b/packages/e2e-tests/specs/block-deletion.test.js @@ -5,6 +5,7 @@ import { clickBlockAppender, getEditedPostContent, createNewPost, + isInDefaultBlock, pressKeyWithModifier, } from '@wordpress/e2e-test-utils'; @@ -109,3 +110,24 @@ describe( 'block deletion -', () => { } ); } ); } ); + +describe( 'deleting all blocks', () => { + it( 'results in the default block getting selected', async () => { + await createNewPost(); + await clickBlockAppender(); + await page.keyboard.type( 'Paragraph' ); + + await page.keyboard.press( 'Escape' ); + + await clickOnBlockSettingsMenuItem( 'Remove Block' ); + + // There is a default block: + expect( await page.$$( '.editor-block-list__block' ) ).toHaveLength( 1 ); + + // But the effective saved content is still empty: + expect( await getEditedPostContent() ).toBe( '' ); + + // And focus is retained: + expect( await isInDefaultBlock() ).toBe( true ); + } ); +} ); diff --git a/packages/e2e-tests/specs/splitting-merging.test.js b/packages/e2e-tests/specs/splitting-merging.test.js index 1120f04a2b767..c5ea8bf94ce77 100644 --- a/packages/e2e-tests/specs/splitting-merging.test.js +++ b/packages/e2e-tests/specs/splitting-merging.test.js @@ -4,6 +4,7 @@ import { createNewPost, insertBlock, + isInDefaultBlock, getEditedPostContent, pressKeyTimes, pressKeyWithModifier, @@ -199,15 +200,6 @@ describe( 'splitting and merging blocks', () => { expect( await getEditedPostContent() ).toBe( '' ); // And focus is retained: - const isInDefaultBlock = await page.evaluate( () => { - const activeBlockName = document.activeElement - .closest( '[data-type]' ) - .getAttribute( 'data-type' ); - const defaultBlockName = window.wp.blocks.getDefaultBlockName(); - - return activeBlockName === defaultBlockName; - } ); - - expect( isInDefaultBlock ).toBe( true ); + expect( await isInDefaultBlock() ).toBe( true ); } ); } ); From 2a660dd921d990083581f765ee1c794733680096 Mon Sep 17 00:00:00 2001 From: Thiago Locks <thiago@zira.com.br> Date: Sat, 9 Mar 2019 11:54:43 -0300 Subject: [PATCH 620/691] Fix the Deprecated Blocks link (#14355) The *Deprecated Blocks link* was pointing to the wrong URL. --- .../developers/block-api/block-edit-save.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/designers-developers/developers/block-api/block-edit-save.md b/docs/designers-developers/developers/block-api/block-edit-save.md index 79a3206326031..c5e35eddb10a7 100644 --- a/docs/designers-developers/developers/block-api/block-edit-save.md +++ b/docs/designers-developers/developers/block-api/block-edit-save.md @@ -406,4 +406,4 @@ When a block is detected as invalid, a warning will be logged into your browser' **I've changed my block's `save` behavior and old content now includes invalid blocks. How can I fix this?** -Refer to the guide on [Deprecated Blocks](/docs/designers-developers/developers/block-api/block-deprecations.md) to learn more about how to accommodate legacy content in intentional markup changes. +Refer to the guide on [Deprecated Blocks](/docs/designers-developers/developers/block-api/block-deprecation.md) to learn more about how to accommodate legacy content in intentional markup changes. From fd604dfe0f6917d17817f8323bf3ecc809494bdd Mon Sep 17 00:00:00 2001 From: Ashwin P Chandran <ashwinpc1993@gmail.com> Date: Sun, 10 Mar 2019 03:13:02 -0400 Subject: [PATCH 621/691] Added testcases for isKeyboardEvent in keycodes module (#14073) * Added testcases for isKeyboardEvent in keycodes module * Fixed assertion checks and updated attachEventListeners function * Added testcases for isKeyboardEvent in keycodes module * Fixed assertion checks and updated attachEventListeners function --- packages/keycodes/src/test/index.js | 244 ++++++++++++++++++++++++++++ 1 file changed, 244 insertions(+) diff --git a/packages/keycodes/src/test/index.js b/packages/keycodes/src/test/index.js index d795560fcb3a7..2d7d83249ec57 100644 --- a/packages/keycodes/src/test/index.js +++ b/packages/keycodes/src/test/index.js @@ -6,6 +6,7 @@ import { displayShortcut, rawShortcut, shortcutAriaLabel, + isKeyboardEvent, } from '../'; const isAppleOSFalse = () => false; @@ -230,3 +231,246 @@ describe( 'rawShortcut', () => { } ); } ); } ); + +describe( 'isKeyboardEvent', () => { + afterEach( () => { + while ( document.body.firstChild ) { + document.body.removeChild( document.body.firstChild ); + } + } ); + + function keyPress( target, modifiers = {} ) { + [ 'keydown', 'keypress', 'keyup' ].forEach( ( eventName ) => { + const event = new window.Event( eventName, { bubbles: true } ); + Object.assign( event, modifiers ); + target.dispatchEvent( event ); + } ); + } + + function attachEventListeners( eventHandler ) { + const attachNode = document.createElement( 'div' ); + document.body.appendChild( attachNode ); + + [ 'keydown', 'keypress', 'keyup' ].forEach( ( eventName ) => { + attachNode.addEventListener( eventName, eventHandler ); + } ); + + return attachNode; + } + + describe( 'primary', () => { + it( 'should identify modifier key when Ctrl is pressed', () => { + expect.assertions( 3 ); + const attachNode = attachEventListeners( ( event ) => { + expect( isKeyboardEvent.primary( event, undefined, isAppleOSFalse ) ).toBe( true ); + } ); + + keyPress( attachNode, { + ctrlKey: true, + key: 'Ctrl', + } ); + } ); + + it( 'should identify modifier key when ⌘ is pressed', () => { + expect.assertions( 3 ); + const attachNode = attachEventListeners( ( event ) => { + expect( isKeyboardEvent.primary( event, undefined, isAppleOSTrue ) ).toBe( true ); + } ); + + keyPress( attachNode, { + metaKey: true, + key: 'Meta', + } ); + } ); + + it( 'should identify modifier key when Ctrl + M is pressed', () => { + expect.assertions( 3 ); + const attachNode = attachEventListeners( ( event ) => { + expect( isKeyboardEvent.primary( event, 'm', isAppleOSFalse ) ).toBe( true ); + } ); + + keyPress( attachNode, { + ctrlKey: true, + key: 'm', + } ); + } ); + + it( 'should identify modifier key when ⌘M is pressed', () => { + expect.assertions( 3 ); + const attachNode = attachEventListeners( ( event ) => { + expect( isKeyboardEvent.primary( event, 'm', isAppleOSTrue ) ).toBe( true ); + } ); + + keyPress( attachNode, { + metaKey: true, + key: 'm', + } ); + } ); + } ); + + describe( 'primaryShift', () => { + it( 'should identify modifier key when Shift + Ctrl is pressed', () => { + expect.assertions( 3 ); + const attachNode = attachEventListeners( ( event ) => { + expect( isKeyboardEvent.primary( event, undefined, isAppleOSFalse ) ).toBe( true ); + } ); + + keyPress( attachNode, { + ctrlKey: true, + shiftKey: true, + key: 'Ctrl', + } ); + } ); + + it( 'should identify modifier key when ⇧⌘ is pressed', () => { + expect.assertions( 3 ); + const attachNode = attachEventListeners( ( event ) => { + expect( isKeyboardEvent.primary( event, undefined, isAppleOSTrue ) ).toBe( true ); + } ); + + keyPress( attachNode, { + metaKey: true, + shiftKey: true, + key: 'Meta', + } ); + } ); + + it( 'should identify modifier key when Shift + Ctrl + M is pressed', () => { + expect.assertions( 3 ); + const attachNode = attachEventListeners( ( event ) => { + expect( isKeyboardEvent.primary( event, 'm', isAppleOSFalse ) ).toBe( true ); + } ); + + keyPress( attachNode, { + ctrlKey: true, + shiftKey: true, + key: 'm', + } ); + } ); + + it( 'should identify modifier key when ⇧⌘M is pressed', () => { + expect.assertions( 3 ); + const attachNode = attachEventListeners( ( event ) => { + expect( isKeyboardEvent.primary( event, 'm', isAppleOSTrue ) ).toBe( true ); + } ); + + keyPress( attachNode, { + metaKey: true, + shiftKey: true, + key: 'm', + } ); + } ); + } ); + + describe( 'secondary', () => { + it( 'should identify modifier key when Shift + Alt + Ctrl is pressed', () => { + expect.assertions( 3 ); + const attachNode = attachEventListeners( ( event ) => { + expect( isKeyboardEvent.primary( event, undefined, isAppleOSFalse ) ).toBe( true ); + } ); + + keyPress( attachNode, { + ctrlKey: true, + shiftKey: true, + altKey: true, + key: 'Ctrl', + } ); + } ); + + it( 'should identify modifier key when ⇧⌥⌘ is pressed', () => { + expect.assertions( 3 ); + const attachNode = attachEventListeners( ( event ) => { + expect( isKeyboardEvent.primary( event, undefined, isAppleOSTrue ) ).toBe( true ); + } ); + + keyPress( attachNode, { + metaKey: true, + shiftKey: true, + altKey: true, + key: 'Meta', + } ); + } ); + + it( 'should identify modifier key when Shift + Ctrl + ALt + M is pressed', () => { + expect.assertions( 3 ); + const attachNode = attachEventListeners( ( event ) => { + expect( isKeyboardEvent.primary( event, 'm', isAppleOSFalse ) ).toBe( true ); + } ); + + keyPress( attachNode, { + ctrlKey: true, + shiftKey: true, + altKey: true, + key: 'm', + } ); + } ); + + it( 'should identify modifier key when ⇧⌥⌘M is pressed', () => { + expect.assertions( 3 ); + const attachNode = attachEventListeners( ( event ) => { + expect( isKeyboardEvent.primary( event, 'm', isAppleOSTrue ) ).toBe( true ); + } ); + + keyPress( attachNode, { + metaKey: true, + shiftKey: true, + altKey: true, + key: 'm', + } ); + } ); + } ); + + describe( 'access', () => { + it( 'should identify modifier key when Alt + Ctrl is pressed', () => { + expect.assertions( 3 ); + const attachNode = attachEventListeners( ( event ) => { + expect( isKeyboardEvent.primary( event, undefined, isAppleOSFalse ) ).toBe( true ); + } ); + + keyPress( attachNode, { + ctrlKey: true, + altKey: true, + key: 'Ctrl', + } ); + } ); + + it( 'should identify modifier key when ⌥⌘ is pressed', () => { + expect.assertions( 3 ); + const attachNode = attachEventListeners( ( event ) => { + expect( isKeyboardEvent.primary( event, undefined, isAppleOSTrue ) ).toBe( true ); + } ); + + keyPress( attachNode, { + metaKey: true, + altKey: true, + key: 'Meta', + } ); + } ); + + it( 'should identify modifier key when Ctrl + ALt + M is pressed', () => { + expect.assertions( 3 ); + const attachNode = attachEventListeners( ( event ) => { + expect( isKeyboardEvent.primary( event, 'm', isAppleOSFalse ) ).toBe( true ); + } ); + + keyPress( attachNode, { + ctrlKey: true, + altKey: true, + key: 'm', + } ); + } ); + + it( 'should identify modifier key when ⌥⌘M is pressed', () => { + expect.assertions( 3 ); + const attachNode = attachEventListeners( ( event ) => { + expect( isKeyboardEvent.primary( event, 'm', isAppleOSTrue ) ).toBe( true ); + } ); + + keyPress( attachNode, { + metaKey: true, + altKey: true, + key: 'm', + } ); + } ); + } ); +} ); From 23ffc009ca0bfa72b90e327fbb971f5e4da19059 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Sun, 10 Mar 2019 12:11:58 +0000 Subject: [PATCH 622/691] Fix: Quote to heading transform (#14348) --- packages/block-library/src/quote/index.js | 30 +++-- .../blocks/__snapshots__/quote.test.js.snap | 108 ++++++++++++------ packages/e2e-tests/specs/blocks/quote.test.js | 35 +++++- 3 files changed, 122 insertions(+), 51 deletions(-) diff --git a/packages/block-library/src/quote/index.js b/packages/block-library/src/quote/index.js index 535dd29e4d8b5..20458db83572e 100644 --- a/packages/block-library/src/quote/index.js +++ b/packages/block-library/src/quote/index.js @@ -186,21 +186,27 @@ export const settings = { } const pieces = split( create( { html: value, multilineTag: 'p' } ), '\u2028' ); + + const headingBlock = createBlock( 'core/heading', { + content: toHTMLString( { value: pieces[ 0 ] } ), + } ); + + if ( ! citation && pieces.length === 1 ) { + return headingBlock; + } + const quotePieces = pieces.slice( 1 ); - return [ - createBlock( 'core/heading', { - content: toHTMLString( { value: pieces[ 0 ] } ), - } ), - createBlock( 'core/quote', { - ...attrs, - citation, - value: toHTMLString( { - value: quotePieces.length ? join( pieces.slice( 1 ), '\u2028' ) : create(), - multilineTag: 'p', - } ), + const quoteBlock = createBlock( 'core/quote', { + ...attrs, + citation, + value: toHTMLString( { + value: quotePieces.length ? join( pieces.slice( 1 ), '\u2028' ) : create(), + multilineTag: 'p', } ), - ]; + } ); + + return [ headingBlock, quoteBlock ]; }, }, diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/quote.test.js.snap b/packages/e2e-tests/specs/blocks/__snapshots__/quote.test.js.snap index 6a46b767390ef..57d1d187978f4 100644 --- a/packages/e2e-tests/specs/blocks/__snapshots__/quote.test.js.snap +++ b/packages/e2e-tests/specs/blocks/__snapshots__/quote.test.js.snap @@ -6,44 +6,6 @@ exports[`Quote can be converted to a pullquote 1`] = ` <!-- /wp:pullquote -->" `; -exports[`Quote can be converted to headings 1`] = ` -"<!-- wp:heading --> -<h2>one</h2> -<!-- /wp:heading --> - -<!-- wp:quote --> -<blockquote class=\\"wp-block-quote\\"><p>two</p><cite>cite</cite></blockquote> -<!-- /wp:quote -->" -`; - -exports[`Quote can be converted to headings 2`] = ` -"<!-- wp:heading --> -<h2>one</h2> -<!-- /wp:heading --> - -<!-- wp:heading --> -<h2>two</h2> -<!-- /wp:heading --> - -<!-- wp:quote --> -<blockquote class=\\"wp-block-quote\\"><p></p><cite>cite</cite></blockquote> -<!-- /wp:quote -->" -`; - -exports[`Quote can be converted to headings 3`] = ` -"<!-- wp:heading --> -<h2>one</h2> -<!-- /wp:heading --> - -<!-- wp:heading --> -<h2>two</h2> -<!-- /wp:heading --> - -<!-- wp:heading --> -<h2>cite</h2> -<!-- /wp:heading -->" -`; - exports[`Quote can be converted to paragraphs and renders a paragraph for the cite, if it exists 1`] = ` "<!-- wp:paragraph --> <p>one</p> @@ -117,3 +79,73 @@ exports[`Quote can be merged into from a paragraph 1`] = ` <blockquote class=\\"wp-block-quote\\"><p>test</p></blockquote> <!-- /wp:quote -->" `; + +exports[`Quote is transformed to a heading and a quote if the quote contains a citation 1`] = ` +"<!-- wp:heading --> +<h2>one</h2> +<!-- /wp:heading --> + +<!-- wp:quote --> +<blockquote class=\\"wp-block-quote\\"><p></p><cite>cite</cite></blockquote> +<!-- /wp:quote -->" +`; + +exports[`Quote is transformed to a heading and a quote if the quote contains multiple paragraphs 1`] = ` +"<!-- wp:heading --> +<h2>one</h2> +<!-- /wp:heading --> + +<!-- wp:quote --> +<blockquote class=\\"wp-block-quote\\"><p>two</p><p>three</p></blockquote> +<!-- /wp:quote -->" +`; + +exports[`Quote is transformed to a heading if the quote just contains one paragraph 1`] = ` +"<!-- wp:heading --> +<h2>one</h2> +<!-- /wp:heading -->" +`; + +exports[`Quote is transformed to an empty heading if the quote is empty 1`] = ` +"<!-- wp:heading --> +<h2></h2> +<!-- /wp:heading -->" +`; + +exports[`Quote the resuling quote after transforming to a heading can be transformed again 1`] = ` +"<!-- wp:heading --> +<h2>one</h2> +<!-- /wp:heading --> + +<!-- wp:quote --> +<blockquote class=\\"wp-block-quote\\"><p>two</p><cite>cite</cite></blockquote> +<!-- /wp:quote -->" +`; + +exports[`Quote the resuling quote after transforming to a heading can be transformed again 2`] = ` +"<!-- wp:heading --> +<h2>one</h2> +<!-- /wp:heading --> + +<!-- wp:heading --> +<h2>two</h2> +<!-- /wp:heading --> + +<!-- wp:quote --> +<blockquote class=\\"wp-block-quote\\"><p></p><cite>cite</cite></blockquote> +<!-- /wp:quote -->" +`; + +exports[`Quote the resuling quote after transforming to a heading can be transformed again 3`] = ` +"<!-- wp:heading --> +<h2>one</h2> +<!-- /wp:heading --> + +<!-- wp:heading --> +<h2>two</h2> +<!-- /wp:heading --> + +<!-- wp:heading --> +<h2>cite</h2> +<!-- /wp:heading -->" +`; diff --git a/packages/e2e-tests/specs/blocks/quote.test.js b/packages/e2e-tests/specs/blocks/quote.test.js index 11f1fd5e08638..67e279ea19038 100644 --- a/packages/e2e-tests/specs/blocks/quote.test.js +++ b/packages/e2e-tests/specs/blocks/quote.test.js @@ -116,7 +116,40 @@ describe( 'Quote', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); - it( 'can be converted to headings', async () => { + it( 'is transformed to an empty heading if the quote is empty', async () => { + await insertBlock( 'Quote' ); + await transformBlockTo( 'Heading' ); + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'is transformed to a heading if the quote just contains one paragraph', async () => { + await insertBlock( 'Quote' ); + await page.keyboard.type( 'one' ); + await transformBlockTo( 'Heading' ); + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'is transformed to a heading and a quote if the quote contains multiple paragraphs', async () => { + await insertBlock( 'Quote' ); + await page.keyboard.type( 'one' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( 'two' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( 'three' ); + await transformBlockTo( 'Heading' ); + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'is transformed to a heading and a quote if the quote contains a citation', async () => { + await insertBlock( 'Quote' ); + await page.keyboard.type( 'one' ); + await page.keyboard.press( 'Tab' ); + await page.keyboard.type( 'cite' ); + await transformBlockTo( 'Heading' ); + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'the resuling quote after transforming to a heading can be transformed again', async () => { await insertBlock( 'Quote' ); await page.keyboard.type( 'one' ); await page.keyboard.press( 'Enter' ); From 6d81e2a7d0cfef455ba35b52fa485ebd1eefc763 Mon Sep 17 00:00:00 2001 From: Zebulan Stanphill <zebulanstanphill@gmail.com> Date: Mon, 11 Mar 2019 03:46:13 -0500 Subject: [PATCH 623/691] Latest Comments block: use align supports flag + code cleanup (#11411) --- .../block-library/src/latest-comments/edit.js | 22 +++---------------- .../src/latest-comments/index.js | 13 ++--------- .../src/latest-comments/index.php | 8 +++---- .../e2e-tests/fixtures/blocks/core__rss.html | 2 +- .../fixtures/blocks/core__rss.serialized.html | 2 +- .../blocks/core__search__custom-text.html | 2 +- .../core__search__custom-text.serialized.html | 2 +- .../core__tag-cloud__showTagCounts.html | 2 +- ...__tag-cloud__showTagCounts.serialized.html | 2 +- .../full-content/server-registered.json | 2 +- 10 files changed, 16 insertions(+), 41 deletions(-) diff --git a/packages/block-library/src/latest-comments/edit.js b/packages/block-library/src/latest-comments/edit.js index 0f44f30b8ac5a..1bc21e552939e 100644 --- a/packages/block-library/src/latest-comments/edit.js +++ b/packages/block-library/src/latest-comments/edit.js @@ -1,20 +1,16 @@ /** * WordPress dependencies */ -import { Component, Fragment } from '@wordpress/element'; +import { InspectorControls } from '@wordpress/block-editor'; import { Disabled, PanelBody, RangeControl, ToggleControl, } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { - InspectorControls, - BlockAlignmentToolbar, - BlockControls, -} from '@wordpress/block-editor'; import { ServerSideRender } from '@wordpress/editor'; +import { Component, Fragment } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; /** * Minimum number of comments a user can show using this block. @@ -33,7 +29,6 @@ class LatestComments extends Component { constructor() { super( ...arguments ); - this.setAlignment = this.setAlignment.bind( this ); this.setCommentsToShow = this.setCommentsToShow.bind( this ); // Create toggles for each attribute; we create them here rather than @@ -53,17 +48,12 @@ class LatestComments extends Component { }; } - setAlignment( align ) { - this.props.setAttributes( { align } ); - } - setCommentsToShow( commentsToShow ) { this.props.setAttributes( { commentsToShow } ); } render() { const { - align, commentsToShow, displayAvatar, displayDate, @@ -72,12 +62,6 @@ class LatestComments extends Component { return ( <Fragment> - <BlockControls> - <BlockAlignmentToolbar - value={ align } - onChange={ this.setAlignment } - /> - </BlockControls> <InspectorControls> <PanelBody title={ __( 'Latest Comments Settings' ) }> <ToggleControl diff --git a/packages/block-library/src/latest-comments/index.js b/packages/block-library/src/latest-comments/index.js index d742c1dcbbc24..3f0700543f3b1 100644 --- a/packages/block-library/src/latest-comments/index.js +++ b/packages/block-library/src/latest-comments/index.js @@ -1,8 +1,8 @@ /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; import { G, Path, SVG } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies @@ -23,19 +23,10 @@ export const settings = { keywords: [ __( 'recent comments' ) ], supports: { + align: true, html: false, }, - getEditWrapperProps( attributes ) { - const { align } = attributes; - - // TODO: Use consistent values across the app; - // see: https://github.com/WordPress/gutenberg/issues/7908. - if ( [ 'left', 'center', 'right', 'wide', 'full' ].includes( align ) ) { - return { 'data-align': align }; - } - }, - edit, save() { diff --git a/packages/block-library/src/latest-comments/index.php b/packages/block-library/src/latest-comments/index.php index 26a8f4d63607d..ae14d31b6f1c5 100644 --- a/packages/block-library/src/latest-comments/index.php +++ b/packages/block-library/src/latest-comments/index.php @@ -154,6 +154,10 @@ function render_block_core_latest_comments( $attributes = array() ) { 'core/latest-comments', array( 'attributes' => array( + 'align' => array( + 'type' => 'string', + 'enum' => array( 'left', 'center', 'right', 'wide', 'full' ), + ), 'className' => array( 'type' => 'string', ), @@ -175,10 +179,6 @@ function render_block_core_latest_comments( $attributes = array() ) { 'type' => 'boolean', 'default' => true, ), - 'align' => array( - 'type' => 'string', - 'enum' => array( 'center', 'left', 'right', 'wide', 'full', '' ), - ), ), 'render_callback' => 'render_block_core_latest_comments', ) diff --git a/packages/e2e-tests/fixtures/blocks/core__rss.html b/packages/e2e-tests/fixtures/blocks/core__rss.html index 69b41ce041862..5a69011df61e0 100644 --- a/packages/e2e-tests/fixtures/blocks/core__rss.html +++ b/packages/e2e-tests/fixtures/blocks/core__rss.html @@ -1 +1 @@ -<!-- wp:rss {"blockLayout":"grid","feedURL":"https://wordpress.org/news/","itemsToShow":4,"displayExcerpt":true,"displayAuthor":true,"displayDate":true,"excerptLength":20} /--> +<!-- wp:rss {"blockLayout":"grid","displayDate":true,"displayExcerpt":true,"displayAuthor":true,"excerptLength":20,"feedURL":"https://wordpress.org/news/","itemsToShow":4} /--> diff --git a/packages/e2e-tests/fixtures/blocks/core__rss.serialized.html b/packages/e2e-tests/fixtures/blocks/core__rss.serialized.html index 69b41ce041862..bcb7f0c0f760b 100644 --- a/packages/e2e-tests/fixtures/blocks/core__rss.serialized.html +++ b/packages/e2e-tests/fixtures/blocks/core__rss.serialized.html @@ -1 +1 @@ -<!-- wp:rss {"blockLayout":"grid","feedURL":"https://wordpress.org/news/","itemsToShow":4,"displayExcerpt":true,"displayAuthor":true,"displayDate":true,"excerptLength":20} /--> +<!-- wp:rss {"blockLayout":"grid","displayAuthor":true,"displayDate":true,"displayExcerpt":true,"excerptLength":20,"feedURL":"https://wordpress.org/news/","itemsToShow":4} /--> diff --git a/packages/e2e-tests/fixtures/blocks/core__search__custom-text.html b/packages/e2e-tests/fixtures/blocks/core__search__custom-text.html index bb6e6a56c9a33..de15a05a22774 100644 --- a/packages/e2e-tests/fixtures/blocks/core__search__custom-text.html +++ b/packages/e2e-tests/fixtures/blocks/core__search__custom-text.html @@ -1 +1 @@ -<!-- wp:search {"label":"Custom label","placeholder":"Custom placeholder","buttonText":"Custom button text"} /--> +<!-- wp:search {"buttonText":"Custom button text","label":"Custom label","placeholder":"Custom placeholder"} /--> diff --git a/packages/e2e-tests/fixtures/blocks/core__search__custom-text.serialized.html b/packages/e2e-tests/fixtures/blocks/core__search__custom-text.serialized.html index bb6e6a56c9a33..de15a05a22774 100644 --- a/packages/e2e-tests/fixtures/blocks/core__search__custom-text.serialized.html +++ b/packages/e2e-tests/fixtures/blocks/core__search__custom-text.serialized.html @@ -1 +1 @@ -<!-- wp:search {"label":"Custom label","placeholder":"Custom placeholder","buttonText":"Custom button text"} /--> +<!-- wp:search {"buttonText":"Custom button text","label":"Custom label","placeholder":"Custom placeholder"} /--> diff --git a/packages/e2e-tests/fixtures/blocks/core__tag-cloud__showTagCounts.html b/packages/e2e-tests/fixtures/blocks/core__tag-cloud__showTagCounts.html index 3f22f21fc6cfe..c58be50bf6a2d 100644 --- a/packages/e2e-tests/fixtures/blocks/core__tag-cloud__showTagCounts.html +++ b/packages/e2e-tests/fixtures/blocks/core__tag-cloud__showTagCounts.html @@ -1 +1 @@ -<!-- wp:tag-cloud {"taxonomy":"category","showTagCounts":true} /--> \ No newline at end of file +<!-- wp:tag-cloud {"showTagCounts":true,"taxonomy":"category"} /--> \ No newline at end of file diff --git a/packages/e2e-tests/fixtures/blocks/core__tag-cloud__showTagCounts.serialized.html b/packages/e2e-tests/fixtures/blocks/core__tag-cloud__showTagCounts.serialized.html index 9711e8c6ad6c0..8a0a951d49efb 100644 --- a/packages/e2e-tests/fixtures/blocks/core__tag-cloud__showTagCounts.serialized.html +++ b/packages/e2e-tests/fixtures/blocks/core__tag-cloud__showTagCounts.serialized.html @@ -1 +1 @@ -<!-- wp:tag-cloud {"taxonomy":"category","showTagCounts":true} /--> +<!-- wp:tag-cloud {"showTagCounts":true,"taxonomy":"category"} /--> diff --git a/test/integration/full-content/server-registered.json b/test/integration/full-content/server-registered.json index eeae11cb99e9c..bdb2ffdd1fa5d 100644 --- a/test/integration/full-content/server-registered.json +++ b/test/integration/full-content/server-registered.json @@ -1 +1 @@ -{"core\/block":{"attributes":{"ref":{"type":"number"}}},"core\/latest-comments":{"attributes":{"className":{"type":"string"},"commentsToShow":{"type":"number","default":5,"minimum":1,"maximum":100},"displayAvatar":{"type":"boolean","default":true},"displayDate":{"type":"boolean","default":true},"displayExcerpt":{"type":"boolean","default":true},"align":{"type":"string","enum":["center","left","right","wide","full",""]}}},"core\/archives":{"attributes":{"align":{"type":"string"},"className":{"type":"string"},"displayAsDropdown":{"type":"boolean","default":false},"showPostCounts":{"type":"boolean","default":false}}},"core\/calendar":{"attributes":{"align":{"type":"string"},"className":{"type":"string"},"month":{"type":"integer"},"year":{"type":"integer"}}},"core\/latest-posts":{"attributes":{"categories":{"type":"string"},"className":{"type":"string"},"postsToShow":{"type":"number","default":5},"displayPostDate":{"type":"boolean","default":false},"postLayout":{"type":"string","default":"list"},"columns":{"type":"number","default":3},"align":{"type":"string"},"order":{"type":"string","default":"desc"},"orderBy":{"type":"string","default":"date"}}},"core\/rss":{"attributes":{"columns":{"type":"number","default":2},"blockLayout":{"type":"string","default":"list"},"feedURL":{"type":"string","default":""},"itemsToShow":{"type":"number","default":5},"displayExcerpt":{"type":"boolean","default":false},"displayAuthor":{"type":"boolean","default":false},"displayDate":{"type":"boolean","default":false},"excerptLength":{"type":"number","default":55}}},"core\/search":{"attributes":{"label":{"type":"string","default":"Search"},"placeholder":{"type":"string","default":""},"buttonText":{"type":"string","default":"Search"}}},"core\/tag-cloud":{"attributes":{"taxonomy":{"type":"string","default":"post_tag"},"className":{"type":"string"},"showTagCounts":{"type":"boolean","default":false},"align":{"type":"string"}}}} \ No newline at end of file +{"core\/archives":{"attributes":{"align":{"type":"string"},"className":{"type":"string"},"displayAsDropdown":{"type":"boolean","default":false},"showPostCounts":{"type":"boolean","default":false}}},"core\/block":{"attributes":{"ref":{"type":"number"}}},"core\/calendar":{"attributes":{"align":{"type":"string"},"className":{"type":"string"},"month":{"type":"integer"},"year":{"type":"integer"}}},"core\/latest-comments":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"commentsToShow":{"type":"number","default":5,"minimum":1,"maximum":100},"displayAvatar":{"type":"boolean","default":true},"displayDate":{"type":"boolean","default":true},"displayExcerpt":{"type":"boolean","default":true}}},"core\/latest-posts":{"attributes":{"categories":{"type":"string"},"className":{"type":"string"},"postsToShow":{"type":"number","default":5},"displayPostDate":{"type":"boolean","default":false},"postLayout":{"type":"string","default":"list"},"columns":{"type":"number","default":3},"align":{"type":"string"},"order":{"type":"string","default":"desc"},"orderBy":{"type":"string","default":"date"}}},"core\/rss":{"attributes":{"blockLayout":{"type":"string","default":"list"},"columns":{"type":"number","default":2},"displayAuthor":{"type":"boolean","default":false},"displayDate":{"type":"boolean","default":false},"displayExcerpt":{"type":"boolean","default":false},"excerptLength":{"type":"number","default":55},"feedURL":{"type":"string","default":""},"itemsToShow":{"type":"number","default":5}}},"core\/search":{"attributes":{"buttonText":{"type":"string","default":"Search"},"label":{"type":"string","default":"Search"},"placeholder":{"type":"string","default":""}}},"core\/tag-cloud":{"attributes":{"align":{"type":"string"},"className":{"type":"string"},"showTagCounts":{"type":"boolean","default":false},"taxonomy":{"type":"string","default":"post_tag"}}}} From b2746729122a5aae2a72c36a4f4c13eb4a81543d Mon Sep 17 00:00:00 2001 From: AmartyaU <44530193+AmartyaU@users.noreply.github.com> Date: Mon, 11 Mar 2019 04:59:06 -0400 Subject: [PATCH 624/691] ToggleControl allows setting custom classes (#13804) * ToggleControl allows setting custom classes Related Issue: #11349 This pull request fixes the "ToggleControl does not allow setting custom classes" problem. This lets the user to add a custom classname on the element, just like in TextControl. * Update README of toggle-control Add property className and its description * Update packages/components/src/toggle-control/README.md Formatted style of README Co-Authored-By: AmartyaU <44530193+AmartyaU@users.noreply.github.com> --- packages/components/src/toggle-control/README.md | 7 +++++++ packages/components/src/toggle-control/index.js | 5 +++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/components/src/toggle-control/README.md b/packages/components/src/toggle-control/README.md index c41bb464a44f3..a55c0f30df8bb 100644 --- a/packages/components/src/toggle-control/README.md +++ b/packages/components/src/toggle-control/README.md @@ -55,3 +55,10 @@ A function that receives the checked state (boolean) as input. - Type: `function` - Required: Yes +### className + +The class that will be added with `components-base-control` and `components-toggle-control` to the classes of the wrapper div. If no className is passed only `components-base-control` and `components-toggle-control` are used. + +Type: String +Required: No + diff --git a/packages/components/src/toggle-control/index.js b/packages/components/src/toggle-control/index.js index 29d558d6b80c6..39f4a7f372715 100644 --- a/packages/components/src/toggle-control/index.js +++ b/packages/components/src/toggle-control/index.js @@ -2,6 +2,7 @@ * External dependencies */ import { isFunction } from 'lodash'; +import classnames from 'classnames'; /** * WordPress dependencies @@ -29,7 +30,7 @@ class ToggleControl extends Component { } render() { - const { label, checked, help, instanceId } = this.props; + const { label, checked, help, instanceId, className } = this.props; const id = `inspector-toggle-control-${ instanceId }`; let describedBy, helpLabel; @@ -42,7 +43,7 @@ class ToggleControl extends Component { <BaseControl id={ id } help={ helpLabel } - className="components-toggle-control" + className={ classnames( 'components-toggle-control', className ) } > <FormToggle id={ id } From daa2096d24ded61388b439feebe1593d5e10ca80 Mon Sep 17 00:00:00 2001 From: Mark Uraine <uraine@gmail.com> Date: Mon, 11 Mar 2019 02:05:03 -0700 Subject: [PATCH 625/691] Edited text of button to be actionable (#14347) --- packages/editor/src/components/post-locked-modal/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor/src/components/post-locked-modal/index.js b/packages/editor/src/components/post-locked-modal/index.js index dd681f1f80980..b9c8de97f3d93 100644 --- a/packages/editor/src/components/post-locked-modal/index.js +++ b/packages/editor/src/components/post-locked-modal/index.js @@ -145,7 +145,7 @@ class PostLockedModal extends Component { const allPostsUrl = getWPAdminURL( 'edit.php', { post_type: get( postType, [ 'slug' ] ), } ); - const allPostsLabel = get( postType, [ 'labels', 'all_items' ] ); + const allPostsLabel = __( 'Exit the Editor' ); return ( <Modal title={ isTakeover ? __( 'Someone else has taken over this post.' ) : __( 'This post is already being edited.' ) } From fa9bbf31bb2effb179e645bd2748fadf09926e32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Mon, 11 Mar 2019 10:16:26 +0100 Subject: [PATCH 626/691] Block library: Remove all test snapshots for blocks (#14349) --- .../audio/test/__snapshots__/index.js.snap | 53 ------------- .../block-library/src/audio/test/index.js | 13 ---- .../button/test/__snapshots__/index.js.snap | 42 ---------- .../block-library/src/button/test/index.js | 13 ---- .../classic/test/__snapshots__/index.js.snap | 15 ---- .../block-library/src/classic/test/index.js | 13 ---- .../src/code/test/__snapshots__/index.js.snap | 14 ---- packages/block-library/src/code/test/index.js | 13 ---- .../cover/test/__snapshots__/index.js.snap | 42 ---------- .../block-library/src/cover/test/index.js | 13 ---- .../gallery/test/__snapshots__/index.js.snap | 44 ----------- .../block-library/src/gallery/test/index.js | 13 ---- .../heading/test/__snapshots__/index.js.snap | 34 --------- .../block-library/src/heading/test/index.js | 11 +-- .../src/html/test/__snapshots__/index.js.snap | 14 ---- packages/block-library/src/html/test/index.js | 13 ---- .../src/list/test/__snapshots__/index.js.snap | 38 ---------- packages/block-library/src/list/test/index.js | 13 ---- .../src/more/test/__snapshots__/index.js.snap | 13 ---- packages/block-library/src/more/test/index.js | 13 ---- .../nextpage/test/__snapshots__/index.js.snap | 11 --- .../block-library/src/nextpage/test/index.js | 13 ---- .../test/__snapshots__/index.js.snap | 38 ---------- .../block-library/src/paragraph/test/index.js | 13 ---- .../test/__snapshots__/index.js.snap | 34 --------- .../src/preformatted/test/index.js | 13 ---- .../test/__snapshots__/index.js.snap | 44 ----------- .../block-library/src/pullquote/test/index.js | 13 ---- .../quote/test/__snapshots__/index.js.snap | 42 ---------- .../block-library/src/quote/test/index.js | 13 ---- .../test/__snapshots__/index.js.snap | 7 -- .../block-library/src/separator/test/index.js | 13 ---- .../test/__snapshots__/index.js.snap | 33 -------- .../block-library/src/shortcode/test/index.js | 13 ---- .../table/test/__snapshots__/index.js.snap | 54 ------------- .../block-library/src/table/test/index.js | 13 ---- .../block-library/src/test/helpers/index.js | 33 -------- .../test/__snapshots__/index.js.snap | 76 ------------------- .../src/text-columns/test/index.js | 16 ---- .../verse/test/__snapshots__/index.js.snap | 34 --------- .../block-library/src/verse/test/index.js | 13 ---- .../video/test/__snapshots__/index.js.snap | 53 ------------- .../block-library/src/video/test/index.js | 13 ---- 43 files changed, 1 insertion(+), 1041 deletions(-) delete mode 100644 packages/block-library/src/audio/test/__snapshots__/index.js.snap delete mode 100644 packages/block-library/src/audio/test/index.js delete mode 100644 packages/block-library/src/button/test/__snapshots__/index.js.snap delete mode 100644 packages/block-library/src/button/test/index.js delete mode 100644 packages/block-library/src/classic/test/__snapshots__/index.js.snap delete mode 100644 packages/block-library/src/classic/test/index.js delete mode 100644 packages/block-library/src/code/test/__snapshots__/index.js.snap delete mode 100644 packages/block-library/src/code/test/index.js delete mode 100644 packages/block-library/src/cover/test/__snapshots__/index.js.snap delete mode 100644 packages/block-library/src/cover/test/index.js delete mode 100644 packages/block-library/src/gallery/test/__snapshots__/index.js.snap delete mode 100644 packages/block-library/src/gallery/test/index.js delete mode 100644 packages/block-library/src/heading/test/__snapshots__/index.js.snap delete mode 100644 packages/block-library/src/html/test/__snapshots__/index.js.snap delete mode 100644 packages/block-library/src/html/test/index.js delete mode 100644 packages/block-library/src/list/test/__snapshots__/index.js.snap delete mode 100644 packages/block-library/src/list/test/index.js delete mode 100644 packages/block-library/src/more/test/__snapshots__/index.js.snap delete mode 100644 packages/block-library/src/more/test/index.js delete mode 100644 packages/block-library/src/nextpage/test/__snapshots__/index.js.snap delete mode 100644 packages/block-library/src/nextpage/test/index.js delete mode 100644 packages/block-library/src/paragraph/test/__snapshots__/index.js.snap delete mode 100644 packages/block-library/src/paragraph/test/index.js delete mode 100644 packages/block-library/src/preformatted/test/__snapshots__/index.js.snap delete mode 100644 packages/block-library/src/preformatted/test/index.js delete mode 100644 packages/block-library/src/pullquote/test/__snapshots__/index.js.snap delete mode 100644 packages/block-library/src/pullquote/test/index.js delete mode 100644 packages/block-library/src/quote/test/__snapshots__/index.js.snap delete mode 100644 packages/block-library/src/quote/test/index.js delete mode 100644 packages/block-library/src/separator/test/__snapshots__/index.js.snap delete mode 100644 packages/block-library/src/separator/test/index.js delete mode 100644 packages/block-library/src/shortcode/test/__snapshots__/index.js.snap delete mode 100644 packages/block-library/src/shortcode/test/index.js delete mode 100644 packages/block-library/src/table/test/__snapshots__/index.js.snap delete mode 100644 packages/block-library/src/table/test/index.js delete mode 100644 packages/block-library/src/test/helpers/index.js delete mode 100644 packages/block-library/src/text-columns/test/__snapshots__/index.js.snap delete mode 100644 packages/block-library/src/text-columns/test/index.js delete mode 100644 packages/block-library/src/verse/test/__snapshots__/index.js.snap delete mode 100644 packages/block-library/src/verse/test/index.js delete mode 100644 packages/block-library/src/video/test/__snapshots__/index.js.snap delete mode 100644 packages/block-library/src/video/test/index.js diff --git a/packages/block-library/src/audio/test/__snapshots__/index.js.snap b/packages/block-library/src/audio/test/__snapshots__/index.js.snap deleted file mode 100644 index 09f551226b702..0000000000000 --- a/packages/block-library/src/audio/test/__snapshots__/index.js.snap +++ /dev/null @@ -1,53 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`core/audio block edit matches snapshot 1`] = ` -<div - class="components-placeholder editor-media-placeholder wp-block-audio" -> - <div - class="components-placeholder__label" - > - <span - class="editor-block-icon" - > - <svg - aria-hidden="true" - focusable="false" - height="24" - role="img" - viewBox="0 0 24 24" - width="24" - xmlns="http://www.w3.org/2000/svg" - > - <path - d="M0,0h24v24H0V0z" - fill="none" - /> - <path - d="m12 3l0.01 10.55c-0.59-0.34-1.27-0.55-2-0.55-2.22 0-4.01 1.79-4.01 4s1.79 4 4.01 4 3.99-1.79 3.99-4v-10h4v-4h-6zm-1.99 16c-1.1 0-2-0.9-2-2s0.9-2 2-2 2 0.9 2 2-0.9 2-2 2z" - /> - </svg> - </span> - Audio - </div> - <div - class="components-placeholder__instructions" - > - Drag an audio, upload a new one or select a file from your library. - </div> - <div - class="components-placeholder__fieldset" - > - <div - class="editor-media-placeholder__url-input-container" - > - <button - class="components-button editor-media-placeholder__button is-button is-default is-large" - type="button" - > - Insert from URL - </button> - </div> - </div> -</div> -`; diff --git a/packages/block-library/src/audio/test/index.js b/packages/block-library/src/audio/test/index.js deleted file mode 100644 index a5611a3dd8ce6..0000000000000 --- a/packages/block-library/src/audio/test/index.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Internal dependencies - */ -import { name, settings } from '../'; -import { blockEditRender } from '../../test/helpers'; - -describe( 'core/audio', () => { - test( 'block edit matches snapshot', () => { - const wrapper = blockEditRender( name, settings ); - - expect( wrapper ).toMatchSnapshot(); - } ); -} ); diff --git a/packages/block-library/src/button/test/__snapshots__/index.js.snap b/packages/block-library/src/button/test/__snapshots__/index.js.snap deleted file mode 100644 index fec3d15878b50..0000000000000 --- a/packages/block-library/src/button/test/__snapshots__/index.js.snap +++ /dev/null @@ -1,42 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`core/button block edit matches snapshot 1`] = ` -<div> - - <div - class="wp-block-button" - > - <div - class="editor-rich-text" - > - <div> - <div> - <div - class="components-autocomplete" - > - <div - aria-autocomplete="list" - aria-label="Add text…" - aria-multiline="true" - class="wp-block-button__link editor-rich-text__editable" - contenteditable="true" - data-is-placeholder-visible="true" - role="textbox" - > - <br - data-rich-text-padding="true" - /> - </div> - <div - class="editor-rich-text__editable wp-block-button__link" - > - Add text… - </div> - </div> - </div> - </div> - </div> - </div> - -</div> -`; diff --git a/packages/block-library/src/button/test/index.js b/packages/block-library/src/button/test/index.js deleted file mode 100644 index ed83ce055e6e2..0000000000000 --- a/packages/block-library/src/button/test/index.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Internal dependencies - */ -import { name, settings } from '../'; -import { blockEditRender } from '../../test/helpers'; - -describe( 'core/button', () => { - test( 'block edit matches snapshot', () => { - const wrapper = blockEditRender( name, settings ); - - expect( wrapper ).toMatchSnapshot(); - } ); -} ); diff --git a/packages/block-library/src/classic/test/__snapshots__/index.js.snap b/packages/block-library/src/classic/test/__snapshots__/index.js.snap deleted file mode 100644 index 690b0d5e30fdd..0000000000000 --- a/packages/block-library/src/classic/test/__snapshots__/index.js.snap +++ /dev/null @@ -1,15 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`core/freeform block edit matches snapshot 1`] = ` -Array [ - <div - class="block-library-classic__toolbar" - data-placeholder="Classic" - id="toolbar-undefined" - />, - <div - class="wp-block-freeform block-library-rich-text__tinymce" - id="editor-undefined" - />, -] -`; diff --git a/packages/block-library/src/classic/test/index.js b/packages/block-library/src/classic/test/index.js deleted file mode 100644 index 824c835543e7b..0000000000000 --- a/packages/block-library/src/classic/test/index.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Internal dependencies - */ -import { name, settings } from '../'; -import { blockEditRender } from '../../test/helpers'; - -describe( 'core/freeform', () => { - test( 'block edit matches snapshot', () => { - const wrapper = blockEditRender( name, settings ); - - expect( wrapper ).toMatchSnapshot(); - } ); -} ); diff --git a/packages/block-library/src/code/test/__snapshots__/index.js.snap b/packages/block-library/src/code/test/__snapshots__/index.js.snap deleted file mode 100644 index 4d44efaa90a74..0000000000000 --- a/packages/block-library/src/code/test/__snapshots__/index.js.snap +++ /dev/null @@ -1,14 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`core/code block edit matches snapshot 1`] = ` -<div - class="wp-block-code" -> - <textarea - aria-label="Code" - class="editor-plain-text" - placeholder="Write code…" - rows="1" - /> -</div> -`; diff --git a/packages/block-library/src/code/test/index.js b/packages/block-library/src/code/test/index.js deleted file mode 100644 index a34add8c0c53a..0000000000000 --- a/packages/block-library/src/code/test/index.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Internal dependencies - */ -import { name, settings } from '../'; -import { blockEditRender } from '../../test/helpers'; - -describe( 'core/code', () => { - test( 'block edit matches snapshot', () => { - const wrapper = blockEditRender( name, settings ); - - expect( wrapper ).toMatchSnapshot(); - } ); -} ); diff --git a/packages/block-library/src/cover/test/__snapshots__/index.js.snap b/packages/block-library/src/cover/test/__snapshots__/index.js.snap deleted file mode 100644 index 36fb23ceb5916..0000000000000 --- a/packages/block-library/src/cover/test/__snapshots__/index.js.snap +++ /dev/null @@ -1,42 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`core/cover block edit matches snapshot 1`] = ` -<div - class="components-placeholder editor-media-placeholder wp-block-cover" -> - <div - class="components-placeholder__label" - > - <span - class="editor-block-icon" - > - <svg - aria-hidden="true" - focusable="false" - height="24" - role="img" - viewBox="0 0 24 24" - width="24" - xmlns="http://www.w3.org/2000/svg" - > - <path - d="M4 4h7V2H4c-1.1 0-2 .9-2 2v7h2V4zm6 9l-4 5h12l-3-4-2.03 2.71L10 13zm7-4.5c0-.83-.67-1.5-1.5-1.5S14 7.67 14 8.5s.67 1.5 1.5 1.5S17 9.33 17 8.5zM20 2h-7v2h7v7h2V4c0-1.1-.9-2-2-2zm0 18h-7v2h7c1.1 0 2-.9 2-2v-7h-2v7zM4 13H2v7c0 1.1.9 2 2 2h7v-2H4v-7z" - /> - <path - d="M0 0h24v24H0z" - fill="none" - /> - </svg> - </span> - Cover - </div> - <div - class="components-placeholder__instructions" - > - Drag an image or a video, upload a new one or select a file from your library. - </div> - <div - class="components-placeholder__fieldset" - /> -</div> -`; diff --git a/packages/block-library/src/cover/test/index.js b/packages/block-library/src/cover/test/index.js deleted file mode 100644 index a088404e7c41d..0000000000000 --- a/packages/block-library/src/cover/test/index.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Internal dependencies - */ -import { name, settings } from '../'; -import { blockEditRender } from '../../test/helpers'; - -describe( 'core/cover', () => { - test( 'block edit matches snapshot', () => { - const wrapper = blockEditRender( name, settings ); - - expect( wrapper ).toMatchSnapshot(); - } ); -} ); diff --git a/packages/block-library/src/gallery/test/__snapshots__/index.js.snap b/packages/block-library/src/gallery/test/__snapshots__/index.js.snap deleted file mode 100644 index 6e88dd4a4f949..0000000000000 --- a/packages/block-library/src/gallery/test/__snapshots__/index.js.snap +++ /dev/null @@ -1,44 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`core/gallery block edit matches snapshot 1`] = ` -<div - class="components-placeholder editor-media-placeholder wp-block-gallery" -> - <div - class="components-placeholder__label" - > - <span - class="editor-block-icon" - > - <svg - aria-hidden="true" - focusable="false" - height="24" - role="img" - viewBox="0 0 24 24" - width="24" - xmlns="http://www.w3.org/2000/svg" - > - <path - d="M0 0h24v24H0V0z" - fill="none" - /> - <g> - <path - d="M20 4v12H8V4h12m0-2H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-8.5 9.67l1.69 2.26 2.48-3.1L19 15H9zM2 6v14c0 1.1.9 2 2 2h14v-2H4V6H2z" - /> - </g> - </svg> - </span> - Gallery - </div> - <div - class="components-placeholder__instructions" - > - Drag images, upload new ones or select files from your library. - </div> - <div - class="components-placeholder__fieldset" - /> -</div> -`; diff --git a/packages/block-library/src/gallery/test/index.js b/packages/block-library/src/gallery/test/index.js deleted file mode 100644 index 5731cc00ba483..0000000000000 --- a/packages/block-library/src/gallery/test/index.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Internal dependencies - */ -import { name, settings } from '../'; -import { blockEditRender } from '../../test/helpers'; - -describe( 'core/gallery', () => { - test( 'block edit matches snapshot', () => { - const wrapper = blockEditRender( name, settings ); - - expect( wrapper ).toMatchSnapshot(); - } ); -} ); diff --git a/packages/block-library/src/heading/test/__snapshots__/index.js.snap b/packages/block-library/src/heading/test/__snapshots__/index.js.snap deleted file mode 100644 index 0df94748b04b5..0000000000000 --- a/packages/block-library/src/heading/test/__snapshots__/index.js.snap +++ /dev/null @@ -1,34 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`core/heading block edit matches snapshot 1`] = ` -<div - class="wp-block-heading editor-rich-text" -> - <div> - <div> - <div - class="components-autocomplete" - > - <h2 - aria-autocomplete="list" - aria-label="Write heading…" - aria-multiline="true" - class="editor-rich-text__editable" - contenteditable="true" - data-is-placeholder-visible="true" - role="textbox" - > - <br - data-rich-text-padding="true" - /> - </h2> - <h2 - class="editor-rich-text__editable" - > - Write heading… - </h2> - </div> - </div> - </div> -</div> -`; diff --git a/packages/block-library/src/heading/test/index.js b/packages/block-library/src/heading/test/index.js index 58a3ec1770347..ffa9d2538674d 100644 --- a/packages/block-library/src/heading/test/index.js +++ b/packages/block-library/src/heading/test/index.js @@ -1,16 +1,7 @@ /** * Internal dependencies */ -import { name, settings, getLevelFromHeadingNodeName } from '../'; -import { blockEditRender } from '../../test/helpers'; - -describe( 'core/heading', () => { - test( 'block edit matches snapshot', () => { - const wrapper = blockEditRender( name, settings ); - - expect( wrapper ).toMatchSnapshot(); - } ); -} ); +import { getLevelFromHeadingNodeName } from '../'; describe( 'getLevelFromHeadingNodeName()', () => { it( 'should return a numeric value from nodeName', () => { diff --git a/packages/block-library/src/html/test/__snapshots__/index.js.snap b/packages/block-library/src/html/test/__snapshots__/index.js.snap deleted file mode 100644 index 5d35f054994fb..0000000000000 --- a/packages/block-library/src/html/test/__snapshots__/index.js.snap +++ /dev/null @@ -1,14 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`core/html block edit matches snapshot 1`] = ` -<div - class="wp-block-html" -> - <textarea - aria-label="HTML" - class="editor-plain-text" - placeholder="Write HTML…" - rows="1" - /> -</div> -`; diff --git a/packages/block-library/src/html/test/index.js b/packages/block-library/src/html/test/index.js deleted file mode 100644 index 2da5c107bea5b..0000000000000 --- a/packages/block-library/src/html/test/index.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Internal dependencies - */ -import { name, settings } from '../'; -import { blockEditRender } from '../../test/helpers'; - -describe( 'core/html', () => { - test( 'block edit matches snapshot', () => { - const wrapper = blockEditRender( name, settings ); - - expect( wrapper ).toMatchSnapshot(); - } ); -} ); diff --git a/packages/block-library/src/list/test/__snapshots__/index.js.snap b/packages/block-library/src/list/test/__snapshots__/index.js.snap deleted file mode 100644 index 3015cc0fcaff3..0000000000000 --- a/packages/block-library/src/list/test/__snapshots__/index.js.snap +++ /dev/null @@ -1,38 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`core/list block edit matches snapshot 1`] = ` -<div - class="block-library-list editor-rich-text" -> - <div> - <div> - <div - class="components-autocomplete" - > - <ul - aria-autocomplete="list" - aria-label="Write list…" - aria-multiline="true" - class="editor-rich-text__editable" - contenteditable="true" - data-is-placeholder-visible="true" - role="textbox" - > - <li> - <br - data-rich-text-padding="true" - /> - </li> - </ul> - <ul - class="editor-rich-text__editable" - > - <li> - Write list… - </li> - </ul> - </div> - </div> - </div> -</div> -`; diff --git a/packages/block-library/src/list/test/index.js b/packages/block-library/src/list/test/index.js deleted file mode 100644 index 9ed0ec09a19b3..0000000000000 --- a/packages/block-library/src/list/test/index.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Internal dependencies - */ -import { name, settings } from '../'; -import { blockEditRender } from '../../test/helpers'; - -describe( 'core/list', () => { - test( 'block edit matches snapshot', () => { - const wrapper = blockEditRender( name, settings ); - - expect( wrapper ).toMatchSnapshot(); - } ); -} ); diff --git a/packages/block-library/src/more/test/__snapshots__/index.js.snap b/packages/block-library/src/more/test/__snapshots__/index.js.snap deleted file mode 100644 index 500ae5f763fc7..0000000000000 --- a/packages/block-library/src/more/test/__snapshots__/index.js.snap +++ /dev/null @@ -1,13 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`core/more block edit matches snapshot 1`] = ` -<div - class="wp-block-more" -> - <input - size="10" - type="text" - value="Read more" - /> -</div> -`; diff --git a/packages/block-library/src/more/test/index.js b/packages/block-library/src/more/test/index.js deleted file mode 100644 index 6c50a352c1033..0000000000000 --- a/packages/block-library/src/more/test/index.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Internal dependencies - */ -import { name, settings } from '../'; -import { blockEditRender } from '../../test/helpers'; - -describe( 'core/more', () => { - test( 'block edit matches snapshot', () => { - const wrapper = blockEditRender( name, settings ); - - expect( wrapper ).toMatchSnapshot(); - } ); -} ); diff --git a/packages/block-library/src/nextpage/test/__snapshots__/index.js.snap b/packages/block-library/src/nextpage/test/__snapshots__/index.js.snap deleted file mode 100644 index 9a324d23afcca..0000000000000 --- a/packages/block-library/src/nextpage/test/__snapshots__/index.js.snap +++ /dev/null @@ -1,11 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`core/nextpage block edit matches snapshot 1`] = ` -<div - class="wp-block-nextpage" -> - <span> - Page break - </span> -</div> -`; diff --git a/packages/block-library/src/nextpage/test/index.js b/packages/block-library/src/nextpage/test/index.js deleted file mode 100644 index 8277f13d6a4f0..0000000000000 --- a/packages/block-library/src/nextpage/test/index.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Internal dependencies - */ -import { name, settings } from '../'; -import { blockEditRender } from '../../test/helpers'; - -describe( 'core/nextpage', () => { - test( 'block edit matches snapshot', () => { - const wrapper = blockEditRender( name, settings ); - - expect( wrapper ).toMatchSnapshot(); - } ); -} ); diff --git a/packages/block-library/src/paragraph/test/__snapshots__/index.js.snap b/packages/block-library/src/paragraph/test/__snapshots__/index.js.snap deleted file mode 100644 index 52a4f07a4b653..0000000000000 --- a/packages/block-library/src/paragraph/test/__snapshots__/index.js.snap +++ /dev/null @@ -1,38 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`core/paragraph block edit matches snapshot 1`] = ` -<div> - - <div - class="editor-rich-text" - > - <div> - <div> - <div - class="components-autocomplete" - > - <p - aria-autocomplete="list" - aria-label="Empty block; start writing or type forward slash to choose a block" - aria-multiline="true" - class="wp-block-paragraph editor-rich-text__editable" - contenteditable="true" - data-is-placeholder-visible="true" - role="textbox" - > - <br - data-rich-text-padding="true" - /> - </p> - <p - class="editor-rich-text__editable wp-block-paragraph" - > - Start writing or type / to choose a block - </p> - </div> - </div> - </div> - </div> - -</div> -`; diff --git a/packages/block-library/src/paragraph/test/index.js b/packages/block-library/src/paragraph/test/index.js deleted file mode 100644 index fb7e03415a536..0000000000000 --- a/packages/block-library/src/paragraph/test/index.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Internal dependencies - */ -import { name, settings } from '../'; -import { blockEditRender } from '../../test/helpers'; - -describe( 'core/paragraph', () => { - test( 'block edit matches snapshot', () => { - const wrapper = blockEditRender( name, settings ); - - expect( wrapper ).toMatchSnapshot(); - } ); -} ); diff --git a/packages/block-library/src/preformatted/test/__snapshots__/index.js.snap b/packages/block-library/src/preformatted/test/__snapshots__/index.js.snap deleted file mode 100644 index a18b12cb5728f..0000000000000 --- a/packages/block-library/src/preformatted/test/__snapshots__/index.js.snap +++ /dev/null @@ -1,34 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`core/preformatted block edit matches snapshot 1`] = ` -<div - class="wp-block-preformatted editor-rich-text" -> - <div> - <div> - <div - class="components-autocomplete" - > - <pre - aria-autocomplete="list" - aria-label="Write preformatted text…" - aria-multiline="true" - class="editor-rich-text__editable" - contenteditable="true" - data-is-placeholder-visible="true" - role="textbox" - > - <br - data-rich-text-padding="true" - /> - </pre> - <pre - class="editor-rich-text__editable" - > - Write preformatted text… - </pre> - </div> - </div> - </div> -</div> -`; diff --git a/packages/block-library/src/preformatted/test/index.js b/packages/block-library/src/preformatted/test/index.js deleted file mode 100644 index b658b059a067e..0000000000000 --- a/packages/block-library/src/preformatted/test/index.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Internal dependencies - */ -import { name, settings } from '../'; -import { blockEditRender } from '../../test/helpers'; - -describe( 'core/preformatted', () => { - test( 'block edit matches snapshot', () => { - const wrapper = blockEditRender( name, settings ); - - expect( wrapper ).toMatchSnapshot(); - } ); -} ); diff --git a/packages/block-library/src/pullquote/test/__snapshots__/index.js.snap b/packages/block-library/src/pullquote/test/__snapshots__/index.js.snap deleted file mode 100644 index 824b6c491a8ad..0000000000000 --- a/packages/block-library/src/pullquote/test/__snapshots__/index.js.snap +++ /dev/null @@ -1,44 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`core/pullquote block edit matches snapshot 1`] = ` -<figure - class="wp-block-pullquote" -> - <blockquote> - <div - class="block-library-pullquote__content editor-rich-text" - > - <div> - <div> - <div - class="components-autocomplete" - > - <div - aria-autocomplete="list" - aria-label="Write quote…" - aria-multiline="true" - class="editor-rich-text__editable" - contenteditable="true" - data-is-placeholder-visible="true" - role="textbox" - > - <p> - <br - data-rich-text-padding="true" - /> - </p> - </div> - <div - class="editor-rich-text__editable" - > - <p> - Write quote… - </p> - </div> - </div> - </div> - </div> - </div> - </blockquote> -</figure> -`; diff --git a/packages/block-library/src/pullquote/test/index.js b/packages/block-library/src/pullquote/test/index.js deleted file mode 100644 index 24e135dd4c02c..0000000000000 --- a/packages/block-library/src/pullquote/test/index.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Internal dependencies - */ -import { name, settings } from '../'; -import { blockEditRender } from '../../test/helpers'; - -describe( 'core/pullquote', () => { - test( 'block edit matches snapshot', () => { - const wrapper = blockEditRender( name, settings ); - - expect( wrapper ).toMatchSnapshot(); - } ); -} ); diff --git a/packages/block-library/src/quote/test/__snapshots__/index.js.snap b/packages/block-library/src/quote/test/__snapshots__/index.js.snap deleted file mode 100644 index 886ef7e4560a8..0000000000000 --- a/packages/block-library/src/quote/test/__snapshots__/index.js.snap +++ /dev/null @@ -1,42 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`core/quote block edit matches snapshot 1`] = ` -<blockquote - class="wp-block-quote" -> - <div - class="editor-rich-text" - > - <div> - <div> - <div - class="components-autocomplete" - > - <div - aria-autocomplete="list" - aria-label="Write quote…" - aria-multiline="true" - class="editor-rich-text__editable" - contenteditable="true" - data-is-placeholder-visible="true" - role="textbox" - > - <p> - <br - data-rich-text-padding="true" - /> - </p> - </div> - <div - class="editor-rich-text__editable" - > - <p> - Write quote… - </p> - </div> - </div> - </div> - </div> - </div> -</blockquote> -`; diff --git a/packages/block-library/src/quote/test/index.js b/packages/block-library/src/quote/test/index.js deleted file mode 100644 index 08553f5d8fd09..0000000000000 --- a/packages/block-library/src/quote/test/index.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Internal dependencies - */ -import { name, settings } from '../'; -import { blockEditRender } from '../../test/helpers'; - -describe( 'core/quote', () => { - test( 'block edit matches snapshot', () => { - const wrapper = blockEditRender( name, settings ); - - expect( wrapper ).toMatchSnapshot(); - } ); -} ); diff --git a/packages/block-library/src/separator/test/__snapshots__/index.js.snap b/packages/block-library/src/separator/test/__snapshots__/index.js.snap deleted file mode 100644 index 2985274d75ea3..0000000000000 --- a/packages/block-library/src/separator/test/__snapshots__/index.js.snap +++ /dev/null @@ -1,7 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`core/separator block edit matches snapshot 1`] = ` -<hr - class="wp-block-separator" -/> -`; diff --git a/packages/block-library/src/separator/test/index.js b/packages/block-library/src/separator/test/index.js deleted file mode 100644 index 8041257d0caa6..0000000000000 --- a/packages/block-library/src/separator/test/index.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Internal dependencies - */ -import { name, settings } from '../'; -import { blockEditRender } from '../../test/helpers'; - -describe( 'core/separator', () => { - test( 'block edit matches snapshot', () => { - const wrapper = blockEditRender( name, settings ); - - expect( wrapper ).toMatchSnapshot(); - } ); -} ); diff --git a/packages/block-library/src/shortcode/test/__snapshots__/index.js.snap b/packages/block-library/src/shortcode/test/__snapshots__/index.js.snap deleted file mode 100644 index 5ef96b79116b0..0000000000000 --- a/packages/block-library/src/shortcode/test/__snapshots__/index.js.snap +++ /dev/null @@ -1,33 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`core/shortcode block edit matches snapshot 1`] = ` -<div - class="wp-block-shortcode" -> - <label - for="blocks-shortcode-input-0" - > - <svg - aria-hidden="true" - class="dashicon dashicons-shortcode" - focusable="false" - height="20" - role="img" - viewBox="0 0 20 20" - width="20" - xmlns="http://www.w3.org/2000/svg" - > - <path - d="M6 14H4V6h2V4H2v12h4M7.1 17h2.1l3.7-14h-2.1M14 4v2h2v8h-2v2h4V4" - /> - </svg> - Shortcode - </label> - <textarea - class="editor-plain-text input-control" - id="blocks-shortcode-input-0" - placeholder="Write shortcode here…" - rows="1" - /> -</div> -`; diff --git a/packages/block-library/src/shortcode/test/index.js b/packages/block-library/src/shortcode/test/index.js deleted file mode 100644 index e0fe629323ba7..0000000000000 --- a/packages/block-library/src/shortcode/test/index.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Internal dependencies - */ -import { name, settings } from '../'; -import { blockEditRender } from '../../test/helpers'; - -describe( 'core/shortcode', () => { - test( 'block edit matches snapshot', () => { - const wrapper = blockEditRender( name, settings ); - - expect( wrapper ).toMatchSnapshot(); - } ); -} ); diff --git a/packages/block-library/src/table/test/__snapshots__/index.js.snap b/packages/block-library/src/table/test/__snapshots__/index.js.snap deleted file mode 100644 index 6361bb4649ea4..0000000000000 --- a/packages/block-library/src/table/test/__snapshots__/index.js.snap +++ /dev/null @@ -1,54 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`core/table block edit matches snapshot 1`] = ` -<form> - <div - class="components-base-control" - > - <div - class="components-base-control__field" - > - <label - class="components-base-control__label" - for="inspector-text-control-0" - > - Column Count - </label> - <input - class="components-text-control__input" - id="inspector-text-control-0" - min="1" - type="number" - value="2" - /> - </div> - </div> - <div - class="components-base-control" - > - <div - class="components-base-control__field" - > - <label - class="components-base-control__label" - for="inspector-text-control-1" - > - Row Count - </label> - <input - class="components-text-control__input" - id="inspector-text-control-1" - min="1" - type="number" - value="2" - /> - </div> - </div> - <button - class="components-button is-button is-primary" - type="submit" - > - Create - </button> -</form> -`; diff --git a/packages/block-library/src/table/test/index.js b/packages/block-library/src/table/test/index.js deleted file mode 100644 index cfd3b3916a6ff..0000000000000 --- a/packages/block-library/src/table/test/index.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Internal dependencies - */ -import { name, settings } from '../'; -import { blockEditRender } from '../../test/helpers'; - -describe( 'core/table', () => { - test( 'block edit matches snapshot', () => { - const wrapper = blockEditRender( name, settings ); - - expect( wrapper ).toMatchSnapshot(); - } ); -} ); diff --git a/packages/block-library/src/test/helpers/index.js b/packages/block-library/src/test/helpers/index.js deleted file mode 100644 index 2f672f2090bd3..0000000000000 --- a/packages/block-library/src/test/helpers/index.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * External dependencies - */ -import { render } from 'enzyme'; -import { noop } from 'lodash'; - -/** - * WordPress dependencies - */ -import { - createBlock, - getBlockType, - registerBlockType, -} from '@wordpress/blocks'; -import { BlockEdit } from '@wordpress/block-editor'; -import '@wordpress/editor'; - -export const blockEditRender = ( name, settings ) => { - if ( ! getBlockType( name ) ) { - registerBlockType( name, settings ); - } - const block = createBlock( name ); - - return render( - <BlockEdit - name={ name } - isSelected={ false } - attributes={ block.attributes } - setAttributes={ noop } - user={ {} } - /> - ); -}; diff --git a/packages/block-library/src/text-columns/test/__snapshots__/index.js.snap b/packages/block-library/src/text-columns/test/__snapshots__/index.js.snap deleted file mode 100644 index 4f89f08374630..0000000000000 --- a/packages/block-library/src/text-columns/test/__snapshots__/index.js.snap +++ /dev/null @@ -1,76 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`core/text-columns block edit matches snapshot 1`] = ` -<div - class="wp-block-text-columns alignundefined columns-2" -> - <div - class="wp-block-column" - > - <div - class="editor-rich-text" - > - <div> - <div> - <div - class="components-autocomplete" - > - <p - aria-autocomplete="list" - aria-label="New Column" - aria-multiline="true" - class="editor-rich-text__editable" - contenteditable="true" - data-is-placeholder-visible="true" - role="textbox" - > - <br - data-rich-text-padding="true" - /> - </p> - <p - class="editor-rich-text__editable" - > - New Column - </p> - </div> - </div> - </div> - </div> - </div> - <div - class="wp-block-column" - > - <div - class="editor-rich-text" - > - <div> - <div> - <div - class="components-autocomplete" - > - <p - aria-autocomplete="list" - aria-label="New Column" - aria-multiline="true" - class="editor-rich-text__editable" - contenteditable="true" - data-is-placeholder-visible="true" - role="textbox" - > - <br - data-rich-text-padding="true" - /> - </p> - <p - class="editor-rich-text__editable" - > - New Column - </p> - </div> - </div> - </div> - </div> - </div> -</div> -`; diff --git a/packages/block-library/src/text-columns/test/index.js b/packages/block-library/src/text-columns/test/index.js deleted file mode 100644 index 61f0796e30ce8..0000000000000 --- a/packages/block-library/src/text-columns/test/index.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Internal dependencies - */ -import { name, settings } from '../'; -import { blockEditRender } from '../../test/helpers'; - -describe( 'core/text-columns', () => { - test( 'block edit matches snapshot', () => { - const wrapper = blockEditRender( name, settings ); - - expect( wrapper ).toMatchSnapshot(); - expect( console ).toHaveWarnedWith( - 'The Text Columns block is deprecated and will be removed. Please use the Columns block instead.' - ); - } ); -} ); diff --git a/packages/block-library/src/verse/test/__snapshots__/index.js.snap b/packages/block-library/src/verse/test/__snapshots__/index.js.snap deleted file mode 100644 index 617e1296fbcad..0000000000000 --- a/packages/block-library/src/verse/test/__snapshots__/index.js.snap +++ /dev/null @@ -1,34 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`core/verse block edit matches snapshot 1`] = ` -<div - class="wp-block-verse editor-rich-text" -> - <div> - <div> - <div - class="components-autocomplete" - > - <pre - aria-autocomplete="list" - aria-label="Write…" - aria-multiline="true" - class="editor-rich-text__editable" - contenteditable="true" - data-is-placeholder-visible="true" - role="textbox" - > - <br - data-rich-text-padding="true" - /> - </pre> - <pre - class="editor-rich-text__editable" - > - Write… - </pre> - </div> - </div> - </div> -</div> -`; diff --git a/packages/block-library/src/verse/test/index.js b/packages/block-library/src/verse/test/index.js deleted file mode 100644 index e1150b440fdc4..0000000000000 --- a/packages/block-library/src/verse/test/index.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Internal dependencies - */ -import { name, settings } from '../'; -import { blockEditRender } from '../../test/helpers'; - -describe( 'core/verse', () => { - test( 'block edit matches snapshot', () => { - const wrapper = blockEditRender( name, settings ); - - expect( wrapper ).toMatchSnapshot(); - } ); -} ); diff --git a/packages/block-library/src/video/test/__snapshots__/index.js.snap b/packages/block-library/src/video/test/__snapshots__/index.js.snap deleted file mode 100644 index 88942a51fb5c1..0000000000000 --- a/packages/block-library/src/video/test/__snapshots__/index.js.snap +++ /dev/null @@ -1,53 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`core/video block edit matches snapshot 1`] = ` -<div - class="components-placeholder editor-media-placeholder wp-block-video" -> - <div - class="components-placeholder__label" - > - <span - class="editor-block-icon" - > - <svg - aria-hidden="true" - focusable="false" - height="24" - role="img" - viewBox="0 0 24 24" - width="24" - xmlns="http://www.w3.org/2000/svg" - > - <path - d="M0 0h24v24H0V0z" - fill="none" - /> - <path - d="M4 6.47L5.76 10H20v8H4V6.47M22 4h-4l2 4h-3l-2-4h-2l2 4h-3l-2-4H8l2 4H7L5 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V4z" - /> - </svg> - </span> - Video - </div> - <div - class="components-placeholder__instructions" - > - Drag a video, upload a new one or select a file from your library. - </div> - <div - class="components-placeholder__fieldset" - > - <div - class="editor-media-placeholder__url-input-container" - > - <button - class="components-button editor-media-placeholder__button is-button is-default is-large" - type="button" - > - Insert from URL - </button> - </div> - </div> -</div> -`; diff --git a/packages/block-library/src/video/test/index.js b/packages/block-library/src/video/test/index.js deleted file mode 100644 index a947f278a5882..0000000000000 --- a/packages/block-library/src/video/test/index.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Internal dependencies - */ -import { name, settings } from '../'; -import { blockEditRender } from '../../test/helpers'; - -describe( 'core/video', () => { - test( 'block edit matches snapshot', () => { - const wrapper = blockEditRender( name, settings ); - - expect( wrapper ).toMatchSnapshot(); - } ); -} ); From f990fce86283f33a86b252690c9b9c210f793621 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Mon, 11 Mar 2019 09:41:45 +0000 Subject: [PATCH 627/691] Fix: Inserter impossible to collapse panels while searching. (#13884) --- .../src/components/inserter/menu.js | 50 +++++++++++++------ 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/packages/block-editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js index 92253b32cfc1d..1ed720113e766 100644 --- a/packages/block-editor/src/components/inserter/menu.js +++ b/packages/block-editor/src/components/inserter/menu.js @@ -170,6 +170,25 @@ export class InserterMenu extends Component { }; } + filterOpenPanels( filterValue, itemsPerCategory, filteredItems, reusableItems ) { + if ( filterValue === this.state.filterValue ) { + return this.state.openPanels; + } + if ( ! filterValue ) { + return [ 'suggested' ]; + } + let openPanels = []; + if ( reusableItems.length > 0 ) { + openPanels.push( 'reusable' ); + } + if ( filteredItems.length > 0 ) { + openPanels = openPanels.concat( + Object.keys( itemsPerCategory ) + ); + } + return openPanels; + } + filter( filterValue = '' ) { const { debouncedSpeak, items, rootChildBlocks } = this.props; const filteredItems = searchItems( items, filterValue ); @@ -193,18 +212,6 @@ export class InserterMenu extends Component { ( itemList ) => groupBy( itemList, 'category' ) )( filteredItems ); - let openPanels = this.state.openPanels; - if ( filterValue !== this.state.filterValue ) { - if ( ! filterValue ) { - openPanels = [ 'suggested' ]; - } else if ( reusableItems.length ) { - openPanels = [ 'reusable' ]; - } else if ( filteredItems.length ) { - const firstCategory = find( getCategories(), ( { slug } ) => itemsPerCategory[ slug ] && itemsPerCategory[ slug ].length ); - openPanels = [ firstCategory.slug ]; - } - } - this.setState( { hoveredItem: null, childItems, @@ -212,7 +219,12 @@ export class InserterMenu extends Component { suggestedItems, reusableItems, itemsPerCategory, - openPanels, + openPanels: this.filterOpenPanels( + filterValue, + itemsPerCategory, + filteredItems, + reusableItems + ), } ); const resultCount = Object.keys( itemsPerCategory ).reduce( ( accumulator, currentCategorySlug ) => { @@ -236,9 +248,15 @@ export class InserterMenu extends Component { render() { const { instanceId, onSelect, rootClientId } = this.props; - const { childItems, filterValue, hoveredItem, suggestedItems, reusableItems, itemsPerCategory, openPanels } = this.state; + const { + childItems, + hoveredItem, + itemsPerCategory, + openPanels, + reusableItems, + suggestedItems, + } = this.state; const isPanelOpen = ( panel ) => openPanels.indexOf( panel ) !== -1; - const isSearching = !! filterValue; // Disable reason (no-autofocus): The inserter menu is a modal display, not one which // is always visible, and one which already incurs this behavior of autoFocus via @@ -300,7 +318,7 @@ export class InserterMenu extends Component { key={ category.slug } title={ category.title } icon={ category.icon } - opened={ isSearching || isPanelOpen( category.slug ) } + opened={ isPanelOpen( category.slug ) } onToggle={ this.onTogglePanel( category.slug ) } ref={ this.bindPanel( category.slug ) } > From d6c0f068e0e21a6a60de8088d0aff0e978ce1a50 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Mon, 11 Mar 2019 10:35:19 +0000 Subject: [PATCH 628/691] Make taxonomies test relieable. (#14340) --- packages/e2e-tests/specs/taxonomies.test.js | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/e2e-tests/specs/taxonomies.test.js b/packages/e2e-tests/specs/taxonomies.test.js index ee3759fe296f0..2cd8e7665eda4 100644 --- a/packages/e2e-tests/specs/taxonomies.test.js +++ b/packages/e2e-tests/specs/taxonomies.test.js @@ -8,6 +8,11 @@ import { publishPost, } from '@wordpress/e2e-test-utils'; +/** + * Module constants + */ +const TAG_TOKEN_SELECTOR = '.components-form-token-field__token-text span:not(.screen-reader-text)'; + describe( 'Taxonomies', () => { const canCreatTermInTaxonomy = ( taxonomy ) => { return page.evaluate( @@ -38,13 +43,13 @@ describe( 'Taxonomies', () => { const getCurrentTags = async () => { const tagsPanel = await findSidebarPanelWithTitle( 'Tags' ); - return page.evaluate( ( node ) => { + return page.evaluate( ( node, selector ) => { return Array.from( node.querySelectorAll( - '.components-form-token-field__token-text span:not(.screen-reader-text)' + selector ) ).map( ( field ) => { return field.innerText; } ); - }, tagsPanel ); + }, tagsPanel, TAG_TOKEN_SELECTOR ); }; it( 'should be able to open the categories panel and create a new main category if the user has the right capabilities', async () => { @@ -123,6 +128,13 @@ describe( 'Taxonomies', () => { // Open the tags panel. await tagsPanel.click( 'button' ); + // At the start there are no tag tokens + expect( + await page.$$( + TAG_TOKEN_SELECTOR + ) + ).toHaveLength( 0 ); + const tagInput = await tagsPanel.$( '.components-form-token-field__input' ); // Click the tag input field. @@ -134,7 +146,7 @@ describe( 'Taxonomies', () => { // Press enter to create a new tag. await tagInput.press( 'Enter' ); - await page.waitForSelector( '.components-form-token-field__token' ); + await page.waitForSelector( TAG_TOKEN_SELECTOR ); // Get an array with the tags of the post. let tags = await getCurrentTags(); From ca1a39a20e85e71beac16c56727e19d6bee6a243 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Mon, 11 Mar 2019 11:47:11 +0100 Subject: [PATCH 629/691] edit-post: set up autogenerated API docs (#14271) --- bin/update-readmes.js | 2 +- packages/edit-post/README.md | 483 ++++++++---------- .../plugin-block-settings-menu-item.js | 53 ++ .../header/plugin-more-menu-item/index.js | 54 ++ .../plugin-sidebar-more-menu-item/index.js | 45 ++ .../plugin-post-publish-panel/index.js | 47 ++ .../sidebar/plugin-post-status-info/index.js | 42 ++ .../sidebar/plugin-pre-publish-panel/index.js | 47 ++ .../sidebar/plugin-sidebar/index.js | 71 ++- 9 files changed, 564 insertions(+), 280 deletions(-) diff --git a/bin/update-readmes.js b/bin/update-readmes.js index f15bedfdd0b64..b649ef5f99603 100755 --- a/bin/update-readmes.js +++ b/bin/update-readmes.js @@ -19,7 +19,7 @@ const packages = [ 'dom', 'dom-ready', 'e2e-test-utils', - //'edit-post', + 'edit-post', 'element', 'escape-html', 'html-entities', diff --git a/packages/edit-post/README.md b/packages/edit-post/README.md index ca4fc9e947dd2..0c8eb1326e884 100644 --- a/packages/edit-post/README.md +++ b/packages/edit-post/README.md @@ -20,21 +20,40 @@ Extending the editor UI can be accomplished with the `registerPlugin` API, allow Refer to [the plugins module documentation](/packages/plugins/README.md) for more information. -## Plugin Components - -The following components can be used with the `registerPlugin` ([see documentation](/packages/plugins/README.md)) API. +The components exported through the API can be used with the `registerPlugin` ([see documentation](/packages/plugins/README.md)) API. They can be found in the global variable `wp.editPost` when defining `wp-edit-post` as a script dependency. -### `PluginBlockSettingsMenuItem` +## API -Renders a new item in the block settings menu. +<!-- START TOKEN(Autogenerated API docs) --> + +### initializeEditor + +[src/index.js#L66-L96](src/index.js#L66-L96) + +Initializes and returns an instance of Editor. + +The return value of this function is not necessary if we change where we +call initializeEditor(). This is due to metaBox timing. + +**Parameters** -Example: +- **id** `string`: Unique identifier for editor instance. +- **postType** `Object`: Post type of the post to edit. +- **postId** `Object`: ID of the post to edit. +- **settings** `?Object`: Editor settings object. +- **initialEdits** `Object`: Programmatic edits to apply initially, to be considered as non-user-initiated (bypass for unsaved changes prompt). -{% codetabs %} +### PluginBlockSettingsMenuItem + +[src/index.js#L98-L98](src/index.js#L98-L98) + +Renders a new item in the block settings menu. + +**Usage** -{% ES5 %} ```js +// Using ES5 syntax var __ = wp.i18n.__; var PluginBlockSettingsMenuItem = wp.editPost.PluginBlockSettingsMenuItem; @@ -55,8 +74,8 @@ function MyPluginBlockSettingsMenuItem() { } ``` -{% ESNext %} ```jsx +// Using ESNext syntax import { __ } from wp.i18n; import { PluginBlockSettingsMenuItem } from wp.editPost; @@ -73,149 +92,29 @@ const MyPluginBlockSettingsMenuItem = () => ( ); ``` -{% end %} - -#### Props - -##### allowedBlockNames - -An array containing a whitelist of block names for which the item should be shown. If this prop is not present the item will be rendered for any block. If multiple blocks are selected, it'll be shown if and only if all of them are in the whitelist. - -- Type: `Array` -- Required: No -- Default: Menu item is shown for any block - -##### icon - -The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered to the left of the menu item label. - -- Type: `String` | `Element` -- Required: No -- Default: Menu item wil be rendered without icon - -##### label - -A string containing the menu item text. - -- Type: `String` -- Required: Yes - -##### onClick - -The callback function to be executed when the user clicks the menu item. - -- Type: `function` -- Required: Yes - -### `PluginSidebar` - -Renders a sidebar when activated. The contents within the `PluginSidebar` will appear as content within the sidebar. - -If you wish to display the sidebar, you can with use the [`PluginSidebarMoreMenuItem`](#pluginsidebarmoremenuitem) component or the `wp.data.dispatch` API: - -```js -wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/sidebar-name' ); -``` - -_Example:_ - -{% codetabs %} - -{% ES5 %} -```js -var __ = wp.i18n.__; -var el = wp.element.createElement; -var PanelBody = wp.components.PanelBody; -var PluginSidebar = wp.editPost.PluginSidebar; - - -function MyPluginSidebar() { - return el( - PluginSidebar, - { - name: 'my-sidebar', - title: 'My sidebar title', - icon: 'smiley', - }, - el( - PanelBody, - {}, - __( 'My sidebar content' ) - ) - ); -} -``` - -{% ESNext %} -```jsx -const { __ } = wp.i18n; -const { PanelBody } = wp.components; -const { PluginSidebar } = wp.editPost; - -const MyPluginSidebar = () => ( - <PluginSidebar - name="my-sidebar" - title="My sidebar title" - icon="smiley" - > - <PanelBody> - { __( 'My sidebar content' ) } - </PanelBody> - </PluginSidebar> -); -``` -{% end %} - -#### Props - -##### name - -A string identifying the sidebar. Must be unique for every sidebar registered within the scope of your plugin. - -- Type: `String` -- Required: Yes +**Parameters** -##### className +- **props** `Object`: Component props. +- **props.allowedBlockNames** `[Array]`: An array containing a list of block names for which the item should be shown. If not present, it'll be rendered for any block. If multiple blocks are selected, it'll be shown if and only if all of them are in the whitelist. +- **props.icon** `[(string|Element)]`: The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element. +- **props.label** `string`: The menu item text. +- **props.onClick** `Function`: Callback function to be executed when the user click the menu item. -An optional class name added to the sidebar body. +**Returns** -- Type: `String` -- Required: No +`WPElement`: The WPElement to be rendered. -##### title +### PluginMoreMenuItem -Title displayed at the top of the sidebar. - -- Type: `String` -- Required: Yes - -##### isPinnable - -Whether to allow to pin sidebar to toolbar. - -- Type: `Boolean` -- Required: No -- Default: `true` - -##### icon - -The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered when the sidebar is pinned to toolbar. - -- Type: `String` | `Element` -- Required: No -- Default: _inherits from the plugin_ - -### `PluginMoreMenuItem` +[src/index.js#L99-L99](src/index.js#L99-L99) Renders a menu item in `Plugins` group in `More Menu` drop down, and can be used to as a button or link depending on the props provided. The text within the component appears as the menu item label. -_Example:_ - -{% codetabs %} +**Usage** -{% ES5 %} ```js +// Using ES5 syntax var __ = wp.i18n.__; var PluginMoreMenuItem = wp.editPost.PluginMoreMenuItem; @@ -235,8 +134,8 @@ function MyButtonMoreMenuItem() { } ``` -{% ESNext %} ```jsx +// Using ESNext syntax const { __ } = wp.i18n; const { PluginMoreMenuItem } = wp.editPost; @@ -253,106 +152,85 @@ const MyButtonMoreMenuItem = () => ( </PluginMoreMenuItem> ); ``` -{% end %} - -#### Props - -`PluginMoreMenuItem` supports the following props. Any additional props are passed through to the underlying [MenuItem](/packages/components/src/menu-item/README.md) component. - -##### href - -When `href` is provided then the menu item is represented as an anchor rather than button. It corresponds to the `href` attribute of the anchor. -- Type: `String` -- Required: No +**Parameters** -##### icon +- **props** `Object`: Component properties. +- **props.href** `[string]`: When `href` is provided then the menu item is represented as an anchor rather than button. It corresponds to the `href` attribute of the anchor. +- **props.icon** `[(string|Element)]`: The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered to the left of the menu item label. +- **props.onClick** `[Function]`: The callback function to be executed when the user clicks the menu item. +- **props.other** `[...*]`: Any additional props are passed through to the underlying [MenuItem](/packages/components/src/menu-item/README.md) component. -The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered to the left of the menu item label. +**Returns** -- Type: `String` | `Element` -- Required: No -- Default: _inherits from the plugin_ +`WPElement`: The element to be rendered. -##### onClick +### PluginPostPublishPanel -The callback function to be executed when the user clicks the menu item. +[src/index.js#L100-L100](src/index.js#L100-L100) -- Type: `function` -- Required: No -- Default: _function which does nothing_ +Renders provided content to the post-publish panel in the publish flow +(side panel that opens after a user publishes the post). -### `PluginSidebarMoreMenuItem` +**Usage** -Renders a menu item in `Plugins` group in `More Menu` drop down, and can be used to activate the corresponding `PluginSidebar` component. -The text within the component appears as the menu item label. - -_Example:_ - -{% codetabs %} - -{% ES5 %} ```js +// Using ES5 syntax var __ = wp.i18n.__; -var PluginSidebarMoreMenuItem = wp.editPost.PluginSidebarMoreMenuItem; +var PluginPostPublishPanel = wp.editPost.PluginPostPublishPanel; -function MySidebarMoreMenuItem() { +function MyPluginPostPublishPanel() { return wp.element.createElement( - PluginSidebarMoreMenuItem, + PluginPostPublishPanel, { - target: 'my-sidebar', - icon: 'smiley', + className: 'my-plugin-post-publish-panel', + title: __( 'My panel title' ), + initialOpen: true, }, - __( 'My sidebar title' ) - ) + __( 'My panel content' ) + ); } ``` -{% ESNext %} ```jsx +// Using ESNext syntax const { __ } = wp.i18n; -const { PluginSidebarMoreMenuItem } = wp.editPost; +const { PluginPostPublishPanel } = wp.editPost; -const MySidebarMoreMenuItem = () => ( - <PluginSidebarMoreMenuItem - target="my-sidebar" - icon="smiley" +const MyPluginPostPublishPanel = () => ( + <PluginPostPublishPanel + className="my-plugin-post-publish-panel" + title={ __( 'My panel title' ) } + initialOpen={ true } > - { __( 'My sidebar title' ) } - </PluginSidebarMoreMenuItem> + { __( 'My panel content' ) } + </PluginPostPublishPanel> ); ``` -{% end %} -#### Props +**Parameters** -##### target +- **props** `Object`: Component properties. +- **props.className** `[string]`: An optional class name added to the panel. +- **props.title** `[string]`: Title displayed at the top of the panel. +- **props.initialOpen** `[boolean]`: Whether to have the panel initially opened. When no title is provided it is always opened. -A string identifying the target sidebar you wish to be activated by this menu item. Must be the same as the `name` prop you have given to that sidebar. +**Returns** -- Type: `String` -- Required: Yes +`WPElement`: The WPElement to be rendered. -##### icon +### PluginPostStatusInfo -The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered to the left of the menu item label. - -- Type: `String` | `Element` -- Required: No -- Default: _inherits from the plugin_ - - -### `PluginPostStatusInfo` +[src/index.js#L101-L101](src/index.js#L101-L101) Renders a row in the Status & Visibility panel of the Document sidebar. -It should be noted that this is named and implemented around the function it serves and not its location, which may change in future iterations. +It should be noted that this is named and implemented around the function it serves +and not its location, which may change in future iterations. -_Example:_ +**Usage** -{% codetabs %} - -{% ES5 %} ```js +// Using ES5 syntax var __ = wp.i18n.__; var PluginPostStatusInfo = wp.editPost.PluginPostStatusInfo; @@ -367,8 +245,8 @@ function MyPluginPostStatusInfo() { } ``` -{% ESNext %} ```jsx +// Using ESNext syntax const { __ } = wp.i18n; const { PluginPostStatusInfo } = wp.editPost; @@ -380,27 +258,27 @@ const MyPluginPostStatusInfo = () => ( </PluginPostStatusInfo> ); ``` -{% end %} -#### Props +**Parameters** -##### className +- **props** `Object`: Component properties. +- **props.className** `[string]`: An optional class name added to the row. -An optional class name added to the row. +**Returns** -- Type: `String` -- Required: No +`WPElement`: The WPElement to be rendered. -### `PluginPrePublishPanel` +### PluginPrePublishPanel -Renders provided content to the pre-publish side panel in the publish flow (side panel that opens when a user first pushes "Publish" from the main editor). +[src/index.js#L102-L102](src/index.js#L102-L102) -_Example:_ +Renders provided content to the pre-publish side panel in the publish flow +(side panel that opens when a user first pushes "Publish" from the main editor). -{% codetabs %} +**Usage** -{% ES5 %} ```js +// Using ES5 syntax var __ = wp.i18n.__; var PluginPrePublishPanel = wp.editPost.PluginPrePublishPanel; @@ -417,8 +295,8 @@ function MyPluginPrePublishPanel() { } ``` -{% ESNext %} ```jsx +// Using ESNext syntax const { __ } = wp.i18n; const { PluginPrePublishPanel } = wp.editPost; @@ -432,99 +310,160 @@ const MyPluginPrePublishPanel = () => ( </PluginPrePublishPanel> ); ``` -{% end %} -#### Props +**Parameters** -##### className +- **props** `Object`: Component props. +- **props.className** `[string]`: An optional class name added to the panel. +- **props.title** `[string]`: Title displayed at the top of the panel. +- **props.initialOpen** `[boolean]`: Whether to have the panel initially opened. When no title is provided it is always opened. -An optional class name added to the panel. +**Returns** -- Type: `String` -- Required: No +`WPElement`: The WPElement to be rendered. -##### title +### PluginSidebar -Title displayed at the top of the panel. +[src/index.js#L103-L103](src/index.js#L103-L103) -- Type: `String` -- Required: No +Renders a sidebar when activated. The contents within the `PluginSidebar` will appear as content within the sidebar. +If you wish to display the sidebar, you can with use the `PluginSidebarMoreMenuItem` component or the `wp.data.dispatch` API: -##### initialOpen +```js +wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/sidebar-name' ); +``` -Whether to have the panel initially opened. When no title is provided it is always opened. +**Related** -- Type: `Boolean` -- Required: No -- Default: `false` +- PluginSidebarMoreMenuItem +**Usage** -### `PluginPostPublishPanel` +```js +// Using ES5 syntax +var __ = wp.i18n.__; +var el = wp.element.createElement; +var PanelBody = wp.components.PanelBody; +var PluginSidebar = wp.editPost.PluginSidebar; -Renders provided content to the post-publish panel in the publish flow (side panel that opens after a user publishes the post). +function MyPluginSidebar() { + return el( + PluginSidebar, + { + name: 'my-sidebar', + title: 'My sidebar title', + icon: 'smiley', + }, + el( + PanelBody, + {}, + __( 'My sidebar content' ) + ) + ); +} +``` -_Example:_ +```jsx +// Using ESNext syntax +const { __ } = wp.i18n; +const { PanelBody } = wp.components; +const { PluginSidebar } = wp.editPost; + +const MyPluginSidebar = () => ( + <PluginSidebar + name="my-sidebar" + title="My sidebar title" + icon="smiley" + > + <PanelBody> + { __( 'My sidebar content' ) } + </PanelBody> + </PluginSidebar> +); +``` + +**Parameters** + +- **props** `Object`: Element props. +- **props.name** `string`: A string identifying the sidebar. Must be unique for every sidebar registered within the scope of your plugin. +- **props.className** `[string]`: An optional class name added to the sidebar body. +- **props.title** `string`: Title displayed at the top of the sidebar. +- **props.isPinnable** `[boolean]`: Whether to allow to pin sidebar to toolbar. +- **props.icon** `[(string|Element)]`: The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered when the sidebar is pinned to toolbar. + +**Returns** -{% codetabs %} +`WPElement`: Plugin sidebar component. + +### PluginSidebarMoreMenuItem + +[src/index.js#L104-L104](src/index.js#L104-L104) + +Renders a menu item in `Plugins` group in `More Menu` drop down, +and can be used to activate the corresponding `PluginSidebar` component. +The text within the component appears as the menu item label. + +**Usage** -{% ES5 %} ```js +// Using ES5 syntax var __ = wp.i18n.__; -var PluginPostPublishPanel = wp.editPost.PluginPostPublishPanel; +var PluginSidebarMoreMenuItem = wp.editPost.PluginSidebarMoreMenuItem; -function MyPluginPostPublishPanel() { +function MySidebarMoreMenuItem() { return wp.element.createElement( - PluginPostPublishPanel, + PluginSidebarMoreMenuItem, { - className: 'my-plugin-post-publish-panel', - title: __( 'My panel title' ), - initialOpen: true, + target: 'my-sidebar', + icon: 'smiley', }, - __( 'My panel content' ) - ); + __( 'My sidebar title' ) + ) } ``` -{% ESNext %} ```jsx +// Using ESNext syntax const { __ } = wp.i18n; -const { PluginPostPublishPanel } = wp.editPost; +const { PluginSidebarMoreMenuItem } = wp.editPost; -const MyPluginPostPublishPanel = () => ( - <PluginPostPublishPanel - className="my-plugin-post-publish-panel" - title={ __( 'My panel title' ) } - initialOpen={ true } +const MySidebarMoreMenuItem = () => ( + <PluginSidebarMoreMenuItem + target="my-sidebar" + icon="smiley" > - { __( 'My panel content' ) } - </PluginPostPublishPanel> + { __( 'My sidebar title' ) } + </PluginSidebarMoreMenuItem> ); ``` -{% end %} -#### Props +**Parameters** -##### className +- **props** `Object`: Component props. +- **props.target** `string`: A string identifying the target sidebar you wish to be activated by this menu item. Must be the same as the `name` prop you have given to that sidebar. +- **props.icon** `[(string|Element)]`: The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered to the left of the menu item label. -An optional class name added to the panel. +**Returns** -- Type: `String` -- Required: No +`WPElement`: The element to be rendered. -##### title +### reinitializeEditor -Title displayed at the top of the panel. +[src/index.js#L35-L50](src/index.js#L35-L50) -- Type: `String` -- Required: No +Reinitializes the editor after the user chooses to reboot the editor after +an unhandled error occurs, replacing previously mounted editor element using +an initial state from prior to the crash. -##### initialOpen +**Parameters** -Whether to have the panel initially opened. When no title is provided it is always opened. +- **postType** `Object`: Post type of the post to edit. +- **postId** `Object`: ID of the post to edit. +- **target** `Element`: DOM node in which editor is rendered. +- **settings** `?Object`: Editor settings object. +- **initialEdits** `Object`: Programmatic edits to apply initially, to be considered as non-user-initiated (bypass for unsaved changes prompt). -- Type: `Boolean` -- Required: No -- Default: `false` +<!-- END TOKEN(Autogenerated API docs) --> <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/edit-post/src/components/block-settings-menu/plugin-block-settings-menu-item.js b/packages/edit-post/src/components/block-settings-menu/plugin-block-settings-menu-item.js index f887931acc8f7..810d558346cc7 100644 --- a/packages/edit-post/src/components/block-settings-menu/plugin-block-settings-menu-item.js +++ b/packages/edit-post/src/components/block-settings-menu/plugin-block-settings-menu-item.js @@ -30,6 +30,59 @@ const isEverySelectedBlockAllowed = ( selected, allowed ) => difference( selecte const shouldRenderItem = ( selectedBlockNames, allowedBlockNames ) => ! Array.isArray( allowedBlockNames ) || isEverySelectedBlockAllowed( selectedBlockNames, allowedBlockNames ); +/** + * Renders a new item in the block settings menu. + * + * @param {Object} props Component props. + * @param {Array} [props.allowedBlockNames] An array containing a list of block names for which the item should be shown. If not present, it'll be rendered for any block. If multiple blocks are selected, it'll be shown if and only if all of them are in the whitelist. + * @param {string|Element} [props.icon] The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element. + * @param {string} props.label The menu item text. + * @param {Function} props.onClick Callback function to be executed when the user click the menu item. + * + * @example <caption>ES5</caption> + * ```js + * // Using ES5 syntax + * var __ = wp.i18n.__; + * var PluginBlockSettingsMenuItem = wp.editPost.PluginBlockSettingsMenuItem; + * + * function doOnClick(){ + * // To be called when the user clicks the menu item. + * } + * + * function MyPluginBlockSettingsMenuItem() { + * return wp.element.createElement( + * PluginBlockSettingsMenuItem, + * { + * allowedBlockNames: [ 'core/paragraph' ], + * icon: 'dashicon-name', + * label: __( 'Menu item text' ), + * onClick: doOnClick, + * } + * ); + * } + * ``` + * + * @example <caption>ESNext</caption> + * ```jsx + * // Using ESNext syntax + * import { __ } from wp.i18n; + * import { PluginBlockSettingsMenuItem } from wp.editPost; + * + * const doOnClick = ( ) => { + * // To be called when the user clicks the menu item. + * }; + * + * const MyPluginBlockSettingsMenuItem = () => ( + * <PluginBlockSettingsMenuItem + * allowedBlockNames=[ 'core/paragraph' ] + * icon='dashicon-name' + * label=__( 'Menu item text' ) + * onClick={ doOnClick } /> + * ); + * ``` + * + * @return {WPElement} The WPElement to be rendered. + */ const PluginBlockSettingsMenuItem = ( { allowedBlocks, icon, label, onClick, small, role } ) => ( <PluginBlockSettingsMenuGroup> { ( { selectedBlocks, onClose } ) => { diff --git a/packages/edit-post/src/components/header/plugin-more-menu-item/index.js b/packages/edit-post/src/components/header/plugin-more-menu-item/index.js index 2f2e279c7616a..e29ec22e90a20 100644 --- a/packages/edit-post/src/components/header/plugin-more-menu-item/index.js +++ b/packages/edit-post/src/components/header/plugin-more-menu-item/index.js @@ -26,6 +26,60 @@ const PluginMoreMenuItem = ( { onClick = noop, ...props } ) => ( </PluginsMoreMenuGroup> ); +/** + * Renders a menu item in `Plugins` group in `More Menu` drop down, and can be used to as a button or link depending on the props provided. + * The text within the component appears as the menu item label. + * + * @param {Object} props Component properties. + * @param {string} [props.href] When `href` is provided then the menu item is represented as an anchor rather than button. It corresponds to the `href` attribute of the anchor. + * @param {string|Element} [props.icon=inherits from the plugin] The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered to the left of the menu item label. + * @param {Function} [props.onClick=noop] The callback function to be executed when the user clicks the menu item. + * @param {...*} [props.other] Any additional props are passed through to the underlying [MenuItem](/packages/components/src/menu-item/README.md) component. + * + * @example <caption>ES5</caption> + * ```js + * // Using ES5 syntax + * var __ = wp.i18n.__; + * var PluginMoreMenuItem = wp.editPost.PluginMoreMenuItem; + * + * function onButtonClick() { + * alert( 'Button clicked.' ); + * } + * + * function MyButtonMoreMenuItem() { + * return wp.element.createElement( + * PluginMoreMenuItem, + * { + * icon: 'smiley', + * onClick: onButtonClick + * }, + * __( 'My button title' ) + * ) + * } + * ``` + * + * @example <caption>ESNext</caption> + * ```jsx + * // Using ESNext syntax + * const { __ } = wp.i18n; + * const { PluginMoreMenuItem } = wp.editPost; + * + * function onButtonClick() { + * alert( 'Button clicked.' ); + * } + * + * const MyButtonMoreMenuItem = () => ( + * <PluginMoreMenuItem + * icon="smiley" + * onClick={ onButtonClick } + * > + * { __( 'My button title' ) } + * </PluginMoreMenuItem> + * ); + * ``` + * + * @return {WPElement} The element to be rendered. + */ export default compose( withPluginContext( ( context, ownProps ) => { return { diff --git a/packages/edit-post/src/components/header/plugin-sidebar-more-menu-item/index.js b/packages/edit-post/src/components/header/plugin-sidebar-more-menu-item/index.js index 0579ccec8a3b0..e1849f2e7890e 100644 --- a/packages/edit-post/src/components/header/plugin-sidebar-more-menu-item/index.js +++ b/packages/edit-post/src/components/header/plugin-sidebar-more-menu-item/index.js @@ -21,6 +21,51 @@ const PluginSidebarMoreMenuItem = ( { children, icon, isSelected, onClick } ) => </PluginMoreMenuItem> ); +/** + * Renders a menu item in `Plugins` group in `More Menu` drop down, + * and can be used to activate the corresponding `PluginSidebar` component. + * The text within the component appears as the menu item label. + * + * @param {Object} props Component props. + * @param {string} props.target A string identifying the target sidebar you wish to be activated by this menu item. Must be the same as the `name` prop you have given to that sidebar. + * @param {string|Element} [props.icon=inherits from the plugin] The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered to the left of the menu item label. + * + * @example <caption>ES5</caption> + * ```js + * // Using ES5 syntax + * var __ = wp.i18n.__; + * var PluginSidebarMoreMenuItem = wp.editPost.PluginSidebarMoreMenuItem; + * + * function MySidebarMoreMenuItem() { + * return wp.element.createElement( + * PluginSidebarMoreMenuItem, + * { + * target: 'my-sidebar', + * icon: 'smiley', + * }, + * __( 'My sidebar title' ) + * ) + * } + * ``` + * + * @example <caption>ESNext</caption> + * ```jsx + * // Using ESNext syntax + * const { __ } = wp.i18n; + * const { PluginSidebarMoreMenuItem } = wp.editPost; + * + * const MySidebarMoreMenuItem = () => ( + * <PluginSidebarMoreMenuItem + * target="my-sidebar" + * icon="smiley" + * > + * { __( 'My sidebar title' ) } + * </PluginSidebarMoreMenuItem> + * ); + * ``` + * + * @return {WPElement} The element to be rendered. + */ export default compose( withPluginContext( ( context, ownProps ) => { return { diff --git a/packages/edit-post/src/components/sidebar/plugin-post-publish-panel/index.js b/packages/edit-post/src/components/sidebar/plugin-post-publish-panel/index.js index a23dd1a9a2ff6..b8a49115e5d54 100644 --- a/packages/edit-post/src/components/sidebar/plugin-post-publish-panel/index.js +++ b/packages/edit-post/src/components/sidebar/plugin-post-publish-panel/index.js @@ -5,6 +5,53 @@ import { createSlotFill, PanelBody } from '@wordpress/components'; const { Fill, Slot } = createSlotFill( 'PluginPostPublishPanel' ); +/** + * Renders provided content to the post-publish panel in the publish flow + * (side panel that opens after a user publishes the post). + * + * @param {Object} props Component properties. + * @param {string} [props.className] An optional class name added to the panel. + * @param {string} [props.title] Title displayed at the top of the panel. + * @param {boolean} [props.initialOpen=false] Whether to have the panel initially opened. When no title is provided it is always opened. + * + * @example <caption>ES5</caption> + * ```js + * // Using ES5 syntax + * var __ = wp.i18n.__; + * var PluginPostPublishPanel = wp.editPost.PluginPostPublishPanel; + * + * function MyPluginPostPublishPanel() { + * return wp.element.createElement( + * PluginPostPublishPanel, + * { + * className: 'my-plugin-post-publish-panel', + * title: __( 'My panel title' ), + * initialOpen: true, + * }, + * __( 'My panel content' ) + * ); + * } + * ``` + * + * @example <caption>ESNext</caption> + * ```jsx + * // Using ESNext syntax + * const { __ } = wp.i18n; + * const { PluginPostPublishPanel } = wp.editPost; + * + * const MyPluginPostPublishPanel = () => ( + * <PluginPostPublishPanel + * className="my-plugin-post-publish-panel" + * title={ __( 'My panel title' ) } + * initialOpen={ true } + * > + * { __( 'My panel content' ) } + * </PluginPostPublishPanel> + * ); + * ``` + * + * @return {WPElement} The WPElement to be rendered. + */ const PluginPostPublishPanel = ( { children, className, title, initialOpen = false } ) => ( <Fill> <PanelBody diff --git a/packages/edit-post/src/components/sidebar/plugin-post-status-info/index.js b/packages/edit-post/src/components/sidebar/plugin-post-status-info/index.js index 43fedee759309..e099fa232e84f 100644 --- a/packages/edit-post/src/components/sidebar/plugin-post-status-info/index.js +++ b/packages/edit-post/src/components/sidebar/plugin-post-status-info/index.js @@ -9,6 +9,48 @@ import { createSlotFill, PanelRow } from '@wordpress/components'; export const { Fill, Slot } = createSlotFill( 'PluginPostStatusInfo' ); +/** + * Renders a row in the Status & Visibility panel of the Document sidebar. + * It should be noted that this is named and implemented around the function it serves + * and not its location, which may change in future iterations. + * + * @param {Object} props Component properties. + * @param {string} [props.className] An optional class name added to the row. + * + * @example <caption>ES5</caption> + * ```js + * // Using ES5 syntax + * var __ = wp.i18n.__; + * var PluginPostStatusInfo = wp.editPost.PluginPostStatusInfo; + * + * function MyPluginPostStatusInfo() { + * return wp.element.createElement( + * PluginPostStatusInfo, + * { + * className: 'my-plugin-post-status-info', + * }, + * __( 'My post status info' ) + * ) + * } + * ``` + * + * @example <caption>ESNext</caption> + * ```jsx + * // Using ESNext syntax + * const { __ } = wp.i18n; + * const { PluginPostStatusInfo } = wp.editPost; + * + * const MyPluginPostStatusInfo = () => ( + * <PluginPostStatusInfo + * className="my-plugin-post-status-info" + * > + * { __( 'My post status info' ) } + * </PluginPostStatusInfo> + * ); + * ``` + * + * @return {WPElement} The WPElement to be rendered. + */ const PluginPostStatusInfo = ( { children, className } ) => ( <Fill> <PanelRow className={ className }> diff --git a/packages/edit-post/src/components/sidebar/plugin-pre-publish-panel/index.js b/packages/edit-post/src/components/sidebar/plugin-pre-publish-panel/index.js index 6803b039e2628..7ffe80fb6e6a4 100644 --- a/packages/edit-post/src/components/sidebar/plugin-pre-publish-panel/index.js +++ b/packages/edit-post/src/components/sidebar/plugin-pre-publish-panel/index.js @@ -5,6 +5,53 @@ import { createSlotFill, PanelBody } from '@wordpress/components'; const { Fill, Slot } = createSlotFill( 'PluginPrePublishPanel' ); +/** + * Renders provided content to the pre-publish side panel in the publish flow + * (side panel that opens when a user first pushes "Publish" from the main editor). + * + * @param {Object} props Component props. + * @param {string} [props.className] An optional class name added to the panel. + * @param {string} [props.title] Title displayed at the top of the panel. + * @param {boolean} [props.initialOpen=false] Whether to have the panel initially opened. When no title is provided it is always opened. + * + * @example <caption>ES5</caption> + * ```js + * // Using ES5 syntax + * var __ = wp.i18n.__; + * var PluginPrePublishPanel = wp.editPost.PluginPrePublishPanel; + * + * function MyPluginPrePublishPanel() { + * return wp.element.createElement( + * PluginPrePublishPanel, + * { + * className: 'my-plugin-pre-publish-panel', + * title: __( 'My panel title' ), + * initialOpen: true, + * }, + * __( 'My panel content' ) + * ); + * } + * ``` + * + * @example <caption>ESNext</caption> + * ```jsx + * // Using ESNext syntax + * const { __ } = wp.i18n; + * const { PluginPrePublishPanel } = wp.editPost; + * + * const MyPluginPrePublishPanel = () => ( + * <PluginPrePublishPanel + * className="my-plugin-pre-publish-panel" + * title={ __( 'My panel title' ) } + * initialOpen={ true } + * > + * { __( 'My panel content' ) } + * </PluginPrePublishPanel> + * ); + * ``` + * + * @return {WPElement} The WPElement to be rendered. + */ const PluginPrePublishPanel = ( { children, className, title, initialOpen = false } ) => ( <Fill> <PanelBody diff --git a/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js b/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js index 72c0cc5da1c6b..f02deeab87bca 100644 --- a/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js +++ b/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js @@ -15,13 +15,6 @@ import PinnedPlugins from '../../header/pinned-plugins'; import Sidebar from '../'; import SidebarHeader from '../sidebar-header'; -/** - * Renders the plugin sidebar component. - * - * @param {Object} props Element props. - * - * @return {WPElement} Plugin sidebar component. - */ function PluginSidebar( props ) { const { children, @@ -75,6 +68,70 @@ function PluginSidebar( props ) { ); } +/** + * Renders a sidebar when activated. The contents within the `PluginSidebar` will appear as content within the sidebar. + * If you wish to display the sidebar, you can with use the `PluginSidebarMoreMenuItem` component or the `wp.data.dispatch` API: + * + * ```js + * wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/sidebar-name' ); + * ``` + * + * @see PluginSidebarMoreMenuItem + * + * @param {Object} props Element props. + * @param {string} props.name A string identifying the sidebar. Must be unique for every sidebar registered within the scope of your plugin. + * @param {string} [props.className] An optional class name added to the sidebar body. + * @param {string} props.title Title displayed at the top of the sidebar. + * @param {boolean} [props.isPinnable=true] Whether to allow to pin sidebar to toolbar. + * @param {string|Element} [props.icon=inherits from the plugin] The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered when the sidebar is pinned to toolbar. + * + * @example <caption>ES5</caption> + * ```js + * // Using ES5 syntax + * var __ = wp.i18n.__; + * var el = wp.element.createElement; + * var PanelBody = wp.components.PanelBody; + * var PluginSidebar = wp.editPost.PluginSidebar; + * + * function MyPluginSidebar() { + * return el( + * PluginSidebar, + * { + * name: 'my-sidebar', + * title: 'My sidebar title', + * icon: 'smiley', + * }, + * el( + * PanelBody, + * {}, + * __( 'My sidebar content' ) + * ) + * ); + * } + * ``` + * + * @example <caption>ESNext</caption> + * ```jsx + * // Using ESNext syntax + * const { __ } = wp.i18n; + * const { PanelBody } = wp.components; + * const { PluginSidebar } = wp.editPost; + * + * const MyPluginSidebar = () => ( + * <PluginSidebar + * name="my-sidebar" + * title="My sidebar title" + * icon="smiley" + * > + * <PanelBody> + * { __( 'My sidebar content' ) } + * </PanelBody> + * </PluginSidebar> + * ); + * ``` + * + * @return {WPElement} Plugin sidebar component. + */ export default compose( withPluginContext( ( context, ownProps ) => { return { From 93e5c1d43a57031107380bb1e20205ee68056d31 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Mon, 11 Mar 2019 07:00:28 -0400 Subject: [PATCH 630/691] Plugin: Remove postinstall step (#14353) * Plugin: Remove postinstall step * Framework: Run build for JS unit tests Assumed previously relied on postinstall --- .travis.yml | 2 ++ package.json | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8a464d766d973..7b87e251a2525 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,8 +34,10 @@ jobs: install: - npm ci script: + - npm run build - npm run lint - npm run check-local-changes + - npm run check-licenses - npm run test-unit -- --ci --maxWorkers=2 --cacheDirectory="$HOME/.jest-cache" - name: PHP unit tests (Docker) diff --git a/package.json b/package.json index 0700d64f3141b..1f0e798ff37f1 100644 --- a/package.json +++ b/package.json @@ -175,7 +175,6 @@ "lint-css": "wp-scripts lint-style '**/*.scss'", "lint-css:fix": "npm run lint-css -- --fix", "package-plugin": "./bin/build-plugin-zip.sh", - "postinstall": "npm run check-licenses && npm run build:packages", "pot-to-php": "./bin/pot-to-php.js", "precommit": "lint-staged", "publish:check": "npm run build:packages && lerna updated", From 6e0137f55fe7f88a93e02b1f207d40095c1928dc Mon Sep 17 00:00:00 2001 From: Brent Swisher <brent@brentswisher.com> Date: Mon, 11 Mar 2019 09:00:29 -0400 Subject: [PATCH 631/691] Add check to the merge function in headings and paragraph blocks (#13981) If trying to merge a blank heading or paragraph, if this check isn't performed, the result is the string 'Null' appearing in the text. --- packages/block-library/src/heading/index.js | 2 +- packages/block-library/src/paragraph/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/heading/index.js b/packages/block-library/src/heading/index.js index f576098578568..5a12b6e7a8cc0 100644 --- a/packages/block-library/src/heading/index.js +++ b/packages/block-library/src/heading/index.js @@ -168,7 +168,7 @@ export const settings = { merge( attributes, attributesToMerge ) { return { - content: attributes.content + attributesToMerge.content, + content: ( attributes.content || '' ) + ( attributesToMerge.content || '' ), }; }, diff --git a/packages/block-library/src/paragraph/index.js b/packages/block-library/src/paragraph/index.js index 460bb680d3dcf..6a0c6172c3695 100644 --- a/packages/block-library/src/paragraph/index.js +++ b/packages/block-library/src/paragraph/index.js @@ -212,7 +212,7 @@ export const settings = { merge( attributes, attributesToMerge ) { return { - content: attributes.content + attributesToMerge.content, + content: ( attributes.content || '' ) + ( attributesToMerge.content || '' ), }; }, From 9c3dbde9f7585ed776fa52a3720ad581d556f865 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Mon, 11 Mar 2019 06:31:04 -0700 Subject: [PATCH 632/691] Update internationalization process with complete updated example. (#13909) * Update internationalization process with complete updated example. * Minor edits * Update i18n package documentation Updates with links to expanded section in Gutenberg Handbook Fixes instructions to use wp-cli which is the recommended tool for creating pot files, and po2json to convert the format. * Apply suggestions from code review Co-Authored-By: mkaz <marcus@mkaz.com> * Remove duplicate documentation, just link to Handbook * Batch of changes from reviews props @swisspiddy * :shakes-fist-at-whitespace: * Updates to include full .pot and .po files per @nosolosw review * Add JSON translation example --- .../developers/internationalization.md | 245 +++++++++++++++--- packages/i18n/README.md | 16 +- 2 files changed, 217 insertions(+), 44 deletions(-) diff --git a/docs/designers-developers/developers/internationalization.md b/docs/designers-developers/developers/internationalization.md index e8e02de2192e9..190eb3023a7ee 100644 --- a/docs/designers-developers/developers/internationalization.md +++ b/docs/designers-developers/developers/internationalization.md @@ -1,47 +1,232 @@ # Internationalization -This document aims to give an overview of the possibilities for both internationalization and localization when developing with WordPress. +## What is Internationalization? -## PHP +Internationalization is the process to provide multiple language support to software, in this case WordPress. Internationalization is often abbreviated as **i18n**, where 18 stands for the number of letters between the first _i_ and the last _n_. -For years, WordPress has been providing the necessary tools and functions to internationalize plugins and themes. This includes helper functions like `__()` and similar. +Providing i18n support to your plugin and theme allows it to reach the largest possible audience, even without requiring you to provide the additional language translations. When you upload your software to WordPress.org, all JS and PHP files will automatically be parsed. Any detected translation strings are added to [translate.wordpress.org](https://translate.wordpress.org/) to allow the community to translate, ensuring WordPress plugins and themes are available in as many languages as possible. -### Common Methods +For PHP, WordPress has a long established process, see [How to Internationalize Your Plugin](https://developer.wordpress.org/plugins/internationalization/how-to-internationalize-your-plugin/). The release of WordPress 5.0 brings a similar process for translation to JavaScript code. -- `__( 'Hello World', 'my-text-domain' )`: Translate a certain string. -- `_x( 'Block', 'noun', 'my-text-domain' )`: Translate a certain string with some additional context. -- `_e( 'Hello World', 'my-text-domain' )`: Translate and print a certain string. -- `esc_html__( 'Hello World', 'my-text-domain' )`: Translate a certain string and escape it for safe use in HTML output. -- `esc_html_e( 'Hello World', 'my-text-domain' )`: Translate a certain string, escape it for safe use in HTML output, and print it. -- `_n( '%s Comment', '%s Comments', $number, 'my-text-domain' )`: Translate and retrieve the singular or plural form based on the supplied number. - Usually used in combination with `sprintf()` and `number_format_i18n()`. +## How to use i18n in JavaScript -## JavaScript +WordPress 5.0 introduced the wp-i18n JavaScript package that provides the functions needed to add translatable strings as you would in PHP. -Historically, `wp_localize_script()` has been used to put server-side PHP data into a properly-escaped native JavaScript object. +First, add **wp-i18n** as a dependency when registering your script: -The new editor introduces a new approach to translating strings for the editor through a new package called `@wordpress/i18n`. +```php +<?php +/** + * Plugin Name: Myguten Plugin + * Text Domain: myguten + */ +function myguten_block_init() { + wp_register_script( + 'myguten-script', + plugins_url( 'block.js', __FILE__ ), + array( 'wp-blocks', 'wp-element', 'wp-i18n' ) + ); -The new script package is registered with WordPress as `wp-i18n` and should be declared as a dependency during `wp_register_script()` and imported as a global off the Window object as `wp.i18n`. + register_block_type( 'myguten/simple', array( + 'editor_script' => 'myguten-script', + ) ); +} +add_action( 'init', 'myguten_block_init' ); +``` -Depending on your developer workflow, you might want to use WP-CLI's `wp i18n make-pot` command or a build tool for Babel called `@wordpress/babel-plugin-makepot` to create the necessary translation file. The latter approach integrates with Babel to extract the I18N methods. +In your code, you can include the i18n functions. The most common function is **__** (a double underscore) which provides translation of a simple string. Here is a basic static block example, this is in a file called `block.js`: -### Common Methods in wp.i18n (May Look Similar) +```js +const { __ } = wp.i18n; +const el = wp.element.createElement; +const { registerBlockType } = wp.blocks; -- `setLocaleData( data: Object, domain: string )`: Creates a new I18N instance providing translation data for a domain. -- `__( 'Hello World', 'my-text-domain' )`: Translate a certain string. -- `_n( '%s Comment', '%s Comments', numberOfComments, 'my-text-domain' )`: Translate and retrieve the singular or plural form based on the supplied number. -- `_x( 'Default', 'block style', 'my-text-domain' )`: Translate a certain string with some additional context. -- `sprintf()`: JavaScript port of the PHP function with the same name. +registerBlockType( 'myguten/simple', { + title: __('Simple Block', 'myguten'), + category: 'widgets', -### Loading Translations + edit: () => { + return el( + 'p', + { style: { color:'red'}, }, + __('Hello World', 'myguten') + ); + }, -WordPress 5.0 introduces a new function called `wp_set_script_translations( 'my-script-handle', 'my-text-domain' )` to load translation files for a given script handle. + save: () => { + return el( + 'p', + { style: { color:'red'}, }, + __('Hello World', 'myguten') + ); + } +}); +``` + +In the above example, the function will use the first argument for the string to be translated. The second argument is the text domain which must match the text domain slug specified by your plugin. + +Common functions available, these mirror their PHP counterparts are: + +- `__( 'Hello World', 'my-text-domain' )` - Translate a certain string. +- `_n( '%s Comment', '%s Comments', numberOfComments, 'my-text-domain' )` - Translate and retrieve the singular or plural form based on the supplied number. +- `_x( 'Default', 'block style', 'my-text-domain' )` - Translate a certain string with some additional context. + +**Note:** Every string displayed to the user should be wrapped in an i18n function. + +After all strings in your code is wrapped, the final step is to tell WordPress your JavaScript contains translations, using the [wp_set_script_translations()](https://developer.wordpress.org/reference/functions/wp_set_script_translations/) function. + +```php +<?php + function myguten_set_script_translations() { + wp_set_script_translations( 'myguten-script', 'myguten' ); + } + add_action( 'init', 'myguten_set_script_translations' ); +``` + +This is all you need to make your plugin JavaScript code translatable. + +When you set script translations for a handle WordPress will automatically figure out if a translations file exists on translate.wordpress.org, and if so ensure that it's loaded into `wp.i18n` before your script runs. With translate.wordpress.org, plugin authors also do not need to worry about setting up their own infrastructure for translations and can rely on a global community with dozens of active locales. Read more about [WordPress Translations](https://make.wordpress.org/meta/handbook/documentation/translations/). + +## Provide Your Own Translations + +You can create and ship your own translations with your plugin, if you have sufficient knowledge of the language(s) you can ensure the translations are available. + +### Create Translation File + +The translation files must be in the JED 1.x JSON format. + +To create a JED translation file, first you need to extract the strings from the text. Typically, the language files all live in a directory called `languages` in your plugin. Using [WP-CLI](https://wp-cli.org/), you create a `.pot` file using the following command from within your plugin directory: + +``` +mkdir languages +wp i18n make-pot ./ languages/myguten.pot +``` + +This will create the file `myguten.pot` which contains all the translatable strings from your project. + +``` +msgid "" +msgstr "" +"Project-Id-Version: Scratch Plugin\n" +"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/scratch\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"POT-Creation-Date: 2019-03-08T11:26:56-08:00\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"X-Generator: WP-CLI 2.1.0\n" +"X-Domain: myguten\n" + +#. Plugin Name of the plugin +msgid "Scratch Plugin" +msgstr "" + +#: block.js:6 +msgid "Simple Block" +msgstr "" + +#: block.js:13 +#: block.js:21 +msgid "Hello World" +msgstr "" +``` + +Here, `msgid` is the string to be translated, and `msgstr` is the actual translation. In the POT file, `msgstr` will always be empty. + +This POT file can then be used as the template for new translations. You should **copy the file** using the language code you are going to translate, this example will use the Esperanto (eo) language: + +``` +cp myguten.pot myguten-eo.po +``` + +For this simple example, you can simply edit the `.po` file in your editor and add the translation to all the `msgstr` sets. For a larger, more complex set of translation, the [Glotpress](https://glotpress.blog/) and [poedit](https://poedit.net/) tools exist to help. + +You need also to add the `Language: eo` parameter. Here is full `myguten-eo.po` translated file + +``` +# Copyright (C) 2019 +# This file is distributed under the same license as the Scratch Plugin plugin. +msgid "" +msgstr "" +"Project-Id-Version: Scratch Plugin\n" +"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/scratch\n" +"Last-Translator: Marcus Kazmierczak <marcus@mkaz.com>\n" +"Language-Team: Esperanto <marcus@mkaz.com>\n" +"Language: eo\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"POT-Creation-Date: 2019-02-18T07:20:46-08:00\n" +"PO-Revision-Date: 2019-02-18 08:16-0800\n" +"X-Generator: Poedit 2.2.1\n" +"X-Domain: myguten\n" -You can learn more about it in [the JavaScript I18N dev note](https://make.wordpress.org/core/2018/11/09/new-javascript-i18n-support-in-wordpress/). +#. Plugin Name of the plugin +msgid "Scratch Plugin" +msgstr "Scratch kromprogrameto" + +#: block.js:6 +msgid "Simple Block" +msgstr "Simpla bloko" + +#: block.js:13 block.js:21 +msgid "Hello World" +msgstr "Saltuon mundo" +``` + +The last step to create the translation file is to convert the `myguten-eo.po` to the JSON format needed. For this, you can use the [po2json utility](https://github.com/mikeedwards/po2json) which you install using npm. It might be easiest to install globally using: `npm install -g po2json`. Once installed, use the following command to convert to JED format: + +``` +po2json myguten-eo.po myguten-eo.json -f jed +``` + +This will generate the JSON file `myguten-eo.json` which looks like: + +```json +{ + "domain": "messages", + "locale_data": { + "messages": { + "": { + "domain": "messages", + "lang": "eo" + }, + "Scratch Plugin": [ + "Scratch kromprogrameto" + ], + "Simple Block": [ + "Simpla bloko" + ], + "Hello World": [ + "Saltuon mundo" + ] + } + } +} +``` + + +### Load Translation File + +The final part is to tell WordPress where it can look to find the translation file. The `wp_set_script_translations` function accepts an optional third argument that is the path it will first check for translations. For example: + +```php +<?php + function myguten_set_script_translations() { + wp_set_script_translations( 'myguten-script', 'myguten', plugin_dir_path( __FILE__ ) . 'languages' ); + } + add_action( 'init', 'myguten_set_script_translations' ); +``` + +WordPress will check for a file in that path with the format `${domain}-${locale}-${handle}.json` as the source of translations. Alternatively, instead of the registered handle you can use the md5 hash of the relative path of the file, `${domain}-${locale} in the form of ${domain}-${locale}-${md5}.json.` + +This example uses the handle, rename the `myguten-eo.json` file to `myguten-eo-myguten-script.json`. + +### Test Translations + +You will need to set your WordPress installation to Esperanto language. Go to Settings > General and change your site language to Esperanto. + +With the language set, create a new post, add the block, and you will see the translations used. -## More Resources - -- [WP-CLI I18N command to generate translation catalogues](https://github.com/wp-cli/i18n-command) -- [Plugin Developer Handbook](https://developer.wordpress.org/plugins/internationalization/) -- [Theme Developer Handbook](https://developer.wordpress.org/themes/internationalization/) diff --git a/packages/i18n/README.md b/packages/i18n/README.md index 6779e866f2453..373ba5849db73 100644 --- a/packages/i18n/README.md +++ b/packages/i18n/README.md @@ -1,8 +1,6 @@ # Internationalization (i18n) -Internationalization utilities for client-side localization. - -<https://codex.wordpress.org/I18n_for_WordPress_Developers> +Internationalization utilities for client-side localization. See [How to Internationalize Your Plugin](https://developer.wordpress.org/plugins/internationalization/how-to-internationalize-your-plugin/) for server-side documentation. ## Installation @@ -23,17 +21,7 @@ sprintf( _n( '%d hat', '%d hats', 4, 'text-domain' ), 4 ); // 4 hats ``` -Note that you will not need to specify [domain](https://codex.wordpress.org/I18n_for_WordPress_Developers#Text_Domains) for the strings. - -## Build - -You can use the [WordPress i18n babel plugin](/packages/babel-plugin-makepot/README.md) to generate a `.pot` file containing all your localized strings. - -The package also includes a `pot-to-php` script used to generate a php files containing the messages listed in a `.pot` file. This is useful to trick WordPress.org translation strings discovery since at the moment, WordPress.org is not capable of parsing strings directly from JavaScript files. - -```sh -npx pot-to-php languages/myplugin.pot languages/myplugin-translations.php text-domain -``` +For a complete example, see the [Internationalization section of the Gutenberg Handbook](https://wordpress.org/gutenberg/handbook/designers-developers/developers/internationalization/). ## API From 97c07dbdf514c8ad440c891b61b21d50e1f7101e Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Mon, 11 Mar 2019 10:16:22 -0400 Subject: [PATCH 633/691] E2E Test Utils: Add missing babel/runtime dependency (#14374) --- package-lock.json | 1 + packages/e2e-test-utils/package.json | 1 + 2 files changed, 2 insertions(+) diff --git a/package-lock.json b/package-lock.json index 93f1ca6a19331..d01d93ab49028 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2747,6 +2747,7 @@ "version": "file:packages/e2e-test-utils", "dev": true, "requires": { + "@babel/runtime": "^7.3.1", "@wordpress/keycodes": "file:packages/keycodes", "@wordpress/url": "file:packages/url", "lodash": "^4.17.11", diff --git a/packages/e2e-test-utils/package.json b/packages/e2e-test-utils/package.json index 33404a2d3d956..8cfe7c23759c6 100644 --- a/packages/e2e-test-utils/package.json +++ b/packages/e2e-test-utils/package.json @@ -28,6 +28,7 @@ "main": "build/index.js", "module": "build-module/index.js", "dependencies": { + "@babel/runtime": "^7.3.1", "@wordpress/keycodes": "file:../keycodes", "@wordpress/url": "file:../url", "lodash": "^4.17.11", From 3e1af348fc60e66f189dafc37b9a0e1400316058 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Mon, 11 Mar 2019 10:37:36 -0400 Subject: [PATCH 634/691] Build: Only prompt clean if unclean (#14352) --- bin/build-plugin-zip.sh | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/bin/build-plugin-zip.sh b/bin/build-plugin-zip.sh index 1a6344fa2e67a..6c6d9d10512ee 100755 --- a/bin/build-plugin-zip.sh +++ b/bin/build-plugin-zip.sh @@ -47,18 +47,21 @@ fi # Do a dry run of the repository reset. Prompting the user for a list of all # files that will be removed should prevent them from losing important files! status "Resetting the repository to pristine condition. ✨" -git clean -xdf --dry-run -warning "🚨 About to delete everything above! Is this okay? 🚨" -echo -n "[y]es/[N]o: " -read answer -if [ "$answer" != "${answer#[Yy]}" ]; then - # Remove ignored files to reset repository to pristine condition. Previous - # test ensures that changed files abort the plugin build. - status "Cleaning working directory... 🛀" - git clean -xdf -else - error "Fair enough; aborting. Tidy up your repo and try again. 🙂" - exit 1 +to_clean=$(git clean -xdf --dry-run) +if [ ! -z "$to_clean" ]; then + echo $to_clean + warning "🚨 About to delete everything above! Is this okay? 🚨" + echo -n "[y]es/[N]o: " + read answer + if [ "$answer" != "${answer#[Yy]}" ]; then + # Remove ignored files to reset repository to pristine condition. Previous + # test ensures that changed files abort the plugin build. + status "Cleaning working directory... 🛀" + git clean -xdf + else + error "Fair enough; aborting. Tidy up your repo and try again. 🙂" + exit 1 + fi fi # Download all vendor scripts From 52336bab0603793a209b1a8123509575a4b49dd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Wrede?= <soerenwrede@gmail.com> Date: Tue, 12 Mar 2019 00:21:43 +0200 Subject: [PATCH 635/691] fix names (#14382) --- docs/designers-developers/developers/internationalization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/designers-developers/developers/internationalization.md b/docs/designers-developers/developers/internationalization.md index 190eb3023a7ee..2ae1b8f8777f3 100644 --- a/docs/designers-developers/developers/internationalization.md +++ b/docs/designers-developers/developers/internationalization.md @@ -141,7 +141,7 @@ This POT file can then be used as the template for new translations. You should cp myguten.pot myguten-eo.po ``` -For this simple example, you can simply edit the `.po` file in your editor and add the translation to all the `msgstr` sets. For a larger, more complex set of translation, the [Glotpress](https://glotpress.blog/) and [poedit](https://poedit.net/) tools exist to help. +For this simple example, you can simply edit the `.po` file in your editor and add the translation to all the `msgstr` sets. For a larger, more complex set of translation, the [GlotPress](https://glotpress.blog/) and [Poedit](https://poedit.net/) tools exist to help. You need also to add the `Language: eo` parameter. Here is full `myguten-eo.po` translated file From c4ea2518d060d4de3a2ae4cab13d3be50f751317 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Tue, 12 Mar 2019 08:20:26 +0100 Subject: [PATCH 636/691] Add withRegistry HigherOrderComponent (#14370) --- .../src/components/provider/index.js | 26 ++------------- .../src/components/with-registry/index.js | 33 +++++++++++++++++++ packages/data/src/index.js | 1 + 3 files changed, 36 insertions(+), 24 deletions(-) create mode 100644 packages/data/src/components/with-registry/index.js diff --git a/packages/block-editor/src/components/provider/index.js b/packages/block-editor/src/components/provider/index.js index a8073ace46c5d..fd788477a0d6d 100644 --- a/packages/block-editor/src/components/provider/index.js +++ b/packages/block-editor/src/components/provider/index.js @@ -3,30 +3,8 @@ */ import { Component } from '@wordpress/element'; import { DropZoneProvider, SlotFillProvider } from '@wordpress/components'; -import { withDispatch, RegistryConsumer } from '@wordpress/data'; -import { createHigherOrderComponent, compose } from '@wordpress/compose'; - -/** - * Higher-order component which renders the original component with the current - * registry context passed as its `registry` prop. - * - * @param {WPComponent} OriginalComponent Original component. - * - * @return {WPComponent} Enhanced component. - */ -const withRegistry = createHigherOrderComponent( - ( OriginalComponent ) => ( props ) => ( - <RegistryConsumer> - { ( registry ) => ( - <OriginalComponent - { ...props } - registry={ registry } - /> - ) } - </RegistryConsumer> - ), - 'withRegistry' -); +import { withDispatch, withRegistry } from '@wordpress/data'; +import { compose } from '@wordpress/compose'; class BlockEditorProvider extends Component { componentDidMount() { diff --git a/packages/data/src/components/with-registry/index.js b/packages/data/src/components/with-registry/index.js new file mode 100644 index 0000000000000..32d1c8e1a40fd --- /dev/null +++ b/packages/data/src/components/with-registry/index.js @@ -0,0 +1,33 @@ +/** + * WordPress dependencies + */ +import { createHigherOrderComponent } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import { RegistryConsumer } from '../registry-provider'; + +/** + * Higher-order component which renders the original component with the current + * registry context passed as its `registry` prop. + * + * @param {WPComponent} OriginalComponent Original component. + * + * @return {WPComponent} Enhanced component. + */ +const withRegistry = createHigherOrderComponent( + ( OriginalComponent ) => ( props ) => ( + <RegistryConsumer> + { ( registry ) => ( + <OriginalComponent + { ...props } + registry={ registry } + /> + ) } + </RegistryConsumer> + ), + 'withRegistry' +); + +export default withRegistry; diff --git a/packages/data/src/index.js b/packages/data/src/index.js index 06f074bec1541..b87a42b608f2b 100644 --- a/packages/data/src/index.js +++ b/packages/data/src/index.js @@ -11,6 +11,7 @@ import * as plugins from './plugins'; export { default as withSelect } from './components/with-select'; export { default as withDispatch } from './components/with-dispatch'; +export { default as withRegistry } from './components/with-registry'; export { default as RegistryProvider, RegistryConsumer } from './components/registry-provider'; export { default as __experimentalAsyncModeProvider } from './components/async-mode-provider'; export { createRegistry } from './registry'; From 3edaae723b420d4f05561f10f53bcbba77f68440 Mon Sep 17 00:00:00 2001 From: Anton Timmermans <email@atimmer.com> Date: Tue, 12 Mar 2019 08:20:57 +0100 Subject: [PATCH 637/691] Fix block validation error message (#13499) * Rename variable to make code more clear * Fix display of expected and actual block HTML The variable were in the wrong order, making the message confusing. It would show the actual HTML as the expected HTML and visa versa. * Change wording to be more clear Expected & Actual were confusing words to use in this content. This change makes the message actually reflect what the values are. * Regenerate docs --- packages/blocks/README.md | 2 +- packages/blocks/src/api/validation.js | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/blocks/README.md b/packages/blocks/README.md index e2074fa23bacc..960edae9da773 100644 --- a/packages/blocks/README.md +++ b/packages/blocks/README.md @@ -574,7 +574,7 @@ Logs to console in development environments when invalid. - **blockTypeOrName** `(string|Object)`: Block type. - **attributes** `Object`: Parsed block attributes. -- **innerHTML** `string`: Original block content. +- **originalBlockContent** `string`: Original block content. **Returns** diff --git a/packages/blocks/src/api/validation.js b/packages/blocks/src/api/validation.js index 452c8f832a43f..335f5c3b8bd39 100644 --- a/packages/blocks/src/api/validation.js +++ b/packages/blocks/src/api/validation.js @@ -620,30 +620,30 @@ export function isEquivalentHTML( actual, expected ) { * * Logs to console in development environments when invalid. * - * @param {string|Object} blockTypeOrName Block type. - * @param {Object} attributes Parsed block attributes. - * @param {string} innerHTML Original block content. + * @param {string|Object} blockTypeOrName Block type. + * @param {Object} attributes Parsed block attributes. + * @param {string} originalBlockContent Original block content. * * @return {boolean} Whether block is valid. */ -export function isValidBlockContent( blockTypeOrName, attributes, innerHTML ) { +export function isValidBlockContent( blockTypeOrName, attributes, originalBlockContent ) { const blockType = normalizeBlockType( blockTypeOrName ); - let saveContent; + let generatedBlockContent; try { - saveContent = getSaveContent( blockType, attributes ); + generatedBlockContent = getSaveContent( blockType, attributes ); } catch ( error ) { log.error( 'Block validation failed because an error occurred while generating block content:\n\n%s', error.toString() ); return false; } - const isValid = isEquivalentHTML( innerHTML, saveContent ); + const isValid = isEquivalentHTML( originalBlockContent, generatedBlockContent ); if ( ! isValid ) { log.error( - 'Block validation failed for `%s` (%o).\n\nExpected:\n\n%s\n\nActual:\n\n%s', + 'Block validation failed for `%s` (%o).\n\nContent generated by `save` function:\n\n%s\n\nContent retrieved from post body:\n\n%s', blockType.name, blockType, - saveContent, - innerHTML + generatedBlockContent, + originalBlockContent ); } From cdd9c04faf005b17edc3490f2b53c7065b612479 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Tue, 12 Mar 2019 08:39:49 +0000 Subject: [PATCH 638/691] Fix: Global inserter does not validates block insert restrictions (#14020) --- .../src/components/inserter/menu.js | 53 ++++++++++--------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/packages/block-editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js index 1ed720113e766..8033b613c69ac 100644 --- a/packages/block-editor/src/components/inserter/menu.js +++ b/packages/block-editor/src/components/inserter/menu.js @@ -360,21 +360,30 @@ export class InserterMenu extends Component { } export default compose( - withSelect( ( select, { rootClientId } ) => { + withSelect( ( select, { clientId, isAppender, rootClientId } ) => { const { getInserterItems, getBlockName, + getBlockRootClientId, + getBlockSelectionEnd, } = select( 'core/block-editor' ); const { getChildBlockNames, } = select( 'core/blocks' ); - const rootBlockName = getBlockName( rootClientId ); + let destinationRootClientId = rootClientId; + if ( ! destinationRootClientId && ! clientId && ! isAppender ) { + const end = getBlockSelectionEnd(); + if ( end ) { + destinationRootClientId = getBlockRootClientId( end ) || undefined; + } + } + const destinationRootBlockName = getBlockName( destinationRootClientId ); return { - rootChildBlocks: getChildBlockNames( rootBlockName ), - items: getInserterItems( rootClientId ), - rootClientId, + rootChildBlocks: getChildBlockNames( destinationRootBlockName ), + items: getInserterItems( destinationRootClientId ), + destinationRootClientId, }; } ), withDispatch( ( dispatch, ownProps, { select } ) => { @@ -388,50 +397,39 @@ export default compose( __experimentalFetchReusableBlocks: fetchReusableBlocks, } = dispatch( 'core/editor' ); - // To avoid duplication, getInsertionPoint is extracted and used in two event handlers + // To avoid duplication, getInsertionIndex is extracted and used in two event handlers // This breaks the withDispatch not containing any logic rule. // Since it's a function only called when the event handlers are called, // it's fine to extract it. // eslint-disable-next-line no-restricted-syntax - function getInsertionPoint() { + function getInsertionIndex() { const { getBlockIndex, - getBlockRootClientId, getBlockSelectionEnd, getBlockOrder, } = select( 'core/block-editor' ); - const { clientId, rootClientId, isAppender } = ownProps; + const { clientId, destinationRootClientId, isAppender } = ownProps; // If the clientId is defined, we insert at the position of the block. if ( clientId ) { - return { - index: getBlockIndex( clientId, rootClientId ), - rootClientId, - }; + return getBlockIndex( clientId, destinationRootClientId ); } // If there a selected block, we insert after the selected block. const end = getBlockSelectionEnd(); if ( ! isAppender && end ) { - const selectedBlockRootClientId = getBlockRootClientId( end ) || undefined; - return { - index: getBlockIndex( end, selectedBlockRootClientId ) + 1, - rootClientId: selectedBlockRootClientId, - }; + return getBlockIndex( end, destinationRootClientId ) + 1; } // Otherwise, we insert at the end of the current rootClientId - return { - index: getBlockOrder( rootClientId ).length, - rootClientId, - }; + return getBlockOrder( destinationRootClientId ).length; } return { fetchReusableBlocks, showInsertionPoint() { - const { index, rootClientId } = getInsertionPoint(); - showInsertionPoint( rootClientId, index ); + const index = getInsertionIndex(); + showInsertionPoint( ownProps.destinationRootClientId, index ); }, hideInsertionPoint, onSelect( item ) { @@ -449,8 +447,11 @@ export default compose( if ( ! isAppender && selectedBlock && isUnmodifiedDefaultBlock( selectedBlock ) ) { replaceBlocks( selectedBlock.clientId, insertedBlock ); } else { - const { index, rootClientId } = getInsertionPoint(); - insertBlock( insertedBlock, index, rootClientId ); + insertBlock( + insertedBlock, + getInsertionIndex(), + ownProps.destinationRootClientId + ); } ownProps.onSelect(); From 9f3f392c0ed41b770311bbaf2bf67380e86e6907 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Tue, 12 Mar 2019 10:23:36 +0100 Subject: [PATCH 639/691] Teach build and start commands to use Webpack default if none is provided (#13877) * Use a default webpack config if none is provided * Extract webpack utils from build command * Update start command * Add docs * Add Webpack documentation * Add example of how to overwrite the default plugins * Do not export hasWebpackConfig as it is not used anywhere else. * Always pass webpack CLI args to command * Update README * Remove section on extending the default webpack config file * Simplify array passing Co-Authored-By: nosolosw <nosolosw@users.noreply.github.com> * Simplify pushing to array Co-Authored-By: nosolosw <nosolosw@users.noreply.github.com> * Fix externals docs * Use webpack instead of Webpack * Add Changelog entry --- packages/scripts/CHANGELOG.md | 6 +++++ packages/scripts/README.md | 42 ++++++++++++++++++++++++++++--- packages/scripts/scripts/build.js | 32 ++++++----------------- packages/scripts/scripts/start.js | 28 ++++++--------------- packages/scripts/utils/config.js | 18 +++++++++++-- packages/scripts/utils/index.js | 2 ++ 6 files changed, 78 insertions(+), 50 deletions(-) diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index c9373b3025f38..b5455d3d98c8b 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -1,3 +1,9 @@ +## 3.1.0 (unreleased) + +## New features + +- The `build` and `start` commands will use a default webpack config if none is provided. + ## 3.0.0 (2019-03-06) ### Breaking Changes diff --git a/packages/scripts/README.md b/packages/scripts/README.md index cc3f3b2b04a7c..3ed6065b67532 100644 --- a/packages/scripts/README.md +++ b/packages/scripts/README.md @@ -38,7 +38,7 @@ _Example:_ ### `build` -Builds the code to the designated `build` folder in the configuration file. It correctly bundles code in production mode and optimizes the build for the best performance. Your code is ready to be deployed. It uses [Webpack](https://webpack.js.org/) behind the scenes and you still need to provide your own config as described in the [documentation](https://webpack.js.org/concepts/configuration/). +Transforms your code according the configuration provided so it's ready for production and optimized for the best performance. It uses [webpack](https://webpack.js.org/) behind the scenes. It'll lookup for a webpack config in the top-level directory of your package and will use it if it finds one. If none is found, it'll use the default config bundled within `@wordpress/scripts` packages. Learn more in the "webpack config" section. _Example:_ @@ -51,8 +51,8 @@ _Example:_ ``` This is how you execute the script with presented setup: -* `npm run build` - builds the code for production. +* `npm run build` - builds the code for production. ### `check-engines` @@ -69,6 +69,7 @@ _Example:_ ``` This is how you execute the script with presented setup: + * `npm run check-engines` - checks installed version of `node` and `npm`. ### `check-licenses` @@ -107,6 +108,7 @@ _Example:_ ``` This is how you execute the script with presented setup: + * `npm run lint:js` - lints JavaScript files in the entire project's directories. ### `lint-pkg-json` @@ -124,6 +126,7 @@ _Example:_ ``` This is how you execute those scripts using the presented setup: + * `npm run lint:pkg-json` - lints `package.json` file in the project's root folder. ### `lint-style` @@ -141,11 +144,12 @@ _Example:_ ``` This is how you execute the script with presented setup: + * `npm run lint:css` - lints CSS files in the whole project's directory. ### `start` -Builds the code for development to the designated `build` folder in the configuration file. The script will automatically rebuild if you make changes to the code. You will see the build errors in the console. It uses [Webpack](https://webpack.js.org/) behind the scenes and you still need to provide your own config as described in the [documentation](https://webpack.js.org/concepts/configuration/). +Transforms your code according the configuration provided so it's ready for development. The script will automatically rebuild if you make changes to the code, and you will see the build errors in the console. It uses [webpack](https://webpack.js.org/) behind the scenes. It'll lookup for a webpack config in the top-level directory of your package and will use it if it finds one. If none is found, it'll use the default config bundled within `@wordpress/scripts` packages. Learn more in the "webpack config" section. _Example:_ @@ -158,6 +162,7 @@ _Example:_ ``` This is how you execute the script with presented setup: + * `npm start` - starts the build for development. ### `test-e2e` @@ -180,6 +185,7 @@ _Example:_ ``` This is how you execute those scripts using the presented setup: + * `npm run test:e2e` - runs all unit tests. * `npm run test:e2e:help` - prints all available options to configure unit tests runner. @@ -207,8 +213,38 @@ _Example:_ ``` This is how you execute those scripts using the presented setup: + * `npm run test:unit` - runs all unit tests. * `npm run test:unit:help` - prints all available options to configure unit tests runner. * `npm run test:unit:watch` - runs all unit tests in the watch mode. +## webpack config + +The `build` and `start` commands use [webpack](https://webpack.js.org/) behind the scenes. webpack is a tool that helps you transform your code into something else. For example: it can take code written in ESNext and output ES5 compatible code that is minified for production. + +### Default webpack config + +`@wordpress/scripts` bundles the default webpack config used as a base by the WordPress editor. These are the defaults: + +* [Entry](https://webpack.js.org/configuration/entry-context/#entry): `src/index.js` +* [Output](https://webpack.js.org/configuration/output): `build/index.js` +* [Externals](https://webpack.js.org/configuration/externals). These are libraries that are to be found in the global scope: + +Package | Input syntax | Output +--- | --- | --- +React | `import x from React;` | `var x = window.React.x;` +ReactDOM | `import x from ReactDOM;` | `var x = window.ReactDOM.x;` +moment | `import x from moment;` | `var x = window.moment.x;` +jQuery | `import x from jQuery;` | `var x = window.jQuery.x;` +lodash | `import x from lodash;` | `var x = window.lodash.x;` +lodash-es | `import x from lodash-es;` | `var x = window.lodash.x;` +WordPress packages | `import x from '@wordpress/package-name` | `var x = window.wp.packageName.x` + +### Provide your own webpack config + +Should there be any situation where you want to provide your own webpack config, you can do so. The `build` and `start` commands will use your provided file when: + +* the command receives a `--config` argument. Example: `wp-scripts build --config my-own-webpack-config.js`. +* there is a file called `webpack.config.js` or `webpack.config.babel.js` in the top-level directory of your package (at the same level than your `package.json`). + <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/scripts/scripts/build.js b/packages/scripts/scripts/build.js index ea2c7d303f2fa..567a2405f2f1d 100644 --- a/packages/scripts/scripts/build.js +++ b/packages/scripts/scripts/build.js @@ -7,28 +7,12 @@ const { sync: resolveBin } = require( 'resolve-bin' ); /** * Internal dependencies */ -const { - getCliArgs, - hasCliArg, - hasProjectFile, -} = require( '../utils' ); +const { getWebpackArgs } = require( '../utils' ); -const hasWebpackConfig = hasCliArg( '--config' ) || - hasProjectFile( 'webpack.config.js' ) || - hasProjectFile( 'webpack.config.babel.js' ); - -if ( hasWebpackConfig ) { - // Sets environment to production. - process.env.NODE_ENV = 'production'; - - const { status } = spawn( - resolveBin( 'webpack' ), - getCliArgs(), - { stdio: 'inherit' } - ); - process.exit( status ); -} else { - // eslint-disable-next-line no-console - console.log( 'Webpack config file is missing.' ); - process.exit( 1 ); -} +process.env.NODE_ENV = 'production'; +const { status } = spawn( + resolveBin( 'webpack' ), + getWebpackArgs(), + { stdio: 'inherit' } +); +process.exit( status ); diff --git a/packages/scripts/scripts/start.js b/packages/scripts/scripts/start.js index db42c33ba447f..d27fa3a5b0ec4 100644 --- a/packages/scripts/scripts/start.js +++ b/packages/scripts/scripts/start.js @@ -7,25 +7,11 @@ const { sync: resolveBin } = require( 'resolve-bin' ); /** * Internal dependencies */ -const { - getCliArgs, - hasCliArg, - hasProjectFile, -} = require( '../utils' ); +const { getWebpackArgs } = require( '../utils' ); -const hasWebpackConfig = hasCliArg( '--config' ) || - hasProjectFile( 'webpack.config.js' ) || - hasProjectFile( 'webpack.config.babel.js' ); - -if ( hasWebpackConfig ) { - const { status } = spawn( - resolveBin( 'webpack' ), - [ '--watch', ...getCliArgs() ], - { stdio: 'inherit' } - ); - process.exit( status ); -} else { - // eslint-disable-next-line no-console - console.log( 'Webpack config file is missing.' ); - process.exit( 1 ); -} +const { status } = spawn( + resolveBin( 'webpack' ), + getWebpackArgs( [ '--watch' ] ), + { stdio: 'inherit' } +); +process.exit( status ); diff --git a/packages/scripts/utils/config.js b/packages/scripts/utils/config.js index 6be8ff91f0a4d..0c5ed70c293ce 100644 --- a/packages/scripts/utils/config.js +++ b/packages/scripts/utils/config.js @@ -1,8 +1,8 @@ /** * Internal dependencies */ -const { hasCliArg } = require( './cli' ); -const { hasProjectFile } = require( './file' ); +const { hasCliArg, getCliArgs } = require( './cli' ); +const { fromConfigRoot, hasProjectFile } = require( './file' ); const { hasPackageProp } = require( './package' ); const hasBabelConfig = () => @@ -17,7 +17,21 @@ const hasJestConfig = () => hasProjectFile( 'jest.config.json' ) || hasPackageProp( 'jest' ); +const hasWebpackConfig = () => hasCliArg( '--config' ) || + hasProjectFile( 'webpack.config.js' ) || + hasProjectFile( 'webpack.config.babel.js' ); + +const getWebpackArgs = ( additionalArgs = [] ) => { + const webpackArgs = getCliArgs(); + if ( ! hasWebpackConfig() ) { + webpackArgs.push( '--config', fromConfigRoot( 'webpack.config.js' ) ); + } + webpackArgs.push( ...additionalArgs ); + return webpackArgs; +}; + module.exports = { + getWebpackArgs, hasBabelConfig, hasJestConfig, }; diff --git a/packages/scripts/utils/index.js b/packages/scripts/utils/index.js index b1a78d0d600c9..0837f7ab80996 100644 --- a/packages/scripts/utils/index.js +++ b/packages/scripts/utils/index.js @@ -8,6 +8,7 @@ const { spawnScript, } = require( './cli' ); const { + getWebpackArgs, hasBabelConfig, hasJestConfig, } = require( './config' ); @@ -27,6 +28,7 @@ module.exports = { fromConfigRoot, getCliArg, getCliArgs, + getWebpackArgs, hasBabelConfig, hasCliArg, hasJestConfig, From ecd6ae9170134bae8383f4bde31e52e4b3fe45f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <iseulde@automattic.com> Date: Tue, 12 Mar 2019 12:55:43 +0100 Subject: [PATCH 640/691] RichText: Fix prepareEditableTree (#14284) * Fix use of __experimentalCreatePrepareEditableTree without __experimentalCreateOnChangeEditableValue * Add unit tests --- packages/rich-text/src/create.js | 7 ++++ .../rich-text/src/register-format-type.js | 12 +++--- packages/rich-text/src/test/helpers/index.js | 38 +++++++++++++++++++ packages/rich-text/src/test/to-html-string.js | 5 +++ 4 files changed, 57 insertions(+), 5 deletions(-) diff --git a/packages/rich-text/src/create.js b/packages/rich-text/src/create.js index 939b56fb9a8ed..d1c1e47d4df06 100644 --- a/packages/rich-text/src/create.js +++ b/packages/rich-text/src/create.js @@ -57,6 +57,13 @@ function toFormat( { type, attributes } ) { return attributes ? { type, attributes } : { type }; } + if ( + formatType.__experimentalCreatePrepareEditableTree && + ! formatType.__experimentalCreateOnChangeEditableValue + ) { + return null; + } + if ( ! attributes ) { return { type: formatType.name }; } diff --git a/packages/rich-text/src/register-format-type.js b/packages/rich-text/src/register-format-type.js index 8b07559541984..8bee07ff8c776 100644 --- a/packages/rich-text/src/register-format-type.js +++ b/packages/rich-text/src/register-format-type.js @@ -143,7 +143,7 @@ export function registerFormatType( name, settings ) { } ); if ( - settings.__experimentalGetPropsForEditableTreePreparation + settings.__experimentalCreatePrepareEditableTree ) { addFilter( 'experimentalRichText', name, ( OriginalComponent ) => { let Component = OriginalComponent; @@ -197,8 +197,10 @@ export function registerFormatType( name, settings ) { }; } - const hocs = [ - withSelect( ( sel, { clientId, identifier } ) => ( { + const hocs = []; + + if ( settings.__experimentalGetPropsForEditableTreePreparation ) { + hocs.push( withSelect( ( sel, { clientId, identifier } ) => ( { [ `format_${ name }` ]: settings.__experimentalGetPropsForEditableTreePreparation( sel, { @@ -206,8 +208,8 @@ export function registerFormatType( name, settings ) { blockClientId: clientId, } ), - } ) ), - ]; + } ) ) ); + } if ( settings.__experimentalGetPropsForEditableTreeChangeHandler ) { hocs.push( withDispatch( ( disp, { clientId, identifier } ) => { diff --git a/packages/rich-text/src/test/helpers/index.js b/packages/rich-text/src/test/helpers/index.js index e12757ad03530..859e7e3acefc2 100644 --- a/packages/rich-text/src/test/helpers/index.js +++ b/packages/rich-text/src/test/helpers/index.js @@ -650,4 +650,42 @@ export const specWithRegistration = [ text: 'a', }, }, + { + description: 'should not create format if editable tree only', + formatName: 'my-plugin/link', + formatType: { + title: 'Custom Link', + tagName: 'a', + className: 'custom-format', + edit() {}, + __experimentalCreatePrepareEditableTree() {}, + }, + html: '<a class="custom-format">a</a>', + value: { + formats: [ , ], + text: 'a', + }, + noToHTMLString: true, + }, + { + description: 'should create format if editable tree only but changes need to be recorded', + formatName: 'my-plugin/link', + formatType: { + title: 'Custom Link', + tagName: 'a', + className: 'custom-format', + edit() {}, + __experimentalCreatePrepareEditableTree() {}, + __experimentalCreateOnChangeEditableValue() {}, + }, + html: '<a class="custom-format">a</a>', + value: { + formats: [ [ { + type: 'my-plugin/link', + attributes: {}, + unregisteredAttributes: {}, + } ] ], + text: 'a', + }, + }, ]; diff --git a/packages/rich-text/src/test/to-html-string.js b/packages/rich-text/src/test/to-html-string.js index cdb491b21dedf..bd8e846cb13ef 100644 --- a/packages/rich-text/src/test/to-html-string.js +++ b/packages/rich-text/src/test/to-html-string.js @@ -35,7 +35,12 @@ describe( 'toHTMLString', () => { formatType, html, value, + noToHTMLString, } ) => { + if ( noToHTMLString ) { + return; + } + it( description, () => { if ( formatName ) { registerFormatType( formatName, formatType ); From dfc726feb6e38bf7bda1770b5a7805c6cf255650 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Tue, 12 Mar 2019 13:58:54 +0100 Subject: [PATCH 641/691] Add module entry point to notices package.json file (#14388) --- packages/notices/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/notices/package.json b/packages/notices/package.json index 929afd3efd5ab..1e07cd176cd8d 100644 --- a/packages/notices/package.json +++ b/packages/notices/package.json @@ -18,6 +18,7 @@ "url": "https://github.com/WordPress/gutenberg/issues" }, "main": "build/index.js", + "module": "build-module/index.js", "react-native": "src/index", "dependencies": { "@babel/runtime": "^7.3.1", From 1386ab4ba9bcc5941bae520b72b7d9a60eb4714b Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Tue, 12 Mar 2019 09:14:07 -0400 Subject: [PATCH 642/691] Plugin: Remove PHP functions slated for 5.3 removal (#14380) --- lib/load.php | 1 - lib/register.php | 86 ------------------------------------------------ 2 files changed, 87 deletions(-) delete mode 100644 lib/register.php diff --git a/lib/load.php b/lib/load.php index cb6f3c5cee300..6e8f9a6e101c8 100644 --- a/lib/load.php +++ b/lib/load.php @@ -26,7 +26,6 @@ require dirname( __FILE__ ) . '/client-assets.php'; require dirname( __FILE__ ) . '/i18n.php'; -require dirname( __FILE__ ) . '/register.php'; require dirname( __FILE__ ) . '/demo.php'; require dirname( __FILE__ ) . '/widgets-page.php'; diff --git a/lib/register.php b/lib/register.php deleted file mode 100644 index 91ef3ee26e9aa..0000000000000 --- a/lib/register.php +++ /dev/null @@ -1,86 +0,0 @@ -<?php -/** - * Initialization and wp-admin integration for the Gutenberg editor plugin. - * - * @package gutenberg - */ - -if ( ! defined( 'ABSPATH' ) ) { - die( 'Silence is golden.' ); -} - -/** - * Return whether the post can be edited in Gutenberg and by the current user. - * - * @since 0.5.0 - * @deprecated 5.1.0 use_block_editor_for_post - * - * @param int|WP_Post $post Post ID or WP_Post object. - * @return bool Whether the post can be edited with Gutenberg. - */ -function gutenberg_can_edit_post( $post ) { - _deprecated_function( __FUNCTION__, '5.1.0', 'use_block_editor_for_post' ); - - require_once ABSPATH . 'wp-admin/includes/post.php'; - return use_block_editor_for_post( $post ); -} - -/** - * Return whether the post type can be edited in Gutenberg. - * - * Gutenberg depends on the REST API, and if the post type is not shown in the - * REST API, then the post cannot be edited in Gutenberg. - * - * @since 1.5.2 - * @deprecated 5.1.0 use_block_editor_for_post_type - * - * @param string $post_type The post type. - * @return bool Whether the post type can be edited with Gutenberg. - */ -function gutenberg_can_edit_post_type( $post_type ) { - _deprecated_function( __FUNCTION__, '5.1.0', 'use_block_editor_for_post_type' ); - - require_once ABSPATH . 'wp-admin/includes/post.php'; - return use_block_editor_for_post_type( $post_type ); -} - -/** - * Injects a hidden input in the edit form to propagate the information that classic editor is selected. - * - * @since 1.5.2 - * @deprecated 5.1.0 - */ -function gutenberg_remember_classic_editor_when_saving_posts() { - _deprecated_function( __FUNCTION__, '5.1.0' ); -} - -/** - * Appends a query argument to the redirect url to make sure it gets redirected to the classic editor. - * - * @since 1.5.2 - * @deprecated 5.1.0 - * - * @param string $url Redirect url. - * @return string Redirect url. - */ -function gutenberg_redirect_to_classic_editor_when_saving_posts( $url ) { - _deprecated_function( __FUNCTION__, '5.1.0' ); - - return $url; -} - -/** - * Appends a query argument to the edit url to make sure it is redirected to - * the editor from which the user navigated. - * - * @since 1.5.2 - * @deprecated 5.1.0 - * - * @param string $url Edit url. - * @return string Edit url. - */ -function gutenberg_revisions_link_to_editor( $url ) { - _deprecated_function( __FUNCTION__, '5.1.0' ); - - return $url; -} From 186b672ac8bba85ac113158019cf8722d821867c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <iseulde@automattic.com> Date: Wed, 13 Mar 2019 12:55:28 +0100 Subject: [PATCH 643/691] RichText: change value to have separate keys for line and object formats (#13948) * Add objects and lineFormats * Update RichText * Fix image toolbar * Update format placeholder * lineFormat => lines * concatPair => mergePair * Update selectedFormat checks * Add some extra info to create docs * Move create docs inline * Merge lines and objects * Fix typos * Add getActiveObject unit tests * Update docs * Rebase * Adjust unstableToDom arguments * Remove normaliseFormats from list functions * Update native files * Update native file --- .../src/components/rich-text/format-edit.js | 13 +- .../src/components/rich-text/index.js | 24 +-- .../src/components/rich-text/index.native.js | 15 +- packages/format-library/src/image/index.js | 21 ++- packages/rich-text/README.md | 65 ++++++-- packages/rich-text/src/apply-format.js | 18 +-- packages/rich-text/src/apply-format.native.js | 14 +- packages/rich-text/src/change-list-type.js | 25 ++-- packages/rich-text/src/concat.js | 23 ++- packages/rich-text/src/create.js | 140 ++++++++---------- packages/rich-text/src/get-active-object.js | 20 +++ .../rich-text/src/get-last-child-index.js | 6 +- .../rich-text/src/get-parent-line-index.js | 6 +- packages/rich-text/src/indent-list-items.js | 29 ++-- packages/rich-text/src/index.js | 1 + .../rich-text/src/insert-line-separator.js | 9 +- packages/rich-text/src/insert-object.js | 6 +- packages/rich-text/src/insert.js | 9 +- packages/rich-text/src/join.js | 5 +- packages/rich-text/src/normalise-formats.js | 8 +- .../rich-text/src/normalise-formats.native.js | 36 ----- packages/rich-text/src/outdent-list-items.js | 19 +-- packages/rich-text/src/remove-format.js | 38 +++-- .../rich-text/src/remove-format.native.js | 9 +- packages/rich-text/src/replace.js | 8 +- packages/rich-text/src/slice.js | 11 +- packages/rich-text/src/split.js | 7 +- .../src/test/__snapshots__/to-dom.js.snap | 11 +- packages/rich-text/src/test/apply-format.js | 2 +- .../rich-text/src/test/change-list-type.js | 24 ++- packages/rich-text/src/test/concat.js | 3 + packages/rich-text/src/test/create.js | 1 + .../rich-text/src/test/get-active-object.js | 41 +++++ .../src/test/get-last-child-index.js | 12 +- .../src/test/get-parent-line-index.js | 8 +- packages/rich-text/src/test/helpers/index.js | 61 ++++++-- .../rich-text/src/test/indent-list-items.js | 74 ++++----- .../src/test/insert-line-separator.js | 20 ++- packages/rich-text/src/test/insert-object.js | 7 +- packages/rich-text/src/test/insert.js | 6 + packages/rich-text/src/test/join.js | 6 +- .../rich-text/src/test/outdent-list-items.js | 64 ++++---- packages/rich-text/src/test/replace.js | 7 + packages/rich-text/src/test/slice.js | 4 + packages/rich-text/src/test/split.js | 22 +++ packages/rich-text/src/test/to-dom.js | 2 - packages/rich-text/src/to-dom.js | 4 - packages/rich-text/src/to-html-string.js | 5 +- packages/rich-text/src/to-tree.js | 54 ++++--- packages/rich-text/src/toggle-format.js | 5 +- 50 files changed, 583 insertions(+), 445 deletions(-) create mode 100644 packages/rich-text/src/get-active-object.js delete mode 100644 packages/rich-text/src/normalise-formats.native.js create mode 100644 packages/rich-text/src/test/get-active-object.js diff --git a/packages/block-editor/src/components/rich-text/format-edit.js b/packages/block-editor/src/components/rich-text/format-edit.js index 7505ed1f81a3c..29911206aba83 100644 --- a/packages/block-editor/src/components/rich-text/format-edit.js +++ b/packages/block-editor/src/components/rich-text/format-edit.js @@ -3,7 +3,7 @@ */ import { withSelect } from '@wordpress/data'; import { Fragment } from '@wordpress/element'; -import { getActiveFormat } from '@wordpress/rich-text'; +import { getActiveFormat, getActiveObject } from '@wordpress/rich-text'; const FormatEdit = ( { formatTypes, onChange, value } ) => { return ( @@ -15,13 +15,20 @@ const FormatEdit = ( { formatTypes, onChange, value } ) => { const activeFormat = getActiveFormat( value, name ); const isActive = activeFormat !== undefined; - const activeAttributes = isActive ? activeFormat.attributes || {} : {}; + const activeObject = getActiveObject( value ); + const isObjectActive = activeObject !== undefined; return ( <Edit key={ name } isActive={ isActive } - activeAttributes={ activeAttributes } + activeAttributes={ + isActive ? activeFormat.attributes || {} : {} + } + isObjectActive={ isObjectActive } + activeObjectAttributes={ + isObjectActive ? activeObject.attributes || {} : {} + } value={ value } onChange={ onChange } /> diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 5b91674b9a673..1d714ba9844d3 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -177,10 +177,10 @@ export class RichText extends Component { * @return {Object} The current record (value and selection). */ getRecord() { - const { formats, text } = this.formatToValue( this.props.value ); + const { formats, replacements, text } = this.formatToValue( this.props.value ); const { start, end, selectedFormat } = this.state; - return { formats, text, start, end, selectedFormat }; + return { formats, replacements, text, start, end, selectedFormat }; } createRecord() { @@ -394,13 +394,17 @@ export class RichText extends Component { } let { selectedFormat } = this.state; - const { formats, text, start, end } = this.createRecord(); + const { formats, replacements, text, start, end } = this.createRecord(); if ( this.formatPlaceholder ) { - formats[ this.state.start ] = formats[ this.state.start ] || []; - formats[ this.state.start ].push( this.formatPlaceholder ); - selectedFormat = formats[ this.state.start ].length; - } else if ( selectedFormat ) { + selectedFormat = this.formatPlaceholder.length; + + if ( selectedFormat > 0 ) { + formats[ this.state.start ] = this.formatPlaceholder; + } else { + delete formats[ this.state.start ]; + } + } else if ( selectedFormat > 0 ) { const formatsBefore = formats[ start - 1 ] || []; const formatsAfter = formats[ start ] || []; @@ -411,12 +415,13 @@ export class RichText extends Component { } source = source.slice( 0, selectedFormat ); + formats[ this.state.start ] = source; } else { delete formats[ this.state.start ]; } - const change = { formats, text, start, end, selectedFormat }; + const change = { formats, replacements, text, start, end, selectedFormat }; this.onChange( change, { withoutHistory: true, @@ -936,7 +941,6 @@ export class RichText extends Component { return unstableToDom( { value, multilineTag: this.multilineTag, - multilineWrapperTags: this.multilineWrapperTags, prepareEditableTree: this.props.prepareEditableTree, } ).body.innerHTML; } @@ -975,7 +979,6 @@ export class RichText extends Component { return children.fromDOM( unstableToDom( { value, multilineTag: this.multilineTag, - multilineWrapperTags: this.multilineWrapperTags, isEditableTree: false, } ).body.childNodes ); } @@ -984,7 +987,6 @@ export class RichText extends Component { return toHTMLString( { value, multilineTag: this.multilineTag, - multilineWrapperTags: this.multilineWrapperTags, } ); } diff --git a/packages/block-editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js index 55bf4580e5e70..db750a18d41f2 100644 --- a/packages/block-editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -98,9 +98,9 @@ export class RichText extends Component { const { formatPlaceholder, start, end } = this.state; // Since we get the text selection from Aztec we need to be in sync with the HTML `value` // Removing leading white spaces using `trim()` should make sure this is the case. - const { formats, text } = this.formatToValue( this.props.value === undefined ? undefined : this.props.value.trimLeft() ); + const { formats, replacements, text } = this.formatToValue( this.props.value === undefined ? undefined : this.props.value.trimLeft() ); - return { formats, formatPlaceholder, text, start, end }; + return { formats, replacements, formatPlaceholder, text, start, end }; } /* @@ -156,13 +156,12 @@ export class RichText extends Component { onSplit( before, after, ...blocks ); } - valueToFormat( { formats, text } ) { - const value = toHTMLString( { - value: { formats, text }, - multilineTag: this.multilineTag, - } ); + valueToFormat( value ) { // remove the outer root tags - return this.removeRootTagsProduceByAztec( value ); + return this.removeRootTagsProduceByAztec( toHTMLString( { + value, + multilineTag: this.multilineTag, + } ) ); } getActiveFormatNames( record ) { diff --git a/packages/format-library/src/image/index.js b/packages/format-library/src/image/index.js index 2dca1c41506b6..ce62a8b6c5a75 100644 --- a/packages/format-library/src/image/index.js +++ b/packages/format-library/src/image/index.js @@ -40,7 +40,7 @@ export const image = { } static getDerivedStateFromProps( props, state ) { - const { activeAttributes: { style } } = props; + const { activeObjectAttributes: { style } } = props; if ( style === state.previousStyle ) { return null; @@ -79,8 +79,8 @@ export const image = { } render() { - const { value, onChange, isActive, activeAttributes } = this.props; - const { style } = activeAttributes; + const { value, onChange, isObjectActive, activeObjectAttributes } = this.props; + const { style } = activeObjectAttributes; // Rerender PositionedAtSelection when the selection changes or when // the width changes. const key = value.start + style; @@ -91,7 +91,7 @@ export const image = { icon={ <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><Path d="M4 16h10c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v9c0 1.1.9 2 2 2zM4 5h10v9H4V5zm14 9v2h4v-2h-4zM2 20h20v-2H2v2zm6.4-8.8L7 9.4 5 12h8l-2.6-3.4-2 2.6z" /></SVG> } title={ __( 'Inline Image' ) } onClick={ this.openModal } - isActive={ isActive } + isActive={ isObjectActive } /> { this.state.modal && <MediaUpload allowedTypes={ ALLOWED_MEDIA_TYPES } @@ -113,7 +113,7 @@ export const image = { return null; } } /> } - { isActive && <PositionedAtSelection key={ key }> + { isObjectActive && <PositionedAtSelection key={ key }> <Popover position="bottom center" focusOnMount={ false } @@ -125,20 +125,19 @@ export const image = { onKeyPress={ stopKeyPropagation } onKeyDown={ this.onKeyDown } onSubmit={ ( event ) => { - const newFormats = value.formats.slice( 0 ); + const newReplacements = value.replacements.slice(); - newFormats[ value.start ] = [ { + newReplacements[ value.start ] = { type: name, - object: true, attributes: { - ...activeAttributes, + ...activeObjectAttributes, style: `width: ${ this.state.width }px;`, }, - } ]; + }; onChange( { ...value, - formats: newFormats, + replacements: newReplacements, } ); event.preventDefault(); diff --git a/packages/rich-text/README.md b/packages/rich-text/README.md index 0ac85d85b640d..90fe67367ddc8 100644 --- a/packages/rich-text/README.md +++ b/packages/rich-text/README.md @@ -61,6 +61,26 @@ called without any input, an empty value will be created. If `multilineTag` will be separated by two newlines. The optional functions can be used to filter out content. +A value will have the following shape, which you are strongly encouraged not +to modify without the use of helper functions: + +```js +{ + text: string, + formats: Array, + replacements: Array, + ?start: number, + ?end: number, +} +``` + +As you can see, text and formatting are separated. `text` holds the text, +including any replacement characters for objects and lines. `formats`, +`objects` and `lines` are all sparse arrays of the same length as `text`. It +holds information about the formatting at the relevant text indices. Finally +`start` and `end` state which text indices are selected. They are only +provided if a `Range` was given. + **Parameters** - **$1** `[Object]`: Optional named arguments. @@ -93,9 +113,23 @@ is no format at the selection. `(Object|undefined)`: Active format object of the specified type, or undefined. +### getActiveObject + +[src/index.js#L11-L11](src/index.js#L11-L11) + +Gets the active object, if there is any. + +**Parameters** + +- **value** `Object`: Value to inspect. + +**Returns** + +`?Object`: Active object, or undefined. + ### getTextContent -[src/index.js#L13-L13](src/index.js#L13-L13) +[src/index.js#L14-L14](src/index.js#L14-L14) Get the textual content of a Rich Text value. This is similar to `Element.textContent`. @@ -110,7 +144,7 @@ Get the textual content of a Rich Text value. This is similar to ### insert -[src/index.js#L21-L21](src/index.js#L21-L21) +[src/index.js#L22-L22](src/index.js#L22-L22) Insert a Rich Text value, an HTML string, or a plain text string, into a Rich Text value at the given `startIndex`. Any content between `startIndex` @@ -130,7 +164,7 @@ none are provided. ### insertObject -[src/index.js#L24-L24](src/index.js#L24-L24) +[src/index.js#L25-L25](src/index.js#L25-L25) Insert a format as an object into a Rich Text value at the given `startIndex`. Any content between `startIndex` and `endIndex` will be @@ -149,7 +183,7 @@ removed. Indices are retrieved from the selection if none are provided. ### isCollapsed -[src/index.js#L14-L14](src/index.js#L14-L14) +[src/index.js#L15-L15](src/index.js#L15-L15) Check if the selection of a Rich Text value is collapsed or not. Collapsed means that no characters are selected, but there is a caret present. If there @@ -166,7 +200,7 @@ is no selection, `undefined` will be returned. This is similar to ### isEmpty -[src/index.js#L15-L15](src/index.js#L15-L15) +[src/index.js#L16-L16](src/index.js#L16-L16) Check if a Rich Text value is Empty, meaning it contains no text or any objects (such as images). @@ -181,7 +215,7 @@ objects (such as images). ### join -[src/index.js#L16-L16](src/index.js#L16-L16) +[src/index.js#L17-L17](src/index.js#L17-L17) Combine an array of Rich Text values into one, optionally separated by `separator`, which can be a Rich Text value, HTML string, or plain text @@ -198,7 +232,7 @@ string. This is similar to `Array.prototype.join`. ### registerFormatType -[src/index.js#L17-L17](src/index.js#L17-L17) +[src/index.js#L18-L18](src/index.js#L18-L18) Registers a new format provided a unique name and an object defining its behavior. @@ -218,7 +252,7 @@ behavior. ### remove -[src/index.js#L19-L19](src/index.js#L19-L19) +[src/index.js#L20-L20](src/index.js#L20-L20) Remove content from a Rich Text value between the given `startIndex` and `endIndex`. Indices are retrieved from the selection if none are provided. @@ -235,7 +269,7 @@ Remove content from a Rich Text value between the given `startIndex` and ### removeFormat -[src/index.js#L18-L18](src/index.js#L18-L18) +[src/index.js#L19-L19](src/index.js#L19-L19) Remove any format object from a Rich Text value by type from the given `startIndex` to the given `endIndex`. Indices are retrieved from the @@ -254,7 +288,7 @@ selection if none are provided. ### replace -[src/index.js#L20-L20](src/index.js#L20-L20) +[src/index.js#L21-L21](src/index.js#L21-L21) Search a Rich Text value and replace the match(es) with `replacement`. This is similar to `String.prototype.replace`. @@ -271,7 +305,7 @@ is similar to `String.prototype.replace`. ### slice -[src/index.js#L25-L25](src/index.js#L25-L25) +[src/index.js#L26-L26](src/index.js#L26-L26) Slice a Rich Text value from `startIndex` to `endIndex`. Indices are retrieved from the selection if none are provided. This is similar to @@ -289,7 +323,7 @@ retrieved from the selection if none are provided. This is similar to ### split -[src/index.js#L26-L26](src/index.js#L26-L26) +[src/index.js#L27-L27](src/index.js#L27-L27) Split a Rich Text value in two at the given `startIndex` and `endIndex`, or split at the given separator. This is similar to `String.prototype.split`. @@ -307,7 +341,7 @@ Indices are retrieved from the selection if none are provided. ### toggleFormat -[src/index.js#L29-L29](src/index.js#L29-L29) +[src/index.js#L30-L30](src/index.js#L30-L30) Toggles a format object to a Rich Text value at the current selection. @@ -322,7 +356,7 @@ Toggles a format object to a Rich Text value at the current selection. ### toHTMLString -[src/index.js#L28-L28](src/index.js#L28-L28) +[src/index.js#L29-L29](src/index.js#L29-L29) Create an HTML string from a Rich Text value. If a `multilineTag` is provided, text separated by a line separator will be wrapped in it. @@ -332,7 +366,6 @@ provided, text separated by a line separator will be wrapped in it. - **$1** `Object`: Named argements. - **$1.value** `Object`: Rich text value. - **$1.multilineTag** `[string]`: Multiline tag. -- **$1.multilineWrapperTags** `[Array]`: Tags where lines can be found if nesting is possible. **Returns** @@ -340,7 +373,7 @@ provided, text separated by a line separator will be wrapped in it. ### unregisterFormatType -[src/index.js#L31-L31](src/index.js#L31-L31) +[src/index.js#L32-L32](src/index.js#L32-L32) Unregisters a format. diff --git a/packages/rich-text/src/apply-format.js b/packages/rich-text/src/apply-format.js index 8134cd8d742a9..c9a59b96e6a65 100644 --- a/packages/rich-text/src/apply-format.js +++ b/packages/rich-text/src/apply-format.js @@ -23,12 +23,12 @@ import { normaliseFormats } from './normalise-formats'; * @return {Object} A new value with the format applied. */ export function applyFormat( - { formats, text, start, end }, + value, format, - startIndex = start, - endIndex = end + startIndex = value.start, + endIndex = value.end ) { - const newFormats = formats.slice( 0 ); + const newFormats = value.formats.slice( 0 ); // The selection is collapsed. if ( startIndex === endIndex ) { @@ -52,14 +52,10 @@ export function applyFormat( // with the format applied. } else { const previousFormat = newFormats[ startIndex - 1 ] || []; - const hasType = find( previousFormat, { type: format.type } ); return { - formats, - text, - start, - end, - formatPlaceholder: hasType ? undefined : format, + ...value, + formatPlaceholder: [ ...previousFormat, format ], }; } } else { @@ -68,7 +64,7 @@ export function applyFormat( } } - return normaliseFormats( { formats: newFormats, text, start, end } ); + return normaliseFormats( { ...value, formats: newFormats } ); } function applyFormats( formats, index, format ) { diff --git a/packages/rich-text/src/apply-format.native.js b/packages/rich-text/src/apply-format.native.js index 9c5a4749ae928..ef641cbaa224d 100644 --- a/packages/rich-text/src/apply-format.native.js +++ b/packages/rich-text/src/apply-format.native.js @@ -23,11 +23,13 @@ import { normaliseFormats } from './normalise-formats'; * @return {Object} A new value with the format applied. */ export function applyFormat( - { formats: currentFormats, formatPlaceholder, text, start, end }, + value, formats, - startIndex = start, - endIndex = end + startIndex = value.start, + endIndex = value.end ) { + const { formats: currentFormats, formatPlaceholder, start } = value; + if ( ! Array.isArray( formats ) ) { formats = [ formats ]; } @@ -40,10 +42,8 @@ export function applyFormat( // Follow the same logic as in getActiveFormat: placeholderFormats has priority over previousFormats const activeFormats = ( placeholderFormats ? placeholderFormats : previousFormats ) || []; return { + ...value, formats: currentFormats, - text, - start, - end, formatPlaceholder: { index: start, formats: mergeFormats( activeFormats, formats ), @@ -57,7 +57,7 @@ export function applyFormat( applyFormats( newFormats, index, formats ); } - return normaliseFormats( { formats: newFormats, text, start, end } ); + return normaliseFormats( { ...value, formats: newFormats } ); } function mergeFormats( formats1, formats2 ) { diff --git a/packages/rich-text/src/change-list-type.js b/packages/rich-text/src/change-list-type.js index 1dfc040657363..e429b91c6cbca 100644 --- a/packages/rich-text/src/change-list-type.js +++ b/packages/rich-text/src/change-list-type.js @@ -3,7 +3,6 @@ */ import { LINE_SEPARATOR } from './special-characters'; -import { normaliseFormats } from './normalise-formats'; import { getLineIndex } from './get-line-index'; import { getParentLineIndex } from './get-parent-line-index'; @@ -20,12 +19,12 @@ import { getParentLineIndex } from './get-parent-line-index'; * @return {Object} The changed value. */ export function changeListType( value, newFormat ) { - const { text, formats, start, end } = value; + const { text, replacements, start, end } = value; const startingLineIndex = getLineIndex( value, start ); - const startLineFormats = formats[ startingLineIndex ] || []; - const endLineFormats = formats[ getLineIndex( value, end ) ] || []; + const startLineFormats = replacements[ startingLineIndex ] || []; + const endLineFormats = replacements[ getLineIndex( value, end ) ] || []; const startIndex = getParentLineIndex( value, startingLineIndex ); - const newFormats = formats.slice( 0 ); + const newReplacements = replacements.slice(); const startCount = startLineFormats.length - 1; const endCount = endLineFormats.length - 1; @@ -36,16 +35,16 @@ export function changeListType( value, newFormat ) { continue; } - if ( ( newFormats[ index ] || [] ).length <= startCount ) { + if ( ( newReplacements[ index ] || [] ).length <= startCount ) { break; } - if ( ! newFormats[ index ] ) { + if ( ! newReplacements[ index ] ) { continue; } changed = true; - newFormats[ index ] = newFormats[ index ].map( ( format, i ) => { + newReplacements[ index ] = newReplacements[ index ].map( ( format, i ) => { return i < startCount || i > endCount ? format : newFormat; } ); } @@ -54,10 +53,8 @@ export function changeListType( value, newFormat ) { return value; } - return normaliseFormats( { - text, - formats: newFormats, - start, - end, - } ); + return { + ...value, + replacements: newReplacements, + }; } diff --git a/packages/rich-text/src/concat.js b/packages/rich-text/src/concat.js index 9f14091f9b1c8..07be12140db91 100644 --- a/packages/rich-text/src/concat.js +++ b/packages/rich-text/src/concat.js @@ -3,6 +3,24 @@ */ import { normaliseFormats } from './normalise-formats'; +import { create } from './create'; + +/** + * Concats a pair of rich text values. Not that this mutates `a` and does NOT + * normalise formats! + * + * @param {Object} a Value to mutate. + * @param {Object} b Value to add read from. + * + * @return {Object} `a`, mutated. + */ +export function mergePair( a, b ) { + a.formats = a.formats.concat( b.formats ); + a.replacements = a.replacements.concat( b.replacements ); + a.text += b.text; + + return a; +} /** * Combine all Rich Text values into one. This is similar to @@ -13,8 +31,5 @@ import { normaliseFormats } from './normalise-formats'; * @return {Object} A new value combining all given records. */ export function concat( ...values ) { - return normaliseFormats( values.reduce( ( accumlator, { formats, text } ) => ( { - text: accumlator.text + text, - formats: accumlator.formats.concat( formats ), - } ) ) ); + return normaliseFormats( values.reduce( mergePair, create() ) ); } diff --git a/packages/rich-text/src/create.js b/packages/rich-text/src/create.js index d1c1e47d4df06..37bd1189193f2 100644 --- a/packages/rich-text/src/create.js +++ b/packages/rich-text/src/create.js @@ -7,9 +7,9 @@ import { select } from '@wordpress/data'; * Internal dependencies */ -import { isEmpty } from './is-empty'; import { isFormatEqual } from './is-format-equal'; import { createElement } from './create-element'; +import { mergePair } from './concat'; import { LINE_SEPARATOR, OBJECT_REPLACEMENT_CHARACTER, @@ -22,7 +22,11 @@ import { const { TEXT_NODE, ELEMENT_NODE } = window.Node; function createEmptyValue() { - return { formats: [], text: '' }; + return { + formats: [], + replacements: [], + text: '', + }; } function simpleFindKey( object, value ) { @@ -96,6 +100,26 @@ function toFormat( { type, attributes } ) { * `multilineTag` will be separated by two newlines. The optional functions can * be used to filter out content. * + * A value will have the following shape, which you are strongly encouraged not + * to modify without the use of helper functions: + * + * ```js + * { + * text: string, + * formats: Array, + * replacements: Array, + * ?start: number, + * ?end: number, + * } + * ``` + * + * As you can see, text and formatting are separated. `text` holds the text, + * including any replacement characters for objects and lines. `formats`, + * `objects` and `lines` are all sparse arrays of the same length as `text`. It + * holds information about the formatting at the relevant text indices. Finally + * `start` and `end` state which text indices are selected. They are only + * provided if a `Range` was given. + * * @param {Object} [$1] Optional named arguments. * @param {Element} [$1.element] Element to create value from. * @param {string} [$1.text] Text to create value from. @@ -120,6 +144,7 @@ export function create( { if ( typeof text === 'string' && text.length > 0 ) { return { formats: Array( text.length ), + replacements: Array( text.length ), text, }; } @@ -291,10 +316,11 @@ function createFromElement( { const text = filterString( node.nodeValue ); range = filterRange( node, range, filterString ); accumulateSelection( accumulator, node, range, { text } ); - accumulator.text += text; // Create a sparse array of the same length as `text`, in which // formats can be added. accumulator.formats.length += text.length; + accumulator.replacements.length += text.length; + accumulator.text += text; continue; } @@ -312,8 +338,7 @@ function createFromElement( { if ( type === 'br' ) { accumulateSelection( accumulator, node, range, createEmptyValue() ); - accumulator.text += '\n'; - accumulator.formats.length += 1; + mergePair( accumulator, create( { text: '\n' } ) ); continue; } @@ -323,22 +348,10 @@ function createFromElement( { type, attributes: getAttributes( { element: node } ), } ); - - let format; - - if ( newFormat ) { - // Reuse the last format if it's equal. - if ( isFormatEqual( newFormat, lastFormat ) ) { - format = lastFormat; - } else { - format = newFormat; - } - } - - let value; + const format = isFormatEqual( newFormat, lastFormat ) ? lastFormat : newFormat; if ( multilineWrapperTags && multilineWrapperTags.indexOf( type ) !== -1 ) { - value = createFromMultilineElement( { + const value = createFromMultilineElement( { element: node, range, multilineTag, @@ -346,64 +359,39 @@ function createFromElement( { currentWrapperTags: [ ...currentWrapperTags, format ], isEditableTree, } ); - format = undefined; - } else { - value = createFromElement( { - element: node, - range, - multilineTag, - multilineWrapperTags, - isEditableTree, - } ); - } - - const text = value.text; - const start = accumulator.text.length; - accumulateSelection( accumulator, node, range, value ); - - // Don't apply the element as formatting if it has no content. - if ( isEmpty( value ) && format && ! format.attributes ) { + accumulateSelection( accumulator, node, range, value ); + mergePair( accumulator, value ); continue; } - const { formats } = accumulator; + const value = createFromElement( { + element: node, + range, + multilineTag, + multilineWrapperTags, + isEditableTree, + } ); - if ( format && format.attributes && text.length === 0 ) { - format.object = true; - accumulator.text += OBJECT_REPLACEMENT_CHARACTER; + accumulateSelection( accumulator, node, range, value ); - if ( formats[ start ] ) { - formats[ start ].unshift( format ); - } else { - formats[ start ] = [ format ]; + if ( ! format ) { + mergePair( accumulator, value ); + } else if ( value.text.length === 0 ) { + if ( format.attributes ) { + mergePair( accumulator, { + formats: [ , ], + replacements: [ format ], + text: OBJECT_REPLACEMENT_CHARACTER, + } ); } } else { - accumulator.text += text; - accumulator.formats.length += text.length; - - let i = value.formats.length; - - // Optimise for speed. - while ( i-- ) { - const formatIndex = start + i; - - if ( format ) { - if ( formats[ formatIndex ] ) { - formats[ formatIndex ].push( format ); - } else { - formats[ formatIndex ] = [ format ]; - } - } - - if ( value.formats[ i ] ) { - if ( formats[ formatIndex ] ) { - formats[ formatIndex ].push( ...value.formats[ i ] ); - } else { - formats[ formatIndex ] = value.formats[ i ]; - } - } - } + mergePair( accumulator, { + ...value, + formats: Array.from( value.formats, ( formats ) => + formats ? [ format, ...formats ] : [ format ] + ), + } ); } } @@ -459,17 +447,17 @@ function createFromMultilineElement( { isEditableTree, } ); - // Multiline value text should be separated by a double line break. + // Multiline value text should be separated by a line separator. if ( index !== 0 || currentWrapperTags.length > 0 ) { - const formats = currentWrapperTags.length > 0 ? [ currentWrapperTags ] : [ , ]; - accumulator.formats = accumulator.formats.concat( formats ); - accumulator.text += LINE_SEPARATOR; + mergePair( accumulator, { + formats: [ , ], + replacements: currentWrapperTags.length > 0 ? [ currentWrapperTags ] : [ , ], + text: LINE_SEPARATOR, + } ); } accumulateSelection( accumulator, node, range, value ); - - accumulator.formats = accumulator.formats.concat( value.formats ); - accumulator.text += value.text; + mergePair( accumulator, value ); } return accumulator; diff --git a/packages/rich-text/src/get-active-object.js b/packages/rich-text/src/get-active-object.js new file mode 100644 index 0000000000000..e324521284597 --- /dev/null +++ b/packages/rich-text/src/get-active-object.js @@ -0,0 +1,20 @@ +/** + * Internal dependencies + */ + +import { OBJECT_REPLACEMENT_CHARACTER } from './special-characters'; + +/** + * Gets the active object, if there is any. + * + * @param {Object} value Value to inspect. + * + * @return {?Object} Active object, or undefined. + */ +export function getActiveObject( { start, end, replacements, text } ) { + if ( start + 1 !== end || text[ start ] !== OBJECT_REPLACEMENT_CHARACTER ) { + return; + } + + return replacements[ start ]; +} diff --git a/packages/rich-text/src/get-last-child-index.js b/packages/rich-text/src/get-last-child-index.js index 976051b10c0a3..0fc4aeb32ff3e 100644 --- a/packages/rich-text/src/get-last-child-index.js +++ b/packages/rich-text/src/get-last-child-index.js @@ -12,8 +12,8 @@ import { LINE_SEPARATOR } from './special-characters'; * * @return {Array} The index of the last child. */ -export function getLastChildIndex( { text, formats }, lineIndex ) { - const lineFormats = formats[ lineIndex ] || []; +export function getLastChildIndex( { text, replacements }, lineIndex ) { + const lineFormats = replacements[ lineIndex ] || []; // Use the given line index in case there are no next children. let childIndex = lineIndex; @@ -24,7 +24,7 @@ export function getLastChildIndex( { text, formats }, lineIndex ) { continue; } - const formatsAtIndex = formats[ index ] || []; + const formatsAtIndex = replacements[ index ] || []; // If the amout of formats is equal or more, store it, then return the // last one if the amount of formats is less. diff --git a/packages/rich-text/src/get-parent-line-index.js b/packages/rich-text/src/get-parent-line-index.js index bd3f72de96519..1b0a0ecb5cf48 100644 --- a/packages/rich-text/src/get-parent-line-index.js +++ b/packages/rich-text/src/get-parent-line-index.js @@ -14,8 +14,8 @@ import { LINE_SEPARATOR } from './special-characters'; * * @return {Array} The parent list line index. */ -export function getParentLineIndex( { text, formats }, lineIndex ) { - const startFormats = formats[ lineIndex ] || []; +export function getParentLineIndex( { text, replacements }, lineIndex ) { + const startFormats = replacements[ lineIndex ] || []; let index = lineIndex; @@ -24,7 +24,7 @@ export function getParentLineIndex( { text, formats }, lineIndex ) { continue; } - const formatsAtIndex = formats[ index ] || []; + const formatsAtIndex = replacements[ index ] || []; if ( formatsAtIndex.length === startFormats.length - 1 ) { return index; diff --git a/packages/rich-text/src/indent-list-items.js b/packages/rich-text/src/indent-list-items.js index 1a61bd7db1536..985d1062c76ed 100644 --- a/packages/rich-text/src/indent-list-items.js +++ b/packages/rich-text/src/indent-list-items.js @@ -3,7 +3,6 @@ */ import { LINE_SEPARATOR } from './special-characters'; -import { normaliseFormats } from './normalise-formats'; import { getLineIndex } from './get-line-index'; /** @@ -14,8 +13,8 @@ import { getLineIndex } from './get-line-index'; * * @return {boolean} The line index. */ -function getTargetLevelLineIndex( { text, formats }, lineIndex ) { - const startFormats = formats[ lineIndex ] || []; +function getTargetLevelLineIndex( { text, replacements }, lineIndex ) { + const startFormats = replacements[ lineIndex ] || []; let index = lineIndex; @@ -24,7 +23,7 @@ function getTargetLevelLineIndex( { text, formats }, lineIndex ) { continue; } - const formatsAtIndex = formats[ index ] || []; + const formatsAtIndex = replacements[ index ] || []; // Return the first line index that is one level higher. If the level is // lower or equal, there is no result. @@ -52,10 +51,10 @@ export function indentListItems( value, rootFormat ) { return value; } - const { text, formats, start, end } = value; + const { text, replacements, end } = value; const previousLineIndex = getLineIndex( value, lineIndex ); - const formatsAtLineIndex = formats[ lineIndex ] || []; - const formatsAtPreviousLineIndex = formats[ previousLineIndex ] || []; + const formatsAtLineIndex = replacements[ lineIndex ] || []; + const formatsAtPreviousLineIndex = replacements[ previousLineIndex ] || []; // The the indentation of the current line is greater than previous line, // then the line cannot be furter indented. @@ -63,7 +62,7 @@ export function indentListItems( value, rootFormat ) { return value; } - const newFormats = formats.slice(); + const newFormats = replacements.slice(); const targetLevelLineIndex = getTargetLevelLineIndex( value, lineIndex ); for ( let index = lineIndex; index < end; index++ ) { @@ -74,12 +73,12 @@ export function indentListItems( value, rootFormat ) { // Get the previous list, and if there's a child list, take over the // formats. If not, duplicate the last level and create a new level. if ( targetLevelLineIndex ) { - const targetFormats = formats[ targetLevelLineIndex ] || []; + const targetFormats = replacements[ targetLevelLineIndex ] || []; newFormats[ index ] = targetFormats.concat( ( newFormats[ index ] || [] ).slice( targetFormats.length - 1 ) ); } else { - const targetFormats = formats[ previousLineIndex ] || []; + const targetFormats = replacements[ previousLineIndex ] || []; const lastformat = targetFormats[ targetFormats.length - 1 ] || rootFormat; newFormats[ index ] = targetFormats.concat( @@ -89,10 +88,8 @@ export function indentListItems( value, rootFormat ) { } } - return normaliseFormats( { - text, - formats: newFormats, - start, - end, - } ); + return { + ...value, + replacements: newFormats, + }; } diff --git a/packages/rich-text/src/index.js b/packages/rich-text/src/index.js index 423046079132b..9d9f462021287 100644 --- a/packages/rich-text/src/index.js +++ b/packages/rich-text/src/index.js @@ -8,6 +8,7 @@ export { charAt } from './char-at'; export { concat } from './concat'; export { create } from './create'; export { getActiveFormat } from './get-active-format'; +export { getActiveObject } from './get-active-object'; export { getSelectionEnd } from './get-selection-end'; export { getSelectionStart } from './get-selection-start'; export { getTextContent } from './get-text-content'; diff --git a/packages/rich-text/src/insert-line-separator.js b/packages/rich-text/src/insert-line-separator.js index e273c9eea9ac4..77434a25ac012 100644 --- a/packages/rich-text/src/insert-line-separator.js +++ b/packages/rich-text/src/insert-line-separator.js @@ -24,15 +24,16 @@ export function insertLineSeparator( ) { const beforeText = getTextContent( value ).slice( 0, startIndex ); const previousLineSeparatorIndex = beforeText.lastIndexOf( LINE_SEPARATOR ); - const previousLineSeparatorFormats = value.formats[ previousLineSeparatorIndex ]; - let formats = [ , ]; + const previousLineSeparatorFormats = value.replacements[ previousLineSeparatorIndex ]; + let replacements = [ , ]; if ( previousLineSeparatorFormats ) { - formats = [ previousLineSeparatorFormats ]; + replacements = [ previousLineSeparatorFormats ]; } const valueToInsert = { - formats, + formats: [ , ], + replacements, text: LINE_SEPARATOR, }; diff --git a/packages/rich-text/src/insert-object.js b/packages/rich-text/src/insert-object.js index fcdfc6f897c2d..7495d6082cbcb 100644 --- a/packages/rich-text/src/insert-object.js +++ b/packages/rich-text/src/insert-object.js @@ -25,11 +25,9 @@ export function insertObject( endIndex ) { const valueToInsert = { + formats: [ , ], + replacements: [ formatToInsert ], text: OBJECT_REPLACEMENT_CHARACTER, - formats: [ [ { - ...formatToInsert, - object: true, - } ] ], }; return insert( value, valueToInsert, startIndex, endIndex ); diff --git a/packages/rich-text/src/insert.js b/packages/rich-text/src/insert.js index cf46305240fb7..4d2353eaba4cb 100644 --- a/packages/rich-text/src/insert.js +++ b/packages/rich-text/src/insert.js @@ -19,11 +19,13 @@ import { normaliseFormats } from './normalise-formats'; * @return {Object} A new value with the value inserted. */ export function insert( - { formats, text, start, end }, + value, valueToInsert, - startIndex = start, - endIndex = end + startIndex = value.start, + endIndex = value.end ) { + const { formats, replacements, text } = value; + if ( typeof valueToInsert === 'string' ) { valueToInsert = create( { text: valueToInsert } ); } @@ -32,6 +34,7 @@ export function insert( return normaliseFormats( { formats: formats.slice( 0, startIndex ).concat( valueToInsert.formats, formats.slice( endIndex ) ), + replacements: replacements.slice( 0, startIndex ).concat( valueToInsert.replacements, replacements.slice( endIndex ) ), text: text.slice( 0, startIndex ) + valueToInsert.text + text.slice( endIndex ), start: index, end: index, diff --git a/packages/rich-text/src/join.js b/packages/rich-text/src/join.js index 7784b5962ca53..cabfc82756016 100644 --- a/packages/rich-text/src/join.js +++ b/packages/rich-text/src/join.js @@ -20,8 +20,9 @@ export function join( values, separator = '' ) { separator = create( { text: separator } ); } - return normaliseFormats( values.reduce( ( accumlator, { formats, text } ) => ( { - text: accumlator.text + separator.text + text, + return normaliseFormats( values.reduce( ( accumlator, { formats, replacements, text } ) => ( { formats: accumlator.formats.concat( separator.formats, formats ), + replacements: accumlator.replacements.concat( separator.replacements, replacements ), + text: accumlator.text + separator.text + text, } ) ) ); } diff --git a/packages/rich-text/src/normalise-formats.js b/packages/rich-text/src/normalise-formats.js index 533df66933886..72c04f818f9a4 100644 --- a/packages/rich-text/src/normalise-formats.js +++ b/packages/rich-text/src/normalise-formats.js @@ -13,13 +13,13 @@ import { isFormatEqual } from './is-format-equal'; /** * Normalises formats: ensures subsequent equal formats have the same reference. * - * @param {Object} value Value to normalise formats of. + * @param {Object} value Value to normalise formats of. * * @return {Object} New value with normalised formats. */ -export function normaliseFormats( { formats, text, start, end } ) { +export function normaliseFormats( value ) { const refs = []; - const newFormats = formats.map( ( formatsAtIndex ) => + const newFormats = value.formats.map( ( formatsAtIndex ) => formatsAtIndex.map( ( format ) => { const equalRef = find( refs, ( ref ) => isFormatEqual( ref, format ) @@ -35,5 +35,5 @@ export function normaliseFormats( { formats, text, start, end } ) { } ) ); - return { formats: newFormats, text, start, end }; + return { ...value, formats: newFormats }; } diff --git a/packages/rich-text/src/normalise-formats.native.js b/packages/rich-text/src/normalise-formats.native.js deleted file mode 100644 index 2a75e343a2c12..0000000000000 --- a/packages/rich-text/src/normalise-formats.native.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Internal dependencies - */ - -import { isFormatEqual } from './is-format-equal'; - -/** - * Normalises formats: ensures subsequent equal formats have the same reference. - * - * @param {Object} value Value to normalise formats of. - * - * @return {Object} New value with normalised formats. - */ -export function normaliseFormats( { formats, formatPlaceholder, text, start, end } ) { - const newFormats = formats.slice( 0 ); - - newFormats.forEach( ( formatsAtIndex, index ) => { - const lastFormatsAtIndex = newFormats[ index - 1 ]; - - if ( lastFormatsAtIndex ) { - const newFormatsAtIndex = formatsAtIndex.slice( 0 ); - - newFormatsAtIndex.forEach( ( format, formatIndex ) => { - const lastFormat = lastFormatsAtIndex[ formatIndex ]; - - if ( isFormatEqual( format, lastFormat ) ) { - newFormatsAtIndex[ formatIndex ] = lastFormat; - } - } ); - - newFormats[ index ] = newFormatsAtIndex; - } - } ); - - return { formats: newFormats, formatPlaceholder, text, start, end }; -} diff --git a/packages/rich-text/src/outdent-list-items.js b/packages/rich-text/src/outdent-list-items.js index 3a493caa9b03a..19fac90515dfb 100644 --- a/packages/rich-text/src/outdent-list-items.js +++ b/packages/rich-text/src/outdent-list-items.js @@ -3,7 +3,6 @@ */ import { LINE_SEPARATOR } from './special-characters'; -import { normaliseFormats } from './normalise-formats'; import { getLineIndex } from './get-line-index'; import { getParentLineIndex } from './get-parent-line-index'; import { getLastChildIndex } from './get-last-child-index'; @@ -16,16 +15,16 @@ import { getLastChildIndex } from './get-last-child-index'; * @return {Object} The changed value. */ export function outdentListItems( value ) { - const { text, formats, start, end } = value; + const { text, replacements, start, end } = value; const startingLineIndex = getLineIndex( value, start ); // Return early if the starting line index cannot be further outdented. - if ( formats[ startingLineIndex ] === undefined ) { + if ( replacements[ startingLineIndex ] === undefined ) { return value; } - const newFormats = formats.slice( 0 ); - const parentFormats = formats[ getParentLineIndex( value, startingLineIndex ) ] || []; + const newFormats = replacements.slice( 0 ); + const parentFormats = replacements[ getParentLineIndex( value, startingLineIndex ) ] || []; const endingLineIndex = getLineIndex( value, end ); const lastChildIndex = getLastChildIndex( value, endingLineIndex ); @@ -51,10 +50,8 @@ export function outdentListItems( value ) { } } - return normaliseFormats( { - text, - formats: newFormats, - start, - end, - } ); + return { + ...value, + replacements: newFormats, + }; } diff --git a/packages/rich-text/src/remove-format.js b/packages/rich-text/src/remove-format.js index be1c92ace7fd4..405cb6265e1e0 100644 --- a/packages/rich-text/src/remove-format.js +++ b/packages/rich-text/src/remove-format.js @@ -2,7 +2,7 @@ * External dependencies */ -import { find } from 'lodash'; +import { find, reject } from 'lodash'; /** * Internal dependencies @@ -23,28 +23,38 @@ import { normaliseFormats } from './normalise-formats'; * @return {Object} A new value with the format applied. */ export function removeFormat( - { formats, text, start, end }, + value, formatType, - startIndex = start, - endIndex = end + startIndex = value.start, + endIndex = value.end ) { - const newFormats = formats.slice( 0 ); + const newFormats = value.formats.slice( 0 ); // If the selection is collapsed, expand start and end to the edges of the // format. if ( startIndex === endIndex ) { const format = find( newFormats[ startIndex ], { type: formatType } ); - while ( find( newFormats[ startIndex ], format ) ) { - filterFormats( newFormats, startIndex, formatType ); - startIndex--; - } - - endIndex++; + if ( format ) { + while ( find( newFormats[ startIndex ], format ) ) { + filterFormats( newFormats, startIndex, formatType ); + startIndex--; + } - while ( find( newFormats[ endIndex ], format ) ) { - filterFormats( newFormats, endIndex, formatType ); endIndex++; + + while ( find( newFormats[ endIndex ], format ) ) { + filterFormats( newFormats, endIndex, formatType ); + endIndex++; + } + } else { + return { + ...value, + formatPlaceholder: reject( + newFormats[ startIndex - 1 ] || [], + { type: formatType } + ), + }; } } else { for ( let i = startIndex; i < endIndex; i++ ) { @@ -54,7 +64,7 @@ export function removeFormat( } } - return normaliseFormats( { formats: newFormats, text, start, end } ); + return normaliseFormats( { ...value, formats: newFormats } ); } function filterFormats( formats, index, formatType ) { diff --git a/packages/rich-text/src/remove-format.native.js b/packages/rich-text/src/remove-format.native.js index b6213f813bb63..5c20260728b71 100644 --- a/packages/rich-text/src/remove-format.native.js +++ b/packages/rich-text/src/remove-format.native.js @@ -23,11 +23,12 @@ import { normaliseFormats } from './normalise-formats'; * @return {Object} A new value with the format applied. */ export function removeFormat( - { formats, formatPlaceholder, text, start, end }, + value, formatType, - startIndex = start, - endIndex = end + startIndex = value.start, + endIndex = value.end ) { + const { formats, formatPlaceholder, start, end } = value; const newFormats = formats.slice( 0 ); let newFormatPlaceholder = null; @@ -55,7 +56,7 @@ export function removeFormat( } } - return normaliseFormats( { formats: newFormats, formatPlaceholder: newFormatPlaceholder, text, start, end } ); + return normaliseFormats( { ...value, formats: newFormats, formatPlaceholder: newFormatPlaceholder } ); } function filterFormats( formats, index, formatType ) { diff --git a/packages/rich-text/src/replace.js b/packages/rich-text/src/replace.js index 110fc186bd638..0cb26cb7431bd 100644 --- a/packages/rich-text/src/replace.js +++ b/packages/rich-text/src/replace.js @@ -20,11 +20,12 @@ import { normaliseFormats } from './normalise-formats'; * * @return {Object} A new value with replacements applied. */ -export function replace( { formats, text, start, end }, pattern, replacement ) { +export function replace( { formats, replacements, text, start, end }, pattern, replacement ) { text = text.replace( pattern, ( match, ...rest ) => { const offset = rest[ rest.length - 2 ]; let newText = replacement; let newFormats; + let newReplacements; if ( typeof newText === 'function' ) { newText = replacement( match, ...rest ); @@ -32,9 +33,11 @@ export function replace( { formats, text, start, end }, pattern, replacement ) { if ( typeof newText === 'object' ) { newFormats = newText.formats; + newReplacements = newText.replacements; newText = newText.text; } else { newFormats = Array( newText.length ); + newReplacements = Array( newText.length ); if ( formats[ offset ] ) { newFormats = newFormats.fill( formats[ offset ] ); @@ -42,6 +45,7 @@ export function replace( { formats, text, start, end }, pattern, replacement ) { } formats = formats.slice( 0, offset ).concat( newFormats, formats.slice( offset + match.length ) ); + replacements = replacements.slice( 0, offset ).concat( newReplacements, replacements.slice( offset + match.length ) ); if ( start ) { start = end = offset + newText.length; @@ -50,5 +54,5 @@ export function replace( { formats, text, start, end }, pattern, replacement ) { return newText; } ); - return normaliseFormats( { formats, text, start, end } ); + return normaliseFormats( { formats, replacements, text, start, end } ); } diff --git a/packages/rich-text/src/slice.js b/packages/rich-text/src/slice.js index bb4313dd61309..b535a445d45f5 100644 --- a/packages/rich-text/src/slice.js +++ b/packages/rich-text/src/slice.js @@ -10,16 +10,19 @@ * @return {Object} A new extracted value. */ export function slice( - { formats, text, start, end }, - startIndex = start, - endIndex = end + value, + startIndex = value.start, + endIndex = value.end ) { + const { formats, replacements, text } = value; + if ( startIndex === undefined || endIndex === undefined ) { - return { formats, text }; + return { ...value }; } return { formats: formats.slice( startIndex, endIndex ), + replacements: replacements.slice( startIndex, endIndex ), text: text.slice( startIndex, endIndex ), }; } diff --git a/packages/rich-text/src/split.js b/packages/rich-text/src/split.js index a300c3ccbcca4..f79a675b1e1f4 100644 --- a/packages/rich-text/src/split.js +++ b/packages/rich-text/src/split.js @@ -15,7 +15,7 @@ import { replace } from './replace'; * * @return {Array} An array of new values. */ -export function split( { formats, text, start, end }, string ) { +export function split( { formats, replacements, text, start, end }, string ) { if ( typeof string !== 'string' ) { return splitAtSelection( ...arguments ); } @@ -26,6 +26,7 @@ export function split( { formats, text, start, end }, string ) { const startIndex = nextStart; const value = { formats: formats.slice( startIndex, startIndex + substring.length ), + replacements: replacements.slice( startIndex, startIndex + substring.length ), text: substring, }; @@ -50,16 +51,18 @@ export function split( { formats, text, start, end }, string ) { } function splitAtSelection( - { formats, text, start, end }, + { formats, replacements, text, start, end }, startIndex = start, endIndex = end ) { const before = { formats: formats.slice( 0, startIndex ), + replacements: replacements.slice( 0, startIndex ), text: text.slice( 0, startIndex ), }; const after = { formats: formats.slice( endIndex ), + replacements: replacements.slice( endIndex ), text: text.slice( endIndex ), start: 0, end: 0, diff --git a/packages/rich-text/src/test/__snapshots__/to-dom.js.snap b/packages/rich-text/src/test/__snapshots__/to-dom.js.snap index a68cdab1c911e..ae6c0db2d5575 100644 --- a/packages/rich-text/src/test/__snapshots__/to-dom.js.snap +++ b/packages/rich-text/src/test/__snapshots__/to-dom.js.snap @@ -36,6 +36,7 @@ exports[`recordToDom should create a value with formatting with attributes 1`] = exports[`recordToDom should create a value with image object 1`] = ` <body> + <img src="" /> @@ -45,7 +46,10 @@ exports[`recordToDom should create a value with image object 1`] = ` exports[`recordToDom should create a value with image object and formatting 1`] = ` <body> - <em> + <em + data-rich-text-format-boundary="true" + > + <img src="" /> @@ -57,7 +61,10 @@ exports[`recordToDom should create a value with image object and formatting 1`] exports[`recordToDom should create a value with image object and text after 1`] = ` <body> - <em> + <em + data-rich-text-format-boundary="true" + > + <img src="" /> diff --git a/packages/rich-text/src/test/apply-format.js b/packages/rich-text/src/test/apply-format.js index de4f7b69e5f5e..b8cff3bc47620 100644 --- a/packages/rich-text/src/test/apply-format.js +++ b/packages/rich-text/src/test/apply-format.js @@ -61,7 +61,7 @@ describe( 'applyFormat', () => { }; const expected = { ...record, - formatPlaceholder: a2, + formatPlaceholder: [ a2 ], }; const result = applyFormat( deepFreeze( record ), a2 ); diff --git a/packages/rich-text/src/test/change-list-type.js b/packages/rich-text/src/test/change-list-type.js index 3817c66ba7977..6294368c6427d 100644 --- a/packages/rich-text/src/test/change-list-type.js +++ b/packages/rich-text/src/test/change-list-type.js @@ -17,7 +17,7 @@ describe( 'changeListType', () => { it( 'should only change list type if list item is indented', () => { const record = { - formats: [ , ], + replacements: [ , ], text: '1', start: 1, end: 1, @@ -26,27 +26,25 @@ describe( 'changeListType', () => { expect( result ).toEqual( record ); expect( result ).toBe( record ); - expect( getSparseArrayLength( result.formats ) ).toBe( 0 ); + expect( getSparseArrayLength( result.replacements ) ).toBe( 0 ); } ); it( 'should change list type', () => { const record = { - formats: [ , [ ul ] ], + replacements: [ , [ ul ] ], text: `1${ LINE_SEPARATOR }`, start: 2, end: 2, }; const expected = { - formats: [ , [ ol ] ], - text: `1${ LINE_SEPARATOR }`, - start: 2, - end: 2, + ...record, + replacements: [ , [ ol ] ], }; const result = changeListType( deepFreeze( record ), ol ); expect( result ).toEqual( expected ); expect( result ).not.toBe( record ); - expect( getSparseArrayLength( result.formats ) ).toBe( 1 ); + expect( getSparseArrayLength( result.replacements ) ).toBe( 1 ); } ); it( 'should outdent with multiple lines selected', () => { @@ -54,21 +52,19 @@ describe( 'changeListType', () => { const text = `a${ LINE_SEPARATOR }1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }i${ LINE_SEPARATOR }3${ LINE_SEPARATOR }4${ LINE_SEPARATOR }b`; const record = { - formats: [ , [ ul ], , [ ul ], , [ ul, ul ], , [ ul ], , [ ul ], , , , [ ul ], , ], + replacements: [ , [ ul ], , [ ul ], , [ ul, ul ], , [ ul ], , [ ul ], , , , [ ul ], , ], text, start: 4, end: 9, }; const expected = { - formats: [ , [ ol ], , [ ol ], , [ ol, ul ], , [ ol ], , [ ol ], , , , [ ul ], , ], - text, - start: 4, - end: 9, + ...record, + replacements: [ , [ ol ], , [ ol ], , [ ol, ul ], , [ ol ], , [ ol ], , , , [ ul ], , ], }; const result = changeListType( deepFreeze( record ), ol ); expect( result ).toEqual( expected ); expect( result ).not.toBe( record ); - expect( getSparseArrayLength( result.formats ) ).toBe( 6 ); + expect( getSparseArrayLength( result.replacements ) ).toBe( 6 ); } ); } ); diff --git a/packages/rich-text/src/test/concat.js b/packages/rich-text/src/test/concat.js index 9ac2aa2dc7556..ae8253e1a5461 100644 --- a/packages/rich-text/src/test/concat.js +++ b/packages/rich-text/src/test/concat.js @@ -16,14 +16,17 @@ describe( 'concat', () => { it( 'should merge records', () => { const one = { formats: [ , , [ em ] ], + replacements: [ , , , ], text: 'one', }; const two = { formats: [ [ em ], , , ], + replacements: [ , , , ], text: 'two', }; const three = { formats: [ , , [ em ], [ em ], , , ], + replacements: [ , , , , , , ], text: 'onetwo', }; diff --git a/packages/rich-text/src/test/create.js b/packages/rich-text/src/test/create.js index 5128c55273d78..b585d72ec6614 100644 --- a/packages/rich-text/src/test/create.js +++ b/packages/rich-text/src/test/create.js @@ -81,6 +81,7 @@ describe( 'create', () => { expect( value ).toEqual( { formats: [ [ em ], [ em ], [ em, strong ], [ em, strong ] ], + replacements: [ , , , , ], text: 'test', } ); diff --git a/packages/rich-text/src/test/get-active-object.js b/packages/rich-text/src/test/get-active-object.js new file mode 100644 index 0000000000000..35d0f7637c7bc --- /dev/null +++ b/packages/rich-text/src/test/get-active-object.js @@ -0,0 +1,41 @@ +/** + * Internal dependencies + */ + +import { getActiveObject } from '../get-active-object'; +import { OBJECT_REPLACEMENT_CHARACTER } from '../special-characters'; + +describe( 'getActiveObject', () => { + it( 'should return object if selected', () => { + const record = { + replacements: [ { type: 'img' } ], + text: OBJECT_REPLACEMENT_CHARACTER, + start: 0, + end: 1, + }; + + expect( getActiveObject( record ) ).toEqual( { type: 'img' } ); + } ); + + it( 'should return nothing if nothing is selected', () => { + const record = { + replacements: [ { type: 'img' } ], + text: OBJECT_REPLACEMENT_CHARACTER, + start: 0, + end: 0, + }; + + expect( getActiveObject( record ) ).toBe( undefined ); + } ); + + it( 'should return nothing if te selection is not an object', () => { + const record = { + replacements: [ { type: 'em' } ], + text: 'a', + start: 0, + end: 1, + }; + + expect( getActiveObject( record ) ).toBe( undefined ); + } ); +} ); diff --git a/packages/rich-text/src/test/get-last-child-index.js b/packages/rich-text/src/test/get-last-child-index.js index 55c881d356555..f59cec9eeeab1 100644 --- a/packages/rich-text/src/test/get-last-child-index.js +++ b/packages/rich-text/src/test/get-last-child-index.js @@ -10,40 +10,40 @@ import deepFreeze from 'deep-freeze'; import { getLastChildIndex } from '../get-last-child-index'; import { LINE_SEPARATOR } from '../special-characters'; -describe( 'outdentListItems', () => { +describe( 'getLastChildIndex', () => { const ul = { type: 'ul' }; it( 'should return undefined if there is only one line', () => { expect( getLastChildIndex( deepFreeze( { - formats: [ , ], + replacements: [ , ], text: '1', } ), undefined ) ).toBe( undefined ); } ); it( 'should return the last line if no line is indented', () => { expect( getLastChildIndex( deepFreeze( { - formats: [ , ], + replacements: [ , ], text: `1${ LINE_SEPARATOR }`, } ), undefined ) ).toBe( 1 ); } ); it( 'should return the last child index', () => { expect( getLastChildIndex( deepFreeze( { - formats: [ , [ ul ], , [ ul ], , ], + replacements: [ , [ ul ], , [ ul ], , ], text: `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3`, } ), undefined ) ).toBe( 3 ); } ); it( 'should return the last child index by sibling', () => { expect( getLastChildIndex( deepFreeze( { - formats: [ , [ ul ], , [ ul ], , ], + replacements: [ , [ ul ], , [ ul ], , ], text: `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3`, } ), 1 ) ).toBe( 3 ); } ); it( 'should return the last child index (with further lower indented items)', () => { expect( getLastChildIndex( deepFreeze( { - formats: [ , [ ul ], , , , ], + replacements: [ , [ ul ], , , , ], text: `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3`, } ), 1 ) ).toBe( 1 ); } ); diff --git a/packages/rich-text/src/test/get-parent-line-index.js b/packages/rich-text/src/test/get-parent-line-index.js index 4e6a75ffd0a6e..832ee4412dacc 100644 --- a/packages/rich-text/src/test/get-parent-line-index.js +++ b/packages/rich-text/src/test/get-parent-line-index.js @@ -15,28 +15,28 @@ describe( 'getParentLineIndex', () => { it( 'should return undefined if there is only one line', () => { expect( getParentLineIndex( deepFreeze( { - formats: [ , ], + replacements: [ , ], text: '1', } ), undefined ) ).toBe( undefined ); } ); it( 'should return undefined if the list is part of the first root list child', () => { expect( getParentLineIndex( deepFreeze( { - formats: [ , ], + replacements: [ , ], text: `1${ LINE_SEPARATOR }2`, } ), 2 ) ).toBe( undefined ); } ); it( 'should return the line index of the parent list (1)', () => { expect( getParentLineIndex( deepFreeze( { - formats: [ , , , [ ul ], , ], + replacements: [ , , , [ ul ], , ], text: `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3`, } ), 3 ) ).toBe( 1 ); } ); it( 'should return the line index of the parent list (2)', () => { expect( getParentLineIndex( deepFreeze( { - formats: [ , [ ul ], , [ ul, ul ], , [ ul ], , ], + replacements: [ , [ ul ], , [ ul, ul ], , [ ul ], , ], text: `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3${ LINE_SEPARATOR }4`, } ), 5 ) ).toBe( undefined ); } ); diff --git a/packages/rich-text/src/test/helpers/index.js b/packages/rich-text/src/test/helpers/index.js index 859e7e3acefc2..16b09bb19b832 100644 --- a/packages/rich-text/src/test/helpers/index.js +++ b/packages/rich-text/src/test/helpers/index.js @@ -4,7 +4,7 @@ export function getSparseArrayLength( array ) { const em = { type: 'em' }; const strong = { type: 'strong' }; -const img = { type: 'img', attributes: { src: '' }, object: true }; +const img = { type: 'img', attributes: { src: '' } }; const a = { type: 'a', attributes: { href: '#' } }; const ul = { type: 'ul' }; const ol = { type: 'ol' }; @@ -25,6 +25,7 @@ export const spec = [ start: 0, end: 0, formats: [], + replacements: [], text: '', }, }, @@ -43,6 +44,7 @@ export const spec = [ start: 0, end: 1, formats: [ , ], + replacements: [ , ], text: ' ', }, }, @@ -61,6 +63,7 @@ export const spec = [ start: 5, end: 5, formats: [ , , , , , , , , , , ], + replacements: [ , , , , , , , , , , ], text: 'test\u00a0 test', }, }, @@ -79,6 +82,7 @@ export const spec = [ start: 0, end: 0, formats: [], + replacements: [], text: '', }, }, @@ -97,6 +101,7 @@ export const spec = [ start: 0, end: 4, formats: [ , , , , ], + replacements: [ , , , , ], text: 'test', }, }, @@ -115,6 +120,7 @@ export const spec = [ start: 0, end: 2, formats: [ , , ], + replacements: [ , , ], text: '🍒', }, }, @@ -133,6 +139,7 @@ export const spec = [ start: 0, end: 2, formats: [ [ em ], [ em ] ], + replacements: [ , , ], text: '🍒', }, }, @@ -151,6 +158,7 @@ export const spec = [ start: 0, end: 4, formats: [ [ em ], [ em ], [ em ], [ em ] ], + replacements: [ , , , , ], text: 'test', }, }, @@ -169,6 +177,7 @@ export const spec = [ start: 0, end: 4, formats: [ [ em, strong ], [ em, strong ], [ em, strong ], [ em, strong ] ], + replacements: [ , , , , ], text: 'test', }, }, @@ -187,6 +196,7 @@ export const spec = [ start: 0, end: 2, formats: [ [ em ], [ em ], [ em ], [ em ] ], + replacements: [ , , , , ], text: 'test', }, }, @@ -205,6 +215,7 @@ export const spec = [ start: 0, end: 4, formats: [ [ a ], [ a ], [ a ], [ a ] ], + replacements: [ , , , , ], text: 'test', }, }, @@ -217,12 +228,13 @@ export const spec = [ endOffset: 1, endContainer: element, } ), - startPath: [ 1, 0 ], - endPath: [ 1, 0 ], + startPath: [ 0, 0 ], + endPath: [ 0, 0 ], record: { start: 0, end: 0, - formats: [ [ img ] ], + formats: [ , ], + replacements: [ img ], text: '\ufffc', }, }, @@ -235,12 +247,13 @@ export const spec = [ endOffset: 1, endContainer: element.querySelector( 'img' ), } ), - startPath: [ 0, 1, 0 ], - endPath: [ 0, 1, 0 ], + startPath: [ 0, 0, 0 ], + endPath: [ 0, 2, 0 ], record: { start: 0, end: 1, - formats: [ [ em, img ] ], + formats: [ [ em ] ], + replacements: [ img ], text: '\ufffc', }, }, @@ -258,7 +271,8 @@ export const spec = [ record: { start: 0, end: 5, - formats: [ , , [ em ], [ em ], [ em, img ] ], + formats: [ , , [ em ], [ em ], [ em ] ], + replacements: [ , , , , img ], text: 'test\ufffc', }, }, @@ -271,12 +285,13 @@ export const spec = [ endOffset: 2, endContainer: element, } ), - startPath: [ 0, 1, 0 ], + startPath: [ 0, 0, 0 ], endPath: [ 1, 2 ], record: { start: 0, end: 5, - formats: [ [ em, img ], [ em ], [ em ], , , ], + formats: [ [ em ], [ em ], [ em ], , , ], + replacements: [ img, , , , , ], text: '\ufffctest', }, }, @@ -295,6 +310,7 @@ export const spec = [ start: 0, end: 0, formats: [ , ], + replacements: [ , ], text: '\n', }, }, @@ -313,6 +329,7 @@ export const spec = [ start: 2, end: 3, formats: [ , , , , , ], + replacements: [ , , , , , ], text: 'te\nst', }, }, @@ -331,6 +348,7 @@ export const spec = [ start: 0, end: 1, formats: [ [ em ] ], + replacements: [ , ], text: '\n', }, }, @@ -347,6 +365,7 @@ export const spec = [ endPath: [ 4, 0 ], record: { formats: [ , , , , ], + replacements: [ , , , , ], text: 'a\n\nb', start: 2, end: 3, @@ -365,6 +384,7 @@ export const spec = [ endPath: [ 2, 0 ], record: { formats: [ , , , , ], + replacements: [ , , , , ], text: 'a\n\nb', start: 2, end: 2, @@ -386,6 +406,7 @@ export const spec = [ start: 0, end: 0, formats: [], + replacements: [], text: '', }, }, @@ -405,6 +426,7 @@ export const spec = [ start: 1, end: 4, formats: [ , , , , , , , ], + replacements: [ , , , , , , , ], text: 'one\u2028two', }, }, @@ -424,7 +446,8 @@ export const spec = [ record: { start: 0, end: 9, - formats: [ , , , [ ul ], , [ ul ], , [ ul, ol ], , [ ul, ol ], , , , , , , , ], + formats: [ , , , , , , , , , , , , , , , , , ], + replacements: [ , , , [ ul ], , [ ul ], , [ ul, ol ], , [ ul, ol ], , , , , , , , ], text: 'one\u2028a\u2028b\u20281\u20282\u2028three', }, }, @@ -445,6 +468,7 @@ export const spec = [ start: 0, end: 0, formats: [], + replacements: [], text: '', }, }, @@ -464,7 +488,8 @@ export const spec = [ record: { start: 1, end: 1, - formats: [ [ ul ] ], + formats: [ , ], + replacements: [ [ ul ] ], text: '\u2028', }, }, @@ -485,6 +510,7 @@ export const spec = [ start: 1, end: 1, formats: [ , , ], + replacements: [ , , ], text: '\u2028\u2028', }, }, @@ -504,6 +530,7 @@ export const spec = [ start: 4, end: 4, formats: [ , , , , ], + replacements: [ , , , , ], text: 'one\u2028', }, }, @@ -524,6 +551,7 @@ export const spec = [ start: 3, end: 3, formats: [ , , , ], + replacements: [ , , , ], text: 'one', }, }, @@ -534,6 +562,7 @@ export const spec = [ endPath: [], record: { formats: [ [ em ], [ em ], [ em ], [ em ], [ em ], [ em ], [ em ] ], + replacements: [ , , , , , , , ], text: 'one\u2028two', }, }, @@ -552,6 +581,7 @@ export const spec = [ start: 0, end: 0, formats: [], + replacements: [], text: '', }, }, @@ -570,6 +600,7 @@ export const spec = [ start: 0, end: 4, formats: [ [ strong ], [ strong ], [ strong ], [ strong ] ], + replacements: [ , , , , ], text: 'test', }, }, @@ -592,6 +623,7 @@ export const specWithRegistration = [ attributes: {}, unregisteredAttributes: {}, } ] ], + replacements: [ , ], text: 'a', }, }, @@ -613,6 +645,7 @@ export const specWithRegistration = [ class: 'test', }, } ] ], + replacements: [ , ], text: 'a', }, }, @@ -634,6 +667,7 @@ export const specWithRegistration = [ class: 'custom-format', }, } ] ], + replacements: [ , ], text: 'a', }, }, @@ -647,6 +681,7 @@ export const specWithRegistration = [ class: 'custom-format', }, } ] ], + replacements: [ , ], text: 'a', }, }, @@ -663,6 +698,7 @@ export const specWithRegistration = [ html: '<a class="custom-format">a</a>', value: { formats: [ , ], + replacements: [ , ], text: 'a', }, noToHTMLString: true, @@ -685,6 +721,7 @@ export const specWithRegistration = [ attributes: {}, unregisteredAttributes: {}, } ] ], + replacements: [ , ], text: 'a', }, }, diff --git a/packages/rich-text/src/test/indent-list-items.js b/packages/rich-text/src/test/indent-list-items.js index e7f631e5fa8f9..349174247cc42 100644 --- a/packages/rich-text/src/test/indent-list-items.js +++ b/packages/rich-text/src/test/indent-list-items.js @@ -17,7 +17,7 @@ describe( 'indentListItems', () => { it( 'should not indent only item', () => { const record = { - formats: [ , ], + replacements: [ , ], text: '1', start: 1, end: 1, @@ -26,34 +26,32 @@ describe( 'indentListItems', () => { expect( result ).toEqual( record ); expect( result ).toBe( record ); - expect( getSparseArrayLength( result.formats ) ).toBe( 0 ); + expect( getSparseArrayLength( result.replacements ) ).toBe( 0 ); } ); it( 'should indent', () => { // As we're testing list formats, the text should remain the same. const text = `1${ LINE_SEPARATOR }`; const record = { - formats: [ , , ], + replacements: [ , , ], text, start: 2, end: 2, }; const expected = { - formats: [ , [ ul ] ], - text, - start: 2, - end: 2, + ...record, + replacements: [ , [ ul ] ], }; const result = indentListItems( deepFreeze( record ), ul ); expect( result ).toEqual( expected ); expect( result ).not.toBe( record ); - expect( getSparseArrayLength( result.formats ) ).toBe( 1 ); + expect( getSparseArrayLength( result.replacements ) ).toBe( 1 ); } ); it( 'should not indent without target list', () => { const record = { - formats: [ , [ ul ] ], + replacements: [ , [ ul ] ], text: `1${ LINE_SEPARATOR }`, start: 2, end: 2, @@ -62,80 +60,74 @@ describe( 'indentListItems', () => { expect( result ).toEqual( record ); expect( result ).toBe( record ); - expect( getSparseArrayLength( result.formats ) ).toBe( 1 ); + expect( getSparseArrayLength( result.replacements ) ).toBe( 1 ); } ); it( 'should indent and merge with previous list', () => { // As we're testing list formats, the text should remain the same. const text = `1${ LINE_SEPARATOR }${ LINE_SEPARATOR }`; const record = { - formats: [ , [ ol ], , ], + replacements: [ , [ ol ], , ], text, start: 3, end: 3, }; const expected = { - formats: [ , [ ol ], [ ol ] ], - text, - start: 3, - end: 3, + ...record, + replacements: [ , [ ol ], [ ol ] ], }; const result = indentListItems( deepFreeze( record ), ul ); expect( result ).toEqual( expected ); expect( result ).not.toBe( record ); - expect( getSparseArrayLength( result.formats ) ).toBe( 2 ); + expect( getSparseArrayLength( result.replacements ) ).toBe( 2 ); } ); it( 'should indent already indented item', () => { // As we're testing list formats, the text should remain the same. const text = `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3`; const record = { - formats: [ , [ ul ], , [ ul ], , ], + replacements: [ , [ ul ], , [ ul ], , ], text, start: 5, end: 5, }; const expected = { - formats: [ , [ ul ], , [ ul, ul ], , ], - text, - start: 5, - end: 5, + ...record, + replacements: [ , [ ul ], , [ ul, ul ], , ], }; const result = indentListItems( deepFreeze( record ), ul ); expect( result ).toEqual( expected ); expect( result ).not.toBe( record ); - expect( getSparseArrayLength( result.formats ) ).toBe( 2 ); + expect( getSparseArrayLength( result.replacements ) ).toBe( 2 ); } ); it( 'should indent with multiple lines selected', () => { // As we're testing list formats, the text should remain the same. const text = `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3`; const record = { - formats: [ , , , [ ul ], , ], + replacements: [ , , , [ ul ], , ], text, start: 2, end: 5, }; const expected = { - formats: [ , [ ul ], , [ ul, ul ], , ], - text, - start: 2, - end: 5, + ...record, + replacements: [ , [ ul ], , [ ul, ul ], , ], }; const result = indentListItems( deepFreeze( record ), ul ); expect( result ).toEqual( expected ); expect( result ).not.toBe( record ); - expect( getSparseArrayLength( result.formats ) ).toBe( 2 ); + expect( getSparseArrayLength( result.replacements ) ).toBe( 2 ); } ); it( 'should indent one level at a time', () => { // As we're testing list formats, the text should remain the same. const text = `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3${ LINE_SEPARATOR }4`; const record = { - formats: [ , [ ul ], , [ ul, ul ], , , , ], + replacements: [ , [ ul ], , [ ul, ul ], , , , ], text, start: 6, end: 6, @@ -144,34 +136,28 @@ describe( 'indentListItems', () => { const result1 = indentListItems( deepFreeze( record ), ul ); expect( result1 ).not.toBe( record ); - expect( getSparseArrayLength( result1.formats ) ).toBe( 3 ); + expect( getSparseArrayLength( result1.replacements ) ).toBe( 3 ); expect( result1 ).toEqual( { - formats: [ , [ ul ], , [ ul, ul ], , [ ul ], , ], - text, - start: 6, - end: 6, + ...record, + replacements: [ , [ ul ], , [ ul, ul ], , [ ul ], , ], } ); const result2 = indentListItems( deepFreeze( result1 ), ul ); expect( result2 ).not.toBe( result1 ); - expect( getSparseArrayLength( result2.formats ) ).toBe( 3 ); + expect( getSparseArrayLength( result2.replacements ) ).toBe( 3 ); expect( result2 ).toEqual( { - formats: [ , [ ul ], , [ ul, ul ], , [ ul, ul ], , ], - text, - start: 6, - end: 6, + ...record, + replacements: [ , [ ul ], , [ ul, ul ], , [ ul, ul ], , ], } ); const result3 = indentListItems( deepFreeze( result2 ), ul ); expect( result3 ).not.toBe( result2 ); - expect( getSparseArrayLength( result3.formats ) ).toBe( 3 ); + expect( getSparseArrayLength( result3.replacements ) ).toBe( 3 ); expect( result3 ).toEqual( { - formats: [ , [ ul ], , [ ul, ul ], , [ ul, ul, ul ], , ], - text, - start: 6, - end: 6, + ...record, + replacements: [ , [ ul ], , [ ul, ul ], , [ ul, ul, ul ], , ], } ); } ); } ); diff --git a/packages/rich-text/src/test/insert-line-separator.js b/packages/rich-text/src/test/insert-line-separator.js index 398b0a2b8834f..497555cc4a01a 100644 --- a/packages/rich-text/src/test/insert-line-separator.js +++ b/packages/rich-text/src/test/insert-line-separator.js @@ -17,12 +17,14 @@ describe( 'insertLineSeparator', () => { it( 'should insert line separator at end', () => { const value = { formats: [ , ], + replacements: [ , ], text: '1', start: 1, end: 1, }; const expected = { formats: [ , , ], + replacements: [ , , ], text: `1${ LINE_SEPARATOR }`, start: 2, end: 2, @@ -31,18 +33,20 @@ describe( 'insertLineSeparator', () => { expect( result ).not.toBe( value ); expect( result ).toEqual( expected ); - expect( getSparseArrayLength( result.formats ) ).toBe( 0 ); + expect( getSparseArrayLength( result.replacements ) ).toBe( 0 ); } ); it( 'should insert line separator at start', () => { const value = { formats: [ , ], + replacements: [ , ], text: '1', start: 0, end: 0, }; const expected = { formats: [ , , ], + replacements: [ , , ], text: `${ LINE_SEPARATOR }1`, start: 1, end: 1, @@ -51,18 +55,20 @@ describe( 'insertLineSeparator', () => { expect( result ).not.toBe( value ); expect( result ).toEqual( expected ); - expect( getSparseArrayLength( result.formats ) ).toBe( 0 ); + expect( getSparseArrayLength( result.replacements ) ).toBe( 0 ); } ); it( 'should insert line separator with previous line separator formats', () => { const value = { - formats: [ , , , [ ol ], , ], + formats: [ , , , , , ], + replacements: [ , , , [ ol ], , ], text: `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }a`, start: 5, end: 5, }; const expected = { - formats: [ , , , [ ol ], , [ ol ] ], + formats: [ , , , , , , ], + replacements: [ , , , [ ol ], , [ ol ] ], text: `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }a${ LINE_SEPARATOR }`, start: 6, end: 6, @@ -71,18 +77,20 @@ describe( 'insertLineSeparator', () => { expect( result ).not.toBe( value ); expect( result ).toEqual( expected ); - expect( getSparseArrayLength( result.formats ) ).toBe( 2 ); + expect( getSparseArrayLength( result.replacements ) ).toBe( 2 ); } ); it( 'should insert line separator without formats if previous line separator did not have any', () => { const value = { formats: [ , , , , , ], + replacements: [ , , , , , ], text: `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }a`, start: 5, end: 5, }; const expected = { formats: [ , , , , , , ], + replacements: [ , , , , , , ], text: `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }a${ LINE_SEPARATOR }`, start: 6, end: 6, @@ -91,6 +99,6 @@ describe( 'insertLineSeparator', () => { expect( result ).not.toBe( value ); expect( result ).toEqual( expected ); - expect( getSparseArrayLength( result.formats ) ).toBe( 0 ); + expect( getSparseArrayLength( result.replacements ) ).toBe( 0 ); } ); } ); diff --git a/packages/rich-text/src/test/insert-object.js b/packages/rich-text/src/test/insert-object.js index 15680372986a9..7c2ba57806ad0 100644 --- a/packages/rich-text/src/test/insert-object.js +++ b/packages/rich-text/src/test/insert-object.js @@ -17,12 +17,14 @@ describe( 'insert', () => { it( 'should delete and insert', () => { const record = { formats: [ , , , , [ em ], [ em ], [ em ], , , , , , , ], + replacements: [ , , , , , , , , , , , , , ], text: 'one two three', start: 6, end: 6, }; const expected = { - formats: [ , , [ { ...obj, object: true } ], [ em ], , , , , , , ], + formats: [ , , , [ em ], , , , , , , ], + replacements: [ , , obj, , , , , , , , ], text: `on${ OBJECT_REPLACEMENT_CHARACTER }o three`, start: 3, end: 3, @@ -31,6 +33,7 @@ describe( 'insert', () => { expect( result ).toEqual( expected ); expect( result ).not.toBe( record ); - expect( getSparseArrayLength( result.formats ) ).toBe( 2 ); + expect( getSparseArrayLength( result.formats ) ).toBe( 1 ); + expect( getSparseArrayLength( result.replacements ) ).toBe( 1 ); } ); } ); diff --git a/packages/rich-text/src/test/insert.js b/packages/rich-text/src/test/insert.js index 64de9f44828a2..cae4cc5b85d5f 100644 --- a/packages/rich-text/src/test/insert.js +++ b/packages/rich-text/src/test/insert.js @@ -17,16 +17,19 @@ describe( 'insert', () => { it( 'should delete and insert', () => { const record = { formats: [ , , , , [ em ], [ em ], [ em ], , , , , , , ], + replacements: [], text: 'one two three', start: 6, end: 6, }; const toInsert = { formats: [ [ strong ] ], + replacements: [], text: 'a', }; const expected = { formats: [ , , [ strong ], [ em ], , , , , , , ], + replacements: [], text: 'onao three', start: 3, end: 3, @@ -41,16 +44,19 @@ describe( 'insert', () => { it( 'should insert line break with selection', () => { const record = { formats: [ , , ], + replacements: [], text: 'tt', start: 1, end: 1, }; const toInsert = { formats: [ , ], + replacements: [], text: '\n', }; const expected = { formats: [ , , , ], + replacements: [], text: 't\nt', start: 2, end: 2, diff --git a/packages/rich-text/src/test/join.js b/packages/rich-text/src/test/join.js index fb2f20b1b2784..84801125cc2b0 100644 --- a/packages/rich-text/src/test/join.js +++ b/packages/rich-text/src/test/join.js @@ -15,8 +15,9 @@ describe( 'join', () => { const separators = [ ' ', { - text: ' ', formats: [ , ], + replacements: [ , ], + text: ' ', }, ]; @@ -24,14 +25,17 @@ describe( 'join', () => { it( 'should join records with string separator', () => { const one = { formats: [ , , [ em ] ], + replacements: [ , , , ], text: 'one', }; const two = { formats: [ [ em ], , , ], + replacements: [ , , , ], text: 'two', }; const three = { formats: [ , , [ em ], , [ em ], , , ], + replacements: [ , , , , , , , ], text: 'one two', }; const result = join( [ deepFreeze( one ), deepFreeze( two ) ], separator ); diff --git a/packages/rich-text/src/test/outdent-list-items.js b/packages/rich-text/src/test/outdent-list-items.js index c2bf8b30e4766..e4325dfb61c90 100644 --- a/packages/rich-text/src/test/outdent-list-items.js +++ b/packages/rich-text/src/test/outdent-list-items.js @@ -16,7 +16,7 @@ describe( 'outdentListItems', () => { it( 'should not outdent only item', () => { const record = { - formats: [ , ], + replacements: [ , ], text: '1', start: 1, end: 1, @@ -25,138 +25,126 @@ describe( 'outdentListItems', () => { expect( result ).toEqual( record ); expect( result ).toBe( record ); - expect( getSparseArrayLength( result.formats ) ).toBe( 0 ); + expect( getSparseArrayLength( result.replacements ) ).toBe( 0 ); } ); it( 'should indent', () => { // As we're testing list formats, the text should remain the same. const text = `1${ LINE_SEPARATOR }`; const record = { - formats: [ , [ ul ] ], + replacements: [ , [ ul ] ], text, start: 2, end: 2, }; const expected = { - formats: [ , , ], - text, - start: 2, - end: 2, + ...record, + replacements: [ , , ], }; const result = outdentListItems( deepFreeze( record ) ); expect( result ).toEqual( expected ); expect( result ).not.toBe( record ); - expect( getSparseArrayLength( result.formats ) ).toBe( 0 ); + expect( getSparseArrayLength( result.replacements ) ).toBe( 0 ); } ); it( 'should outdent two levels deep', () => { // As we're testing list formats, the text should remain the same. const text = `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3`; const record = { - formats: [ , [ ul ], , [ ul, ul ], , ], + replacements: [ , [ ul ], , [ ul, ul ], , ], text, start: 5, end: 5, }; const expected = { - formats: [ , [ ul ], , [ ul ], , ], - text, - start: 5, - end: 5, + ...record, + replacements: [ , [ ul ], , [ ul ], , ], }; const result = outdentListItems( deepFreeze( record ) ); expect( result ).toEqual( expected ); expect( result ).not.toBe( record ); - expect( getSparseArrayLength( result.formats ) ).toBe( 2 ); + expect( getSparseArrayLength( result.replacements ) ).toBe( 2 ); } ); it( 'should outdent with multiple lines selected', () => { // As we're testing list formats, the text should remain the same. const text = `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3`; const record = { - formats: [ , [ ul ], , [ ul, ul ], , ], + replacements: [ , [ ul ], , [ ul, ul ], , ], text, start: 2, end: 5, }; const expected = { - formats: [ , , , [ ul ], , ], - text, - start: 2, - end: 5, + ...record, + replacements: [ , , , [ ul ], , ], }; const result = outdentListItems( deepFreeze( record ) ); expect( result ).toEqual( expected ); expect( result ).not.toBe( record ); - expect( getSparseArrayLength( result.formats ) ).toBe( 1 ); + expect( getSparseArrayLength( result.replacements ) ).toBe( 1 ); } ); it( 'should outdent list item with children', () => { // As we're testing list formats, the text should remain the same. const text = `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3${ LINE_SEPARATOR }4`; const record = { - formats: [ , [ ul ], , [ ul, ul ], , [ ul, ul ], , ], + replacements: [ , [ ul ], , [ ul, ul ], , [ ul, ul ], , ], text, start: 2, end: 2, }; const expected = { - formats: [ , , , [ ul ], , [ ul ], , ], - text, - start: 2, - end: 2, + ...record, + replacements: [ , , , [ ul ], , [ ul ], , ], }; const result = outdentListItems( deepFreeze( record ) ); expect( result ).toEqual( expected ); expect( result ).not.toBe( record ); - expect( getSparseArrayLength( result.formats ) ).toBe( 2 ); + expect( getSparseArrayLength( result.replacements ) ).toBe( 2 ); } ); it( 'should outdent list based on parent list', () => { // As we're testing list formats, the text should remain the same. const text = `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3${ LINE_SEPARATOR }4`; const record = { - formats: [ , [ ul ], , [ ul, ul ], , [ ul ], , ], + replacements: [ , [ ul ], , [ ul, ul ], , [ ul ], , ], text, start: 6, end: 6, }; const expected = { - formats: [ , [ ul ], , [ ul, ul ], , , , ], - text, - start: 6, - end: 6, + ...record, + replacements: [ , [ ul ], , [ ul, ul ], , , , ], }; const result = outdentListItems( deepFreeze( record ) ); expect( result ).toEqual( expected ); expect( result ).not.toBe( record ); - expect( getSparseArrayLength( result.formats ) ).toBe( 2 ); + expect( getSparseArrayLength( result.replacements ) ).toBe( 2 ); } ); it( 'should outdent when a selected item is at level 0', () => { // As we're testing list formats, the text should remain the same. const text = `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3`; const record = { - formats: [ , [ ul ], , , , ], + replacements: [ , [ ul ], , , , ], text, start: 2, end: 5, }; const expected = { - formats: [ , , , , , ], - text, - start: 2, - end: 5, + ...record, + replacements: [ , , , , , ], }; const result = outdentListItems( deepFreeze( record ) ); expect( result ).toEqual( expected ); expect( result ).not.toBe( record ); - expect( getSparseArrayLength( result.formats ) ).toBe( 0 ); + expect( getSparseArrayLength( result.replacements ) ).toBe( 0 ); } ); } ); diff --git a/packages/rich-text/src/test/replace.js b/packages/rich-text/src/test/replace.js index f3c7d9aa923e9..6cfd4dcc9e48b 100644 --- a/packages/rich-text/src/test/replace.js +++ b/packages/rich-text/src/test/replace.js @@ -16,12 +16,14 @@ describe( 'replace', () => { it( 'should replace string to string', () => { const record = { formats: [ , , , , [ em ], [ em ], [ em ], , , , , , , ], + replacements: [ , , , , , , , , , , , , , ], text: 'one two three', start: 6, end: 6, }; const expected = { formats: [ , , , , [ em ], , , , , , , ], + replacements: [ , , , , , , , , , , , ], text: 'one 2 three', start: 5, end: 5, @@ -36,16 +38,19 @@ describe( 'replace', () => { it( 'should replace string to record', () => { const record = { formats: [ , , , , [ em ], [ em ], [ em ], , , , , , , ], + replacements: [ , , , , , , , , , , , , , ], text: 'one two three', start: 6, end: 6, }; const replacement = { formats: [ , ], + replacements: [ , ], text: '2', }; const expected = { formats: [ , , , , , , , , , , , ], + replacements: [ , , , , , , , , , , , ], text: 'one 2 three', start: 5, end: 5, @@ -60,12 +65,14 @@ describe( 'replace', () => { it( 'should replace string to function', () => { const record = { formats: [ , , , , , , , , , , , , ], + replacements: [ , , , , , , , , , , , , ], text: 'abc12345#$*%', start: 6, end: 6, }; const expected = { formats: [ , , , , , , , , , , , , , , , , , , ], + replacements: [ , , , , , , , , , , , , , , , , , , ], text: 'abc - 12345 - #$*%', start: 18, end: 18, diff --git a/packages/rich-text/src/test/slice.js b/packages/rich-text/src/test/slice.js index 9d40f7a6de037..b181e5e9be817 100644 --- a/packages/rich-text/src/test/slice.js +++ b/packages/rich-text/src/test/slice.js @@ -16,10 +16,12 @@ describe( 'slice', () => { it( 'should slice', () => { const record = { formats: [ , , , , [ em ], [ em ], [ em ], , , , , , , ], + replacements: [ , , , , , , , , , , , , , ], text: 'one two three', }; const expected = { formats: [ , [ em ], [ em ] ], + replacements: [ , , , ], text: ' tw', }; const result = slice( deepFreeze( record ), 3, 6 ); @@ -32,12 +34,14 @@ describe( 'slice', () => { it( 'should slice record', () => { const record = { formats: [ , , , , [ em ], [ em ], [ em ], , , , , , , ], + replacements: [ , , , , , , , , , , , , , ], text: 'one two three', start: 3, end: 6, }; const expected = { formats: [ , [ em ], [ em ] ], + replacements: [ , , , ], text: ' tw', }; const result = slice( deepFreeze( record ) ); diff --git a/packages/rich-text/src/test/split.js b/packages/rich-text/src/test/split.js index 1cdfbae9630d2..8eef998e488d6 100644 --- a/packages/rich-text/src/test/split.js +++ b/packages/rich-text/src/test/split.js @@ -18,17 +18,20 @@ describe( 'split', () => { start: 5, end: 10, formats: [ , , , , [ em ], [ em ], [ em ], , , , , , , ], + replacements: [ , , , , , , , , , , , , , ], text: 'one two three', }; const expected = [ { formats: [ , , , , [ em ], [ em ] ], + replacements: [ , , , , , , ], text: 'one tw', }, { start: 0, end: 0, formats: [ [ em ], , , , , , , ], + replacements: [ , , , , , , , ], text: 'o three', }, ]; @@ -45,6 +48,7 @@ describe( 'split', () => { it( 'should split with selection', () => { const record = { formats: [ , , , , [ em ], [ em ], [ em ], , , , , , , ], + replacements: [ , , , , , , , , , , , , , ], text: 'one two three', start: 6, end: 6, @@ -52,10 +56,12 @@ describe( 'split', () => { const expected = [ { formats: [ , , , , [ em ], [ em ] ], + replacements: [ , , , , , , ], text: 'one tw', }, { formats: [ [ em ], , , , , , , ], + replacements: [ , , , , , , , ], text: 'o three', start: 0, end: 0, @@ -74,6 +80,7 @@ describe( 'split', () => { it( 'should split empty', () => { const record = { formats: [], + replacements: [], text: '', start: 0, end: 0, @@ -81,10 +88,12 @@ describe( 'split', () => { const expected = [ { formats: [], + replacements: [], text: '', }, { formats: [], + replacements: [], text: '', start: 0, end: 0, @@ -103,6 +112,7 @@ describe( 'split', () => { it( 'should split multiline', () => { const record = { formats: [ , , , , , , , , , , ], + replacements: [ , , , , , , , , , , ], text: 'test\u2028\u2028test', start: 5, end: 5, @@ -110,10 +120,12 @@ describe( 'split', () => { const expected = [ { formats: [ , , , , ], + replacements: [ , , , , ], text: 'test', }, { formats: [ , , , , ], + replacements: [ , , , , ], text: 'test', start: 0, end: 0, @@ -134,33 +146,39 @@ describe( 'split', () => { start: 6, end: 16, formats: [ , , , , [ em ], [ em ], [ em ], , , , , , , , , , , , , , , , , ], + replacements: [ , , , , , , , , , , , , , , , , , , , , , , , ], text: 'one two three four five', }; const expected = [ { formats: [ , , , ], + replacements: [ , , , ], text: 'one', }, { start: 2, end: 3, formats: [ [ em ], [ em ], [ em ] ], + replacements: [ , , , ], text: 'two', }, { start: 0, end: 5, formats: [ , , , , , ], + replacements: [ , , , , , ], text: 'three', }, { start: 0, end: 2, formats: [ , , , , ], + replacements: [ , , , , ], text: 'four', }, { formats: [ , , , , ], + replacements: [ , , , , ], text: 'five', }, ]; @@ -179,21 +197,25 @@ describe( 'split', () => { start: 5, end: 6, formats: [ , , , , [ em ], [ em ], [ em ], , , , , , , ], + replacements: [ , , , , , , , , , , , , , ], text: 'one two three', }; const expected = [ { formats: [ , , , ], + replacements: [ , , , ], text: 'one', }, { start: 1, end: 2, formats: [ [ em ], [ em ], [ em ] ], + replacements: [ , , , ], text: 'two', }, { formats: [ , , , , , ], + replacements: [ , , , , , ], text: 'three', }, ]; diff --git a/packages/rich-text/src/test/to-dom.js b/packages/rich-text/src/test/to-dom.js index 1df1e8e25b229..8db8a52e1b443 100644 --- a/packages/rich-text/src/test/to-dom.js +++ b/packages/rich-text/src/test/to-dom.js @@ -24,7 +24,6 @@ describe( 'recordToDom', () => { spec.forEach( ( { description, multilineTag, - multilineWrapperTags, record, startPath, endPath, @@ -33,7 +32,6 @@ describe( 'recordToDom', () => { const { body, selection } = toDom( { value: record, multilineTag, - multilineWrapperTags, } ); expect( body ).toMatchSnapshot(); expect( selection ).toEqual( { startPath, endPath } ); diff --git a/packages/rich-text/src/to-dom.js b/packages/rich-text/src/to-dom.js index b81c518468444..9ebe8365a25ba 100644 --- a/packages/rich-text/src/to-dom.js +++ b/packages/rich-text/src/to-dom.js @@ -122,7 +122,6 @@ function prepareFormats( prepareEditableTree = [], value ) { export function toDom( { value, multilineTag, - multilineWrapperTags, prepareEditableTree, isEditableTree = true, } ) { @@ -135,7 +134,6 @@ export function toDom( { formats: prepareFormats( prepareEditableTree, value ), }, multilineTag, - multilineWrapperTags, createEmpty, append, getLastChild, @@ -174,7 +172,6 @@ export function apply( { value, current, multilineTag, - multilineWrapperTags, prepareEditableTree, __unstableDomOnly, } ) { @@ -182,7 +179,6 @@ export function apply( { const { body, selection } = toDom( { value, multilineTag, - multilineWrapperTags, prepareEditableTree, } ); diff --git a/packages/rich-text/src/to-html-string.js b/packages/rich-text/src/to-html-string.js index 0ba36e62510a3..bf6c60fda0442 100644 --- a/packages/rich-text/src/to-html-string.js +++ b/packages/rich-text/src/to-html-string.js @@ -21,16 +21,13 @@ import { toTree } from './to-tree'; * @param {Object} $1 Named argements. * @param {Object} $1.value Rich text value. * @param {string} [$1.multilineTag] Multiline tag. - * @param {Array} [$1.multilineWrapperTags] Tags where lines can be found if - * nesting is possible. * * @return {string} HTML string. */ -export function toHTMLString( { value, multilineTag, multilineWrapperTags } ) { +export function toHTMLString( { value, multilineTag } ) { const tree = toTree( { value, multilineTag, - multilineWrapperTags, createEmpty, append, getLastChild, diff --git a/packages/rich-text/src/to-tree.js b/packages/rich-text/src/to-tree.js index c89bdc94a2d06..ef15fe7524b6e 100644 --- a/packages/rich-text/src/to-tree.js +++ b/packages/rich-text/src/to-tree.js @@ -91,7 +91,6 @@ const padding = { export function toTree( { value, multilineTag, - multilineWrapperTags = [], createEmpty, append, getLastChild, @@ -104,7 +103,7 @@ export function toTree( { onEndIndex, isEditableTree, } ) { - const { formats, text, start, end } = value; + const { formats, replacements, text, start, end } = value; const formatsLength = formats.length + 1; const tree = createEmpty(); const multilineFormat = { type: multilineTag }; @@ -138,12 +137,8 @@ export function toTree( { // Set multiline tags in queue for building the tree. if ( multilineTag ) { if ( character === LINE_SEPARATOR ) { - characterFormats = lastSeparatorFormats = ( characterFormats || [] ).reduce( ( accumulator, format ) => { - if ( character === LINE_SEPARATOR && multilineWrapperTags.indexOf( format.type ) !== -1 ) { - accumulator.push( format ); - accumulator.push( multilineFormat ); - } - + characterFormats = lastSeparatorFormats = ( replacements[ i ] || [] ).reduce( ( accumulator, format ) => { + accumulator.push( format, multilineFormat ); return accumulator; }, [ multilineFormat ] ); } else { @@ -196,11 +191,10 @@ export function toTree( { return; } - const { type, attributes, unregisteredAttributes, object } = format; + const { type, attributes, unregisteredAttributes } = format; const boundaryClass = ( isEditableTree && - ! object && character !== LINE_SEPARATOR && format === deepestActiveFormat ); @@ -210,7 +204,6 @@ export function toTree( { type, attributes, unregisteredAttributes, - object, boundaryClass, } ) ); @@ -218,7 +211,7 @@ export function toTree( { remove( pointer ); } - pointer = append( format.object ? parent : newNode, '' ); + pointer = append( newNode, '' ); } ); } @@ -240,22 +233,27 @@ export function toTree( { } } - if ( character !== OBJECT_REPLACEMENT_CHARACTER ) { - if ( character === '\n' ) { - pointer = append( getParent( pointer ), { - type: 'br', - attributes: isEditableTree ? { - 'data-rich-text-line-break': 'true', - } : undefined, - object: true, - } ); - // Ensure pointer is text node. - pointer = append( getParent( pointer ), '' ); - } else if ( ! isText( pointer ) ) { - pointer = append( getParent( pointer ), character ); - } else { - appendText( pointer, character ); - } + if ( character === OBJECT_REPLACEMENT_CHARACTER ) { + pointer = append( getParent( pointer ), fromFormat( { + ...replacements[ i ], + object: true, + } ) ); + // Ensure pointer is text node. + pointer = append( getParent( pointer ), '' ); + } else if ( character === '\n' ) { + pointer = append( getParent( pointer ), { + type: 'br', + attributes: isEditableTree ? { + 'data-rich-text-line-break': 'true', + } : undefined, + object: true, + } ); + // Ensure pointer is text node. + pointer = append( getParent( pointer ), '' ); + } else if ( ! isText( pointer ) ) { + pointer = append( getParent( pointer ), character ); + } else { + appendText( pointer, character ); } if ( onStartIndex && start === i + 1 ) { diff --git a/packages/rich-text/src/toggle-format.js b/packages/rich-text/src/toggle-format.js index 6e7854dcaa662..7545f5d30c9d6 100644 --- a/packages/rich-text/src/toggle-format.js +++ b/packages/rich-text/src/toggle-format.js @@ -14,10 +14,7 @@ import { applyFormat } from './apply-format'; * * @return {Object} A new value with the format applied or removed. */ -export function toggleFormat( - value, - format -) { +export function toggleFormat( value, format ) { if ( getActiveFormat( value, format.type ) ) { return removeFormat( value, format.type ); } From 08da64b407e95683946b34cf6ebca5d52ac28227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <iseulde@automattic.com> Date: Wed, 13 Mar 2019 13:31:27 +0100 Subject: [PATCH 644/691] RichText: try alternative list shortcuts (to tab) (#14343) * RichText: try alternative list shortcuts * Try tooltips * Change tooltips to use text * Add inline comments * Add e2e test * Rebase --- .../src/components/rich-text/index.js | 79 +++++++++++++++---- .../src/components/rich-text/list-edit.js | 4 +- .../blocks/__snapshots__/list.test.js.snap | 38 +++++++++ packages/e2e-tests/specs/blocks/list.test.js | 42 ++++++++++ 4 files changed, 145 insertions(+), 18 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 1d714ba9844d3..92afb3799eb90 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -19,7 +19,7 @@ import memize from 'memize'; import { Component, Fragment, RawHTML } from '@wordpress/element'; import { isHorizontalEdge } from '@wordpress/dom'; import { createBlobURL } from '@wordpress/blob'; -import { BACKSPACE, DELETE, ENTER, LEFT, RIGHT } from '@wordpress/keycodes'; +import { BACKSPACE, DELETE, ENTER, LEFT, RIGHT, SPACE } from '@wordpress/keycodes'; import { withDispatch, withSelect } from '@wordpress/data'; import { pasteHandler, children, getBlockTransforms, findTransform } from '@wordpress/blocks'; import { withInstanceId, withSafeTimeout, compose } from '@wordpress/compose'; @@ -37,13 +37,11 @@ import { insertLineSeparator, isEmptyLine, unstableToDom, - getSelectionStart, - getSelectionEnd, remove, removeFormat, isCollapsed, LINE_SEPARATOR, - charAt, + indentListItems, } from '@wordpress/rich-text'; import { decodeEntities } from '@wordpress/html-entities'; import { withFilters, IsolatedEventContainer } from '@wordpress/components'; @@ -599,10 +597,26 @@ export class RichText extends Component { this.handleHorizontalNavigation( event ); } + // Use the space key in list items (at the start of an item) to indent + // the list item. + if ( keyCode === SPACE && this.multilineTag === 'li' ) { + const value = this.createRecord(); + + if ( isCollapsed( value ) ) { + const { text, start } = value; + const characterBefore = text[ start - 1 ]; + + // The caret must be at the start of a line. + if ( ! characterBefore || characterBefore === LINE_SEPARATOR ) { + this.onChange( indentListItems( value, { type: this.props.tagName } ) ); + event.preventDefault(); + } + } + } + if ( keyCode === DELETE || keyCode === BACKSPACE ) { const value = this.createRecord(); - const start = getSelectionStart( value ); - const end = getSelectionEnd( value ); + const { replacements, text, start, end } = value; // Always handle full content deletion ourselves. if ( start === 0 && end !== 0 && end === value.text.length ) { @@ -615,22 +629,53 @@ export class RichText extends Component { let newValue; if ( keyCode === BACKSPACE ) { - if ( charAt( value, start - 1 ) === LINE_SEPARATOR ) { + const index = start - 1; + + if ( text[ index ] === LINE_SEPARATOR ) { + const collapsed = isCollapsed( value ); + + // If the line separator that is about te be removed + // contains wrappers, remove the wrappers first. + if ( collapsed && replacements[ index ] && replacements[ index ].length ) { + const newReplacements = replacements.slice(); + + newReplacements[ index ] = replacements[ index ].slice( 0, -1 ); + newValue = { + ...value, + replacements: newReplacements, + }; + } else { + newValue = remove( + value, + // Only remove the line if the selection is + // collapsed, otherwise remove the selection. + collapsed ? start - 1 : start, + end + ); + } + } + } else if ( text[ end ] === LINE_SEPARATOR ) { + const collapsed = isCollapsed( value ); + + // If the line separator that is about te be removed + // contains wrappers, remove the wrappers first. + if ( collapsed && replacements[ end ] && replacements[ end ].length ) { + const newReplacements = replacements.slice(); + + newReplacements[ end ] = replacements[ end ].slice( 0, -1 ); + newValue = { + ...value, + replacements: newReplacements, + }; + } else { newValue = remove( value, + start, // Only remove the line if the selection is - // collapsed. - isCollapsed( value ) ? start - 1 : start, - end + // collapsed, otherwise remove the selection. + collapsed ? end + 1 : end, ); } - } else if ( charAt( value, end ) === LINE_SEPARATOR ) { - newValue = remove( - value, - start, - // Only remove the line if the selection is collapsed. - isCollapsed( value ) ? end + 1 : end, - ); } if ( newValue ) { diff --git a/packages/block-editor/src/components/rich-text/list-edit.js b/packages/block-editor/src/components/rich-text/list-edit.js index 81de868f62719..aee85983258df 100644 --- a/packages/block-editor/src/components/rich-text/list-edit.js +++ b/packages/block-editor/src/components/rich-text/list-edit.js @@ -3,7 +3,7 @@ */ import { Toolbar } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; +import { __, _x } from '@wordpress/i18n'; import { Fragment } from '@wordpress/element'; import { indentListItems, @@ -149,6 +149,7 @@ export const ListEdit = ( { { icon: 'editor-outdent', title: __( 'Outdent list item' ), + shortcut: _x( 'Backspace', 'keyboard key' ), onClick: () => { onChange( outdentListItems( value ) ); }, @@ -156,6 +157,7 @@ export const ListEdit = ( { { icon: 'editor-indent', title: __( 'Indent list item' ), + shortcut: _x( 'Space', 'keyboard key' ), onClick: () => { onChange( indentListItems( value, { type: tagName } ) ); }, diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap b/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap index b2e8ec4875aac..4d473f282a17a 100644 --- a/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap +++ b/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap @@ -98,6 +98,44 @@ exports[`List should change the indented list type 1`] = ` <!-- /wp:list -->" `; +exports[`List should create and remove indented list with keyboard only 1`] = ` +"<!-- wp:list --> +<ul><li>1<ul><li>a<ul><li>i</li></ul></li></ul></li></ul> +<!-- /wp:list -->" +`; + +exports[`List should create and remove indented list with keyboard only 2`] = ` +"<!-- wp:list --> +<ul><li>1<ul><li>a</li><li></li></ul></li></ul> +<!-- /wp:list -->" +`; + +exports[`List should create and remove indented list with keyboard only 3`] = ` +"<!-- wp:list --> +<ul><li>1<ul><li>a</li></ul></li><li></li></ul> +<!-- /wp:list -->" +`; + +exports[`List should create and remove indented list with keyboard only 4`] = ` +"<!-- wp:list --> +<ul><li>1<ul><li>a</li></ul></li></ul> +<!-- /wp:list -->" +`; + +exports[`List should create and remove indented list with keyboard only 5`] = ` +"<!-- wp:list --> +<ul><li>1</li><li></li></ul> +<!-- /wp:list -->" +`; + +exports[`List should create and remove indented list with keyboard only 6`] = ` +"<!-- wp:list --> +<ul><li>1</li></ul> +<!-- /wp:list -->" +`; + +exports[`List should create and remove indented list with keyboard only 7`] = `""`; + exports[`List should create paragraph on split at end and merge back with content 1`] = ` "<!-- wp:list --> <ul><li>one</li></ul> diff --git a/packages/e2e-tests/specs/blocks/list.test.js b/packages/e2e-tests/specs/blocks/list.test.js index 23d02e0f10cb7..2bbc038627885 100644 --- a/packages/e2e-tests/specs/blocks/list.test.js +++ b/packages/e2e-tests/specs/blocks/list.test.js @@ -304,4 +304,46 @@ describe( 'List', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + + it( 'should create and remove indented list with keyboard only', async () => { + await clickBlockAppender(); + + await page.keyboard.type( '* 1' ); // Should be at level 0. + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( ' a' ); // Should be at level 1. + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( ' i' ); // Should be at level 2. + + expect( await getEditedPostContent() ).toMatchSnapshot(); + + await page.keyboard.press( 'Backspace' ); + await page.keyboard.press( 'Backspace' ); // Should be at level 1. + + expect( await getEditedPostContent() ).toMatchSnapshot(); + + await page.keyboard.press( 'Backspace' ); // Should be at level 0. + + expect( await getEditedPostContent() ).toMatchSnapshot(); + + await page.keyboard.press( 'Backspace' ); // Should be at level 1. + + expect( await getEditedPostContent() ).toMatchSnapshot(); + + await page.keyboard.press( 'Backspace' ); + await page.keyboard.press( 'Backspace' ); // Should be at level 0. + + expect( await getEditedPostContent() ).toMatchSnapshot(); + + await page.keyboard.press( 'Backspace' ); // Should be at level 0. + + expect( await getEditedPostContent() ).toMatchSnapshot(); + + await page.keyboard.press( 'Backspace' ); + await page.keyboard.press( 'Backspace' ); // Should remove list. + + expect( await getEditedPostContent() ).toMatchSnapshot(); + + // That's 9 key presses to create the list, and 9 key presses to remove + // the list. ;) + } ); } ); From d7a4dfa1d45aa417281e65cf4455316e59921143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Wed, 13 Mar 2019 15:32:50 +0100 Subject: [PATCH 645/691] Fix source map paths published to npm (#14409) --- bin/packages/build.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/packages/build.js b/bin/packages/build.js index 02009c1e4a4d3..8a13f8f10b048 100755 --- a/bin/packages/build.js +++ b/bin/packages/build.js @@ -166,7 +166,7 @@ function buildJsFileFor( file, silent, environment ) { const destPath = getBuildPath( file, buildDir ); const babelOptions = getBabelConfig( environment ); babelOptions.sourceMaps = true; - babelOptions.sourceFileName = file; + babelOptions.sourceFileName = file.replace( PACKAGES_DIR, '@wordpress' ); mkdirp.sync( path.dirname( destPath ) ); const transformed = babel.transformFileSync( file, babelOptions ); From 1e541fb4ceacbabc3ca8aa6d7a3c95532891d09b Mon Sep 17 00:00:00 2001 From: Dave Whitley <drw158@gmail.com> Date: Wed, 13 Mar 2019 10:05:16 -0500 Subject: [PATCH 646/691] Components: update Button readme to add design guidelines (#14194) These changes add design documentation. Co-Authored-By: kjellr <kjell@kjellr.com> --- packages/components/src/button/README.md | 123 ++++++++++++++++++++--- 1 file changed, 110 insertions(+), 13 deletions(-) diff --git a/packages/components/src/button/README.md b/packages/components/src/button/README.md index 7f7667004b7de..53b857f00c434 100644 --- a/packages/components/src/button/README.md +++ b/packages/components/src/button/README.md @@ -1,29 +1,126 @@ # Button +Buttons let users take actions and make choices with a single click or tap. -Buttons express what action will occur when the user clicks or taps it. Buttons are used to trigger an action, and they can be used for any type of action, including navigation. +![Button components](https://make.wordpress.org/design/files/2019/03/button.png) -The presence of a `href` prop determines whether an `anchor` element is rendered instead of a `button`. +## Table of contents + +1. [Design guidelines](#design-guidelines) +2. [Development guidelines](#development-guidelines) +3. [Related components](#related-components) + +## Design guidelines + +### Usage + +Buttons tell users what actions they can take and give them a way to interact with the interface. You’ll find them throughout a UI, particularly in places like: + +- Modals +- Forms +- Toolbars + +### Best practices + +Buttons should: + +- **Be clearly and accurately labeled.** +- **Clearly communicate that clicking or tapping will trigger an action.** +- **Use established colors appropriately.** For example, only use red buttons for actions that are difficult or impossible to undo. +- **Prioritize the most important actions.** This helps users focus. Too many calls to action on one screen can be confusing, making users unsure what to do next. +- **Have consistent locations in the interface.** + +### Content guidelines + +Buttons should be clear and predictable—users should be able to anticipate what will happen when they click a button. Never deceive a user by mislabeling a button. + +Buttons text should lead with a strong verb that encourages action, and add a noun that clarifies what will actually change. The only exceptions are common actions like Save, Close, Cancel, or OK. Otherwise, use the {verb}+{noun} format to ensure that your button gives the user enough information. + +Button text should also be quickly scannable — avoid unnecessary words and articles like the, an, or a. + +### Types + +#### Link button + +Link buttons have low emphasis. They don’t stand out much on the page, so they’re used for less-important actions. What’s less important can vary based on context, but it’s usually a supplementary action to the main action we want someone to take. Link buttons are also useful when you don’t want to distract from the content. + +![Link button](https://make.wordpress.org/design/files/2019/03/link-button.png) + +#### Default button + +Default buttons have medium emphasis. The button appearance helps differentiate them from the page background, so they’re useful when you want more emphasis than a link button offers. + +![Default button](https://make.wordpress.org/design/files/2019/03/default-button.png) + +#### Primary button + +Primary buttons have high emphasis. Their color fill and shadow means they pop off the background. + +Since a high-emphasis button commands the most attention, a layout should contain a single primary button. This makes it clear that other buttons have less importance and helps users understand when an action requires their attention. -Note that this component may sometimes be confused with the Button block, which has semantically different use cases and functionality. +![Primary button](https://make.wordpress.org/design/files/2019/03/primary-button.png) -## Usage +#### Text label + +All button types use text labels to describe the action that happens when a user taps a button. If there’s no text label, there should be an icon to signify what the button does (e.g. an IconButton component). + +![](https://make.wordpress.org/design/files/2019/03/do-link-button.png) + +**Do** +Use color to distinguish link button labels from other text. + +![](https://make.wordpress.org/design/files/2019/03/dont-wrap-button-text.png) + +**Don’t** +Don’t wrap button text. For maximum legibility, keep text labels on a single line. + +### Hierarchy + +![A layout with a single prominent button](https://make.wordpress.org/design/files/2019/03/button.png) + +A layout should contain a single prominently-located button. If multiple buttons are required, a single high-emphasis button can be joined by medium- and low-emphasis buttons mapped to less-important actions. When using multiple buttons, make sure the available state of one button doesn’t look like the disabled state of another. + +![A diagram showing high emphasis at the top, medium emphasis in the middle, and low emphasis at the bottom](https://make.wordpress.org/design/files/2019/03/button-hierarchy.png) + +A button’s level of emphasis helps determine its appearance, typography, and placement. + +#### Placement + +Use button types to express different emphasis levels for all the actions a user can perform. + +![A link, default, and primary button](https://make.wordpress.org/design/files/2019/03/button-layout.png) + +This screen layout uses: + +1. A primary button for high emphasis. +2. A default button for medium emphasis. +3. A link button for low emphasis. + +Placement best practices: + +- **Do**: When using multiple buttons in a row, show users which action is more important by placing it next to a button with a lower emphasis (e.g. a primary button next to a default button, or a default button next to a link button). +- **Don’t**: Don’t place two primary buttons next to one another — they compete for focus. Only use one primary button per view. +- **Don’t**: Don’t place a button below another button if there is space to place them side by side. +- **Caution**: Avoid using too many buttons on a single page. When designing pages in the app or website, think about the most important actions for users to take. Too many calls to action can cause confusion and make users unsure what to do next — we always want users to feel confident and capable. + +## Development guidelines + +### Usage Renders a button with default style. -{% codetabs %} -{% ESNext %} ```jsx import { Button } from "@wordpress/components"; const MyButton = () => ( - <Button isDefault> - Click me! - </Button> + +Click me! + ); ``` -{% end %} -## Props +### Props + +The presence of a `href` prop determines whether an `anchor` element is rendered instead of a `button`. Name | Type | Default | Description --- | --- | --- | --- @@ -39,5 +136,5 @@ Name | Type | Default | Description ## Related components -* To group buttons together, use the `ButtonGroup` component. -* To display an icon inside the button, use the `IconButton` component. +- To group buttons together, use the `ButtonGroup` component. +- To display an icon inside the button, use the `IconButton` component. From c7d14ca3b988e690b141aeefe1da5592f75a735e Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Wed, 13 Mar 2019 16:26:06 +0100 Subject: [PATCH 647/691] Upgrade React to 16.8.4: Welcome React Hooks (#14400) --- lib/client-assets.php | 19 +- package-lock.json | 209 +++--------------- package.json | 5 +- packages/element/README.md | 60 +++++ packages/element/package.json | 4 +- packages/element/src/react.js | 26 +++ phpunit/class-vendor-script-filename-test.php | 8 +- 7 files changed, 142 insertions(+), 189 deletions(-) diff --git a/lib/client-assets.php b/lib/client-assets.php index 801aefe8c9177..3b0f4ae6d0137 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -380,10 +380,21 @@ function gutenberg_register_scripts_and_styles() { * @since 0.1.0 */ function gutenberg_register_vendor_scripts() { - /* - * This function is kept as an empty stub, in case Gutenberg should need to - * explicitly provide a version newer than that provided by core. - */ + $suffix = SCRIPT_DEBUG ? '' : '.min'; + + // Vendor Scripts. + $react_suffix = ( SCRIPT_DEBUG ? '.development' : '.production' ) . $suffix; + + gutenberg_register_vendor_script( + 'react', + 'https://unpkg.com/react@16.8.4/umd/react' . $react_suffix . '.js', + array( 'wp-polyfill' ) + ); + gutenberg_register_vendor_script( + 'react-dom', + 'https://unpkg.com/react-dom@16.8.4/umd/react-dom' . $react_suffix . '.js', + array( 'react' ) + ); } /** diff --git a/package-lock.json b/package-lock.json index d01d93ab49028..f1897ceec693d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2842,8 +2842,8 @@ "@babel/runtime": "^7.3.1", "@wordpress/escape-html": "file:packages/escape-html", "lodash": "^4.17.11", - "react": "^16.6.3", - "react-dom": "^16.6.3" + "react": "^16.8.4", + "react-dom": "^16.8.4" } }, "@wordpress/escape-html": { @@ -3203,17 +3203,6 @@ "object.entries": "^1.0.4", "prop-types": "^15.6.1", "prop-types-exact": "^1.1.2" - }, - "dependencies": { - "prop-types": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", - "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", - "requires": { - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" - } - } } }, "ajv": { @@ -7414,23 +7403,6 @@ "function-bind": "^1.1.1", "has": "^1.0.3" } - }, - "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "dev": true, - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - } - }, - "react-is": { - "version": "16.8.3", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.3.tgz", - "integrity": "sha512-Y4rC1ZJmsxxkkPuMLwvKvlL1Zfpbcu+Bf4ZigkHup3v9EfdYhAlWAaVyA19olXq2o2mGn0w+dFKvk3pVVlYcIA==", - "dev": true } } }, @@ -7447,23 +7419,6 @@ "semver": "^5.6.0" }, "dependencies": { - "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "dev": true, - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - } - }, - "react-is": { - "version": "16.8.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.2.tgz", - "integrity": "sha512-D+NxhSR2HUCjYky1q1DwpNUD44cDpUXzSmmFyC3ug1bClcU/iDNy0YNn1iwme28fn+NFhpA13IndOd42CrFb+Q==", - "dev": true - }, "semver": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", @@ -7778,23 +7733,6 @@ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, - "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "dev": true, - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - } - }, - "react-is": { - "version": "16.8.3", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.3.tgz", - "integrity": "sha512-Y4rC1ZJmsxxkkPuMLwvKvlL1Zfpbcu+Bf4ZigkHup3v9EfdYhAlWAaVyA19olXq2o2mGn0w+dFKvk3pVVlYcIA==", - "dev": true - }, "resolve": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", @@ -10027,6 +9965,11 @@ "minimalistic-crypto-utils": "^1.0.1" } }, + "hoist-non-react-statics": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", + "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" + }, "homedir-polyfill": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz", @@ -17770,12 +17713,13 @@ } }, "prop-types": { - "version": "15.5.10", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.5.10.tgz", - "integrity": "sha1-J5ffwxJhguOpXj37suiT3ddFYVQ=", + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", "requires": { - "fbjs": "^0.8.9", - "loose-envify": "^1.3.1" + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" } }, "prop-types-exact": { @@ -18012,25 +17956,14 @@ "integrity": "sha512-pLJkPbZCe+3ml+9Q15z+R69qYZDsluj0KwrdFb8kSNaqDzYAveDUblf7voHH9hNTdKIiIvP8iIdGFFKSgffVaQ==" }, "react": { - "version": "16.6.3", - "resolved": "https://registry.npmjs.org/react/-/react-16.6.3.tgz", - "integrity": "sha512-zCvmH2vbEolgKxtqXL2wmGCUxUyNheYn/C+PD1YAjfxHC54+MhdruyhO7QieQrYsYeTxrn93PM2y0jRH1zEExw==", + "version": "16.8.4", + "resolved": "https://registry.npmjs.org/react/-/react-16.8.4.tgz", + "integrity": "sha512-0GQ6gFXfUH7aZcjGVymlPOASTuSjlQL4ZtVC5YKH+3JL6bBLCVO21DknzmaPlI90LN253ojj02nsapy+j7wIjg==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", "prop-types": "^15.6.2", - "scheduler": "^0.11.2" - }, - "dependencies": { - "prop-types": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", - "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", - "requires": { - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" - } - } + "scheduler": "^0.13.4" } }, "react-addons-shallow-compare": { @@ -18085,46 +18018,23 @@ "react-portal": "^4.1.5", "react-with-styles": "^3.2.0", "react-with-styles-interface-css": "^4.0.2" - }, - "dependencies": { - "prop-types": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", - "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", - "requires": { - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" - } - } } }, "react-dom": { - "version": "16.6.3", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.6.3.tgz", - "integrity": "sha512-8ugJWRCWLGXy+7PmNh8WJz3g1TaTUt1XyoIcFN+x0Zbkoz+KKdUyx1AQLYJdbFXjuF41Nmjn5+j//rxvhFjgSQ==", + "version": "16.8.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.4.tgz", + "integrity": "sha512-Ob2wK7XG2tUDt7ps7LtLzGYYB6DXMCLj0G5fO6WeEICtT4/HdpOi7W/xLzZnR6RCG1tYza60nMdqtxzA8FaPJQ==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", "prop-types": "^15.6.2", - "scheduler": "^0.11.2" - }, - "dependencies": { - "prop-types": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", - "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", - "requires": { - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" - } - } + "scheduler": "^0.13.4" } }, "react-is": { - "version": "16.6.3", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.6.3.tgz", - "integrity": "sha512-u7FDWtthB4rWibG/+mFbVd5FvdI20yde86qKGx4lVUTWmPlSWQ4QxbBIrrs+HnXGbxOUlUzTAP/VDmvCwaP2yA==", - "dev": true + "version": "16.8.4", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.4.tgz", + "integrity": "sha512-PVadd+WaUDOAciICm/J1waJaSvgq+4rHE/K70j0PFqKhkTBsPv/82UGQJNXAngz1fOQLLxI6z1sEDmJDQhCTAA==" }, "react-moment-proptypes": { "version": "1.6.0", @@ -18143,17 +18053,6 @@ "consolidated-events": "^1.1.1 || ^2.0.0", "object.values": "^1.0.4", "prop-types": "^15.6.1" - }, - "dependencies": { - "prop-types": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", - "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", - "requires": { - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" - } - } } }, "react-portal": { @@ -18165,27 +18064,15 @@ } }, "react-test-renderer": { - "version": "16.6.3", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.6.3.tgz", - "integrity": "sha512-B5bCer+qymrQz/wN03lT0LppbZUDRq6AMfzMKrovzkGzfO81a9T+PWQW6MzkWknbwODQH/qpJno/yFQLX5IWrQ==", + "version": "16.8.4", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.8.4.tgz", + "integrity": "sha512-jQ9Tf/ilIGSr55Cz23AZ/7H3ABEdo9oy2zF9nDHZyhLHDSLKuoILxw2ifpBfuuwQvj4LCoqdru9iZf7gwFH28A==", "dev": true, "requires": { "object-assign": "^4.1.1", "prop-types": "^15.6.2", - "react-is": "^16.6.3", - "scheduler": "^0.11.2" - }, - "dependencies": { - "prop-types": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", - "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", - "dev": true, - "requires": { - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" - } - } + "react-is": "^16.8.4", + "scheduler": "^0.13.4" } }, "react-with-direction": { @@ -18201,22 +18088,6 @@ "object.assign": "^4.1.0", "object.values": "^1.0.4", "prop-types": "^15.6.0" - }, - "dependencies": { - "hoist-non-react-statics": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", - "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" - }, - "prop-types": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", - "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", - "requires": { - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" - } - } } }, "react-with-styles": { @@ -18228,22 +18099,6 @@ "hoist-non-react-statics": "^2.5.0", "prop-types": "^15.6.1", "react-with-direction": "^1.3.0" - }, - "dependencies": { - "hoist-non-react-statics": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", - "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" - }, - "prop-types": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", - "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", - "requires": { - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" - } - } } }, "react-with-styles-interface-css": { @@ -19333,9 +19188,9 @@ "dev": true }, "scheduler": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.11.2.tgz", - "integrity": "sha512-+WCP3s3wOaW4S7C1tl3TEXp4l9lJn0ZK8G3W3WKRWmw77Z2cIFUW2MiNTMHn5sCjxN+t7N43HAOOgMjyAg5hlg==", + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.4.tgz", + "integrity": "sha512-cvSOlRPxOHs5dAhP9yiS/6IDmVAVxmk33f0CtTJRkmUWcb1Us+t7b1wqdzoC0REw2muC9V5f1L/w5R5uKGaepA==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" diff --git a/package.json b/package.json index 1f0e798ff37f1..6e071182be1a5 100644 --- a/package.json +++ b/package.json @@ -106,8 +106,9 @@ "pegjs": "0.10.0", "phpegjs": "1.0.0-beta7", "postcss": "7.0.13", - "react-dom": "16.6.3", - "react-test-renderer": "16.6.3", + "react": "16.8.4", + "react-dom": "16.8.4", + "react-test-renderer": "16.8.4", "redux": "4.0.0", "rimraf": "2.6.2", "rtlcss": "2.4.0", diff --git a/packages/element/README.md b/packages/element/README.md index ae28a29cf6ec2..c39eb933ec8b9 100755 --- a/packages/element/README.md +++ b/packages/element/README.md @@ -314,6 +314,66 @@ Removes any mounted element from the target DOM node. - **target** `Element`: DOM node in which element is to be removed +### useCallback + +[src/index.js#L1-L1](src/index.js#L1-L1) + +Make React Hooks available + +### useContext + +[src/index.js#L1-L1](src/index.js#L1-L1) + +Make React Hooks available + +### useDebugValue + +[src/index.js#L1-L1](src/index.js#L1-L1) + +Make React Hooks available + +### useEffect + +[src/index.js#L1-L1](src/index.js#L1-L1) + +Make React Hooks available + +### useImperativeHandle + +[src/index.js#L1-L1](src/index.js#L1-L1) + +Make React Hooks available + +### useLayoutEffect + +[src/index.js#L1-L1](src/index.js#L1-L1) + +Make React Hooks available + +### useMemo + +[src/index.js#L1-L1](src/index.js#L1-L1) + +Make React Hooks available + +### useReducer + +[src/index.js#L1-L1](src/index.js#L1-L1) + +Make React Hooks available + +### useRef + +[src/index.js#L1-L1](src/index.js#L1-L1) + +Make React Hooks available + +### useState + +[src/index.js#L1-L1](src/index.js#L1-L1) + +Make React Hooks available + <!-- END TOKEN(Autogenerated API docs) --> diff --git a/packages/element/package.json b/packages/element/package.json index 3702a0b559a4d..3df21197c4278 100644 --- a/packages/element/package.json +++ b/packages/element/package.json @@ -25,8 +25,8 @@ "@babel/runtime": "^7.3.1", "@wordpress/escape-html": "file:../escape-html", "lodash": "^4.17.11", - "react": "^16.6.3", - "react-dom": "^16.6.3" + "react": "^16.8.4", + "react-dom": "^16.8.4" }, "publishConfig": { "access": "public" diff --git a/packages/element/src/react.js b/packages/element/src/react.js index 703a0455d0a16..a2fbd1cda3671 100644 --- a/packages/element/src/react.js +++ b/packages/element/src/react.js @@ -12,6 +12,16 @@ import { Fragment, isValidElement, StrictMode, + useState, + useEffect, + useContext, + useReducer, + useCallback, + useMemo, + useRef, + useImperativeHandle, + useLayoutEffect, + useDebugValue, } from 'react'; import { isString } from 'lodash'; @@ -99,6 +109,22 @@ export { isValidElement }; */ export { StrictMode }; +/** + * Make React Hooks available + */ +export { + useCallback, + useContext, + useDebugValue, + useEffect, + useImperativeHandle, + useLayoutEffect, + useMemo, + useReducer, + useRef, + useState, +}; + /** * Concatenate two or more React children objects. * diff --git a/phpunit/class-vendor-script-filename-test.php b/phpunit/class-vendor-script-filename-test.php index 0ee7a7b78b83d..e3febd3557f26 100644 --- a/phpunit/class-vendor-script-filename-test.php +++ b/phpunit/class-vendor-script-filename-test.php @@ -11,23 +11,23 @@ function vendor_script_filename_cases() { // Development mode scripts. array( 'react-handle', - 'https://unpkg.com/react@16.6.3/umd/react.development.js', + 'https://unpkg.com/react@16.8.4/umd/react.development.js', 'react-handle.HASH.js', ), array( 'react-dom-handle', - 'https://unpkg.com/react-dom@16.6.3/umd/react-dom.development.js', + 'https://unpkg.com/react-dom@16.8.4/umd/react-dom.development.js', 'react-dom-handle.HASH.js', ), // Production mode scripts. array( 'react-handle', - 'https://unpkg.com/react@16.6.3/umd/react.production.min.js', + 'https://unpkg.com/react@16.8.4/umd/react.production.min.js', 'react-handle.min.HASH.js', ), array( 'react-dom-handle', - 'https://unpkg.com/react-dom@16.6.3/umd/react-dom.production.min.js', + 'https://unpkg.com/react-dom@16.8.4/umd/react-dom.production.min.js', 'react-dom-handle.min.HASH.js', ), // Other cases. From a06f9312633dabaa31f4559e63003a4ce00d0807 Mon Sep 17 00:00:00 2001 From: Adam Silverstein <adam@10up.com> Date: Wed, 13 Mar 2019 10:29:56 -0600 Subject: [PATCH 648/691] Document outline: Use links not buttons (#10815) * Adjust document outline to use an a tag vs button * target href links directly to page anchors, remove onClick handler * update test snapshot * update snapshot * better titleNode targeting * update snapshot * update snapshot * Close the table of contents panel when a link is clicked * add deterministic block id to tests * remove redundant screen reader text * Adjust map to avoid mutating original object * update snapshot * Update packages/editor/src/components/document-outline/index.js Co-Authored-By: adamsilverstein <adam@10up.com> * Update packages/editor/src/components/document-outline/test/index.js remove leading _ Co-Authored-By: adamsilverstein <adam@10up.com> * Update packages/editor/src/components/document-outline/index.js Co-Authored-By: adamsilverstein <adam@10up.com> * update snapshot * update snapshots * update snapshot * update snapshots * fix up e2e tests * Fix snapshots by removing single quotes from outline links * Update packages/editor/src/components/document-outline/index.js Co-Authored-By: adamsilverstein <adam@10up.com> * change target to href * rename close -> closeOutline * update snapshots after property name changes * rename close/onClose -> onRequestClose for TOC, on * restore onSelect * complete renaming * Block links are only valid for the current session - remove hash after following * update snapshot * cleanup; move block id hash removal functionality up to document outline; now includes title * update snapshot * use replaceState vs pushState * use defer and import at top of file * removeURLHash as helper * remove passing event in select handler * improve doc block * Skip title in outline when title node not found * remove removeURLHash --- .../src/components/document-outline/index.js | 29 +++++-------------- .../src/components/document-outline/item.js | 14 ++++----- .../components/document-outline/style.scss | 4 +++ .../test/__snapshots__/index.js.snap | 8 ++--- .../components/document-outline/test/index.js | 15 ++++++++-- .../src/components/table-of-contents/index.js | 2 +- .../src/components/table-of-contents/panel.js | 4 +-- 7 files changed, 37 insertions(+), 39 deletions(-) diff --git a/packages/editor/src/components/document-outline/index.js b/packages/editor/src/components/document-outline/index.js index b73af4a92d087..5a61503909a88 100644 --- a/packages/editor/src/components/document-outline/index.js +++ b/packages/editor/src/components/document-outline/index.js @@ -8,7 +8,7 @@ import { countBy, flatMap, get } from 'lodash'; */ import { __ } from '@wordpress/i18n'; import { compose } from '@wordpress/compose'; -import { withSelect, withDispatch } from '@wordpress/data'; +import { withSelect } from '@wordpress/data'; import { create, getTextContent, @@ -73,18 +73,9 @@ export const DocumentOutline = ( { blocks = [], title, onSelect, isTitleSupporte let prevHeadingLevel = 1; - // Select the corresponding block in the main editor - // when clicking on a heading item from the list. - const onSelectHeading = ( clientId ) => onSelect( clientId ); - const focusTitle = () => { - // Not great but it's the simplest way to focus the title right now. - const titleNode = document.querySelector( '.editor-post-title__input' ); - if ( titleNode ) { - titleNode.focus(); - } - }; - - const hasTitle = isTitleSupported && title; + // Not great but it's the simplest way to locate the title right now. + const titleNode = document.querySelector( '.editor-post-title__input' ); + const hasTitle = isTitleSupported && title && titleNode; const countByLevel = countBy( headings, 'level' ); const hasMultipleH1 = countByLevel[ 1 ] > 1; @@ -95,7 +86,8 @@ export const DocumentOutline = ( { blocks = [], title, onSelect, isTitleSupporte <DocumentOutlineItem level={ __( 'Title' ) } isValid - onClick={ focusTitle } + onSelect={ onSelect } + href={ `#${ titleNode.id }` } isDisabled={ hasOutlineItemsDisabled } > { title } @@ -119,9 +111,10 @@ export const DocumentOutline = ( { blocks = [], title, onSelect, isTitleSupporte key={ index } level={ `H${ item.level }` } isValid={ isValid } - onClick={ () => onSelectHeading( item.clientId ) } path={ item.path } isDisabled={ hasOutlineItemsDisabled } + href={ `#block-${ item.clientId }` } + onSelect={ onSelect } > { item.isEmpty ? emptyHeadingContent : @@ -152,11 +145,5 @@ export default compose( blocks: getBlocks(), isTitleSupported: get( postType, [ 'supports', 'title' ], false ), }; - } ), - withDispatch( ( dispatch ) => { - const { selectBlock } = dispatch( 'core/block-editor' ); - return { - onSelect: selectBlock, - }; } ) )( DocumentOutline ); diff --git a/packages/editor/src/components/document-outline/item.js b/packages/editor/src/components/document-outline/item.js index c0638100d25f1..1a92e7dbafc4e 100644 --- a/packages/editor/src/components/document-outline/item.js +++ b/packages/editor/src/components/document-outline/item.js @@ -6,16 +6,15 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; import { BlockTitle } from '@wordpress/block-editor'; const TableOfContentsItem = ( { children, isValid, level, - onClick, - isDisabled, path = [], + href, + onSelect, } ) => ( <li className={ classnames( @@ -26,10 +25,10 @@ const TableOfContentsItem = ( { } ) } > - <button + <a + href={ href } className="document-outline__button" - onClick={ isDisabled ? undefined : onClick } - disabled={ isDisabled } + onClick={ onSelect } > <span className="document-outline__emdash" aria-hidden="true"></span> { @@ -47,8 +46,7 @@ const TableOfContentsItem = ( { <span className="document-outline__item-content"> { children } </span> - { ! isDisabled && <span className="screen-reader-text">{ __( '(Click to focus this heading)' ) }</span> } - </button> + </a> </li> ); diff --git a/packages/editor/src/components/document-outline/style.scss b/packages/editor/src/components/document-outline/style.scss index 53008b91e6b3d..c02f815eb6f09 100644 --- a/packages/editor/src/components/document-outline/style.scss +++ b/packages/editor/src/components/document-outline/style.scss @@ -11,6 +11,10 @@ display: flex; margin: 4px 0; + a { + text-decoration: none; + } + .document-outline__emdash::before { color: $light-gray-500; margin-right: 4px; diff --git a/packages/editor/src/components/document-outline/test/__snapshots__/index.js.snap b/packages/editor/src/components/document-outline/test/__snapshots__/index.js.snap index 89d0ebb04d658..8f79943b2f5d9 100644 --- a/packages/editor/src/components/document-outline/test/__snapshots__/index.js.snap +++ b/packages/editor/src/components/document-outline/test/__snapshots__/index.js.snap @@ -6,19 +6,19 @@ exports[`DocumentOutline header blocks present should match snapshot 1`] = ` > <ul> <TableOfContentsItem + href="#block-clientId_0" isValid={true} key="0" level="H2" - onClick={[Function]} path={Array []} > Heading parent </TableOfContentsItem> <TableOfContentsItem + href="#block-clientId_1" isValid={true} key="1" level="H3" - onClick={[Function]} path={Array []} > Heading child @@ -33,10 +33,10 @@ exports[`DocumentOutline header blocks present should render warnings for multip > <ul> <TableOfContentsItem + href="#block-clientId_0" isValid={false} key="0" level="H1" - onClick={[Function]} path={Array []} > Heading 1 @@ -50,10 +50,10 @@ exports[`DocumentOutline header blocks present should render warnings for multip </em> </TableOfContentsItem> <TableOfContentsItem + href="#block-clientId_2" isValid={false} key="1" level="H1" - onClick={[Function]} path={Array []} > Heading 1 diff --git a/packages/editor/src/components/document-outline/test/index.js b/packages/editor/src/components/document-outline/test/index.js index 024cb615beeeb..5ee66565d2dad 100644 --- a/packages/editor/src/components/document-outline/test/index.js +++ b/packages/editor/src/components/document-outline/test/index.js @@ -80,7 +80,10 @@ describe( 'DocumentOutline', () => { } ); it( 'should not render when no heading blocks provided', () => { - const blocks = [ paragraph ]; + const blocks = [ paragraph ].map( ( block, index ) => { + // Set client IDs to a predictable value. + return { ...block, clientId: `clientId_${ index }` }; + } ); const wrapper = shallow( <DocumentOutline blocks={ blocks } /> ); expect( wrapper.html() ).toBe( null ); @@ -89,7 +92,10 @@ describe( 'DocumentOutline', () => { describe( 'header blocks present', () => { it( 'should match snapshot', () => { - const blocks = [ headingParent, headingChild ]; + const blocks = [ headingParent, headingChild ].map( ( block, index ) => { + // Set client IDs to a predictable value. + return { ...block, clientId: `clientId_${ index }` }; + } ); const wrapper = shallow( <DocumentOutline blocks={ blocks } /> ); expect( wrapper ).toMatchSnapshot(); @@ -110,7 +116,10 @@ describe( 'DocumentOutline', () => { } ); it( 'should render warnings for multiple h1 headings', () => { - const blocks = [ headingH1, paragraph, headingH1, paragraph ]; + const blocks = [ headingH1, paragraph, headingH1, paragraph ].map( ( block, index ) => { + // Set client IDs to a predictable value. + return { ...block, clientId: `clientId_${ index }` }; + } ); const wrapper = shallow( <DocumentOutline blocks={ blocks } /> ); expect( wrapper ).toMatchSnapshot(); diff --git a/packages/editor/src/components/table-of-contents/index.js b/packages/editor/src/components/table-of-contents/index.js index 767f51dc6c1af..b027f6c09eefc 100644 --- a/packages/editor/src/components/table-of-contents/index.js +++ b/packages/editor/src/components/table-of-contents/index.js @@ -26,7 +26,7 @@ function TableOfContents( { hasBlocks, hasOutlineItemsDisabled } ) { aria-disabled={ ! hasBlocks } /> ) } - renderContent={ () => <TableOfContentsPanel hasOutlineItemsDisabled={ hasOutlineItemsDisabled } /> } + renderContent={ ( { onClose } ) => <TableOfContentsPanel onRequestClose={ onClose } hasOutlineItemsDisabled={ hasOutlineItemsDisabled } /> } /> ); } diff --git a/packages/editor/src/components/table-of-contents/panel.js b/packages/editor/src/components/table-of-contents/panel.js index a1a2a4b413055..901c2a31556e0 100644 --- a/packages/editor/src/components/table-of-contents/panel.js +++ b/packages/editor/src/components/table-of-contents/panel.js @@ -11,7 +11,7 @@ import { withSelect } from '@wordpress/data'; import WordCount from '../word-count'; import DocumentOutline from '../document-outline'; -function TableOfContentsPanel( { headingCount, paragraphCount, numberOfBlocks, hasOutlineItemsDisabled } ) { +function TableOfContentsPanel( { headingCount, paragraphCount, numberOfBlocks, hasOutlineItemsDisabled, onRequestClose } ) { return ( <Fragment> <div @@ -49,7 +49,7 @@ function TableOfContentsPanel( { headingCount, paragraphCount, numberOfBlocks, h <span className="table-of-contents__title"> { __( 'Document Outline' ) } </span> - <DocumentOutline hasOutlineItemsDisabled={ hasOutlineItemsDisabled } /> + <DocumentOutline onSelect={ onRequestClose } hasOutlineItemsDisabled={ hasOutlineItemsDisabled } /> </Fragment> ) } </Fragment> From cd456f6539981c2640f05d210782dba06a6913c8 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Wed, 13 Mar 2019 19:45:46 +0000 Subject: [PATCH 649/691] Add: end to end test to InnerBlocks allowed block restriction (#14054) --- packages/e2e-test-utils/CHANGELOG.md | 9 ++ packages/e2e-test-utils/README.md | 90 ++++++++++++------- .../src/get-all-block-inserter-item-titles.js | 24 +++++ packages/e2e-test-utils/src/index.js | 3 + .../src/open-all-block-inserter-categories.js | 11 +++ .../src/open-global-block-inserter.js | 9 ++ .../e2e-test-utils/src/search-for-block.js | 10 ++- .../plugins/inner-blocks-allowed-blocks.php | 28 ++++++ .../inner-blocks-allowed-blocks/index.js | 58 ++++++++++++ .../__snapshots__/media-text.test.js.snap | 10 +++ .../e2e-tests/specs/blocks/columns.test.js | 26 ++++++ .../e2e-tests/specs/blocks/media-text.test.js | 24 +++++ .../inner-blocks-allowed-blocks.test.js | 59 ++++++++++++ 13 files changed, 323 insertions(+), 38 deletions(-) create mode 100644 packages/e2e-test-utils/src/get-all-block-inserter-item-titles.js create mode 100644 packages/e2e-test-utils/src/open-all-block-inserter-categories.js create mode 100644 packages/e2e-test-utils/src/open-global-block-inserter.js create mode 100644 packages/e2e-tests/plugins/inner-blocks-allowed-blocks.php create mode 100644 packages/e2e-tests/plugins/inner-blocks-allowed-blocks/index.js create mode 100644 packages/e2e-tests/specs/blocks/__snapshots__/media-text.test.js.snap create mode 100644 packages/e2e-tests/specs/blocks/columns.test.js create mode 100644 packages/e2e-tests/specs/blocks/media-text.test.js create mode 100644 packages/e2e-tests/specs/plugins/inner-blocks-allowed-blocks.test.js diff --git a/packages/e2e-test-utils/CHANGELOG.md b/packages/e2e-test-utils/CHANGELOG.md index 75b6278875283..026555fffe27f 100644 --- a/packages/e2e-test-utils/CHANGELOG.md +++ b/packages/e2e-test-utils/CHANGELOG.md @@ -1,3 +1,12 @@ +## 1.1.0 (Unreleased) + +### New Features + +- New Function: `getAllBlockInserterItemTitles` - Returns an array of strings with all inserter item titles. +- New Function: `openAllBlockInserterCategories` - Opens all block inserter categories. +- New Function: `getAllBlockInserterItemTitles` - Opens the global block inserter. + + ## 1.0.0 (2019-03-06) - Initial release. diff --git a/packages/e2e-test-utils/README.md b/packages/e2e-test-utils/README.md index e987e7002839b..039bd766b1963 100644 --- a/packages/e2e-test-utils/README.md +++ b/packages/e2e-test-utils/README.md @@ -88,7 +88,7 @@ Clicks on More Menu item, searches for the button with the text provided and cli ### createEmbeddingMatcher -[src/index.js#L48-L48](src/index.js#L48-L48) +[src/index.js#L51-L51](src/index.js#L51-L51) Creates a function to determine if a request is embedding a certain URL. @@ -102,7 +102,7 @@ Creates a function to determine if a request is embedding a certain URL. ### createJSONResponse -[src/index.js#L48-L48](src/index.js#L48-L48) +[src/index.js#L51-L51](src/index.js#L51-L51) Respond to a request with a JSON response. @@ -141,7 +141,7 @@ Creates new URL by parsing base URL, WPPath and query string. ### createURLMatcher -[src/index.js#L48-L48](src/index.js#L48-L48) +[src/index.js#L51-L51](src/index.js#L51-L51) Creates a function to determine if a request is calling a URL with the substring present. @@ -220,10 +220,20 @@ Finds the button responsible for toggling the sidebar panel with the provided ti `?ElementHandle`: Object that represents an in-page DOM element. -### getAllBlocks +### getAllBlockInserterItemTitles [src/index.js#L18-L18](src/index.js#L18-L18) +Returns an array of strings with all inserter item titles. + +**Returns** + +`Promise`: Promise resolving with an array containing all inserter item titles. + +### getAllBlocks + +[src/index.js#L19-L19](src/index.js#L19-L19) + Returns an array with all blocks; Equivalent to calling wp.data.select( 'core/editor' ).getBlocks(); **Returns** @@ -232,7 +242,7 @@ Returns an array with all blocks; Equivalent to calling wp.data.select( 'core/ed ### getAvailableBlockTransforms -[src/index.js#L19-L19](src/index.js#L19-L19) +[src/index.js#L20-L20](src/index.js#L20-L20) Returns an array of strings with all block titles, that the current selected block can be transformed into. @@ -243,7 +253,7 @@ that the current selected block can be transformed into. ### getEditedPostContent -[src/index.js#L20-L20](src/index.js#L20-L20) +[src/index.js#L21-L21](src/index.js#L21-L21) Returns a promise which resolves with the edited post content (HTML string). @@ -253,7 +263,7 @@ Returns a promise which resolves with the edited post content (HTML string). ### hasBlockSwitcher -[src/index.js#L21-L21](src/index.js#L21-L21) +[src/index.js#L22-L22](src/index.js#L22-L22) Returns a boolean indicating if the current selected block has a block switcher or not. @@ -263,7 +273,7 @@ Returns a boolean indicating if the current selected block has a block switcher ### insertBlock -[src/index.js#L22-L22](src/index.js#L22-L22) +[src/index.js#L23-L23](src/index.js#L23-L23) Opens the inserter, searches for the given term, then selects the first result that appears. @@ -275,7 +285,7 @@ result that appears. ### installPlugin -[src/index.js#L23-L23](src/index.js#L23-L23) +[src/index.js#L24-L24](src/index.js#L24-L24) Installs a plugin from the WP.org repository. @@ -286,7 +296,7 @@ Installs a plugin from the WP.org repository. ### isCurrentURL -[src/index.js#L24-L24](src/index.js#L24-L24) +[src/index.js#L25-L25](src/index.js#L25-L25) Checks if current URL is a WordPress path. @@ -301,7 +311,7 @@ Checks if current URL is a WordPress path. ### isInDefaultBlock -[src/index.js#L25-L25](src/index.js#L25-L25) +[src/index.js#L26-L26](src/index.js#L26-L26) Checks if the block that is focused is the default block. @@ -311,7 +321,7 @@ Checks if the block that is focused is the default block. ### loginUser -[src/index.js#L26-L26](src/index.js#L26-L26) +[src/index.js#L27-L27](src/index.js#L27-L27) Performs log in with specified username and password. @@ -322,7 +332,7 @@ Performs log in with specified username and password. ### mockOrTransform -[src/index.js#L48-L48](src/index.js#L48-L48) +[src/index.js#L51-L51](src/index.js#L51-L51) Mocks a request with the supplied mock object, or allows it to run with an optional transform, based on the deserialised JSON response for the request. @@ -339,26 +349,38 @@ deserialised JSON response for the request. ### observeFocusLoss -[src/index.js#L27-L27](src/index.js#L27-L27) +[src/index.js#L28-L28](src/index.js#L28-L28) Binds to the document on page load which throws an error if a `focusout` event occurs without a related target (i.e. focus loss). +### openAllBlockInserterCategories + +[src/index.js#L29-L29](src/index.js#L29-L29) + +Opens all block inserter categories. + ### openDocumentSettingsSidebar -[src/index.js#L28-L28](src/index.js#L28-L28) +[src/index.js#L30-L30](src/index.js#L30-L30) Clicks on the button in the header which opens Document Settings sidebar when it is closed. +### openGlobalBlockInserter + +[src/index.js#L31-L31](src/index.js#L31-L31) + +Opens the global block inserter. + ### openPublishPanel -[src/index.js#L29-L29](src/index.js#L29-L29) +[src/index.js#L32-L32](src/index.js#L32-L32) Opens the publish panel. ### pressKeyTimes -[src/index.js#L30-L30](src/index.js#L30-L30) +[src/index.js#L33-L33](src/index.js#L33-L33) Presses the given keyboard key a number of times in sequence. @@ -373,7 +395,7 @@ Presses the given keyboard key a number of times in sequence. ### pressKeyWithModifier -[src/index.js#L31-L31](src/index.js#L31-L31) +[src/index.js#L34-L34](src/index.js#L34-L34) Performs a key press with modifier (Shift, Control, Meta, Alt), where each modifier is normalized to platform-specific modifier. @@ -385,7 +407,7 @@ is normalized to platform-specific modifier. ### publishPost -[src/index.js#L32-L32](src/index.js#L32-L32) +[src/index.js#L35-L35](src/index.js#L35-L35) Publishes the post, resolving once the request is complete (once a notice is displayed). @@ -396,7 +418,7 @@ is displayed). ### publishPostWithPrePublishChecksDisabled -[src/index.js#L33-L33](src/index.js#L33-L33) +[src/index.js#L36-L36](src/index.js#L36-L36) Publishes the post without the pre-publish checks, resolving once the request is complete (once a notice is displayed). @@ -407,7 +429,7 @@ resolving once the request is complete (once a notice is displayed). ### saveDraft -[src/index.js#L34-L34](src/index.js#L34-L34) +[src/index.js#L37-L37](src/index.js#L37-L37) Saves the post as a draft, resolving once the request is complete (once the "Saved" indicator is displayed). @@ -418,7 +440,7 @@ Saves the post as a draft, resolving once the request is complete (once the ### searchForBlock -[src/index.js#L35-L35](src/index.js#L35-L35) +[src/index.js#L38-L38](src/index.js#L38-L38) Search for block in the global inserter @@ -428,7 +450,7 @@ Search for block in the global inserter ### selectBlockByClientId -[src/index.js#L36-L36](src/index.js#L36-L36) +[src/index.js#L39-L39](src/index.js#L39-L39) Given the clientId of a block, selects the block on the editor. @@ -438,7 +460,7 @@ Given the clientId of a block, selects the block on the editor. ### setBrowserViewport -[src/index.js#L37-L37](src/index.js#L37-L37) +[src/index.js#L40-L40](src/index.js#L40-L40) Sets browser viewport to specified type. @@ -448,7 +470,7 @@ Sets browser viewport to specified type. ### setPostContent -[src/index.js#L38-L38](src/index.js#L38-L38) +[src/index.js#L41-L41](src/index.js#L41-L41) Sets code editor content @@ -462,7 +484,7 @@ Sets code editor content ### setUpResponseMocking -[src/index.js#L48-L48](src/index.js#L48-L48) +[src/index.js#L51-L51](src/index.js#L51-L51) Sets up mock checks and responses. Accepts a list of mock settings with the following properties: @@ -493,7 +515,7 @@ If none of the mock settings match the request, the request is allowed to contin ### switchEditorModeTo -[src/index.js#L39-L39](src/index.js#L39-L39) +[src/index.js#L42-L42](src/index.js#L42-L42) Switches editor mode. @@ -503,21 +525,21 @@ Switches editor mode. ### switchUserToAdmin -[src/index.js#L40-L40](src/index.js#L40-L40) +[src/index.js#L43-L43](src/index.js#L43-L43) Switches the current user to the admin user (if the user running the test is not already the admin user). ### switchUserToTest -[src/index.js#L41-L41](src/index.js#L41-L41) +[src/index.js#L44-L44](src/index.js#L44-L44) Switches the current user to whichever user we should be running the tests as (if we're not already that user). ### toggleScreenOption -[src/index.js#L42-L42](src/index.js#L42-L42) +[src/index.js#L45-L45](src/index.js#L45-L45) Toggles the screen option with the given label. @@ -528,7 +550,7 @@ Toggles the screen option with the given label. ### transformBlockTo -[src/index.js#L43-L43](src/index.js#L43-L43) +[src/index.js#L46-L46](src/index.js#L46-L46) Converts editor's block type. @@ -538,7 +560,7 @@ Converts editor's block type. ### uninstallPlugin -[src/index.js#L44-L44](src/index.js#L44-L44) +[src/index.js#L47-L47](src/index.js#L47-L47) Uninstalls a plugin. @@ -548,7 +570,7 @@ Uninstalls a plugin. ### visitAdminPage -[src/index.js#L45-L45](src/index.js#L45-L45) +[src/index.js#L48-L48](src/index.js#L48-L48) Visits admin page; if user is not logged in then it logging in it first, then visits admin page. @@ -559,7 +581,7 @@ Visits admin page; if user is not logged in then it logging in it first, then vi ### waitForWindowDimensions -[src/index.js#L46-L46](src/index.js#L46-L46) +[src/index.js#L49-L49](src/index.js#L49-L49) Function that waits until the page viewport has the required dimensions. It is being used to address a problem where after using setViewport the execution may continue, diff --git a/packages/e2e-test-utils/src/get-all-block-inserter-item-titles.js b/packages/e2e-test-utils/src/get-all-block-inserter-item-titles.js new file mode 100644 index 0000000000000..859adc89bac15 --- /dev/null +++ b/packages/e2e-test-utils/src/get-all-block-inserter-item-titles.js @@ -0,0 +1,24 @@ +/** + * External dependencies + */ +import { sortBy, uniq } from 'lodash'; + +/** + * Returns an array of strings with all inserter item titles. + * + * @return {Promise} Promise resolving with an array containing all inserter item titles. + */ +export async function getAllBlockInserterItemTitles() { + const inserterItemTitles = await page.evaluate( () => { + return Array.from( + document.querySelectorAll( + '.editor-inserter__results .editor-block-types-list__item-title' + ) + ).map( + ( inserterItem ) => { + return inserterItem.innerText; + } + ); + } ); + return sortBy( uniq( inserterItemTitles ) ); +} diff --git a/packages/e2e-test-utils/src/index.js b/packages/e2e-test-utils/src/index.js index 09e11bf9ec0ed..3543abbbb3609 100644 --- a/packages/e2e-test-utils/src/index.js +++ b/packages/e2e-test-utils/src/index.js @@ -15,6 +15,7 @@ export { enablePrePublishChecks } from './enable-pre-publish-checks'; export { ensureSidebarOpened } from './ensure-sidebar-opened'; export { findSidebarPanelToggleButtonWithTitle } from './find-sidebar-panel-toggle-button-with-title'; export { findSidebarPanelWithTitle } from './find-sidebar-panel-with-title'; +export { getAllBlockInserterItemTitles } from './get-all-block-inserter-item-titles'; export { getAllBlocks } from './get-all-blocks'; export { getAvailableBlockTransforms } from './get-available-block-transforms'; export { getEditedPostContent } from './get-edited-post-content'; @@ -25,7 +26,9 @@ export { isCurrentURL } from './is-current-url'; export { isInDefaultBlock } from './is-in-default-block'; export { loginUser } from './login-user'; export { observeFocusLoss } from './observe-focus-loss'; +export { openAllBlockInserterCategories } from './open-all-block-inserter-categories'; export { openDocumentSettingsSidebar } from './open-document-settings-sidebar'; +export { openGlobalBlockInserter } from './open-global-block-inserter'; export { openPublishPanel } from './open-publish-panel'; export { pressKeyTimes } from './press-key-times'; export { pressKeyWithModifier } from './press-key-with-modifier'; diff --git a/packages/e2e-test-utils/src/open-all-block-inserter-categories.js b/packages/e2e-test-utils/src/open-all-block-inserter-categories.js new file mode 100644 index 0000000000000..d0b70387c9275 --- /dev/null +++ b/packages/e2e-test-utils/src/open-all-block-inserter-categories.js @@ -0,0 +1,11 @@ +/** + * Opens all block inserter categories. + */ +export async function openAllBlockInserterCategories() { + const notOpenCategoryPanels = await page.$$( + '.editor-inserter__results .components-panel__body:not(.is-opened)' + ); + for ( const categoryPanel of notOpenCategoryPanels ) { + await categoryPanel.click(); + } +} diff --git a/packages/e2e-test-utils/src/open-global-block-inserter.js b/packages/e2e-test-utils/src/open-global-block-inserter.js new file mode 100644 index 0000000000000..88fb28703c23d --- /dev/null +++ b/packages/e2e-test-utils/src/open-global-block-inserter.js @@ -0,0 +1,9 @@ +/** + * Opens the global block inserter. + */ +export async function openGlobalBlockInserter() { + await page.click( '.edit-post-header [aria-label="Add block"]' ); + // Waiting here is necessary because sometimes the inserter takes more time to + // render than Puppeteer takes to complete the 'click' action + await page.waitForSelector( '.editor-inserter__menu' ); +} diff --git a/packages/e2e-test-utils/src/search-for-block.js b/packages/e2e-test-utils/src/search-for-block.js index e96b92839221a..bf8b7cd1e74d4 100644 --- a/packages/e2e-test-utils/src/search-for-block.js +++ b/packages/e2e-test-utils/src/search-for-block.js @@ -1,12 +1,14 @@ +/** + * Internal dependencies + */ +import { openGlobalBlockInserter } from './open-global-block-inserter'; + /** * Search for block in the global inserter * * @param {string} searchTerm The text to search the inserter for. */ export async function searchForBlock( searchTerm ) { - await page.click( '.edit-post-header [aria-label="Add block"]' ); - // Waiting here is necessary because sometimes the inserter takes more time to - // render than Puppeteer takes to complete the 'click' action - await page.waitForSelector( '.editor-inserter__menu' ); + await openGlobalBlockInserter(); await page.keyboard.type( searchTerm ); } diff --git a/packages/e2e-tests/plugins/inner-blocks-allowed-blocks.php b/packages/e2e-tests/plugins/inner-blocks-allowed-blocks.php new file mode 100644 index 0000000000000..8dcf752ba7df4 --- /dev/null +++ b/packages/e2e-tests/plugins/inner-blocks-allowed-blocks.php @@ -0,0 +1,28 @@ +<?php +/** + * Plugin Name: Gutenberg Test InnerBlocks Allowed Blocks + * Plugin URI: https://github.com/WordPress/gutenberg + * Author: Gutenberg Team + * + * @package gutenberg-test-inner-blocks-allowed-blocks + */ + +/** + * Registers a custom script for the plugin. + */ +function enqueue_inner_blocks_allowed_blocks_script() { + wp_enqueue_script( + 'gutenberg-test-block-icons', + plugins_url( 'inner-blocks-allowed-blocks/index.js', __FILE__ ), + array( + 'wp-blocks', + 'wp-editor', + 'wp-element', + 'wp-i18n', + ), + filemtime( plugin_dir_path( __FILE__ ) . 'inner-blocks-allowed-blocks/index.js' ), + true + ); +} + +add_action( 'init', 'enqueue_inner_blocks_allowed_blocks_script' ); diff --git a/packages/e2e-tests/plugins/inner-blocks-allowed-blocks/index.js b/packages/e2e-tests/plugins/inner-blocks-allowed-blocks/index.js new file mode 100644 index 0000000000000..2ade659973f7a --- /dev/null +++ b/packages/e2e-tests/plugins/inner-blocks-allowed-blocks/index.js @@ -0,0 +1,58 @@ +( function() { + const { registerBlockType } = wp.blocks; + const { createElement: el } = wp.element; + const { InnerBlocks } = wp.editor; + const __ = wp.i18n.__; + const divProps = { className: 'product', style: { outline: '1px solid gray', padding: 5 } }; + const template = [ + [ 'core/image' ], + [ 'core/paragraph', { placeholder: __( 'Add a description' ) } ], + [ 'core/quote' ] + ]; + + const save = function() { + return el( 'div', divProps, + el( InnerBlocks.Content ) + ); + }; + registerBlockType( 'test/allowed-blocks-unset', { + title: 'Allowed Blocks Unset', + icon: 'carrot', + category: 'common', + + edit() { + return el( 'div', divProps, + el( InnerBlocks, { template } ) + ); + }, + + save, + } ); + + registerBlockType( 'test/allowed-blocks-set', { + title: 'Allowed Blocks Set', + icon: 'carrot', + category: 'common', + + edit() { + return el( 'div', divProps, + el( + InnerBlocks, + { + template, + allowedBlocks: [ + 'core/button', + 'core/gallery', + 'core/list', + 'core/media-text', + 'core/quote', + ], + } + ) + ); + }, + + save, + } ); + +} )(); diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/media-text.test.js.snap b/packages/e2e-tests/specs/blocks/__snapshots__/media-text.test.js.snap new file mode 100644 index 0000000000000..7ce74edf9b706 --- /dev/null +++ b/packages/e2e-tests/specs/blocks/__snapshots__/media-text.test.js.snap @@ -0,0 +1,10 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Media Text restricts blocks that can be inserted 1`] = ` +Array [ + "Button", + "Heading", + "List", + "Paragraph", +] +`; diff --git a/packages/e2e-tests/specs/blocks/columns.test.js b/packages/e2e-tests/specs/blocks/columns.test.js new file mode 100644 index 0000000000000..f8b97c62b91a8 --- /dev/null +++ b/packages/e2e-tests/specs/blocks/columns.test.js @@ -0,0 +1,26 @@ +/** + * WordPress dependencies + */ +import { + createNewPost, + getAllBlockInserterItemTitles, + insertBlock, + openAllBlockInserterCategories, + openGlobalBlockInserter, +} from '@wordpress/e2e-test-utils'; + +describe( 'Columns', () => { + beforeEach( async () => { + await createNewPost(); + } ); + + it( 'restricts all blocks inside the columns block', async () => { + await insertBlock( 'Columns' ); + await page.click( '[aria-label="Block Navigation"]' ); + const columnBlockMenuItem = ( await page.$x( '//button[contains(concat(" ", @class, " "), " editor-block-navigation__item-button ")][text()="Column"]' ) )[ 0 ]; + await columnBlockMenuItem.click(); + await openGlobalBlockInserter(); + await openAllBlockInserterCategories(); + expect( await getAllBlockInserterItemTitles() ).toHaveLength( 0 ); + } ); +} ); diff --git a/packages/e2e-tests/specs/blocks/media-text.test.js b/packages/e2e-tests/specs/blocks/media-text.test.js new file mode 100644 index 0000000000000..f9687b3af99cf --- /dev/null +++ b/packages/e2e-tests/specs/blocks/media-text.test.js @@ -0,0 +1,24 @@ +/** + * WordPress dependencies + */ +import { + createNewPost, + getAllBlockInserterItemTitles, + insertBlock, + openAllBlockInserterCategories, + openGlobalBlockInserter, +} from '@wordpress/e2e-test-utils'; + +describe( 'Media Text', () => { + beforeEach( async () => { + await createNewPost(); + } ); + + it( 'restricts blocks that can be inserted', async () => { + await insertBlock( 'Media & Text' ); + await page.click( '.wp-block-media-text .editor-rich-text' ); + await openGlobalBlockInserter(); + await openAllBlockInserterCategories(); + expect( await getAllBlockInserterItemTitles() ).toMatchSnapshot(); + } ); +} ); diff --git a/packages/e2e-tests/specs/plugins/inner-blocks-allowed-blocks.test.js b/packages/e2e-tests/specs/plugins/inner-blocks-allowed-blocks.test.js new file mode 100644 index 0000000000000..7334ccce4f1c8 --- /dev/null +++ b/packages/e2e-tests/specs/plugins/inner-blocks-allowed-blocks.test.js @@ -0,0 +1,59 @@ +/** + * WordPress dependencies + */ +import { + activatePlugin, + createNewPost, + deactivatePlugin, + getAllBlockInserterItemTitles, + insertBlock, + openAllBlockInserterCategories, + openGlobalBlockInserter, +} from '@wordpress/e2e-test-utils'; + +describe( 'Allowed Blocks Setting on InnerBlocks ', () => { + const paragraphSelector = '.editor-rich-text__editable.wp-block-paragraph'; + beforeAll( async () => { + await activatePlugin( 'gutenberg-test-innerblocks-allowed-blocks' ); + } ); + + beforeEach( async () => { + await createNewPost(); + } ); + + afterAll( async () => { + await deactivatePlugin( 'gutenberg-test-innerblocks-allowed-blocks' ); + } ); + + it( 'allows all blocks if the allowed blocks setting was not set', async () => { + const parentBlockSelector = '[data-type="test/allowed-blocks-unset"]'; + const childParagraphSelector = `${ parentBlockSelector } ${ paragraphSelector }`; + await insertBlock( 'Allowed Blocks Unset' ); + await page.waitForSelector( childParagraphSelector ); + await page.click( childParagraphSelector ); + await openGlobalBlockInserter(); + await openAllBlockInserterCategories(); + expect( + ( await getAllBlockInserterItemTitles() ).length + ).toBeGreaterThan( 20 ); + } ); + + it( 'allows the blocks if the allowed blocks setting was set', async () => { + const parentBlockSelector = '[data-type="test/allowed-blocks-set"]'; + const childParagraphSelector = `${ parentBlockSelector } ${ paragraphSelector }`; + await insertBlock( 'Allowed Blocks Set' ); + await page.waitForSelector( childParagraphSelector ); + await page.click( childParagraphSelector ); + await openGlobalBlockInserter(); + await openAllBlockInserterCategories(); + expect( + await getAllBlockInserterItemTitles() + ).toEqual( [ + 'Button', + 'Gallery', + 'List', + 'Media & Text', + 'Quote', + ] ); + } ); +} ); From 67656bc2fa53e487423937b3f99d8e701ab3196d Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Wed, 13 Mar 2019 19:06:51 -0400 Subject: [PATCH 650/691] Plugin: Remove replace_editor filter, extend core editor (#13569) * Plugin: Remove replace_editor filter, extend core editor * Plugin: Avoid dynamic dependencies for wp-block-library style This is handled by core in common blocks style enqueues behavior https://github.com/WordPress/wordpress-develop/blob/e421f26/src/wp-includes/script-loader.php#L2626-L2630 * Testing: Leverage saveDraft for meta boxes save --- .../backward-compatibility/deprecations.md | 11 + gutenberg.php | 132 +---- lib/client-assets.php | 479 +----------------- lib/load.php | 1 + lib/widgets.php | 123 +++++ .../specs/plugins/meta-boxes.test.js | 23 +- phpunit/class-core-block-theme-test.php | 106 ---- 7 files changed, 173 insertions(+), 702 deletions(-) create mode 100644 lib/widgets.php delete mode 100644 phpunit/class-core-block-theme-test.php diff --git a/docs/designers-developers/developers/backward-compatibility/deprecations.md b/docs/designers-developers/developers/backward-compatibility/deprecations.md index 8e8f8ee2d3c37..496a8b3e5d571 100644 --- a/docs/designers-developers/developers/backward-compatibility/deprecations.md +++ b/docs/designers-developers/developers/backward-compatibility/deprecations.md @@ -2,6 +2,17 @@ The Gutenberg project's deprecation policy is intended to support backward compatibility for releases, when possible. The current deprecations are listed below and are grouped by _the version at which they will be removed completely_. If your plugin depends on these behaviors, you must update to the recommended alternative before the noted version. +## 5.5.0 + +- The PHP function `gutenberg_init` has been removed. +- The PHP function `is_gutenberg_page` has been removed. Use [`WP_Screen::is_block_editor`](https://developer.wordpress.org/reference/classes/wp_screen/is_block_editor/) instead. +- The PHP function `the_gutenberg_project` has been removed. +- The PHP function `gutenberg_default_post_format_template` has been removed. +- The PHP function `gutenberg_get_available_image_sizes` has been removed. +- The PHP function `gutenberg_get_autosave_newer_than_post_save` has been removed. +- The PHP function `gutenberg_default_post_format_template` has been removed. +- The PHP function `gutenberg_editor_scripts_and_styles` has been removed. + ## 5.4.0 - The PHP function `gutenberg_load_plugin_textdomain` has been removed. diff --git a/gutenberg.php b/gutenberg.php index cbb0375d2a760..5676c589b7d07 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -22,47 +22,11 @@ * The main entry point for the Gutenberg editor. Renders the editor on the * wp-admin page for the plugin. * - * The gutenberg and gutenberg__editor classNames are left for backward compatibility. - * * @since 0.1.0 + * @deprecated 5.3.0 */ function the_gutenberg_project() { - global $post_type_object; - ?> - <noscript> - <div class="error" style="position:absolute;top:32px;z-index:40"><p> - <?php - printf( - /* translators: %s: https://wordpress.org/plugins/classic-editor/ */ - __( 'The block editor requires JavaScript. Please try the <a href="%s">Classic Editor plugin</a>.', 'gutenberg' ), - __( 'https://wordpress.org/plugins/classic-editor/', 'gutenberg' ) - ); - ?> - </p></div> - </noscript> - <div class="block-editor gutenberg"> - <h1 class="screen-reader-text"><?php echo esc_html( $post_type_object->labels->edit_item ); ?></h1> - <div id="editor" class="block-editor__container gutenberg__editor"></div> - <div id="metaboxes" class="hidden"> - <?php the_block_editor_meta_boxes(); ?> - </div> - <?php - /** - * Start: Include for phase 2 - */ - /** This action is documented in wp-admin/admin-footer.php */ - // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores - do_action( 'admin_print_footer_scripts-widgets.php' ); - - /** This action is documented in wp-admin/admin-footer.php */ - // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores - do_action( 'admin_footer-widgets.php' ); - /** - * End: Include for phase 2 - */ - ?> - </div> - <?php + _deprecated_function( __FUNCTION__, '5.3.0' ); } /** @@ -80,7 +44,7 @@ function gutenberg_menu() { 'Gutenberg', 'edit_posts', 'gutenberg', - 'the_gutenberg_project', + '', 'dashicons-edit' ); @@ -120,34 +84,16 @@ function gutenberg_menu() { /** * Checks whether we're currently loading a Gutenberg page * - * @return boolean Whether Gutenberg is being loaded. - * * @since 3.1.0 + * @deprecated 5.3.0 WP_Screen::is_block_editor + * + * @return boolean Whether Gutenberg is being loaded. */ function is_gutenberg_page() { - global $post; - - if ( ! is_admin() ) { - return false; - } - - /* - * There have been reports of specialized loading scenarios where `get_current_screen` - * does not exist. In these cases, it is safe to say we are not loading Gutenberg. - */ - if ( ! function_exists( 'get_current_screen' ) ) { - return false; - } - - if ( get_current_screen()->base !== 'post' ) { - return false; - } + _deprecated_function( __FUNCTION__, '5.3.0', 'WP_Screen::is_block_editor' ); - if ( ! use_block_editor_for_post( $post ) ) { - return false; - } - - return true; + require_once ABSPATH . 'wp-admin/includes/screen.php'; + return get_current_screen()->is_block_editor(); } /** @@ -198,8 +144,6 @@ function gutenberg_pre_init() { } require_once dirname( __FILE__ ) . '/lib/load.php'; - - add_filter( 'replace_editor', 'gutenberg_init', 10, 2 ); } /** @@ -207,57 +151,13 @@ function gutenberg_pre_init() { * * Load API functions, register scripts and actions, etc. * - * @param bool $return Whether to replace the editor. Used in the `replace_editor` filter. - * @param object $post The post to edit or an auto-draft. - * @return bool Whether Gutenberg was initialized. + * @deprecated 5.3.0 + * + * @return bool Whether Gutenberg was initialized. */ -function gutenberg_init( $return, $post ) { - if ( true === $return && current_filter() === 'replace_editor' ) { - return $return; - } - - if ( ! is_gutenberg_page() ) { - return false; - } - - // Instruct WordPress that this is the block editor. Without this, a call - // to `is_block_editor()` would yield `false` while editing a post with - // Gutenberg. - // - // [TODO]: This is temporary so long as Gutenberg is implemented to use - // `replace_editor`, rather than allow `edit-form-blocks.php` from core to - // take effect, where this would otherwise be assigned. - get_current_screen()->is_block_editor( true ); - - add_action( 'admin_enqueue_scripts', 'gutenberg_editor_scripts_and_styles' ); - add_filter( 'screen_options_show_screen', '__return_false' ); - - /* - * Remove the emoji script as it is incompatible with both React and any - * contenteditable fields. - */ - remove_action( 'admin_print_scripts', 'print_emoji_detection_script' ); - - /** - * Start: Include for phase 2 - */ - // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores - do_action( 'admin_print_styles-widgets.php' ); - // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores - do_action( 'admin_print_scripts-widgets.php' ); - /** - * End: Include for phase 2 - */ - - /* - * Ensure meta box functions are available to third-party code; - * includes/meta-boxes is typically loaded from edit-form-advanced.php. - */ - require_once ABSPATH . 'wp-admin/includes/meta-boxes.php'; - register_and_do_post_meta_boxes( $post ); - - require_once ABSPATH . 'wp-admin/admin-header.php'; - the_gutenberg_project(); +function gutenberg_init() { + _deprecated_function( __FUNCTION__, '5.3.0' ); - return true; + require_once ABSPATH . 'wp-admin/includes/screen.php'; + return get_current_screen()->is_block_editor(); } diff --git a/lib/client-assets.php b/lib/client-assets.php index 3b0f4ae6d0137..2a97319dd9f1c 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -299,7 +299,7 @@ function gutenberg_register_scripts_and_styles() { gutenberg_override_style( 'wp-block-library', gutenberg_url( 'build/block-library/style.css' ), - current_theme_supports( 'wp-block-styles' ) ? array( 'wp-block-library-theme' ) : array(), + array(), filemtime( gutenberg_dir_path() . 'build/block-library/style.css' ) ); wp_style_add_data( 'wp-block-library', 'rtl', 'replace' ); @@ -500,67 +500,29 @@ function gutenberg_register_vendor_script( $handle, $src, $deps = array() ) { * Assigns a default editor template with a default block by post format, if * not otherwise assigned for a new post of type "post". * - * @param array $settings Default editor settings. - * @param WP_Post $post Post being edited. + * @deprecated 5.3.0 + * + * @param array $settings Default editor settings. * * @return array Filtered block editor settings. */ -function gutenberg_default_post_format_template( $settings, $post ) { - // Only assign template for new posts without explicitly assigned template. - $is_new_post = 'auto-draft' === $post->post_status; - if ( $is_new_post && ! isset( $settings['template'] ) && 'post' === $post->post_type ) { - switch ( get_post_format() ) { - case 'audio': - $default_block_name = 'core/audio'; - break; - case 'gallery': - $default_block_name = 'core/gallery'; - break; - case 'image': - $default_block_name = 'core/image'; - break; - case 'quote': - $default_block_name = 'core/quote'; - break; - case 'video': - $default_block_name = 'core/video'; - break; - } - - if ( isset( $default_block_name ) ) { - $settings['template'] = array( array( $default_block_name ) ); - } - } +function gutenberg_default_post_format_template( $settings ) { + _deprecated_function( __FUNCTION__, '5.3.0' ); return $settings; } -add_filter( 'block_editor_settings', 'gutenberg_default_post_format_template', 10, 2 ); /** * Retrieve a stored autosave that is newer than the post save. * * Deletes autosaves that are older than the post save. * - * @param WP_Post $post Post object. + * @deprecated 5.3.0 + * * @return WP_Post|boolean The post autosave. False if none found. */ -function gutenberg_get_autosave_newer_than_post_save( $post ) { - // Add autosave data if it is newer and changed. - $autosave = wp_get_post_autosave( $post->ID ); - - if ( ! $autosave ) { - return false; - } - - // Check if the autosave is newer than the current post. - if ( - mysql2date( 'U', $autosave->post_modified_gmt, false ) > mysql2date( 'U', $post->post_modified_gmt, false ) - ) { - return $autosave; - } - - // If the autosave isn't newer, remove it. - wp_delete_post_revision( $autosave->ID ); +function gutenberg_get_autosave_newer_than_post_save() { + _deprecated_function( __FUNCTION__, '5.3.0' ); return false; } @@ -577,28 +539,14 @@ function gutenberg_load_locale_data() { /** * Retrieve The available image sizes for a post * + * @deprecated 5.3.0 + * * @return array */ function gutenberg_get_available_image_sizes() { - $size_names = apply_filters( - 'image_size_names_choose', - array( - 'thumbnail' => __( 'Thumbnail', 'gutenberg' ), - 'medium' => __( 'Medium', 'gutenberg' ), - 'large' => __( 'Large', 'gutenberg' ), - 'full' => __( 'Full Size', 'gutenberg' ), - ) - ); - - $all_sizes = array(); - foreach ( $size_names as $size_slug => $size_name ) { - $all_sizes[] = array( - 'slug' => $size_slug, - 'name' => $size_name, - ); - } + _deprecated_function( __FUNCTION__, '5.3.0' ); - return $all_sizes; + return array(); } /** @@ -661,402 +609,3 @@ function gutenberg_extend_block_editor_styles( $settings ) { return $settings; } add_filter( 'block_editor_settings', 'gutenberg_extend_block_editor_styles' ); - -/** - * Scripts & Styles. - * - * Enqueues the needed scripts and styles when visiting the top-level page of - * the Gutenberg editor. - * - * @since 0.1.0 - * - * @param string $hook Screen name. - */ -function gutenberg_editor_scripts_and_styles( $hook ) { - global $wp_meta_boxes; - - // Enqueue heartbeat separately as an "optional" dependency of the editor. - // Heartbeat is used for automatic nonce refreshing, but some hosts choose - // to disable it outright. - wp_enqueue_script( 'heartbeat' ); - - wp_enqueue_script( 'wp-edit-post' ); - wp_enqueue_script( 'wp-format-library' ); - wp_enqueue_style( 'wp-format-library' ); - - global $post; - - // Set initial title to empty string for auto draft for duration of edit. - // Otherwise, title defaults to and displays as "Auto Draft". - $is_new_post = 'auto-draft' === $post->post_status; - - // Set the post type name. - $post_type = get_post_type( $post ); - $post_type_object = get_post_type_object( $post_type ); - $rest_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name; - - $preload_paths = array( - '/', - '/wp/v2/types?context=edit', - '/wp/v2/taxonomies?per_page=-1&context=edit', - '/wp/v2/themes?status=active', - sprintf( '/wp/v2/%s/%s?context=edit', $rest_base, $post->ID ), - sprintf( '/wp/v2/types/%s?context=edit', $post_type ), - sprintf( '/wp/v2/users/me?post_type=%s&context=edit', $post_type ), - array( '/wp/v2/media', 'OPTIONS' ), - array( '/wp/v2/blocks', 'OPTIONS' ), - ); - - /** - * Preload common data by specifying an array of REST API paths that will be preloaded. - * - * Filters the array of paths that will be preloaded. - * - * @param array $preload_paths Array of paths to preload - * @param object $post The post resource data. - */ - $preload_paths = apply_filters( 'block_editor_preload_paths', $preload_paths, $post ); - - // Ensure the global $post remains the same after - // API data is preloaded. Because API preloading - // can call the_content and other filters, callbacks - // can unexpectedly modify $post resulting in issues - // like https://github.com/WordPress/gutenberg/issues/7468. - $backup_global_post = $post; - - $preload_data = array_reduce( - $preload_paths, - 'rest_preload_api_request', - array() - ); - - // Restore the global $post as it was before API preloading. - $post = $backup_global_post; - - wp_add_inline_script( - 'wp-api-fetch', - sprintf( 'wp.apiFetch.use( wp.apiFetch.createPreloadingMiddleware( %s ) );', wp_json_encode( $preload_data ) ), - 'after' - ); - - wp_add_inline_script( - 'wp-blocks', - sprintf( 'wp.blocks.setCategories( %s );', wp_json_encode( get_block_categories( $post ) ) ), - 'after' - ); - - // Assign initial edits, if applicable. These are not initially assigned - // to the persisted post, but should be included in its save payload. - if ( $is_new_post ) { - // Override "(Auto Draft)" new post default title with empty string, - // or filtered value. - $initial_edits = array( - 'title' => $post->post_title, - 'content' => $post->post_content, - 'excerpt' => $post->post_excerpt, - ); - } else { - $initial_edits = null; - } - - // Preload server-registered block schemas. - wp_add_inline_script( - 'wp-blocks', - 'wp.blocks.unstable__bootstrapServerSideBlockDefinitions(' . json_encode( get_block_editor_server_block_settings() ) . ');' - ); - - // Get admin url for handling meta boxes. - $meta_box_url = admin_url( 'post.php' ); - $meta_box_url = add_query_arg( - array( - 'post' => $post->ID, - 'action' => 'edit', - 'meta-box-loader' => true, - '_wpnonce' => wp_create_nonce( 'meta-box-loader' ), - ), - $meta_box_url - ); - wp_localize_script( 'wp-editor', '_wpMetaBoxUrl', $meta_box_url ); - - // Initialize the editor. - $align_wide = get_theme_support( 'align-wide' ); - $color_palette = current( (array) get_theme_support( 'editor-color-palette' ) ); - $font_sizes = current( (array) get_theme_support( 'editor-font-sizes' ) ); - - /** - * Filters the allowed block types for the editor, defaulting to true (all - * block types supported). - * - * @param bool|array $allowed_block_types Array of block type slugs, or - * boolean to enable/disable all. - * @param object $post The post resource data. - */ - $allowed_block_types = apply_filters( 'allowed_block_types', true, $post ); - - // Get all available templates for the post/page attributes meta-box. - // The "Default template" array element should only be added if the array is - // not empty so we do not trigger the template select element without any options - // besides the default value. - $available_templates = wp_get_theme()->get_page_templates( get_post( $post->ID ) ); - $available_templates = ! empty( $available_templates ) ? array_merge( - array( - '' => apply_filters( 'default_page_template_title', __( 'Default template', 'gutenberg' ), 'rest-api' ), - ), - $available_templates - ) : $available_templates; - - // Media settings. - $max_upload_size = wp_max_upload_size(); - if ( ! $max_upload_size ) { - $max_upload_size = 0; - } - - // Editor Styles. - global $editor_styles; - $styles = array( - array( - 'css' => file_get_contents( - ABSPATH . WPINC . '/css/dist/editor/editor-styles.css' - ), - ), - ); - - /* Translators: Use this to specify the CSS font family for the default font */ - $locale_font_family = esc_html_x( 'Noto Serif', 'CSS Font Family for Editor Font', 'gutenberg' ); - $styles[] = array( - 'css' => "body { font-family: '$locale_font_family' }", - ); - - if ( $editor_styles && current_theme_supports( 'editor-styles' ) ) { - foreach ( $editor_styles as $style ) { - if ( filter_var( $style, FILTER_VALIDATE_URL ) ) { - $styles[] = array( - 'css' => file_get_contents( $style ), - ); - } else { - $file = get_theme_file_path( $style ); - if ( file_exists( $file ) ) { - $styles[] = array( - 'css' => file_get_contents( $file ), - 'baseURL' => get_theme_file_uri( $style ), - ); - } - } - } - } - - // Lock settings. - $user_id = wp_check_post_lock( $post->ID ); - if ( $user_id ) { - /** - * Filters whether to show the post locked dialog. - * - * Returning a falsey value to the filter will short-circuit displaying the dialog. - * - * @since 3.6.0 - * - * @param bool $display Whether to display the dialog. Default true. - * @param WP_Post $post Post object. - * @param WP_User|bool $user The user id currently editing the post. - */ - if ( apply_filters( 'show_post_locked_dialog', true, $post, $user_id ) ) { - $locked = true; - } - - $user_details = null; - if ( $locked ) { - $user = get_userdata( $user_id ); - $user_details = array( - 'name' => $user->display_name, - ); - $avatar = get_avatar( $user_id, 64 ); - if ( $avatar ) { - if ( preg_match( "|src='([^']+)'|", $avatar, $matches ) ) { - $user_details['avatar'] = $matches[1]; - } - } - } - - $lock_details = array( - 'isLocked' => $locked, - 'user' => $user_details, - ); - } else { - - // Lock the post. - $active_post_lock = wp_set_post_lock( $post->ID ); - $lock_details = array( - 'isLocked' => false, - 'activePostLock' => esc_attr( implode( ':', $active_post_lock ) ), - ); - } - - /** - * Start: Include for phase 2 - */ - - /** - * Todo: The hardcoded array should be replaced with a mechanisms that allows core blocks - * and third party blocks to specify they already have equivalent blocks, and maybe even allow them - * to have a migration function. - */ - $core_widgets = array( 'WP_Widget_Pages', 'WP_Widget_Calendar', 'WP_Widget_Archives', 'WP_Widget_Media_Audio', 'WP_Widget_Media_Image', 'WP_Widget_Media_Gallery', 'WP_Widget_Media_Video', 'WP_Widget_Meta', 'WP_Widget_Search', 'WP_Widget_Text', 'WP_Widget_Categories', 'WP_Widget_Recent_Posts', 'WP_Widget_Recent_Comments', 'WP_Widget_RSS', 'WP_Widget_Tag_Cloud', 'WP_Nav_Menu_Widget', 'WP_Widget_Custom_HTML' ); - - $has_permissions_to_manage_widgets = current_user_can( 'edit_theme_options' ); - $available_legacy_widgets = array(); - global $wp_widget_factory, $wp_registered_widgets; - foreach ( $wp_widget_factory->widgets as $class => $widget_obj ) { - if ( ! in_array( $class, $core_widgets ) ) { - $available_legacy_widgets[ $class ] = array( - 'name' => html_entity_decode( $widget_obj->name ), - 'description' => html_entity_decode( $widget_obj->widget_options['description'] ), - 'isCallbackWidget' => false, - ); - } - } - foreach ( $wp_registered_widgets as $widget_id => $widget_obj ) { - if ( - is_array( $widget_obj['callback'] ) && - isset( $widget_obj['callback'][0] ) && - ( $widget_obj['callback'][0] instanceof WP_Widget ) - ) { - continue; - } - $available_legacy_widgets[ $widget_id ] = array( - 'name' => html_entity_decode( $widget_obj['name'] ), - 'description' => null, - 'isCallbackWidget' => true, - ); - } - /** - * End: Include for phase 2 - */ - - $editor_settings = array( - 'alignWide' => $align_wide, - 'availableTemplates' => $available_templates, - /** - * Start: Include for phase 2 - */ - 'hasPermissionsToManageWidgets' => $has_permissions_to_manage_widgets, - 'availableLegacyWidgets' => $available_legacy_widgets, - /** - * End: Include for phase 2 - */ - 'allowedBlockTypes' => $allowed_block_types, - 'disableCustomColors' => get_theme_support( 'disable-custom-colors' ), - 'disableCustomFontSizes' => get_theme_support( 'disable-custom-font-sizes' ), - 'disablePostFormats' => ! current_theme_supports( 'post-formats' ), - 'titlePlaceholder' => apply_filters( 'enter_title_here', __( 'Add title', 'gutenberg' ), $post ), - 'bodyPlaceholder' => apply_filters( 'write_your_story', __( 'Start writing or type / to choose a block', 'gutenberg' ), $post ), - 'isRTL' => is_rtl(), - 'autosaveInterval' => 10, - 'maxUploadFileSize' => $max_upload_size, - 'allowedMimeTypes' => get_allowed_mime_types(), - 'styles' => $styles, - 'imageSizes' => gutenberg_get_available_image_sizes(), - 'richEditingEnabled' => user_can_richedit(), - - // Ideally, we'd remove this and rely on a REST API endpoint. - 'postLock' => $lock_details, - 'postLockUtils' => array( - 'nonce' => wp_create_nonce( 'lock-post_' . $post->ID ), - 'unlockNonce' => wp_create_nonce( 'update-post_' . $post->ID ), - 'ajaxUrl' => admin_url( 'admin-ajax.php' ), - ), - - // Whether or not to load the 'postcustom' meta box is stored as a user meta - // field so that we're not always loading its assets. - 'enableCustomFields' => (bool) get_user_meta( get_current_user_id(), 'enable_custom_fields', true ), - ); - - $post_autosave = gutenberg_get_autosave_newer_than_post_save( $post ); - if ( $post_autosave ) { - $editor_settings['autosave'] = array( - 'editLink' => get_edit_post_link( $post_autosave->ID ), - ); - } - - if ( false !== $color_palette ) { - $editor_settings['colors'] = $color_palette; - } - - if ( false !== $font_sizes ) { - $editor_settings['fontSizes'] = $font_sizes; - } - - if ( ! empty( $post_type_object->template ) ) { - $editor_settings['template'] = $post_type_object->template; - $editor_settings['templateLock'] = ! empty( $post_type_object->template_lock ) ? $post_type_object->template_lock : false; - } - - $current_screen = get_current_screen(); - $core_meta_boxes = array(); - - // Make sure the current screen is set as well as the normal core metaboxes. - if ( isset( $current_screen->id ) && isset( $wp_meta_boxes[ $current_screen->id ]['normal']['core'] ) ) { - $core_meta_boxes = $wp_meta_boxes[ $current_screen->id ]['normal']['core']; - } - - // Check if the Custom Fields meta box has been removed at some point. - if ( ! isset( $core_meta_boxes['postcustom'] ) || ! $core_meta_boxes['postcustom'] ) { - unset( $editor_settings['enableCustomFields'] ); - } - - /** - * Filters the settings to pass to the block editor. - * - * @since 3.7.0 - * - * @param array $editor_settings Default editor settings. - * @param WP_Post $post Post being edited. - */ - $editor_settings = apply_filters( 'block_editor_settings', $editor_settings, $post ); - - $init_script = <<<JS -( function() { - window._wpLoadBlockEditor = new Promise( function( resolve ) { - wp.domReady( function() { - resolve( wp.editPost.initializeEditor( 'editor', "%s", %d, %s, %s ) ); - } ); - } ); -} )(); -JS; - - $script = sprintf( - $init_script, - $post->post_type, - $post->ID, - wp_json_encode( $editor_settings ), - wp_json_encode( $initial_edits ) - ); - wp_add_inline_script( 'wp-edit-post', $script ); - - /** - * Scripts - */ - wp_enqueue_media( - array( - 'post' => $post->ID, - ) - ); - wp_tinymce_inline_scripts(); - wp_enqueue_editor(); - - /** - * Styles - */ - wp_enqueue_style( 'wp-edit-post' ); - - /** - * Fires after block assets have been enqueued for the editing interface. - * - * Call `add_action` on any hook before 'admin_enqueue_scripts'. - * - * In the function call you supply, simply use `wp_enqueue_script` and - * `wp_enqueue_style` to add your functionality to the Gutenberg editor. - * - * @since 0.4.0 - */ - do_action( 'enqueue_block_editor_assets' ); -} diff --git a/lib/load.php b/lib/load.php index 6e8f9a6e101c8..f469513fd83a9 100644 --- a/lib/load.php +++ b/lib/load.php @@ -27,6 +27,7 @@ require dirname( __FILE__ ) . '/client-assets.php'; require dirname( __FILE__ ) . '/i18n.php'; require dirname( __FILE__ ) . '/demo.php'; +require dirname( __FILE__ ) . '/widgets.php'; require dirname( __FILE__ ) . '/widgets-page.php'; // Register server-side code for individual blocks. diff --git a/lib/widgets.php b/lib/widgets.php new file mode 100644 index 0000000000000..b756cce99d522 --- /dev/null +++ b/lib/widgets.php @@ -0,0 +1,123 @@ +<?php +/** + * Functions used in making widgets interopable with block editors. + * + * @package gutenberg + */ + +/** + * Emulates the Widgets screen `admin_print_styles` when at the block editor + * screen. + */ +function gutenberg_block_editor_admin_print_styles() { + if ( get_current_screen()->is_block_editor() ) { + /** This action is documented in wp-admin/admin-footer.php */ + // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores + do_action( 'admin_print_styles-widgets.php' ); + } +} +add_action( 'admin_print_styles', 'gutenberg_block_editor_admin_print_styles' ); + +/** + * Emulates the Widgets screen `admin_print_scripts` when at the block editor + * screen. + */ +function gutenberg_block_editor_admin_print_scripts() { + if ( get_current_screen()->is_block_editor() ) { + // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores + do_action( 'admin_print_scripts-widgets.php' ); + } +} +add_action( 'admin_print_scripts', 'gutenberg_block_editor_admin_print_scripts' ); + +/** + * Emulates the Widgets screen `admin_print_footer_scripts` when at the block + * editor screen. + */ +function gutenberg_block_editor_admin_print_footer_scripts() { + if ( get_current_screen()->is_block_editor() ) { + /** This action is documented in wp-admin/admin-footer.php */ + // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores + do_action( 'admin_print_footer_scripts-widgets.php' ); + } +} +add_action( 'admin_print_footer_scripts', 'gutenberg_block_editor_admin_print_footer_scripts' ); + +/** + * Emulates the Widgets screen `admin_footer` when at the block editor screen. + */ +function gutenberg_block_editor_admin_footer() { + if ( get_current_screen()->is_block_editor() ) { + /** This action is documented in wp-admin/admin-footer.php */ + // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores + do_action( 'admin_footer-widgets.php' ); + } +} +add_action( 'admin_footer', 'gutenberg_block_editor_admin_footer' ); + +/** + * Extends default editor settings with values supporting legacy widgets. + * + * @param array $settings Default editor settings. + * + * @return array Filtered editor settings. + */ +function gutenberg_legacy_widget_settings( $settings ) { + /** + * TODO: The hardcoded array should be replaced with a mechanism to allow + * core and third party blocks to specify they already have equivalent + * blocks, and maybe even allow them to have a migration function. + */ + $core_widgets = array( + 'WP_Widget_Pages', + 'WP_Widget_Calendar', + 'WP_Widget_Archives', + 'WP_Widget_Media_Audio', + 'WP_Widget_Media_Image', + 'WP_Widget_Media_Gallery', + 'WP_Widget_Media_Video', + 'WP_Widget_Meta', + 'WP_Widget_Search', + 'WP_Widget_Text', + 'WP_Widget_Categories', + 'WP_Widget_Recent_Posts', + 'WP_Widget_Recent_Comments', + 'WP_Widget_RSS', + 'WP_Widget_Tag_Cloud', + 'WP_Nav_Menu_Widget', + 'WP_Widget_Custom_HTML', + ); + + $has_permissions_to_manage_widgets = current_user_can( 'edit_theme_options' ); + $available_legacy_widgets = array(); + global $wp_widget_factory, $wp_registered_widgets; + foreach ( $wp_widget_factory->widgets as $class => $widget_obj ) { + if ( ! in_array( $class, $core_widgets ) ) { + $available_legacy_widgets[ $class ] = array( + 'name' => html_entity_decode( $widget_obj->name ), + 'description' => html_entity_decode( $widget_obj->widget_options['description'] ), + 'isCallbackWidget' => false, + ); + } + } + foreach ( $wp_registered_widgets as $widget_id => $widget_obj ) { + if ( + is_array( $widget_obj['callback'] ) && + isset( $widget_obj['callback'][0] ) && + ( $widget_obj['callback'][0] instanceof WP_Widget ) + ) { + continue; + } + $available_legacy_widgets[ $widget_id ] = array( + 'name' => html_entity_decode( $widget_obj['name'] ), + 'description' => null, + 'isCallbackWidget' => true, + ); + } + + $settings['hasPermissionsToManageWidgets'] = $has_permissions_to_manage_widgets; + $settings['availableLegacyWidgets'] = $available_legacy_widgets; + + return $settings; +} +add_filter( 'block_editor_settings', 'gutenberg_legacy_widget_settings' ); diff --git a/packages/e2e-tests/specs/plugins/meta-boxes.test.js b/packages/e2e-tests/specs/plugins/meta-boxes.test.js index b2e7649f53b57..0a21dfe20d9d7 100644 --- a/packages/e2e-tests/specs/plugins/meta-boxes.test.js +++ b/packages/e2e-tests/specs/plugins/meta-boxes.test.js @@ -9,11 +9,15 @@ import { insertBlock, openDocumentSettingsSidebar, publishPost, + saveDraft, } from '@wordpress/e2e-test-utils'; describe( 'Meta boxes', () => { beforeAll( async () => { await activatePlugin( 'gutenberg-test-plugin-meta-box' ); + } ); + + beforeEach( async () => { await createNewPost(); } ); @@ -29,23 +33,14 @@ describe( 'Meta boxes', () => { await page.type( '.editor-post-title__input', 'Hello Meta' ); expect( await page.$( '.editor-post-save-draft' ) ).not.toBe( null ); - await Promise.all( [ - // Transitions between three states "Saving..." -> "Saved" -> "Save - // Draft" (the button is always visible while meta are present). - page.waitForSelector( '.editor-post-saved-state.is-saving' ), - page.waitForSelector( '.editor-post-saved-state.is-saved' ), - page.waitForSelector( '.editor-post-save-draft' ), - - // Keyboard shortcut Ctrl+S save. - page.keyboard.down( 'Meta' ), - page.keyboard.press( 'S' ), - page.keyboard.up( 'Meta' ), - ] ); + await saveDraft(); + + // After saving, affirm that the button returns to Save Draft. + await page.waitForSelector( '.editor-post-save-draft' ); } ); it( 'Should render dynamic blocks when the meta box uses the excerpt for front end rendering', async () => { // Publish a post so there's something for the latest posts dynamic block to render. - await createNewPost(); await page.type( '.editor-post-title__input', 'A published post' ); await insertBlock( 'Paragraph' ); await page.keyboard.type( 'Hello there!' ); @@ -67,7 +62,6 @@ describe( 'Meta boxes', () => { } ); it( 'Should render the excerpt in meta based on post content if no explicit excerpt exists', async () => { - await createNewPost(); await insertBlock( 'Paragraph' ); await page.keyboard.type( 'Excerpt from content.' ); await page.type( '.editor-post-title__input', 'A published post' ); @@ -91,7 +85,6 @@ describe( 'Meta boxes', () => { } ); it( 'Should render the explicitly set excerpt in meta instead of the content based one', async () => { - await createNewPost(); await insertBlock( 'Paragraph' ); await page.keyboard.type( 'Excerpt from content.' ); await page.type( '.editor-post-title__input', 'A published post' ); diff --git a/phpunit/class-core-block-theme-test.php b/phpunit/class-core-block-theme-test.php deleted file mode 100644 index dc378dd85a4ea..0000000000000 --- a/phpunit/class-core-block-theme-test.php +++ /dev/null @@ -1,106 +0,0 @@ -<?php -/** - * Core block theme tests. - * - * @package Gutenberg - */ - -/** - * Test inclusion of opt-in core block theme. - */ -class Core_Block_Theme_Test extends WP_UnitTestCase { - private $old_wp_styles; - private $old_wp_scripts; - - function setUp() { - parent::setUp(); - - $this->old_wp_scripts = isset( $GLOBALS['wp_scripts'] ) ? $GLOBALS['wp_scripts'] : null; - remove_action( 'wp_default_scripts', 'wp_default_scripts' ); - - $GLOBALS['wp_scripts'] = new WP_Scripts(); - $GLOBALS['wp_scripts']->default_version = get_bloginfo( 'version' ); - - $this->old_wp_styles = isset( $GLOBALS['wp_styles'] ) ? $GLOBALS['wp_styles'] : null; - remove_action( 'wp_default_styles', 'wp_default_styles' ); - - $GLOBALS['wp_styles'] = new WP_Styles(); - $GLOBALS['wp_styles']->default_version = get_bloginfo( 'version' ); - } - - function tearDown() { - $GLOBALS['wp_scripts'] = $this->old_wp_scripts; - add_action( 'wp_default_scripts', 'wp_default_scripts' ); - - $GLOBALS['wp_styles'] = $this->old_wp_styles; - add_action( 'wp_default_styles', 'wp_default_styles' ); - - if ( current_theme_supports( 'wp-block-styles' ) ) { - remove_theme_support( 'wp-block-styles' ); - } - - parent::tearDown(); - } - - /** - * Tests that visual block styles are enqueued in the editor even when there is not theme support for 'wp-block-styles'. - * - * Visual block styles should always be enqueued when editing to avoid the appearance of a broken editor. - */ - function test_block_styles_for_editing_without_theme_support() { - // Confirm we are without theme support by default. - $this->assertFalse( current_theme_supports( 'wp-block-styles' ) ); - - gutenberg_register_scripts_and_styles(); - - $this->assertFalse( wp_style_is( 'wp-block-library-theme' ) ); - wp_enqueue_style( 'wp-edit-blocks' ); - $this->assertTrue( wp_style_is( 'wp-block-library-theme' ) ); - } - - /** - * Tests that visual block styles are enqueued when there is theme support for 'wp-block-styles'. - * - * Visual block styles should always be enqueued when editing to avoid the appearance of a broken editor. - */ - function test_block_styles_for_editing_with_theme_support() { - add_theme_support( 'wp-block-styles' ); - gutenberg_register_scripts_and_styles(); - - $this->assertFalse( wp_style_is( 'wp-block-library-theme' ) ); - wp_enqueue_style( 'wp-edit-blocks' ); - $this->assertTrue( wp_style_is( 'wp-block-library-theme' ) ); - } - - /** - * Tests that visual block styles are not enqueued for viewing when there is no theme support for 'wp-block-styles'. - * - * Visual block styles should not be enqueued unless a theme opts in. - * This way we avoid style conflicts with existing themes. - */ - function test_no_block_styles_for_viewing_without_theme_support() { - // Confirm we are without theme support by default. - $this->assertFalse( current_theme_supports( 'wp-block-styles' ) ); - - gutenberg_register_scripts_and_styles(); - - $this->assertFalse( wp_style_is( 'wp-block-library-theme' ) ); - wp_enqueue_style( 'wp-block-library' ); - $this->assertFalse( wp_style_is( 'wp-block-library-theme' ) ); - } - - /** - * Tests that visual block styles are enqueued for viewing when there is theme support for 'wp-block-styles'. - * - * Visual block styles should be enqueued when a theme opts in. - */ - function test_block_styles_for_viewing_with_theme_support() { - add_theme_support( 'wp-block-styles' ); - - gutenberg_register_scripts_and_styles(); - - $this->assertFalse( wp_style_is( 'wp-block-library-theme' ) ); - wp_enqueue_style( 'wp-block-library' ); - $this->assertTrue( wp_style_is( 'wp-block-library-theme' ) ); - } -} From 68ef5e7c9d36d19b00cb62e8e6d394d7500bf734 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Thu, 14 Mar 2019 10:35:52 +0100 Subject: [PATCH 651/691] Add docs for React hooks (#14425) --- packages/element/README.md | 40 +++++++++++++++++------ packages/element/src/react.js | 60 +++++++++++++++++++++++++++-------- 2 files changed, 77 insertions(+), 23 deletions(-) diff --git a/packages/element/README.md b/packages/element/README.md index c39eb933ec8b9..1985045799bea 100755 --- a/packages/element/README.md +++ b/packages/element/README.md @@ -318,61 +318,81 @@ Removes any mounted element from the target DOM node. [src/index.js#L1-L1](src/index.js#L1-L1) -Make React Hooks available +**Related** + +- <https://reactjs.org/docs/hooks-reference.html#usecallback> ### useContext [src/index.js#L1-L1](src/index.js#L1-L1) -Make React Hooks available +**Related** + +- <https://reactjs.org/docs/hooks-reference.html#usecontext> ### useDebugValue [src/index.js#L1-L1](src/index.js#L1-L1) -Make React Hooks available +**Related** + +- <https://reactjs.org/docs/hooks-reference.html#usedebugvalue> ### useEffect [src/index.js#L1-L1](src/index.js#L1-L1) -Make React Hooks available +**Related** + +- <https://reactjs.org/docs/hooks-reference.html#useeffect> ### useImperativeHandle [src/index.js#L1-L1](src/index.js#L1-L1) -Make React Hooks available +**Related** + +- <https://reactjs.org/docs/hooks-reference.html#useimperativehandle> ### useLayoutEffect [src/index.js#L1-L1](src/index.js#L1-L1) -Make React Hooks available +**Related** + +- <https://reactjs.org/docs/hooks-reference.html#uselayouteffect> ### useMemo [src/index.js#L1-L1](src/index.js#L1-L1) -Make React Hooks available +**Related** + +- <https://reactjs.org/docs/hooks-reference.html#usememo> ### useReducer [src/index.js#L1-L1](src/index.js#L1-L1) -Make React Hooks available +**Related** + +- <https://reactjs.org/docs/hooks-reference.html#usereducer> ### useRef [src/index.js#L1-L1](src/index.js#L1-L1) -Make React Hooks available +**Related** + +- <https://reactjs.org/docs/hooks-reference.html#useref> ### useState [src/index.js#L1-L1](src/index.js#L1-L1) -Make React Hooks available +**Related** + +- <https://reactjs.org/docs/hooks-reference.html#usestate> <!-- END TOKEN(Autogenerated API docs) --> diff --git a/packages/element/src/react.js b/packages/element/src/react.js index a2fbd1cda3671..b4b9ef4146533 100644 --- a/packages/element/src/react.js +++ b/packages/element/src/react.js @@ -110,20 +110,54 @@ export { isValidElement }; export { StrictMode }; /** - * Make React Hooks available + * @see https://reactjs.org/docs/hooks-reference.html#usecallback */ -export { - useCallback, - useContext, - useDebugValue, - useEffect, - useImperativeHandle, - useLayoutEffect, - useMemo, - useReducer, - useRef, - useState, -}; +export { useCallback }; + +/** + * @see https://reactjs.org/docs/hooks-reference.html#usecontext + */ +export { useContext }; + +/** + * @see https://reactjs.org/docs/hooks-reference.html#usedebugvalue + */ +export { useDebugValue }; + +/** + * @see https://reactjs.org/docs/hooks-reference.html#useeffect + */ +export { useEffect }; + +/** + * @see https://reactjs.org/docs/hooks-reference.html#useimperativehandle + */ +export { useImperativeHandle }; + +/** + * @see https://reactjs.org/docs/hooks-reference.html#uselayouteffect + */ +export { useLayoutEffect }; + +/** + * @see https://reactjs.org/docs/hooks-reference.html#usememo + */ +export { useMemo }; + +/** + * @see https://reactjs.org/docs/hooks-reference.html#usereducer + */ +export { useReducer }; + +/** + * @see https://reactjs.org/docs/hooks-reference.html#useref + */ +export { useRef }; + +/** + * @see https://reactjs.org/docs/hooks-reference.html#usestate + */ +export { useState }; /** * Concatenate two or more React children objects. From 13334d70466e30dc5f22d112ca7f961b3c529eca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <iseulde@automattic.com> Date: Thu, 14 Mar 2019 10:42:46 +0100 Subject: [PATCH 652/691] Use <s> for strikethrough, not <del> (#14389) * Use <s> for strikethrough, not <del> * Update e2e test --- packages/e2e-tests/specs/__snapshots__/rich-text.test.js.snap | 2 +- packages/format-library/src/strikethrough/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/e2e-tests/specs/__snapshots__/rich-text.test.js.snap b/packages/e2e-tests/specs/__snapshots__/rich-text.test.js.snap index b408c3acfc60d..f5c79ddbd33ac 100644 --- a/packages/e2e-tests/specs/__snapshots__/rich-text.test.js.snap +++ b/packages/e2e-tests/specs/__snapshots__/rich-text.test.js.snap @@ -8,7 +8,7 @@ exports[`RichText should apply formatting when selection is collapsed 1`] = ` exports[`RichText should apply formatting with access shortcut 1`] = ` "<!-- wp:paragraph --> -<p><del>test</del></p> +<p><s>test</s></p> <!-- /wp:paragraph -->" `; diff --git a/packages/format-library/src/strikethrough/index.js b/packages/format-library/src/strikethrough/index.js index 636b1b1d9122c..244fc513143e2 100644 --- a/packages/format-library/src/strikethrough/index.js +++ b/packages/format-library/src/strikethrough/index.js @@ -11,7 +11,7 @@ const name = 'core/strikethrough'; export const strikethrough = { name, title: __( 'Strikethrough' ), - tagName: 'del', + tagName: 's', className: null, edit( { isActive, value, onChange } ) { const onToggle = () => onChange( toggleFormat( value, { type: name } ) ); From 589a634f6c5a0e0569d500fd387415f2ba6abfa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= <aljullu@gmail.com> Date: Thu, 14 Mar 2019 13:10:32 +0100 Subject: [PATCH 653/691] Align Spinner circles in RTL locales (#14418) --- packages/components/src/spinner/style.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/components/src/spinner/style.scss b/packages/components/src/spinner/style.scss index 286b8204edd7d..da5bc3c15c168 100644 --- a/packages/components/src/spinner/style.scss +++ b/packages/components/src/spinner/style.scss @@ -10,6 +10,7 @@ position: relative; &::before { + /* rtl:begin:ignore */ content: ""; position: absolute; background-color: $white; @@ -20,6 +21,7 @@ border-radius: 100%; transform-origin: 6px 6px; animation: components-spinner__animation 1s infinite linear; + /* rtl:end:ignore */ } } From dc03691681a060bdd1529114a5b18306ab7f49a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Thu, 14 Mar 2019 17:39:59 +0100 Subject: [PATCH 654/691] Improve handling of transpiled packages in unit tests (#14432) --- package.json | 2 +- .../test/index.js | 2 +- .../components/src/disabled/test/index.js | 2 +- test/unit/jest.config.js | 30 +++++++++++++++++++ test/unit/jest.config.json | 22 -------------- 5 files changed, 33 insertions(+), 25 deletions(-) create mode 100644 test/unit/jest.config.js delete mode 100644 test/unit/jest.config.json diff --git a/package.json b/package.json index 6e071182be1a5..f9ada126e5e1b 100644 --- a/package.json +++ b/package.json @@ -186,7 +186,7 @@ "test-e2e": "wp-scripts test-e2e --config packages/e2e-tests/jest.config.js", "test-e2e:watch": "npm run test-e2e -- --watch", "test-php": "npm run lint-php && npm run test-unit-php", - "test-unit": "wp-scripts test-unit-js --config test/unit/jest.config.json", + "test-unit": "wp-scripts test-unit-js --config test/unit/jest.config.js", "test-unit:update": "npm run test-unit -- --updateSnapshot", "test-unit:watch": "npm run test-unit -- --watch", "test-unit-php": "docker-compose run --rm wordpress_phpunit phpunit", diff --git a/packages/block-serialization-default-parser/test/index.js b/packages/block-serialization-default-parser/test/index.js index 72f4121ce1944..068db2ef14c72 100644 --- a/packages/block-serialization-default-parser/test/index.js +++ b/packages/block-serialization-default-parser/test/index.js @@ -12,7 +12,7 @@ import { jsTester, phpTester } from '@wordpress/block-serialization-spec-parser/ /** * Internal dependencies */ -import { parse } from '../'; +import { parse } from '../src'; describe( 'block-serialization-default-parser-js', jsTester( parse ) ); diff --git a/packages/components/src/disabled/test/index.js b/packages/components/src/disabled/test/index.js index 36fdf3a85524d..bd7c556163e4e 100644 --- a/packages/components/src/disabled/test/index.js +++ b/packages/components/src/disabled/test/index.js @@ -14,7 +14,7 @@ import { Component } from '@wordpress/element'; import Disabled from '../'; jest.mock( '@wordpress/dom', () => { - const focus = require.requireActual( '@wordpress/dom' ).focus; + const focus = require.requireActual( '../../../../dom/src' ).focus; return { focus: { diff --git a/test/unit/jest.config.js b/test/unit/jest.config.js new file mode 100644 index 0000000000000..6c5db3c8db011 --- /dev/null +++ b/test/unit/jest.config.js @@ -0,0 +1,30 @@ +/** + * External dependencies + */ +const glob = require( 'glob' ).sync; + +// Finds all packages which are transpiled with Babel to force Jest to use their source code. +const transpiledPackageNames = glob( 'packages/*/src/index.js' ) + .map( ( fileName ) => fileName.split( '/' )[ 1 ] ); + +module.exports = { + rootDir: '../../', + moduleNameMapper: { + [ `@wordpress\\/(${ transpiledPackageNames.join( '|' ) })$` ]: 'packages/$1/src', + }, + preset: '@wordpress/jest-preset-default', + setupFiles: [ + 'core-js/fn/symbol/async-iterator', + '<rootDir>/test/unit/config/gutenberg-phase.js', + ], + testURL: 'http://localhost', + testPathIgnorePatterns: [ + '/node_modules/', + '/packages/e2e-tests', + '<rootDir>/.*/build/', + '<rootDir>/.*/build-module/', + ], + transformIgnorePatterns: [ + 'node_modules/(?!(simple-html-tokenizer)/)', + ], +}; diff --git a/test/unit/jest.config.json b/test/unit/jest.config.json deleted file mode 100644 index 3b7d0ca732a99..0000000000000 --- a/test/unit/jest.config.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "rootDir": "../../", - "moduleNameMapper": { - "@wordpress\\/(block-serialization-spec-parser|is-shallow-equal)$": "packages/$1", - "@wordpress\\/([a-z0-9-]+)$": "packages/$1/src" - }, - "preset": "@wordpress/jest-preset-default", - "setupFiles": [ - "core-js/fn/symbol/async-iterator", - "<rootDir>/test/unit/config/gutenberg-phase.js" - ], - "testURL": "http://localhost", - "testPathIgnorePatterns": [ - "/node_modules/", - "/packages/e2e-tests", - "<rootDir>/.*/build/", - "<rootDir>/.*/build-module/" - ], - "transformIgnorePatterns": [ - "node_modules/(?!(simple-html-tokenizer)/)" - ] -} From 244d7ceed9fb9d867175afdd965121e690f57856 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Thu, 14 Mar 2019 18:51:26 +0100 Subject: [PATCH 655/691] Scripts: use default babel if none is found in project (#14168) --- bin/packages/build.js | 4 +- bin/packages/get-babel-config.js | 96 +++++++++-------------- package.json | 2 +- packages/babel-preset-default/index.js | 74 ++++++++++++----- packages/scripts/config/webpack.config.js | 13 ++- packages/scripts/utils/config.js | 1 + 6 files changed, 104 insertions(+), 86 deletions(-) diff --git a/bin/packages/build.js b/bin/packages/build.js index 8a13f8f10b048..bb6954b4102e7 100755 --- a/bin/packages/build.js +++ b/bin/packages/build.js @@ -164,9 +164,7 @@ function buildScssFile( styleFile ) { function buildJsFileFor( file, silent, environment ) { const buildDir = BUILD_DIR[ environment ]; const destPath = getBuildPath( file, buildDir ); - const babelOptions = getBabelConfig( environment ); - babelOptions.sourceMaps = true; - babelOptions.sourceFileName = file.replace( PACKAGES_DIR, '@wordpress' ); + const babelOptions = getBabelConfig( environment, file.replace( PACKAGES_DIR, '@wordpress' ) ); mkdirp.sync( path.dirname( destPath ) ); const transformed = babel.transformFileSync( file, babelOptions ); diff --git a/bin/packages/get-babel-config.js b/bin/packages/get-babel-config.js index b2646da46955c..d76e171d46b21 100644 --- a/bin/packages/get-babel-config.js +++ b/bin/packages/get-babel-config.js @@ -1,64 +1,38 @@ -/** - * External dependencies - */ -const { get, map } = require( 'lodash' ); -const babel = require( '@babel/core' ); - -/** - * WordPress dependencies - */ -const { options: babelDefaultConfig } = babel.loadPartialConfig( { - configFile: '@wordpress/babel-preset-default', -} ); -const { plugins, presets } = babelDefaultConfig; - -const overrideOptions = ( target, targetName, options ) => { - if ( get( target, [ 'file', 'request' ] ) === targetName ) { - return [ targetName, Object.assign( - {}, - target.options, - options - ) ]; +module.exports = function( environment = '', file ) { + /* + * Specific options to be passed using the caller config option: + * https://babeljs.io/docs/en/options#caller + * + * The caller options can only be 'boolean', 'string', or 'number' by design: + * https://github.com/babel/babel/blob/bd0c62dc0c30cf16a4d4ef0ddf21d386f673815c/packages/babel-core/src/config/validation/option-assertions.js#L122 + */ + const callerOpts = { caller: { + name: `WP_BUILD_${ environment.toUpperCase() }`, + } }; + switch ( environment ) { + case 'main': + // to be merged as a presetEnv option + callerOpts.caller.modules = 'commonjs'; + break; + case 'module': + // to be merged as a presetEnv option + callerOpts.caller.modules = false; + // to be merged as a pluginTransformRuntime option + callerOpts.caller.useESModules = true; + break; + default: + // preventing measure, this shouldn't happen ever + delete callerOpts.caller; } - return target; -}; -const babelConfigs = { - main: Object.assign( - {}, - babelDefaultConfig, - { - plugins, - presets: map( - presets, - ( preset ) => overrideOptions( preset, '@babel/preset-env', { - modules: 'commonjs', - } ) - ), - } - ), - module: Object.assign( - {}, - babelDefaultConfig, - { - plugins: map( - plugins, - ( plugin ) => overrideOptions( plugin, '@babel/plugin-transform-runtime', { - useESModules: true, - } ) - ), - presets: map( - presets, - ( preset ) => overrideOptions( preset, '@babel/preset-env', { - modules: false, - } ) - ), - } - ), -}; - -function getBabelConfig( environment ) { - return babelConfigs[ environment ]; -} + // Sourcemaps options + const sourceMapsOpts = { + sourceMaps: true, + sourceFileName: file, + }; -module.exports = getBabelConfig; + return { + ...callerOpts, + ...sourceMapsOpts, + }; +}; diff --git a/package.json b/package.json index f9ada126e5e1b..71bf946b7694e 100644 --- a/package.json +++ b/package.json @@ -152,7 +152,7 @@ }, "scripts": { "prebuild": "npm run check-engines", - "clean:packages": "rimraf ./packages/*/build ./packages/*/build-module ./packages/*/build-style", + "clean:packages": "rimraf ./packages/*/build ./packages/*/build-module ./packages/*/build-style ./packages/*/node_modules", "prebuild:packages": "npm run clean:packages && lerna run build", "build:packages": "node ./bin/packages/build.js", "build": "npm run build:packages && wp-scripts build", diff --git a/packages/babel-preset-default/index.js b/packages/babel-preset-default/index.js index 8a893ee9c5ea7..5c7f22d98439a 100644 --- a/packages/babel-preset-default/index.js +++ b/packages/babel-preset-default/index.js @@ -1,36 +1,72 @@ module.exports = function( api ) { + let wpBuildOpts = {}; + const isWPBuild = ( name ) => [ 'WP_BUILD_MAIN', 'WP_BUILD_MODULE' ].some( + ( buildName ) => name === buildName + ); + const isTestEnv = api.env() === 'test'; + api.caller( ( caller ) => { + if ( caller && isWPBuild( caller.name ) ) { + wpBuildOpts = { ...caller }; + return caller.name; + } + return undefined; + } ); + + const getPresetEnv = () => { + const opts = {}; + + if ( isTestEnv ) { + opts.useBuiltIns = 'usage'; + } else { + opts.modules = false; + opts.targets = { + browsers: require( '@wordpress/browserslist-config' ), + }; + } + + if ( isWPBuild( wpBuildOpts.name ) ) { + opts.modules = wpBuildOpts.modules; + } + + return [ require.resolve( '@babel/preset-env' ), opts ]; + }; + + const maybeGetPluginTransformRuntime = () => { + if ( isTestEnv ) { + return undefined; + } + + const opts = { + helpers: true, + useESModules: false, + }; + + if ( wpBuildOpts.name === 'WP_BUILD_MODULE' ) { + opts.useESModules = wpBuildOpts.useESModules; + } + + return [ require.resolve( '@babel/plugin-transform-runtime' ), opts ]; + }; + return { - presets: [ - ! isTestEnv && [ '@babel/preset-env', { - modules: false, - targets: { - browsers: [ 'extends @wordpress/browserslist-config' ], - }, - } ], - isTestEnv && [ '@babel/preset-env', { - useBuiltIns: 'usage', - } ], - ].filter( Boolean ), + presets: [ getPresetEnv() ], plugins: [ - '@babel/plugin-proposal-object-rest-spread', + require.resolve( '@babel/plugin-proposal-object-rest-spread' ), [ - '@wordpress/babel-plugin-import-jsx-pragma', + require.resolve( '@wordpress/babel-plugin-import-jsx-pragma' ), { scopeVariable: 'createElement', source: '@wordpress/element', isDefault: false, }, ], - [ '@babel/plugin-transform-react-jsx', { + [ require.resolve( '@babel/plugin-transform-react-jsx' ), { pragma: 'createElement', } ], - '@babel/plugin-proposal-async-generator-functions', - ! isTestEnv && [ '@babel/plugin-transform-runtime', { - helpers: true, - useESModules: false, - } ], + require.resolve( '@babel/plugin-proposal-async-generator-functions' ), + maybeGetPluginTransformRuntime(), ].filter( Boolean ), }; }; diff --git a/packages/scripts/config/webpack.config.js b/packages/scripts/config/webpack.config.js index 70fed30229ba4..9a1daabf5d752 100644 --- a/packages/scripts/config/webpack.config.js +++ b/packages/scripts/config/webpack.config.js @@ -8,7 +8,7 @@ const path = require( 'path' ); /** * Internal dependencies */ -const { camelCaseDash } = require( '../utils' ); +const { camelCaseDash, hasBabelConfig } = require( '../utils' ); /** * Converts @wordpress/* string request into request object. @@ -66,6 +66,12 @@ const externals = [ const isProduction = process.env.NODE_ENV === 'production'; const mode = isProduction ? 'production' : 'development'; +const getBabelLoaderOptions = () => hasBabelConfig() ? {} : { + babelrc: false, + configFile: false, + presets: [ require.resolve( '@wordpress/babel-preset-default' ) ], +}; + const config = { mode, entry: { @@ -91,7 +97,10 @@ const config = { { test: /\.js$/, exclude: /node_modules/, - use: require.resolve( 'babel-loader' ), + use: { + loader: require.resolve( 'babel-loader' ), + options: getBabelLoaderOptions(), + }, }, ], }, diff --git a/packages/scripts/utils/config.js b/packages/scripts/utils/config.js index 0c5ed70c293ce..3ae98725e5fbc 100644 --- a/packages/scripts/utils/config.js +++ b/packages/scripts/utils/config.js @@ -7,6 +7,7 @@ const { hasPackageProp } = require( './package' ); const hasBabelConfig = () => hasProjectFile( '.babelrc' ) || + hasProjectFile( '.babelrc.js' ) || hasProjectFile( 'babel.config.js' ) || hasPackageProp( 'babel' ); From 7964f381e07e06b2ac13698fd1465a4d0c16da1b Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Thu, 14 Mar 2019 14:12:17 -0400 Subject: [PATCH 656/691] Block Editor: Update Block Editor classNames to convention (#14420) --- assets/stylesheets/_z-index.scss | 36 +-- .../components/block-compare/block-view.js | 10 +- .../src/components/block-compare/index.js | 10 +- .../src/components/block-compare/style.scss | 18 +- .../test/__snapshots__/block-view.js.snap | 10 +- .../src/components/block-drop-zone/index.js | 2 +- .../src/components/block-drop-zone/style.scss | 2 +- .../src/components/block-icon/index.js | 2 +- .../src/components/block-icon/style.scss | 2 +- .../src/components/block-inspector/index.js | 12 +- .../src/components/block-inspector/style.scss | 14 +- .../components/block-list-appender/style.scss | 2 +- .../block-list/block-contextual-toolbar.js | 2 +- .../src/components/block-list/block-html.js | 2 +- .../block-list/block-invalid-warning.js | 2 +- .../block-list/block-mobile-toolbar.js | 2 +- .../src/components/block-list/block.js | 8 +- .../src/components/block-list/breadcrumb.js | 4 +- .../src/components/block-list/index.js | 2 +- .../components/block-list/insertion-point.js | 6 +- .../src/components/block-list/style.scss | 244 +++++++++--------- .../src/components/block-mover/drag-handle.js | 2 +- .../src/components/block-mover/index.js | 16 +- .../src/components/block-mover/style.scss | 22 +- .../src/components/block-mover/test/index.js | 12 +- .../components/block-navigation/dropdown.js | 2 +- .../src/components/block-navigation/index.js | 10 +- .../components/block-navigation/style.scss | 20 +- .../src/components/block-preview/index.js | 6 +- .../src/components/block-preview/style.scss | 6 +- .../block-convert-button.js | 2 +- .../block-settings-menu/block-mode-toggle.js | 2 +- .../components/block-settings-menu/index.js | 16 +- .../reusable-block-convert-button.js | 4 +- .../reusable-block-delete-button.js | 2 +- .../components/block-settings-menu/style.scss | 12 +- .../reusable-block-delete-button.js.snap | 2 +- .../src/components/block-styles/index.js | 8 +- .../src/components/block-styles/style.scss | 10 +- .../src/components/block-switcher/index.js | 10 +- .../src/components/block-switcher/style.scss | 52 ++-- .../test/__snapshots__/index.js.snap | 10 +- .../src/components/block-toolbar/index.js | 4 +- .../src/components/block-toolbar/style.scss | 2 +- .../src/components/block-types-list/index.js | 2 +- .../components/block-types-list/style.scss | 2 +- .../src/components/color-palette/control.js | 4 +- .../src/components/color-palette/control.scss | 2 +- .../test/__snapshots__/control.js.snap | 4 +- .../src/components/contrast-checker/index.js | 2 +- .../components/contrast-checker/style.scss | 2 +- .../test/__snapshots__/index.js.snap | 12 +- .../default-block-appender/index.js | 4 +- .../default-block-appender/style.scss | 32 +-- .../test/__snapshots__/index.js.snap | 12 +- .../src/components/inner-blocks/index.js | 2 +- .../src/components/inner-blocks/style.scss | 4 +- .../components/inserter-list-item/index.js | 12 +- .../components/inserter-list-item/style.scss | 24 +- .../inserter-with-shortcuts/index.js | 4 +- .../inserter-with-shortcuts/style.scss | 4 +- .../src/components/inserter/child-blocks.js | 4 +- .../src/components/inserter/index.js | 6 +- .../src/components/inserter/menu.js | 16 +- .../src/components/inserter/style.scss | 32 +-- .../src/components/inserter/test/menu.js | 24 +- .../src/components/media-placeholder/index.js | 16 +- .../components/media-placeholder/style.scss | 14 +- .../multi-selection-inspector/index.js | 8 +- .../multi-selection-inspector/style.scss | 10 +- .../src/components/observe-typing/index.js | 2 +- .../components/panel-color-settings/index.js | 6 +- .../panel-color-settings/style.scss | 2 +- .../test/__snapshots__/index.js.snap | 12 +- .../src/components/plain-text/index.js | 2 +- .../src/components/plain-text/index.native.js | 4 +- .../components/plain-text/style.native.scss | 2 +- .../src/components/plain-text/style.scss | 2 +- .../src/components/rich-text/editable.js | 2 +- .../rich-text/format-toolbar/index.js | 2 +- .../rich-text/format-toolbar/style.scss | 6 +- .../src/components/rich-text/index.js | 6 +- .../src/components/rich-text/index.native.js | 4 +- .../components/rich-text/style.native.scss | 2 +- .../src/components/rich-text/style.scss | 10 +- .../skip-to-selected-block/index.js | 2 +- .../skip-to-selected-block/style.scss | 2 +- .../src/components/url-input/button.js | 8 +- .../src/components/url-input/index.js | 12 +- .../src/components/url-input/style.scss | 22 +- .../src/components/url-input/test/button.js | 4 +- .../src/components/url-popover/index.js | 8 +- .../src/components/url-popover/style.scss | 10 +- .../test/__snapshots__/index.js.snap | 18 +- .../src/components/url-popover/test/index.js | 2 +- .../src/components/warning/index.js | 12 +- .../src/components/warning/style.scss | 12 +- .../warning/test/__snapshots__/index.js.snap | 6 +- .../src/components/warning/test/index.js | 12 +- .../src/components/writing-flow/index.js | 4 +- .../src/components/writing-flow/style.scss | 4 +- packages/block-editor/src/utils/dom.js | 8 +- packages/block-editor/src/utils/test/dom.js | 12 +- .../src/block/edit-panel/style.scss | 4 +- .../src/block/indicator/style.scss | 4 +- packages/block-library/src/button/editor.scss | 18 +- .../block-library/src/classic/editor.scss | 16 +- packages/block-library/src/code/editor.scss | 2 +- .../block-library/src/columns/editor.scss | 22 +- packages/block-library/src/cover/editor.scss | 12 +- packages/block-library/src/embed/style.scss | 4 +- .../embed/test/__snapshots__/index.js.snap | 2 +- .../block-library/src/gallery/editor.scss | 10 +- packages/block-library/src/html/editor.scss | 2 +- packages/block-library/src/image/editor.scss | 12 +- .../block-library/src/media-text/editor.scss | 4 +- packages/block-library/src/more/editor.scss | 2 +- .../block-library/src/nextpage/editor.scss | 2 +- .../block-library/src/paragraph/editor.scss | 2 +- .../block-library/src/pullquote/editor.scss | 8 +- .../block-library/src/shortcode/editor.scss | 2 +- packages/block-library/src/table/editor.scss | 2 +- .../src/text-columns/editor.scss | 2 +- packages/block-library/src/video/editor.scss | 2 +- .../components/src/placeholder/style.scss | 2 +- .../src/positioned-at-selection/index.js | 2 +- .../src/click-block-appender.js | 2 +- .../src/click-block-toolbar-button.js | 2 +- .../src/get-all-block-inserter-item-titles.js | 2 +- .../src/get-available-block-transforms.js | 4 +- .../e2e-test-utils/src/has-block-switcher.js | 2 +- .../src/open-all-block-inserter-categories.js | 2 +- .../src/open-global-block-inserter.js | 2 +- .../e2e-test-utils/src/transform-block-to.js | 6 +- packages/e2e-tests/specs/a11y.test.js | 2 +- .../e2e-tests/specs/adding-blocks.test.js | 22 +- .../e2e-tests/specs/block-deletion.test.js | 8 +- .../specs/block-hierarchy-navigation.test.js | 4 +- packages/e2e-tests/specs/block-mover.test.js | 12 +- .../e2e-tests/specs/blocks/columns.test.js | 2 +- .../e2e-tests/specs/blocks/media-text.test.js | 2 +- .../specs/convert-block-type.test.js | 2 +- packages/e2e-tests/specs/editor-modes.test.js | 16 +- .../e2e-tests/specs/invalid-block.test.js | 6 +- .../specs/keyboard-navigable-blocks.test.js | 10 +- packages/e2e-tests/specs/links.test.js | 54 ++-- .../e2e-tests/specs/navigable-toolbar.test.js | 2 +- .../__snapshots__/plugins-api.test.js.snap | 2 +- .../specs/plugins/align-hook.test.js | 8 +- .../specs/plugins/annotations.test.js | 6 +- .../specs/plugins/block-icons.test.js | 10 +- .../specs/plugins/container-blocks.test.js | 4 +- .../inner-blocks-allowed-blocks.test.js | 2 +- .../e2e-tests/specs/reusable-blocks.test.js | 16 +- .../e2e-tests/specs/splitting-merging.test.js | 2 +- .../e2e-tests/specs/style-variation.test.js | 2 +- .../header/header-toolbar/style.scss | 8 +- .../src/components/sidebar/style.scss | 2 +- .../visual-editor/block-inspector-button.js | 2 +- .../src/components/visual-editor/style.scss | 26 +- packages/edit-post/src/style.scss | 4 +- .../src/components/autocompleters/style.scss | 14 +- packages/format-library/src/image/index.js | 4 +- packages/format-library/src/image/style.scss | 4 +- packages/format-library/src/link/inline.js | 6 +- packages/format-library/src/link/style.scss | 4 +- 166 files changed, 765 insertions(+), 767 deletions(-) diff --git a/assets/stylesheets/_z-index.scss b/assets/stylesheets/_z-index.scss index 87bc9ea5575de..6b1a15b95ec7b 100644 --- a/assets/stylesheets/_z-index.scss +++ b/assets/stylesheets/_z-index.scss @@ -3,37 +3,37 @@ // value is designed to work with). $z-layers: ( - ".editor-block-list__block-edit::before": 0, - ".editor-block-switcher__arrow": 1, - ".editor-block-list__block {core/image aligned wide or fullwide}": 20, + ".block-editor-block-list__block-edit::before": 0, + ".block-editor-block-switcher__arrow": 1, + ".block-editor-block-list__block {core/image aligned wide or fullwide}": 20, ".block-library-classic__toolbar": 10, - ".editor-block-list__layout .reusable-block-indicator": 1, - ".editor-block-list__breadcrumb": 2, + ".block-editor-block-list__layout .reusable-block-indicator": 1, + ".block-editor-block-list__breadcrumb": 2, ".components-form-toggle__input": 1, ".components-panel__header.edit-post-sidebar__panel-tabs": -1, ".edit-post-sidebar .components-panel": -2, - ".editor-inserter__tabs": 1, - ".editor-inserter__tab.is-active": 1, + ".block-editor-inserter__tabs": 1, + ".block-editor-inserter__tab.is-active": 1, ".components-panel__header": 1, ".components-modal__header": 10, ".edit-post-meta-boxes-area.is-loading::before": 1, ".edit-post-meta-boxes-area .spinner": 5, - ".editor-block-contextual-toolbar": 21, + ".block-editor-block-contextual-toolbar": 21, ".components-popover__close": 5, - ".editor-block-list__insertion-point": 6, - ".editor-inserter-with-shortcuts": 5, - ".editor-warning": 5, + ".block-editor-block-list__insertion-point": 6, + ".block-editor-inserter-with-shortcuts": 5, + ".block-editor-warning": 5, ".block-library-gallery-item__inline-menu": 20, - ".editor-url-input__suggestions": 30, + ".block-editor-url-input__suggestions": 30, ".edit-post-header": 30, - ".block-library-button__inline-link .editor-url-input__suggestions": 6, // URL suggestions for button block above sibling inserter + ".block-library-button__inline-link .block-editor-url-input__suggestions": 6, // URL suggestions for button block above sibling inserter ".block-library-image__resize-handlers": 1, // Resize handlers above sibling inserter ".wp-block-cover__inner-container": 1, // InnerBlocks area inside cover image block ".wp-block-cover.has-background-dim::before": 1, // Overlay area inside block cover need to be higher than the video background. ".wp-block-cover__video-background": 0, // Video background inside cover block. // Side UI active buttons - ".editor-block-mover__control": 1, + ".block-editor-block-mover__control": 1, // Active pill button ".components-button.is-button {:focus or .is-primary}": 1, @@ -43,7 +43,7 @@ $z-layers: ( // Should have higher index than the inset/underlay used for dragging ".components-placeholder__fieldset": 1, - ".editor-block-list__block-edit .reusable-block-edit-panel *": 1, + ".block-editor-block-list__block-edit .reusable-block-edit-panel *": 1, // Show drop zone above most standard content, but below any overlays ".components-drop-zone": 100, @@ -51,14 +51,14 @@ $z-layers: ( // The block mover, particularly in nested contexts, // should overlap most block content. - ".editor-block-list__block.is-{selected,hovered} .editor-block-mover": 80, + ".block-editor-block-list__block.is-{selected,hovered} .block-editor-block-mover": 80, // The block mover for floats should overlap the controls of adjacent blocks. - ".editor-block-list__block {core/image aligned left or right}": 81, + ".block-editor-block-list__block {core/image aligned left or right}": 81, // Small screen inner blocks overlay must be displayed above drop zone, // settings menu, and movers. - ".editor-inner-blocks__small-screen-overlay:after": 120, + ".block-editor-inner-blocks__small-screen-overlay:after": 120, // Show sidebar above wp-admin navigation bar for mobile viewports: // #wpadminbar { z-index: 99999 } diff --git a/packages/block-editor/src/components/block-compare/block-view.js b/packages/block-editor/src/components/block-compare/block-view.js index 0c8189a32baa3..274c2896d98a9 100644 --- a/packages/block-editor/src/components/block-compare/block-view.js +++ b/packages/block-editor/src/components/block-compare/block-view.js @@ -6,19 +6,19 @@ import { Button } from '@wordpress/components'; const BlockView = ( { title, rawContent, renderedContent, action, actionText, className } ) => { return ( <div className={ className }> - <div className="editor-block-compare__content"> - <h2 className="editor-block-compare__heading">{ title }</h2> + <div className="editor-block-compare__content block-editor-block-compare__content"> + <h2 className="editor-block-compare__heading block-editor-block-compare__heading">{ title }</h2> - <div className="editor-block-compare__html"> + <div className="editor-block-compare__html block-editor-block-compare__html"> { rawContent } </div> - <div className="editor-block-compare__preview edit-post-visual-editor"> + <div className="editor-block-compare__preview block-editor-block-compare__preview edit-post-visual-editor"> { renderedContent } </div> </div> - <div className="editor-block-compare__action"> + <div className="editor-block-compare__action block-editor-block-compare__action"> <Button isLarge tabIndex="0" onClick={ action }>{ actionText }</Button> </div> </div> diff --git a/packages/block-editor/src/components/block-compare/index.js b/packages/block-editor/src/components/block-compare/index.js index 0782976519201..1b294649e9c62 100644 --- a/packages/block-editor/src/components/block-compare/index.js +++ b/packages/block-editor/src/components/block-compare/index.js @@ -23,8 +23,8 @@ class BlockCompare extends Component { return difference.map( ( item, pos ) => { const classes = classnames( { - 'editor-block-compare__added': item.added, - 'editor-block-compare__removed': item.removed, + 'editor-block-compare__added block-editor-block-compare__added': item.added, + 'editor-block-compare__removed block-editor-block-compare__removed': item.removed, } ); return <span key={ pos } className={ classes }>{ item.value }</span>; @@ -59,10 +59,10 @@ class BlockCompare extends Component { const difference = this.getDifference( original.rawContent, converted.rawContent ); return ( - <div className="editor-block-compare__wrapper"> + <div className="editor-block-compare__wrapper block-editor-block-compare__wrapper"> <BlockView title={ __( 'Current' ) } - className="editor-block-compare__current" + className="editor-block-compare__current block-editor-block-compare__current" action={ onKeep } actionText={ __( 'Convert to HTML' ) } rawContent={ original.rawContent } @@ -71,7 +71,7 @@ class BlockCompare extends Component { <BlockView title={ __( 'After Conversion' ) } - className="editor-block-compare__converted" + className="editor-block-compare__converted block-editor-block-compare__converted" action={ onConvert } actionText={ convertButtonText } rawContent={ difference } diff --git a/packages/block-editor/src/components/block-compare/style.scss b/packages/block-editor/src/components/block-compare/style.scss index dd6242be2640a..f520ee566097b 100644 --- a/packages/block-editor/src/components/block-compare/style.scss +++ b/packages/block-editor/src/components/block-compare/style.scss @@ -3,7 +3,7 @@ */ // Ensure the modal fits the content, otherwise it could be too big -.editor-block-compare { +.block-editor-block-compare { overflow: auto; height: auto; @@ -12,7 +12,7 @@ } } -.editor-block-compare__wrapper { +.block-editor-block-compare__wrapper { display: flex; padding-bottom: $panel-padding; @@ -29,12 +29,12 @@ } } - .editor-block-compare__converted { + .block-editor-block-compare__converted { border-left: 1px solid #ddd; padding-left: 15px; } - .editor-block-compare__html { + .block-editor-block-compare__html { font-family: $editor-html-font; font-size: 12px; color: $dark-gray-800; @@ -48,16 +48,16 @@ padding-bottom: 3px; } - span.editor-block-compare__added { + span.block-editor-block-compare__added { background-color: #acf2bd; } - span.editor-block-compare__removed { + span.block-editor-block-compare__removed { background-color: $alert-red; } } - .editor-block-compare__preview { + .block-editor-block-compare__preview { padding: 0; padding-top: $block-padding; @@ -67,11 +67,11 @@ } } - .editor-block-compare__action { + .block-editor-block-compare__action { margin-top: $block-padding; } - .editor-block-compare__heading { + .block-editor-block-compare__heading { font-size: 1em; font-weight: 400; margin: 0.67em 0; diff --git a/packages/block-editor/src/components/block-compare/test/__snapshots__/block-view.js.snap b/packages/block-editor/src/components/block-compare/test/__snapshots__/block-view.js.snap index 5177c366d7006..f28df1bbb5548 100644 --- a/packages/block-editor/src/components/block-compare/test/__snapshots__/block-view.js.snap +++ b/packages/block-editor/src/components/block-compare/test/__snapshots__/block-view.js.snap @@ -5,26 +5,26 @@ exports[`BlockView should match snapshot 1`] = ` className="class" > <div - className="editor-block-compare__content" + className="editor-block-compare__content block-editor-block-compare__content" > <h2 - className="editor-block-compare__heading" + className="editor-block-compare__heading block-editor-block-compare__heading" > title </h2> <div - className="editor-block-compare__html" + className="editor-block-compare__html block-editor-block-compare__html" > raw </div> <div - className="editor-block-compare__preview edit-post-visual-editor" + className="editor-block-compare__preview block-editor-block-compare__preview edit-post-visual-editor" > render </div> </div> <div - className="editor-block-compare__action" + className="editor-block-compare__action block-editor-block-compare__action" > <ForwardRef(Button) isLarge={true} diff --git a/packages/block-editor/src/components/block-drop-zone/index.js b/packages/block-editor/src/components/block-drop-zone/index.js index 4434a6c338a97..fc71ee0a0c7d2 100644 --- a/packages/block-editor/src/components/block-drop-zone/index.js +++ b/packages/block-editor/src/components/block-drop-zone/index.js @@ -120,7 +120,7 @@ class BlockDropZone extends Component { return ( <MediaUploadCheck> <DropZone - className={ classnames( 'editor-block-drop-zone', { + className={ classnames( 'editor-block-drop-zone block-editor-block-drop-zone', { 'is-appender': isAppender, } ) } onFilesDrop={ this.onFilesDrop } diff --git a/packages/block-editor/src/components/block-drop-zone/style.scss b/packages/block-editor/src/components/block-drop-zone/style.scss index e07fdf66bf96a..5eef575111152 100644 --- a/packages/block-editor/src/components/block-drop-zone/style.scss +++ b/packages/block-editor/src/components/block-drop-zone/style.scss @@ -1,5 +1,5 @@ // Dropzones -.editor-block-drop-zone { +.block-editor-block-drop-zone { border: none; border-radius: 0; diff --git a/packages/block-editor/src/components/block-icon/index.js b/packages/block-editor/src/components/block-icon/index.js index d0a9cd044fb02..4d6106214df7d 100644 --- a/packages/block-editor/src/components/block-icon/index.js +++ b/packages/block-editor/src/components/block-icon/index.js @@ -26,7 +26,7 @@ export default function BlockIcon( { icon, showColors = false, className } ) { <span style={ style } className={ classnames( - 'editor-block-icon', + 'editor-block-icon block-editor-block-icon', className, { 'has-colors': showColors } ) } diff --git a/packages/block-editor/src/components/block-icon/style.scss b/packages/block-editor/src/components/block-icon/style.scss index d929f4a7370d8..07eff392cca18 100644 --- a/packages/block-editor/src/components/block-icon/style.scss +++ b/packages/block-editor/src/components/block-icon/style.scss @@ -1,4 +1,4 @@ -.editor-block-icon { +.block-editor-block-icon { display: flex; align-items: center; justify-content: center; diff --git a/packages/block-editor/src/components/block-inspector/index.js b/packages/block-editor/src/components/block-inspector/index.js index f7cca99eb5af4..fc54f9d174e61 100644 --- a/packages/block-editor/src/components/block-inspector/index.js +++ b/packages/block-editor/src/components/block-inspector/index.js @@ -34,16 +34,16 @@ const BlockInspector = ( { selectedBlockClientId, selectedBlockName, blockType, * because we want the user to focus on the unregistered block warning, not block settings. */ if ( ! blockType || ! selectedBlockClientId || isSelectedBlockUnregistered ) { - return <span className="editor-block-inspector__no-blocks">{ __( 'No block selected.' ) }</span>; + return <span className="editor-block-inspector__no-blocks block-editor-block-inspector__no-blocks">{ __( 'No block selected.' ) }</span>; } return ( <Fragment> - <div className="editor-block-inspector__card"> + <div className="editor-block-inspector__card block-editor-block-inspector__card"> <BlockIcon icon={ blockType.icon } showColors /> - <div className="editor-block-inspector__card-content"> - <div className="editor-block-inspector__card-title">{ blockType.title }</div> - <div className="editor-block-inspector__card-description">{ blockType.description }</div> + <div className="editor-block-inspector__card-content block-editor-block-inspector__card-content"> + <div className="editor-block-inspector__card-title block-editor-block-inspector__card-title">{ blockType.title }</div> + <div className="editor-block-inspector__card-description block-editor-block-inspector__card-description">{ blockType.description }</div> </div> </div> { hasBlockStyles && ( @@ -63,7 +63,7 @@ const BlockInspector = ( { selectedBlockClientId, selectedBlockName, blockType, <InspectorAdvancedControls.Slot> { ( fills ) => ! isEmpty( fills ) && ( <PanelBody - className="editor-block-inspector__advanced" + className="editor-block-inspector__advanced block-editor-block-inspector__advanced" title={ __( 'Advanced' ) } initialOpen={ false } > diff --git a/packages/block-editor/src/components/block-inspector/style.scss b/packages/block-editor/src/components/block-inspector/style.scss index e238a08561684..2fdb6d04b44aa 100644 --- a/packages/block-editor/src/components/block-inspector/style.scss +++ b/packages/block-editor/src/components/block-inspector/style.scss @@ -1,4 +1,4 @@ -.editor-block-inspector__no-blocks { +.block-editor-block-inspector__no-blocks { display: block; font-size: $default-font-size; background: $white; @@ -7,14 +7,14 @@ } -.editor-block-inspector__card { +.block-editor-block-inspector__card { display: flex; align-items: flex-start; margin: -16px; padding: 16px; } -.editor-block-inspector__card-icon { +.block-editor-block-inspector__card-icon { border: $border-width solid $light-gray-700; padding: 7px; margin-right: 10px; @@ -22,20 +22,20 @@ width: 36px; } -.editor-block-inspector__card-content { +.block-editor-block-inspector__card-content { flex-grow: 1; } -.editor-block-inspector__card-title { +.block-editor-block-inspector__card-title { font-weight: 500; margin-bottom: 5px; } -.editor-block-inspector__card-description { +.block-editor-block-inspector__card-description { font-size: $default-font-size; } -.editor-block-inspector__card .editor-block-icon { +.block-editor-block-inspector__card .block-editor-block-icon { margin-left: -2px; margin-right: 10px; padding: 0 3px; diff --git a/packages/block-editor/src/components/block-list-appender/style.scss b/packages/block-editor/src/components/block-list-appender/style.scss index a31989fcc5432..9f53f7572f179 100644 --- a/packages/block-editor/src/components/block-list-appender/style.scss +++ b/packages/block-editor/src/components/block-list-appender/style.scss @@ -1,4 +1,4 @@ -.block-list-appender > .editor-inserter { +.block-list-appender > .block-editor-inserter { display: block; } diff --git a/packages/block-editor/src/components/block-list/block-contextual-toolbar.js b/packages/block-editor/src/components/block-list/block-contextual-toolbar.js index 8e6726e78f118..36575d90f9ab5 100644 --- a/packages/block-editor/src/components/block-list/block-contextual-toolbar.js +++ b/packages/block-editor/src/components/block-list/block-contextual-toolbar.js @@ -13,7 +13,7 @@ function BlockContextualToolbar( { focusOnMount } ) { return ( <NavigableToolbar focusOnMount={ focusOnMount } - className="editor-block-contextual-toolbar" + className="editor-block-contextual-toolbar block-editor-block-contextual-toolbar" /* translators: accessibility text for the block toolbar */ aria-label={ __( 'Block tools' ) } > diff --git a/packages/block-editor/src/components/block-list/block-html.js b/packages/block-editor/src/components/block-list/block-html.js index 9ec13410b367a..ce70122701828 100644 --- a/packages/block-editor/src/components/block-list/block-html.js +++ b/packages/block-editor/src/components/block-list/block-html.js @@ -56,7 +56,7 @@ export class BlockHTML extends Component { const { html } = this.state; return ( <TextareaAutosize - className="editor-block-list__block-html-textarea" + className="editor-block-list__block-html-textarea block-editor-block-list__block-html-textarea" value={ html } onBlur={ this.onBlur } onChange={ this.onChange } diff --git a/packages/block-editor/src/components/block-list/block-invalid-warning.js b/packages/block-editor/src/components/block-list/block-invalid-warning.js index 7d9efe1693523..6cd4bd8773d14 100644 --- a/packages/block-editor/src/components/block-list/block-invalid-warning.js +++ b/packages/block-editor/src/components/block-list/block-invalid-warning.js @@ -52,7 +52,7 @@ export class BlockInvalidWarning extends Component { __( 'Resolve Block' ) } onRequestClose={ this.onCompareClose } - className="editor-block-compare" + className="editor-block-compare block-editor-block-compare" > <BlockCompare block={ block } diff --git a/packages/block-editor/src/components/block-list/block-mobile-toolbar.js b/packages/block-editor/src/components/block-list/block-mobile-toolbar.js index 53b17ebb2786c..f35a7add73641 100644 --- a/packages/block-editor/src/components/block-list/block-mobile-toolbar.js +++ b/packages/block-editor/src/components/block-list/block-mobile-toolbar.js @@ -11,7 +11,7 @@ import VisualEditorInserter from '../inserter'; function BlockMobileToolbar( { clientId } ) { return ( - <div className="editor-block-list__block-mobile-toolbar"> + <div className="editor-block-list__block-mobile-toolbar block-editor-block-list__block-mobile-toolbar"> <VisualEditorInserter /> <BlockMover clientIds={ [ clientId ] } /> </div> diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index 2b828a34cdd93..c5abcdc5deee3 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -431,7 +431,7 @@ export class BlockListBlock extends Component { // The wp-block className is important for editor styles. // Generate the wrapper class names handling the different states of the block. const wrapperClassName = classnames( - 'wp-block editor-block-list__block', + 'wp-block editor-block-list__block block-editor-block-list__block', { 'has-warning': ! isValid || !! error || isUnregisteredBlock, 'is-selected': shouldAppearSelected, @@ -522,7 +522,7 @@ export class BlockListBlock extends Component { { isFirstMultiSelected && ( <BlockMultiControls rootClientId={ rootClientId } /> ) } - <div className="editor-block-list__block-edit"> + <div className="editor-block-list__block-edit block-editor-block-list__block-edit"> { shouldRenderMovers && ( <BlockMover clientIds={ clientId } @@ -595,14 +595,14 @@ export class BlockListBlock extends Component { </div> { showEmptyBlockSideInserter && ( <Fragment> - <div className="editor-block-list__side-inserter"> + <div className="editor-block-list__side-inserter block-editor-block-list__side-inserter"> <InserterWithShortcuts clientId={ clientId } rootClientId={ rootClientId } onToggle={ this.selectOnOpen } /> </div> - <div className="editor-block-list__empty-block-inserter"> + <div className="editor-block-list__empty-block-inserter block-editor-block-list__empty-block-inserter"> <Inserter position="top right" onToggle={ this.selectOnOpen } diff --git a/packages/block-editor/src/components/block-list/breadcrumb.js b/packages/block-editor/src/components/block-list/breadcrumb.js index 544ef992daf1f..0f22df00312b3 100644 --- a/packages/block-editor/src/components/block-list/breadcrumb.js +++ b/packages/block-editor/src/components/block-list/breadcrumb.js @@ -51,12 +51,12 @@ export class BlockBreadcrumb extends Component { const { clientId, rootClientId } = this.props; return ( - <div className={ 'editor-block-list__breadcrumb' }> + <div className={ 'editor-block-list__breadcrumb block-editor-block-list__breadcrumb' }> <Toolbar> { rootClientId && ( <Fragment> <BlockTitle clientId={ rootClientId } /> - <span className="editor-block-list__descendant-arrow" /> + <span className="editor-block-list__descendant-arrow block-editor-block-list__descendant-arrow" /> </Fragment> ) } <BlockTitle clientId={ clientId } /> diff --git a/packages/block-editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js index cc998be715c68..5231dcb71749b 100644 --- a/packages/block-editor/src/components/block-list/index.js +++ b/packages/block-editor/src/components/block-list/index.js @@ -199,7 +199,7 @@ class BlockList extends Component { } = this.props; return ( - <div className="editor-block-list__layout"> + <div className="editor-block-list__layout block-editor-block-list__layout"> { map( blockClientIds, ( clientId, blockIndex ) => { const isBlockInSelection = hasMultiSelection ? multiSelectedBlockClientIds.includes( clientId ) : diff --git a/packages/block-editor/src/components/block-list/insertion-point.js b/packages/block-editor/src/components/block-list/insertion-point.js index b0c7b5b270f16..daa1a0f408f4e 100644 --- a/packages/block-editor/src/components/block-list/insertion-point.js +++ b/packages/block-editor/src/components/block-list/insertion-point.js @@ -51,9 +51,9 @@ class BlockInsertionPoint extends Component { } = this.props; return ( - <div className="editor-block-list__insertion-point"> + <div className="editor-block-list__insertion-point block-editor-block-list__insertion-point"> { showInsertionPoint && ( - <div className="editor-block-list__insertion-point-indicator" /> + <div className="editor-block-list__insertion-point-indicator block-editor-block-list__insertion-point-indicator" /> ) } <div onFocus={ this.onFocusInserter } @@ -66,7 +66,7 @@ class BlockInsertionPoint extends Component { // See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus tabIndex={ -1 } className={ - classnames( 'editor-block-list__insertion-point-inserter', { + classnames( 'editor-block-list__insertion-point-inserter block-editor-block-list__insertion-point-inserter', { 'is-visible': isInserterFocused, } ) } diff --git a/packages/block-editor/src/components/block-list/style.scss b/packages/block-editor/src/components/block-list/style.scss index 043ddd2613607..bca7a3dc5f654 100644 --- a/packages/block-editor/src/components/block-list/style.scss +++ b/packages/block-editor/src/components/block-list/style.scss @@ -1,35 +1,35 @@ -.editor-block-list__layout .components-draggable__clone { +.block-editor-block-list__layout .components-draggable__clone { // Hide the Block UI when dragging the block. // This ensures the page scroll properly (no sticky elements). - .editor-block-contextual-toolbar { + .block-editor-block-contextual-toolbar { // It's probably okay to use !important here to avoid over-complicating the selector. display: none !important; } } -.editor-block-list__layout .editor-block-list__block.is-selected { // Needs specificity to override inherited styles. +.block-editor-block-list__layout .block-editor-block-list__block.is-selected { // Needs specificity to override inherited styles. // While block is being dragged, dim the slot dragged from, and hide some UI. &.is-dragging { - .editor-block-list__block-edit::before { + .block-editor-block-list__block-edit::before { outline: none; } - > .editor-block-list__block-edit > * { + > .block-editor-block-list__block-edit > * { background: $light-gray-100; } - > .editor-block-list__block-edit > * > * { + > .block-editor-block-list__block-edit > * > * { visibility: hidden; } - .editor-block-mover, - .editor-block-contextual-toolbar { + .block-editor-block-mover, + .block-editor-block-contextual-toolbar { display: none; } } - > .editor-block-list__block-edit .reusable-block-edit-panel * { - z-index: z-index(".editor-block-list__block-edit .reusable-block-edit-panel *"); + > .block-editor-block-list__block-edit .reusable-block-edit-panel * { + z-index: z-index(".block-editor-block-list__block-edit .reusable-block-edit-panel *"); } } @@ -38,7 +38,7 @@ * General layout */ -.editor-block-list__layout { +.block-editor-block-list__layout { // Make room in the main content column for the side UI. // The side UI uses negative margins to position itself so as to not affect the block width. @include break-small() { @@ -47,7 +47,7 @@ } // Don't add side padding for nested blocks. - .editor-block-list__block & { + .block-editor-block-list__block & { // Compensate for side UI. padding-left: 0; padding-right: 0; @@ -59,15 +59,15 @@ // Space every block, and the default appender, using margins. // This allows margins to collapse, which gives a better representation of how it looks on the frontend. - .editor-default-block-appender > .editor-default-block-appender__content, - > .editor-block-list__block > .editor-block-list__block-edit, - > .editor-block-list__layout > .editor-block-list__block > .editor-block-list__block-edit { + .block-editor-default-block-appender > .block-editor-default-block-appender__content, + > .block-editor-block-list__block > .block-editor-block-list__block-edit, + > .block-editor-block-list__layout > .block-editor-block-list__block > .block-editor-block-list__block-edit { margin-top: $block-padding * 2 + $block-spacing; margin-bottom: $block-padding * 2 + $block-spacing; } } -.editor-block-list__layout .editor-block-list__block { +.block-editor-block-list__layout .block-editor-block-list__block { position: relative; padding-left: $block-padding; padding-right: $block-padding; @@ -108,11 +108,11 @@ * Block outline layout */ - .editor-block-list__block-edit { + .block-editor-block-list__block-edit { position: relative; &::before { - z-index: z-index(".editor-block-list__block-edit::before"); + z-index: z-index(".block-editor-block-list__block-edit::before"); content: ""; position: absolute; outline: $border-width solid transparent; @@ -128,7 +128,7 @@ } // Selected style. - &.is-selected > .editor-block-list__block-edit::before { + &.is-selected > .block-editor-block-list__block-edit::before { // Use opacity to work in various editor styles. outline: $border-width solid $dark-opacity-light-500; @@ -138,7 +138,7 @@ } // Hover style. - &.is-hovered > .editor-block-list__block-edit::before { + &.is-hovered > .block-editor-block-list__block-edit::before { outline: $border-width solid theme(outlines); } @@ -147,7 +147,7 @@ opacity: 0.5; transition: opacity 0.1s linear; - &:not(.is-focused) .editor-block-list__block, + &:not(.is-focused) .block-editor-block-list__block, &.is-focused { opacity: 1; } @@ -159,7 +159,7 @@ * Cross-block selection */ -.editor-block-list__layout .editor-block-list__block { +.block-editor-block-list__layout .block-editor-block-list__block { ::-moz-selection { background-color: $blue-medium-highlight; } @@ -173,7 +173,7 @@ background-color: transparent; } - &.is-multi-selected .editor-block-list__block-edit::before { + &.is-multi-selected .block-editor-block-list__block-edit::before { background: $blue-medium-highlight; // Use opacity to work in various editor styles. @@ -194,13 +194,13 @@ * Block styles and alignments */ -.editor-block-list__layout .editor-block-list__block { +.block-editor-block-list__layout .block-editor-block-list__block { &.has-warning { min-height: ( $block-padding + $block-spacing ) * 2; } // Warnings - &.has-warning .editor-block-list__block-edit { + &.has-warning .block-editor-block-list__block-edit { // When a block has a warning, you shouldn't be able to manipulate the contents. > * { pointer-events: none; @@ -208,12 +208,12 @@ } // Allow the warning action buttons to be manipulable. - .editor-warning { + .block-editor-warning { pointer-events: all; } } - &.has-warning:not(.is-hovered) .editor-block-list__block-edit::before { + &.has-warning:not(.is-hovered) .block-editor-block-list__block-edit::before { // Use opacity to work in various editor styles. outline-color: $dark-opacity-light-500; @@ -222,7 +222,7 @@ } } - &.has-warning .editor-block-list__block-edit::after { + &.has-warning .block-editor-block-list__block-edit::after { content: ""; position: absolute; background-color: rgba($light-gray-100, 0.4); @@ -234,11 +234,11 @@ } // Avoid conflict with the multi-selection highlight color. - &.has-warning.is-multi-selected .editor-block-list__block-edit::after { + &.has-warning.is-multi-selected .block-editor-block-list__block-edit::after { background-color: transparent; } - &.has-warning.is-selected .editor-block-list__block-edit::after { + &.has-warning.is-selected .block-editor-block-list__block-edit::after { bottom: ( $block-toolbar-height - $block-padding - $border-width ); @include break-small() { @@ -247,19 +247,19 @@ } // Appender - &.is-typing .editor-block-list__empty-block-inserter, - &.is-typing .editor-block-list__side-inserter { + &.is-typing .block-editor-block-list__empty-block-inserter, + &.is-typing .block-editor-block-list__side-inserter { opacity: 0; animation: none; } - .editor-block-list__empty-block-inserter, - .editor-block-list__side-inserter { + .block-editor-block-list__empty-block-inserter, + .block-editor-block-list__side-inserter { @include edit-post__fade-in-animation; } // Reusable blocks - &.is-reusable > .editor-block-list__block-edit::before { + &.is-reusable > .block-editor-block-list__block-edit::before { // Use opacity to work in various editor styles. outline: $border-width dashed $dark-opacity-light-500; @@ -272,14 +272,14 @@ &[data-align="left"], &[data-align="right"] { // Without z-index, won't be clickable as "above" adjacent content. - z-index: z-index(".editor-block-list__block {core/image aligned left or right}"); + z-index: z-index(".block-editor-block-list__block {core/image aligned left or right}"); width: 100%; // When images are floated, the block itself should collapse to zero height. height: 0; // Hide block outline when an image is floated. - .editor-block-list__block-edit { + .block-editor-block-list__block-edit { &::before { content: none; } @@ -289,12 +289,12 @@ } // Keep a 1px margin to compensate for the border/outline. - .editor-block-contextual-toolbar { + .block-editor-block-contextual-toolbar { margin-bottom: $border-width; } // Position toolbar better on mobile. - .editor-block-contextual-toolbar { + .block-editor-block-contextual-toolbar { width: auto; border-bottom: $border-width solid $light-gray-500; bottom: auto; @@ -302,20 +302,20 @@ } // Unlike most explicit left/right alignments, this one should be flipped by the auto-RTL system. - &[data-align="left"] .editor-block-contextual-toolbar { + &[data-align="left"] .block-editor-block-contextual-toolbar { left: 0; right: auto; } - &[data-align="right"] .editor-block-contextual-toolbar { + &[data-align="right"] .block-editor-block-contextual-toolbar { left: auto; right: 0; } // Position the sticky toolbar correctly beyond the mobile breakpoint. @include break-small() { - &[data-align="right"] .editor-block-contextual-toolbar, - &[data-align="left"] .editor-block-contextual-toolbar { + &[data-align="right"] .block-editor-block-contextual-toolbar, + &[data-align="left"] .block-editor-block-contextual-toolbar { top: $block-padding; } } @@ -323,7 +323,7 @@ // Left &[data-align="left"] { // This is in the editor only; the image should be floated on the frontend. - .editor-block-list__block-edit { + .block-editor-block-list__block-edit { /*!rtl:begin:ignore*/ float: left; margin-right: 2em; @@ -332,7 +332,7 @@ // Align block toolbar to floated content. @include break-small() { - .editor-block-toolbar { + .block-editor-block-toolbar { /*!rtl:begin:ignore*/ left: $block-padding; right: auto; @@ -344,7 +344,7 @@ // Right &[data-align="right"] { // Right: This is in the editor only; the image should be floated on the frontend. - > .editor-block-list__block-edit { + > .block-editor-block-list__block-edit { /*!rtl:begin:ignore*/ float: right; margin-left: 2em; @@ -353,7 +353,7 @@ // Align block toolbar to floated content. @include break-small() { - .editor-block-toolbar { + .block-editor-block-toolbar { /*!rtl:begin:ignore*/ right: $block-padding; left: auto; @@ -368,10 +368,10 @@ clear: both; // Without z-index, the block toolbar will be below an adjecent float - z-index: z-index(".editor-block-list__block {core/image aligned wide or fullwide}"); + z-index: z-index(".block-editor-block-list__block {core/image aligned wide or fullwide}"); // Mover and settings above - > .editor-block-mover { + > .block-editor-block-mover { // This moves the menu up by the height of the button + border + padding. top: -$block-side-ui-width - $block-padding - $block-side-ui-clearance; bottom: auto; @@ -385,22 +385,22 @@ } } - > .editor-block-mover .editor-block-mover__control { + > .block-editor-block-mover .block-editor-block-mover__control { float: left; } // Position hover label on the right - > .editor-block-list__breadcrumb { + > .block-editor-block-list__breadcrumb { right: -$border-width; } // Hide mover until wide breakpoints, or it might be covered by toolbar - > .editor-block-mover { + > .block-editor-block-mover { display: none; } @include break-wide() { - > .editor-block-mover { + > .block-editor-block-mover { display: block; } } @@ -408,7 +408,7 @@ // Beyond the mobile breakpoint, wide images stretch outside of the column. // To center the toolbar, we make it inline-flex so the toolbar is not full-wide. @include break-small () { - .editor-block-toolbar { + .block-editor-block-toolbar { display: inline-flex; } } @@ -417,7 +417,7 @@ // Wide &[data-align="wide"] { // Position mover - > .editor-block-mover { + > .block-editor-block-mover { left: -$block-padding + $border-width; } } @@ -425,7 +425,7 @@ // Full-wide &[data-align="full"] { // Position hover label on the right for the top level block. - > .editor-block-list__block-edit > .editor-block-list__breadcrumb { + > .block-editor-block-list__block-edit > .block-editor-block-list__breadcrumb { right: 0; } @@ -435,7 +435,7 @@ margin-right: -$block-side-ui-width - $block-padding - $block-side-ui-clearance - $border-width; } - > .editor-block-list__block-edit { + > .block-editor-block-list__block-edit { margin-left: -$block-padding; margin-right: -$block-padding; @@ -451,7 +451,7 @@ } } - > .editor-block-list__block-edit::before { + > .block-editor-block-list__block-edit::before { left: 0; right: 0; border-left-width: 0; @@ -459,7 +459,7 @@ } // Position mover - > .editor-block-mover { + > .block-editor-block-mover { left: $border-width; } } @@ -470,7 +470,7 @@ } // Dropzones - .editor-block-drop-zone { + .block-editor-block-drop-zone { top: -4px; bottom: -3px; margin: 0 $block-padding; @@ -479,13 +479,13 @@ // Hide appender shortcuts in nested blocks // This essentially duplicates the mobile styles for the appender component // It would be nice to be able to use element queries in that component instead https://github.com/tomhodgins/element-queries-spec - .editor-block-list__layout { - .editor-inserter-with-shortcuts { + .block-editor-block-list__layout { + .block-editor-inserter-with-shortcuts { display: none; } - .editor-block-list__empty-block-inserter, - .editor-default-block-appender .editor-inserter { + .block-editor-block-list__empty-block-inserter, + .block-editor-default-block-appender .block-editor-inserter { left: auto; right: $grid-size; } @@ -497,11 +497,11 @@ * Left and right side UI; Unified toolbar on Mobile */ -.editor-block-list__block { +.block-editor-block-list__block { // Left and right block settings and mover. - &.is-multi-selected > .editor-block-mover, - > .editor-block-list__block-edit > .editor-block-mover { + &.is-multi-selected > .block-editor-block-mover, + > .block-editor-block-list__block-edit > .block-editor-block-mover { position: absolute; width: $block-side-ui-width + $block-side-ui-clearance; @@ -511,8 +511,8 @@ } // Position depending on whether selected or not. - &.is-multi-selected > .editor-block-mover, - > .editor-block-list__block-edit > .editor-block-mover { + &.is-multi-selected > .block-editor-block-mover, + > .block-editor-block-list__block-edit > .block-editor-block-mover { top: -$block-padding - $border-width; } @@ -521,15 +521,15 @@ &.is-multi-selected, &.is-selected, &.is-hovered { - .editor-block-mover { - z-index: z-index(".editor-block-list__block.is-{selected,hovered} .editor-block-mover"); + .block-editor-block-mover { + z-index: z-index(".block-editor-block-list__block.is-{selected,hovered} .block-editor-block-mover"); } } } // Left side UI. - &.is-multi-selected > .editor-block-mover, - > .editor-block-list__block-edit > .editor-block-mover { + &.is-multi-selected > .block-editor-block-mover, + > .block-editor-block-list__block-edit > .block-editor-block-mover { padding-right: $block-side-ui-clearance; // Position for top level blocks. @@ -542,7 +542,7 @@ } } - &.is-multi-selected > .editor-block-mover { + &.is-multi-selected > .block-editor-block-mover { left: -$block-side-ui-width - $block-side-ui-clearance; } @@ -550,7 +550,7 @@ &[data-align="left"], &[data-align="right"] { // Show always when the block is selected. - &.is-selected > .editor-block-list__block-edit > .editor-block-mover { + &.is-selected > .block-editor-block-list__block-edit > .block-editor-block-mover { // Don't show on mobile, allow the special mobile toolbar to work there. display: none; @include break-small() { @@ -571,8 +571,8 @@ } // Don't show on hover, or on the "ghost" when dragging. - &.is-hovered > .editor-block-list__block-edit > .editor-block-mover, - &.is-dragging > .editor-block-list__block-edit > .editor-block-mover { + &.is-hovered > .block-editor-block-list__block-edit > .block-editor-block-mover, + &.is-dragging > .block-editor-block-list__block-edit > .block-editor-block-mover { display: none; } } @@ -583,10 +583,10 @@ * Mobile unified toolbar. */ -.editor-block-list__block { +.block-editor-block-list__block { // Show side UI inline below the block on mobile. - .editor-block-list__block-mobile-toolbar { + .block-editor-block-list__block-mobile-toolbar { display: flex; flex-direction: row; @@ -609,15 +609,15 @@ } // Movers, inserter, trash, and ellipsis. - .editor-inserter { + .block-editor-inserter { position: relative; left: auto; top: auto; margin: 0; } - .editor-inserter__toggle, - .editor-block-mover__control { + .block-editor-inserter__toggle, + .block-editor-block-mover__control { width: $icon-button-size; height: $icon-button-size; border-radius: $radius-round-rectangle; @@ -632,19 +632,19 @@ } // Movers - .editor-block-mover { + .block-editor-block-mover { display: flex; margin-right: auto; - .editor-inserter, - .editor-block-mover__control { + .block-editor-inserter, + .block-editor-block-mover__control { float: left; } } } // Reset negative margins on mobile for full-width. - &[data-align="full"] .editor-block-list__block-mobile-toolbar { + &[data-align="full"] .block-editor-block-list__block-mobile-toolbar { margin-left: 0; margin-right: 0; } @@ -655,20 +655,20 @@ * In-Canvas Inserter */ -.editor-block-list .editor-inserter { +.block-editor-block-list .block-editor-inserter { margin: $grid-size; cursor: move; // Fallback for IE/Edge < 14 cursor: grab; } // Insertion point (includes inbetween inserter and insertion indicator) -.editor-block-list__insertion-point { +.block-editor-block-list__insertion-point { position: relative; - z-index: z-index(".editor-block-list__insertion-point"); + z-index: z-index(".block-editor-block-list__insertion-point"); margin-top: -$block-padding; } -.editor-block-list__insertion-point-indicator { +.block-editor-block-list__insertion-point-indicator { position: absolute; top: calc(50% - #{ $border-width }); height: 2px; @@ -678,7 +678,7 @@ } // This is the clickable plus. -.editor-block-list__insertion-point-inserter { +.block-editor-block-list__insertion-point-inserter { // Don't show on mobile. display: none; @include break-mobile() { @@ -692,7 +692,7 @@ justify-content: center; // Show a clickable plus. - .editor-inserter__toggle { + .block-editor-inserter__toggle { margin-top: -4px; border-radius: 50%; color: $blue-medium-focus; @@ -718,8 +718,8 @@ // Don't show the sibling inserter before the selected block. .edit-post-layout:not(.has-fixed-toolbar) { // The child selector is necessary for this to work properly in nested contexts. - .is-selected > .editor-block-list__insertion-point > .editor-block-list__insertion-point-inserter, - .is-focused > .editor-block-list__insertion-point > .editor-block-list__insertion-point-inserter { + .is-selected > .block-editor-block-list__insertion-point > .block-editor-block-list__insertion-point-inserter, + .is-focused > .block-editor-block-list__insertion-point > .block-editor-block-list__insertion-point-inserter { opacity: 0; pointer-events: none; @@ -732,8 +732,8 @@ } // This is the edge-to-edge hover area that contains the plus. -.editor-block-list__block { - > .editor-block-list__insertion-point { +.block-editor-block-list__block { + > .block-editor-block-list__insertion-point { position: absolute; top: -$block-padding - $block-spacing / 2; @@ -752,13 +752,13 @@ } } - &[data-align="full"] > .editor-block-list__insertion-point { + &[data-align="full"] > .block-editor-block-list__insertion-point { left: 0; right: 0; } } -.editor-block-list__block .editor-block-list__block-html-textarea { +.block-editor-block-list__block .block-editor-block-list__block-html-textarea { display: block; margin: 0; width: 100%; @@ -782,9 +782,9 @@ * Block Toolbar when contextual. */ -.editor-block-list__block { - .editor-block-contextual-toolbar { - z-index: z-index(".editor-block-contextual-toolbar"); +.block-editor-block-list__block { + .block-editor-block-contextual-toolbar { + z-index: z-index(".block-editor-block-contextual-toolbar"); white-space: nowrap; text-align: left; pointer-events: none; @@ -812,14 +812,14 @@ } // Floated items have special needs for the contextual toolbar position. - &[data-align="left"] .editor-block-contextual-toolbar, - &[data-align="right"] .editor-block-contextual-toolbar { + &[data-align="left"] .block-editor-block-contextual-toolbar, + &[data-align="right"] .block-editor-block-contextual-toolbar { margin-bottom: $border-width; margin-top: -$block-toolbar-height; } // Make block toolbar full width on mobile. - .editor-block-contextual-toolbar { + .block-editor-block-contextual-toolbar { margin-left: 0; margin-right: 0; @include break-small() { @@ -829,30 +829,30 @@ } // For floats, compensate for this so content doesn't grow smaller. - &[data-align="left"] .editor-block-contextual-toolbar { + &[data-align="left"] .block-editor-block-contextual-toolbar { /*rtl:ignore*/ margin-right: $block-padding + $border-width; } - &[data-align="right"] .editor-block-contextual-toolbar { + &[data-align="right"] .block-editor-block-contextual-toolbar { /*rtl:ignore*/ margin-left: $block-padding + $border-width; } // Reset pointer-events on children. - .editor-block-contextual-toolbar > * { + .block-editor-block-contextual-toolbar > * { pointer-events: auto; } } -.editor-block-list__block.is-focus-mode:not(.is-multi-selected) > .editor-block-contextual-toolbar { +.block-editor-block-list__block.is-focus-mode:not(.is-multi-selected) > .block-editor-block-contextual-toolbar { margin-left: -$block-side-ui-width; } // Enable toolbar footprint collapsing -.editor-block-contextual-toolbar { +.block-editor-block-contextual-toolbar { // Position the contextual toolbar above the block. - .editor-block-list__block & { + .block-editor-block-list__block & { @include break-small() { bottom: auto; left: auto; @@ -872,27 +872,27 @@ } } - .editor-block-list__block[data-align="left"] & { + .block-editor-block-list__block[data-align="left"] & { // RTL note: this rule should not be auto-flipped based on direction. /*rtl:ignore*/ float: left; } - .editor-block-list__block[data-align="right"] & { + .block-editor-block-list__block[data-align="right"] & { // RTL note: this rule should not be auto-flipped based on direction. /*rtl:ignore*/ float: right; } - .editor-block-list__block[data-align="left"] &, - .editor-block-list__block[data-align="right"] & { + .block-editor-block-list__block[data-align="left"] &, + .block-editor-block-list__block[data-align="right"] & { // Move the block toolbar out of the flow using translate, but less for floats. transform: translateY(-$block-padding -$border-width); } } // Position the block toolbar when contextual. -.editor-block-contextual-toolbar .editor-block-toolbar { +.block-editor-block-contextual-toolbar .block-editor-block-toolbar { width: 100%; @include break-small() { @@ -912,10 +912,10 @@ * Hover label */ -.editor-block-list__breadcrumb { +.block-editor-block-list__breadcrumb { position: absolute; line-height: 1; - z-index: z-index(".editor-block-list__breadcrumb"); + z-index: z-index(".block-editor-block-list__breadcrumb"); // Position in the top right of the border. right: -$block-padding; @@ -933,7 +933,7 @@ color: $white; // Animate in - .editor-block-list__block:hover & { + .block-editor-block-list__block:hover & { opacity: 0; @include edit-post__fade-in-animation(60ms, 0.5s); } @@ -947,7 +947,7 @@ } } -.editor-block-list__descendant-arrow::before { +.block-editor-block-list__descendant-arrow::before { content: "→"; display: inline-block; padding: 0 4px; @@ -957,7 +957,7 @@ } } -.editor-block-list__block { +.block-editor-block-list__block { @include break-small { // Increase the hover and selection area around blocks. // This makes the blue hover line and the settings button appear even if @@ -989,8 +989,8 @@ } } -.editor-block-list__block .editor-warning { - z-index: z-index(".editor-warning"); +.block-editor-block-list__block .block-editor-warning { + z-index: z-index(".block-editor-warning"); position: relative; margin-right: -$block-padding - $border-width; margin-left: -$block-padding - $border-width; diff --git a/packages/block-editor/src/components/block-mover/drag-handle.js b/packages/block-editor/src/components/block-mover/drag-handle.js index 005487e11c5c7..86551bf4be0e3 100644 --- a/packages/block-editor/src/components/block-mover/drag-handle.js +++ b/packages/block-editor/src/components/block-mover/drag-handle.js @@ -13,7 +13,7 @@ export const IconDragHandle = ( { isVisible, className, icon, onDragStart, onDra return null; } - const dragHandleClassNames = classnames( 'editor-block-mover__control-drag-handle', className ); + const dragHandleClassNames = classnames( 'editor-block-mover__control-drag-handle block-editor-block-mover__control-drag-handle', className ); return ( <BlockDraggable diff --git a/packages/block-editor/src/components/block-mover/index.js b/packages/block-editor/src/components/block-mover/index.js index 0ea8166cc279c..6220f440edcbc 100644 --- a/packages/block-editor/src/components/block-mover/index.js +++ b/packages/block-editor/src/components/block-mover/index.js @@ -56,19 +56,19 @@ export class BlockMover extends Component { // to an unfocused state (body as active element) without firing blur on, // the rendering parent, leaving it unable to react to focus out. return ( - <div className={ classnames( 'editor-block-mover', { 'is-visible': isFocused || ! isHidden } ) }> + <div className={ classnames( 'editor-block-mover block-editor-block-mover', { 'is-visible': isFocused || ! isHidden } ) }> <IconButton - className="editor-block-mover__control" + className="editor-block-mover__control block-editor-block-mover__control" onClick={ isFirst ? null : onMoveUp } icon={ upArrow } label={ __( 'Move up' ) } - aria-describedby={ `editor-block-mover__up-description-${ instanceId }` } + aria-describedby={ `block-editor-block-mover__up-description-${ instanceId }` } aria-disabled={ isFirst } onFocus={ this.onFocus } onBlur={ this.onBlur } /> <IconDragHandle - className="editor-block-mover__control" + className="editor-block-mover__control block-editor-block-mover__control" icon={ dragHandle } clientId={ clientIds } blockElementId={ blockElementId } @@ -77,16 +77,16 @@ export class BlockMover extends Component { onDragEnd={ onDragEnd } /> <IconButton - className="editor-block-mover__control" + className="editor-block-mover__control block-editor-block-mover__control" onClick={ isLast ? null : onMoveDown } icon={ downArrow } label={ __( 'Move down' ) } - aria-describedby={ `editor-block-mover__down-description-${ instanceId }` } + aria-describedby={ `block-editor-block-mover__down-description-${ instanceId }` } aria-disabled={ isLast } onFocus={ this.onFocus } onBlur={ this.onBlur } /> - <span id={ `editor-block-mover__up-description-${ instanceId }` } className="editor-block-mover__description"> + <span id={ `block-editor-block-mover__up-description-${ instanceId }` } className="editor-block-mover__description block-editor-block-mover__description"> { getBlockMoverDescription( blocksCount, @@ -98,7 +98,7 @@ export class BlockMover extends Component { ) } </span> - <span id={ `editor-block-mover__down-description-${ instanceId }` } className="editor-block-mover__description"> + <span id={ `block-editor-block-mover__down-description-${ instanceId }` } className="editor-block-mover__description block-editor-block-mover__description"> { getBlockMoverDescription( blocksCount, diff --git a/packages/block-editor/src/components/block-mover/style.scss b/packages/block-editor/src/components/block-mover/style.scss index be9ddeab9c1b1..5bcc3bfaa63a2 100644 --- a/packages/block-editor/src/components/block-mover/style.scss +++ b/packages/block-editor/src/components/block-mover/style.scss @@ -1,4 +1,4 @@ -.editor-block-mover { +.block-editor-block-mover { min-height: $empty-paragraph-height; opacity: 0; @@ -11,14 +11,14 @@ // To vertically center against a 56px paragraph, move upwards 72px - 56px / 2. // Don't do this for wide, fullwide, or mobile. @include break-small() { - .editor-block-list__block:not([data-align="wide"]):not([data-align="full"]) & { + .block-editor-block-list__block:not([data-align="wide"]):not([data-align="full"]) & { margin-top: -$grid-size; } } } // Mover icon buttons. -.editor-block-mover__control { +.block-editor-block-mover__control { display: flex; align-items: center; justify-content: center; @@ -59,7 +59,7 @@ } } -.editor-block-mover__control-drag-handle { +.block-editor-block-mover__control-drag-handle { cursor: move; // Fallback for IE/Edge < 14 cursor: grab; fill: currentColor; @@ -91,17 +91,17 @@ } } -.editor-block-mover__description { +.block-editor-block-mover__description { display: none; } // Apply a background in nested contexts, only on desktop. -.editor-block-mover__control-drag-handle:not(:disabled):not([aria-disabled="true"]):not(.is-default), -.editor-block-mover__control { +.block-editor-block-mover__control-drag-handle:not(:disabled):not([aria-disabled="true"]):not(.is-default), +.block-editor-block-mover__control { @include break-small() { - .editor-block-list__layout [data-align="right"] &, - .editor-block-list__layout [data-align="left"] &, - .editor-block-list__layout .editor-block-list__layout & { + .block-editor-block-list__layout [data-align="right"] &, + .block-editor-block-list__layout [data-align="left"] &, + .block-editor-block-list__layout .block-editor-block-list__layout & { background: $white; box-shadow: inset 0 0 0 1px $light-gray-500; @@ -113,7 +113,7 @@ &:active, &:focus { // Buttons are stacked with overlapping border to look like a unit, so elevate on interactions. - z-index: z-index(".editor-block-mover__control"); + z-index: z-index(".block-editor-block-mover__control"); } } } diff --git a/packages/block-editor/src/components/block-mover/test/index.js b/packages/block-editor/src/components/block-mover/test/index.js index ffb4ef6ab8198..48809ae790566 100644 --- a/packages/block-editor/src/components/block-mover/test/index.js +++ b/packages/block-editor/src/components/block-mover/test/index.js @@ -30,7 +30,7 @@ describe( 'BlockMover', () => { firstIndex={ 0 } instanceId={ 1 } /> ); - expect( blockMover.hasClass( 'editor-block-mover' ) ).toBe( true ); + expect( blockMover.hasClass( 'block-editor-block-mover' ) ).toBe( true ); const moveUp = blockMover.childAt( 0 ); const drag = blockMover.childAt( 1 ); @@ -41,24 +41,24 @@ describe( 'BlockMover', () => { expect( drag.type().name ).toBe( 'IconDragHandle' ); expect( moveDown.name() ).toBe( 'ForwardRef(IconButton)' ); expect( moveUp.props() ).toMatchObject( { - className: 'editor-block-mover__control', + className: 'editor-block-mover__control block-editor-block-mover__control', onClick: undefined, label: 'Move up', icon: upArrow, 'aria-disabled': undefined, - 'aria-describedby': 'editor-block-mover__up-description-1', + 'aria-describedby': 'block-editor-block-mover__up-description-1', } ); expect( drag.props() ).toMatchObject( { - className: 'editor-block-mover__control', + className: 'editor-block-mover__control block-editor-block-mover__control', icon: dragHandle, } ); expect( moveDown.props() ).toMatchObject( { - className: 'editor-block-mover__control', + className: 'editor-block-mover__control block-editor-block-mover__control', onClick: undefined, label: 'Move down', icon: downArrow, 'aria-disabled': undefined, - 'aria-describedby': 'editor-block-mover__down-description-1', + 'aria-describedby': 'block-editor-block-mover__down-description-1', } ); expect( moveUpDesc.text() ).toBe( 'Move 2 blocks from position 1 up by one place' ); expect( moveDownDesc.text() ).toBe( 'Move 2 blocks from position 1 down by one place' ); diff --git a/packages/block-editor/src/components/block-navigation/dropdown.js b/packages/block-editor/src/components/block-navigation/dropdown.js index 35db5d9b2d1f2..ab8790cefa0d9 100644 --- a/packages/block-editor/src/components/block-navigation/dropdown.js +++ b/packages/block-editor/src/components/block-navigation/dropdown.js @@ -37,7 +37,7 @@ function BlockNavigationDropdown( { hasBlocks, isDisabled } ) { aria-expanded={ isOpen } onClick={ isEnabled ? onToggle : undefined } label={ __( 'Block Navigation' ) } - className="editor-block-navigation" + className="editor-block-navigation block-editor-block-navigation" shortcut={ displayShortcut.access( 'o' ) } aria-disabled={ ! isEnabled } /> diff --git a/packages/block-editor/src/components/block-navigation/index.js b/packages/block-editor/src/components/block-navigation/index.js index be7206d6cac0e..b0542caa3d6f3 100644 --- a/packages/block-editor/src/components/block-navigation/index.js +++ b/packages/block-editor/src/components/block-navigation/index.js @@ -30,16 +30,16 @@ function BlockNavigationList( { * Safari+VoiceOver won't announce the list otherwise. */ /* eslint-disable jsx-a11y/no-redundant-roles */ - <ul className="editor-block-navigation__list" role="list"> + <ul className="editor-block-navigation__list block-editor-block-navigation__list" role="list"> { map( blocks, ( block ) => { const blockType = getBlockType( block.name ); const isSelected = block.clientId === selectedBlockClientId; return ( <li key={ block.clientId }> - <div className="editor-block-navigation__item"> + <div className="editor-block-navigation__item block-editor-block-navigation__item"> <Button - className={ classnames( 'editor-block-navigation__item-button', { + className={ classnames( 'editor-block-navigation__item-button block-editor-block-navigation__item-button', { 'is-selected': isSelected, } ) } onClick={ () => selectBlock( block.clientId ) } @@ -80,9 +80,9 @@ function BlockNavigation( { rootBlock, rootBlocks, selectedBlockClientId, select return ( <NavigableMenu role="presentation" - className="editor-block-navigation__container" + className="editor-block-navigation__container block-editor-block-navigation__container" > - <p className="editor-block-navigation__label">{ __( 'Block Navigation' ) }</p> + <p className="editor-block-navigation__label block-editor-block-navigation__label">{ __( 'Block Navigation' ) }</p> { hasHierarchy && ( <BlockNavigationList blocks={ [ rootBlock ] } diff --git a/packages/block-editor/src/components/block-navigation/style.scss b/packages/block-editor/src/components/block-navigation/style.scss index f5a06ea1b8097..881a531ab71cc 100644 --- a/packages/block-editor/src/components/block-navigation/style.scss +++ b/packages/block-editor/src/components/block-navigation/style.scss @@ -1,31 +1,31 @@ $tree-border-width: 2px; $tree-item-height: 36px; -.editor-block-navigation__container { +.block-editor-block-navigation__container { padding: $grid-size - $border-width; } -.editor-block-navigation__label { +.block-editor-block-navigation__label { margin: 0 0 $grid-size; color: $dark-gray-300; } -.editor-block-navigation__list, -.editor-block-navigation__paragraph { +.block-editor-block-navigation__list, +.block-editor-block-navigation__paragraph { padding: 0; margin: 0; } -.editor-block-navigation__list .editor-block-navigation__list { +.block-editor-block-navigation__list .block-editor-block-navigation__list { margin-top: 2px; border-left: $tree-border-width solid $light-gray-900; margin-left: 1em; - .editor-block-navigation__list { + .block-editor-block-navigation__list { margin-left: 1.5em; } - .editor-block-navigation__item { + .block-editor-block-navigation__item { position: relative; &::before { @@ -39,7 +39,7 @@ $tree-item-height: 36px; } } - .editor-block-navigation__item-button { + .block-editor-block-navigation__item-button { margin-left: 0.8em; width: calc(100% - 0.8em); } @@ -58,7 +58,7 @@ $tree-item-height: 36px; } } -.editor-block-navigation__item-button { +.block-editor-block-navigation__item-button { display: flex; align-items: center; width: 100%; @@ -67,7 +67,7 @@ $tree-item-height: 36px; color: $dark-gray-600; border-radius: 4px; - .editor-block-icon { + .block-editor-block-icon { margin-right: 6px; } diff --git a/packages/block-editor/src/components/block-preview/index.js b/packages/block-editor/src/components/block-preview/index.js index 9a75152f5350d..97e9a9ebef40c 100644 --- a/packages/block-editor/src/components/block-preview/index.js +++ b/packages/block-editor/src/components/block-preview/index.js @@ -24,8 +24,8 @@ import BlockEdit from '../block-edit'; */ function BlockPreview( props ) { return ( - <div className="editor-block-preview"> - <div className="editor-block-preview__title">{ __( 'Preview' ) }</div> + <div className="editor-block-preview block-editor-block-preview"> + <div className="editor-block-preview__title block-editor-block-preview__title">{ __( 'Preview' ) }</div> <BlockPreviewContent { ...props } /> </div> ); @@ -34,7 +34,7 @@ function BlockPreview( props ) { export function BlockPreviewContent( { name, attributes } ) { const block = createBlock( name, attributes ); return ( - <Disabled className="editor-block-preview__content editor-styles-wrapper" aria-hidden> + <Disabled className="editor-block-preview__content block-editor-block-preview__content editor-styles-wrapper" aria-hidden> <BlockEdit name={ name } focus={ false } diff --git a/packages/block-editor/src/components/block-preview/style.scss b/packages/block-editor/src/components/block-preview/style.scss index b9eb15d1b1eb3..ca518cf8b5091 100644 --- a/packages/block-editor/src/components/block-preview/style.scss +++ b/packages/block-editor/src/components/block-preview/style.scss @@ -1,4 +1,4 @@ -.editor-block-preview { +.block-editor-block-preview { pointer-events: none; padding: 10px; overflow: hidden; @@ -8,7 +8,7 @@ display: block; } - .editor-block-preview__content { + .block-editor-block-preview__content { padding: $block-padding; border: $border-width solid $light-gray-500; font-family: $editor-font; @@ -29,7 +29,7 @@ } } -.editor-block-preview__title { +.block-editor-block-preview__title { margin-bottom: 10px; color: $dark-gray-300; } diff --git a/packages/block-editor/src/components/block-settings-menu/block-convert-button.js b/packages/block-editor/src/components/block-settings-menu/block-convert-button.js index 992292e04a5f5..2ae65fed66cfd 100644 --- a/packages/block-editor/src/components/block-settings-menu/block-convert-button.js +++ b/packages/block-editor/src/components/block-settings-menu/block-convert-button.js @@ -12,7 +12,7 @@ export default function BlockConvertButton( { shouldRender, onClick, small } ) { const label = __( 'Convert to Blocks' ); return ( <MenuItem - className="editor-block-settings-menu__control" + className="editor-block-settings-menu__control block-editor-block-settings-menu__control" onClick={ onClick } icon="screenoptions" label={ small ? label : undefined } diff --git a/packages/block-editor/src/components/block-settings-menu/block-mode-toggle.js b/packages/block-editor/src/components/block-settings-menu/block-mode-toggle.js index ffafcffff75f5..8ba029fd1794c 100644 --- a/packages/block-editor/src/components/block-settings-menu/block-mode-toggle.js +++ b/packages/block-editor/src/components/block-settings-menu/block-mode-toggle.js @@ -23,7 +23,7 @@ export function BlockModeToggle( { blockType, mode, onToggleMode, small = false return ( <MenuItem - className="editor-block-settings-menu__control" + className="editor-block-settings-menu__control block-editor-block-settings-menu__control" onClick={ onToggleMode } icon="html" label={ small ? label : undefined } diff --git a/packages/block-editor/src/components/block-settings-menu/index.js b/packages/block-editor/src/components/block-settings-menu/index.js index 6ab78e2b70c20..9c3e5b7b707ff 100644 --- a/packages/block-editor/src/components/block-settings-menu/index.js +++ b/packages/block-editor/src/components/block-settings-menu/index.js @@ -34,10 +34,10 @@ export function BlockSettingsMenu( { clientIds, onSelect } ) { <BlockActions clientIds={ clientIds }> { ( { onDuplicate, onRemove, onInsertAfter, onInsertBefore, canDuplicate, isLocked } ) => ( <Dropdown - contentClassName="editor-block-settings-menu__popover" + contentClassName="editor-block-settings-menu__popover block-editor-block-settings-menu__popover" position="bottom right" renderToggle={ ( { onToggle, isOpen } ) => { - const toggleClassname = classnames( 'editor-block-settings-menu__toggle', { + const toggleClassname = classnames( 'editor-block-settings-menu__toggle block-editor-block-settings-menu__toggle', { 'is-opened': isOpen, } ); const label = isOpen ? __( 'Hide options' ) : __( 'More options' ); @@ -58,7 +58,7 @@ export function BlockSettingsMenu( { clientIds, onSelect } ) { ); } } renderContent={ ( { onClose } ) => ( - <NavigableMenu className="editor-block-settings-menu__content"> + <NavigableMenu className="editor-block-settings-menu__content block-editor-block-settings-menu__content"> <_BlockSettingsMenuFirstItem.Slot fillProps={ { onClose } } /> { count === 1 && ( <BlockUnknownConvertButton @@ -72,7 +72,7 @@ export function BlockSettingsMenu( { clientIds, onSelect } ) { ) } { ! isLocked && canDuplicate && ( <MenuItem - className="editor-block-settings-menu__control" + className="editor-block-settings-menu__control block-editor-block-settings-menu__control" onClick={ onDuplicate } icon="admin-page" shortcut={ shortcuts.duplicate.display } @@ -83,7 +83,7 @@ export function BlockSettingsMenu( { clientIds, onSelect } ) { { ! isLocked && ( <Fragment> <MenuItem - className="editor-block-settings-menu__control" + className="editor-block-settings-menu__control block-editor-block-settings-menu__control" onClick={ onInsertBefore } icon="insert-before" shortcut={ shortcuts.insertBefore.display } @@ -91,7 +91,7 @@ export function BlockSettingsMenu( { clientIds, onSelect } ) { { __( 'Insert Before' ) } </MenuItem> <MenuItem - className="editor-block-settings-menu__control" + className="editor-block-settings-menu__control block-editor-block-settings-menu__control" onClick={ onInsertAfter } icon="insert-after" shortcut={ shortcuts.insertAfter.display } @@ -111,7 +111,7 @@ export function BlockSettingsMenu( { clientIds, onSelect } ) { onToggle={ onClose } /> <_BlockSettingsMenuPluginsExtension.Slot fillProps={ { clientIds, onClose } } /> - <div className="editor-block-settings-menu__separator" /> + <div className="editor-block-settings-menu__separator block-editor-block-settings-menu__separator" /> { count === 1 && ( <ReusableBlockDeleteButton clientId={ firstBlockClientId } @@ -120,7 +120,7 @@ export function BlockSettingsMenu( { clientIds, onSelect } ) { ) } { ! isLocked && ( <MenuItem - className="editor-block-settings-menu__control" + className="editor-block-settings-menu__control block-editor-block-settings-menu__control" onClick={ onRemove } icon="trash" shortcut={ shortcuts.removeBlock.display } diff --git a/packages/block-editor/src/components/block-settings-menu/reusable-block-convert-button.js b/packages/block-editor/src/components/block-settings-menu/reusable-block-convert-button.js index 3ec5076891eec..b699d59aa7572 100644 --- a/packages/block-editor/src/components/block-settings-menu/reusable-block-convert-button.js +++ b/packages/block-editor/src/components/block-settings-menu/reusable-block-convert-button.js @@ -27,7 +27,7 @@ export function ReusableBlockConvertButton( { <Fragment> { ! isReusable && ( <MenuItem - className="editor-block-settings-menu__control" + className="editor-block-settings-menu__control block-editor-block-settings-menu__control" icon="controls-repeat" onClick={ onConvertToReusable } > @@ -36,7 +36,7 @@ export function ReusableBlockConvertButton( { ) } { isReusable && ( <MenuItem - className="editor-block-settings-menu__control" + className="editor-block-settings-menu__control block-editor-block-settings-menu__control" icon="controls-repeat" onClick={ onConvertToStatic } > diff --git a/packages/block-editor/src/components/block-settings-menu/reusable-block-delete-button.js b/packages/block-editor/src/components/block-settings-menu/reusable-block-delete-button.js index a6271874d6533..8c9abb9589c7d 100644 --- a/packages/block-editor/src/components/block-settings-menu/reusable-block-delete-button.js +++ b/packages/block-editor/src/components/block-settings-menu/reusable-block-delete-button.js @@ -19,7 +19,7 @@ export function ReusableBlockDeleteButton( { isVisible, isDisabled, onDelete } ) return ( <MenuItem - className="editor-block-settings-menu__control" + className="editor-block-settings-menu__control block-editor-block-settings-menu__control" icon="no" disabled={ isDisabled } onClick={ () => onDelete() } diff --git a/packages/block-editor/src/components/block-settings-menu/style.scss b/packages/block-editor/src/components/block-settings-menu/style.scss index 418343d72a446..39b62ec34b59d 100644 --- a/packages/block-editor/src/components/block-settings-menu/style.scss +++ b/packages/block-editor/src/components/block-settings-menu/style.scss @@ -1,19 +1,19 @@ -.editor-block-settings-menu__toggle .dashicon { +.block-editor-block-settings-menu__toggle .dashicon { transform: rotate(90deg); } // Popout menu -.editor-block-settings-menu__popover { +.block-editor-block-settings-menu__popover { &::before, &::after { margin-left: 2px; } - .editor-block-settings-menu__content { + .block-editor-block-settings-menu__content { padding: ($grid-size - $border-width) 0; } - .editor-block-settings-menu__separator { + .block-editor-block-settings-menu__separator { margin-top: $grid-size; margin-bottom: $grid-size; margin-left: 0; @@ -26,14 +26,14 @@ } } - .editor-block-settings-menu__title { + .block-editor-block-settings-menu__title { display: block; padding: 6px; color: $dark-gray-300; } // Menu items - .editor-block-settings-menu__control { + .block-editor-block-settings-menu__control { width: 100%; justify-content: flex-start; background: none; diff --git a/packages/block-editor/src/components/block-settings-menu/test/__snapshots__/reusable-block-delete-button.js.snap b/packages/block-editor/src/components/block-settings-menu/test/__snapshots__/reusable-block-delete-button.js.snap index 23e876d36a9c6..0e2d2d28a3216 100644 --- a/packages/block-editor/src/components/block-settings-menu/test/__snapshots__/reusable-block-delete-button.js.snap +++ b/packages/block-editor/src/components/block-settings-menu/test/__snapshots__/reusable-block-delete-button.js.snap @@ -2,7 +2,7 @@ exports[`ReusableBlockDeleteButton matches the snapshot 1`] = ` <WithInstanceId(MenuItem) - className="editor-block-settings-menu__control" + className="editor-block-settings-menu__control block-editor-block-settings-menu__control" disabled={false} icon="no" onClick={[Function]} diff --git a/packages/block-editor/src/components/block-styles/index.js b/packages/block-editor/src/components/block-styles/index.js index ae7da2d26a559..215d9e545b69f 100644 --- a/packages/block-editor/src/components/block-styles/index.js +++ b/packages/block-editor/src/components/block-styles/index.js @@ -98,14 +98,14 @@ function BlockStyles( { } return ( - <div className="editor-block-styles"> + <div className="editor-block-styles block-editor-block-styles"> { styles.map( ( style ) => { const styleClassName = replaceActiveStyle( className, activeStyle, style ); return ( <div key={ style.name } className={ classnames( - 'editor-block-styles__item', { + 'editor-block-styles__item block-editor-block-styles__item', { 'is-active': activeStyle === style, } ) } @@ -122,7 +122,7 @@ function BlockStyles( { tabIndex="0" aria-label={ style.label || style.name } > - <div className="editor-block-styles__item-preview"> + <div className="editor-block-styles__item-preview block-editor-block-styles__item-preview"> <BlockPreviewContent name={ name } attributes={ { @@ -131,7 +131,7 @@ function BlockStyles( { } } /> </div> - <div className="editor-block-styles__item-label"> + <div className="editor-block-styles__item-label block-editor-block-styles__item-label"> { style.label || style.name } </div> </div> diff --git a/packages/block-editor/src/components/block-styles/style.scss b/packages/block-editor/src/components/block-styles/style.scss index 8816ea0b9c4f8..d818bcb15349b 100644 --- a/packages/block-editor/src/components/block-styles/style.scss +++ b/packages/block-editor/src/components/block-styles/style.scss @@ -1,10 +1,10 @@ -.editor-block-styles { +.block-editor-block-styles { display: flex; flex-wrap: wrap; justify-content: space-between; } -.editor-block-styles__item { +.block-editor-block-styles__item { width: calc(50% - #{ $grid-size-small }); margin: $grid-size-small 0; flex-shrink: 0; @@ -27,7 +27,7 @@ } } -.editor-block-styles__item-preview { +.block-editor-block-styles__item-preview { outline: $border-width solid transparent; // Shown in Windows High Contrast mode. border: 1px solid rgba($dark-gray-900, 0.2); overflow: hidden; @@ -39,7 +39,7 @@ background: $white; // Actual preview contents. - .editor-block-preview__content { + .block-editor-block-preview__content { transform: scale(0.7); transform-origin: center center; width: 100%; @@ -52,7 +52,7 @@ } } -.editor-block-styles__item-label { +.block-editor-block-styles__item-label { text-align: center; padding: 4px 2px; } diff --git a/packages/block-editor/src/components/block-switcher/index.js b/packages/block-editor/src/components/block-switcher/index.js index 25ef98edf8230..2d230c317612c 100644 --- a/packages/block-editor/src/components/block-switcher/index.js +++ b/packages/block-editor/src/components/block-switcher/index.js @@ -71,7 +71,7 @@ export class BlockSwitcher extends Component { <Toolbar> <IconButton disabled - className="editor-block-switcher__no-switcher-icon" + className="editor-block-switcher__no-switcher-icon block-editor-block-switcher__no-switcher-icon" label={ __( 'Block icon' ) } > <BlockIcon icon={ icon } showColors /> @@ -83,8 +83,8 @@ export class BlockSwitcher extends Component { return ( <Dropdown position="bottom right" - className="editor-block-switcher" - contentClassName="editor-block-switcher__popover" + className="editor-block-switcher block-editor-block-switcher" + contentClassName="editor-block-switcher__popover block-editor-block-switcher__popover" renderToggle={ ( { onToggle, isOpen } ) => { const openOnArrowDown = ( event ) => { if ( ! isOpen && event.keyCode === DOWN ) { @@ -109,7 +109,7 @@ export class BlockSwitcher extends Component { return ( <Toolbar> <IconButton - className="editor-block-switcher__toggle" + className="editor-block-switcher__toggle block-editor-block-switcher__toggle" onClick={ onToggle } aria-haspopup="true" aria-expanded={ isOpen } @@ -119,7 +119,7 @@ export class BlockSwitcher extends Component { icon={ ( <Fragment> <BlockIcon icon={ icon } showColors /> - <SVG className="editor-block-switcher__transform" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><Path d="M6.5 8.9c.6-.6 1.4-.9 2.2-.9h6.9l-1.3 1.3 1.4 1.4L19.4 7l-3.7-3.7-1.4 1.4L15.6 6H8.7c-1.4 0-2.6.5-3.6 1.5l-2.8 2.8 1.4 1.4 2.8-2.8zm13.8 2.4l-2.8 2.8c-.6.6-1.3.9-2.1.9h-7l1.3-1.3-1.4-1.4L4.6 16l3.7 3.7 1.4-1.4L8.4 17h6.9c1.3 0 2.6-.5 3.5-1.5l2.8-2.8-1.3-1.4z" /></SVG> + <SVG className="editor-block-switcher__transform block-editor-block-switcher__transform" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><Path d="M6.5 8.9c.6-.6 1.4-.9 2.2-.9h6.9l-1.3 1.3 1.4 1.4L19.4 7l-3.7-3.7-1.4 1.4L15.6 6H8.7c-1.4 0-2.6.5-3.6 1.5l-2.8 2.8 1.4 1.4 2.8-2.8zm13.8 2.4l-2.8 2.8c-.6.6-1.3.9-2.1.9h-7l1.3-1.3-1.4-1.4L4.6 16l3.7 3.7 1.4-1.4L8.4 17h6.9c1.3 0 2.6-.5 3.5-1.5l2.8-2.8-1.3-1.4z" /></SVG> </Fragment> ) } /> diff --git a/packages/block-editor/src/components/block-switcher/style.scss b/packages/block-editor/src/components/block-switcher/style.scss index 0f062116d1b00..9431b74fe1f31 100644 --- a/packages/block-editor/src/components/block-switcher/style.scss +++ b/packages/block-editor/src/components/block-switcher/style.scss @@ -1,20 +1,20 @@ -.editor-block-switcher { +.block-editor-block-switcher { position: relative; height: $icon-button-size; } -.components-icon-button.editor-block-switcher__toggle, -.components-icon-button.editor-block-switcher__no-switcher-icon { +.components-icon-button.block-editor-block-switcher__toggle, +.components-icon-button.block-editor-block-switcher__no-switcher-icon { margin: 0; display: block; height: $icon-button-size; padding: 3px; } -.components-icon-button.editor-block-switcher__no-switcher-icon { +.components-icon-button.block-editor-block-switcher__no-switcher-icon { width: $icon-button-size + 6px + 6px; - .editor-block-icon { + .block-editor-block-icon { margin-right: auto; margin-left: auto; } @@ -22,7 +22,7 @@ // When the block switcher does not have any transformations, we show it but as disabled. // The background and opacity change helps make the icon legible, despite being disabled. -.components-button.editor-block-switcher__no-switcher-icon:disabled { +.components-button.block-editor-block-switcher__no-switcher-icon:disabled { background: $light-gray-200; border-radius: 0; opacity: 0.84; @@ -30,14 +30,14 @@ // Also make the icon monochrome to further imply disabled state. // We use !important here because icon colors are set as inline styles, // and should be overridden when disabled. - .editor-block-icon.has-colors { + .block-editor-block-icon.has-colors { color: $dark-gray-500 !important; } } // Style this the same as the block buttons in the library. // Needs specificiity to override the icon button. -.components-icon-button.editor-block-switcher__toggle { +.components-icon-button.block-editor-block-switcher__toggle { width: auto; // Unset icon button styles. &:active, @@ -49,8 +49,8 @@ border: none; } - .editor-block-icon, - .editor-block-switcher__transform { + .block-editor-block-icon, + .block-editor-block-switcher__transform { width: $icon-button-size + 3px + 3px; height: $icon-button-size-small + 6px; position: relative; @@ -62,42 +62,42 @@ } // Add a dropdown arrow indicator. - .editor-block-icon::after { + .block-editor-block-icon::after { @include dropdown-arrow(); } - .editor-block-switcher__transform { + .block-editor-block-switcher__transform { margin-top: 6px; border-radius: $radius-round-rectangle; } // Block hover and focus style. - &[aria-expanded="true"] .editor-block-icon, - &[aria-expanded="true"] .editor-block-switcher__transform, - &:not(:disabled):hover .editor-block-icon, - &:not(:disabled):hover .editor-block-switcher__transform, - &:not(:disabled):focus .editor-block-icon, - &:not(:disabled):focus .editor-block-switcher__transform { + &[aria-expanded="true"] .block-editor-block-icon, + &[aria-expanded="true"] .block-editor-block-switcher__transform, + &:not(:disabled):hover .block-editor-block-icon, + &:not(:disabled):hover .block-editor-block-switcher__transform, + &:not(:disabled):focus .block-editor-block-icon, + &:not(:disabled):focus .block-editor-block-switcher__transform { transform: translateY(-$icon-button-size); } // Block focus style. - &:not(:disabled):focus .editor-block-icon, - &:not(:disabled):focus .editor-block-switcher__transform { + &:not(:disabled):focus .block-editor-block-icon, + &:not(:disabled):focus .block-editor-block-switcher__transform { @include formatting-button-style__focus(); } } -.components-popover:not(.is-mobile).editor-block-switcher__popover .components-popover__content { +.components-popover:not(.is-mobile).block-editor-block-switcher__popover .components-popover__content { min-width: 300px; max-width: 340px; } -.editor-block-switcher__popover .components-popover__content { +.block-editor-block-switcher__popover .components-popover__content { @include break-medium { position: relative; - .editor-block-preview { + .block-editor-block-preview { border: $border-width solid $light-gray-500; box-shadow: $shadow-popover; background: $white; @@ -124,15 +124,15 @@ } } -.editor-block-switcher__popover:not(.is-mobile) > .components-popover__content { +.block-editor-block-switcher__popover:not(.is-mobile) > .components-popover__content { // Reset overflow to allow showing the preview on the left once an item is hovered. overflow-y: visible; } -.editor-block-switcher__popover .editor-block-styles { +.block-editor-block-switcher__popover .block-editor-block-styles { margin: 0 -3px; // Remove the panel body padding while keeping it for the title. } -.editor-block-switcher__popover .editor-block-types-list { +.block-editor-block-switcher__popover .block-editor-block-types-list { margin: 8px -8px -8px; } diff --git a/packages/block-editor/src/components/block-switcher/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/block-switcher/test/__snapshots__/index.js.snap index df5a8d415abad..4680cbc0b0b18 100644 --- a/packages/block-editor/src/components/block-switcher/test/__snapshots__/index.js.snap +++ b/packages/block-editor/src/components/block-switcher/test/__snapshots__/index.js.snap @@ -3,7 +3,7 @@ exports[`BlockSwitcher should render disabled block switcher with multi block of different types when no transforms 1`] = ` <Toolbar> <ForwardRef(IconButton) - className="editor-block-switcher__no-switcher-icon" + className="editor-block-switcher__no-switcher-icon block-editor-block-switcher__no-switcher-icon" disabled={true} label="Block icon" > @@ -17,8 +17,8 @@ exports[`BlockSwitcher should render disabled block switcher with multi block of exports[`BlockSwitcher should render enabled block switcher with multi block when transforms exist 1`] = ` <Dropdown - className="editor-block-switcher" - contentClassName="editor-block-switcher__popover" + className="editor-block-switcher block-editor-block-switcher" + contentClassName="editor-block-switcher__popover block-editor-block-switcher__popover" position="bottom right" renderContent={[Function]} renderToggle={[Function]} @@ -27,8 +27,8 @@ exports[`BlockSwitcher should render enabled block switcher with multi block whe exports[`BlockSwitcher should render switcher with blocks 1`] = ` <Dropdown - className="editor-block-switcher" - contentClassName="editor-block-switcher__popover" + className="editor-block-switcher block-editor-block-switcher" + contentClassName="editor-block-switcher__popover block-editor-block-switcher__popover" position="bottom right" renderContent={[Function]} renderToggle={[Function]} diff --git a/packages/block-editor/src/components/block-toolbar/index.js b/packages/block-editor/src/components/block-toolbar/index.js index 2c3fa018731e9..8933a04665d96 100644 --- a/packages/block-editor/src/components/block-toolbar/index.js +++ b/packages/block-editor/src/components/block-toolbar/index.js @@ -20,7 +20,7 @@ function BlockToolbar( { blockClientIds, isValid, mode } ) { if ( blockClientIds.length > 1 ) { return ( - <div className="editor-block-toolbar"> + <div className="editor-block-toolbar block-editor-block-toolbar"> <MultiBlocksSwitcher /> <BlockSettingsMenu clientIds={ blockClientIds } /> </div> @@ -28,7 +28,7 @@ function BlockToolbar( { blockClientIds, isValid, mode } ) { } return ( - <div className="editor-block-toolbar"> + <div className="editor-block-toolbar block-editor-block-toolbar"> { mode === 'visual' && isValid && ( <Fragment> <BlockSwitcher clientIds={ blockClientIds } /> diff --git a/packages/block-editor/src/components/block-toolbar/style.scss b/packages/block-editor/src/components/block-toolbar/style.scss index 77b15329ad1e2..4b426de6f11d6 100644 --- a/packages/block-editor/src/components/block-toolbar/style.scss +++ b/packages/block-editor/src/components/block-toolbar/style.scss @@ -1,4 +1,4 @@ -.editor-block-toolbar { +.block-editor-block-toolbar { display: flex; flex-grow: 1; width: 100%; diff --git a/packages/block-editor/src/components/block-types-list/index.js b/packages/block-editor/src/components/block-types-list/index.js index 16ec272453a94..d2639e80f2f3c 100644 --- a/packages/block-editor/src/components/block-types-list/index.js +++ b/packages/block-editor/src/components/block-types-list/index.js @@ -15,7 +15,7 @@ function BlockTypesList( { items, onSelect, onHover = () => {}, children } ) { * Safari+VoiceOver won't announce the list otherwise. */ /* eslint-disable jsx-a11y/no-redundant-roles */ - <ul role="list" className="editor-block-types-list"> + <ul role="list" className="editor-block-types-list block-editor-block-types-list"> { items && items.map( ( item ) => <InserterListItem key={ item.id } diff --git a/packages/block-editor/src/components/block-types-list/style.scss b/packages/block-editor/src/components/block-types-list/style.scss index b6ab6afd92516..73872e11ed5e0 100644 --- a/packages/block-editor/src/components/block-types-list/style.scss +++ b/packages/block-editor/src/components/block-types-list/style.scss @@ -1,4 +1,4 @@ -.editor-block-types-list { +.block-editor-block-types-list { list-style: none; padding: 2px 0; overflow: hidden; diff --git a/packages/block-editor/src/components/color-palette/control.js b/packages/block-editor/src/components/color-palette/control.js index d39bb961362c4..097cae030e9a3 100644 --- a/packages/block-editor/src/components/color-palette/control.js +++ b/packages/block-editor/src/components/color-palette/control.js @@ -41,10 +41,10 @@ export function ColorPaletteControl( { return ( <BaseControl - className="editor-color-palette-control" + className="editor-color-palette-control block-editor-color-palette-control" label={ labelElement }> <ColorPalette - className="editor-color-palette-control__color-palette" + className="editor-color-palette-control__color-palette block-editor-color-palette-control__color-palette" value={ value } onChange={ onChange } { ... { colors, disableCustomColors } } diff --git a/packages/block-editor/src/components/color-palette/control.scss b/packages/block-editor/src/components/color-palette/control.scss index c82865028658f..249fc32607be1 100644 --- a/packages/block-editor/src/components/color-palette/control.scss +++ b/packages/block-editor/src/components/color-palette/control.scss @@ -1,4 +1,4 @@ -.editor-color-palette-control__color-palette { +.block-editor-color-palette-control__color-palette { display: inline-block; margin-top: 0.6rem; } diff --git a/packages/block-editor/src/components/color-palette/test/__snapshots__/control.js.snap b/packages/block-editor/src/components/color-palette/test/__snapshots__/control.js.snap index 0114e273c5a01..5d46ddc7277ef 100644 --- a/packages/block-editor/src/components/color-palette/test/__snapshots__/control.js.snap +++ b/packages/block-editor/src/components/color-palette/test/__snapshots__/control.js.snap @@ -2,7 +2,7 @@ exports[`ColorPaletteControl matches the snapshot 1`] = ` <BaseControl - className="editor-color-palette-control" + className="editor-color-palette-control block-editor-color-palette-control" label={ <React.Fragment> Test Color @@ -14,7 +14,7 @@ exports[`ColorPaletteControl matches the snapshot 1`] = ` } > <WithColorContext(ColorPalette) - className="editor-color-palette-control__color-palette" + className="editor-color-palette-control__color-palette block-editor-color-palette-control__color-palette" colors={ Array [ Object { diff --git a/packages/block-editor/src/components/contrast-checker/index.js b/packages/block-editor/src/components/contrast-checker/index.js index a5cbb23257242..3a420448ae7d7 100644 --- a/packages/block-editor/src/components/contrast-checker/index.js +++ b/packages/block-editor/src/components/contrast-checker/index.js @@ -35,7 +35,7 @@ function ContrastChecker( { __( 'This color combination may be hard for people to read. Try using a darker background color and/or a brighter text color.' ) : __( 'This color combination may be hard for people to read. Try using a brighter background color and/or a darker text color.' ); return ( - <div className="editor-contrast-checker"> + <div className="editor-contrast-checker block-editor-contrast-checker"> <Notice status="warning" isDismissible={ false }> { msg } </Notice> diff --git a/packages/block-editor/src/components/contrast-checker/style.scss b/packages/block-editor/src/components/contrast-checker/style.scss index b484e47d48738..b3b08d6230d05 100644 --- a/packages/block-editor/src/components/contrast-checker/style.scss +++ b/packages/block-editor/src/components/contrast-checker/style.scss @@ -1,3 +1,3 @@ -.editor-contrast-checker > .components-notice { +.block-editor-contrast-checker > .components-notice { margin: 0; } diff --git a/packages/block-editor/src/components/contrast-checker/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/contrast-checker/test/__snapshots__/index.js.snap index 1c56207c62bea..16bb2d6c05044 100644 --- a/packages/block-editor/src/components/contrast-checker/test/__snapshots__/index.js.snap +++ b/packages/block-editor/src/components/contrast-checker/test/__snapshots__/index.js.snap @@ -9,7 +9,7 @@ exports[`ContrastChecker should render component when the colors do not meet AA textColor="#666" > <div - className="editor-contrast-checker" + className="editor-contrast-checker block-editor-contrast-checker" > <Notice isDismissible={false} @@ -38,7 +38,7 @@ exports[`ContrastChecker should render different message matching snapshot when textColor="#666" > <div - className="editor-contrast-checker" + className="editor-contrast-checker block-editor-contrast-checker" > <Notice isDismissible={false} @@ -64,7 +64,7 @@ exports[`ContrastChecker should render messages when the textColor is valid, but textColor="#000000" > <div - className="editor-contrast-checker" + className="editor-contrast-checker block-editor-contrast-checker" > <Notice isDismissible={false} @@ -91,7 +91,7 @@ exports[`ContrastChecker should take into consideration the font size passed 1`] textColor="#000000" > <div - className="editor-contrast-checker" + className="editor-contrast-checker block-editor-contrast-checker" > <Notice isDismissible={false} @@ -118,7 +118,7 @@ exports[`ContrastChecker should take into consideration wherever text is large o textColor="#000000" > <div - className="editor-contrast-checker" + className="editor-contrast-checker block-editor-contrast-checker" > <Notice isDismissible={false} @@ -146,7 +146,7 @@ exports[`ContrastChecker should use isLargeText to make decisions if both isLarg textColor="#000000" > <div - className="editor-contrast-checker" + className="editor-contrast-checker block-editor-contrast-checker" > <Notice isDismissible={false} diff --git a/packages/block-editor/src/components/default-block-appender/index.js b/packages/block-editor/src/components/default-block-appender/index.js index 183da30afd8b1..3baf90e440609 100644 --- a/packages/block-editor/src/components/default-block-appender/index.js +++ b/packages/block-editor/src/components/default-block-appender/index.js @@ -53,7 +53,7 @@ export function DefaultBlockAppender( { return ( <div data-root-client-id={ rootClientId || '' } - className="wp-block editor-default-block-appender" + className="wp-block editor-default-block-appender block-editor-default-block-appender" onMouseEnter={ () => setState( { hovered: true } ) } onMouseLeave={ () => setState( { hovered: false } ) } > @@ -61,7 +61,7 @@ export function DefaultBlockAppender( { <TextareaAutosize role="button" aria-label={ __( 'Add block' ) } - className="editor-default-block-appender__content" + className="editor-default-block-appender__content block-editor-default-block-appender__content" readOnly onFocus={ onAppend } value={ showPrompt ? value : '' } diff --git a/packages/block-editor/src/components/default-block-appender/style.scss b/packages/block-editor/src/components/default-block-appender/style.scss index 55bcac1feae3b..d9728e201c1ec 100644 --- a/packages/block-editor/src/components/default-block-appender/style.scss +++ b/packages/block-editor/src/components/default-block-appender/style.scss @@ -1,7 +1,7 @@ -.editor-default-block-appender { +.block-editor-default-block-appender { clear: both; // The appender doesn't scale well to sit next to floats, so clear them. - textarea.editor-default-block-appender__content { // Needs specificity in order to override input field styles from WP-admin styles. + textarea.block-editor-default-block-appender__content { // Needs specificity in order to override input field styles from WP-admin styles. font-family: $editor-font; font-size: $editor-font-size; // It should match the default paragraph size. border: none; @@ -27,17 +27,17 @@ } // Don't show the inserter until mousing over. - .editor-inserter__toggle:not([aria-expanded="true"]) { + .block-editor-inserter__toggle:not([aria-expanded="true"]) { opacity: 0; transition: opacity 0.2s; } &:hover { - .editor-inserter-with-shortcuts { + .block-editor-inserter-with-shortcuts { @include edit-post__fade-in-animation; } - .editor-inserter__toggle { + .block-editor-inserter__toggle { opacity: 1; } } @@ -49,9 +49,9 @@ } // Quick shortcuts, left and right. -.editor-block-list__empty-block-inserter, // Empty paragraph -.editor-default-block-appender .editor-inserter, // Empty appender -.editor-inserter-with-shortcuts { // Right side quick shortcuts +.block-editor-block-list__empty-block-inserter, // Empty paragraph +.block-editor-default-block-appender .block-editor-inserter, // Empty appender +.block-editor-inserter-with-shortcuts { // Right side quick shortcuts position: absolute; top: 0; @@ -63,7 +63,7 @@ padding: 0; } - .editor-block-icon { + .block-editor-block-icon { margin: auto; } @@ -72,14 +72,14 @@ margin: auto; } - .editor-inserter__toggle { + .block-editor-inserter__toggle { margin-right: 0; } } // Left side. -.editor-block-list__empty-block-inserter, -.editor-default-block-appender .editor-inserter { +.block-editor-block-list__empty-block-inserter, +.block-editor-default-block-appender .block-editor-inserter { right: $grid-size; // Show to the right on mobile. @include break-small { @@ -91,7 +91,7 @@ display: none; } - .editor-inserter__toggle { + .block-editor-inserter__toggle { border-radius: 50%; width: $block-side-ui-width; height: $block-side-ui-width; @@ -109,11 +109,11 @@ // Quick block insertion icons on the right side. // Needs specificity to styles from the component itself. -.editor-block-list__side-inserter .editor-inserter-with-shortcuts, -.editor-default-block-appender .editor-inserter-with-shortcuts { +.block-editor-block-list__side-inserter .block-editor-inserter-with-shortcuts, +.block-editor-default-block-appender .block-editor-inserter-with-shortcuts { right: $block-padding; display: none; // Don't show on mobile. - z-index: z-index(".editor-inserter-with-shortcuts"); // Elevate above the sibling inserter. + z-index: z-index(".block-editor-inserter-with-shortcuts"); // Elevate above the sibling inserter. @include break-small { right: 0; diff --git a/packages/block-editor/src/components/default-block-appender/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/default-block-appender/test/__snapshots__/index.js.snap index 9d6198255a377..9a09dae0d4767 100644 --- a/packages/block-editor/src/components/default-block-appender/test/__snapshots__/index.js.snap +++ b/packages/block-editor/src/components/default-block-appender/test/__snapshots__/index.js.snap @@ -2,7 +2,7 @@ exports[`DefaultBlockAppender should append a default block when input focused 1`] = ` <div - className="wp-block editor-default-block-appender" + className="wp-block editor-default-block-appender block-editor-default-block-appender" data-root-client-id="" onMouseEnter={[Function]} onMouseLeave={[Function]} @@ -10,7 +10,7 @@ exports[`DefaultBlockAppender should append a default block when input focused 1 <WithDispatch(WithSelect(WithFilters(BlockDropZone))) /> <TextareaAutosize aria-label="Add block" - className="editor-default-block-appender__content" + className="editor-default-block-appender__content block-editor-default-block-appender__content" onFocus={ [MockFunction] { "calls": Array [ @@ -38,7 +38,7 @@ exports[`DefaultBlockAppender should append a default block when input focused 1 exports[`DefaultBlockAppender should match snapshot 1`] = ` <div - className="wp-block editor-default-block-appender" + className="wp-block editor-default-block-appender block-editor-default-block-appender" data-root-client-id="" onMouseEnter={[Function]} onMouseLeave={[Function]} @@ -46,7 +46,7 @@ exports[`DefaultBlockAppender should match snapshot 1`] = ` <WithDispatch(WithSelect(WithFilters(BlockDropZone))) /> <TextareaAutosize aria-label="Add block" - className="editor-default-block-appender__content" + className="editor-default-block-appender__content block-editor-default-block-appender__content" onFocus={[MockFunction]} readOnly={true} role="button" @@ -62,7 +62,7 @@ exports[`DefaultBlockAppender should match snapshot 1`] = ` exports[`DefaultBlockAppender should optionally show without prompt 1`] = ` <div - className="wp-block editor-default-block-appender" + className="wp-block editor-default-block-appender block-editor-default-block-appender" data-root-client-id="" onMouseEnter={[Function]} onMouseLeave={[Function]} @@ -70,7 +70,7 @@ exports[`DefaultBlockAppender should optionally show without prompt 1`] = ` <WithDispatch(WithSelect(WithFilters(BlockDropZone))) /> <TextareaAutosize aria-label="Add block" - className="editor-default-block-appender__content" + className="editor-default-block-appender__content block-editor-default-block-appender__content" onFocus={[MockFunction]} readOnly={true} role="button" diff --git a/packages/block-editor/src/components/inner-blocks/index.js b/packages/block-editor/src/components/inner-blocks/index.js index eba0122e68856..1dd6292ab324f 100644 --- a/packages/block-editor/src/components/inner-blocks/index.js +++ b/packages/block-editor/src/components/inner-blocks/index.js @@ -105,7 +105,7 @@ class InnerBlocks extends Component { } = this.props; const { templateInProcess } = this.state; - const classes = classnames( 'editor-inner-blocks', { + const classes = classnames( 'editor-inner-blocks block-editor-inner-blocks', { 'has-overlay': isSmallScreen && ! isSelectedBlockInRoot, } ); diff --git a/packages/block-editor/src/components/inner-blocks/style.scss b/packages/block-editor/src/components/inner-blocks/style.scss index 36ffe038223e4..f4218ef0667eb 100644 --- a/packages/block-editor/src/components/inner-blocks/style.scss +++ b/packages/block-editor/src/components/inner-blocks/style.scss @@ -1,9 +1,9 @@ -.editor-inner-blocks.has-overlay::after { +.block-editor-inner-blocks.has-overlay::after { content: ""; position: absolute; top: 0; right: 0; bottom: 0; left: 0; - z-index: z-index(".editor-inner-blocks__small-screen-overlay:after"); + z-index: z-index(".block-editor-inner-blocks__small-screen-overlay:after"); } diff --git a/packages/block-editor/src/components/inserter-list-item/index.js b/packages/block-editor/src/components/inserter-list-item/index.js index 1dafcb522b7a1..69d9e047418f3 100644 --- a/packages/block-editor/src/components/inserter-list-item/index.js +++ b/packages/block-editor/src/components/inserter-list-item/index.js @@ -26,14 +26,14 @@ function InserterListItem( { } : {}; return ( - <li className="editor-block-types-list__list-item"> + <li className="editor-block-types-list__list-item block-editor-block-types-list__list-item"> <button className={ classnames( - 'editor-block-types-list__item', + 'editor-block-types-list__item block-editor-block-types-list__item', className, { - 'editor-block-types-list__item-has-children': + 'editor-block-types-list__item-has-children block-editor-block-types-list__item-has-children': hasChildBlocksWithInserterSupport, } ) @@ -47,18 +47,18 @@ function InserterListItem( { { ...props } > <span - className="editor-block-types-list__item-icon" + className="editor-block-types-list__item-icon block-editor-block-types-list__item-icon" style={ itemIconStyle } > <BlockIcon icon={ icon } showColors /> { hasChildBlocksWithInserterSupport && <span - className="editor-block-types-list__item-icon-stack" + className="editor-block-types-list__item-icon-stack block-editor-block-types-list__item-icon-stack" style={ itemIconStackStyle } /> } </span> - <span className="editor-block-types-list__item-title"> + <span className="editor-block-types-list__item-title block-editor-block-types-list__item-title"> { title } </span> </button> diff --git a/packages/block-editor/src/components/inserter-list-item/style.scss b/packages/block-editor/src/components/inserter-list-item/style.scss index 7a3b97c05c9c9..895f9c39b7866 100644 --- a/packages/block-editor/src/components/inserter-list-item/style.scss +++ b/packages/block-editor/src/components/inserter-list-item/style.scss @@ -1,11 +1,11 @@ -.editor-block-types-list__list-item { +.block-editor-block-types-list__list-item { display: block; width: 33.33%; padding: 0 4px; margin: 0 0 12px; } -.editor-block-types-list__item { +.block-editor-block-types-list__item { display: flex; flex-direction: column; width: 100%; @@ -43,8 +43,8 @@ } &:hover { - .editor-block-types-list__item-icon, - .editor-block-types-list__item-title { + .block-editor-block-types-list__item-icon, + .block-editor-block-types-list__item-title { color: currentColor; } } @@ -58,21 +58,21 @@ outline: none; @include block-style__focus-active(); - .editor-block-types-list__item-icon, - .editor-block-types-list__item-title { + .block-editor-block-types-list__item-icon, + .block-editor-block-types-list__item-title { color: currentColor; } } } } -.editor-block-types-list__item-icon { +.block-editor-block-types-list__item-icon { padding: 12px 20px; border-radius: $radius-round-rectangle; color: $dark-gray-500; transition: all 0.05s ease-in-out; - .editor-block-icon { + .block-editor-block-icon { margin-left: auto; margin-right: auto; } @@ -82,12 +82,12 @@ } } -.editor-block-types-list__item-title { +.block-editor-block-types-list__item-title { padding: 4px 2px 8px; } -.editor-block-types-list__item-has-children { - .editor-block-types-list__item-icon { +.block-editor-block-types-list__item-has-children { + .block-editor-block-types-list__item-icon { background: $white; margin-right: 3px; margin-bottom: 6px; @@ -99,7 +99,7 @@ } // Show a "stacked card" below an item that has children. - .editor-block-types-list__item-icon-stack { + .block-editor-block-types-list__item-icon-stack { display: block; background: $white; box-shadow: 0 0 0 1px $light-gray-500; diff --git a/packages/block-editor/src/components/inserter-with-shortcuts/index.js b/packages/block-editor/src/components/inserter-with-shortcuts/index.js index 8e64fc6eaa580..71adf33922bff 100644 --- a/packages/block-editor/src/components/inserter-with-shortcuts/index.js +++ b/packages/block-editor/src/components/inserter-with-shortcuts/index.js @@ -30,11 +30,11 @@ function InserterWithShortcuts( { items, isLocked, onInsert } ) { } ).slice( 0, 3 ); return ( - <div className="editor-inserter-with-shortcuts"> + <div className="editor-inserter-with-shortcuts block-editor-inserter-with-shortcuts"> { itemsWithoutDefaultBlock.map( ( item ) => ( <IconButton key={ item.id } - className="editor-inserter-with-shortcuts__block" + className="editor-inserter-with-shortcuts__block block-editor-inserter-with-shortcuts__block" onClick={ () => onInsert( item ) } // translators: %s: block title/name to be added label={ sprintf( __( 'Add %s' ), item.title ) } diff --git a/packages/block-editor/src/components/inserter-with-shortcuts/style.scss b/packages/block-editor/src/components/inserter-with-shortcuts/style.scss index 67d4af314f872..ef2d9e82ccebe 100644 --- a/packages/block-editor/src/components/inserter-with-shortcuts/style.scss +++ b/packages/block-editor/src/components/inserter-with-shortcuts/style.scss @@ -1,4 +1,4 @@ -.editor-inserter-with-shortcuts { +.block-editor-inserter-with-shortcuts { display: flex; align-items: center; @@ -12,7 +12,7 @@ } } -.editor-inserter-with-shortcuts__block { +.block-editor-inserter-with-shortcuts__block { margin-right: $block-spacing; width: $icon-button-size; height: $icon-button-size; diff --git a/packages/block-editor/src/components/inserter/child-blocks.js b/packages/block-editor/src/components/inserter/child-blocks.js index 832c1e2b73038..53590af3a1ee7 100644 --- a/packages/block-editor/src/components/inserter/child-blocks.js +++ b/packages/block-editor/src/components/inserter/child-blocks.js @@ -12,9 +12,9 @@ import BlockIcon from '../block-icon'; function ChildBlocks( { rootBlockIcon, rootBlockTitle, items, ...props } ) { return ( - <div className="editor-inserter__child-blocks"> + <div className="editor-inserter__child-blocks block-editor-inserter__child-blocks"> { ( rootBlockIcon || rootBlockTitle ) && ( - <div className="editor-inserter__parent-block-header"> + <div className="editor-inserter__parent-block-header block-editor-inserter__parent-block-header"> <BlockIcon icon={ rootBlockIcon } showColors /> { rootBlockTitle && <h2>{ rootBlockTitle }</h2> } </div> diff --git a/packages/block-editor/src/components/inserter/index.js b/packages/block-editor/src/components/inserter/index.js index 4dfd3303fd4e5..48a28517ebde1 100644 --- a/packages/block-editor/src/components/inserter/index.js +++ b/packages/block-editor/src/components/inserter/index.js @@ -18,7 +18,7 @@ const defaultRenderToggle = ( { onToggle, disabled, isOpen } ) => ( label={ __( 'Add block' ) } labelPosition="bottom" onClick={ onToggle } - className="editor-inserter__toggle" + className="editor-inserter__toggle block-editor-inserter__toggle" aria-haspopup="true" aria-expanded={ isOpen } disabled={ disabled } @@ -87,8 +87,8 @@ class Inserter extends Component { return ( <Dropdown - className="editor-inserter" - contentClassName="editor-inserter__popover" + className="editor-inserter block-editor-inserter" + contentClassName="editor-inserter__popover block-editor-inserter__popover" position={ position } onToggle={ this.onToggle } expandOnMobile diff --git a/packages/block-editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js index 8033b613c69ac..bf659f789d188 100644 --- a/packages/block-editor/src/components/inserter/menu.js +++ b/packages/block-editor/src/components/inserter/menu.js @@ -266,24 +266,24 @@ export class InserterMenu extends Component { /* eslint-disable jsx-a11y/no-autofocus, jsx-a11y/no-static-element-interactions */ return ( <div - className="editor-inserter__menu" + className="editor-inserter__menu block-editor-inserter__menu" onKeyPress={ stopKeyPropagation } onKeyDown={ this.onKeyDown } > - <label htmlFor={ `editor-inserter__search-${ instanceId }` } className="screen-reader-text"> + <label htmlFor={ `block-editor-inserter__search-${ instanceId }` } className="screen-reader-text"> { __( 'Search for a block' ) } </label> <input - id={ `editor-inserter__search-${ instanceId }` } + id={ `block-editor-inserter__search-${ instanceId }` } type="search" placeholder={ __( 'Search for a block' ) } - className="editor-inserter__search" + className="editor-inserter__search block-editor-inserter__search" autoFocus onChange={ this.onChangeSearchInput } /> <div - className="editor-inserter__results" + className="editor-inserter__results block-editor-inserter__results" ref={ this.inserterResults } tabIndex="0" role="region" @@ -329,7 +329,7 @@ export class InserterMenu extends Component { { !! reusableItems.length && ( <PanelBody - className="editor-inserter__reusable-blocks-panel" + className="editor-inserter__reusable-blocks-panel block-editor-inserter__reusable-blocks-panel" title={ __( 'Reusable' ) } opened={ isPanelOpen( 'reusable' ) } onToggle={ this.onTogglePanel( 'reusable' ) } @@ -338,7 +338,7 @@ export class InserterMenu extends Component { > <BlockTypesList items={ reusableItems } onSelect={ onSelect } onHover={ this.onHover } /> <a - className="editor-inserter__manage-reusable-blocks" + className="editor-inserter__manage-reusable-blocks block-editor-inserter__manage-reusable-blocks" href={ addQueryArgs( 'edit.php', { post_type: 'wp_block' } ) } > { __( 'Manage All Reusable Blocks' ) } @@ -346,7 +346,7 @@ export class InserterMenu extends Component { </PanelBody> ) } { isEmpty( suggestedItems ) && isEmpty( reusableItems ) && isEmpty( itemsPerCategory ) && ( - <p className="editor-inserter__no-results">{ __( 'No blocks found.' ) }</p> + <p className="editor-inserter__no-results block-editor-inserter__no-results">{ __( 'No blocks found.' ) }</p> ) } </div> diff --git a/packages/block-editor/src/components/inserter/style.scss b/packages/block-editor/src/components/inserter/style.scss index 9237ac2ed0bc2..41f53d25e00e3 100644 --- a/packages/block-editor/src/components/inserter/style.scss +++ b/packages/block-editor/src/components/inserter/style.scss @@ -2,7 +2,7 @@ $block-inserter-content-height: 350px; $block-inserter-tabs-height: 44px; $block-inserter-search-height: 38px; -.editor-inserter { +.block-editor-inserter { display: inline-block; background: none; border: none; @@ -16,14 +16,14 @@ $block-inserter-search-height: 38px; } } -.editor-inserter__popover:not(.is-mobile) > .components-popover__content { +.block-editor-inserter__popover:not(.is-mobile) > .components-popover__content { @include break-medium { overflow-y: visible; height: $block-inserter-content-height + $block-inserter-tabs-height + $block-inserter-search-height; } } -.editor-inserter__toggle { +.block-editor-inserter__toggle { display: inline-flex; align-items: center; color: $dark-gray-500; @@ -34,7 +34,7 @@ $block-inserter-search-height: 38px; transition: color 0.2s ease; } -.editor-inserter__menu { +.block-editor-inserter__menu { width: auto; display: flex; flex-direction: column; @@ -43,7 +43,7 @@ $block-inserter-search-height: 38px; width: 400px; position: relative; - .editor-block-preview { + .block-editor-block-preview { border: $border-width solid $light-gray-500; box-shadow: $shadow-popover; background: $white; @@ -56,15 +56,15 @@ $block-inserter-search-height: 38px; } } -.editor-inserter__inline-elements { +.block-editor-inserter__inline-elements { margin-top: -1px; } -.editor-inserter__menu.is-bottom::after { +.block-editor-inserter__menu.is-bottom::after { border-bottom-color: $white; } -.components-popover input[type="search"].editor-inserter__search { +.components-popover input[type="search"].block-editor-inserter__search { display: block; margin: $grid-size-large; padding: 11px $grid-size-large; @@ -83,7 +83,7 @@ $block-inserter-search-height: 38px; } } -.editor-inserter__results { +.block-editor-inserter__results { flex-grow: 1; overflow: auto; position: relative; @@ -104,30 +104,30 @@ $block-inserter-search-height: 38px; } } -.editor-inserter__popover .editor-block-types-list { +.block-editor-inserter__popover .block-editor-block-types-list { margin: 0 -8px; } -.editor-inserter__reusable-blocks-panel { +.block-editor-inserter__reusable-blocks-panel { position: relative; text-align: right; } -.editor-inserter__manage-reusable-blocks { +.block-editor-inserter__manage-reusable-blocks { margin: $grid-size-large 0 0 $grid-size-large; } -.editor-inserter__no-results { +.block-editor-inserter__no-results { font-style: italic; padding: 24px; text-align: center; } -.editor-inserter__child-blocks { +.block-editor-inserter__child-blocks { padding: 0 $grid-size-large; } -.editor-inserter__parent-block-header { +.block-editor-inserter__parent-block-header { display: flex; align-items: center; @@ -135,7 +135,7 @@ $block-inserter-search-height: 38px; font-size: 13px; } - .editor-block-icon { + .block-editor-block-icon { margin-right: $grid-size; } } diff --git a/packages/block-editor/src/components/inserter/test/menu.js b/packages/block-editor/src/components/inserter/test/menu.js index 0ad4ef34c1a5d..421b76d6b3593 100644 --- a/packages/block-editor/src/components/inserter/test/menu.js +++ b/packages/block-editor/src/components/inserter/test/menu.js @@ -125,14 +125,14 @@ const initializeAllClosedMenuStateAndReturnElement = ( propOverrides ) => { const assertNoResultsMessageToBePresent = ( element ) => { const noResultsMessage = element.querySelector( - '.editor-inserter__no-results' + '.block-editor-inserter__no-results' ); expect( noResultsMessage.textContent ).toEqual( 'No blocks found.' ); }; const assertNoResultsMessageNotToBePresent = ( element ) => { const noResultsMessage = element.querySelector( - '.editor-inserter__no-results' + '.block-editor-inserter__no-results' ); expect( noResultsMessage ).toBe( null ); }; @@ -154,7 +154,7 @@ const getTabButtonWithContent = ( element, content ) => { }; const performSearchWithText = ( element, searchText ) => { - const searchElement = element.querySelector( '.editor-inserter__search' ); + const searchElement = element.querySelector( '.block-editor-inserter__search' ); TestUtils.Simulate.change( searchElement, { target: { value: searchText } } ); }; @@ -172,7 +172,7 @@ describe( 'InserterMenu', () => { { items: [] } ); const visibleBlocks = element.querySelector( - '.editor-block-types-list__item' + '.block-editor-block-types-list__item' ); expect( visibleBlocks ).toBe( null ); @@ -183,7 +183,7 @@ describe( 'InserterMenu', () => { it( 'should show only high utility items in the suggested tab', () => { const element = initializeMenuDefaultStateAndReturnElement(); const visibleBlocks = element.querySelectorAll( - '.editor-block-types-list__item-title' + '.block-editor-block-types-list__item-title' ); expect( visibleBlocks ).toHaveLength( 3 ); expect( visibleBlocks[ 0 ].textContent ).toEqual( 'Text' ); @@ -196,7 +196,7 @@ describe( 'InserterMenu', () => { { maxSuggestedItems: 2 } ); const visibleBlocks = element.querySelectorAll( - '.editor-block-types-list__list-item' + '.block-editor-block-types-list__list-item' ); expect( visibleBlocks ).toHaveLength( 2 ); } ); @@ -210,7 +210,7 @@ describe( 'InserterMenu', () => { assertOpenedPanels( element, 1 ); const visibleBlocks = element.querySelectorAll( - '.editor-block-types-list__item-title' + '.block-editor-block-types-list__item-title' ); expect( visibleBlocks ).toHaveLength( 2 ); @@ -229,7 +229,7 @@ describe( 'InserterMenu', () => { assertOpenedPanels( element, 1 ); const visibleBlocks = element.querySelectorAll( - '.editor-block-types-list__item-title' + '.block-editor-block-types-list__item-title' ); expect( visibleBlocks ).toHaveLength( 1 ); @@ -247,7 +247,7 @@ describe( 'InserterMenu', () => { assertOpenedPanels( element, 1 ); const visibleBlocks = element.querySelectorAll( - '.editor-block-types-list__item-title' + '.block-editor-block-types-list__item-title' ); expect( visibleBlocks ).toHaveLength( 3 ); @@ -265,7 +265,7 @@ describe( 'InserterMenu', () => { TestUtils.Simulate.click( layoutTab ); const disabledBlocks = element.querySelectorAll( - '.editor-block-types-list__item[disabled]' + '.block-editor-block-types-list__item[disabled]' ); expect( disabledBlocks ).toHaveLength( 1 ); @@ -287,7 +287,7 @@ describe( 'InserterMenu', () => { expect( matchingCategories[ 1 ].textContent ).toBe( 'Embeds' ); const visibleBlocks = element.querySelectorAll( - '.editor-block-types-list__item-title' + '.block-editor-block-types-list__item-title' ); expect( visibleBlocks ).toHaveLength( 3 ); @@ -313,7 +313,7 @@ describe( 'InserterMenu', () => { expect( matchingCategories[ 1 ].textContent ).toBe( 'Embeds' ); const visibleBlocks = element.querySelectorAll( - '.editor-block-types-list__item-title' + '.block-editor-block-types-list__item-title' ); expect( visibleBlocks ).toHaveLength( 3 ); diff --git a/packages/block-editor/src/components/media-placeholder/index.js b/packages/block-editor/src/components/media-placeholder/index.js index 2270b87762ea3..d972aeca06bd7 100644 --- a/packages/block-editor/src/components/media-placeholder/index.js +++ b/packages/block-editor/src/components/media-placeholder/index.js @@ -30,11 +30,11 @@ import URLPopover from '../url-popover'; const InsertFromURLPopover = ( { src, onChange, onSubmit, onClose } ) => ( <URLPopover onClose={ onClose }> <form - className="editor-media-placeholder__url-input-form" + className="editor-media-placeholder__url-input-form block-editor-media-placeholder__url-input-form" onSubmit={ onSubmit } > <input - className="editor-media-placeholder__url-input-field" + className="editor-media-placeholder__url-input-field block-editor-media-placeholder__url-input-field" type="url" aria-label={ __( 'URL' ) } placeholder={ __( 'Paste or type URL' ) } @@ -42,7 +42,7 @@ const InsertFromURLPopover = ( { src, onChange, onSubmit, onClose } ) => ( value={ src } /> <IconButton - className="editor-media-placeholder__url-input-submit-button" + className="editor-media-placeholder__url-input-submit-button block-editor-media-placeholder__url-input-submit-button" icon="editor-break" label={ __( 'Apply' ) } type="submit" @@ -198,7 +198,7 @@ export class MediaPlaceholder extends Component { icon={ icon } label={ title } instructions={ instructions } - className={ classnames( 'editor-media-placeholder', className ) } + className={ classnames( 'editor-media-placeholder block-editor-media-placeholder', className ) } notices={ notices } > <MediaUploadCheck> @@ -210,7 +210,7 @@ export class MediaPlaceholder extends Component { /> <FormFileUpload isLarge - className="editor-media-placeholder__button" + className="editor-media-placeholder__button block-editor-media-placeholder__button" onChange={ this.onUpload } accept={ accept } multiple={ multiple } @@ -228,7 +228,7 @@ export class MediaPlaceholder extends Component { render={ ( { open } ) => ( <Button isLarge - className="editor-media-placeholder__button" + className="editor-media-placeholder__button block-editor-media-placeholder__button" onClick={ open } > { __( 'Media Library' ) } @@ -237,9 +237,9 @@ export class MediaPlaceholder extends Component { /> </MediaUploadCheck> { onSelectURL && ( - <div className="editor-media-placeholder__url-input-container"> + <div className="editor-media-placeholder__url-input-container block-editor-media-placeholder__url-input-container"> <Button - className="editor-media-placeholder__button" + className="editor-media-placeholder__button block-editor-media-placeholder__button" onClick={ this.openURLInput } isToggled={ isURLInputVisible } isLarge diff --git a/packages/block-editor/src/components/media-placeholder/style.scss b/packages/block-editor/src/components/media-placeholder/style.scss index 526399f385c04..7cc50c523885a 100644 --- a/packages/block-editor/src/components/media-placeholder/style.scss +++ b/packages/block-editor/src/components/media-placeholder/style.scss @@ -1,17 +1,17 @@ -.editor-media-placeholder__url-input-container { +.block-editor-media-placeholder__url-input-container { width: 100%; // Reset the margin to ensure the url popover is adjacent to the button. - .editor-media-placeholder__button { + .block-editor-media-placeholder__button { margin-bottom: 0; } } -.editor-media-placeholder__url-input-form { +.block-editor-media-placeholder__url-input-form { display: flex; // Selector requires a lot of specificity to override base styles. - input[type="url"].editor-media-placeholder__url-input-field { + input[type="url"].block-editor-media-placeholder__url-input-field { width: 100%; @include break-small() { width: 300px; @@ -25,11 +25,11 @@ } } -.editor-media-placeholder__url-input-submit-button { +.block-editor-media-placeholder__url-input-submit-button { flex-shrink: 1; } -.editor-media-placeholder__button { +.block-editor-media-placeholder__button { margin-bottom: 0.5rem; .dashicon { @@ -42,6 +42,6 @@ } } -.components-form-file-upload .editor-media-placeholder__button { +.components-form-file-upload .block-editor-media-placeholder__button { margin-right: $grid-size-small; } diff --git a/packages/block-editor/src/components/multi-selection-inspector/index.js b/packages/block-editor/src/components/multi-selection-inspector/index.js index 5032676ae3ab1..bc0bcef955883 100644 --- a/packages/block-editor/src/components/multi-selection-inspector/index.js +++ b/packages/block-editor/src/components/multi-selection-inspector/index.js @@ -19,18 +19,18 @@ function MultiSelectionInspector( { blocks } ) { const words = wordCount( serialize( blocks ), 'words' ); return ( - <div className="editor-multi-selection-inspector__card"> + <div className="editor-multi-selection-inspector__card block-editor-multi-selection-inspector__card"> <BlockIcon icon={ <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><Path d="M3 5H1v16c0 1.1.9 2 2 2h16v-2H3V5zm18-4H7c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V3c0-1.1-.9-2-2-2zm0 16H7V3h14v14z" /></SVG> } showColors /> - <div className="editor-multi-selection-inspector__card-content"> - <div className="editor-multi-selection-inspector__card-title"> + <div className="editor-multi-selection-inspector__card-content block-editor-multi-selection-inspector__card-content"> + <div className="editor-multi-selection-inspector__card-title block-editor-multi-selection-inspector__card-title"> { /* translators: %d: number of blocks */ sprintf( _n( '%d block', '%d blocks', blocks.length ), blocks.length ) } </div> - <div className="editor-multi-selection-inspector__card-description"> + <div className="editor-multi-selection-inspector__card-description block-editor-multi-selection-inspector__card-description"> { /* translators: %d: number of words */ sprintf( _n( '%d word', '%d words', words ), words ) diff --git a/packages/block-editor/src/components/multi-selection-inspector/style.scss b/packages/block-editor/src/components/multi-selection-inspector/style.scss index 36023adaef78d..bd81710ebfcae 100644 --- a/packages/block-editor/src/components/multi-selection-inspector/style.scss +++ b/packages/block-editor/src/components/multi-selection-inspector/style.scss @@ -1,24 +1,24 @@ -.editor-multi-selection-inspector__card { +.block-editor-multi-selection-inspector__card { display: flex; align-items: flex-start; margin: -16px; padding: 16px; } -.editor-multi-selection-inspector__card-content { +.block-editor-multi-selection-inspector__card-content { flex-grow: 1; } -.editor-multi-selection-inspector__card-title { +.block-editor-multi-selection-inspector__card-title { font-weight: 500; margin-bottom: 5px; } -.editor-multi-selection-inspector__card-description { +.block-editor-multi-selection-inspector__card-description { font-size: $default-font-size; } -.editor-multi-selection-inspector__card .editor-block-icon { +.block-editor-multi-selection-inspector__card .block-editor-block-icon { margin-left: -2px; margin-right: 10px; padding: 0 3px; diff --git a/packages/block-editor/src/components/observe-typing/index.js b/packages/block-editor/src/components/observe-typing/index.js index a45931bcee0fd..6998eb771bf9e 100644 --- a/packages/block-editor/src/components/observe-typing/index.js +++ b/packages/block-editor/src/components/observe-typing/index.js @@ -145,7 +145,7 @@ class ObserveTyping extends Component { // Abort early if already typing, or key press is incurred outside a // text field (e.g. arrow-ing through toolbar buttons). // Ignore typing in a block toolbar - if ( isTyping || ! isTextField( target ) || target.closest( '.editor-block-toolbar' ) ) { + if ( isTyping || ! isTextField( target ) || target.closest( '.block-editor-block-toolbar' ) ) { return; } diff --git a/packages/block-editor/src/components/panel-color-settings/index.js b/packages/block-editor/src/components/panel-color-settings/index.js index 050b45f78791d..aaa684579a4a8 100644 --- a/packages/block-editor/src/components/panel-color-settings/index.js +++ b/packages/block-editor/src/components/panel-color-settings/index.js @@ -88,10 +88,8 @@ export const PanelColorSettings = ifCondition( hasColorsToChoose )( title, ...props } ) => { - const className = 'editor-panel-color-settings'; - const titleElement = ( - <span className={ `${ className }__panel-title` }> + <span className="editor-panel-color-settings__panel-title block-editor-panel-color-settings__panel-title"> { title } { renderColorIndicators( colorSettings, colors ) } </span> @@ -99,7 +97,7 @@ export const PanelColorSettings = ifCondition( hasColorsToChoose )( return ( <PanelBody - className={ className } + className="editor-panel-color-settings block-editor-panel-color-settings" title={ titleElement } { ...props } > diff --git a/packages/block-editor/src/components/panel-color-settings/style.scss b/packages/block-editor/src/components/panel-color-settings/style.scss index 933c9063f79a5..3a88a7ecb7adb 100644 --- a/packages/block-editor/src/components/panel-color-settings/style.scss +++ b/packages/block-editor/src/components/panel-color-settings/style.scss @@ -1,4 +1,4 @@ -.editor-panel-color-settings { +.block-editor-panel-color-settings { .component-color-indicator { vertical-align: text-bottom; } diff --git a/packages/block-editor/src/components/panel-color-settings/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/panel-color-settings/test/__snapshots__/index.js.snap index fa697d260d195..569bd074451bd 100644 --- a/packages/block-editor/src/components/panel-color-settings/test/__snapshots__/index.js.snap +++ b/packages/block-editor/src/components/panel-color-settings/test/__snapshots__/index.js.snap @@ -23,10 +23,10 @@ exports[`PanelColorSettings matches the snapshot 1`] = ` exports[`PanelColorSettings matches the snapshot 2`] = ` <ForwardRef(PanelBody) - className="editor-panel-color-settings" + className="editor-panel-color-settings block-editor-panel-color-settings" title={ <span - className="editor-panel-color-settings__panel-title" + className="editor-panel-color-settings__panel-title block-editor-panel-color-settings__panel-title" > Test Title <ColorIndicator @@ -88,10 +88,10 @@ exports[`PanelColorSettings should render a color panel if at least one setting exports[`PanelColorSettings should render a color panel if at least one setting specifies some colors to choose 2`] = ` <ForwardRef(PanelBody) - className="editor-panel-color-settings" + className="editor-panel-color-settings block-editor-panel-color-settings" title={ <span - className="editor-panel-color-settings__panel-title" + className="editor-panel-color-settings__panel-title block-editor-panel-color-settings__panel-title" > Test Title <ColorIndicator @@ -157,10 +157,10 @@ exports[`PanelColorSettings should render a color panel if at least one setting exports[`PanelColorSettings should render a color panel if at least one setting supports custom colors 2`] = ` <ForwardRef(PanelBody) - className="editor-panel-color-settings" + className="editor-panel-color-settings block-editor-panel-color-settings" title={ <span - className="editor-panel-color-settings__panel-title" + className="editor-panel-color-settings__panel-title block-editor-panel-color-settings__panel-title" > Test Title <ColorIndicator diff --git a/packages/block-editor/src/components/plain-text/index.js b/packages/block-editor/src/components/plain-text/index.js index 57b4f1e5d629a..a186c53ca6a55 100644 --- a/packages/block-editor/src/components/plain-text/index.js +++ b/packages/block-editor/src/components/plain-text/index.js @@ -7,7 +7,7 @@ import classnames from 'classnames'; function PlainText( { onChange, className, ...props } ) { return ( <TextareaAutosize - className={ classnames( 'editor-plain-text', className ) } + className={ classnames( 'editor-plain-text block-editor-plain-text', className ) } onChange={ ( event ) => onChange( event.target.value ) } { ...props } /> diff --git a/packages/block-editor/src/components/plain-text/index.native.js b/packages/block-editor/src/components/plain-text/index.native.js index b5c3591b22a6c..9dd7eba346152 100644 --- a/packages/block-editor/src/components/plain-text/index.native.js +++ b/packages/block-editor/src/components/plain-text/index.native.js @@ -41,13 +41,13 @@ export default class PlainText extends Component { <TextInput { ...this.props } ref={ ( x ) => this._input = x } - className={ [ styles[ 'editor-plain-text' ], this.props.className ] } + className={ [ styles[ 'block-editor-plain-text' ], this.props.className ] } onChange={ ( event ) => { this.props.onChange( event.nativeEvent.text ); } } onFocus={ this.props.onFocus } // always assign onFocus as a props onBlur={ this.props.onBlur } // always assign onBlur as a props - fontFamily={ this.props.fontFamily || ( styles[ 'editor-plain-text' ].fontFamily ) } + fontFamily={ this.props.fontFamily || ( styles[ 'block-editor-plain-text' ].fontFamily ) } fontSize={ this.props.fontSize } fontWeight={ this.props.fontWeight } fontStyle={ this.props.fontStyle } diff --git a/packages/block-editor/src/components/plain-text/style.native.scss b/packages/block-editor/src/components/plain-text/style.native.scss index b4d0f9effbb4d..97a21c5dd37a7 100644 --- a/packages/block-editor/src/components/plain-text/style.native.scss +++ b/packages/block-editor/src/components/plain-text/style.native.scss @@ -1,6 +1,6 @@ @import "variables.scss"; -.editor-plain-text { +.block-editor-plain-text { font-family: $default-regular-font; box-shadow: none; diff --git a/packages/block-editor/src/components/plain-text/style.scss b/packages/block-editor/src/components/plain-text/style.scss index fdb44eeea52f9..abcfaf5c8c1de 100644 --- a/packages/block-editor/src/components/plain-text/style.scss +++ b/packages/block-editor/src/components/plain-text/style.scss @@ -1,4 +1,4 @@ -.block-editor .editor-plain-text { +.block-editor .block-editor-plain-text { box-shadow: none; font-family: inherit; font-size: inherit; diff --git a/packages/block-editor/src/components/rich-text/editable.js b/packages/block-editor/src/components/rich-text/editable.js index e375794e907be..401843e00ac14 100644 --- a/packages/block-editor/src/components/rich-text/editable.js +++ b/packages/block-editor/src/components/rich-text/editable.js @@ -83,7 +83,7 @@ function applyInternetExplorerInputFix( editorNode ) { } const IS_PLACEHOLDER_VISIBLE_ATTR_NAME = 'data-is-placeholder-visible'; -const CLASS_NAME = 'editor-rich-text__editable'; +const CLASS_NAME = 'editor-rich-text__editable block-editor-rich-text__editable'; /** * Whether or not the user agent is Internet Explorer. diff --git a/packages/block-editor/src/components/rich-text/format-toolbar/index.js b/packages/block-editor/src/components/rich-text/format-toolbar/index.js index c980119c2ebe7..c1b5879626835 100644 --- a/packages/block-editor/src/components/rich-text/format-toolbar/index.js +++ b/packages/block-editor/src/components/rich-text/format-toolbar/index.js @@ -13,7 +13,7 @@ import { Toolbar, Slot, DropdownMenu } from '@wordpress/components'; const FormatToolbar = ( { controls } ) => { return ( - <div className="editor-format-toolbar"> + <div className="editor-format-toolbar block-editor-format-toolbar"> <Toolbar> { controls.map( ( format ) => <Slot name={ `RichText.ToolbarControls.${ format }` } key={ format } /> diff --git a/packages/block-editor/src/components/rich-text/format-toolbar/style.scss b/packages/block-editor/src/components/rich-text/format-toolbar/style.scss index c9f2d0de0cc91..dc493c3b0c845 100644 --- a/packages/block-editor/src/components/rich-text/format-toolbar/style.scss +++ b/packages/block-editor/src/components/rich-text/format-toolbar/style.scss @@ -1,13 +1,13 @@ -.editor-format-toolbar { +.block-editor-format-toolbar { display: flex; flex-shrink: 0; } -.editor-format-toolbar__selection-position { +.block-editor-format-toolbar__selection-position { position: absolute; transform: translateX(-50%); } -.editor-format-toolbar .components-dropdown-menu__toggle .components-dropdown-menu__indicator::after { +.block-editor-format-toolbar .components-dropdown-menu__toggle .components-dropdown-menu__indicator::after { margin: 7px; } diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 92afb3799eb90..b513c10f737d0 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -1060,7 +1060,7 @@ export class RichText extends Component { const MultilineTag = this.multilineTag; const ariaProps = pickAriaProps( this.props ); const isPlaceholderVisible = placeholder && ( ! isSelected || keepPlaceholderOnFocus ) && this.isEmpty(); - const classes = classnames( wrapperClassName, 'editor-rich-text' ); + const classes = classnames( wrapperClassName, 'editor-rich-text block-editor-rich-text' ); const record = this.getRecord(); return ( @@ -1081,7 +1081,7 @@ export class RichText extends Component { </BlockFormatControls> ) } { isSelected && inlineToolbar && ( - <IsolatedEventContainer className="editor-rich-text__inline-toolbar"> + <IsolatedEventContainer className="editor-rich-text__inline-toolbar block-editor-rich-text__inline-toolbar"> <FormatToolbar controls={ formattingControls } /> </IsolatedEventContainer> ) } @@ -1120,7 +1120,7 @@ export class RichText extends Component { /> { isPlaceholderVisible && <Tagname - className={ classnames( 'editor-rich-text__editable', className ) } + className={ classnames( 'editor-rich-text__editable block-editor-rich-text__editable', className ) } style={ style } > { MultilineTag ? <MultilineTag>{ placeholder }</MultilineTag> : placeholder } diff --git a/packages/block-editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js index db750a18d41f2..883889c77eb12 100644 --- a/packages/block-editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -518,7 +518,7 @@ export class RichText extends Component { } } text={ { text: html, eventCount: this.lastEventCount } } placeholder={ this.props.placeholder } - placeholderTextColor={ this.props.placeholderTextColor || styles[ 'editor-rich-text' ].textDecorationColor } + placeholderTextColor={ this.props.placeholderTextColor || styles[ 'block-editor-rich-text' ].textDecorationColor } onChange={ this.onChange } onFocus={ this.props.onFocus } onBlur={ this.props.onBlur } @@ -534,7 +534,7 @@ export class RichText extends Component { color={ 'black' } maxImagesWidth={ 200 } style={ style } - fontFamily={ this.props.fontFamily || styles[ 'editor-rich-text' ].fontFamily } + fontFamily={ this.props.fontFamily || styles[ 'block-editor-rich-text' ].fontFamily } fontSize={ this.props.fontSize } fontWeight={ this.props.fontWeight } fontStyle={ this.props.fontStyle } diff --git a/packages/block-editor/src/components/rich-text/style.native.scss b/packages/block-editor/src/components/rich-text/style.native.scss index b1207a4338aa4..9436fc9a63528 100644 --- a/packages/block-editor/src/components/rich-text/style.native.scss +++ b/packages/block-editor/src/components/rich-text/style.native.scss @@ -1,7 +1,7 @@ @import "variables.scss"; @import "colors.scss"; -.editor-rich-text { +.block-editor-rich-text { font-family: $default-regular-font; text-decoration-color: $gray; } diff --git a/packages/block-editor/src/components/rich-text/style.scss b/packages/block-editor/src/components/rich-text/style.scss index f731eda584184..16fdc282020e0 100644 --- a/packages/block-editor/src/components/rich-text/style.scss +++ b/packages/block-editor/src/components/rich-text/style.scss @@ -1,10 +1,10 @@ -.editor-rich-text { +.block-editor-rich-text { // This is needed to position the formatting toolbar. position: relative; } -.editor-rich-text__editable { +.block-editor-rich-text__editable { margin: 0; position: relative; // In HTML, leading and trailing spaces are not visible, and multiple spaces @@ -84,7 +84,7 @@ } // Placeholder text. - & + .editor-rich-text__editable { + & + .block-editor-rich-text__editable { pointer-events: none; // Use opacity to work in various editor styles. @@ -97,12 +97,12 @@ // Captions may have lighter (gray) text, or be shown on a range of different background luminosites. // To ensure legibility, we increase the default placeholder opacity to ensure contrast. - &[data-is-placeholder-visible="true"] + figcaption.editor-rich-text__editable { + &[data-is-placeholder-visible="true"] + figcaption.block-editor-rich-text__editable { opacity: 0.8; } } -.editor-rich-text__inline-toolbar { +.block-editor-rich-text__inline-toolbar { display: flex; justify-content: center; position: absolute; diff --git a/packages/block-editor/src/components/skip-to-selected-block/index.js b/packages/block-editor/src/components/skip-to-selected-block/index.js index 419d94b537344..672baa3920fd8 100644 --- a/packages/block-editor/src/components/skip-to-selected-block/index.js +++ b/packages/block-editor/src/components/skip-to-selected-block/index.js @@ -18,7 +18,7 @@ const SkipToSelectedBlock = ( { selectedBlockClientId } ) => { return ( selectedBlockClientId && - <Button isDefault type="button" className="editor-skip-to-selected-block" onClick={ onClick }> + <Button isDefault type="button" className="editor-skip-to-selected-block block-editor-skip-to-selected-block" onClick={ onClick }> { __( 'Skip to the selected block' ) } </Button> ); diff --git a/packages/block-editor/src/components/skip-to-selected-block/style.scss b/packages/block-editor/src/components/skip-to-selected-block/style.scss index 82195ecadf920..a0c707df2167a 100644 --- a/packages/block-editor/src/components/skip-to-selected-block/style.scss +++ b/packages/block-editor/src/components/skip-to-selected-block/style.scss @@ -1,4 +1,4 @@ -.editor-skip-to-selected-block { +.block-editor-skip-to-selected-block { position: absolute; top: -9999em; diff --git a/packages/block-editor/src/components/url-input/button.js b/packages/block-editor/src/components/url-input/button.js index e8b765f5282e7..1a783a62f36a9 100644 --- a/packages/block-editor/src/components/url-input/button.js +++ b/packages/block-editor/src/components/url-input/button.js @@ -40,7 +40,7 @@ class URLInputButton extends Component { const buttonLabel = url ? __( 'Edit Link' ) : __( 'Insert Link' ); return ( - <div className="editor-url-input__button"> + <div className="editor-url-input__button block-editor-url-input__button"> <IconButton icon="admin-links" label={ buttonLabel } @@ -51,12 +51,12 @@ class URLInputButton extends Component { /> { expanded && <form - className="editor-url-input__button-modal" + className="editor-url-input__button-modal block-editor-url-input__button-modal" onSubmit={ this.submitLink } > - <div className="editor-url-input__button-modal-line"> + <div className="editor-url-input__button-modal-line block-editor-url-input__button-modal-line"> <IconButton - className="editor-url-input__back" + className="editor-url-input__back block-editor-url-input__back" icon="arrow-left-alt" label={ __( 'Close' ) } onClick={ this.toggle } diff --git a/packages/block-editor/src/components/url-input/index.js b/packages/block-editor/src/components/url-input/index.js index db658990632e7..66504edec7def 100644 --- a/packages/block-editor/src/components/url-input/index.js +++ b/packages/block-editor/src/components/url-input/index.js @@ -231,7 +231,7 @@ class URLInput extends Component { const { showSuggestions, posts, selectedSuggestion, loading } = this.state; /* eslint-disable jsx-a11y/no-autofocus */ return ( - <div className={ classnames( 'editor-url-input', className ) }> + <div className={ classnames( 'editor-url-input block-editor-url-input', className ) }> <input autoFocus={ autoFocus } type="text" @@ -245,8 +245,8 @@ class URLInput extends Component { role="combobox" aria-expanded={ showSuggestions } aria-autocomplete="list" - aria-owns={ `editor-url-input-suggestions-${ instanceId }` } - aria-activedescendant={ selectedSuggestion !== null ? `editor-url-input-suggestion-${ instanceId }-${ selectedSuggestion }` : undefined } + aria-owns={ `block-editor-url-input-suggestions-${ instanceId }` } + aria-activedescendant={ selectedSuggestion !== null ? `block-editor-url-input-suggestion-${ instanceId }-${ selectedSuggestion }` : undefined } ref={ this.inputRef } /> @@ -255,7 +255,7 @@ class URLInput extends Component { { showSuggestions && !! posts.length && <Popover position="bottom" noArrow focusOnMount={ false }> <div - className="editor-url-input__suggestions" + className="editor-url-input__suggestions block-editor-url-input__suggestions" id={ `editor-url-input-suggestions-${ instanceId }` } ref={ this.autocompleteRef } role="listbox" @@ -265,9 +265,9 @@ class URLInput extends Component { key={ post.id } role="option" tabIndex="-1" - id={ `editor-url-input-suggestion-${ instanceId }-${ index }` } + id={ `block-editor-url-input-suggestion-${ instanceId }-${ index }` } ref={ this.bindSuggestionNode( index ) } - className={ classnames( 'editor-url-input__suggestion', { + className={ classnames( 'editor-url-input__suggestion block-editor-url-input__suggestion', { 'is-selected': index === selectedSuggestion, } ) } onClick={ () => this.handleOnClick( post ) } diff --git a/packages/block-editor/src/components/url-input/style.scss b/packages/block-editor/src/components/url-input/style.scss index 2c1662a4c2ed5..de6d71b9facea 100644 --- a/packages/block-editor/src/components/url-input/style.scss +++ b/packages/block-editor/src/components/url-input/style.scss @@ -2,9 +2,9 @@ $input-padding: 8px; $input-size: 300px; -.editor-block-list__block .editor-url-input, -.components-popover .editor-url-input, -.editor-url-input { +.block-editor-block-list__block .block-editor-url-input, +.components-popover .block-editor-url-input, +.block-editor-url-input { flex-grow: 1; position: relative; padding: 1px; @@ -40,7 +40,7 @@ $input-size: 300px; } // Suggestions -.editor-url-input__suggestions { +.block-editor-url-input__suggestions { max-height: 200px; transition: all 0.15s ease-in-out; padding: 4px 0; @@ -50,15 +50,15 @@ $input-size: 300px; } // Hide suggestions on mobile until we @todo find a better way to show them -.editor-url-input__suggestions, -.editor-url-input .components-spinner { +.block-editor-url-input__suggestions, +.block-editor-url-input .components-spinner { display: none; @include break-small() { display: inherit; } } -.editor-url-input__suggestion { +.block-editor-url-input__suggestion { padding: 4px $input-padding; color: $dark-gray-300; // lightest we can use for contrast display: block; @@ -83,11 +83,11 @@ $input-size: 300px; } // Toolbar button -.components-toolbar > .editor-url-input__button { +.components-toolbar > .block-editor-url-input__button { position: inherit; // Let the dialog position according to parent. } -.editor-url-input__button .editor-url-input__back { +.block-editor-url-input__button .block-editor-url-input__back { margin-right: 4px; overflow: visible; @@ -102,13 +102,13 @@ $input-size: 300px; } } -.editor-url-input__button-modal { +.block-editor-url-input__button-modal { box-shadow: $shadow-popover; border: 1px solid $light-gray-500; background: $white; } -.editor-url-input__button-modal-line { +.block-editor-url-input__button-modal-line { display: flex; flex-direction: row; flex-grow: 1; diff --git a/packages/block-editor/src/components/url-input/test/button.js b/packages/block-editor/src/components/url-input/test/button.js index b054a1a6ba14e..f9bc3aee58e8f 100644 --- a/packages/block-editor/src/components/url-input/test/button.js +++ b/packages/block-editor/src/components/url-input/test/button.js @@ -16,7 +16,7 @@ describe( 'URLInputButton', () => { it( 'should have a valid class name in the wrapper tag', () => { const wrapper = shallow( <URLInputButton /> ); - expect( wrapper.hasClass( 'editor-url-input__button' ) ).toBe( true ); + expect( wrapper.hasClass( 'block-editor-url-input__button' ) ).toBe( true ); } ); it( 'should not have is-active class when url prop not defined', () => { const wrapper = shallow( <URLInputButton /> ); @@ -56,7 +56,7 @@ describe( 'URLInputButton', () => { const wrapper = shallow( <URLInputButton /> ); clickEditLink( wrapper ); expect( wrapper.state().expanded ).toBe( true ); - wrapper.find( '.editor-url-input__back' ).simulate( 'click' ); + wrapper.find( '.block-editor-url-input__back' ).simulate( 'click' ); expect( wrapper.state().expanded ).toBe( false ); } ); it( 'should close the form when user submits it', () => { diff --git a/packages/block-editor/src/components/url-popover/index.js b/packages/block-editor/src/components/url-popover/index.js index 5842dd6017b5e..d173d110c3977 100644 --- a/packages/block-editor/src/components/url-popover/index.js +++ b/packages/block-editor/src/components/url-popover/index.js @@ -43,17 +43,17 @@ class URLPopover extends Component { return ( <Popover - className="editor-url-popover" + className="editor-url-popover block-editor-url-popover" focusOnMount={ focusOnMount } position={ position } onClose={ onClose } onClickOutside={ onClickOutside } > - <div className="editor-url-popover__row"> + <div className="editor-url-popover__row block-editor-url-popover__row"> { children } { !! renderSettings && ( <IconButton - className="editor-url-popover__settings-toggle" + className="editor-url-popover__settings-toggle block-editor-url-popover__settings-toggle" icon="arrow-down-alt2" label={ __( 'Link Settings' ) } onClick={ this.toggleSettingsVisibility } @@ -62,7 +62,7 @@ class URLPopover extends Component { ) } </div> { showSettings && ( - <div className="editor-url-popover__row editor-url-popover__settings"> + <div className="editor-url-popover__row block-editor-url-popover__row editor-url-popover__settings block-editor-url-popover__settings"> { renderSettings() } </div> ) } diff --git a/packages/block-editor/src/components/url-popover/style.scss b/packages/block-editor/src/components/url-popover/style.scss index 51f16cadfb24c..0b98f11afcd31 100644 --- a/packages/block-editor/src/components/url-popover/style.scss +++ b/packages/block-editor/src/components/url-popover/style.scss @@ -1,15 +1,15 @@ -.editor-url-popover__row { +.block-editor-url-popover__row { display: flex; } // Any children of the popover-row that are not the settings-toggle // should take up as much space as possible. -.editor-url-popover__row > :not(.editor-url-popover__settings-toggle) { +.block-editor-url-popover__row > :not(.block-editor-url-popover__settings-toggle) { flex-grow: 1; } // Mimic toolbar component styles for the icons in this popover. -.editor-url-popover .components-icon-button { +.block-editor-url-popover .components-icon-button { padding: 3px; > svg { @@ -36,7 +36,7 @@ } } -.editor-url-popover__settings-toggle { +.block-editor-url-popover__settings-toggle { flex-shrink: 0; // Add a left divider to the toggle button. @@ -49,7 +49,7 @@ } } -.editor-url-popover__settings { +.block-editor-url-popover__settings { padding: $panel-padding; border-top: $border-width solid $light-gray-500; diff --git a/packages/block-editor/src/components/url-popover/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/url-popover/test/__snapshots__/index.js.snap index ccb4526f87457..9613a40af346a 100644 --- a/packages/block-editor/src/components/url-popover/test/__snapshots__/index.js.snap +++ b/packages/block-editor/src/components/url-popover/test/__snapshots__/index.js.snap @@ -2,20 +2,20 @@ exports[`URLPopover matches the snapshot in its default state 1`] = ` <Popover - className="editor-url-popover" + className="editor-url-popover block-editor-url-popover" focusOnMount="firstElement" noArrow={false} position="bottom center" > <div - className="editor-url-popover__row" + className="editor-url-popover__row block-editor-url-popover__row" > <div> Editor </div> <ForwardRef(IconButton) aria-expanded={false} - className="editor-url-popover__settings-toggle" + className="editor-url-popover__settings-toggle block-editor-url-popover__settings-toggle" icon="arrow-down-alt2" label="Link Settings" onClick={[Function]} @@ -26,27 +26,27 @@ exports[`URLPopover matches the snapshot in its default state 1`] = ` exports[`URLPopover matches the snapshot when the settings are toggled open 1`] = ` <Popover - className="editor-url-popover" + className="editor-url-popover block-editor-url-popover" focusOnMount="firstElement" noArrow={false} position="bottom center" > <div - className="editor-url-popover__row" + className="editor-url-popover__row block-editor-url-popover__row" > <div> Editor </div> <ForwardRef(IconButton) aria-expanded={true} - className="editor-url-popover__settings-toggle" + className="editor-url-popover__settings-toggle block-editor-url-popover__settings-toggle" icon="arrow-down-alt2" label="Link Settings" onClick={[Function]} /> </div> <div - className="editor-url-popover__row editor-url-popover__settings" + className="editor-url-popover__row block-editor-url-popover__row editor-url-popover__settings block-editor-url-popover__settings" > <div> Settings @@ -57,13 +57,13 @@ exports[`URLPopover matches the snapshot when the settings are toggled open 1`] exports[`URLPopover matches the snapshot when there are no settings 1`] = ` <Popover - className="editor-url-popover" + className="editor-url-popover block-editor-url-popover" focusOnMount="firstElement" noArrow={false} position="bottom center" > <div - className="editor-url-popover__row" + className="editor-url-popover__row block-editor-url-popover__row" > <div> Editor diff --git a/packages/block-editor/src/components/url-popover/test/index.js b/packages/block-editor/src/components/url-popover/test/index.js index 0dc0b9f420b67..e541343151034 100644 --- a/packages/block-editor/src/components/url-popover/test/index.js +++ b/packages/block-editor/src/components/url-popover/test/index.js @@ -34,7 +34,7 @@ describe( 'URLPopover', () => { </URLPopover> ); - const toggleButton = wrapper.find( '.editor-url-popover__settings-toggle' ); + const toggleButton = wrapper.find( '.block-editor-url-popover__settings-toggle' ); expect( toggleButton ).toHaveLength( 1 ); toggleButton.simulate( 'click' ); diff --git a/packages/block-editor/src/components/warning/index.js b/packages/block-editor/src/components/warning/index.js index 1aa0012853287..38e7dc18a6450 100644 --- a/packages/block-editor/src/components/warning/index.js +++ b/packages/block-editor/src/components/warning/index.js @@ -12,14 +12,14 @@ import { __ } from '@wordpress/i18n'; function Warning( { className, actions, children, secondaryActions } ) { return ( - <div className={ classnames( className, 'editor-warning' ) }> - <div className="editor-warning__contents"> - <p className="editor-warning__message">{ children }</p> + <div className={ classnames( className, 'editor-warning block-editor-warning' ) }> + <div className="editor-warning__contents block-editor-warning__contents"> + <p className="editor-warning__message block-editor-warning__message">{ children }</p> { Children.count( actions ) > 0 && ( - <div className="editor-warning__actions"> + <div className="editor-warning__actions block-editor-warning__actions"> { Children.map( actions, ( action, i ) => ( - <span key={ i } className="editor-warning__action"> + <span key={ i } className="editor-warning__action block-editor-warning__action"> { action } </span> ) ) } @@ -29,7 +29,7 @@ function Warning( { className, actions, children, secondaryActions } ) { { secondaryActions && ( <Dropdown - className="editor-warning__secondary" + className="editor-warning__secondary block-editor-warning__secondary" position="bottom left" renderToggle={ ( { isOpen, onToggle } ) => ( <IconButton diff --git a/packages/block-editor/src/components/warning/style.scss b/packages/block-editor/src/components/warning/style.scss index ecfb3f0c99770..27f571d6a7a77 100644 --- a/packages/block-editor/src/components/warning/style.scss +++ b/packages/block-editor/src/components/warning/style.scss @@ -1,4 +1,4 @@ -.editor-warning { +.block-editor-warning { display: flex; flex-direction: row; justify-content: space-between; @@ -13,13 +13,13 @@ background-color: transparent; } - .editor-warning__message { + .block-editor-warning__message { line-height: $default-line-height; font-family: $default-font; font-size: $default-font-size; } - .editor-warning__contents { + .block-editor-warning__contents { display: flex; flex-direction: row; justify-content: space-between; @@ -28,16 +28,16 @@ width: 100%; } - .editor-warning__actions { + .block-editor-warning__actions { display: flex; } - .editor-warning__action { + .block-editor-warning__action { margin: 0 6px 0 0; } } -.editor-warning__secondary { +.block-editor-warning__secondary { margin: 3px 0 0 -4px; // the padding and margin of the more menu is intentionally non-standard diff --git a/packages/block-editor/src/components/warning/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/warning/test/__snapshots__/index.js.snap index 5e2a8231c7dfd..de9e8358fdb80 100644 --- a/packages/block-editor/src/components/warning/test/__snapshots__/index.js.snap +++ b/packages/block-editor/src/components/warning/test/__snapshots__/index.js.snap @@ -2,13 +2,13 @@ exports[`Warning should match snapshot 1`] = ` <div - className="editor-warning" + className="editor-warning block-editor-warning" > <div - className="editor-warning__contents" + className="editor-warning__contents block-editor-warning__contents" > <p - className="editor-warning__message" + className="editor-warning__message block-editor-warning__message" > error </p> diff --git a/packages/block-editor/src/components/warning/test/index.js b/packages/block-editor/src/components/warning/test/index.js index 71276f17078eb..f1a10c4e6fbc3 100644 --- a/packages/block-editor/src/components/warning/test/index.js +++ b/packages/block-editor/src/components/warning/test/index.js @@ -18,26 +18,26 @@ describe( 'Warning', () => { it( 'should have valid class', () => { const wrapper = shallow( <Warning /> ); - expect( wrapper.hasClass( 'editor-warning' ) ).toBe( true ); - expect( wrapper.find( '.editor-warning__actions' ) ).toHaveLength( 0 ); - expect( wrapper.find( '.editor-warning__hidden' ) ).toHaveLength( 0 ); + expect( wrapper.hasClass( 'block-editor-warning' ) ).toBe( true ); + expect( wrapper.find( '.block-editor-warning__actions' ) ).toHaveLength( 0 ); + expect( wrapper.find( '.block-editor-warning__hidden' ) ).toHaveLength( 0 ); } ); it( 'should show child error message element', () => { const wrapper = shallow( <Warning actions={ <button /> }>Message</Warning> ); - const actions = wrapper.find( '.editor-warning__actions' ); + const actions = wrapper.find( '.block-editor-warning__actions' ); const action = actions.childAt( 0 ); expect( actions ).toHaveLength( 1 ); - expect( action.hasClass( 'editor-warning__action' ) ).toBe( true ); + expect( action.hasClass( 'block-editor-warning__action' ) ).toBe( true ); expect( action.childAt( 0 ).type() ).toBe( 'button' ); } ); it( 'should show hidden actions', () => { const wrapper = shallow( <Warning secondaryActions={ [ { title: 'test', onClick: null } ] }>Message</Warning> ); - const actions = wrapper.find( '.editor-warning__secondary' ); + const actions = wrapper.find( '.block-editor-warning__secondary' ); expect( actions ).toHaveLength( 1 ); } ); diff --git a/packages/block-editor/src/components/writing-flow/index.js b/packages/block-editor/src/components/writing-flow/index.js index 12ea7da18f2e2..92b97badc11c3 100644 --- a/packages/block-editor/src/components/writing-flow/index.js +++ b/packages/block-editor/src/components/writing-flow/index.js @@ -324,7 +324,7 @@ class WritingFlow extends Component { // bubbling events from children to determine focus transition intents. /* eslint-disable jsx-a11y/no-static-element-interactions */ return ( - <div className="editor-writing-flow"> + <div className="editor-writing-flow block-editor-writing-flow"> <div ref={ this.bindContainer } onKeyDown={ this.onKeyDown } @@ -336,7 +336,7 @@ class WritingFlow extends Component { aria-hidden tabIndex={ -1 } onClick={ this.focusLastTextField } - className="wp-block editor-writing-flow__click-redirect" + className="wp-block editor-writing-flow__click-redirect block-editor-writing-flow__click-redirect" /> </div> ); diff --git a/packages/block-editor/src/components/writing-flow/style.scss b/packages/block-editor/src/components/writing-flow/style.scss index 06a7d77c1a69c..e1ff5e860ad14 100644 --- a/packages/block-editor/src/components/writing-flow/style.scss +++ b/packages/block-editor/src/components/writing-flow/style.scss @@ -1,10 +1,10 @@ -.editor-writing-flow { +.block-editor-writing-flow { height: 100%; display: flex; flex-direction: column; } -.editor-writing-flow__click-redirect { +.block-editor-writing-flow__click-redirect { flex-basis: 100%; cursor: text; } diff --git a/packages/block-editor/src/utils/dom.js b/packages/block-editor/src/utils/dom.js index 8ee6fef110d79..f6d3328d2fd86 100644 --- a/packages/block-editor/src/utils/dom.js +++ b/packages/block-editor/src/utils/dom.js @@ -22,7 +22,7 @@ export function getBlockDOMNode( clientId ) { * @return {Element} Block DOM node. */ export function getBlockFocusableWrapper( clientId ) { - return getBlockDOMNode( clientId ).closest( '.editor-block-list__block' ); + return getBlockDOMNode( clientId ).closest( '.block-editor-block-list__block' ); } /** @@ -34,7 +34,7 @@ export function getBlockFocusableWrapper( clientId ) { * @return {boolean} Whether element is a block focus stop. */ export function isBlockFocusStop( element ) { - return element.classList.contains( 'editor-block-list__block' ); + return element.classList.contains( 'block-editor-block-list__block' ); } /** @@ -58,7 +58,7 @@ export function isInSameBlock( a, b ) { * @return {boolean} Whether element is in the block Element but not its children. */ export function isInsideRootBlock( blockElement, element ) { - const innerBlocksContainer = blockElement.querySelector( '.editor-block-list__layout' ); + const innerBlocksContainer = blockElement.querySelector( '.block-editor-block-list__layout' ); return blockElement.contains( element ) && ( ! innerBlocksContainer || ! innerBlocksContainer.contains( element ) ); @@ -73,5 +73,5 @@ export function isInsideRootBlock( blockElement, element ) { * @return {boolean} Whether element contains inner blocks. */ export function hasInnerBlocksContext( element ) { - return !! element.querySelector( '.editor-block-list__layout' ); + return !! element.querySelector( '.block-editor-block-list__layout' ); } diff --git a/packages/block-editor/src/utils/test/dom.js b/packages/block-editor/src/utils/test/dom.js index cb42bbab841e4..45533fa436a19 100644 --- a/packages/block-editor/src/utils/test/dom.js +++ b/packages/block-editor/src/utils/test/dom.js @@ -7,8 +7,8 @@ describe( 'hasInnerBlocksContext()', () => { it( 'should return false for a block node which has no inner blocks', () => { const wrapper = document.createElement( 'div' ); wrapper.innerHTML = ( - '<div class="editor-block-list__block" data-type="core/paragraph" tabindex="0">' + - ' <div class="editor-block-list__block-edit" aria-label="Block: Paragraph">' + + '<div class="editor-block-list__block block-editor-block-list__block" data-type="core/paragraph" tabindex="0">' + + ' <div class="editor-block-list__block-edit block-editor-block-list__block-edit" aria-label="Block: Paragraph">' + ' <p contenteditable="true">This is a test.</p>' + ' </div>' + '</div>' @@ -21,11 +21,11 @@ describe( 'hasInnerBlocksContext()', () => { it( 'should return true for a block node which contains inner blocks', () => { const wrapper = document.createElement( 'div' ); wrapper.innerHTML = ( - '<div class="editor-block-list__block" data-type="core/columns" tabindex="0">' + - ' <div class="editor-block-list__block-edit" aria-label="Block: Columns (beta)">' + + '<div class="editor-block-list__block block-editor-block-list__block" data-type="core/columns" tabindex="0">' + + ' <div class="editor-block-list__block-edit block-editor-block-list__block-edit" aria-label="Block: Columns (beta)">' + ' <div class="wp-block-columns has-2-columns">' + - ' <div class="editor-block-list__layout"></div>' + - ' <div class="editor-block-list__layout"></div>' + + ' <div class="editor-block-list__layout block-editor-block-list__layout"></div>' + + ' <div class="editor-block-list__layout block-editor-block-list__layout"></div>' + ' </div>' + ' </div>' + '</div>' diff --git a/packages/block-library/src/block/edit-panel/style.scss b/packages/block-library/src/block/edit-panel/style.scss index 4573abd8ad81c..0b899669c08f2 100644 --- a/packages/block-library/src/block/edit-panel/style.scss +++ b/packages/block-library/src/block/edit-panel/style.scss @@ -1,4 +1,4 @@ -.editor-block-list__layout .reusable-block-edit-panel { +.block-editor-block-list__layout .reusable-block-edit-panel { align-items: center; background: $light-gray-100; color: $dark-gray-500; @@ -13,7 +13,7 @@ position: relative; // Show a smaller padding when nested. - .editor-block-list__layout & { + .block-editor-block-list__layout & { margin: 0 (-$block-padding); padding: $grid-size $block-padding; } diff --git a/packages/block-library/src/block/indicator/style.scss b/packages/block-library/src/block/indicator/style.scss index 1f3937ea99cd9..2e02c866c3d88 100644 --- a/packages/block-library/src/block/indicator/style.scss +++ b/packages/block-library/src/block/indicator/style.scss @@ -1,4 +1,4 @@ -.editor-block-list__layout .reusable-block-indicator { +.block-editor-block-list__layout .reusable-block-indicator { background: $white; border-left: $border-width dashed $light-gray-500; color: $dark-gray-500; @@ -7,7 +7,7 @@ height: 30px; padding: $grid-size-small; position: absolute; - z-index: z-index(".editor-block-list__layout .reusable-block-indicator"); + z-index: z-index(".block-editor-block-list__layout .reusable-block-indicator"); width: 30px; right: -$block-padding; } diff --git a/packages/block-library/src/button/editor.scss b/packages/block-library/src/button/editor.scss index 0b65eac8bcb78..713e508bef055 100644 --- a/packages/block-library/src/button/editor.scss +++ b/packages/block-library/src/button/editor.scss @@ -1,4 +1,4 @@ -.editor-block-list__block[data-type="core/button"] { +.block-editor-block-list__block[data-type="core/button"] { &[data-align="center"] { text-align: center; } @@ -19,21 +19,21 @@ } // Make placeholder text white unless custom colors or outline versions are chosen. - &:not(.has-text-color):not(.is-style-outline) .editor-rich-text__editable[data-is-placeholder-visible="true"] + .editor-rich-text__editable { + &:not(.has-text-color):not(.is-style-outline) .block-editor-rich-text__editable[data-is-placeholder-visible="true"] + .block-editor-rich-text__editable { color: $white; } // Increase placeholder opacity to meet contrast ratios. - .editor-rich-text__editable[data-is-placeholder-visible="true"] + .editor-rich-text__editable { + .block-editor-rich-text__editable[data-is-placeholder-visible="true"] + .block-editor-rich-text__editable { opacity: 0.8; } // Don't let the placeholder text wrap in the variation preview. - .editor-block-preview__content & { + .block-editor-block-preview__content & { max-width: 100%; // Polish the empty placeholder text for the button in variation previews. - .editor-rich-text__editable[data-is-placeholder-visible="true"] { + .block-editor-rich-text__editable[data-is-placeholder-visible="true"] { height: auto; } @@ -59,13 +59,13 @@ $blocks-button__link-input-width: 300px + 2px + 2 * $icon-button-size; width: $blocks-button__link-input-width; - .editor-url-input { + .block-editor-url-input { width: auto; } - .editor-url-input__suggestions { + .block-editor-url-input__suggestions { width: $blocks-button__link-input-width - $icon-button-size - $icon-button-size; - z-index: z-index(".block-library-button__inline-link .editor-url-input__suggestions"); + z-index: z-index(".block-library-button__inline-link .block-editor-url-input__suggestions"); } > .dashicon { @@ -76,7 +76,7 @@ color: $dark-gray-100; } - .editor-url-input input[type="text"]::placeholder { + .block-editor-url-input input[type="text"]::placeholder { color: $dark-gray-100; } diff --git a/packages/block-library/src/classic/editor.scss b/packages/block-library/src/classic/editor.scss index 1fe580ef581c9..2bb0bd4f77a7f 100644 --- a/packages/block-library/src/classic/editor.scss +++ b/packages/block-library/src/classic/editor.scss @@ -180,7 +180,7 @@ } } -.editor-block-list__layout .editor-block-list__block[data-type="core/freeform"] { +.block-editor-block-list__layout .block-editor-block-list__block[data-type="core/freeform"] { // Not sure why this is necessary, there seems to be a skin file that overrides this upstream. .mce-btn.mce-active button, @@ -206,17 +206,17 @@ padding: 1px 3px; } - .editor-block-list__block-edit::before { + .block-editor-block-list__block-edit::before { outline: $border-width solid #e2e4e7; } // Don't show block type label for classic block - &.is-hovered .editor-block-list__breadcrumb { + &.is-hovered .block-editor-block-list__breadcrumb { display: none; } } -div[data-type="core/freeform"] .editor-block-contextual-toolbar + div { +div[data-type="core/freeform"] .block-editor-block-contextual-toolbar + div { margin-top: 0; padding-top: 0; } @@ -283,17 +283,17 @@ div[data-type="core/freeform"] .editor-block-contextual-toolbar + div { // We don't want the ellipsis to overlap the classic toolbar, which it will due to position sticky. // So we move it to the right, and make room for it. @include break-small() { - .editor-block-list__block[data-type="core/freeform"] { - .editor-block-switcher__no-switcher-icon { + .block-editor-block-list__block[data-type="core/freeform"] { + .block-editor-block-switcher__no-switcher-icon { display: none; } - .editor-block-contextual-toolbar { + .block-editor-block-contextual-toolbar { float: right; margin-right: $icon-button-size - $block-padding + $border-width; transform: translateY(-#{ $block-padding - $border-width }); top: $block-padding; - .editor-block-toolbar { + .block-editor-block-toolbar { border: none; // Match the TinyMCE "mobile" breakpoint buttons alignment. diff --git a/packages/block-library/src/code/editor.scss b/packages/block-library/src/code/editor.scss index ec54fd99ad2bc..5fb4e6e468205 100644 --- a/packages/block-library/src/code/editor.scss +++ b/packages/block-library/src/code/editor.scss @@ -1,4 +1,4 @@ -.wp-block-code .editor-plain-text { +.wp-block-code .block-editor-plain-text { font-family: $editor-html-font; color: $dark-gray-800; diff --git a/packages/block-library/src/columns/editor.scss b/packages/block-library/src/columns/editor.scss index cadafcd2ef360..2f316ef347154 100644 --- a/packages/block-library/src/columns/editor.scss +++ b/packages/block-library/src/columns/editor.scss @@ -2,12 +2,12 @@ // This is sort of an experiment at making sure the editor looks as much like the end result as possible // Potentially the rules here can apply to all nested blocks and enable stacking, in which case it should be moved elsewhere // When using CSS grid, margins do not collapse on the container. -.wp-block-columns .editor-block-list__layout { +.wp-block-columns .block-editor-block-list__layout { margin-left: 0; margin-right: 0; // This max-width is used to constrain the main editor column, it should not cascade into columns - .editor-block-list__block { + .block-editor-block-list__block { max-width: none; } } @@ -16,7 +16,7 @@ // This is not a 1:1 preview with the front-end where these margins would presumably be zero. // @todo This could be revisited, by for example showing this margin only when the parent block was selected first. // Then at least an unselected columns block would be an accurate preview. -.editor-block-list__block[data-align="full"] .wp-block-columns > .editor-inner-blocks { +.block-editor-block-list__block[data-align="full"] .wp-block-columns > .block-editor-inner-blocks { padding-left: $block-padding; padding-right: $block-padding; @@ -29,7 +29,7 @@ .wp-block-columns { display: block; - > .editor-inner-blocks > .editor-block-list__layout { + > .block-editor-inner-blocks > .block-editor-block-list__layout { display: flex; // Responsiveness: Allow wrapping on mobile. @@ -48,22 +48,22 @@ // The Column block is a child of the Columns block and is mostly a passthrough container. // Therefore it shouldn't add additional paddings and margins, so we reset these, and compensate for margins. // @todo In the future, if a passthrough feature lands, it would be good to apply these rules to such an element in a more generic way. - > .editor-block-list__block-edit > div > .editor-inner-blocks { + > .block-editor-block-list__block-edit > div > .block-editor-inner-blocks { margin-top: -$block-padding - $block-padding; margin-bottom: -$block-padding - $block-padding; } - > .editor-block-list__block-edit { + > .block-editor-block-list__block-edit { margin-top: 0; margin-bottom: 0; } // Extend the passthrough concept to the block paddings, which we zero out. - > .editor-block-list__block-edit::before { + > .block-editor-block-list__block-edit::before { left: 0; right: 0; } - > .editor-block-list__block-edit > .editor-block-contextual-toolbar { + > .block-editor-block-list__block-edit > .block-editor-block-contextual-toolbar { margin-left: -$border-width; } @@ -121,17 +121,17 @@ // Since those appenders are not blocks, the parent, actual block, appears "hovered" when hovering the appenders. // Because the column shouldn't be hovered as part of this temporary passthrough, we unset the hover style. &.is-hovered { - > .editor-block-list__block-edit::before { + > .block-editor-block-list__block-edit::before { content: none; } - .editor-block-list__breadcrumb { + .block-editor-block-list__breadcrumb { display: none; } } } // This selector re-enables clicking on any child of a column block. -:not(.components-disabled) > .wp-block-columns > .editor-inner-blocks > .editor-block-list__layout > [data-type="core/column"] > .editor-block-list__block-edit > * { +:not(.components-disabled) > .wp-block-columns > .block-editor-inner-blocks > .block-editor-block-list__layout > [data-type="core/column"] > .block-editor-block-list__block-edit > * { pointer-events: all; } diff --git a/packages/block-library/src/cover/editor.scss b/packages/block-library/src/cover/editor.scss index 6e84c3a4837eb..c74979dc0969d 100644 --- a/packages/block-library/src/cover/editor.scss +++ b/packages/block-library/src/cover/editor.scss @@ -1,6 +1,6 @@ .wp-block-cover-image, .wp-block-cover { - .editor-rich-text__editable:focus a[data-rich-text-format-boundary] { + .block-editor-rich-text__editable:focus a[data-rich-text-format-boundary] { box-shadow: none; background: rgba(255, 255, 255, 0.3); } @@ -9,16 +9,16 @@ color: inherit; } - &.has-right-content .editor-rich-text__inline-toolbar, - &.has-left-content .editor-rich-text__inline-toolbar { + &.has-right-content .block-editor-rich-text__inline-toolbar, + &.has-left-content .block-editor-rich-text__inline-toolbar { display: inline-block; } - .editor-block-list__layout { + .block-editor-block-list__layout { width: 100%; } - .editor-block-list__block { + .block-editor-block-list__block { color: $light-gray-100; } @@ -28,7 +28,7 @@ text-align: left; } - .wp-block-cover__inner-container > .editor-inner-blocks > .editor-block-list__layout { + .wp-block-cover__inner-container > .block-editor-inner-blocks > .block-editor-block-list__layout { margin-left: 0; margin-right: 0; } diff --git a/packages/block-library/src/embed/style.scss b/packages/block-library/src/embed/style.scss index dd636b47eb619..31c568c48b0b7 100644 --- a/packages/block-library/src/embed/style.scss +++ b/packages/block-library/src/embed/style.scss @@ -1,6 +1,6 @@ // Apply max-width to floated items that have no intrinsic width -.editor-block-list__block[data-type="core/embed"][data-align="left"] .editor-block-list__block-edit, -.editor-block-list__block[data-type="core/embed"][data-align="right"] .editor-block-list__block-edit, +.block-editor-block-list__block[data-type="core/embed"][data-align="left"] .block-editor-block-list__block-edit, +.block-editor-block-list__block[data-type="core/embed"][data-align="right"] .block-editor-block-list__block-edit, .wp-block-embed.alignleft, .wp-block-embed.alignright { // Instagram widgets have a min-width of 326px, so go a bit beyond that. diff --git a/packages/block-library/src/embed/test/__snapshots__/index.js.snap b/packages/block-library/src/embed/test/__snapshots__/index.js.snap index 47bbb2dd8c934..4b9c1f4de7f0e 100644 --- a/packages/block-library/src/embed/test/__snapshots__/index.js.snap +++ b/packages/block-library/src/embed/test/__snapshots__/index.js.snap @@ -8,7 +8,7 @@ exports[`core/embed block edit matches snapshot 1`] = ` class="components-placeholder__label" > <span - class="editor-block-icon has-colors" + class="editor-block-icon block-editor-block-icon has-colors" > <svg aria-hidden="true" diff --git a/packages/block-library/src/gallery/editor.scss b/packages/block-library/src/gallery/editor.scss index 7501869574b66..0340f66a31e8e 100644 --- a/packages/block-library/src/gallery/editor.scss +++ b/packages/block-library/src/gallery/editor.scss @@ -21,7 +21,7 @@ ul.wp-block-gallery li { opacity: 0.3; } - .editor-rich-text { + .block-editor-rich-text { position: absolute; bottom: 0; width: 100%; @@ -29,12 +29,12 @@ ul.wp-block-gallery li { overflow-y: auto; } - .editor-rich-text figcaption:not([data-is-placeholder-visible="true"]) { + .block-editor-rich-text figcaption:not([data-is-placeholder-visible="true"]) { position: relative; overflow: hidden; } - .is-selected .editor-rich-text { + .is-selected .block-editor-rich-text { // IE calculates this incorrectly, so leave it to modern browsers. @supports (position: sticky) { right: 0; @@ -43,7 +43,7 @@ ul.wp-block-gallery li { } // Override negative margins so this toolbar isn't hidden by overflow. Overflow is needed for long captions. - .editor-rich-text__inline-toolbar { + .block-editor-rich-text__inline-toolbar { top: 0; } @@ -78,7 +78,7 @@ ul.wp-block-gallery li { } } - .editor-rich-text figcaption { + .block-editor-rich-text figcaption { a { color: $white; } diff --git a/packages/block-library/src/html/editor.scss b/packages/block-library/src/html/editor.scss index 97d4b156aea79..7e93bd2fe5688 100644 --- a/packages/block-library/src/html/editor.scss +++ b/packages/block-library/src/html/editor.scss @@ -1,4 +1,4 @@ -.wp-block-html .editor-plain-text { +.wp-block-html .block-editor-plain-text { font-family: $editor-html-font; color: $dark-gray-800; padding: 0.8em 1em; diff --git a/packages/block-library/src/image/editor.scss b/packages/block-library/src/image/editor.scss index 1c4d985707c3b..86090e85eccbe 100644 --- a/packages/block-library/src/image/editor.scss +++ b/packages/block-library/src/image/editor.scss @@ -35,7 +35,7 @@ z-index: z-index(".block-library-image__resize-handlers"); } -.editor-block-list__block[data-type="core/image"][data-align="center"] { +.block-editor-block-list__block[data-type="core/image"][data-align="center"] { .wp-block-image { margin-left: auto; margin-right: auto; @@ -74,7 +74,7 @@ } } -.editor-block-list__block[data-type="core/image"] .editor-block-toolbar .editor-url-input__button-modal { +.block-editor-block-list__block[data-type="core/image"] .block-editor-block-toolbar .block-editor-url-input__button-modal { position: absolute; left: 0; right: 0; @@ -92,14 +92,14 @@ [data-type="core/image"][data-align="center"], [data-type="core/image"][data-align="left"], [data-type="core/image"][data-align="right"] { - .editor-block-list__block-edit { + .block-editor-block-list__block-edit { figure { margin: 0; display: table; } // This maps to the figcaption on the frontend. - .editor-rich-text { + .block-editor-rich-text { display: table-caption; caption-side: bottom; } @@ -114,12 +114,12 @@ } // This is similar to above but for resized unfloated images only, where the markup is different. -[data-type="core/image"] .editor-block-list__block-edit figure.is-resized { +[data-type="core/image"] .block-editor-block-list__block-edit figure.is-resized { margin: 0; display: table; // This maps to the figcaption on the frontend. - .editor-rich-text { + .block-editor-rich-text { display: table-caption; caption-side: bottom; } diff --git a/packages/block-library/src/media-text/editor.scss b/packages/block-library/src/media-text/editor.scss index bff309d348306..84f26a696f6d7 100644 --- a/packages/block-library/src/media-text/editor.scss +++ b/packages/block-library/src/media-text/editor.scss @@ -22,14 +22,14 @@ width: 100% !important; } -.wp-block-media-text .editor-inner-blocks { +.wp-block-media-text .block-editor-inner-blocks { word-break: break-word; grid-area: media-text-content; text-align: initial; padding: 0 8% 0 8%; } -.wp-block-media-text > .editor-inner-blocks > .editor-block-list__layout > .editor-block-list__block { +.wp-block-media-text > .block-editor-inner-blocks > .block-editor-block-list__layout > .block-editor-block-list__block { max-width: unset; } diff --git a/packages/block-library/src/more/editor.scss b/packages/block-library/src/more/editor.scss index 003c142f252bc..b4706db686679 100644 --- a/packages/block-library/src/more/editor.scss +++ b/packages/block-library/src/more/editor.scss @@ -1,4 +1,4 @@ -.editor-block-list__block[data-type="core/more"] { +.block-editor-block-list__block[data-type="core/more"] { max-width: 100%; text-align: center; } diff --git a/packages/block-library/src/nextpage/editor.scss b/packages/block-library/src/nextpage/editor.scss index e10f3480c3a6f..3b5dd437e6f86 100644 --- a/packages/block-library/src/nextpage/editor.scss +++ b/packages/block-library/src/nextpage/editor.scss @@ -1,4 +1,4 @@ -.editor-visual-editor__block[data-type="core/nextpage"] { +.block-editor-block-list__block[data-type="core/nextpage"] { max-width: 100%; } diff --git a/packages/block-library/src/paragraph/editor.scss b/packages/block-library/src/paragraph/editor.scss index 6cec65a0b8227..8efa8e47f73e0 100644 --- a/packages/block-library/src/paragraph/editor.scss +++ b/packages/block-library/src/paragraph/editor.scss @@ -1,7 +1,7 @@ // Specific to the empty paragraph placeholder: // when shown on mobile and in nested contexts, one or more icons show up on the right. // This padding makes sure it doesn't overlap text. -.editor-rich-text__editable[data-is-placeholder-visible="true"] + .editor-rich-text__editable.wp-block-paragraph { +.block-editor-rich-text__editable[data-is-placeholder-visible="true"] + .block-editor-rich-text__editable.wp-block-paragraph { padding-right: $icon-button-size * 3; // In nested contexts only one icon shows up. diff --git a/packages/block-library/src/pullquote/editor.scss b/packages/block-library/src/pullquote/editor.scss index 6a2eae7f0c6f7..3cb64db5b49ec 100644 --- a/packages/block-library/src/pullquote/editor.scss +++ b/packages/block-library/src/pullquote/editor.scss @@ -1,14 +1,14 @@ -.editor-block-list__block[data-type="core/pullquote"] { +.block-editor-block-list__block[data-type="core/pullquote"] { &[data-align="left"], &[data-align="right"] { - & .editor-rich-text p { + & .block-editor-rich-text p { font-size: 20px; } } } .wp-block-pullquote { - & blockquote > .editor-rich-text p { + & blockquote > .block-editor-rich-text p { font-size: 28px; line-height: 1.6; } @@ -18,7 +18,7 @@ margin-left: 0; margin-right: 0; - & blockquote > .editor-rich-text p { + & blockquote > .block-editor-rich-text p { font-size: 32px; } diff --git a/packages/block-library/src/shortcode/editor.scss b/packages/block-library/src/shortcode/editor.scss index bd3d418f18546..d623927605d40 100644 --- a/packages/block-library/src/shortcode/editor.scss +++ b/packages/block-library/src/shortcode/editor.scss @@ -15,7 +15,7 @@ flex-shrink: 0; } - .editor-plain-text { + .block-editor-plain-text { flex-grow: 1; } diff --git a/packages/block-library/src/table/editor.scss b/packages/block-library/src/table/editor.scss index c6fec7750d23b..c294b5d051666 100644 --- a/packages/block-library/src/table/editor.scss +++ b/packages/block-library/src/table/editor.scss @@ -1,4 +1,4 @@ -.editor-block-list__block[data-type="core/table"] { +.block-editor-block-list__block[data-type="core/table"] { &[data-align="left"], &[data-align="right"], &[data-align="center"] { diff --git a/packages/block-library/src/text-columns/editor.scss b/packages/block-library/src/text-columns/editor.scss index b1a6589806fce..94eee7497b8f8 100644 --- a/packages/block-library/src/text-columns/editor.scss +++ b/packages/block-library/src/text-columns/editor.scss @@ -1,5 +1,5 @@ .wp-block-text-columns { - .editor-rich-text__editable:focus { + .block-editor-rich-text__editable:focus { outline: $border-width solid $light-gray-500; } } diff --git a/packages/block-library/src/video/editor.scss b/packages/block-library/src/video/editor.scss index e75b149e5077c..1dc81eb54e029 100644 --- a/packages/block-library/src/video/editor.scss +++ b/packages/block-library/src/video/editor.scss @@ -1,4 +1,4 @@ -.editor-block-list__block[data-align="center"] { +.block-editor-block-list__block[data-align="center"] { text-align: center; } diff --git a/packages/components/src/placeholder/style.scss b/packages/components/src/placeholder/style.scss index c9779153c0415..9bdc0e93fa26b 100644 --- a/packages/components/src/placeholder/style.scss +++ b/packages/components/src/placeholder/style.scss @@ -27,7 +27,7 @@ margin-bottom: 1em; .dashicon, - .editor-block-icon { + .block-editor-block-icon { fill: currentColor; margin-right: 1ch; } diff --git a/packages/components/src/positioned-at-selection/index.js b/packages/components/src/positioned-at-selection/index.js index 050050ed1fe93..37c039d77777d 100644 --- a/packages/components/src/positioned-at-selection/index.js +++ b/packages/components/src/positioned-at-selection/index.js @@ -57,7 +57,7 @@ export default class PositionedAtSelection extends Component { const { style } = this.state; return ( - <div className="editor-format-toolbar__selection-position" style={ style }> + <div className="editor-format-toolbar__selection-position block-editor-format-toolbar__selection-position" style={ style }> { children } </div> ); diff --git a/packages/e2e-test-utils/src/click-block-appender.js b/packages/e2e-test-utils/src/click-block-appender.js index 8bade1f9bae33..15dd6e824f318 100644 --- a/packages/e2e-test-utils/src/click-block-appender.js +++ b/packages/e2e-test-utils/src/click-block-appender.js @@ -2,5 +2,5 @@ * Clicks the default block appender. */ export async function clickBlockAppender() { - await page.click( '.editor-default-block-appender__content' ); + await page.click( '.block-editor-default-block-appender__content' ); } diff --git a/packages/e2e-test-utils/src/click-block-toolbar-button.js b/packages/e2e-test-utils/src/click-block-toolbar-button.js index 511b300866673..679727189f741 100644 --- a/packages/e2e-test-utils/src/click-block-toolbar-button.js +++ b/packages/e2e-test-utils/src/click-block-toolbar-button.js @@ -4,7 +4,7 @@ * @param {string} buttonAriaLabel The aria label of the button to click. */ export async function clickBlockToolbarButton( buttonAriaLabel ) { - const BLOCK_TOOLBAR_SELECTOR = '.editor-block-toolbar'; + const BLOCK_TOOLBAR_SELECTOR = '.block-editor-block-toolbar'; const BUTTON_SELECTOR = `${ BLOCK_TOOLBAR_SELECTOR } button[aria-label="${ buttonAriaLabel }"]`; if ( await page.$( BLOCK_TOOLBAR_SELECTOR ) === null ) { // Press escape to show the block toolbar diff --git a/packages/e2e-test-utils/src/get-all-block-inserter-item-titles.js b/packages/e2e-test-utils/src/get-all-block-inserter-item-titles.js index 859adc89bac15..ef564d81b3866 100644 --- a/packages/e2e-test-utils/src/get-all-block-inserter-item-titles.js +++ b/packages/e2e-test-utils/src/get-all-block-inserter-item-titles.js @@ -12,7 +12,7 @@ export async function getAllBlockInserterItemTitles() { const inserterItemTitles = await page.evaluate( () => { return Array.from( document.querySelectorAll( - '.editor-inserter__results .editor-block-types-list__item-title' + '.block-editor-inserter__results .block-editor-block-types-list__item-title' ) ).map( ( inserterItem ) => { diff --git a/packages/e2e-test-utils/src/get-available-block-transforms.js b/packages/e2e-test-utils/src/get-available-block-transforms.js index 18810f4183121..36e33f3846f54 100644 --- a/packages/e2e-test-utils/src/get-available-block-transforms.js +++ b/packages/e2e-test-utils/src/get-available-block-transforms.js @@ -13,7 +13,7 @@ export const getAvailableBlockTransforms = async () => { if ( ! await hasBlockSwitcher() ) { return []; } - await page.click( '.editor-block-toolbar .editor-block-switcher' ); + await page.click( '.block-editor-block-toolbar .block-editor-block-switcher' ); return page.evaluate( ( buttonSelector ) => { return Array.from( document.querySelectorAll( @@ -24,5 +24,5 @@ export const getAvailableBlockTransforms = async () => { return button.getAttribute( 'aria-label' ); } ); - }, '.editor-block-types-list .editor-block-types-list__list-item button' ); + }, '.block-editor-block-types-list .block-editor-block-types-list__list-item button' ); }; diff --git a/packages/e2e-test-utils/src/has-block-switcher.js b/packages/e2e-test-utils/src/has-block-switcher.js index 8fa0acd241152..f8d0fd1d81782 100644 --- a/packages/e2e-test-utils/src/has-block-switcher.js +++ b/packages/e2e-test-utils/src/has-block-switcher.js @@ -6,5 +6,5 @@ export const hasBlockSwitcher = async () => { return page.evaluate( ( blockSwitcherSelector ) => { return !! document.querySelector( blockSwitcherSelector ); - }, '.editor-block-toolbar .editor-block-switcher' ); + }, '.block-editor-block-toolbar .block-editor-block-switcher' ); }; diff --git a/packages/e2e-test-utils/src/open-all-block-inserter-categories.js b/packages/e2e-test-utils/src/open-all-block-inserter-categories.js index d0b70387c9275..3960cbe646c6a 100644 --- a/packages/e2e-test-utils/src/open-all-block-inserter-categories.js +++ b/packages/e2e-test-utils/src/open-all-block-inserter-categories.js @@ -3,7 +3,7 @@ */ export async function openAllBlockInserterCategories() { const notOpenCategoryPanels = await page.$$( - '.editor-inserter__results .components-panel__body:not(.is-opened)' + '.block-editor-inserter__results .components-panel__body:not(.is-opened)' ); for ( const categoryPanel of notOpenCategoryPanels ) { await categoryPanel.click(); diff --git a/packages/e2e-test-utils/src/open-global-block-inserter.js b/packages/e2e-test-utils/src/open-global-block-inserter.js index 88fb28703c23d..5bbd83a0d8628 100644 --- a/packages/e2e-test-utils/src/open-global-block-inserter.js +++ b/packages/e2e-test-utils/src/open-global-block-inserter.js @@ -5,5 +5,5 @@ export async function openGlobalBlockInserter() { await page.click( '.edit-post-header [aria-label="Add block"]' ); // Waiting here is necessary because sometimes the inserter takes more time to // render than Puppeteer takes to complete the 'click' action - await page.waitForSelector( '.editor-inserter__menu' ); + await page.waitForSelector( '.block-editor-inserter__menu' ); } diff --git a/packages/e2e-test-utils/src/transform-block-to.js b/packages/e2e-test-utils/src/transform-block-to.js index 902d98d35c254..39f191331e379 100644 --- a/packages/e2e-test-utils/src/transform-block-to.js +++ b/packages/e2e-test-utils/src/transform-block-to.js @@ -6,7 +6,7 @@ export async function transformBlockTo( name ) { await page.mouse.move( 200, 300, { steps: 10 } ); await page.mouse.move( 250, 350, { steps: 10 } ); - await page.click( '.editor-block-switcher__toggle' ); - await page.waitForSelector( `.editor-block-types-list__item[aria-label="${ name }"]` ); - await page.click( `.editor-block-types-list__item[aria-label="${ name }"]` ); + await page.click( '.block-editor-block-switcher__toggle' ); + await page.waitForSelector( `.block-editor-block-types-list__item[aria-label="${ name }"]` ); + await page.click( `.block-editor-block-types-list__item[aria-label="${ name }"]` ); } diff --git a/packages/e2e-tests/specs/a11y.test.js b/packages/e2e-tests/specs/a11y.test.js index 1c87cea4af54f..6f6cc960f204a 100644 --- a/packages/e2e-tests/specs/a11y.test.js +++ b/packages/e2e-tests/specs/a11y.test.js @@ -23,7 +23,7 @@ describe( 'a11y', () => { await page.keyboard.press( 'Tab' ); const isFocusedToggle = await page.$eval( ':focus', ( focusedElement ) => { - return focusedElement.classList.contains( 'editor-inserter__toggle' ); + return focusedElement.classList.contains( 'block-editor-inserter__toggle' ); } ); expect( isFocusedToggle ).toBe( true ); diff --git a/packages/e2e-tests/specs/adding-blocks.test.js b/packages/e2e-tests/specs/adding-blocks.test.js index 01ad864bda188..7a790862e831c 100644 --- a/packages/e2e-tests/specs/adding-blocks.test.js +++ b/packages/e2e-tests/specs/adding-blocks.test.js @@ -29,7 +29,7 @@ describe( 'adding blocks', () => { it( 'Should insert content using the placeholder and the regular inserter', async () => { // Click below editor to focus last field (block appender) - await clickBelow( await page.$( '.editor-default-block-appender' ) ); + await clickBelow( await page.$( '.block-editor-default-block-appender' ) ); expect( await page.$( '[data-type="core/paragraph"]' ) ).not.toBeNull(); await page.keyboard.type( 'Paragraph block' ); @@ -81,16 +81,16 @@ describe( 'adding blocks', () => { await page.click( '.editor-post-title__input' ); // Using the between inserter - const insertionPoint = await page.$( '[data-type="core/quote"] .editor-inserter__toggle' ); + const insertionPoint = await page.$( '[data-type="core/quote"] .block-editor-inserter__toggle' ); const rect = await insertionPoint.boundingBox(); await page.mouse.move( rect.x + ( rect.width / 2 ), rect.y + ( rect.height / 2 ), { steps: 10 } ); - await page.waitForSelector( '[data-type="core/quote"] .editor-inserter__toggle' ); - await page.click( '[data-type="core/quote"] .editor-inserter__toggle' ); + await page.waitForSelector( '[data-type="core/quote"] .block-editor-inserter__toggle' ); + await page.click( '[data-type="core/quote"] .block-editor-inserter__toggle' ); // [TODO]: Search input should be focused immediately. It shouldn't be // necessary to have `waitForFunction`. await page.waitForFunction( () => ( document.activeElement && - document.activeElement.classList.contains( 'editor-inserter__search' ) + document.activeElement.classList.contains( 'block-editor-inserter__search' ) ) ); await page.keyboard.type( 'para' ); await pressKeyTimes( 'Tab', 3 ); @@ -109,38 +109,38 @@ describe( 'adding blocks', () => { it( 'should not allow transfer of focus outside of the block-insertion menu once open', async () => { // Enter the default block and click the inserter toggle button to the left of it. await page.keyboard.press( 'ArrowDown' ); - await page.click( '.editor-block-list__empty-block-inserter .editor-inserter__toggle' ); + await page.click( '.block-editor-block-list__empty-block-inserter .block-editor-inserter__toggle' ); // Expect the inserter search input to be the active element. let activeElementClassList = await page.evaluate( () => document.activeElement.classList ); - expect( Object.values( activeElementClassList ) ).toContain( 'editor-inserter__search' ); + expect( Object.values( activeElementClassList ) ).toContain( 'block-editor-inserter__search' ); // Try using the up arrow key (vertical navigation triggers the issue described in #9583). await page.keyboard.press( 'ArrowUp' ); // Expect the inserter search input to still be the active element. activeElementClassList = await page.evaluate( () => document.activeElement.classList ); - expect( Object.values( activeElementClassList ) ).toContain( 'editor-inserter__search' ); + expect( Object.values( activeElementClassList ) ).toContain( 'block-editor-inserter__search' ); // Tab to the block search results await page.keyboard.press( 'Tab' ); // Expect the search results to be the active element. activeElementClassList = await page.evaluate( () => document.activeElement.classList ); - expect( Object.values( activeElementClassList ) ).toContain( 'editor-inserter__results' ); + expect( Object.values( activeElementClassList ) ).toContain( 'block-editor-inserter__results' ); // Try using the up arrow key await page.keyboard.press( 'ArrowUp' ); // Expect the search results to still be the active element. activeElementClassList = await page.evaluate( () => document.activeElement.classList ); - expect( Object.values( activeElementClassList ) ).toContain( 'editor-inserter__results' ); + expect( Object.values( activeElementClassList ) ).toContain( 'block-editor-inserter__results' ); // Press escape to close the block inserter. await page.keyboard.press( 'Escape' ); // Expect focus to have transferred back to the inserter toggle button. activeElementClassList = await page.evaluate( () => document.activeElement.classList ); - expect( Object.values( activeElementClassList ) ).toContain( 'editor-inserter__toggle' ); + expect( Object.values( activeElementClassList ) ).toContain( 'block-editor-inserter__toggle' ); } ); } ); diff --git a/packages/e2e-tests/specs/block-deletion.test.js b/packages/e2e-tests/specs/block-deletion.test.js index b9013b3572f9e..11227af689d61 100644 --- a/packages/e2e-tests/specs/block-deletion.test.js +++ b/packages/e2e-tests/specs/block-deletion.test.js @@ -21,8 +21,8 @@ const addThreeParagraphsToNewPost = async () => { }; const clickOnBlockSettingsMenuItem = async ( buttonLabel ) => { - await expect( page ).toClick( '.editor-block-settings-menu__toggle' ); - const itemButton = ( await page.$x( `//*[contains(@class, "editor-block-settings-menu__popover")]//button[contains(text(), '${ buttonLabel }')]` ) )[ 0 ]; + await expect( page ).toClick( '.block-editor-block-settings-menu__toggle' ); + const itemButton = ( await page.$x( `//*[contains(@class, "block-editor-block-settings-menu__popover")]//button[contains(text(), '${ buttonLabel }')]` ) )[ 0 ]; await itemButton.click(); }; @@ -80,7 +80,7 @@ describe( 'block deletion -', () => { await page.click( '.editor-post-title' ); // Click on the third (image) block so that its wrapper is selected and backspace to delete it. - await page.click( '.editor-block-list__block:nth-child(3) .components-placeholder__label' ); + await page.click( '.block-editor-block-list__block:nth-child(3) .components-placeholder__label' ); await page.keyboard.press( 'Backspace' ); expect( await getEditedPostContent() ).toMatchSnapshot(); @@ -122,7 +122,7 @@ describe( 'deleting all blocks', () => { await clickOnBlockSettingsMenuItem( 'Remove Block' ); // There is a default block: - expect( await page.$$( '.editor-block-list__block' ) ).toHaveLength( 1 ); + expect( await page.$$( '.block-editor-block-list__block' ) ).toHaveLength( 1 ); // But the effective saved content is still empty: expect( await getEditedPostContent() ).toBe( '' ); diff --git a/packages/e2e-tests/specs/block-hierarchy-navigation.test.js b/packages/e2e-tests/specs/block-hierarchy-navigation.test.js index 28b5c88b9d2c2..200e5a234f06b 100644 --- a/packages/e2e-tests/specs/block-hierarchy-navigation.test.js +++ b/packages/e2e-tests/specs/block-hierarchy-navigation.test.js @@ -26,7 +26,7 @@ describe( 'Navigating the block hierarchy', () => { // Navigate to the columns blocks. await page.click( '[aria-label="Block Navigation"]' ); - const columnsBlockMenuItem = ( await page.$x( "//button[contains(@class,'editor-block-navigation__item') and contains(text(), 'Columns')]" ) )[ 0 ]; + const columnsBlockMenuItem = ( await page.$x( "//button[contains(@class,'block-editor-block-navigation__item') and contains(text(), 'Columns')]" ) )[ 0 ]; await columnsBlockMenuItem.click(); // Tweak the columns count. @@ -39,7 +39,7 @@ describe( 'Navigating the block hierarchy', () => { // Navigate to the last column block. await page.click( '[aria-label="Block Navigation"]' ); const lastColumnsBlockMenuItem = ( await page.$x( - "//button[contains(@class,'editor-block-navigation__item') and contains(text(), 'Column')]" + "//button[contains(@class,'block-editor-block-navigation__item') and contains(text(), 'Column')]" ) )[ 3 ]; await lastColumnsBlockMenuItem.click(); diff --git a/packages/e2e-tests/specs/block-mover.test.js b/packages/e2e-tests/specs/block-mover.test.js index b9011ee7d0101..09f6ba41a2a82 100644 --- a/packages/e2e-tests/specs/block-mover.test.js +++ b/packages/e2e-tests/specs/block-mover.test.js @@ -10,29 +10,29 @@ describe( 'block mover', () => { it( 'should show block mover when more than one block exists', async () => { // Create a two blocks on the page. - await page.click( '.editor-default-block-appender' ); + await page.click( '.block-editor-default-block-appender' ); await page.keyboard.type( 'First Paragraph' ); await page.keyboard.press( 'Enter' ); await page.keyboard.type( 'Second Paragraph' ); // Select a block so the block mover is rendered. - await page.focus( '.editor-block-list__block' ); + await page.focus( '.block-editor-block-list__block' ); - const blockMover = await page.$$( '.editor-block-mover' ); + const blockMover = await page.$$( '.block-editor-block-mover' ); // There should be a block mover. expect( blockMover ).toHaveLength( 1 ); } ); it( 'should hide block mover when only one block exists', async () => { // Create a single block on the page. - await page.click( '.editor-default-block-appender' ); + await page.click( '.block-editor-default-block-appender' ); await page.keyboard.type( 'First Paragraph' ); // Select a block so the block mover has the possibility of being rendered. - await page.focus( '.editor-block-list__block' ); + await page.focus( '.block-editor-block-list__block' ); // Ensure no block mover exists when only one block exists on the page. - const blockMover = await page.$$( '.editor-block-mover' ); + const blockMover = await page.$$( '.block-editor-block-mover' ); expect( blockMover ).toHaveLength( 0 ); } ); } ); diff --git a/packages/e2e-tests/specs/blocks/columns.test.js b/packages/e2e-tests/specs/blocks/columns.test.js index f8b97c62b91a8..fd061642478e7 100644 --- a/packages/e2e-tests/specs/blocks/columns.test.js +++ b/packages/e2e-tests/specs/blocks/columns.test.js @@ -17,7 +17,7 @@ describe( 'Columns', () => { it( 'restricts all blocks inside the columns block', async () => { await insertBlock( 'Columns' ); await page.click( '[aria-label="Block Navigation"]' ); - const columnBlockMenuItem = ( await page.$x( '//button[contains(concat(" ", @class, " "), " editor-block-navigation__item-button ")][text()="Column"]' ) )[ 0 ]; + const columnBlockMenuItem = ( await page.$x( '//button[contains(concat(" ", @class, " "), " block-editor-block-navigation__item-button ")][text()="Column"]' ) )[ 0 ]; await columnBlockMenuItem.click(); await openGlobalBlockInserter(); await openAllBlockInserterCategories(); diff --git a/packages/e2e-tests/specs/blocks/media-text.test.js b/packages/e2e-tests/specs/blocks/media-text.test.js index f9687b3af99cf..2e59b77670327 100644 --- a/packages/e2e-tests/specs/blocks/media-text.test.js +++ b/packages/e2e-tests/specs/blocks/media-text.test.js @@ -16,7 +16,7 @@ describe( 'Media Text', () => { it( 'restricts blocks that can be inserted', async () => { await insertBlock( 'Media & Text' ); - await page.click( '.wp-block-media-text .editor-rich-text' ); + await page.click( '.wp-block-media-text .block-editor-rich-text' ); await openGlobalBlockInserter(); await openAllBlockInserterCategories(); expect( await getAllBlockInserterItemTitles() ).toMatchSnapshot(); diff --git a/packages/e2e-tests/specs/convert-block-type.test.js b/packages/e2e-tests/specs/convert-block-type.test.js index b99c8541d632f..2c71a3d3a139f 100644 --- a/packages/e2e-tests/specs/convert-block-type.test.js +++ b/packages/e2e-tests/specs/convert-block-type.test.js @@ -18,7 +18,7 @@ describe( 'Code block', () => { await insertBlock( 'Code' ); - await page.type( '.editor-block-list__block textarea', code ); + await page.type( '.block-editor-block-list__block textarea', code ); // Verify the content starts out as a Code block. const originalPostContent = await getEditedPostContent(); diff --git a/packages/e2e-tests/specs/editor-modes.test.js b/packages/e2e-tests/specs/editor-modes.test.js index c8572f5526103..155b2c207013b 100644 --- a/packages/e2e-tests/specs/editor-modes.test.js +++ b/packages/e2e-tests/specs/editor-modes.test.js @@ -16,7 +16,7 @@ describe( 'Editing modes (visual/HTML)', () => { it( 'should switch between visual and HTML modes', async () => { // This block should be in "visual" mode by default. - let visualBlock = await page.$$( '.editor-block-list__layout .editor-block-list__block .editor-rich-text' ); + let visualBlock = await page.$$( '.block-editor-block-list__layout .block-editor-block-list__block .block-editor-rich-text' ); expect( visualBlock ).toHaveLength( 1 ); // Move the mouse to show the block toolbar @@ -29,7 +29,7 @@ describe( 'Editing modes (visual/HTML)', () => { await changeModeButton.click(); // Wait for the block to be converted to HTML editing mode. - const htmlBlock = await page.$$( '.editor-block-list__layout .editor-block-list__block .editor-block-list__block-html-textarea' ); + const htmlBlock = await page.$$( '.block-editor-block-list__layout .block-editor-block-list__block .block-editor-block-list__block-html-textarea' ); expect( htmlBlock ).toHaveLength( 1 ); // Move the mouse to show the block toolbar @@ -42,7 +42,7 @@ describe( 'Editing modes (visual/HTML)', () => { await changeModeButton.click(); // This block should be in "visual" mode by default. - visualBlock = await page.$$( '.editor-block-list__layout .editor-block-list__block .editor-rich-text' ); + visualBlock = await page.$$( '.block-editor-block-list__layout .block-editor-block-list__block .block-editor-rich-text' ); expect( visualBlock ).toHaveLength( 1 ); } ); @@ -73,7 +73,7 @@ describe( 'Editing modes (visual/HTML)', () => { await changeModeButton.click(); // Make sure the paragraph content is rendered as expected. - let htmlBlockContent = await page.$eval( '.editor-block-list__layout .editor-block-list__block .editor-block-list__block-html-textarea', ( node ) => node.textContent ); + let htmlBlockContent = await page.$eval( '.block-editor-block-list__layout .block-editor-block-list__block .block-editor-block-list__block-html-textarea', ( node ) => node.textContent ); expect( htmlBlockContent ).toEqual( '<p>Hello world!</p>' ); // Change the font size using the sidebar. @@ -82,14 +82,14 @@ describe( 'Editing modes (visual/HTML)', () => { await changeSizeButton.click(); // Make sure the HTML content updated. - htmlBlockContent = await page.$eval( '.editor-block-list__layout .editor-block-list__block .editor-block-list__block-html-textarea', ( node ) => node.textContent ); + htmlBlockContent = await page.$eval( '.block-editor-block-list__layout .block-editor-block-list__block .block-editor-block-list__block-html-textarea', ( node ) => node.textContent ); expect( htmlBlockContent ).toEqual( '<p class="has-large-font-size">Hello world!</p>' ); } ); it( 'the code editor should unselect blocks and disable the inserter', async () => { // The paragraph block should be selected const title = await page.$eval( - '.editor-block-inspector__card-title', + '.block-editor-block-inspector__card-title', ( element ) => element.innerText ); expect( title ).toBe( 'Paragraph' ); @@ -107,11 +107,11 @@ describe( 'Editing modes (visual/HTML)', () => { // No block is selected await page.click( '.edit-post-sidebar__panel-tab[data-label="Block"]' ); - const noBlocksElement = await page.$( '.editor-block-inspector__no-blocks' ); + const noBlocksElement = await page.$( '.block-editor-block-inspector__no-blocks' ); expect( noBlocksElement ).not.toBeNull(); // The inserter is disabled - const disabledInserter = await page.$( '.editor-inserter > button:disabled' ); + const disabledInserter = await page.$( '.block-editor-inserter > button:disabled' ); expect( disabledInserter ).not.toBeNull(); } ); } ); diff --git a/packages/e2e-tests/specs/invalid-block.test.js b/packages/e2e-tests/specs/invalid-block.test.js index 007912aafe317..d0d8dda5a805d 100644 --- a/packages/e2e-tests/specs/invalid-block.test.js +++ b/packages/e2e-tests/specs/invalid-block.test.js @@ -24,7 +24,7 @@ describe( 'invalid blocks', () => { await changeModeButton.click(); // Focus on the textarea and enter an invalid paragraph - await page.click( '.editor-block-list__layout .editor-block-list__block .editor-block-list__block-html-textarea' ); + await page.click( '.block-editor-block-list__layout .block-editor-block-list__block .block-editor-block-list__block-html-textarea' ); await page.keyboard.type( '<p>invalid paragraph' ); // Takes the focus away from the block so the invalid warning is triggered @@ -33,10 +33,10 @@ describe( 'invalid blocks', () => { expect( console ).toHaveWarned(); // Click on the 'resolve' button - await page.click( '.editor-warning__actions button' ); + await page.click( '.block-editor-warning__actions button' ); // Check we get the resolve modal with the appropriate contents - const htmlBlockContent = await page.$eval( '.editor-block-compare__html', ( node ) => node.textContent ); + const htmlBlockContent = await page.$eval( '.block-editor-block-compare__html', ( node ) => node.textContent ); expect( htmlBlockContent ).toEqual( '<p>hello</p><p>invalid paragraph' ); } ); } ); diff --git a/packages/e2e-tests/specs/keyboard-navigable-blocks.test.js b/packages/e2e-tests/specs/keyboard-navigable-blocks.test.js index e9db2951cc5f6..0945d3525f896 100644 --- a/packages/e2e-tests/specs/keyboard-navigable-blocks.test.js +++ b/packages/e2e-tests/specs/keyboard-navigable-blocks.test.js @@ -29,7 +29,7 @@ const tabThroughParagraphBlock = async ( paragraphText ) => { // Tab causes 'add block' button to receive focus await page.keyboard.press( 'Tab' ); const isFocusedParagraphInserterToggle = await page.evaluate( () => - document.activeElement.classList.contains( 'editor-inserter__toggle' ) + document.activeElement.classList.contains( 'block-editor-inserter__toggle' ) ); await expect( isFocusedParagraphInserterToggle ).toBe( true ); @@ -54,14 +54,14 @@ const tabThroughBlockMoverControl = async () => { // Tab to focus on the 'move up' control await page.keyboard.press( 'Tab' ); const isFocusedMoveUpControl = await page.evaluate( () => - document.activeElement.classList.contains( 'editor-block-mover__control' ) + document.activeElement.classList.contains( 'block-editor-block-mover__control' ) ); await expect( isFocusedMoveUpControl ).toBe( true ); // Tab to focus on the 'move down' control await page.keyboard.press( 'Tab' ); const isFocusedMoveDownControl = await page.evaluate( () => - document.activeElement.classList.contains( 'editor-block-mover__control' ) + document.activeElement.classList.contains( 'block-editor-block-mover__control' ) ); await expect( isFocusedMoveDownControl ).toBe( true ); }; @@ -71,7 +71,7 @@ const tabThroughBlockToolbar = async () => { await page.keyboard.press( 'Tab' ); const isFocusedBlockSwitcherControl = await page.evaluate( () => document.activeElement.classList.contains( - 'editor-block-switcher__toggle' + 'block-editor-block-switcher__toggle' ) ); await expect( isFocusedBlockSwitcherControl ).toBe( true ); @@ -129,7 +129,7 @@ const tabThroughBlockToolbar = async () => { await page.keyboard.press( 'Tab' ); const isFocusedBlockSettingsDropdown = await page.evaluate( () => document.activeElement.classList.contains( - 'editor-block-settings-menu__toggle' + 'block-editor-block-settings-menu__toggle' ) ); await expect( isFocusedBlockSettingsDropdown ).toBe( true ); diff --git a/packages/e2e-tests/specs/links.test.js b/packages/e2e-tests/specs/links.test.js index b4be3dbfc53a6..a41aa953bd8e7 100644 --- a/packages/e2e-tests/specs/links.test.js +++ b/packages/e2e-tests/specs/links.test.js @@ -22,7 +22,7 @@ describe( 'Links', () => { } ); const waitForAutoFocus = async () => { - await page.waitForFunction( () => !! document.activeElement.closest( '.editor-url-input' ) ); + await page.waitForFunction( () => !! document.activeElement.closest( '.block-editor-url-input' ) ); }; it( 'can be created by selecting text and clicking Link', async () => { @@ -205,12 +205,12 @@ describe( 'Links', () => { // Typing "left" should not close the dialog await page.keyboard.press( 'ArrowLeft' ); - let popover = await page.$( '.editor-url-popover' ); + let popover = await page.$( '.block-editor-url-popover' ); expect( popover ).not.toBeNull(); // Escape should close the dialog still. await page.keyboard.press( 'Escape' ); - popover = await page.$( '.editor-url-popover' ); + popover = await page.$( '.block-editor-url-popover' ); expect( popover ).toBeNull(); } ); @@ -224,12 +224,12 @@ describe( 'Links', () => { // Typing "left" should not close the dialog await page.keyboard.press( 'ArrowLeft' ); - let popover = await page.$( '.editor-url-popover' ); + let popover = await page.$( '.block-editor-url-popover' ); expect( popover ).not.toBeNull(); // Escape should close the dialog still. await page.keyboard.press( 'Escape' ); - popover = await page.$( '.editor-url-popover' ); + popover = await page.$( '.block-editor-url-popover' ); expect( popover ).toBeNull(); } ); @@ -284,8 +284,8 @@ describe( 'Links', () => { await waitForAutoFocus(); await page.keyboard.type( titleText ); - await page.waitForSelector( '.editor-url-input__suggestion' ); - const autocompleteSuggestions = await page.$x( `//*[contains(@class, "editor-url-input__suggestion")]//button[contains(text(), '${ titleText }')]` ); + await page.waitForSelector( '.block-editor-url-input__suggestion' ); + const autocompleteSuggestions = await page.$x( `//*[contains(@class, "block-editor-url-input__suggestion")]//button[contains(text(), '${ titleText }')]` ); // Expect there to be some autocomplete suggestions. expect( autocompleteSuggestions.length ).toBeGreaterThan( 0 ); @@ -294,16 +294,16 @@ describe( 'Links', () => { // Expect that clicking on the autocomplete suggestion doesn't dismiss the link popover. await firstSuggestion.click(); - expect( await page.$( '.editor-url-popover' ) ).not.toBeNull(); + expect( await page.$( '.block-editor-url-popover' ) ).not.toBeNull(); // Expect the url input value to have been updated with the post url. - const inputValue = await page.evaluate( () => document.querySelector( '.editor-url-input input[aria-label="URL"]' ).value ); + const inputValue = await page.evaluate( () => document.querySelector( '.block-editor-url-input input[aria-label="URL"]' ).value ); expect( inputValue ).toEqual( postURL ); // Expect the link to apply correctly. // Note - have avoided using snapshots here since the link url can't be determined ahead of time. await page.click( 'button[aria-label="Apply"]' ); - const linkHref = await page.evaluate( () => document.querySelector( '.editor-format-toolbar__link-container-value' ).href ); + const linkHref = await page.evaluate( () => document.querySelector( '.block-editor-format-toolbar__link-container-value' ).href ); expect( linkHref ).toEqual( postURL ); } ); @@ -326,21 +326,21 @@ describe( 'Links', () => { await waitForAutoFocus(); await page.keyboard.type( titleText ); - await page.waitForSelector( '.editor-url-input__suggestion' ); - const autocompleteSuggestions = await page.$x( `//*[contains(@class, "editor-url-input__suggestion")]//button[contains(text(), '${ titleText }')]` ); + await page.waitForSelector( '.block-editor-url-input__suggestion' ); + const autocompleteSuggestions = await page.$x( `//*[contains(@class, "block-editor-url-input__suggestion")]//button[contains(text(), '${ titleText }')]` ); // Expect there to be some autocomplete suggestions. expect( autocompleteSuggestions.length ).toBeGreaterThan( 0 ); // Expect the the first suggestion to be selected when pressing the down arrow. await page.keyboard.press( 'ArrowDown' ); - const isSelected = await page.evaluate( () => document.querySelector( '.editor-url-input__suggestion' ).getAttribute( 'aria-selected' ) ); + const isSelected = await page.evaluate( () => document.querySelector( '.block-editor-url-input__suggestion' ).getAttribute( 'aria-selected' ) ); expect( isSelected ).toBe( 'true' ); // Expect the link to apply correctly when pressing Enter. // Note - have avoided using snapshots here since the link url can't be determined ahead of time. await page.keyboard.press( 'Enter' ); - const linkHref = await page.evaluate( () => document.querySelector( '.editor-format-toolbar__link-container-value' ).href ); + const linkHref = await page.evaluate( () => document.querySelector( '.block-editor-format-toolbar__link-container-value' ).href ); expect( linkHref ).toEqual( postURL ); } ); @@ -360,34 +360,34 @@ describe( 'Links', () => { // Wait for the URL field to auto-focus await waitForAutoFocus(); - expect( await page.$( '.editor-url-popover' ) ).not.toBeNull(); + expect( await page.$( '.block-editor-url-popover' ) ).not.toBeNull(); // Trigger the autocomplete suggestion list and select the first suggestion. await page.keyboard.type( titleText ); - await page.waitForSelector( '.editor-url-input__suggestion' ); + await page.waitForSelector( '.block-editor-url-input__suggestion' ); await page.keyboard.press( 'ArrowDown' ); // Expect the the escape key to dismiss the popover when the autocomplete suggestion list is open. await page.keyboard.press( 'Escape' ); - expect( await page.$( '.editor-url-popover' ) ).toBeNull(); + expect( await page.$( '.block-editor-url-popover' ) ).toBeNull(); // Press Cmd+K to insert a link await pressKeyWithModifier( 'primary', 'K' ); // Wait for the URL field to auto-focus await waitForAutoFocus(); - expect( await page.$( '.editor-url-popover' ) ).not.toBeNull(); + expect( await page.$( '.block-editor-url-popover' ) ).not.toBeNull(); // Expect the the escape key to dismiss the popover normally. await page.keyboard.press( 'Escape' ); - expect( await page.$( '.editor-url-popover' ) ).toBeNull(); + expect( await page.$( '.block-editor-url-popover' ) ).toBeNull(); // Press Cmd+K to insert a link await pressKeyWithModifier( 'primary', 'K' ); // Wait for the URL field to auto-focus await waitForAutoFocus(); - expect( await page.$( '.editor-url-popover' ) ).not.toBeNull(); + expect( await page.$( '.block-editor-url-popover' ) ).not.toBeNull(); // Tab to the settings icon button. await page.keyboard.press( 'Tab' ); @@ -395,7 +395,7 @@ describe( 'Links', () => { // Expect the the escape key to dismiss the popover normally. await page.keyboard.press( 'Escape' ); - expect( await page.$( '.editor-url-popover' ) ).toBeNull(); + expect( await page.$( '.block-editor-url-popover' ) ).toBeNull(); } ); it( 'can be modified using the keyboard once a link has been set', async () => { @@ -413,19 +413,19 @@ describe( 'Links', () => { // Deselect the link text by moving the caret to the end of the line // and the link popover should not be displayed. await page.keyboard.press( 'End' ); - expect( await page.$( '.editor-url-popover' ) ).toBeNull(); + expect( await page.$( '.block-editor-url-popover' ) ).toBeNull(); // Move the caret back into the link text and the link popover // should be displayed. await page.keyboard.press( 'ArrowLeft' ); - expect( await page.$( '.editor-url-popover' ) ).not.toBeNull(); + expect( await page.$( '.block-editor-url-popover' ) ).not.toBeNull(); // Press Cmd+K to edit the link and the url-input should become // focused with the value previously inserted. await pressKeyWithModifier( 'primary', 'K' ); await waitForAutoFocus(); const activeElementParentClasses = await page.evaluate( () => Object.values( document.activeElement.parentElement.classList ) ); - expect( activeElementParentClasses ).toContain( 'editor-url-input' ); + expect( activeElementParentClasses ).toContain( 'block-editor-url-input' ); const activeElementValue = await page.evaluate( () => document.activeElement.value ); expect( activeElementValue ).toBe( URL ); } ); @@ -451,7 +451,7 @@ describe( 'Links', () => { // Focus on first paragraph, so the link popover will appear over the subsequent ones await page.click( '[aria-label="Block Navigation"]' ); - await page.click( '.editor-block-navigation__item button' ); + await page.click( '.block-editor-block-navigation__item button' ); // Select some text await pressKeyWithModifier( 'shiftAlt', 'ArrowLeft' ); @@ -465,7 +465,7 @@ describe( 'Links', () => { await page.click( 'button[aria-label="Link Settings"]' ); // Move mouse over the 'open in new tab' section, then click and drag - const settings = await page.$( '.editor-url-popover__settings' ); + const settings = await page.$( '.block-editor-url-popover__settings' ); const bounds = await settings.boundingBox(); await page.mouse.move( bounds.x, bounds.y ); @@ -474,7 +474,7 @@ describe( 'Links', () => { await page.mouse.up(); // The link popover should still be visible - const popover = await page.$$( '.editor-url-popover' ); + const popover = await page.$$( '.block-editor-url-popover' ); expect( popover ).toHaveLength( 1 ); } ); diff --git a/packages/e2e-tests/specs/navigable-toolbar.test.js b/packages/e2e-tests/specs/navigable-toolbar.test.js index 1ff5f6cb2c953..e3fb884185008 100644 --- a/packages/e2e-tests/specs/navigable-toolbar.test.js +++ b/packages/e2e-tests/specs/navigable-toolbar.test.js @@ -30,7 +30,7 @@ describe( 'block toolbar', () => { ) ); const isInBlockToolbar = () => page.evaluate( () => ( - !! document.activeElement.closest( '.editor-block-toolbar' ) + !! document.activeElement.closest( '.block-editor-block-toolbar' ) ) ); describe( label, () => { diff --git a/packages/e2e-tests/specs/plugins/__snapshots__/plugins-api.test.js.snap b/packages/e2e-tests/specs/plugins/__snapshots__/plugins-api.test.js.snap index 0b62fc522bf7e..a3dda4545d67d 100644 --- a/packages/e2e-tests/specs/plugins/__snapshots__/plugins-api.test.js.snap +++ b/packages/e2e-tests/specs/plugins/__snapshots__/plugins-api.test.js.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Using Plugins API Sidebar Should open plugins sidebar using More Menu item and render content 1`] = `"<div class=\\"components-panel__header edit-post-sidebar-header__small\\"><span class=\\"edit-post-sidebar-header__title\\">(no title)</span><button type=\\"button\\" aria-label=\\"Close plugin\\" class=\\"components-button components-icon-button\\"><svg aria-hidden=\\"true\\" role=\\"img\\" focusable=\\"false\\" class=\\"dashicon dashicons-no-alt\\" xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"20\\" height=\\"20\\" viewBox=\\"0 0 20 20\\"><path d=\\"M14.95 6.46L11.41 10l3.54 3.54-1.41 1.41L10 11.42l-3.53 3.53-1.42-1.42L8.58 10 5.05 6.47l1.42-1.42L10 8.58l3.54-3.53z\\"></path></svg></button></div><div class=\\"components-panel__header edit-post-sidebar-header\\"><strong>Sidebar title plugin</strong><button type=\\"button\\" aria-label=\\"Unpin from toolbar\\" aria-expanded=\\"true\\" class=\\"components-button components-icon-button is-toggled\\"><svg aria-hidden=\\"true\\" role=\\"img\\" focusable=\\"false\\" class=\\"dashicon dashicons-star-filled\\" xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"20\\" height=\\"20\\" viewBox=\\"0 0 20 20\\"><path d=\\"M10 1l3 6 6 .75-4.12 4.62L16 19l-6-3-6 3 1.13-6.63L1 7.75 7 7z\\"></path></svg></button><button type=\\"button\\" aria-label=\\"Close plugin\\" class=\\"components-button components-icon-button\\"><svg aria-hidden=\\"true\\" role=\\"img\\" focusable=\\"false\\" class=\\"dashicon dashicons-no-alt\\" xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"20\\" height=\\"20\\" viewBox=\\"0 0 20 20\\"><path d=\\"M14.95 6.46L11.41 10l3.54 3.54-1.41 1.41L10 11.42l-3.53 3.53-1.42-1.42L8.58 10 5.05 6.47l1.42-1.42L10 8.58l3.54-3.53z\\"></path></svg></button></div><div class=\\"components-panel\\"><div class=\\"components-panel__body is-opened\\"><div class=\\"components-panel__row\\"><label for=\\"title-plain-text\\">Title:</label><textarea class=\\"editor-plain-text\\" id=\\"title-plain-text\\" placeholder=\\"(no title)\\" rows=\\"1\\" style=\\"overflow: hidden; overflow-wrap: break-word; resize: none; height: 18px;\\"></textarea></div><div class=\\"components-panel__row\\"><button type=\\"button\\" class=\\"components-button is-button is-primary\\">Reset</button></div></div></div>"`; +exports[`Using Plugins API Sidebar Should open plugins sidebar using More Menu item and render content 1`] = `"<div class=\\"components-panel__header edit-post-sidebar-header__small\\"><span class=\\"edit-post-sidebar-header__title\\">(no title)</span><button type=\\"button\\" aria-label=\\"Close plugin\\" class=\\"components-button components-icon-button\\"><svg aria-hidden=\\"true\\" role=\\"img\\" focusable=\\"false\\" class=\\"dashicon dashicons-no-alt\\" xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"20\\" height=\\"20\\" viewBox=\\"0 0 20 20\\"><path d=\\"M14.95 6.46L11.41 10l3.54 3.54-1.41 1.41L10 11.42l-3.53 3.53-1.42-1.42L8.58 10 5.05 6.47l1.42-1.42L10 8.58l3.54-3.53z\\"></path></svg></button></div><div class=\\"components-panel__header edit-post-sidebar-header\\"><strong>Sidebar title plugin</strong><button type=\\"button\\" aria-label=\\"Unpin from toolbar\\" aria-expanded=\\"true\\" class=\\"components-button components-icon-button is-toggled\\"><svg aria-hidden=\\"true\\" role=\\"img\\" focusable=\\"false\\" class=\\"dashicon dashicons-star-filled\\" xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"20\\" height=\\"20\\" viewBox=\\"0 0 20 20\\"><path d=\\"M10 1l3 6 6 .75-4.12 4.62L16 19l-6-3-6 3 1.13-6.63L1 7.75 7 7z\\"></path></svg></button><button type=\\"button\\" aria-label=\\"Close plugin\\" class=\\"components-button components-icon-button\\"><svg aria-hidden=\\"true\\" role=\\"img\\" focusable=\\"false\\" class=\\"dashicon dashicons-no-alt\\" xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"20\\" height=\\"20\\" viewBox=\\"0 0 20 20\\"><path d=\\"M14.95 6.46L11.41 10l3.54 3.54-1.41 1.41L10 11.42l-3.53 3.53-1.42-1.42L8.58 10 5.05 6.47l1.42-1.42L10 8.58l3.54-3.53z\\"></path></svg></button></div><div class=\\"components-panel\\"><div class=\\"components-panel__body is-opened\\"><div class=\\"components-panel__row\\"><label for=\\"title-plain-text\\">Title:</label><textarea class=\\"editor-plain-text block-editor-plain-text\\" id=\\"title-plain-text\\" placeholder=\\"(no title)\\" rows=\\"1\\" style=\\"overflow: hidden; overflow-wrap: break-word; resize: none; height: 18px;\\"></textarea></div><div class=\\"components-panel__row\\"><button type=\\"button\\" class=\\"components-button is-button is-primary\\">Reset</button></div></div></div>"`; diff --git a/packages/e2e-tests/specs/plugins/align-hook.test.js b/packages/e2e-tests/specs/plugins/align-hook.test.js index 1fbe802f84142..aacacbe657e46 100644 --- a/packages/e2e-tests/specs/plugins/align-hook.test.js +++ b/packages/e2e-tests/specs/plugins/align-hook.test.js @@ -29,7 +29,7 @@ describe( 'Align Hook Works As Expected', () => { const buttonLabels = await page.evaluate( () => { return Array.from( document.querySelectorAll( - '.editor-block-toolbar button[aria-label^="Align"]' + '.block-editor-block-toolbar button[aria-label^="Align"]' ) ).map( ( button ) => { @@ -54,7 +54,7 @@ describe( 'Align Hook Works As Expected', () => { it( 'Does not apply any alignment by default', async () => { await insertBlock( blockName ); // verify no alignment button is in pressed state - const pressedButtons = await page.$$( '.editor-block-toolbar button[aria-label^="Align"][aria-pressed="true"]' ); + const pressedButtons = await page.$$( '.block-editor-block-toolbar button[aria-label^="Align"][aria-pressed="true"]' ); expect( pressedButtons ).toHaveLength( 0 ); } ); }; @@ -69,7 +69,7 @@ describe( 'Align Hook Works As Expected', () => { const createCorrectlyAppliesAndRemovesAlignmentTest = ( blockName, alignment ) => { it( 'Correctly applies the selected alignment and correctly removes the alignment', async () => { - const BUTTON_SELECTOR = `.editor-block-toolbar button[aria-label="Align ${ alignment }"]`; + const BUTTON_SELECTOR = `.block-editor-block-toolbar button[aria-label="Align ${ alignment }"]`; const BUTTON_PRESSED_SELECTOR = `${ BUTTON_SELECTOR }[aria-pressed="true"]`; // set the specified alignment. await insertBlock( blockName ); @@ -161,7 +161,7 @@ describe( 'Align Hook Works As Expected', () => { describe( 'Block with default align', () => { const BLOCK_NAME = 'Test Default Align'; - const PRESSED_BUTTON_SELECTOR = '.editor-block-toolbar button[aria-label="Align right"][aria-pressed="true"]'; + const PRESSED_BUTTON_SELECTOR = '.block-editor-block-toolbar button[aria-label="Align right"][aria-pressed="true"]'; createShowsTheExpectedButtonsTest( BLOCK_NAME, [ 'Align left', 'Align center', diff --git a/packages/e2e-tests/specs/plugins/annotations.test.js b/packages/e2e-tests/specs/plugins/annotations.test.js index 1fe3554ad2f5e..65474777d3db1 100644 --- a/packages/e2e-tests/specs/plugins/annotations.test.js +++ b/packages/e2e-tests/specs/plugins/annotations.test.js @@ -9,8 +9,8 @@ import { } from '@wordpress/e2e-test-utils'; const clickOnBlockSettingsMenuItem = async ( buttonLabel ) => { - await expect( page ).toClick( '.editor-block-settings-menu__toggle' ); - const itemButton = ( await page.$x( `//*[contains(@class, "editor-block-settings-menu__popover")]//button[contains(text(), '${ buttonLabel }')]` ) )[ 0 ]; + await expect( page ).toClick( '.block-editor-block-settings-menu__toggle' ); + const itemButton = ( await page.$x( `//*[contains(@class, "block-editor-block-settings-menu__popover")]//button[contains(text(), '${ buttonLabel }')]` ) )[ 0 ]; await itemButton.click(); }; @@ -105,7 +105,7 @@ describe( 'Using Plugins API', () => { await clickOnBlockSettingsMenuItem( 'Edit as HTML' ); - const htmlContent = await page.$$( '.editor-block-list__block-html-textarea' ); + const htmlContent = await page.$$( '.block-editor-block-list__block-html-textarea' ); const html = await page.evaluate( ( el ) => { return el.innerHTML; }, htmlContent[ 0 ] ); diff --git a/packages/e2e-tests/specs/plugins/block-icons.test.js b/packages/e2e-tests/specs/plugins/block-icons.test.js index 0e7a15f69580d..3035a6ddf1aea 100644 --- a/packages/e2e-tests/specs/plugins/block-icons.test.js +++ b/packages/e2e-tests/specs/plugins/block-icons.test.js @@ -10,10 +10,10 @@ import { searchForBlock, } from '@wordpress/e2e-test-utils'; -const INSERTER_BUTTON_SELECTOR = '.components-popover__content .editor-block-types-list__item'; -const INSERTER_ICON_WRAPPER_SELECTOR = `${ INSERTER_BUTTON_SELECTOR } .editor-block-types-list__item-icon`; -const INSERTER_ICON_SELECTOR = `${ INSERTER_BUTTON_SELECTOR } .editor-block-icon`; -const INSPECTOR_ICON_SELECTOR = '.edit-post-sidebar .editor-block-icon'; +const INSERTER_BUTTON_SELECTOR = '.components-popover__content .block-editor-block-types-list__item'; +const INSERTER_ICON_WRAPPER_SELECTOR = `${ INSERTER_BUTTON_SELECTOR } .block-editor-block-types-list__item-icon`; +const INSERTER_ICON_SELECTOR = `${ INSERTER_BUTTON_SELECTOR } .block-editor-block-icon`; +const INSPECTOR_ICON_SELECTOR = '.edit-post-sidebar .block-editor-block-icon'; async function getInnerHTML( selector ) { return await page.$eval( selector, ( element ) => element.innerHTML ); @@ -37,7 +37,7 @@ async function getFirstInserterIcon() { async function selectFirstBlock() { await pressKeyWithModifier( 'access', 'o' ); - const navButtons = await page.$$( '.editor-block-navigation__item-button' ); + const navButtons = await page.$$( '.block-editor-block-navigation__item-button' ); await navButtons[ 0 ].click(); } diff --git a/packages/e2e-tests/specs/plugins/container-blocks.test.js b/packages/e2e-tests/specs/plugins/container-blocks.test.js index 50c4d80854e74..c01eb6eeca9a4 100644 --- a/packages/e2e-tests/specs/plugins/container-blocks.test.js +++ b/packages/e2e-tests/specs/plugins/container-blocks.test.js @@ -79,10 +79,10 @@ describe( 'Container block without paragraph support', () => { await insertBlock( 'Container without paragraph' ); // Open the specific appender used when there's no paragraph support. - await page.click( '.editor-inner-blocks .block-list-appender .block-list-appender__toggle' ); + await page.click( '.block-editor-inner-blocks .block-list-appender .block-list-appender__toggle' ); // Insert an image block. - await page.click( '.editor-inserter__results button[aria-label="Image"]' ); + await page.click( '.block-editor-inserter__results button[aria-label="Image"]' ); // Check the inserted content. expect( await getEditedPostContent() ).toMatchSnapshot(); diff --git a/packages/e2e-tests/specs/plugins/inner-blocks-allowed-blocks.test.js b/packages/e2e-tests/specs/plugins/inner-blocks-allowed-blocks.test.js index 7334ccce4f1c8..a25f9a81af95c 100644 --- a/packages/e2e-tests/specs/plugins/inner-blocks-allowed-blocks.test.js +++ b/packages/e2e-tests/specs/plugins/inner-blocks-allowed-blocks.test.js @@ -12,7 +12,7 @@ import { } from '@wordpress/e2e-test-utils'; describe( 'Allowed Blocks Setting on InnerBlocks ', () => { - const paragraphSelector = '.editor-rich-text__editable.wp-block-paragraph'; + const paragraphSelector = '.block-editor-rich-text__editable.wp-block-paragraph'; beforeAll( async () => { await activatePlugin( 'gutenberg-test-innerblocks-allowed-blocks' ); } ); diff --git a/packages/e2e-tests/specs/reusable-blocks.test.js b/packages/e2e-tests/specs/reusable-blocks.test.js index 982762b63dfec..73d7b9117bf43 100644 --- a/packages/e2e-tests/specs/reusable-blocks.test.js +++ b/packages/e2e-tests/specs/reusable-blocks.test.js @@ -61,7 +61,7 @@ describe( 'Reusable Blocks', () => { await page.waitForXPath( '//button[text()="Edit"]' ); // Check that we have a reusable block on the page - const block = await page.$( '.editor-block-list__block[data-type="core/block"]' ); + const block = await page.$( '.block-editor-block-list__block[data-type="core/block"]' ); expect( block ).not.toBeNull(); // Check that its title is displayed @@ -95,7 +95,7 @@ describe( 'Reusable Blocks', () => { await page.waitForXPath( '//button[text()="Edit"]' ); // Check that we have a reusable block on the page - const block = await page.$( '.editor-block-list__block[data-type="core/block"]' ); + const block = await page.$( '.block-editor-block-list__block[data-type="core/block"]' ); expect( block ).not.toBeNull(); // Check that it is untitled @@ -132,7 +132,7 @@ describe( 'Reusable Blocks', () => { await page.waitForXPath( '//button[text()="Edit"]' ); // Check that we have a reusable block on the page - const block = await page.$( '.editor-block-list__block[data-type="core/block"]' ); + const block = await page.$( '.block-editor-block-list__block[data-type="core/block"]' ); expect( block ).not.toBeNull(); // Check that its title is displayed @@ -144,7 +144,7 @@ describe( 'Reusable Blocks', () => { // Check that its content is up to date const text = await page.$eval( - '.editor-block-list__block[data-type="core/block"] .editor-rich-text', + '.block-editor-block-list__block[data-type="core/block"] .block-editor-rich-text', ( element ) => element.innerText ); expect( text ).toMatch( 'Oh! Hello there!' ); @@ -162,12 +162,12 @@ describe( 'Reusable Blocks', () => { await convertButton.click(); // Check that we have a paragraph block on the page - const block = await page.$( '.editor-block-list__block[data-type="core/paragraph"]' ); + const block = await page.$( '.block-editor-block-list__block[data-type="core/paragraph"]' ); expect( block ).not.toBeNull(); // Check that its content is up to date const text = await page.$eval( - '.editor-block-list__block[data-type="core/paragraph"] .editor-rich-text', + '.block-editor-block-list__block[data-type="core/paragraph"] .block-editor-rich-text', ( element ) => element.innerText ); expect( text ).toMatch( 'Oh! Hello there!' ); @@ -195,7 +195,7 @@ describe( 'Reusable Blocks', () => { // Check that we couldn't find it const items = await page.$$( - '.editor-block-types-list__item[aria-label="Surprised greeting block"]' + '.block-editor-block-types-list__item[aria-label="Surprised greeting block"]' ); expect( items ).toHaveLength( 0 ); } ); @@ -240,7 +240,7 @@ describe( 'Reusable Blocks', () => { await page.waitForXPath( '//button[text()="Edit"]' ); // Check that we have a reusable block on the page - const block = await page.$( '.editor-block-list__block[data-type="core/block"]' ); + const block = await page.$( '.block-editor-block-list__block[data-type="core/block"]' ); expect( block ).not.toBeNull(); // Check that its title is displayed diff --git a/packages/e2e-tests/specs/splitting-merging.test.js b/packages/e2e-tests/specs/splitting-merging.test.js index c5ea8bf94ce77..8df6e104645ae 100644 --- a/packages/e2e-tests/specs/splitting-merging.test.js +++ b/packages/e2e-tests/specs/splitting-merging.test.js @@ -194,7 +194,7 @@ describe( 'splitting and merging blocks', () => { await page.keyboard.press( 'Backspace' ); // There is a default block: - expect( await page.$$( '.editor-block-list__block' ) ).toHaveLength( 1 ); + expect( await page.$$( '.block-editor-block-list__block' ) ).toHaveLength( 1 ); // But the effective saved content is still empty: expect( await getEditedPostContent() ).toBe( '' ); diff --git a/packages/e2e-tests/specs/style-variation.test.js b/packages/e2e-tests/specs/style-variation.test.js index 33ddcc3c2f809..db4c0d98fa5cb 100644 --- a/packages/e2e-tests/specs/style-variation.test.js +++ b/packages/e2e-tests/specs/style-variation.test.js @@ -20,7 +20,7 @@ describe( 'adding blocks', () => { await clickBlockToolbarButton( 'Change block type' ); - const styleVariations = await page.$$( '.editor-block-styles__item' ); + const styleVariations = await page.$$( '.block-editor-block-styles__item' ); await styleVariations[ 1 ].click(); // Check the content diff --git a/packages/edit-post/src/components/header/header-toolbar/style.scss b/packages/edit-post/src/components/header/header-toolbar/style.scss index 02410b24407a5..ceed4a0364ffd 100644 --- a/packages/edit-post/src/components/header/header-toolbar/style.scss +++ b/packages/edit-post/src/components/header/header-toolbar/style.scss @@ -12,7 +12,7 @@ } // Hide table of contents and block navigation on mobile. - .editor-block-navigation, + .block-editor-block-navigation, .table-of-contents { display: none; @@ -33,7 +33,7 @@ min-height: $block-toolbar-height; border-bottom: $border-width solid $light-gray-500; - .editor-block-toolbar .components-toolbar { + .block-editor-block-toolbar .components-toolbar { border-top: none; border-bottom: none; } @@ -63,11 +63,11 @@ right: auto; } - .editor-block-toolbar { + .block-editor-block-toolbar { margin: -($grid-size + $border-width) 0; } - .editor-block-toolbar .components-toolbar { + .block-editor-block-toolbar .components-toolbar { padding: ($grid-size + $border-width + $border-width) $grid-size-small ($grid-size + $border-width); } } diff --git a/packages/edit-post/src/components/sidebar/style.scss b/packages/edit-post/src/components/sidebar/style.scss index cfc42eb75f7b3..793088a039091 100644 --- a/packages/edit-post/src/components/sidebar/style.scss +++ b/packages/edit-post/src/components/sidebar/style.scss @@ -98,7 +98,7 @@ margin-top: -1em; } - .editor-skip-to-selected-block:focus { + .block-editor-skip-to-selected-block:focus { top: auto; right: 10px; bottom: 10px; diff --git a/packages/edit-post/src/components/visual-editor/block-inspector-button.js b/packages/edit-post/src/components/visual-editor/block-inspector-button.js index 09a28d76f7eda..283c3f99b86a7 100644 --- a/packages/edit-post/src/components/visual-editor/block-inspector-button.js +++ b/packages/edit-post/src/components/visual-editor/block-inspector-button.js @@ -36,7 +36,7 @@ export function BlockInspectorButton( { return ( <MenuItem - className="editor-block-settings-menu__control" + className="editor-block-settings-menu__control block-editor-block-settings-menu__control" onClick={ flow( areAdvancedSettingsOpened ? closeSidebar : openEditorSidebar, speakMessage, onClick ) } icon="admin-generic" label={ small ? label : undefined } diff --git a/packages/edit-post/src/components/visual-editor/style.scss b/packages/edit-post/src/components/visual-editor/style.scss index f796ed8e4c759..aa3ee7cbc7995 100644 --- a/packages/edit-post/src/components/visual-editor/style.scss +++ b/packages/edit-post/src/components/visual-editor/style.scss @@ -7,7 +7,7 @@ } } -.edit-post-visual-editor .editor-writing-flow__click-redirect { +.edit-post-visual-editor .block-editor-writing-flow__click-redirect { // Collapse to minimum height of 50px, to fully occupy editor bottom pad. height: 50px; width: 100%; @@ -16,21 +16,21 @@ } // The base width of blocks -.edit-post-visual-editor .editor-block-list__block { +.edit-post-visual-editor .block-editor-block-list__block { margin-left: auto; margin-right: auto; @include break-small() { // Compensate for side UI width. - .editor-block-list__block-edit { + .block-editor-block-list__block-edit { margin-left: -$block-side-ui-width; margin-right: -$block-side-ui-width; } // Center the block toolbar on wide and full-wide blocks. // Use specific selector to not affect nested block toolbars. - &[data-align="wide"] > .editor-block-list__block-edit > .editor-block-contextual-toolbar, - &[data-align="full"] > .editor-block-list__block-edit > .editor-block-contextual-toolbar { + &[data-align="wide"] > .block-editor-block-list__block-edit > .block-editor-block-contextual-toolbar, + &[data-align="full"] > .block-editor-block-list__block-edit > .block-editor-block-contextual-toolbar { width: calc(100% + #{ $block-padding * 2 + $border-width * 2 }); // Matches the negative margins applied to parent blocks. height: 0; // This collapses the container to an invisible element without margin. text-align: center; @@ -40,7 +40,7 @@ // Pairs with relative rule on line 49. float: left; - .editor-block-toolbar { + .block-editor-block-toolbar { max-width: $content-width; width: 100%; @@ -51,7 +51,7 @@ } // The centering math is simpler for a fullwide block, which doesn't have any block padding. - &[data-align="full"] > .editor-block-list__block-edit > .editor-block-contextual-toolbar { + &[data-align="full"] > .block-editor-block-list__block-edit > .editor-block-contextual-toolbar { width: 100%; margin-left: 0; margin-right: 0; @@ -91,18 +91,18 @@ .edit-post-visual-editor { // If the first block is floated, it needs top margin, unlike the rule in line 69. - .editor-block-list__layout > .editor-block-list__block[data-align="left"]:first-child, - .editor-block-list__layout > .editor-block-list__block[data-align="right"]:first-child { + .block-editor-block-list__layout > .block-editor-block-list__block[data-align="left"]:first-child, + .block-editor-block-list__layout > .block-editor-block-list__block[data-align="right"]:first-child { margin-top: $block-padding + $block-spacing + $border-width + $border-width + $block-padding; } - .editor-default-block-appender { + .block-editor-default-block-appender { // Default to centered and content-width, like blocks margin-left: auto; margin-right: auto; position: relative; - &[data-root-client-id=""] .editor-default-block-appender__content:hover { + &[data-root-client-id=""] .block-editor-default-block-appender__content:hover { // Outline on root-level default block appender is redundant with the // WritingFlow click redirector. outline: 1px solid transparent; @@ -110,8 +110,8 @@ } // Ensure that the height of the first appender, and the one between blocks, is the same as text. - .editor-block-list__block[data-type="core/paragraph"] p[data-is-placeholder-visible="true"] + p, - .editor-default-block-appender__content { + .block-editor-block-list__block[data-type="core/paragraph"] p[data-is-placeholder-visible="true"] + p, + .block-editor-default-block-appender__content { min-height: $empty-paragraph-height / 2; line-height: $editor-line-height; } diff --git a/packages/edit-post/src/style.scss b/packages/edit-post/src/style.scss index bec0e8484c580..4459591143609 100644 --- a/packages/edit-post/src/style.scss +++ b/packages/edit-post/src/style.scss @@ -151,7 +151,7 @@ body.block-editor-page { .editor-post-permalink, .edit-post-sidebar, .editor-post-publish-panel, -.editor-block-list__block, +.block-editor-block-list__block, .components-popover, .components-modal__content { .input-control, // Upstream name is `.regular-text`. @@ -246,7 +246,7 @@ body.block-editor-page { // Placeholder colors .editor-post-title, -.editor-block-list__block { +.block-editor-block-list__block { input, textarea { // Use opacity to work in various editor styles. diff --git a/packages/editor/src/components/autocompleters/style.scss b/packages/editor/src/components/autocompleters/style.scss index bbb55722d3702..a173c1f0b5e36 100644 --- a/packages/editor/src/components/autocompleters/style.scss +++ b/packages/editor/src/components/autocompleters/style.scss @@ -1,12 +1,12 @@ -.editor-autocompleters__block { - .editor-block-icon { +.block-editor-autocompleters__block { + .block-editor-block-icon { margin-right: 8px; } } -.editor-autocompleters__user { - .editor-autocompleters__user-avatar { +.block-editor-autocompleters__user { + .block-editor-autocompleters__user-avatar { margin-right: 8px; flex-grow: 0; flex-shrink: 0; @@ -14,7 +14,7 @@ width: 24px; // avoid jarring resize by seting the size upfront height: 24px; } - .editor-autocompleters__user-name { + .block-editor-autocompleters__user-name { white-space: nowrap; text-overflow: ellipsis; overflow: hidden; @@ -22,7 +22,7 @@ flex-shrink: 0; flex-grow: 1; } - .editor-autocompleters__user-slug { + .block-editor-autocompleters__user-slug { margin-left: 8px; color: $dark-gray-100; white-space: nowrap; @@ -32,7 +32,7 @@ flex-grow: 0; flex-shrink: 0; } - &:hover .editor-autocompleters__user-slug { + &:hover .block-editor-autocompleters__user-slug { color: $blue-medium-300; } } diff --git a/packages/format-library/src/image/index.js b/packages/format-library/src/image/index.js index ce62a8b6c5a75..0433941fa4b25 100644 --- a/packages/format-library/src/image/index.js +++ b/packages/format-library/src/image/index.js @@ -121,7 +121,7 @@ export const image = { { // Disable reason: KeyPress must be suppressed so the block doesn't hide the toolbar /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ } <form - className="editor-format-toolbar__image-container-content" + className="editor-format-toolbar__image-container-content block-editor-format-toolbar__image-container-content" onKeyPress={ stopKeyPropagation } onKeyDown={ this.onKeyDown } onSubmit={ ( event ) => { @@ -144,7 +144,7 @@ export const image = { } } > <TextControl - className="editor-format-toolbar__image-container-value" + className="editor-format-toolbar__image-container-value block-editor-format-toolbar__image-container-value" type="number" label={ __( 'Width' ) } value={ this.state.width } diff --git a/packages/format-library/src/image/style.scss b/packages/format-library/src/image/style.scss index bfc4599920a21..93bfc96ae7a16 100644 --- a/packages/format-library/src/image/style.scss +++ b/packages/format-library/src/image/style.scss @@ -1,4 +1,4 @@ -.editor-format-toolbar__image-container-content { +.block-editor-format-toolbar__image-container-content { display: flex; .components-icon-button { @@ -7,7 +7,7 @@ } } -.editor-format-toolbar__image-container-value { +.block-editor-format-toolbar__image-container-value { margin: $grid-size - $border-width; flex-grow: 1; flex-shrink: 1; diff --git a/packages/format-library/src/link/inline.js b/packages/format-library/src/link/inline.js index 4f32382bebaa9..58b38bed2c64e 100644 --- a/packages/format-library/src/link/inline.js +++ b/packages/format-library/src/link/inline.js @@ -42,7 +42,7 @@ const LinkEditor = ( { value, onChangeInputValue, onKeyDown, submitLink, autocom // Disable reason: KeyPress must be suppressed so the block doesn't hide the toolbar /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ <form - className="editor-format-toolbar__link-container-content" + className="editor-format-toolbar__link-container-content block-editor-format-toolbar__link-container-content" onKeyPress={ stopKeyPropagation } onKeyDown={ onKeyDown } onSubmit={ submitLink } @@ -59,7 +59,7 @@ const LinkEditor = ( { value, onChangeInputValue, onKeyDown, submitLink, autocom const LinkViewerUrl = ( { url } ) => { const prependedURL = prependHTTP( url ); - const linkClassName = classnames( 'editor-format-toolbar__link-container-value', { + const linkClassName = classnames( 'editor-format-toolbar__link-container-value block-editor-format-toolbar__link-container-value', { 'has-invalid-link': ! isValidHref( prependedURL ), } ); @@ -82,7 +82,7 @@ const LinkViewer = ( { url, editLink } ) => { // Disable reason: KeyPress must be suppressed so the block doesn't hide the toolbar /* eslint-disable jsx-a11y/no-static-element-interactions */ <div - className="editor-format-toolbar__link-container-content" + className="editor-format-toolbar__link-container-content block-editor-format-toolbar__link-container-content" onKeyPress={ stopKeyPropagation } > <LinkViewerUrl url={ url } /> diff --git a/packages/format-library/src/link/style.scss b/packages/format-library/src/link/style.scss index 7db24c50338fa..71b417af9b5fb 100644 --- a/packages/format-library/src/link/style.scss +++ b/packages/format-library/src/link/style.scss @@ -1,8 +1,8 @@ -.editor-format-toolbar__link-container-content { +.block-editor-format-toolbar__link-container-content { display: flex; } -.editor-format-toolbar__link-container-value { +.block-editor-format-toolbar__link-container-value { margin: $grid-size - $border-width; flex-grow: 1; flex-shrink: 1; From 9a93e6a49fa86805e9e0a8bed6f102787e4d2e36 Mon Sep 17 00:00:00 2001 From: Andrea Fercia <a.fercia@gmail.com> Date: Thu, 14 Mar 2019 19:27:03 +0100 Subject: [PATCH 657/691] Use only one button for Set featured image and image preview. (#14415) --- .../components/post-featured-image/index.js | 67 +++++++------------ 1 file changed, 26 insertions(+), 41 deletions(-) diff --git a/packages/editor/src/components/post-featured-image/index.js b/packages/editor/src/components/post-featured-image/index.js index 05e74ae5a2ace..ca3df956c41a4 100644 --- a/packages/editor/src/components/post-featured-image/index.js +++ b/packages/editor/src/components/post-featured-image/index.js @@ -46,30 +46,32 @@ function PostFeaturedImage( { currentPostId, featuredImageId, onUpdateImage, onR return ( <PostFeaturedImageCheck> <div className="editor-post-featured-image"> - { !! featuredImageId && - <MediaUploadCheck fallback={ instructions }> - <MediaUpload - title={ postLabel.featured_image || DEFAULT_FEATURE_IMAGE_LABEL } - onSelect={ onUpdateImage } - allowedTypes={ ALLOWED_MEDIA_TYPES } - modalClass="editor-post-featured-image__media-modal" - render={ ( { open } ) => ( - <Button className="editor-post-featured-image__preview" onClick={ open } aria-label={ __( 'Edit or update the image' ) }> - { media && - <ResponsiveWrapper - naturalWidth={ mediaWidth } - naturalHeight={ mediaHeight } - > - <img src={ mediaSourceUrl } alt="" /> - </ResponsiveWrapper> - } - { ! media && <Spinner /> } - </Button> - ) } - value={ featuredImageId } - /> - </MediaUploadCheck> - } + <MediaUploadCheck fallback={ instructions }> + <MediaUpload + title={ postLabel.featured_image || DEFAULT_FEATURE_IMAGE_LABEL } + onSelect={ onUpdateImage } + allowedTypes={ ALLOWED_MEDIA_TYPES } + modalClass={ ! featuredImageId ? 'editor-post-featured-image__media-modal' : 'editor-post-featured-image__media-modal' } + render={ ( { open } ) => ( + <Button + className={ ! featuredImageId ? 'editor-post-featured-image__toggle' : 'editor-post-featured-image__preview' } + onClick={ open } + aria-label={ ! featuredImageId ? null : __( 'Edit or update the image' ) }> + { !! featuredImageId && media && + <ResponsiveWrapper + naturalWidth={ mediaWidth } + naturalHeight={ mediaHeight } + > + <img src={ mediaSourceUrl } alt="" /> + </ResponsiveWrapper> + } + { !! featuredImageId && ! media && <Spinner /> } + { ! featuredImageId && ( postLabel.set_featured_image || DEFAULT_SET_FEATURE_IMAGE_LABEL ) } + </Button> + ) } + value={ featuredImageId } + /> + </MediaUploadCheck> { !! featuredImageId && media && ! media.isLoading && <MediaUploadCheck> <MediaUpload @@ -85,23 +87,6 @@ function PostFeaturedImage( { currentPostId, featuredImageId, onUpdateImage, onR /> </MediaUploadCheck> } - { ! featuredImageId && - <div> - <MediaUploadCheck fallback={ instructions }> - <MediaUpload - title={ postLabel.featured_image || DEFAULT_FEATURE_IMAGE_LABEL } - onSelect={ onUpdateImage } - allowedTypes={ ALLOWED_MEDIA_TYPES } - modalClass="editor-post-featured-image__media-modal" - render={ ( { open } ) => ( - <Button className="editor-post-featured-image__toggle" onClick={ open }> - { postLabel.set_featured_image || DEFAULT_SET_FEATURE_IMAGE_LABEL } - </Button> - ) } - /> - </MediaUploadCheck> - </div> - } { !! featuredImageId && <MediaUploadCheck> <Button onClick={ onRemoveImage } isLink isDestructive> From 176fdaf62c71442faa8ef4a6433eb90a4e486029 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Fri, 15 Mar 2019 03:53:48 -0700 Subject: [PATCH 658/691] Docs: Add anchor link to section in scripts readme (#14439) * Add anchor link * Add anchor link to second reference --- packages/scripts/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/scripts/README.md b/packages/scripts/README.md index 3ed6065b67532..af2b3a125802b 100644 --- a/packages/scripts/README.md +++ b/packages/scripts/README.md @@ -38,7 +38,7 @@ _Example:_ ### `build` -Transforms your code according the configuration provided so it's ready for production and optimized for the best performance. It uses [webpack](https://webpack.js.org/) behind the scenes. It'll lookup for a webpack config in the top-level directory of your package and will use it if it finds one. If none is found, it'll use the default config bundled within `@wordpress/scripts` packages. Learn more in the "webpack config" section. +Transforms your code according the configuration provided so it's ready for production and optimized for the best performance. It uses [webpack](https://webpack.js.org/) behind the scenes. It'll lookup for a webpack config in the top-level directory of your package and will use it if it finds one. If none is found, it'll use the default config bundled within `@wordpress/scripts` packages. Learn more in the [webpack config](#webpack-config) section. _Example:_ @@ -149,7 +149,7 @@ This is how you execute the script with presented setup: ### `start` -Transforms your code according the configuration provided so it's ready for development. The script will automatically rebuild if you make changes to the code, and you will see the build errors in the console. It uses [webpack](https://webpack.js.org/) behind the scenes. It'll lookup for a webpack config in the top-level directory of your package and will use it if it finds one. If none is found, it'll use the default config bundled within `@wordpress/scripts` packages. Learn more in the "webpack config" section. +Transforms your code according the configuration provided so it's ready for development. The script will automatically rebuild if you make changes to the code, and you will see the build errors in the console. It uses [webpack](https://webpack.js.org/) behind the scenes. It'll lookup for a webpack config in the top-level directory of your package and will use it if it finds one. If none is found, it'll use the default config bundled within `@wordpress/scripts` packages. Learn more in the [webpack config](#webpack-config) section. _Example:_ @@ -170,7 +170,7 @@ This is how you execute the script with presented setup: Launches the End-To-End (E2E) test runner. It uses [Jest](https://jestjs.io/) behind the scenes and you are able to utilize all of its [CLI options](https://jestjs.io/docs/en/cli.html). You can also run `./node_modules/.bin/wp-scripts test:e2e --help` or `npm run test:e2e:help` (as presented below) to view all of the available options. Writing tests can be done using Puppeteer API: - + > [Puppeteer](https://pptr.dev/) is a Node library which provides a high-level API to control Chrome or Chromium over the [DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/). Puppeteer runs [headless](https://developers.google.com/web/updates/2017/04/headless-chrome) by default, but can be configured to run full (non-headless) Chrome or Chromium. _Example:_ @@ -196,7 +196,7 @@ We enforce that all tests run serially in the current process using [--runInBand ### `test-unit-js` -_Alias_: `test-unit-jest` +_Alias_: `test-unit-jest` Launches the unit test runner. It uses [Jest](https://jestjs.io/) behind the scenes and you are able to utilize all of its [CLI options](https://jestjs.io/docs/en/cli.html). You can also run `./node_modules/.bin/wp-scripts test-unit-js --help` or `npm run test:unit:help` (as presented below) to view all of the available options. By default, it uses the set of recommended options defined in [@wordpress/jest-preset-default](https://www.npmjs.com/package/@wordpress/jest-preset-default) npm package. You can override them with your own options as described in [Jest documentation](https://jestjs.io/docs/en/configuration). From d21e382f278d80872964dbbbf41eaf32c41150ea Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 15 Mar 2019 07:03:10 -0400 Subject: [PATCH 659/691] Edit Post: Add block management modal (#14224) --- .../developers/data/data-core-blocks.md | 15 +++ .../developers/data/data-core-edit-post.md | 18 +++ packages/blocks/src/store/selectors.js | 65 +++++++++- packages/blocks/src/store/test/selectors.js | 67 +++++++++- .../edit-post/src/components/layout/index.js | 2 + .../manage-blocks-modal/category.js | 103 +++++++++++++++ .../manage-blocks-modal/checklist.js | 37 ++++++ .../components/manage-blocks-modal/index.js | 53 ++++++++ .../components/manage-blocks-modal/manager.js | 88 +++++++++++++ .../manage-blocks-modal/show-all.js | 31 +++++ .../components/manage-blocks-modal/style.scss | 117 ++++++++++++++++++ .../src/components/options-modal/style.scss | 4 + packages/edit-post/src/editor.js | 58 +++++++-- packages/edit-post/src/plugins/index.js | 2 + .../plugins/manage-blocks-menu-item/index.js | 34 +++++ packages/edit-post/src/store/actions.js | 35 ++++++ packages/edit-post/src/store/defaults.js | 1 + packages/edit-post/src/store/reducer.js | 40 ++++-- packages/edit-post/src/store/test/reducer.js | 45 +++++-- packages/edit-post/src/style.scss | 40 +++++- packages/editor/src/store/defaults.js | 15 +-- 21 files changed, 830 insertions(+), 40 deletions(-) create mode 100644 packages/edit-post/src/components/manage-blocks-modal/category.js create mode 100644 packages/edit-post/src/components/manage-blocks-modal/checklist.js create mode 100644 packages/edit-post/src/components/manage-blocks-modal/index.js create mode 100644 packages/edit-post/src/components/manage-blocks-modal/manager.js create mode 100644 packages/edit-post/src/components/manage-blocks-modal/show-all.js create mode 100644 packages/edit-post/src/components/manage-blocks-modal/style.scss create mode 100644 packages/edit-post/src/plugins/manage-blocks-menu-item/index.js diff --git a/docs/designers-developers/developers/data/data-core-blocks.md b/docs/designers-developers/developers/data/data-core-blocks.md index abdb30b903773..c7a63e769f350 100644 --- a/docs/designers-developers/developers/data/data-core-blocks.md +++ b/docs/designers-developers/developers/data/data-core-blocks.md @@ -129,6 +129,21 @@ Returns true if the block defines support for a feature, or false otherwise. Whether block supports feature. +### isMatchingSearchTerm + +Returns true if the block type by the given name or object value matches a +search term, or false otherwise. + +*Parameters* + + * state: Blocks state. + * nameOrType: Block name or type object. + * searchTerm: Search term by which to filter. + +*Returns* + +Wheter block type matches search term. + ### hasChildBlocks Returns a boolean indicating if a block has child blocks or not. diff --git a/docs/designers-developers/developers/data/data-core-edit-post.md b/docs/designers-developers/developers/data/data-core-edit-post.md index c12cf62a83d63..f36cf5bc47068 100644 --- a/docs/designers-developers/developers/data/data-core-edit-post.md +++ b/docs/designers-developers/developers/data/data-core-edit-post.md @@ -339,6 +339,24 @@ Returns an action object used to toggle a plugin name flag. * pluginName: Plugin name. +### hideBlockTypes + +Returns an action object used in signalling that block types by the given +name(s) should be hidden. + +*Parameters* + + * blockNames: Names of block types to hide. + +### showBlockTypes + +Returns an action object used in signalling that block types by the given +name(s) should be shown. + +*Parameters* + + * blockNames: Names of block types to show. + ### setAvailableMetaBoxesPerLocation Returns an action object used in signaling diff --git a/packages/blocks/src/store/selectors.js b/packages/blocks/src/store/selectors.js index 9ae3373cbd5dc..97d323c65fb16 100644 --- a/packages/blocks/src/store/selectors.js +++ b/packages/blocks/src/store/selectors.js @@ -2,7 +2,22 @@ * External dependencies */ import createSelector from 'rememo'; -import { filter, get, includes, map, some } from 'lodash'; +import { filter, get, includes, map, some, flow, deburr } from 'lodash'; + +/** + * Given a block name or block type object, returns the corresponding + * normalized block type object. + * + * @param {Object} state Blocks state. + * @param {(string|Object)} nameOrType Block name or type object + * + * @return {Object} Block type object. + */ +const getNormalizedBlockType = ( state, nameOrType ) => ( + 'string' === typeof nameOrType ? + getBlockType( state, nameOrType ) : + nameOrType +); /** * Returns all the available block types. @@ -120,9 +135,7 @@ export const getChildBlockNames = createSelector( * @return {?*} Block support value */ export const getBlockSupport = ( state, nameOrType, feature, defaultSupports ) => { - const blockType = 'string' === typeof nameOrType ? - getBlockType( state, nameOrType ) : - nameOrType; + const blockType = getNormalizedBlockType( state, nameOrType ); return get( blockType, [ 'supports', @@ -145,6 +158,50 @@ export function hasBlockSupport( state, nameOrType, feature, defaultSupports ) { return !! getBlockSupport( state, nameOrType, feature, defaultSupports ); } +/** + * Returns true if the block type by the given name or object value matches a + * search term, or false otherwise. + * + * @param {Object} state Blocks state. + * @param {(string|Object)} nameOrType Block name or type object. + * @param {string} searchTerm Search term by which to filter. + * + * @return {Object[]} Wheter block type matches search term. + */ +export function isMatchingSearchTerm( state, nameOrType, searchTerm ) { + const blockType = getNormalizedBlockType( state, nameOrType ); + + const getNormalizedSearchTerm = flow( [ + // Disregard diacritics. + // Input: "média" + deburr, + + // Lowercase. + // Input: "MEDIA" + ( term ) => term.toLowerCase(), + + // Strip leading and trailing whitespace. + // Input: " media " + ( term ) => term.trim(), + ] ); + + const normalizedSearchTerm = getNormalizedSearchTerm( searchTerm ); + + const isSearchMatch = flow( [ + getNormalizedSearchTerm, + ( normalizedCandidate ) => includes( + normalizedCandidate, + normalizedSearchTerm + ), + ] ); + + return ( + isSearchMatch( blockType.title ) || + some( blockType.keywords, isSearchMatch ) || + isSearchMatch( blockType.category ) + ); +} + /** * Returns a boolean indicating if a block has child blocks or not. * diff --git a/packages/blocks/src/store/test/selectors.js b/packages/blocks/src/store/test/selectors.js index ea24fcd7d855b..2f37ed006042c 100644 --- a/packages/blocks/src/store/test/selectors.js +++ b/packages/blocks/src/store/test/selectors.js @@ -1,7 +1,10 @@ /** * Internal dependencies */ -import { getChildBlockNames } from '../selectors'; +import { + getChildBlockNames, + isMatchingSearchTerm, +} from '../selectors'; describe( 'selectors', () => { describe( 'getChildBlockNames', () => { @@ -134,4 +137,66 @@ describe( 'selectors', () => { expect( getChildBlockNames( state, 'parent2' ) ).toEqual( [ 'child2' ] ); } ); } ); + + describe( 'isMatchingSearchTerm', () => { + const name = 'core/paragraph'; + const blockType = { + title: 'Paragraph', + category: 'common', + keywords: [ 'text' ], + }; + + const state = { + blockTypes: { + [ name ]: blockType, + }, + }; + + describe.each( [ + [ 'name', name ], + [ 'block type', blockType ], + ] )( 'by %s', ( label, nameOrType ) => { + it( 'should return false if not match', () => { + const result = isMatchingSearchTerm( state, nameOrType, 'Quote' ); + + expect( result ).toBe( false ); + } ); + + it( 'should return true if match by title', () => { + const result = isMatchingSearchTerm( state, nameOrType, 'Paragraph' ); + + expect( result ).toBe( true ); + } ); + + it( 'should return true if match ignoring case', () => { + const result = isMatchingSearchTerm( state, nameOrType, 'PARAGRAPH' ); + + expect( result ).toBe( true ); + } ); + + it( 'should return true if match ignoring diacritics', () => { + const result = isMatchingSearchTerm( state, nameOrType, 'PÁRAGRAPH' ); + + expect( result ).toBe( true ); + } ); + + it( 'should return true if match ignoring whitespace', () => { + const result = isMatchingSearchTerm( state, nameOrType, ' PARAGRAPH ' ); + + expect( result ).toBe( true ); + } ); + + it( 'should return true if match using the keywords', () => { + const result = isMatchingSearchTerm( state, nameOrType, 'TEXT' ); + + expect( result ).toBe( true ); + } ); + + it( 'should return true if match using the categories', () => { + const result = isMatchingSearchTerm( state, nameOrType, 'COMMON' ); + + expect( result ).toBe( true ); + } ); + } ); + } ); } ); diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index 2274ff9ffa3ec..d033b993a00b2 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -30,6 +30,7 @@ import TextEditor from '../text-editor'; import VisualEditor from '../visual-editor'; import EditorModeKeyboardShortcuts from '../keyboard-shortcuts'; import KeyboardShortcutHelpModal from '../keyboard-shortcut-help-modal'; +import ManageBlocksModal from '../manage-blocks-modal'; import OptionsModal from '../options-modal'; import MetaBoxes from '../meta-boxes'; import SettingsSidebar from '../sidebar/settings-sidebar'; @@ -83,6 +84,7 @@ function Layout( { <PreserveScrollInReorder /> <EditorModeKeyboardShortcuts /> <KeyboardShortcutHelpModal /> + <ManageBlocksModal /> <OptionsModal /> { ( mode === 'text' || ! isRichEditingEnabled ) && <TextEditor /> } { isRichEditingEnabled && mode === 'visual' && <VisualEditor /> } diff --git a/packages/edit-post/src/components/manage-blocks-modal/category.js b/packages/edit-post/src/components/manage-blocks-modal/category.js new file mode 100644 index 0000000000000..00a2ff15ab793 --- /dev/null +++ b/packages/edit-post/src/components/manage-blocks-modal/category.js @@ -0,0 +1,103 @@ +/** + * External dependencies + */ +import { without, map } from 'lodash'; + +/** + * WordPress dependencies + */ +import { withSelect, withDispatch } from '@wordpress/data'; +import { compose, withInstanceId } from '@wordpress/compose'; +import { CheckboxControl } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import BlockTypesChecklist from './checklist'; + +function BlockManagerCategory( { + instanceId, + category, + blockTypes, + hiddenBlockTypes, + toggleVisible, + toggleAllVisible, +} ) { + if ( ! blockTypes.length ) { + return null; + } + + const checkedBlockNames = without( + map( blockTypes, 'name' ), + ...hiddenBlockTypes + ); + + const titleId = 'edit-post-manage-blocks-modal__category-title-' + instanceId; + + const isAllChecked = checkedBlockNames.length === blockTypes.length; + + let ariaChecked; + if ( isAllChecked ) { + ariaChecked = 'true'; + } else if ( checkedBlockNames.length > 0 ) { + ariaChecked = 'mixed'; + } else { + ariaChecked = 'false'; + } + + return ( + <div + role="group" + aria-labelledby={ titleId } + className="edit-post-manage-blocks-modal__category" + > + <CheckboxControl + checked={ isAllChecked } + onChange={ toggleAllVisible } + className="edit-post-manage-blocks-modal__category-title" + aria-checked={ ariaChecked } + label={ <span id={ titleId }>{ category.title }</span> } + /> + <BlockTypesChecklist + blockTypes={ blockTypes } + value={ checkedBlockNames } + onItemChange={ toggleVisible } + /> + </div> + ); +} + +export default compose( [ + withInstanceId, + withSelect( ( select ) => { + const { getPreference } = select( 'core/edit-post' ); + + return { + hiddenBlockTypes: getPreference( 'hiddenBlockTypes' ), + }; + } ), + withDispatch( ( dispatch, ownProps ) => { + const { + showBlockTypes, + hideBlockTypes, + } = dispatch( 'core/edit-post' ); + + return { + toggleVisible( blockName, nextIsChecked ) { + if ( nextIsChecked ) { + showBlockTypes( blockName ); + } else { + hideBlockTypes( blockName ); + } + }, + toggleAllVisible( nextIsChecked ) { + const blockNames = map( ownProps.blockTypes, 'name' ); + if ( nextIsChecked ) { + showBlockTypes( blockNames ); + } else { + hideBlockTypes( blockNames ); + } + }, + }; + } ), +] )( BlockManagerCategory ); diff --git a/packages/edit-post/src/components/manage-blocks-modal/checklist.js b/packages/edit-post/src/components/manage-blocks-modal/checklist.js new file mode 100644 index 0000000000000..b65042929d647 --- /dev/null +++ b/packages/edit-post/src/components/manage-blocks-modal/checklist.js @@ -0,0 +1,37 @@ +/** + * External dependencies + */ +import { partial } from 'lodash'; + +/** + * WordPress dependencies + */ +import { Fragment } from '@wordpress/element'; +import { BlockIcon } from '@wordpress/block-editor'; +import { CheckboxControl } from '@wordpress/components'; + +function BlockTypesChecklist( { blockTypes, value, onItemChange } ) { + return ( + <ul className="edit-post-manage-blocks-modal__checklist"> + { blockTypes.map( ( blockType ) => ( + <li + key={ blockType.name } + className="edit-post-manage-blocks-modal__checklist-item" + > + <CheckboxControl + label={ ( + <Fragment> + { blockType.title } + <BlockIcon icon={ blockType.icon } /> + </Fragment> + ) } + checked={ value.includes( blockType.name ) } + onChange={ partial( onItemChange, blockType.name ) } + /> + </li> + ) ) } + </ul> + ); +} + +export default BlockTypesChecklist; diff --git a/packages/edit-post/src/components/manage-blocks-modal/index.js b/packages/edit-post/src/components/manage-blocks-modal/index.js new file mode 100644 index 0000000000000..ee88068ad6d6e --- /dev/null +++ b/packages/edit-post/src/components/manage-blocks-modal/index.js @@ -0,0 +1,53 @@ +/** + * WordPress dependencies + */ +import { Modal } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { withSelect, withDispatch } from '@wordpress/data'; +import { compose } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import BlockManager from './manager'; + +/** + * Unique identifier for Manage Blocks modal. + * + * @type {string} + */ +const MODAL_NAME = 'edit-post/manage-blocks'; + +export function ManageBlocksModal( { isActive, closeModal } ) { + if ( ! isActive ) { + return null; + } + + return ( + <Modal + className="edit-post-manage-blocks-modal" + title={ __( 'Block Manager' ) } + closeLabel={ __( 'Close' ) } + onRequestClose={ closeModal } + > + <BlockManager /> + </Modal> + ); +} + +export default compose( [ + withSelect( ( select ) => { + const { isModalActive } = select( 'core/edit-post' ); + + return { + isActive: isModalActive( MODAL_NAME ), + }; + } ), + withDispatch( ( dispatch ) => { + const { closeModal } = dispatch( 'core/edit-post' ); + + return { + closeModal, + }; + } ), +] )( ManageBlocksModal ); diff --git a/packages/edit-post/src/components/manage-blocks-modal/manager.js b/packages/edit-post/src/components/manage-blocks-modal/manager.js new file mode 100644 index 0000000000000..7cf25463e8a4d --- /dev/null +++ b/packages/edit-post/src/components/manage-blocks-modal/manager.js @@ -0,0 +1,88 @@ +/** + * External dependencies + */ +import { filter } from 'lodash'; + +/** + * WordPress dependencies + */ +import { withSelect } from '@wordpress/data'; +import { compose, withState } from '@wordpress/compose'; +import { TextControl } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import BlockManagerCategory from './category'; + +function BlockManager( { + search, + setState, + blockTypes, + categories, + hasBlockSupport, + isMatchingSearchTerm, +} ) { + // Filtering occurs here (as opposed to `withSelect`) to avoid wasted + // wasted renders by consequence of `Array#filter` producing a new + // value reference on each call. + blockTypes = blockTypes.filter( ( blockType ) => ( + hasBlockSupport( blockType, 'inserter', true ) && + ( ! search || isMatchingSearchTerm( blockType, search ) ) + ) ); + + return ( + <div className="edit-post-manage-blocks-modal__content"> + <TextControl + type="search" + label={ __( 'Search for a block' ) } + value={ search } + onChange={ ( nextSearch ) => setState( { + search: nextSearch, + } ) } + className="edit-post-manage-blocks-modal__search" + /> + <div + tabIndex="0" + role="region" + aria-label={ __( 'Available block types' ) } + className="edit-post-manage-blocks-modal__results" + > + { blockTypes.length === 0 && ( + <p className="edit-post-manage-blocks-modal__no-results"> + { __( 'No blocks found.' ) } + </p> + ) } + { categories.map( ( category ) => ( + <BlockManagerCategory + key={ category.slug } + category={ category } + blockTypes={ filter( blockTypes, { + category: category.slug, + } ) } + /> + ) ) } + </div> + </div> + ); +} + +export default compose( [ + withState( { search: '' } ), + withSelect( ( select ) => { + const { + getBlockTypes, + getCategories, + hasBlockSupport, + isMatchingSearchTerm, + } = select( 'core/blocks' ); + + return { + blockTypes: getBlockTypes(), + categories: getCategories(), + hasBlockSupport, + isMatchingSearchTerm, + }; + } ), +] )( BlockManager ); diff --git a/packages/edit-post/src/components/manage-blocks-modal/show-all.js b/packages/edit-post/src/components/manage-blocks-modal/show-all.js new file mode 100644 index 0000000000000..7aaa206bc626a --- /dev/null +++ b/packages/edit-post/src/components/manage-blocks-modal/show-all.js @@ -0,0 +1,31 @@ +/** + * WordPress dependencies + */ +import { withInstanceId } from '@wordpress/compose'; +import { FormToggle } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +function BlockManagerShowAll( { instanceId, checked, onChange } ) { + const id = 'edit-post-manage-blocks-modal__show-all-' + instanceId; + + return ( + <div className="edit-post-manage-blocks-modal__show-all"> + <label + htmlFor={ id } + className="edit-post-manage-blocks-modal__show-all-label" + > + { + /* translators: Checkbox toggle label */ + __( 'Show section' ) + } + </label> + <FormToggle + id={ id } + checked={ checked } + onChange={ ( event ) => onChange( event.target.checked ) } + /> + </div> + ); +} + +export default withInstanceId( BlockManagerShowAll ); diff --git a/packages/edit-post/src/components/manage-blocks-modal/style.scss b/packages/edit-post/src/components/manage-blocks-modal/style.scss new file mode 100644 index 0000000000000..45569b28c6083 --- /dev/null +++ b/packages/edit-post/src/components/manage-blocks-modal/style.scss @@ -0,0 +1,117 @@ +.edit-post-manage-blocks-modal { + @include break-small() { + height: calc(100% - #{ $header-height } - #{ $header-height }); + } +} + +.edit-post-manage-blocks-modal .components-modal__content { + padding-bottom: 0; + display: flex; + flex-direction: column; +} + +.edit-post-manage-blocks-modal .components-modal__header { + flex-shrink: 0; + margin-bottom: 0; +} + +.edit-post-manage-blocks-modal__content { + display: flex; + flex-direction: column; + flex: 0 1 100%; + min-height: 0; +} + +.edit-post-manage-blocks-modal__no-results { + font-style: italic; + padding: 24px 0; + text-align: center; +} + +.edit-post-manage-blocks-modal__search { + margin: $grid-size-large 0; + + .components-base-control__field { + margin-bottom: 0; + } + + .components-base-control__label { + margin-top: -0.25 * $grid-size-large; + } + + input[type="search"].components-text-control__input { + padding: 0.75 * $grid-size-large; + border-radius: $radius-round-rectangle; + } +} + +.edit-post-manage-blocks-modal__category { + margin: 0 0 2rem 0; +} + +.edit-post-manage-blocks-modal__category-title { + position: sticky; + top: 0; + padding: $panel-padding 0; + background-color: $white; + + .components-base-control__field { + margin-bottom: 0; + } + + .components-checkbox-control__label { + font-size: 0.9rem; + font-weight: 600; + } +} + +.edit-post-manage-blocks-modal__show-all { + margin-right: $grid-size; +} + +.edit-post-manage-blocks-modal__checklist { + margin-top: 0; +} + +.edit-post-manage-blocks-modal__checklist-item { + margin-bottom: 0; + padding-left: $grid-size-large; + border-top: 1px solid $light-gray-500; + + &:last-child { + border-bottom: 1px solid $light-gray-500; + } + + .components-base-control__field { + align-items: center; + display: flex; + margin: 0; + } + + .components-modal__content & input[type="checkbox"] { + margin: 0 $grid-size; + } + + .components-checkbox-control__label { + display: flex; + align-items: center; + justify-content: space-between; + flex-grow: 1; + padding: 0.6rem 0 0.6rem 10px; + } + + .editor-block-icon { + margin-right: 10px; + fill: $dark-gray-500; + } +} + +.edit-post-manage-blocks-modal__results { + height: 100%; + overflow: auto; + margin-left: -1 * $grid-size-large; + margin-right: -1 * $grid-size-large; + padding-left: $grid-size-large; + padding-right: $grid-size-large; + border-top: $border-width solid $light-gray-500; +} diff --git a/packages/edit-post/src/components/options-modal/style.scss b/packages/edit-post/src/components/options-modal/style.scss index 62b827d104ecd..01b5f816c0ca0 100644 --- a/packages/edit-post/src/components/options-modal/style.scss +++ b/packages/edit-post/src/components/options-modal/style.scss @@ -21,6 +21,10 @@ margin: 0; } + &.components-base-control + &.components-base-control { + margin-bottom: 0; + } + .components-checkbox-control__label { flex-grow: 1; padding: 0.6rem 0 0.6rem 10px; diff --git a/packages/edit-post/src/editor.js b/packages/edit-post/src/editor.js index 3e51956a0e074..28e55b08d6cbf 100644 --- a/packages/edit-post/src/editor.js +++ b/packages/edit-post/src/editor.js @@ -2,6 +2,7 @@ * External dependencies */ import memize from 'memize'; +import { size, map, without } from 'lodash'; /** * WordPress dependencies @@ -26,12 +27,37 @@ class Editor extends Component { } ); } - getEditorSettings( settings, hasFixedToolbar, focusMode ) { - return { + getEditorSettings( + settings, + hasFixedToolbar, + focusMode, + hiddenBlockTypes, + blockTypes, + ) { + settings = { ...settings, hasFixedToolbar, focusMode, }; + + // Omit hidden block types if exists and non-empty. + if ( size( hiddenBlockTypes ) > 0 ) { + // Defer to passed setting for `allowedBlockTypes` if provided as + // anything other than `true` (where `true` is equivalent to allow + // all block types). + const defaultAllowedBlockTypes = ( + true === settings.allowedBlockTypes ? + map( blockTypes, 'name' ) : + ( settings.allowedBlockTypes || [] ) + ); + + settings.allowedBlockTypes = without( + defaultAllowedBlockTypes, + ...hiddenBlockTypes, + ); + } + + return settings; } render() { @@ -42,6 +68,8 @@ class Editor extends Component { post, initialEdits, onError, + hiddenBlockTypes, + blockTypes, ...props } = this.props; @@ -49,11 +77,13 @@ class Editor extends Component { return null; } - const editorSettings = { - ...settings, + const editorSettings = this.getEditorSettings( + settings, hasFixedToolbar, focusMode, - }; + hiddenBlockTypes, + blockTypes, + ); return ( <StrictMode> @@ -74,8 +104,16 @@ class Editor extends Component { } } -export default withSelect( ( select, { postId, postType } ) => ( { - hasFixedToolbar: select( 'core/edit-post' ).isFeatureActive( 'fixedToolbar' ), - focusMode: select( 'core/edit-post' ).isFeatureActive( 'focusMode' ), - post: select( 'core' ).getEntityRecord( 'postType', postType, postId ), -} ) )( Editor ); +export default withSelect( ( select, { postId, postType } ) => { + const { isFeatureActive, getPreference } = select( 'core/edit-post' ); + const { getEntityRecord } = select( 'core' ); + const { getBlockTypes } = select( 'core/blocks' ); + + return { + hasFixedToolbar: isFeatureActive( 'fixedToolbar' ), + focusMode: isFeatureActive( 'focusMode' ), + post: getEntityRecord( 'postType', postType, postId ), + hiddenBlockTypes: getPreference( 'hiddenBlockTypes' ), + blockTypes: getBlockTypes(), + }; +} )( Editor ); diff --git a/packages/edit-post/src/plugins/index.js b/packages/edit-post/src/plugins/index.js index 05a079db4738f..47f09e6bd547e 100644 --- a/packages/edit-post/src/plugins/index.js +++ b/packages/edit-post/src/plugins/index.js @@ -11,6 +11,7 @@ import { addQueryArgs } from '@wordpress/url'; * Internal dependencies */ import CopyContentMenuItem from './copy-content-menu-item'; +import ManageBlocksMenuItem from './manage-blocks-menu-item'; import KeyboardShortcutsHelpMenuItem from './keyboard-shortcuts-help-menu-item'; import ToolsMoreMenuGroup from '../components/header/tools-more-menu-group'; @@ -21,6 +22,7 @@ registerPlugin( 'edit-post', { <ToolsMoreMenuGroup> { ( { onClose } ) => ( <Fragment> + <ManageBlocksMenuItem onSelect={ onClose } /> <MenuItem role="menuitem" href={ addQueryArgs( 'edit.php', { post_type: 'wp_block' } ) } diff --git a/packages/edit-post/src/plugins/manage-blocks-menu-item/index.js b/packages/edit-post/src/plugins/manage-blocks-menu-item/index.js new file mode 100644 index 0000000000000..c304d69a3f238 --- /dev/null +++ b/packages/edit-post/src/plugins/manage-blocks-menu-item/index.js @@ -0,0 +1,34 @@ +/** + * External dependencies + */ +import { flow } from 'lodash'; + +/** + * WordPress dependencies + */ +import { MenuItem } from '@wordpress/components'; +import { withDispatch } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; + +export function ManageBlocksMenuItem( { onSelect, openModal } ) { + return ( + <MenuItem + onClick={ flow( [ + onSelect, + () => openModal( 'edit-post/manage-blocks' ), + ] ) } + > + { __( 'Block Manager' ) } + </MenuItem> + ); +} + +export default withDispatch( ( dispatch ) => { + const { + openModal, + } = dispatch( 'core/edit-post' ); + + return { + openModal, + }; +} )( ManageBlocksMenuItem ); diff --git a/packages/edit-post/src/store/actions.js b/packages/edit-post/src/store/actions.js index 2e2af7a2295f4..c12eb674837eb 100644 --- a/packages/edit-post/src/store/actions.js +++ b/packages/edit-post/src/store/actions.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import { castArray } from 'lodash'; + /** * Returns an action object used in signalling that the user opened an editor sidebar. * @@ -160,6 +165,36 @@ export function togglePinnedPluginItem( pluginName ) { }; } +/** + * Returns an action object used in signalling that block types by the given + * name(s) should be hidden. + * + * @param {string[]} blockNames Names of block types to hide. + * + * @return {Object} Action object. + */ +export function hideBlockTypes( blockNames ) { + return { + type: 'HIDE_BLOCK_TYPES', + blockNames: castArray( blockNames ), + }; +} + +/** + * Returns an action object used in signalling that block types by the given + * name(s) should be shown. + * + * @param {string[]} blockNames Names of block types to show. + * + * @return {Object} Action object. + */ +export function showBlockTypes( blockNames ) { + return { + type: 'SHOW_BLOCK_TYPES', + blockNames: castArray( blockNames ), + }; +} + /** * Returns an action object used in signaling * what Meta boxes are available in which location. diff --git a/packages/edit-post/src/store/defaults.js b/packages/edit-post/src/store/defaults.js index b88b2c24a6dc0..9cfc6506fece0 100644 --- a/packages/edit-post/src/store/defaults.js +++ b/packages/edit-post/src/store/defaults.js @@ -10,4 +10,5 @@ export const PREFERENCES_DEFAULTS = { fixedToolbar: false, }, pinnedPluginItems: {}, + hiddenBlockTypes: [], }; diff --git a/packages/edit-post/src/store/reducer.js b/packages/edit-post/src/store/reducer.js index a016c701582a9..ab64ba7c5ae62 100644 --- a/packages/edit-post/src/store/reducer.js +++ b/packages/edit-post/src/store/reducer.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { get, includes } from 'lodash'; +import { get, includes, flow, without, union } from 'lodash'; /** * WordPress dependencies @@ -20,6 +20,18 @@ import { PREFERENCES_DEFAULTS } from './defaults'; */ export const DEFAULT_ACTIVE_GENERAL_SIDEBAR = 'edit-post/document'; +/** + * Higher-order reducer creator which provides the given initial state for the + * original reducer. + * + * @param {*} initialState Initial state to provide to reducer. + * + * @return {Function} Higher-order reducer. + */ +const createWithInitialState = ( initialState ) => ( reducer ) => { + return ( state = initialState, action ) => reducer( state, action ); +}; + /** * Reducer returning the user preferences. * @@ -39,8 +51,11 @@ export const DEFAULT_ACTIVE_GENERAL_SIDEBAR = 'edit-post/document'; * * @return {Object} Updated state. */ -export const preferences = combineReducers( { - isGeneralSidebarDismissed( state = false, action ) { +export const preferences = flow( [ + combineReducers, + createWithInitialState( PREFERENCES_DEFAULTS ), +] )( { + isGeneralSidebarDismissed( state, action ) { switch ( action.type ) { case 'OPEN_GENERAL_SIDEBAR': case 'CLOSE_GENERAL_SIDEBAR': @@ -49,7 +64,7 @@ export const preferences = combineReducers( { return state; }, - panels( state = PREFERENCES_DEFAULTS.panels, action ) { + panels( state, action ) { switch ( action.type ) { case 'TOGGLE_PANEL_ENABLED': { const { panelName } = action; @@ -77,7 +92,7 @@ export const preferences = combineReducers( { return state; }, - features( state = PREFERENCES_DEFAULTS.features, action ) { + features( state, action ) { if ( action.type === 'TOGGLE_FEATURE' ) { return { ...state, @@ -87,14 +102,14 @@ export const preferences = combineReducers( { return state; }, - editorMode( state = PREFERENCES_DEFAULTS.editorMode, action ) { + editorMode( state, action ) { if ( action.type === 'SWITCH_MODE' ) { return action.mode; } return state; }, - pinnedPluginItems( state = PREFERENCES_DEFAULTS.pinnedPluginItems, action ) { + pinnedPluginItems( state, action ) { if ( action.type === 'TOGGLE_PINNED_PLUGIN_ITEM' ) { return { ...state, @@ -103,6 +118,17 @@ export const preferences = combineReducers( { } return state; }, + hiddenBlockTypes( state, action ) { + switch ( action.type ) { + case 'SHOW_BLOCK_TYPES': + return without( state, ...action.blockNames ); + + case 'HIDE_BLOCK_TYPES': + return union( state, action.blockNames ); + } + + return state; + }, } ); /** diff --git a/packages/edit-post/src/store/test/reducer.js b/packages/edit-post/src/store/test/reducer.js index 66027cd21dc44..4241de25d5b95 100644 --- a/packages/edit-post/src/store/test/reducer.js +++ b/packages/edit-post/src/store/test/reducer.js @@ -15,21 +15,14 @@ import { metaBoxLocations, removedPanels, } from '../reducer'; +import { PREFERENCES_DEFAULTS } from '../defaults'; describe( 'state', () => { describe( 'preferences()', () => { it( 'should apply all defaults', () => { const state = preferences( undefined, {} ); - expect( state ).toEqual( { - editorMode: 'visual', - isGeneralSidebarDismissed: false, - panels: { - 'post-status': { opened: true }, - }, - features: { fixedToolbar: false }, - pinnedPluginItems: {}, - } ); + expect( state ).toEqual( PREFERENCES_DEFAULTS ); } ); it( 'should set the general sidebar dismissed', () => { @@ -223,6 +216,40 @@ describe( 'state', () => { expect( state.pinnedPluginItems[ 'foo/disabled' ] ).toBe( true ); } ); } ); + + describe( 'hiddenBlockTypes', () => { + it( 'concatenates unique names on disable', () => { + const original = deepFreeze( { + hiddenBlockTypes: [ 'a', 'b' ], + } ); + + const state = preferences( original, { + type: 'HIDE_BLOCK_TYPES', + blockNames: [ 'b', 'c' ], + } ); + + expect( state.hiddenBlockTypes ).toEqual( [ + 'a', + 'b', + 'c', + ] ); + } ); + + it( 'omits present names by enable', () => { + const original = deepFreeze( { + hiddenBlockTypes: [ 'a', 'b' ], + } ); + + const state = preferences( original, { + type: 'SHOW_BLOCK_TYPES', + blockNames: [ 'b', 'c' ], + } ); + + expect( state.hiddenBlockTypes ).toEqual( [ + 'a', + ] ); + } ); + } ); } ); describe( 'activeGeneralSidebar', () => { diff --git a/packages/edit-post/src/style.scss b/packages/edit-post/src/style.scss index 4459591143609..4b8b9e3949140 100644 --- a/packages/edit-post/src/style.scss +++ b/packages/edit-post/src/style.scss @@ -6,6 +6,7 @@ @import "./components/header/pinned-plugins/style.scss"; @import "./components/keyboard-shortcut-help-modal/style.scss"; @import "./components/layout/style.scss"; +@import "./components/manage-blocks-modal/style.scss"; @import "./components/meta-boxes/meta-boxes-area/style.scss"; @import "./components/sidebar/style.scss"; @import "./components/sidebar/last-revision/style.scss"; @@ -228,9 +229,44 @@ body.block-editor-page { input[type="checkbox"] { border-radius: $radius-round-rectangle / 2; - &:checked::before { - margin: -4px 0 0 -5px; + &:checked::before, + &[aria-checked="mixed"]::before { + margin: -3px -5px; color: $white; + + @include break-medium() { + margin: -4px 0 0 -5px; + } + } + + &[aria-checked="mixed"] { + background: theme(toggle); + border-color: theme(toggle); + + &::before { + // Inherited from `forms.css`. + // See: https://github.com/WordPress/wordpress-develop/tree/5.1.1/src/wp-admin/css/forms.css#L122-L132 + content: "\f460"; + float: left; + display: inline-block; + vertical-align: middle; + width: 16px; + /* stylelint-disable */ + font: normal 30px/1 dashicons; + /* stylelint-enable */ + speak: none; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + @include break-medium() { + float: none; + font-size: 21px; + } + } + + &:focus { + box-shadow: 0 0 0 2px $dark-gray-500; + } } } diff --git a/packages/editor/src/store/defaults.js b/packages/editor/src/store/defaults.js index 53178b43cb5aa..d22d6e7f67de8 100644 --- a/packages/editor/src/store/defaults.js +++ b/packages/editor/src/store/defaults.js @@ -17,13 +17,14 @@ export const INITIAL_EDITS_DEFAULTS = {}; /** * The default post editor settings * - * richEditingEnabled boolean Whether rich editing is enabled or not - * enableCustomFields boolean Whether the WordPress custom fields are enabled or not - * autosaveInterval number Autosave Interval - * availableTemplates array? The available post templates - * disablePostFormats boolean Whether or not the post formats are disabled - * allowedMimeTypes array? List of allowed mime types and file extensions - * maxUploadFileSize number Maximum upload file size + * allowedBlockTypes boolean|Array Allowed block types + * richEditingEnabled boolean Whether rich editing is enabled or not + * enableCustomFields boolean Whether the WordPress custom fields are enabled or not + * autosaveInterval number Autosave Interval + * availableTemplates array? The available post templates + * disablePostFormats boolean Whether or not the post formats are disabled + * allowedMimeTypes array? List of allowed mime types and file extensions + * maxUploadFileSize number Maximum upload file size */ export const EDITOR_SETTINGS_DEFAULTS = { ...SETTINGS_DEFAULTS, From 3cc3c8973e76698e3e5e1f3a052394fe69048335 Mon Sep 17 00:00:00 2001 From: andrei draganescu <me@andreidraganescu.info> Date: Fri, 15 Mar 2019 20:05:05 +0900 Subject: [PATCH 660/691] Update/e2e tests options (#14129) * added an interactive argument to the test-e2e script * mend * added a way to bypass certain args from Jest, reverted to normal argument passing using double dash * simplified the argument cleanup function * simplified the argument cleanup function * added a way to bypass certain args from Jest, reverted to normal argument passing using double dash * simplified the argument cleanup function * simplified the argument cleanup function * cleaned up the code by moving the prefixed arguments removal to getcliargs --- packages/scripts/scripts/test-e2e.js | 10 +++++++++- packages/scripts/utils/index.js | 2 ++ packages/scripts/utils/process.js | 10 +++++++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/scripts/scripts/test-e2e.js b/packages/scripts/scripts/test-e2e.js index fc2e8b42f345d..8105891c8d838 100644 --- a/packages/scripts/scripts/test-e2e.js +++ b/packages/scripts/scripts/test-e2e.js @@ -19,6 +19,7 @@ const jest = require( 'jest' ); */ const { fromConfigRoot, + getCliArg, getCliArgs, hasCliArg, hasProjectFile, @@ -42,4 +43,11 @@ const runInBand = ! hasRunInBand ? [ '--runInBand' ] : []; -jest.run( [ ...config, ...runInBand, ...getCliArgs() ] ); +const cleanUpPrefixes = [ '--puppeteer-' ]; + +if ( hasCliArg( '--puppeteer-interactive' ) ) { + process.env.PUPPETEER_HEADLESS = 'false'; + process.env.PUPPETEER_SLOWMO = getCliArg( '--puppeteer-slowmo' ) || 80; +} + +jest.run( [ ...config, ...runInBand, ...getCliArgs( cleanUpPrefixes ) ] ); diff --git a/packages/scripts/utils/index.js b/packages/scripts/utils/index.js index 0837f7ab80996..aebbcdca71800 100644 --- a/packages/scripts/utils/index.js +++ b/packages/scripts/utils/index.js @@ -6,6 +6,7 @@ const { getCliArgs, hasCliArg, spawnScript, + cleanUpArgs, } = require( './cli' ); const { getWebpackArgs, @@ -35,4 +36,5 @@ module.exports = { hasPackageProp, hasProjectFile, spawnScript, + cleanUpArgs, }; diff --git a/packages/scripts/utils/process.js b/packages/scripts/utils/process.js index f8d3a0f6b2057..cfa38afa26aa9 100644 --- a/packages/scripts/utils/process.js +++ b/packages/scripts/utils/process.js @@ -1,4 +1,12 @@ -const getCliArgs = () => process.argv.slice( 2 ); +const getCliArgs = ( excludePrefixes ) => { + const args = process.argv.slice( 2 ); + if ( excludePrefixes ) { + return args.filter( ( arg ) => { + return ! excludePrefixes.some( ( prefix ) => arg.startsWith( prefix ) ); + } ); + } + return args; +}; module.exports = { exit: process.exit, From 2dad4efbf4c03c54a8a1d2ef235c2b5879b6397b Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Fri, 15 Mar 2019 11:56:15 +0000 Subject: [PATCH 661/691] Fix: Regression: Template lock option is not taken into consideration; Add: end to end tests; (#14390) --- packages/e2e-tests/plugins/cpt-locking.php | 56 +++++++++++ .../__snapshots__/cpt-locking.test.js.snap | 57 +++++++++++ .../specs/plugins/cpt-locking.test.js | 99 +++++++++++++++++++ .../editor/src/components/provider/index.js | 15 +-- 4 files changed, 220 insertions(+), 7 deletions(-) create mode 100644 packages/e2e-tests/plugins/cpt-locking.php create mode 100644 packages/e2e-tests/specs/plugins/__snapshots__/cpt-locking.test.js.snap create mode 100644 packages/e2e-tests/specs/plugins/cpt-locking.test.js diff --git a/packages/e2e-tests/plugins/cpt-locking.php b/packages/e2e-tests/plugins/cpt-locking.php new file mode 100644 index 0000000000000..ba8a8d611c428 --- /dev/null +++ b/packages/e2e-tests/plugins/cpt-locking.php @@ -0,0 +1,56 @@ +<?php +/** + * Plugin Name: Gutenberg Test Plugin, CPT Locking + * Plugin URI: https://github.com/WordPress/gutenberg + * Author: Gutenberg Team + * + * @package gutenberg-test-cpt-locking + */ + +/** + * Registers CPT's with 3 diffferent types of locking. + */ +function gutenberg_test_cpt_locking() { + $template = array( + array( 'core/image' ), + array( + 'core/paragraph', + array( + 'placeholder' => 'Add a description', + ), + ), + array( 'core/quote' ), + ); + register_post_type( + 'locked-all-post', + array( + 'public' => true, + 'label' => 'Locked All Post', + 'show_in_rest' => true, + 'template' => $template, + 'template_lock' => 'all', + ) + ); + register_post_type( + 'locked-insert-post', + array( + 'public' => true, + 'label' => 'Locked Insert Post', + 'show_in_rest' => true, + 'template' => $template, + 'template_lock' => 'insert', + ) + ); + register_post_type( + 'not-locked-post', + array( + 'public' => true, + 'label' => 'Not Locked Post', + 'show_in_rest' => true, + 'template' => $template, + 'template_lock' => false, + ) + ); +} + +add_action( 'init', 'gutenberg_test_cpt_locking' ); diff --git a/packages/e2e-tests/specs/plugins/__snapshots__/cpt-locking.test.js.snap b/packages/e2e-tests/specs/plugins/__snapshots__/cpt-locking.test.js.snap new file mode 100644 index 0000000000000..de467ce40d3e8 --- /dev/null +++ b/packages/e2e-tests/specs/plugins/__snapshots__/cpt-locking.test.js.snap @@ -0,0 +1,57 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`cpt locking template_lock false should allow blocks to be inserted 1`] = ` +"<!-- wp:image --> +<figure class=\\"wp-block-image\\"><img alt=\\"\\"/></figure> +<!-- /wp:image --> + +<!-- wp:paragraph {\\"placeholder\\":\\"Add a description\\"} --> +<p></p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class=\\"wp-block-quote\\"><p></p></blockquote> +<!-- /wp:quote --> + +<!-- wp:list --> +<ul><li>List content</li></ul> +<!-- /wp:list -->" +`; + +exports[`cpt locking template_lock false should allow blocks to be moved 1`] = ` +"<!-- wp:paragraph {\\"placeholder\\":\\"Add a description\\"} --> +<p>p1</p> +<!-- /wp:paragraph --> + +<!-- wp:image --> +<figure class=\\"wp-block-image\\"><img alt=\\"\\"/></figure> +<!-- /wp:image --> + +<!-- wp:quote --> +<blockquote class=\\"wp-block-quote\\"><p></p></blockquote> +<!-- /wp:quote -->" +`; + +exports[`cpt locking template_lock false should allow blocks to be removed 1`] = ` +"<!-- wp:image --> +<figure class=\\"wp-block-image\\"><img alt=\\"\\"/></figure> +<!-- /wp:image --> + +<!-- wp:quote --> +<blockquote class=\\"wp-block-quote\\"><p></p></blockquote> +<!-- /wp:quote -->" +`; + +exports[`cpt locking template_lock insert should allow blocks to be moved 1`] = ` +"<!-- wp:paragraph {\\"placeholder\\":\\"Add a description\\"} --> +<p>p1</p> +<!-- /wp:paragraph --> + +<!-- wp:image --> +<figure class=\\"wp-block-image\\"><img alt=\\"\\"/></figure> +<!-- /wp:image --> + +<!-- wp:quote --> +<blockquote class=\\"wp-block-quote\\"><p></p></blockquote> +<!-- /wp:quote -->" +`; diff --git a/packages/e2e-tests/specs/plugins/cpt-locking.test.js b/packages/e2e-tests/specs/plugins/cpt-locking.test.js new file mode 100644 index 0000000000000..2a858b8a5c849 --- /dev/null +++ b/packages/e2e-tests/specs/plugins/cpt-locking.test.js @@ -0,0 +1,99 @@ +/** + * WordPress dependencies + */ +import { + activatePlugin, + clickBlockToolbarButton, + createNewPost, + deactivatePlugin, + getEditedPostContent, + insertBlock, +} from '@wordpress/e2e-test-utils'; + +describe( 'cpt locking', () => { + beforeAll( async () => { + await activatePlugin( 'gutenberg-test-plugin-cpt-locking' ); + } ); + + afterAll( async () => { + await deactivatePlugin( 'gutenberg-test-plugin-cpt-locking' ); + } ); + + const shouldRemoveTheInserter = async () => { + expect( + await page.$( '.edit-post-header [aria-label="Add block"]' ) + ).toBeNull(); + }; + + const shouldNotAllowBlocksToBeRemoved = async () => { + await page.type( '.editor-rich-text__editable.wp-block-paragraph', 'p1' ); + await clickBlockToolbarButton( 'More options' ); + expect( + await page.$x( '//button[contains(text(), "Remove Block")]' ) + ).toHaveLength( 0 ); + }; + + const shouldAllowBlocksToBeMoved = async () => { + await page.click( '.editor-rich-text__editable.wp-block-paragraph' ); + expect( + await page.$( 'button[aria-label="Move up"]' ) + ).not.toBeNull(); + await page.click( 'button[aria-label="Move up"]' ); + await page.type( '.editor-rich-text__editable.wp-block-paragraph', 'p1' ); + expect( await getEditedPostContent() ).toMatchSnapshot(); + }; + + describe( 'template_lock all', () => { + beforeEach( async () => { + await createNewPost( { postType: 'locked-all-post' } ); + } ); + + it( 'should remove the inserter', shouldRemoveTheInserter ); + + it( 'should not allow blocks to be removed', shouldNotAllowBlocksToBeRemoved ); + + it( 'should not allow blocks to be moved', async () => { + await page.click( '.editor-rich-text__editable.wp-block-paragraph' ); + expect( + await page.$( 'button[aria-label="Move up"]' ) + ).toBeNull(); + } ); + } ); + + describe( 'template_lock insert', () => { + beforeEach( async () => { + await createNewPost( { postType: 'locked-insert-post' } ); + } ); + + it( 'should remove the inserter', shouldRemoveTheInserter ); + + it( 'should not allow blocks to be removed', shouldNotAllowBlocksToBeRemoved ); + + it( 'should allow blocks to be moved', shouldAllowBlocksToBeMoved ); + } ); + + describe( 'template_lock false', () => { + beforeEach( async () => { + await createNewPost( { postType: 'not-locked-post' } ); + } ); + + it( 'should allow blocks to be inserted', async () => { + expect( + await page.$( '.edit-post-header [aria-label="Add block"]' ) + ).not.toBeNull(); + await insertBlock( 'List' ); + await page.keyboard.type( 'List content' ); + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'should allow blocks to be removed', async () => { + await page.type( '.editor-rich-text__editable.wp-block-paragraph', 'p1' ); + await clickBlockToolbarButton( 'More options' ); + const [ removeBlock ] = await page.$x( '//button[contains(text(), "Remove Block")]' ); + await removeBlock.click(); + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'should allow blocks to be moved', shouldAllowBlocksToBeMoved ); + } ); +} ); diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index 14ead7919501f..8ced0e95422c2 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -55,20 +55,21 @@ class EditorProvider extends Component { return { ...pick( settings, [ 'alignWide', + 'allowedBlockTypes', 'availableLegacyWidgets', + 'bodyPlaceholder', 'colors', 'disableCustomColors', - 'fontSizes', 'disableCustomFontSizes', - 'imageSizes', - 'maxWidth', - 'allowedBlockTypes', + 'focusMode', + 'fontSizes', 'hasFixedToolbar', 'hasPermissionsToManageWidgets', - 'focusMode', - 'styles', + 'imageSizes', 'isRTL', - 'bodyPlaceholder', + 'maxWidth', + 'styles', + 'templateLock', 'titlePlaceholder', ] ), __experimentalMetaSource: { From 790cb304b6a6125f23293b0820cbc785dfed417a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <iseulde@automattic.com> Date: Fri, 15 Mar 2019 13:52:44 +0100 Subject: [PATCH 662/691] RichText: remove selection change listener during composition (#14449) --- .../src/components/rich-text/editable.js | 32 ++++--------------- .../src/components/rich-text/index.js | 6 ++-- 2 files changed, 11 insertions(+), 27 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/editable.js b/packages/block-editor/src/components/rich-text/editable.js index 401843e00ac14..58c7217e1944c 100644 --- a/packages/block-editor/src/components/rich-text/editable.js +++ b/packages/block-editor/src/components/rich-text/editable.js @@ -13,7 +13,7 @@ import { BACKSPACE, DELETE } from '@wordpress/keycodes'; /** * Internal dependencies */ -import { diffAriaProps, pickAriaProps } from './aria'; +import { diffAriaProps } from './aria'; /** * Browser dependencies @@ -137,10 +137,7 @@ export default class Editable extends Component { bindEditorNode( editorNode ) { this.editorNode = editorNode; - - if ( this.props.setRef ) { - this.props.setRef( editorNode ); - } + this.props.setRef( editorNode ); if ( IS_IE ) { if ( editorNode ) { @@ -154,7 +151,6 @@ export default class Editable extends Component { } render() { - const ariaProps = pickAriaProps( this.props ); const { tagName = 'div', style, @@ -162,21 +158,14 @@ export default class Editable extends Component { valueToEditableHTML, className, isPlaceholderVisible, - onPaste, - onInput, - onKeyDown, - onCompositionEnd, - onFocus, - onBlur, - onMouseDown, - onTouchStart, + ...remainingProps } = this.props; - ariaProps.role = 'textbox'; - ariaProps[ 'aria-multiline' ] = true; + delete remainingProps.setRef; return createElement( tagName, { - ...ariaProps, + role: 'textbox', + 'aria-multiline': true, className: classnames( className, CLASS_NAME ), contentEditable: true, [ IS_PLACEHOLDER_VISIBLE_ATTR_NAME ]: isPlaceholderVisible, @@ -184,14 +173,7 @@ export default class Editable extends Component { style, suppressContentEditableWarning: true, dangerouslySetInnerHTML: { __html: valueToEditableHTML( record ) }, - onPaste, - onInput, - onFocus, - onBlur, - onKeyDown, - onCompositionEnd, - onMouseDown, - onTouchStart, + ...remainingProps, } ); } } diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index b513c10f737d0..006ec4a697760 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -373,6 +373,8 @@ export class RichText extends Component { // Browsers setting `isComposing` to `true` will usually emit a final // `input` event when the characters are composed. if ( event && event.nativeEvent.isComposing ) { + // Also don't update any selection. + document.removeEventListener( 'selectionchange', this.onSelectionChange ); return; } @@ -444,6 +446,8 @@ export class RichText extends Component { // Ensure the value is up-to-date for browsers that don't emit a final // input event after composition. this.onInput(); + // Tracking selection changes can be resumed. + document.addEventListener( 'selectionchange', this.onSelectionChange ); } /** @@ -1114,8 +1118,6 @@ export class RichText extends Component { onBlur={ this.onBlur } onMouseDown={ this.onPointerDown } onTouchStart={ this.onPointerDown } - multilineTag={ this.multilineTag } - multilineWrapperTags={ this.multilineWrapperTags } setRef={ this.setRef } /> { isPlaceholderVisible && From 384816757265f0a0ef0d0bcca24f0ff8d8855a25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <iseulde@automattic.com> Date: Fri, 15 Mar 2019 14:08:17 +0100 Subject: [PATCH 663/691] Input Interaction: fix buffer for the triggering of multi-select (#14448) * Input Interaction: fix buffer for the triggering of multi-select * Add inline comment * Add e2e test --- packages/dom/src/dom.js | 8 +++++- .../multi-block-selection.test.js.snap | 11 ++++++++ .../specs/multi-block-selection.test.js | 28 +++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 packages/e2e-tests/specs/__snapshots__/multi-block-selection.test.js.snap diff --git a/packages/dom/src/dom.js b/packages/dom/src/dom.js index 26f2985e5c48b..75f2d10311345 100644 --- a/packages/dom/src/dom.js +++ b/packages/dom/src/dom.js @@ -180,9 +180,15 @@ export function isVerticalEdge( container, isReverse ) { return false; } - const buffer = rangeRect.height / 2; const editableRect = container.getBoundingClientRect(); + // Calculate a buffer that is half the line height. In some browsers, the + // selection rectangle may not fill the entire height of the line, so we add + // half the line height to the selection rectangle to ensure that it is well + // over its line boundary. + const { lineHeight } = window.getComputedStyle( container ); + const buffer = parseInt( lineHeight, 10 ) / 2; + // Too low. if ( isReverse && rangeRect.top - buffer > editableRect.top ) { return false; diff --git a/packages/e2e-tests/specs/__snapshots__/multi-block-selection.test.js.snap b/packages/e2e-tests/specs/__snapshots__/multi-block-selection.test.js.snap new file mode 100644 index 0000000000000..3634f485a83ee --- /dev/null +++ b/packages/e2e-tests/specs/__snapshots__/multi-block-selection.test.js.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Multi-block selection should only trigger multi-selection when at the end 1`] = ` +"<!-- wp:paragraph --> +<p>1.</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p></p> +<!-- /wp:paragraph -->" +`; diff --git a/packages/e2e-tests/specs/multi-block-selection.test.js b/packages/e2e-tests/specs/multi-block-selection.test.js index 3f1fb30370ef6..5a83abc74036d 100644 --- a/packages/e2e-tests/specs/multi-block-selection.test.js +++ b/packages/e2e-tests/specs/multi-block-selection.test.js @@ -6,6 +6,8 @@ import { insertBlock, createNewPost, pressKeyWithModifier, + pressKeyTimes, + getEditedPostContent, } from '@wordpress/e2e-test-utils'; describe( 'Multi-block selection', () => { @@ -137,4 +139,30 @@ describe( 'Multi-block selection', () => { const speakTextContent = await page.$eval( '#a11y-speak-assertive', ( element ) => element.textContent ); expect( speakTextContent.trim() ).toEqual( '3 blocks selected.' ); } ); + + // See #14448: an incorrect buffer may trigger multi-selection too soon. + it( 'should only trigger multi-selection when at the end', async () => { + // Create a paragraph with four lines. + await clickBlockAppender(); + await page.keyboard.type( '1.' ); + await pressKeyWithModifier( 'shift', 'Enter' ); + await page.keyboard.type( '2.' ); + await pressKeyWithModifier( 'shift', 'Enter' ); + await page.keyboard.type( '3.' ); + await pressKeyWithModifier( 'shift', 'Enter' ); + await page.keyboard.type( '4.' ); + // Create a second block. + await page.keyboard.press( 'Enter' ); + // Move to the middle of the first line. + await pressKeyTimes( 'ArrowUp', 4 ); + await page.keyboard.press( 'ArrowRight' ); + // Select mid line one to mid line four. + await pressKeyWithModifier( 'shift', 'ArrowDown' ); + await pressKeyWithModifier( 'shift', 'ArrowDown' ); + await pressKeyWithModifier( 'shift', 'ArrowDown' ); + // Delete the text to see if the selection was correct. + await page.keyboard.press( 'Backspace' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); } ); From 6c8486f410ab12c149561ba34034f357a171ba38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Wrede?= <soerenwrede@gmail.com> Date: Fri, 15 Mar 2019 15:03:23 +0100 Subject: [PATCH 664/691] Fix typos (#14451) --- docs/contributors/release.md | 14 +++++++------- .../tutorials/format-api/1-register-format.md | 4 ++-- .../tutorials/format-api/2-toolbar-button.md | 4 ++-- .../tutorials/javascript/loading-javascript.md | 2 +- .../tutorials/javascript/scope-your-code.md | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/contributors/release.md b/docs/contributors/release.md index cee20e5cc0219..d88c260a3904a 100644 --- a/docs/contributors/release.md +++ b/docs/contributors/release.md @@ -188,7 +188,7 @@ If you don't have access to [make.wordpress.org/core](https://make.wordpress.org The Gutenberg repository mirrors the [WordPress SVN repository](https://make.wordpress.org/core/handbook/about/release-cycle/) in terms of branching for each SVN branch, a corresponding Gutenberg `wp/*` branch is created: - The `wp/trunk` branch contains all the packages that are published and used in the `trunk` branch of WordPress. - - A Gutenberg branch targetting a specific WordPress major release (including its further minor increments) is created (example `wp/5.2`) based on the `wp/trunk` Gutenberg branch when the WordPress `trunk` branch is marked as "feature-freezed". (This usually happens when the first `beta` of the next WordPress major version is released). + - A Gutenberg branch targeting a specific WordPress major release (including its further minor increments) is created (example `wp/5.2`) based on the `wp/trunk` Gutenberg branch when the WordPress `trunk` branch is marked as "feature-freezed". (This usually happens when the first `beta` of the next WordPress major version is released). ### Synchronizing WordPress Trunk @@ -196,9 +196,9 @@ For each Gutenberg plugin release, WordPress trunk should be synchronized with t **Note:** The WordPress `trunk` branch can be closed or in "feature-freeze" mode. Usually, this happens between the first `beta` and the first `RC` of the WordPress release cycle. During this period, the Gutenberg plugin releases should not be synchronized with WordPress Core. -1. Ensure the WordPress `trunk` branch is open for enhancements. +1. Ensure the WordPress `trunk` branch is open for enhancements. 2. Check out the last published Gutenberg release branch `git checkout release/x.x` -3. Create a Pull Request from this branch targetting `wp/trunk`. +3. Create a Pull Request from this branch targeting `wp/trunk`. 4. Merge the Pull Request using the "Rebase and Merge" button to keep the history of the commits. Now, the branch is ready to be used to publish the npm packages. @@ -208,7 +208,7 @@ Now, the branch is ready to be used to publish the npm packages. 3. Update the `CHANGELOG.md` files of the published packages with the new released versions and commit to the `wp/trunk` branch. 4. Cherry-pick the "Publish" (created by Lerna) and the CHANGELOG update commits into the `master` branch of Gutenberg. -Now, the npm packages should be ready and a patch can be created and commited into WordPress `trunk`. +Now, the npm packages should be ready and a patch can be created and committed into WordPress `trunk`. ### Minor WordPress Releases @@ -220,7 +220,7 @@ The following workflow is needed when bug fixes or security releases need to be 1. Cherry-pick 2. Check out the last published Gutenberg release branch `git checkout release/x.x` -3. Create a Pull Request from this branch targetting the WordPress related major branch (Example `wp/5.2`). +3. Create a Pull Request from this branch targeting the WordPress related major branch (Example `wp/5.2`). 4. Merge the Pull Request using the "Rebase and Merge" button to keep the history of the commits. Now, the branch is ready to be used to publish the npm packages. @@ -228,12 +228,12 @@ Now, the branch is ready to be used to publish the npm packages. 1. Check out the WordPress branch used before (Example `wp/5.2`). 2. Run the [package release process] but when asked for the version numbers to choose for each package, (assuming the package versions are written using this format `major.minor.patch`) make sure to bump only the `patch` version number. For example, if the last published package version for this WordPress branch was `5.6.0`, choose `5.6.1` as a version. -**Note:** For WordPress `5.0` and WordPress `5.1`, a different release process was used. This means that when choosing npm package versions targetting these two releases, you won't be able to use the next `patch` version number as it may have been already used. You should use the "metadata" modifier for these. For example, if the last published package version for this WordPress branch was `5.6.1`, choose `5.6.1+patch.1` as a version. +**Note:** For WordPress `5.0` and WordPress `5.1`, a different release process was used. This means that when choosing npm package versions targeting these two releases, you won't be able to use the next `patch` version number as it may have been already used. You should use the "metadata" modifier for these. For example, if the last published package version for this WordPress branch was `5.6.1`, choose `5.6.1+patch.1` as a version. 3. Update the `CHANGELOG.md` files of the published packages with the new released versions and commit to the corresponding branch (Example `wp/5.2`). 4. Cherry-pick the CHANGELOG update commits into the `master` branch of Gutenberg. -Now, the npm packages should be ready and a patch can be created and commited into the corresponding WordPress SVN branch. +Now, the npm packages should be ready and a patch can be created and committed into the corresponding WordPress SVN branch. --------- diff --git a/docs/designers-developers/developers/tutorials/format-api/1-register-format.md b/docs/designers-developers/developers/tutorials/format-api/1-register-format.md index 667a63206295e..6a18806f6a48a 100644 --- a/docs/designers-developers/developers/tutorials/format-api/1-register-format.md +++ b/docs/designers-developers/developers/tutorials/format-api/1-register-format.md @@ -39,9 +39,9 @@ Then add a new file named `my-custom-format.js` with the following contents: } )( window.wp ); ``` -Make that plugin available in your WordPress setup and activate it. Then, load a new page/post. +Make that plugin available in your WordPress setup and activate it. Then, load a new page/post. -The list of available format types is maintained in the `core/rich-text` store. You can query the store to check that your custom format is now avaliable. To do so, run this code in your browser's console: +The list of available format types is maintained in the `core/rich-text` store. You can query the store to check that your custom format is now available. To do so, run this code in your browser's console: wp.data.select( 'core/rich-text' ).getFormatTypes(); diff --git a/docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md b/docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md index c6e72011f1782..53b776b562b41 100644 --- a/docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md +++ b/docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md @@ -1,6 +1,6 @@ # Add a Button to the Toolbar -Now that the format is avaible, the next step is to surface it to the UI. You can make use of the [`RichTextToolbarButton`](/packages/editor/src/components/rich-text/README.md#RichTextToolbarButton) component to extend the format toolbar. +Now that the format is available, the next step is to surface it to the UI. You can make use of the [`RichTextToolbarButton`](/packages/editor/src/components/rich-text/README.md#RichTextToolbarButton) component to extend the format toolbar. Paste this code in `my-custom-format.js`: @@ -68,7 +68,7 @@ The following sample code renders the previously shown button only on paragraph return ( props.selectedBlock && props.selectedBlock.name === 'core/paragraph' - ); + ); } ) )( MyCustomButton ); diff --git a/docs/designers-developers/developers/tutorials/javascript/loading-javascript.md b/docs/designers-developers/developers/tutorials/javascript/loading-javascript.md index c3805f26845cc..f48775df5782d 100644 --- a/docs/designers-developers/developers/tutorials/javascript/loading-javascript.md +++ b/docs/designers-developers/developers/tutorials/javascript/loading-javascript.md @@ -30,7 +30,7 @@ If your code is registered and enqueued correctly, you should see a message in y ![Console Log Message Success](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/assets/js-tutorial-console-log-success.png) -**Note for Theme Developers:** The above method of enqueing is used for plugins. If you are extending the block editor for your theme there is a minor difference, you will use the `get_template_directory_uri()` function instead of `plugins_url()`. So for a theme, the enqueue example is: +**Note for Theme Developers:** The above method of enqueuing is used for plugins. If you are extending the block editor for your theme there is a minor difference, you will use the `get_template_directory_uri()` function instead of `plugins_url()`. So for a theme, the enqueue example is: ```php function myguten_enqueue() { diff --git a/docs/designers-developers/developers/tutorials/javascript/scope-your-code.md b/docs/designers-developers/developers/tutorials/javascript/scope-your-code.md index 5d78b928ae603..802e4872cf8d5 100644 --- a/docs/designers-developers/developers/tutorials/javascript/scope-your-code.md +++ b/docs/designers-developers/developers/tutorials/javascript/scope-your-code.md @@ -28,7 +28,7 @@ This behavior can be problematic, and is the reason we need to scope the code. B ## Scoping Code Within a Function -In JavaScript, you can scope your code by writing it within a function. Functions have "local scope", or a scope that is specific only to that function. Aditionally, in JavaScript you can write anonymous functions, functions without a name, which will also prevent your function name from being overridden in the global scope. +In JavaScript, you can scope your code by writing it within a function. Functions have "local scope", or a scope that is specific only to that function. Additionally, in JavaScript you can write anonymous functions, functions without a name, which will also prevent your function name from being overridden in the global scope. Taking advantage of these two JavaScript features, `first.js` could be scoped as: From 542151de7a2db693e63958335a57254127474e86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <iseulde@automattic.com> Date: Fri, 15 Mar 2019 15:07:31 +0100 Subject: [PATCH 665/691] Input Interaction: only consider selection at edge if directed towards it (#14450) * Input Interaction: only consider selection at edge if directed towards it * Add e2e test --- packages/dom/src/dom.js | 11 +++++++++++ .../multi-block-selection.test.js.snap | 6 ++++++ .../e2e-tests/specs/multi-block-selection.test.js | 15 +++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/packages/dom/src/dom.js b/packages/dom/src/dom.js index 75f2d10311345..4ea9ad8c0ef78 100644 --- a/packages/dom/src/dom.js +++ b/packages/dom/src/dom.js @@ -169,7 +169,18 @@ export function isVerticalEdge( container, isReverse ) { } const selection = window.getSelection(); + + // Only consider the selection at the edge if the direction is towards the + // edge. + if ( + ! selection.isCollapsed && + isSelectionForward( selection ) === isReverse + ) { + return false; + } + const range = selection.rangeCount ? selection.getRangeAt( 0 ) : null; + if ( ! range ) { return false; } diff --git a/packages/e2e-tests/specs/__snapshots__/multi-block-selection.test.js.snap b/packages/e2e-tests/specs/__snapshots__/multi-block-selection.test.js.snap index 3634f485a83ee..dad6ac487081b 100644 --- a/packages/e2e-tests/specs/__snapshots__/multi-block-selection.test.js.snap +++ b/packages/e2e-tests/specs/__snapshots__/multi-block-selection.test.js.snap @@ -9,3 +9,9 @@ exports[`Multi-block selection should only trigger multi-selection when at the e <p></p> <!-- /wp:paragraph -->" `; + +exports[`Multi-block selection should use selection direction to determine vertical edge 1`] = ` +"<!-- wp:paragraph --> +<p>1<br>2.</p> +<!-- /wp:paragraph -->" +`; diff --git a/packages/e2e-tests/specs/multi-block-selection.test.js b/packages/e2e-tests/specs/multi-block-selection.test.js index 5a83abc74036d..b65a7afa49b64 100644 --- a/packages/e2e-tests/specs/multi-block-selection.test.js +++ b/packages/e2e-tests/specs/multi-block-selection.test.js @@ -165,4 +165,19 @@ describe( 'Multi-block selection', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + + it( 'should use selection direction to determine vertical edge', async () => { + await clickBlockAppender(); + await page.keyboard.type( '1' ); + await pressKeyWithModifier( 'shift', 'Enter' ); + await page.keyboard.type( '2' ); + + await pressKeyWithModifier( 'shift', 'ArrowUp' ); + await pressKeyWithModifier( 'shift', 'ArrowDown' ); + + // Should type at the end of the paragraph. + await page.keyboard.type( '.' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); } ); From 7723b06b34a9c9f2ebc72eae863724c2895b54bd Mon Sep 17 00:00:00 2001 From: Dave Whitley <drw158@gmail.com> Date: Fri, 15 Mar 2019 09:37:27 -0500 Subject: [PATCH 666/691] Components: update CheckboxControl readme (#14153) * Initial commit to update CheckboxControl readme These changes add design documentation. * Fix tabbing on code example --- .../components/src/checkbox-control/README.md | 70 ++++++++++++++++--- 1 file changed, 61 insertions(+), 9 deletions(-) diff --git a/packages/components/src/checkbox-control/README.md b/packages/components/src/checkbox-control/README.md index 2538fda6b3dd0..91b50a09b2d97 100644 --- a/packages/components/src/checkbox-control/README.md +++ b/packages/components/src/checkbox-control/README.md @@ -1,9 +1,57 @@ # CheckboxControl -CheckboxControl component is used to generate a checkbox input field. +Checkboxes allow the user to select one or more items from a set. +![](https://make.wordpress.org/design/files/2019/02/CheckboxControl.png) -## Usage +Selected and unselected checkboxes + +## Table of contents + +1. [Design guidelines](http://#design-guidelines) +2. [Development guidelines](http://#development-guidelines) +3. [Related components](http://#related-components) + +## Design guidelines + +### Usage + +#### When to use checkboxes + +Use checkboxes when you want users to: + +- Select one or multiple items from a list. +- Open a list containing sub-selections. + +![](https://make.wordpress.org/design/files/2019/02/select-from-list.png) + +**Do** +Use checkboxes when users can select multiple items from a list. They let users select more than one item. + +![](https://make.wordpress.org/design/files/2019/02/many-form-toggles.png) + +**Don’t** +Don’t use toggles when a list consists of multiple options. Use checkboxes — they take up less space. + +![](https://make.wordpress.org/design/files/2019/02/checkbox-sublist.gif) + +Checkboxes can be used to open a list containing sub-selections. + +#### Parent and child checkboxes + +Checkboxes can have a parent-child relationship, with secondary options nested under primary options. + +![](https://make.wordpress.org/design/files/2019/02/checkbox-parent.gif) + +When the parent checkbox is *checked*, all the child checkboxes are checked. When a parent checkbox is *unchecked*, all the child checkboxes are unchecked. + +![](https://make.wordpress.org/design/files/2019/02/mixed-checkbox.png) + +If only a few child checkboxes are checked, the parent checkbox becomes a mixed checkbox. + +## Development guidelines + +### Usage Render an is author checkbox: ```jsx @@ -23,20 +71,19 @@ const MyCheckboxControl = withState( { ) ); ``` -## Props +### Props The set of props accepted by the component will be specified below. Props not included in this set will be applied to the input element. -### heading +#### heading A heading for the input field, that appears above the checkbox. If the prop is not passed no heading will be rendered. - Type: `String` - Required: No - -### label +#### label A label for the input field, that appears at the side of the checkbox. The prop will be rendered as content a label element. @@ -45,14 +92,14 @@ If no prop is passed an empty label is rendered. - Type: `String` - Required: No -### help +#### help If this property is added, a help text will be generated using help property as the content. - Type: `String` - Required: No -### checked +#### checked If checked is true the checkbox will be checked. If checked is false the checkbox will be unchecked. If no value is passed the checkbox will be unchecked. @@ -60,9 +107,14 @@ If no value is passed the checkbox will be unchecked. - Type: `Boolean` - Required: No -### onChange +#### onChange A function that receives the checked state (boolean) as input. - Type: `function` - Required: Yes + +## Related components + +- To select one option from a set, and you want to show all the available options at once, use the `RadioControl` component. +- To toggle a single setting on or off, use the `FormToggle` component. From 08bf9e1adaef7c804c4715978e79cd4af2ac8f98 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell@kjellr.com> Date: Fri, 15 Mar 2019 11:27:19 -0400 Subject: [PATCH 667/691] Use a left border for hover + selection states (#14145) * Add thick borders to the left of blocks when they're hovred + selected. * Add thick left border to the page title. * Turn off block toolbar centering for alignwide blocks. This splits the left border in two, which looks a bit weird. * Move block breadcrump to the left side, position it on top of the block. * Clean up the block toolbar's left border. * Use inset borders on mobile. * Prevent inset borders from overlapping with full-bleed content. * Use a gray border instead of a blue one on hover. * Use a sass variable to define the left block border width * Fix breadcrumb potision for alignfull blocks. * Clean up breadcrumb position for left & right-aligned blocks. * Sync block mover animation up with the hover state. * Darken focused block borders slightly. From $light-gray-500 to $light-gray-800. * Switch to using border instead of outline for block borders. Also, change the thick left border to a solid color, to prevent weird overlap. * Make this work better with Windows High Contrast Mode * Adjust z-index of border + breadcrumb for child blocks. So that they're not overlapped by the parent block's border + toolbar. * Remove extra z-index rule from the block border. Turns out this wasn't needed anyway. * Remove extra z-index rule from the block border. Minor description cleanup. * Ensure these styles are compatible with Top Toolbar mode. * Use the new gray value for the mobile toolbar border. * Add a matching left border to the post permalink area above the title. * Improve border position for mobile screens, especially for elements that float left/right. * Remove a couple unnecessary border updates from 047e1e4. Turns out these styles can be preserved on all screen sizes with no ill effect. * Clean up bugs related to the hover + focus states of the classic editor block. * Classic Block toolbar icon cleanup. Even out margins, remove white background. * Reusable Block border cleanup. * Keeping a light border on the classic block when it's inactive. * Clean up borders on warning blocks. * Switch to a solid color border color for the permalink box. This mirrors the approach we use for block toolbars, and also ensures that we don't layer opacities and make the permalink toolbar darker than intended. * Update z-index rule name to match the one used in the latest merge. * Combine full-wide toolbar centering rules. Previously, these were declared in two separate palces. * Add a darker hover state for dark themes. * Remove the left toolbar border on mobile screens. This fixes some visual bugs with themes like TwentyNineteen, which include margins on either side of the block on mobile. --- assets/stylesheets/_variables.scss | 1 + assets/stylesheets/_z-index.scss | 1 + .../src/components/block-list/style.scss | 155 +++++++++++++----- .../src/components/block-toolbar/style.scss | 32 +++- .../src/components/warning/style.scss | 10 ++ .../src/block/edit-panel/style.scss | 14 ++ .../src/block/indicator/style.scss | 3 +- .../block-library/src/classic/editor.scss | 25 ++- .../src/components/visual-editor/style.scss | 14 +- .../src/components/post-permalink/style.scss | 16 +- .../src/components/post-title/style.scss | 27 ++- 11 files changed, 228 insertions(+), 70 deletions(-) diff --git a/assets/stylesheets/_variables.scss b/assets/stylesheets/_variables.scss index 7fda170813add..1c30d4f053d66 100644 --- a/assets/stylesheets/_variables.scss +++ b/assets/stylesheets/_variables.scss @@ -52,6 +52,7 @@ $resize-handler-size: 16px; $resize-handler-container-size: $resize-handler-size + ($grid-size-small * 2); // Make the resize handle container larger so there's a larger grabbable area. // Blocks +$block-left-border-width: $border-width * 3; $block-padding: 14px; // Space between block footprint and focus boundaries. These are drawn outside the block footprint, and do not affect the size. $block-spacing: 4px; // Vertical space between blocks. $block-side-ui-width: 28px; // Width of the movers/drag handle UI. diff --git a/assets/stylesheets/_z-index.scss b/assets/stylesheets/_z-index.scss index 6b1a15b95ec7b..86ab07f3e28ab 100644 --- a/assets/stylesheets/_z-index.scss +++ b/assets/stylesheets/_z-index.scss @@ -9,6 +9,7 @@ $z-layers: ( ".block-library-classic__toolbar": 10, ".block-editor-block-list__layout .reusable-block-indicator": 1, ".block-editor-block-list__breadcrumb": 2, + ".editor-inner-blocks .block-editor-block-list__breadcrumb": 22, ".components-form-toggle__input": 1, ".components-panel__header.edit-post-sidebar__panel-tabs": -1, ".edit-post-sidebar .components-panel": -2, diff --git a/packages/block-editor/src/components/block-list/style.scss b/packages/block-editor/src/components/block-list/style.scss index bca7a3dc5f654..4a72cb3ed3423 100644 --- a/packages/block-editor/src/components/block-list/style.scss +++ b/packages/block-editor/src/components/block-list/style.scss @@ -11,7 +11,7 @@ // While block is being dragged, dim the slot dragged from, and hide some UI. &.is-dragging { .block-editor-block-list__block-edit::before { - outline: none; + border: none; } > .block-editor-block-list__block-edit > * { @@ -105,7 +105,7 @@ } /** - * Block outline layout + * Block border layout */ .block-editor-block-list__block-edit { @@ -115,10 +115,15 @@ z-index: z-index(".block-editor-block-list__block-edit::before"); content: ""; position: absolute; - outline: $border-width solid transparent; - transition: outline 0.1s linear; + border: $border-width solid transparent; + border-left: none; + box-shadow: none; + transition: border-color 0.1s linear, box-shadow 0.1s linear; pointer-events: none; + // Include a transparent outline for Windows High Contrast mode. + outline: $border-width solid transparent; + // Go edge-to-edge on mobile. right: -$block-padding; left: -$block-padding; @@ -128,18 +133,36 @@ } // Selected style. - &.is-selected > .block-editor-block-list__block-edit::before { - // Use opacity to work in various editor styles. - outline: $border-width solid $dark-opacity-light-500; + &.is-selected { - .is-dark-theme & { - outline-color: $light-opacity-light-500; + > .block-editor-block-list__block-edit::before { + // Use opacity to work in various editor styles. + border-color: $dark-opacity-light-800; + box-shadow: inset $block-left-border-width 0 0 0 $dark-gray-500; + + .is-dark-theme & { + border-color: $light-opacity-light-800; + box-shadow: inset $block-left-border-width 0 0 0 $light-gray-600; + } + + // Switch to outset borders on larger screens. + @include break-small() { + box-shadow: -$block-left-border-width 0 0 0 $dark-gray-500; + + .is-dark-theme & { + box-shadow: -$block-left-border-width 0 0 0 $light-gray-600; + } + } } } // Hover style. &.is-hovered > .block-editor-block-list__block-edit::before { - outline: $border-width solid theme(outlines); + box-shadow: -$block-left-border-width 0 0 0 $light-gray-500; + + .is-dark-theme & { + box-shadow: -$block-left-border-width 0 0 0 $dark-gray-600; + } } // Spotlight mode. @@ -213,12 +236,23 @@ } } - &.has-warning:not(.is-hovered) .block-editor-block-list__block-edit::before { + &.has-warning .block-editor-block-list__block-edit::before { + // Use opacity to work in various editor styles. + border-color: $dark-opacity-light-500; + border-left: $border-width solid $dark-opacity-light-500; + + .is-dark-theme & { + border-color: $light-opacity-light-600; + } + } + + &.has-warning.is-selected .editor-block-list__block-edit::before { // Use opacity to work in various editor styles. - outline-color: $dark-opacity-light-500; + border-color: $dark-opacity-light-800; + border-left-color: transparent; .is-dark-theme & { - outline-color: $light-opacity-light-500; + border-color: $light-opacity-light-800; } } @@ -260,11 +294,21 @@ // Reusable blocks &.is-reusable > .block-editor-block-list__block-edit::before { + border: $border-width dashed $dark-opacity-light-500; + + .is-dark-theme & { + border-color: $light-opacity-light-600; + } + } + + &.is-reusable.is-selected > .block-editor-block-list__block-edit::before { // Use opacity to work in various editor styles. - outline: $border-width dashed $dark-opacity-light-500; + border-color: $dark-opacity-light-800; + border-left-color: transparent; .is-dark-theme & { - outline-color: $light-opacity-light-500; + border-color: $light-opacity-light-800; + border-left-color: transparent; } } @@ -278,7 +322,7 @@ // When images are floated, the block itself should collapse to zero height. height: 0; - // Hide block outline when an image is floated. + // Hide block border when an image is floated. .block-editor-block-list__block-edit { &::before { content: none; @@ -296,7 +340,7 @@ // Position toolbar better on mobile. .block-editor-block-contextual-toolbar { width: auto; - border-bottom: $border-width solid $light-gray-500; + border-bottom: $border-width solid $light-gray-800; bottom: auto; } } @@ -424,9 +468,9 @@ // Full-wide &[data-align="full"] { - // Position hover label on the right for the top level block. + // Position hover label on the left for the top level block. > .block-editor-block-list__block-edit > .block-editor-block-list__breadcrumb { - right: 0; + left: 0; } // Compensate for main container padding and subtract border. @@ -595,13 +639,16 @@ margin-top: $block-toolbar-height; margin-right: -$block-padding; margin-left: -$block-padding; - border-top: $border-width solid $light-gray-500; + border-top: $border-width solid $light-gray-800; height: $block-toolbar-height; @include break-small() { display: none; } + // Add a white background to prevent the block's left border from showing through. + background-color: $white; + // Show a shadow below the selected block to imply separation. box-shadow: $shadow-below-only; @include break-small() { @@ -707,7 +754,7 @@ // Hide both the button until hovered. opacity: 0; - transition: opacity 0.1s linear 0.1s; + transition: opacity 0.1s linear; &:hover, &.is-visible { @@ -791,12 +838,12 @@ // Position toolbar below the block on mobile. position: absolute; - bottom: $block-toolbar-height - $block-padding; + bottom: $block-toolbar-height - $block-padding - $border-width; left: -$block-padding; right: -$block-padding; // Paint the borders on the toolbar itself on mobile. - border-top: $border-width solid $light-gray-500; + border-top: $border-width solid $light-gray-800; .components-toolbar { border-top: none; border-bottom: none; @@ -805,17 +852,31 @@ @include break-small() { border-top: none; .components-toolbar { - border-top: $border-width solid $light-gray-500; - border-bottom: $border-width solid $light-gray-500; + border-top: $border-width solid $light-gray-800; + border-bottom: $border-width solid $light-gray-800; } } } - // Floated items have special needs for the contextual toolbar position. + // Floated items have special needs for the contextual toolbar position + the thicker left border. &[data-align="left"] .block-editor-block-contextual-toolbar, &[data-align="right"] .block-editor-block-contextual-toolbar { margin-bottom: $border-width; margin-top: -$block-toolbar-height; + + // Display the box-shadow on the parent element. + box-shadow: -$block-left-border-width 0 0 0 $dark-gray-500; + .is-dark-theme & { + box-shadow: -$block-left-border-width 0 0 0 $light-gray-600; + } + + @include break-small() { + box-shadow: none; + } + + .editor-block-toolbar { + border-left: none; + } } // Make block toolbar full width on mobile. @@ -903,7 +964,8 @@ // This prevents floats from messing up the position of the block toolbar on floats-adjacent blocks when selected. position: absolute; - left: 0; + left: $border-width; + top: $border-width; } } @@ -917,20 +979,24 @@ line-height: 1; z-index: z-index(".block-editor-block-list__breadcrumb"); - // Position in the top right of the border. - right: -$block-padding; - top: -$block-padding - $border-width; + // Position in the top left of the border. + left: -$block-padding - $block-left-border-width; + top: (($block-padding * -2) - $block-left-border-width); .components-toolbar { padding: 0; border: none; - background: transparent; line-height: 1; font-family: $default-font; font-size: 11px; padding: 4px 4px; - background: theme(outlines); - color: $white; + background: $light-gray-500; + color: $dark-gray-900; + + .is-dark-theme & { + background: $dark-gray-600; + color: $white; + } // Animate in .block-editor-block-list__block:hover & { @@ -939,11 +1005,20 @@ } } - // Position the breadcrumb closer on mobile. - [data-align="left"] &, + // Position this above the toolbar of parent blocks. + .editor-inner-blocks & { + z-index: z-index(".editor-inner-blocks .block-editor-block-list__breadcrumb"); + } + + // Remove negative left breadcrumb position for left aligned blocks. + [data-align="left"] & { + left: 0; + } + + // Right-align the breadcrumb for right-aligned blocks. [data-align="right"] & { + left: auto; right: 0; - top: 0; } } @@ -992,12 +1067,12 @@ .block-editor-block-list__block .block-editor-warning { z-index: z-index(".block-editor-warning"); position: relative; - margin-right: -$block-padding - $border-width; - margin-left: -$block-padding - $border-width; + margin-right: -$block-padding; + margin-left: -$block-padding; // Pull the warning upwards to the edge, and add a negative bottom margin to compensate. - margin-bottom: -$block-padding - $border-width; - transform: translateY(-$block-padding - $border-width); + margin-bottom: -$block-padding; + transform: translateY(-$block-padding); // Bigger padding on mobile where blocks are edge to edge. padding: 10px $block-padding; diff --git a/packages/block-editor/src/components/block-toolbar/style.scss b/packages/block-editor/src/components/block-toolbar/style.scss index 4b426de6f11d6..e0fbc6601ee36 100644 --- a/packages/block-editor/src/components/block-toolbar/style.scss +++ b/packages/block-editor/src/components/block-toolbar/style.scss @@ -4,22 +4,40 @@ width: 100%; overflow: auto; // Allow horizontal scrolling on mobile. position: relative; + transition: border-color 0.1s linear, box-shadow 0.1s linear; + border-left: $border-width solid $light-gray-800; - // Allow overflow on desktop. @include break-small() { + // Allow overflow on desktop. overflow: inherit; - } - // Show a left border on the parent container. - border-left: $border-width solid $light-gray-500; + // Show a left border on the parent container. + border-left: none; + box-shadow: -$block-left-border-width 0 0 0 $dark-gray-500; + + // Show a lighter version for dark themes. + .is-dark-theme & { + box-shadow: -$block-left-border-width 0 0 0 $light-gray-600; + } + } // The component is born with a border, but we only need some of them. .components-toolbar { border: 0; - border-top: $border-width solid $light-gray-500; - border-bottom: $border-width solid $light-gray-500; + border-top: $border-width solid $light-gray-800; + border-bottom: $border-width solid $light-gray-800; // Add a right border to show as separator in the block toolbar. - border-right: $border-width solid $light-gray-500; + border-right: $border-width solid $light-gray-800; + } + + // Add a left border and adjust the color for Top Toolbar mode. + .has-fixed-toolbar & { + box-shadow: none; + border-left: $border-width solid $light-gray-500; + + .components-toolbar { + border-color: $light-gray-500; + } } } diff --git a/packages/block-editor/src/components/warning/style.scss b/packages/block-editor/src/components/warning/style.scss index 27f571d6a7a77..6af2f5e9a7704 100644 --- a/packages/block-editor/src/components/warning/style.scss +++ b/packages/block-editor/src/components/warning/style.scss @@ -13,6 +13,16 @@ background-color: transparent; } + .is-selected & { + // Use opacity to work in various editor styles. + border-color: $dark-opacity-light-800; + border-left-color: transparent; + + .is-dark-theme & { + border-color: $light-opacity-light-800; + } + } + .block-editor-warning__message { line-height: $default-line-height; font-family: $default-font; diff --git a/packages/block-library/src/block/edit-panel/style.scss b/packages/block-library/src/block/edit-panel/style.scss index 0b899669c08f2..bb22417472a37 100644 --- a/packages/block-library/src/block/edit-panel/style.scss +++ b/packages/block-library/src/block/edit-panel/style.scss @@ -12,6 +12,10 @@ padding: $grid-size $block-padding; position: relative; + // Use opacity to work in various editor styles. + border: $border-width dashed $dark-opacity-light-500; + border-bottom: none; + // Show a smaller padding when nested. .block-editor-block-list__layout & { margin: 0 (-$block-padding); @@ -58,3 +62,13 @@ } } } + +.editor-block-list__layout .is-selected .reusable-block-edit-panel { + border-color: $dark-opacity-light-800; + border-left-color: transparent; + + .is-dark-theme & { + border-color: $light-opacity-light-800; + border-left-color: transparent; + } +} diff --git a/packages/block-library/src/block/indicator/style.scss b/packages/block-library/src/block/indicator/style.scss index 2e02c866c3d88..08ebbce4674fa 100644 --- a/packages/block-library/src/block/indicator/style.scss +++ b/packages/block-library/src/block/indicator/style.scss @@ -1,8 +1,7 @@ .block-editor-block-list__layout .reusable-block-indicator { background: $white; - border-left: $border-width dashed $light-gray-500; + border: $border-width dashed $light-gray-500; color: $dark-gray-500; - border-bottom: $border-width dashed $light-gray-500; top: -$block-padding; height: 30px; padding: $grid-size-small; diff --git a/packages/block-library/src/classic/editor.scss b/packages/block-library/src/classic/editor.scss index 2bb0bd4f77a7f..758609eb9d549 100644 --- a/packages/block-library/src/classic/editor.scss +++ b/packages/block-library/src/classic/editor.scss @@ -207,7 +207,16 @@ } .block-editor-block-list__block-edit::before { - outline: $border-width solid #e2e4e7; + transition: border-color 0.1s linear, box-shadow 0.1s linear; + border: $border-width solid $light-gray-500; + + // Windows High Contrast mode will show this outline. + outline: $border-width solid transparent; + } + + &.is-selected .block-editor-block-list__block-edit::before { + border-color: $light-gray-800; + border-left-color: transparent; } // Don't show block type label for classic block @@ -228,10 +237,17 @@ div[data-type="core/freeform"] .block-editor-block-contextual-toolbar + div { z-index: z-index(".block-library-classic__toolbar"); top: $block-padding; transform: translateY(-$block-padding); + border: $border-width solid $light-gray-500; + border-bottom: none; // On mobile, toolbars go edge to edge. padding: 0 $block-padding; + .is-selected & { + border-color: $light-gray-800; + border-left-color: transparent; + } + @include break-small() { padding: 0; } @@ -289,12 +305,13 @@ div[data-type="core/freeform"] .block-editor-block-contextual-toolbar + div { } .block-editor-block-contextual-toolbar { float: right; - margin-right: $icon-button-size - $block-padding + $border-width; + margin-right: $icon-button-size - $block-padding + ($border-width * 3); transform: translateY(-#{ $block-padding - $border-width }); top: $block-padding; .block-editor-block-toolbar { border: none; + box-shadow: none; // Match the TinyMCE "mobile" breakpoint buttons alignment. margin-top: 3px; @@ -309,6 +326,10 @@ div[data-type="core/freeform"] .block-editor-block-contextual-toolbar + div { margin-top: $grid-size-small; margin-bottom: $grid-size-small; } + + .components-toolbar__control.components-button:hover { + background-color: transparent; + } } .components-toolbar { diff --git a/packages/edit-post/src/components/visual-editor/style.scss b/packages/edit-post/src/components/visual-editor/style.scss index aa3ee7cbc7995..1ba9ce38e0113 100644 --- a/packages/edit-post/src/components/visual-editor/style.scss +++ b/packages/edit-post/src/components/visual-editor/style.scss @@ -27,12 +27,13 @@ margin-right: -$block-side-ui-width; } - // Center the block toolbar on wide and full-wide blocks. + // Center the block toolbar on full-wide blocks. // Use specific selector to not affect nested block toolbars. - &[data-align="wide"] > .block-editor-block-list__block-edit > .block-editor-block-contextual-toolbar, &[data-align="full"] > .block-editor-block-list__block-edit > .block-editor-block-contextual-toolbar { - width: calc(100% + #{ $block-padding * 2 + $border-width * 2 }); // Matches the negative margins applied to parent blocks. height: 0; // This collapses the container to an invisible element without margin. + width: 100%; + margin-left: 0; + margin-right: 0; text-align: center; // This float rule takes the toolbar out of the flow, without it having to be absolute positioned. @@ -49,13 +50,6 @@ position: relative; } } - - // The centering math is simpler for a fullwide block, which doesn't have any block padding. - &[data-align="full"] > .block-editor-block-list__block-edit > .editor-block-contextual-toolbar { - width: 100%; - margin-left: 0; - margin-right: 0; - } } } diff --git a/packages/editor/src/components/post-permalink/style.scss b/packages/editor/src/components/post-permalink/style.scss index c35abcb009dfc..5b13e75843994 100644 --- a/packages/editor/src/components/post-permalink/style.scss +++ b/packages/editor/src/components/post-permalink/style.scss @@ -7,11 +7,21 @@ font-size: $default-font-size; height: 40px; white-space: nowrap; - - // Use opacity to work in various editor styles. - border: $border-width solid $dark-opacity-light-500; + border: $border-width solid $light-gray-800; background-clip: padding-box; + // Show a thick left border to match the left border on the title field. + border-left: 0; + box-shadow: -$block-left-border-width 0 0 0 $dark-gray-500; + + // Use a lighter border for dark themes. + .is-dark-theme & { + box-shadow: -$block-left-border-width 0 0 0 $light-gray-600; + } + + // Include transparent outline for Windows High Contrast mode. + outline: $border-width solid transparent; + // Put toolbar snugly to edge on mobile. margin-left: -$block-padding - $border-width; // This hides the border off the edge of the screen. margin-right: -$block-padding - $border-width; diff --git a/packages/editor/src/components/post-title/style.scss b/packages/editor/src/components/post-title/style.scss index 71fc698dea202..a065097cd6465 100644 --- a/packages/editor/src/components/post-title/style.scss +++ b/packages/editor/src/components/post-title/style.scss @@ -16,7 +16,7 @@ font-family: $editor-font; line-height: $default-line-height; color: $dark-gray-900; - transition: border 0.1s ease-out; + transition: border 0.1s ease-out, box-shadow 0.1s linear; padding: #{ $block-padding + 5px } $block-padding; word-break: keep-all; @@ -25,8 +25,12 @@ border-left-width: 0; border-right-width: 0; + // Include transparent outline for Windows High Contrast mode. + outline: $border-width solid transparent; + @include break-small() { border-width: $border-width; + border-left-width: 0; } // Match h1 heading. @@ -49,18 +53,29 @@ &:not(.is-focus-mode) { &.is-selected .editor-post-title__input { - // use opacity to work in various editor styles - border-color: $dark-opacity-light-500; + // use opacity to work in various editor styles. + border-color: $dark-opacity-light-800; + box-shadow: inset $block-left-border-width 0 0 0 $dark-gray-500; .is-dark-theme & { - border-color: $light-opacity-light-500; + border-color: $light-opacity-light-800; + box-shadow: inset $block-left-border-width 0 0 0 $light-gray-600; + } + + // Switch to outset borders on larger screens. + @include break-small() { + box-shadow: -$block-left-border-width 0 0 0 $dark-gray-500; + + .is-dark-theme & { + box-shadow: -$block-left-border-width 0 0 0 $light-gray-600; + } } } } - &:not(.is-focus-mode):not(.has-fixed-toolbar) { + &:not(.is-focus-mode):not(.has-fixed-toolbar):not(.is-selected) { .editor-post-title__input:hover { - border-color: theme(outlines); + box-shadow: -$block-left-border-width 0 0 0 $light-gray-500; } } From 39bd834528fa74382745bd14476f7dd6dba42e81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <iseulde@automattic.com> Date: Fri, 15 Mar 2019 16:36:15 +0100 Subject: [PATCH 668/691] Input Interaction: allow outer vertical edge to be selected (#14453) * Input Interaction: allow outer vertical edge to be selected * Add e2e test --- .../src/components/writing-flow/index.js | 33 ++++++++++++++----- .../multi-block-selection.test.js.snap | 6 ++++ .../specs/multi-block-selection.test.js | 10 ++++++ 3 files changed, 41 insertions(+), 8 deletions(-) diff --git a/packages/block-editor/src/components/writing-flow/index.js b/packages/block-editor/src/components/writing-flow/index.js index 92b97badc11c3..d03b11d0a84f7 100644 --- a/packages/block-editor/src/components/writing-flow/index.js +++ b/packages/block-editor/src/components/writing-flow/index.js @@ -220,7 +220,13 @@ class WritingFlow extends Component { } onKeyDown( event ) { - const { hasMultiSelection, onMultiSelect, blocks } = this.props; + const { + hasMultiSelection, + onMultiSelect, + blocks, + selectionBeforeEndClientId, + selectionAfterEndClientId, + } = this.props; const { keyCode, target } = event; const isUp = keyCode === UP; @@ -281,13 +287,24 @@ class WritingFlow extends Component { this.verticalRect = computeCaretRect( target ); } - if ( isShift && ( hasMultiSelection || ( - this.isTabbableEdge( target, isReverse ) && - isNavEdge( target, isReverse ) - ) ) ) { - // Shift key is down, and there is multi selection or we're at the end of the current block. - this.expandSelection( isReverse ); - event.preventDefault(); + if ( isShift ) { + if ( + ( + // Ensure that there is a target block. + ( isReverse && selectionBeforeEndClientId ) || + ( ! isReverse && selectionAfterEndClientId ) + ) && ( + hasMultiSelection || ( + this.isTabbableEdge( target, isReverse ) && + isNavEdge( target, isReverse ) + ) + ) + ) { + // Shift key is down, and there is multi selection or we're at + // the end of the current block. + this.expandSelection( isReverse ); + event.preventDefault(); + } } else if ( hasMultiSelection ) { // Moving from block multi-selection to single block selection this.moveSelection( isReverse ); diff --git a/packages/e2e-tests/specs/__snapshots__/multi-block-selection.test.js.snap b/packages/e2e-tests/specs/__snapshots__/multi-block-selection.test.js.snap index dad6ac487081b..6986ffc94968b 100644 --- a/packages/e2e-tests/specs/__snapshots__/multi-block-selection.test.js.snap +++ b/packages/e2e-tests/specs/__snapshots__/multi-block-selection.test.js.snap @@ -1,5 +1,11 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Multi-block selection should allow selecting outer edge if there is no sibling block 1`] = ` +"<!-- wp:paragraph --> +<p>2</p> +<!-- /wp:paragraph -->" +`; + exports[`Multi-block selection should only trigger multi-selection when at the end 1`] = ` "<!-- wp:paragraph --> <p>1.</p> diff --git a/packages/e2e-tests/specs/multi-block-selection.test.js b/packages/e2e-tests/specs/multi-block-selection.test.js index b65a7afa49b64..76ea211367660 100644 --- a/packages/e2e-tests/specs/multi-block-selection.test.js +++ b/packages/e2e-tests/specs/multi-block-selection.test.js @@ -180,4 +180,14 @@ describe( 'Multi-block selection', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + + it( 'should allow selecting outer edge if there is no sibling block', async () => { + await clickBlockAppender(); + await page.keyboard.type( '1' ); + await pressKeyWithModifier( 'shift', 'ArrowUp' ); + // This should replace the content. + await page.keyboard.type( '2' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); } ); From 7779964b5df81b1ea299cc4b2c06ea597e6ee5d1 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Fri, 15 Mar 2019 17:06:35 +0000 Subject: [PATCH 669/691] Skip test case packages/e2e-tests/specs/plugins/inner-blocks-allowed-blocks.test.js (#14458) --- .../specs/plugins/inner-blocks-allowed-blocks.test.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/e2e-tests/specs/plugins/inner-blocks-allowed-blocks.test.js b/packages/e2e-tests/specs/plugins/inner-blocks-allowed-blocks.test.js index a25f9a81af95c..78f08c0bb1279 100644 --- a/packages/e2e-tests/specs/plugins/inner-blocks-allowed-blocks.test.js +++ b/packages/e2e-tests/specs/plugins/inner-blocks-allowed-blocks.test.js @@ -11,7 +11,12 @@ import { openGlobalBlockInserter, } from '@wordpress/e2e-test-utils'; -describe( 'Allowed Blocks Setting on InnerBlocks ', () => { +// Todo: Understand why this test causing intermitent fails on travis. +// Error: ● Allowed Blocks Setting on InnerBlocks › allows all blocks if the allowed blocks setting was not set +// Node is detached from document +// at ElementHandle._scrollIntoViewIfNeeded (../../node_modules/puppeteer/lib/ElementHandle.js:75:13) +// eslint-disable-next-line jest/no-disabled-tests +describe.skip( 'Allowed Blocks Setting on InnerBlocks ', () => { const paragraphSelector = '.block-editor-rich-text__editable.wp-block-paragraph'; beforeAll( async () => { await activatePlugin( 'gutenberg-test-innerblocks-allowed-blocks' ); From 1019c1705bfb64fecfa48a1de42913f53ce70945 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 15 Mar 2019 15:48:10 -0400 Subject: [PATCH 670/691] DOM: Limit single tabbable radio input by name (#14128) * DOM: Limit single tabbable radio input by name * DOM: Avoid consolidating unnamed radio inputs --- packages/dom/CHANGELOG.md | 1 + packages/dom/src/tabbable.js | 49 +++++++++++++++- packages/dom/src/test/tabbable.js | 96 +++++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+), 1 deletion(-) diff --git a/packages/dom/CHANGELOG.md b/packages/dom/CHANGELOG.md index eeca6c818528d..47785fe2827fa 100644 --- a/packages/dom/CHANGELOG.md +++ b/packages/dom/CHANGELOG.md @@ -3,6 +3,7 @@ ### Bug Fix - Update `isHorizontalEdge` to account for empty text nodes. +- `tabbables.find` considers at most a single radio input for a given name. The checked input is given priority, falling back to the first in the tabindex-sorted set if there is no checked input. ## 2.0.8 (2019-01-03) diff --git a/packages/dom/src/tabbable.js b/packages/dom/src/tabbable.js index e70cba256401d..315a98b8b2a8e 100644 --- a/packages/dom/src/tabbable.js +++ b/packages/dom/src/tabbable.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import { without } from 'lodash'; + /** * Internal dependencies */ @@ -31,6 +36,47 @@ export function isTabbableIndex( element ) { return getTabIndex( element ) !== -1; } +/** + * Returns a stateful reducer function which constructs a filtered array of + * tabbable elements, where at most one radio input is selected for a given + * name, giving priority to checked input, falling back to the first + * encountered. + * + * @return {Function} Radio group collapse reducer. + */ +function createStatefulCollapseRadioGroup() { + const CHOSEN_RADIO_BY_NAME = {}; + + return function collapseRadioGroup( result, element ) { + const { nodeName, type, checked, name } = element; + + // For all non-radio tabbables, construct to array by concatenating. + if ( nodeName !== 'INPUT' || type !== 'radio' || ! name ) { + return result.concat( element ); + } + + const hasChosen = CHOSEN_RADIO_BY_NAME.hasOwnProperty( name ); + + // Omit by skipping concatenation if the radio element is not chosen. + const isChosen = checked || ! hasChosen; + if ( ! isChosen ) { + return result; + } + + // At this point, if there had been a chosen element, the current + // element is checked and should take priority. Retroactively remove + // the element which had previously been considered the chosen one. + if ( hasChosen ) { + const hadChosenElement = CHOSEN_RADIO_BY_NAME[ name ]; + result = without( result, hadChosenElement ); + } + + CHOSEN_RADIO_BY_NAME[ name ] = element; + + return result.concat( element ); + }; +} + /** * An array map callback, returning an object with the element value and its * array index location as properties. This is used to emulate a proper stable @@ -84,5 +130,6 @@ export function find( context ) { .filter( isTabbableIndex ) .map( mapElementToObjectTabbable ) .sort( compareObjectTabbables ) - .map( mapObjectTabbableToElement ); + .map( mapObjectTabbableToElement ) + .reduce( createStatefulCollapseRadioGroup(), [] ); } diff --git a/packages/dom/src/test/tabbable.js b/packages/dom/src/test/tabbable.js index 992b9a052ff4e..e262fccd86d65 100644 --- a/packages/dom/src/test/tabbable.js +++ b/packages/dom/src/test/tabbable.js @@ -32,5 +32,101 @@ describe( 'tabbable', () => { third, ] ); } ); + + it( 'consolidates radio group to the first, if unchecked', () => { + const node = createElement( 'div' ); + const firstRadio = createElement( 'input' ); + firstRadio.type = 'radio'; + firstRadio.name = 'a'; + firstRadio.value = 'firstRadio'; + const secondRadio = createElement( 'input' ); + secondRadio.type = 'radio'; + secondRadio.name = 'a'; + secondRadio.value = 'secondRadio'; + const text = createElement( 'input' ); + text.type = 'text'; + text.name = 'b'; + const thirdRadio = createElement( 'input' ); + thirdRadio.type = 'radio'; + thirdRadio.name = 'a'; + thirdRadio.value = 'thirdRadio'; + const fourthRadio = createElement( 'input' ); + fourthRadio.type = 'radio'; + fourthRadio.name = 'b'; + fourthRadio.value = 'fourthRadio'; + const fifthRadio = createElement( 'input' ); + fifthRadio.type = 'radio'; + fifthRadio.name = 'b'; + fifthRadio.value = 'fifthRadio'; + node.appendChild( firstRadio ); + node.appendChild( secondRadio ); + node.appendChild( text ); + node.appendChild( thirdRadio ); + node.appendChild( fourthRadio ); + node.appendChild( fifthRadio ); + + const tabbables = find( node ); + + expect( tabbables ).toEqual( [ + firstRadio, + text, + fourthRadio, + ] ); + } ); + + it( 'consolidates radio group to the checked', () => { + const node = createElement( 'div' ); + const firstRadio = createElement( 'input' ); + firstRadio.type = 'radio'; + firstRadio.name = 'a'; + firstRadio.value = 'firstRadio'; + const secondRadio = createElement( 'input' ); + secondRadio.type = 'radio'; + secondRadio.name = 'a'; + secondRadio.value = 'secondRadio'; + const text = createElement( 'input' ); + text.type = 'text'; + text.name = 'b'; + const thirdRadio = createElement( 'input' ); + thirdRadio.type = 'radio'; + thirdRadio.name = 'a'; + thirdRadio.value = 'thirdRadio'; + thirdRadio.checked = true; + node.appendChild( firstRadio ); + node.appendChild( secondRadio ); + node.appendChild( text ); + node.appendChild( thirdRadio ); + + const tabbables = find( node ); + + expect( tabbables ).toEqual( [ + text, + thirdRadio, + ] ); + } ); + + it( 'not consolidate unnamed radio inputs', () => { + const node = createElement( 'div' ); + const firstRadio = createElement( 'input' ); + firstRadio.type = 'radio'; + firstRadio.value = 'firstRadio'; + const text = createElement( 'input' ); + text.type = 'text'; + text.name = 'b'; + const secondRadio = createElement( 'input' ); + secondRadio.type = 'radio'; + secondRadio.value = 'secondRadio'; + node.appendChild( firstRadio ); + node.appendChild( text ); + node.appendChild( secondRadio ); + + const tabbables = find( node ); + + expect( tabbables ).toEqual( [ + firstRadio, + text, + secondRadio, + ] ); + } ); } ); } ); From 09e72dff2e3202e88aa0ffdc77f6f72b21b50693 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell@kjellr.com> Date: Fri, 15 Mar 2019 16:06:00 -0400 Subject: [PATCH 671/691] Update MenuItemsChoice readme (#14465) Add design guidelines and screenshots. --- .../src/menu-items-choice/README.md | 55 ++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/packages/components/src/menu-items-choice/README.md b/packages/components/src/menu-items-choice/README.md index be4388376ebf3..cbd7fd9f9d663 100644 --- a/packages/components/src/menu-items-choice/README.md +++ b/packages/components/src/menu-items-choice/README.md @@ -1,6 +1,59 @@ # MenuItemsChoice -## Usage +`MenuItemsChoice` functions similarly to a set of `MenuItem`s, but allows the user to select one option from a set of multiple choices. + +![MenuItemsChoice Example](https://wordpress.org/gutenberg/files/2019/03/MenuItemsChoice.png) + +1. MenuItemsChoice + +## Table of contents + +1. [Design guidelines](#design-guidelines) +2. [Development guidelines](#development-guidelines) +3. [Related components](#related-components) + +## Design guidelines + +A `MenuItemsChoice` should be housed within in its own distinct `MenuGroup`, so that the set of options are distinct from nearby `MenuItems`. + +### Usage + +`MenuItemsChoice` is used in a `DropdownMenu` to present users with a set of options. When one option in a `MenuItemsChoice` is selected, the others are automatically deselected. + +![MenuItemsChoice Diagram](https://wordpress.org/gutenberg/files/2019/03/MenuItemsChoice-Checkbox-Keyboard-Shortcut.png) + +1. A checkmark icon appears next to the choice when it’s enabled, and disappears when disabled. +2. If an item in `MenuItemsChoice` has an associated keyboard shortcut, that should be displayed to the right of the menu title, aligned to the right side of the menu item. Selected choices should not have visible shortcuts, since they are already active. + +#### When to use `MenuItemsChoice` + +Use `MenuItemsChoice` when you want users to: + +- Select a single option from a set of choices in a menu. +- Expose all available options. + +`MenuItemsChoice` should not be used to toggle individual features on and off. For that, consider using a `FeatureToggle`. + +#### Defaults + +When using `MenuItemsChoice` , **one option should be selected by default** (i.e., when the page loads, in the case of a web application). + +**User control** + +Selecting an option by default communicates that the user is required to choose one in the set. + +**Expediting tasks** + +When one choice in a set of `MenuItemsChoice` is the most desirable or frequently selected, it’s helpful to select it by default. Doing this reduces the interaction cost and can save the user time and clicks. + +**The power of suggestion** + +Designs with a `MenuItemsChoice` option selected by default make a strong suggestion to the user. It can help them make the best decision and increase their confidence. (Use this guidance with caution, and only for good.) + + +## Development guidelines + +### Usage ```jsx import { MenuGroup, MenuItemsChoice } from '@wordpress/components'; From 6f7fc0990cb87b8f71ecfac4f1ee18a040fea027 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell@kjellr.com> Date: Fri, 15 Mar 2019 16:09:07 -0400 Subject: [PATCH 672/691] Add MenuGroup design documentation (#14466) * Update MenuGroup Readme. Add design guidelines and screenshots. * Adjust introduction to be more concise. --- packages/components/src/menu-group/README.md | 33 +++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/components/src/menu-group/README.md b/packages/components/src/menu-group/README.md index b40a902b01370..3a6c21791cae0 100644 --- a/packages/components/src/menu-group/README.md +++ b/packages/components/src/menu-group/README.md @@ -1,6 +1,31 @@ # MenuGroup -## Usage +`MenuGroup` wraps a series of related `MenuItem` components into a common section. + +![MenuGroup Example](https://wordpress.org/gutenberg/files/2019/03/MenuGroup.png) + +1. MenuGroup + +## Table of Contents + +1. [Design guidelines](#design-guidelines) +2. [Development guidelines](#development-guidelines) +3. [Related components](#related-components) + +## Design guidelines + +### Usage + +A `MenuGroup` should be used to indicate that two or more individual MenuItems are related. When other menu items exist above or below a `MenuGroup`, the group should have a divider line between it and the adjacent item. A MenuGroup can optionally include a label to describe its contents. + +![MenuGroup diagram with label and dividers](https://wordpress.org/gutenberg/files/2019/03/MenuGroup-Anatomy.png) + +1. MenuGroup label +2. MenuGroup dividers + +## Development guidelines + +### Usage ```jsx import { MenuGroup, MenuItem } from '@wordpress/components'; @@ -12,3 +37,9 @@ const MyMenuGroup = () => ( </MenuGroup> ); ``` + +## Related Components + +- `MenuGroup`s are intended to be used in a `DropDownMenu`. +- To use a single button in a menu, use `MenuItem`. +- To allow users to toggle between a set of menu options, use `MenuItemsChoice` inside of a `MenuGroup`. From 6a9665a157fea63554c3a63fc7c10968703ccc0d Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Sat, 16 Mar 2019 10:52:33 +0000 Subject: [PATCH 673/691] Fix: use checkValidity() to perform the validation in RangeControl (#14322) --- packages/block-library/src/columns/index.js | 1 + packages/block-library/src/cover/edit.js | 1 + packages/block-library/src/gallery/edit.js | 1 + .../block-library/src/latest-comments/edit.js | 1 + .../block-library/src/latest-posts/edit.js | 1 + packages/block-library/src/rss/edit.js | 3 + .../block-library/src/text-columns/index.js | 1 + packages/components/CHANGELOG.md | 10 ++ .../components/src/query-controls/index.js | 1 + .../components/src/range-control/index.js | 14 +-- .../src/range-control/test/index.js | 116 ++++++++++++++++-- 11 files changed, 130 insertions(+), 20 deletions(-) diff --git a/packages/block-library/src/columns/index.js b/packages/block-library/src/columns/index.js index 7d28794e8a776..eabd06e0e5ae2 100644 --- a/packages/block-library/src/columns/index.js +++ b/packages/block-library/src/columns/index.js @@ -172,6 +172,7 @@ export const settings = { } } min={ 2 } max={ 6 } + required /> </PanelBody> </InspectorControls> diff --git a/packages/block-library/src/cover/edit.js b/packages/block-library/src/cover/edit.js index 398e842d9eafb..bdf85c0874a75 100644 --- a/packages/block-library/src/cover/edit.js +++ b/packages/block-library/src/cover/edit.js @@ -214,6 +214,7 @@ class CoverEdit extends Component { min={ 0 } max={ 100 } step={ 10 } + required /> </PanelColorSettings> </PanelBody> diff --git a/packages/block-library/src/gallery/edit.js b/packages/block-library/src/gallery/edit.js index 868a6a083723d..ade7ba978264b 100644 --- a/packages/block-library/src/gallery/edit.js +++ b/packages/block-library/src/gallery/edit.js @@ -250,6 +250,7 @@ class GalleryEdit extends Component { onChange={ this.setColumnsNumber } min={ 1 } max={ Math.min( MAX_COLUMNS, images.length ) } + required /> } <ToggleControl label={ __( 'Crop Images' ) } diff --git a/packages/block-library/src/latest-comments/edit.js b/packages/block-library/src/latest-comments/edit.js index 1bc21e552939e..b87aecdabd7d1 100644 --- a/packages/block-library/src/latest-comments/edit.js +++ b/packages/block-library/src/latest-comments/edit.js @@ -85,6 +85,7 @@ class LatestComments extends Component { onChange={ this.setCommentsToShow } min={ MIN_COMMENTS } max={ MAX_COMMENTS } + required /> </PanelBody> </InspectorControls> diff --git a/packages/block-library/src/latest-posts/edit.js b/packages/block-library/src/latest-posts/edit.js index 2e0f00e32eea3..d9790466b492e 100644 --- a/packages/block-library/src/latest-posts/edit.js +++ b/packages/block-library/src/latest-posts/edit.js @@ -109,6 +109,7 @@ class LatestPostsEdit extends Component { onChange={ ( value ) => setAttributes( { columns: value } ) } min={ 2 } max={ ! hasPosts ? MAX_POSTS_COLUMNS : Math.min( MAX_POSTS_COLUMNS, latestPosts.length ) } + required /> } </PanelBody> diff --git a/packages/block-library/src/rss/edit.js b/packages/block-library/src/rss/edit.js index e7262e11b2bc1..9b98eaea53781 100644 --- a/packages/block-library/src/rss/edit.js +++ b/packages/block-library/src/rss/edit.js @@ -119,6 +119,7 @@ class RSSEdit extends Component { onChange={ ( value ) => setAttributes( { itemsToShow: value } ) } min={ DEFAULT_MIN_ITEMS } max={ DEFAULT_MAX_ITEMS } + required /> <ToggleControl label={ __( 'Display author' ) } @@ -142,6 +143,7 @@ class RSSEdit extends Component { onChange={ ( value ) => setAttributes( { excerptLength: value } ) } min={ 10 } max={ 100 } + required /> } { blockLayout === 'grid' && @@ -151,6 +153,7 @@ class RSSEdit extends Component { onChange={ ( value ) => setAttributes( { columns: value } ) } min={ 2 } max={ 6 } + required /> } </PanelBody> diff --git a/packages/block-library/src/text-columns/index.js b/packages/block-library/src/text-columns/index.js index 5bcf5cc3b6a79..f14b3b8153baa 100644 --- a/packages/block-library/src/text-columns/index.js +++ b/packages/block-library/src/text-columns/index.js @@ -114,6 +114,7 @@ export const settings = { onChange={ ( value ) => setAttributes( { columns: value } ) } min={ 2 } max={ 4 } + required /> </PanelBody> </InspectorControls> diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index b596c09199849..8bba28618e50d 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -1,3 +1,13 @@ +## 7.2.0 (Unreleased) + +### Improvements + +- Make `RangeControl` validation rely on the `checkValidity` provided by the browsers instead of using our own validation. + +### Bug Fixes + +- Fix a problem that made `RangeControl` not work as expected with float values. + ## 7.1.0 (2019-03-06) ### New Features diff --git a/packages/components/src/query-controls/index.js b/packages/components/src/query-controls/index.js index 4ed57e468eb69..f3cbf47998325 100644 --- a/packages/components/src/query-controls/index.js +++ b/packages/components/src/query-controls/index.js @@ -79,6 +79,7 @@ export default function QueryControls( { onChange={ onNumberOfItemsChange } min={ minItems } max={ maxItems } + required /> ), ]; diff --git a/packages/components/src/range-control/index.js b/packages/components/src/range-control/index.js index 1503813def5cb..2fd28d31bcae1 100644 --- a/packages/components/src/range-control/index.js +++ b/packages/components/src/range-control/index.js @@ -48,14 +48,9 @@ function RangeControl( { const onChangeValue = ( event ) => { const newValue = event.target.value; - const newNumericValue = parseInt( newValue, 10 ); // If the input value is invalid temporarily save it to the state, // without calling on change. - if ( - isNaN( newNumericValue ) || - ( min !== undefined && newNumericValue < min ) || - ( max !== undefined && newNumericValue > max ) - ) { + if ( ! event.target.checkValidity() ) { setState( { currentInput: newValue, } ); @@ -64,9 +59,12 @@ function RangeControl( { // The input is valid, reset the local state property used to temporaly save the value, // and call onChange with the new value as a number. resetCurrentInput(); - onChange( newNumericValue ); + onChange( ( newValue === '' ) ? + undefined : + parseFloat( newValue ) + ); }; - const initialSliderValue = isFinite( value ) ? + const initialSliderValue = isFinite( currentInputValue ) ? currentInputValue : initialPosition || ''; diff --git a/packages/components/src/range-control/test/index.js b/packages/components/src/range-control/test/index.js index 5f8e0bccb8aa8..e1308c06363e4 100644 --- a/packages/components/src/range-control/test/index.js +++ b/packages/components/src/range-control/test/index.js @@ -42,13 +42,23 @@ describe( 'RangeControl', () => { TestUtils.Simulate.change( rangeInputElement(), { - target: { value: '5' }, + target: { + value: '5', + checkValidity() { + return true; + }, + }, } ); TestUtils.Simulate.change( numberInputElement(), { - target: { value: '10' }, + target: { + value: '10', + checkValidity() { + return true; + }, + }, } ); @@ -96,7 +106,12 @@ describe( 'RangeControl', () => { TestUtils.Simulate.change( numberInputElement(), { - target: { value: '10' }, + target: { + value: '10', + checkValidity() { + return false; + }, + }, } ); @@ -116,7 +131,12 @@ describe( 'RangeControl', () => { TestUtils.Simulate.change( numberInputElement(), { - target: { value: '21' }, + target: { + value: '21', + checkValidity() { + return false; + }, + }, } ); @@ -136,14 +156,24 @@ describe( 'RangeControl', () => { TestUtils.Simulate.change( numberInputElement(), { - target: { value: '10' }, + target: { + value: '10', + checkValidity() { + return false; + }, + }, } ); TestUtils.Simulate.change( numberInputElement(), { - target: { value: '21' }, + target: { + value: '21', + checkValidity() { + return false; + }, + }, } ); @@ -152,7 +182,12 @@ describe( 'RangeControl', () => { TestUtils.Simulate.change( numberInputElement(), { - target: { value: '14' }, + target: { + value: '14', + checkValidity() { + return true; + }, + }, } ); @@ -171,7 +206,12 @@ describe( 'RangeControl', () => { TestUtils.Simulate.change( numberInputElement(), { - target: { value: '1' }, + target: { + value: '1', + checkValidity() { + return false; + }, + }, } ); @@ -190,7 +230,12 @@ describe( 'RangeControl', () => { TestUtils.Simulate.change( numberInputElement(), { - target: { value: '-101' }, + target: { + value: '-101', + checkValidity() { + return false; + }, + }, } ); @@ -199,7 +244,12 @@ describe( 'RangeControl', () => { TestUtils.Simulate.change( numberInputElement(), { - target: { value: '-49' }, + target: { + value: '-49', + checkValidity() { + return false; + }, + }, } ); @@ -208,11 +258,53 @@ describe( 'RangeControl', () => { TestUtils.Simulate.change( numberInputElement(), { - target: { value: '-50' }, + target: { + value: '-50', + checkValidity() { + return true; + }, + }, } ); - expect( onChange ).toHaveBeenCalled(); + expect( onChange ).toHaveBeenCalledWith( -50 ); + } ); + it( 'takes into account the step starting from min', () => { + const onChange = jest.fn(); + const wrapper = getWrapper( { onChange, min: 0.1, step: 0.125, value: 0.1 } ); + + const numberInputElement = () => TestUtils.findRenderedDOMComponentWithClass( + wrapper, + 'components-range-control__number' + ); + + TestUtils.Simulate.change( + numberInputElement(), + { + target: { + value: '0.125', + checkValidity() { + return false; + }, + }, + } + ); + + expect( onChange ).not.toHaveBeenCalled(); + + TestUtils.Simulate.change( + numberInputElement(), + { + target: { + value: '0.225', + checkValidity() { + return true; + }, + }, + } + ); + + expect( onChange ).toHaveBeenCalledWith( 0.225 ); } ); } ); } ); From f3148cc2b53efe30643705c76c1e1f5496318e99 Mon Sep 17 00:00:00 2001 From: Rudy Susanto <email@rsusanto.com> Date: Sat, 16 Mar 2019 20:00:11 +0700 Subject: [PATCH 674/691] Fix typo on hooks package readme. (#14471) --- packages/hooks/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/hooks/README.md b/packages/hooks/README.md index d839876d30155..be0148f3747a0 100644 --- a/packages/hooks/README.md +++ b/packages/hooks/README.md @@ -27,10 +27,10 @@ In the WordPress context, API functions can be called via the global `wp.hooks` ### API Usage * `createHooks()` -* `addAction( 'hookName', 'namespace', 'functionName', callback, priority )` -* `addFilter( 'hookName', 'namespace', 'functionName', callback, priority )` -* `removeAction( 'hookName', 'namespace', 'functionName' )` -* `removeFilter( 'hookName', 'namespace', 'functionName' )` +* `addAction( 'hookName', 'namespace', callback, priority )` +* `addFilter( 'hookName', 'namespace', callback, priority )` +* `removeAction( 'hookName', 'namespace' )` +* `removeFilter( 'hookName', 'namespace' )` * `removeAllActions( 'hookName' )` * `removeAllFilters( 'hookName' )` * `doAction( 'hookName', arg1, arg2, moreArgs, finalArg )` From 5b37ee70ef5a74c6c617828b30cd5e8dac8861da Mon Sep 17 00:00:00 2001 From: Andrea Fercia <a.fercia@gmail.com> Date: Sat, 16 Mar 2019 15:24:26 +0100 Subject: [PATCH 675/691] Better center tooltip content. (#14473) --- packages/components/src/tooltip/style.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/tooltip/style.scss b/packages/components/src/tooltip/style.scss index 06ce8a290fab3..79739693294c3 100644 --- a/packages/components/src/tooltip/style.scss +++ b/packages/components/src/tooltip/style.scss @@ -20,6 +20,7 @@ border-width: 0; color: $white; white-space: nowrap; + text-align: center; } .components-tooltip:not(.is-mobile) .components-popover__content { @@ -28,6 +29,5 @@ .components-tooltip__shortcut { display: block; - text-align: center; color: $dark-gray-200; } From 54a5f42ae1fde8a73d35d581c60f442a565fc9bc Mon Sep 17 00:00:00 2001 From: Mark Uraine <uraine@gmail.com> Date: Sun, 17 Mar 2019 23:37:45 -0700 Subject: [PATCH 676/691] Added language for block style to the tooltip on block transformations (#14470) --- packages/block-editor/src/components/block-switcher/index.js | 2 +- packages/e2e-tests/specs/style-variation.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/block-switcher/index.js b/packages/block-editor/src/components/block-switcher/index.js index 2d230c317612c..371975e53d088 100644 --- a/packages/block-editor/src/components/block-switcher/index.js +++ b/packages/block-editor/src/components/block-switcher/index.js @@ -95,7 +95,7 @@ export class BlockSwitcher extends Component { }; const label = ( 1 === blocks.length ? - __( 'Change block type' ) : + __( 'Change block type or style' ) : sprintf( _n( 'Change type of %d block', diff --git a/packages/e2e-tests/specs/style-variation.test.js b/packages/e2e-tests/specs/style-variation.test.js index db4c0d98fa5cb..7b073fe7b3f26 100644 --- a/packages/e2e-tests/specs/style-variation.test.js +++ b/packages/e2e-tests/specs/style-variation.test.js @@ -18,7 +18,7 @@ describe( 'adding blocks', () => { await insertBlock( 'Quote' ); await page.keyboard.type( 'Quote content' ); - await clickBlockToolbarButton( 'Change block type' ); + await clickBlockToolbarButton( 'Change block type or style' ); const styleVariations = await page.$$( '.block-editor-block-styles__item' ); await styleVariations[ 1 ].click(); From cafb041d6d8f0d592bde0bba1083ea4c32480728 Mon Sep 17 00:00:00 2001 From: Dennis Snell <dennis.snell@automattic.com> Date: Sun, 17 Mar 2019 23:44:41 -0700 Subject: [PATCH 677/691] Fix: Pasting captions without an image fails (#14365) * Fix: Pasting captions without an image fails Fixes #13527 See original work in #12315 When pasting content which includes the `[caption]` shortcode we assume that the content is well-formed (that there is not only an `<img />` in there but also in the first position). In this patch we fix the problem by removing the old code, which removed the first `Element` node, and replaced it with code that removes the first `IMG` node _if one is found_. We're leaving other image nodes in place in case the caption contains image nodes and we're not requiring that the `IMG` be the first child of the caption shortcode in case people are wrapping the image in other valid HTML like this... ```html [caption]<a href="some.link"><img src="some.image"></a>[/caption] ``` See the new unit tests for a more complete specification of the intended behaviors. PR reviewed, debugged, and created by: -> LANNISTER MOB <- - @codebykat - @dmsnell - @gwwar - @kwight - @mmtr - @obenland - @rodrigoi - @vindl * Update stripFirstImage behavior to also remove matching topmost parent node --- packages/block-library/src/image/index.js | 28 +++++++++++----- .../block-library/src/image/test/index.js | 32 +++++++++++++++++++ 2 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 packages/block-library/src/image/test/index.js diff --git a/packages/block-library/src/image/index.js b/packages/block-library/src/image/index.js index 92b9af7ec9a42..ace6b55f31636 100644 --- a/packages/block-library/src/image/index.js +++ b/packages/block-library/src/image/index.js @@ -123,6 +123,25 @@ function getFirstAnchorAttributeFormHTML( html, attributeName ) { } } +export function stripFirstImage( attributes, { shortcode } ) { + const { body } = document.implementation.createHTMLDocument( '' ); + + body.innerHTML = shortcode.content; + + let nodeToRemove = body.querySelector( 'img' ); + + // if an image has parents, find the topmost node to remove + while ( nodeToRemove && nodeToRemove.parentNode && nodeToRemove.parentNode !== body ) { + nodeToRemove = nodeToRemove.parentNode; + } + + if ( nodeToRemove ) { + nodeToRemove.parentNode.removeChild( nodeToRemove ); + } + + return body.innerHTML.trim(); +} + export const settings = { title: __( 'Image' ), @@ -196,14 +215,7 @@ export const settings = { selector: 'img', }, caption: { - shortcode: ( attributes, { shortcode } ) => { - const { body } = document.implementation.createHTMLDocument( '' ); - - body.innerHTML = shortcode.content; - body.removeChild( body.firstElementChild ); - - return body.innerHTML.trim(); - }, + shortcode: stripFirstImage, }, href: { shortcode: ( attributes, { shortcode } ) => { diff --git a/packages/block-library/src/image/test/index.js b/packages/block-library/src/image/test/index.js new file mode 100644 index 0000000000000..5b21dcefdf6eb --- /dev/null +++ b/packages/block-library/src/image/test/index.js @@ -0,0 +1,32 @@ +/** + * Internal dependencies + */ +import { stripFirstImage } from '../'; + +describe( 'stripFirstImage', () => { + test( 'should do nothing if no image is present', () => { + expect( stripFirstImage( {}, { shortcode: { content: '' } } ) ).toEqual( '' ); + expect( stripFirstImage( {}, { shortcode: { content: 'Tucson' } } ) ).toEqual( 'Tucson' ); + expect( stripFirstImage( {}, { shortcode: { content: '<em>Tucson</em>' } } ) ).toEqual( '<em>Tucson</em>' ); + } ); + + test( 'should strip out image when leading as expected', () => { + expect( stripFirstImage( {}, { shortcode: { content: '<img>' } } ) ).toEqual( '' ); + expect( stripFirstImage( {}, { shortcode: { content: '<img>Image!' } } ) ).toEqual( 'Image!' ); + expect( stripFirstImage( {}, { shortcode: { content: '<img src="image.png">Image!' } } ) ).toEqual( 'Image!' ); + } ); + + test( 'should strip out image when not in leading position as expected', () => { + expect( stripFirstImage( {}, { shortcode: { content: 'Before<img>' } } ) ).toEqual( 'Before' ); + expect( stripFirstImage( {}, { shortcode: { content: 'Before<img>Image!' } } ) ).toEqual( 'BeforeImage!' ); + expect( stripFirstImage( {}, { shortcode: { content: 'Before<img src="image.png">Image!' } } ) ).toEqual( 'BeforeImage!' ); + } ); + + test( 'should strip out only the first of many images', () => { + expect( stripFirstImage( {}, { shortcode: { content: '<img><img>' } } ) ).toEqual( '<img>' ); + } ); + + test( 'should strip out the first image and its wrapping parents', () => { + expect( stripFirstImage( {}, { shortcode: { content: '<p><a><img></a></p><p><img></p>' } } ) ).toEqual( '<p><img></p>' ); + } ); +} ); From 98c97a718799131968e5bfa17a50858e3508c187 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Mon, 18 Mar 2019 03:15:42 -0400 Subject: [PATCH 678/691] Components: Add onFocusLoss option to withFocusReturn (#14444) --- .../higher-order/with-focus-return/README.md | 46 ++++++++ .../higher-order/with-focus-return/context.js | 71 ++++++++++++ .../higher-order/with-focus-return/index.js | 103 +++++++++++++++--- .../with-focus-return/test/index.js | 55 ++++++++-- packages/components/src/index.js | 2 +- .../edit-post/src/components/layout/index.js | 12 +- .../edit-post/src/components/sidebar/index.js | 41 ++++--- 7 files changed, 286 insertions(+), 44 deletions(-) create mode 100644 packages/components/src/higher-order/with-focus-return/context.js diff --git a/packages/components/src/higher-order/with-focus-return/README.md b/packages/components/src/higher-order/with-focus-return/README.md index c583cded6332c..15ec5ef09ef25 100644 --- a/packages/components/src/higher-order/with-focus-return/README.md +++ b/packages/components/src/higher-order/with-focus-return/README.md @@ -1,7 +1,13 @@ # withFocusReturn +`withFocusReturn` is a higher-order component used typically in scenarios of short-lived elements (modals, dropdowns) where, upon the element's unmounting, focus should be restored to the focused element which had initiated it being rendered. + +Optionally, it can be used in combination with a `FocusRenderProvider` which, when rendered toward the top of an application, will remember a history of elements focused during a session. This can provide safeguards for scenarios where one short-lived element triggers the creation of another (e.g. a dropdown menu triggering a modal display). The combined effect of `FocusRenderProvider` and `withFocusReturn` is that focus will be returned to the most recent focused element which is still present in the document. + ## Usage +### `withFocusReturn` + ```jsx import { withFocusReturn, TextControl, Button } from '@wordpress/components'; import { withState } from '@wordpress/compose'; @@ -39,3 +45,43 @@ const MyComponentWithFocusReturn = withState( { ); } ); ``` + +`withFocusReturn` can optionally be called as a higher-order function creator. Provided an options object, a new higher-order function is returned. + +Currently, the following options are supported: + +#### `onFocusReturn` + +An optional function which allows the developer to customize the focus return behavior. A return value of `false` should be returned from this function to indicate that the default focus return behavior should be skipped. + +- Type: `Function` +- Required: No + +_Example:_ + +```jsx +function MyComponent() { + return <textarea />; +} + +const EnhancedMyComponent = withFocusReturn( { + onFocusReturn() { + document.getElementById( 'other-input' ).focus(); + return false; + }, +} )( MyComponent ); +``` + +### `FocusReturnProvider` + +```jsx +import { FocusReturnProvider } from '@wordpress/components'; + +function App() { + return ( + <FocusReturnProvider> + { /* ... */ } + </FocusReturnProvider> + ); +} +``` diff --git a/packages/components/src/higher-order/with-focus-return/context.js b/packages/components/src/higher-order/with-focus-return/context.js new file mode 100644 index 0000000000000..f65834f488692 --- /dev/null +++ b/packages/components/src/higher-order/with-focus-return/context.js @@ -0,0 +1,71 @@ +/** + * External dependencies + */ +import { uniq } from 'lodash'; + +/** + * WordPress dependencies + */ +import { Component, createContext } from '@wordpress/element'; + +const { Provider, Consumer } = createContext( { + focusHistory: [], +} ); + +Provider.displayName = 'FocusReturnProvider'; +Consumer.displayName = 'FocusReturnConsumer'; + +/** + * The maximum history length to capture for the focus stack. When exceeded, + * items should be shifted from the stack for each consecutive push. + * + * @type {number} + */ +const MAX_STACK_LENGTH = 100; + +class FocusReturnProvider extends Component { + constructor() { + super( ...arguments ); + + this.onFocus = this.onFocus.bind( this ); + + this.state = { + focusHistory: [], + }; + } + + onFocus( event ) { + const { focusHistory } = this.state; + + // Push the focused element to the history stack, keeping only unique + // members but preferring the _last_ occurrence of any duplicates. + // Lodash's `uniq` behavior favors the first occurrence, so the array + // is temporarily reversed prior to it being called upon. Uniqueness + // helps avoid situations where, such as in a constrained tabbing area, + // the user changes focus enough within a transient element that the + // stack may otherwise only consist of members pending destruction, at + // which point focus might have been lost. + const nextFocusHistory = uniq( + [ ...focusHistory, event.target ] + .slice( -1 * MAX_STACK_LENGTH ) + .reverse() + ).reverse(); + + this.setState( { focusHistory: nextFocusHistory } ); + } + + render() { + const { children, className } = this.props; + + return ( + <Provider value={ this.state }> + <div onFocus={ this.onFocus } className={ className }> + { children } + </div> + </Provider> + ); + } +} + +export default FocusReturnProvider; +export { Consumer }; diff --git a/packages/components/src/higher-order/with-focus-return/index.js b/packages/components/src/higher-order/with-focus-return/index.js index ae77932d13548..470d1f1fba7b5 100644 --- a/packages/components/src/higher-order/with-focus-return/index.js +++ b/packages/components/src/higher-order/with-focus-return/index.js @@ -1,39 +1,105 @@ +/** + * External dependencies + */ +import { stubTrue, without } from 'lodash'; + /** * WordPress dependencies */ import { Component } from '@wordpress/element'; import { createHigherOrderComponent } from '@wordpress/compose'; +/** + * Internal dependencies + */ +import Provider, { Consumer } from './context'; + +/** + * Returns true if the given object is component-like. An object is component- + * like if it is an instance of wp.element.Component, or is a function. + * + * @param {*} object Object to test. + * + * @return {boolean} Whether object is component-like. + */ +function isComponentLike( object ) { + return ( + object instanceof Component || + typeof object === 'function' + ); +} + /** * Higher Order Component used to be used to wrap disposable elements like * sidebars, modals, dropdowns. When mounting the wrapped component, we track a * reference to the current active element so we know where to restore focus * when the component is unmounted. * - * @param {WPElement} WrappedComponent The disposable component. + * @param {(WPComponent|Object)} options The component to be enhanced with + * focus return behavior, or an object + * describing the component and the + * focus return characteristics. * * @return {Component} Component with the focus restauration behaviour. */ -export default createHigherOrderComponent( - ( WrappedComponent ) => { - return class extends Component { +function withFocusReturn( options ) { + // Normalize as overloaded form `withFocusReturn( options )( Component )` + // or as `withFocusReturn( Component )`. + if ( isComponentLike( options ) ) { + const WrappedComponent = options; + return withFocusReturn( {} )( WrappedComponent ); + } + + const { onFocusReturn = stubTrue } = options; + + return function( WrappedComponent ) { + class FocusReturn extends Component { constructor() { super( ...arguments ); - this.setIsFocusedTrue = () => this.isFocused = true; - this.setIsFocusedFalse = () => this.isFocused = false; + this.ownFocusedElements = new Set; this.activeElementOnMount = document.activeElement; + this.setIsFocusedFalse = () => this.isFocused = false; + this.setIsFocusedTrue = ( event ) => { + this.ownFocusedElements.add( event.target ); + this.isFocused = true; + }; } componentWillUnmount() { - const { activeElementOnMount, isFocused } = this; - if ( ! activeElementOnMount ) { + const { + activeElementOnMount, + isFocused, + ownFocusedElements, + } = this; + + if ( ! isFocused ) { return; } - const { body, activeElement } = document; - if ( isFocused || null === activeElement || body === activeElement ) { - activeElementOnMount.focus(); + // Defer to the component's own explicit focus return behavior, + // if specified. The function should return `false` to prevent + // the default behavior otherwise occurring here. This allows + // for support that the `onFocusReturn` decides to allow the + // default behavior to occur under some conditions. + if ( onFocusReturn() === false ) { + return; + } + + const stack = [ + ...without( + this.props.focusHistory, + ...ownFocusedElements + ), + activeElementOnMount, + ]; + + let candidate; + while ( ( candidate = stack.pop() ) ) { + if ( document.body.contains( candidate ) ) { + candidate.focus(); + return; + } } } @@ -47,6 +113,15 @@ export default createHigherOrderComponent( </div> ); } - }; - }, 'withFocusReturn' -); + } + + return ( props ) => ( + <Consumer> + { ( context ) => <FocusReturn { ...props } { ...context } /> } + </Consumer> + ); + }; +} + +export default createHigherOrderComponent( withFocusReturn, 'withFocusReturn' ); +export { Provider }; diff --git a/packages/components/src/higher-order/with-focus-return/test/index.js b/packages/components/src/higher-order/with-focus-return/test/index.js index 3f88457e52732..175a118efc285 100644 --- a/packages/components/src/higher-order/with-focus-return/test/index.js +++ b/packages/components/src/higher-order/with-focus-return/test/index.js @@ -2,21 +2,22 @@ * External dependencies */ import renderer from 'react-test-renderer'; +import { mount } from 'enzyme'; /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; +import { Component, createElement } from '@wordpress/element'; /** * Internal dependencies */ -import withFocusReturn from '../'; +import withFocusReturn, { Provider } from '../'; class Test extends Component { render() { return ( - <div className="test">Testing</div> + <div className="test"><textarea /></div> ); } } @@ -47,7 +48,7 @@ describe( 'withFocusReturn()', () => { const wrappedElementShallow = wrappedElement.children[ 0 ]; expect( wrappedElementShallow.props.className ).toBe( 'test' ); expect( wrappedElementShallow.type ).toBe( 'div' ); - expect( wrappedElementShallow.children[ 0 ] ).toBe( 'Testing' ); + expect( wrappedElementShallow.children[ 0 ].type ).toBe( 'textarea' ); } ); it( 'should pass additional props through to the wrapped element', () => { @@ -71,17 +72,47 @@ describe( 'withFocusReturn()', () => { expect( document.activeElement ).toBe( switchFocusTo ); } ); - it( 'should return focus to element associated with HOC', () => { - const mountedComposite = renderer.create( <Composite /> ); - expect( getInstance( mountedComposite ).activeElementOnMount ).toBe( activeElement ); - - // Change activeElement. - document.activeElement.blur(); - expect( document.activeElement ).toBe( document.body ); + it( 'should switch focus back when unmounted while having focus', () => { + const wrapper = mount( <Composite /> ); + wrapper.find( 'textarea' ).at( 0 ).simulate( 'focus' ); // Should return to the activeElement saved with this component. - mountedComposite.unmount(); + wrapper.unmount(); expect( document.activeElement ).toBe( activeElement ); } ); + + it( 'should switch focus to the most recent still-available focus target', () => { + const container = document.createElement( 'div' ); + document.body.appendChild( container ); + const wrapper = mount( + createElement( + ( props ) => ( + <Provider> + <input name="first" /> + { props.renderSecondInput && <input name="second" /> } + { props.renderComposite && <Composite /> } + </Provider> + ), + { renderSecondInput: true } + ), + { attachTo: container } + ); + + function focus( selector ) { + const childWrapper = wrapper.find( selector ); + const childNode = childWrapper.getDOMNode(); + childWrapper.simulate( 'focus', { target: childNode } ); + } + + focus( 'input[name="first"]' ); + jest.spyOn( wrapper.find( 'input[name="first"]' ).getDOMNode(), 'focus' ); + focus( 'input[name="second"]' ); + wrapper.setProps( { renderComposite: true } ); + focus( 'textarea' ); + wrapper.setProps( { renderSecondInput: false } ); + wrapper.setProps( { renderComposite: false } ); + + expect( wrapper.find( 'input[name="first"]' ).getDOMNode().focus ).toHaveBeenCalled(); + } ); } ); } ); diff --git a/packages/components/src/index.js b/packages/components/src/index.js index 6e45caa6e1e05..c5c34089cdb7c 100644 --- a/packages/components/src/index.js +++ b/packages/components/src/index.js @@ -70,6 +70,6 @@ export { default as withConstrainedTabbing } from './higher-order/with-constrain export { default as withFallbackStyles } from './higher-order/with-fallback-styles'; export { default as withFilters } from './higher-order/with-filters'; export { default as withFocusOutside } from './higher-order/with-focus-outside'; -export { default as withFocusReturn } from './higher-order/with-focus-return'; +export { default as withFocusReturn, Provider as FocusReturnProvider } from './higher-order/with-focus-return'; export { default as withNotices } from './higher-order/with-notices'; export { default as withSpokenMessages } from './higher-order/with-spoken-messages'; diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index d033b993a00b2..643184f4b971b 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -6,7 +6,13 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { Button, Popover, ScrollLock, navigateRegions } from '@wordpress/components'; +import { + Button, + Popover, + ScrollLock, + FocusReturnProvider, + navigateRegions, +} from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { PreserveScrollInReorder } from '@wordpress/block-editor'; import { @@ -66,7 +72,7 @@ function Layout( { tabIndex: -1, }; return ( - <div className={ className }> + <FocusReturnProvider className={ className }> <FullscreenMode /> <BrowserURL /> <UnsavedChangesWarning /> @@ -126,7 +132,7 @@ function Layout( { ) } <Popover.Slot /> <PluginArea /> - </div> + </FocusReturnProvider> ); } diff --git a/packages/edit-post/src/components/sidebar/index.js b/packages/edit-post/src/components/sidebar/index.js index a9da35865e875..21559c2eea255 100644 --- a/packages/edit-post/src/components/sidebar/index.js +++ b/packages/edit-post/src/components/sidebar/index.js @@ -17,32 +17,45 @@ const { Fill, Slot } = createSlotFill( 'Sidebar' ); * * @return {Object} The rendered sidebar. */ -const Sidebar = ( { children, label } ) => { +function Sidebar( { children, label, className } ) { + return ( + <div + className={ classnames( 'edit-post-sidebar', className ) } + role="region" + aria-label={ label } + tabIndex="-1" + > + { children } + </div> + ); +} + +Sidebar = withFocusReturn( { + onFocusReturn() { + const button = document.querySelector( '.edit-post-header__settings [aria-label="Settings"]' ); + if ( button ) { + button.focus(); + return false; + } + }, +} )( Sidebar ); + +function AnimatedSidebarFill( props ) { return ( <Fill> <Animate type="slide-in" options={ { origin: 'left' } }> - { ( { className } ) => ( - <div - className={ classnames( 'edit-post-sidebar', className ) } - role="region" - aria-label={ label } - tabIndex="-1" - > - { children } - </div> - ) } + { () => <Sidebar { ...props } /> } </Animate> </Fill> ); -}; +} const WrappedSidebar = compose( withSelect( ( select, { name } ) => ( { isActive: select( 'core/edit-post' ).getActiveGeneralSidebarName() === name, } ) ), ifCondition( ( { isActive } ) => isActive ), - withFocusReturn, -)( Sidebar ); +)( AnimatedSidebarFill ); WrappedSidebar.Slot = Slot; From 74c0ed75f43e1b740228e7eb5b7b1e844418e383 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Mon, 18 Mar 2019 00:18:41 -0700 Subject: [PATCH 679/691] Docs: Update JavaScript build and setup with wp-scripts updates (#14440) --- .../tutorials/javascript/js-build-setup.md | 122 ++++-------------- 1 file changed, 22 insertions(+), 100 deletions(-) diff --git a/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md b/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md index 067e99e5790f2..ebd65dc6a232d 100644 --- a/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md +++ b/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md @@ -8,10 +8,12 @@ Most browsers can not interpret or run ESNext and JSX syntaxes, so we use a tran There are a few reasons to use ESNext and the extra step. First, it makes for simpler code that is easier to read and write. Using a transformation step allows for tools to optimize the code to work on the widest variety of browsers. Also, by using a build step you can organize your code into smaller modules and files that can be bundled together into a single download. -There are many tools that can perform this transformation or build step, but in this tutorial we will focus on Webpack and Babel. +There are different tools that can perform this transformation or build step, but WordPress uses Webpack and Babel. [Webpack](https://webpack.js.org/) is a pluggable tool that processes JavaScript creating a compiled bundle that runs in a browser. [Babel](https://babeljs.io/) transforms JavaScript from one format to another. You use Babel as a plugin to Webpack to transform both ESNext and JSX to JavaScript. +The [@wordpress/scripts](https://www.npmjs.com/package/@wordpress/scripts) package abstracts these libraries away to standardize and simplify development, so you won't need to handle the details for configuring those libraries. + ## Quick Start For a quick start, you can use one of the examples from the [Gutenberg Examples repository](https://github.com/wordpress/gutenberg-examples/). Each one of the `-esnext` directories contain the necessary files for working with ESNext and JSX. @@ -38,6 +40,7 @@ To start a new node project, first create a directory to work in. ``` mkdir myguten-block +cd myguten-block ``` You create a new package.json running `npm init` in your terminal. This will walk you through creating your package.json file: @@ -48,7 +51,7 @@ npm init package name: (myguten-block) myguten-block version: (1.0.0) description: Test block -entry point: (index.js) block.js +entry point: (index.js) build/index.js test command: git repository: keywords: @@ -76,76 +79,23 @@ Is this OK? (yes) yes The next step is to install the packages required. You can install packages using the npm command `npm install`. If you pass the `--save-dev` parameter, npm will write the package as a dev dependency in the package.json file. The `--save-exact` parameter instructs npm to save an exact version of a dependency, not a range of valid versions. See [npm install documentation](https://docs.npmjs.com/cli/install) for more details. -Run `npm install --save-dev --save-exact webpack webpack-cli` +Run `npm install --save-dev --save-exact @wordpress/scripts` -After installing, a `node_modules` directory is created with the webpack module and its dependencies. +After installing, a `node_modules` directory is created with the modules and their dependencies. Also, if you look at package.json file it will include a new section: ```json "dependencies": { - "webpack": "4.29.0" + "@wordpress/scripts": "3.1.0" } ``` ## Webpack & Babel -Next, we will configure webpack to process the `block.js` file and run babel to transform the JSX within it. - -Create the file `webpack.config.js` - -```js -// sets mode webpack runs under -const NODE_ENV = process.env.NODE_ENV || 'development'; - -module.exports = { - mode: NODE_ENV, - - // entry is the source script - entry: './block.js', - - // output is where to write the built file - output: { - path: __dirname, - filename: 'block.build.js', - }, - module: { - // the list of rules used to process files - // this looks for .js files, exclude files - // in node_modules directory, and uses the - // babel-loader to process - rules: [ - { - test: /.js$/, - exclude: /node_modules/, - loader: 'babel-loader', - }, - ], - }, -}; -``` - -Next, you need to install babel, the webpack loader, and the JSX plugin using: - -`npm install --save-dev --save-exact babel-loader @babel/core @babel/plugin-transform-react-jsx` - -You configure babel by creating a `.babelrc` file: - -```json -{ - "plugins": [ - [ "@babel/plugin-transform-react-jsx", { - "pragma": "wp.element.createElement" - } ] - ] -} -``` - -This pragma setting instructs Babel that any JSX syntax such as `<Hello />` should be transformed into `wp.element.createElement( Hello )`. The name of the setting (`transform-react-jsx`) is derived from the fact that it overrides the default assumption to transform to `React.createElement( Hello )`. - -With both configs in place, you can now run webpack. +The `@wordpress/scripts` package handles the dependencies and default configuration for Webpack and Babel. The scripts package expects the source file to compile to be found at `src/index.js`, and will save the compiled output to `build/index.js`. -First you need a basic block.js to build. Create `block.js` with the following content: +With that in mind, let's set up a basic block. Create a file at `src/index.js` with the following content: ```js const { registerBlockType } = wp.blocks; @@ -154,12 +104,8 @@ registerBlockType( 'myguten/test-block', { title: 'Basic Example', icon: 'smiley', category: 'layout', - edit() { - return <div>Hola, mundo!</div>; - }, - save() { - return <div>Hola, mundo!</div>; - }, + edit: () => <div>Hola, mundo!</div>, + save: () => <div>Hola, mundo!</div>, } ); ``` @@ -167,71 +113,47 @@ To configure npm to run a script, you use the scripts section in `package.json` ```json "scripts": { - "build": "webpack" + "build": "wp-scripts build" }, ``` You can then run the build using: `npm run build`. -After the build finishes, you will see the built file created at `block.build.js`. +After the build finishes, you will see the built file created at `build/index.js`. ## Finishing Touches ### Development Mode -The basics are in place to build. You might have noticed the webpack.config.js sets a default mode of "development". Webpack can also run in a "production" mode, which shrinks the code down so it downloads faster, but makes it difficult to read. +The **build** command in `@wordpress/scripts` runs in a "production" mode. This shrinks the code down so it downloads faster, but makes it difficult to read in the process. You can use the **start** command which runs a development mode that does not shrink the code, and additionally continues a running process to watch the source file for more changes and rebuild as you develop. -The mode is setup so it can be configured using environment variables, which can be added in the scripts section of `package.json`. +The start command can be added to the same scripts section of `package.json`: ```json "scripts": { - "dev": "webpack --watch", - "build": "cross-env NODE_ENV=production webpack" + "start": "wp-scripts start", + "build": "wp-scripts build" }, ``` -This sets the environment variables, but different environments handle these settings in different ways. Using the `cross-env` helper module can help to handle this. Be sure to install the `cross-env` package using `npm install --save-dev --save-exact cross-env`. - -Additionally, webpack has a `--watch` flag that will keep the process running, watching for any changes to the `block.js` file and re-building as changes occur. This is useful during development, when you might have a lot of changes in progress. - -You can start the watcher by running `npm run dev` in a terminal. You can then edit away in your text editor; after each save, webpack will automatically build. You can then use the familiar edit/save/reload development process. +Now, when you run `npm run start` a watcher will run in the terminal. You can then edit away in your text editor; after each save, it will automatically build. You can then use the familiar edit/save/reload development process. **Note:** keep an eye on your terminal for any errors. If you make a typo or syntax error, the build will fail and the error will be in the terminal. -### Babel Browser Targeting - -Babel has the ability to build JavaScript using rules that target certain browsers and versions. By setting a reasonable set of modern browsers, Babel can optimize the JavaScript it generates. - -WordPress has a preset default you can use to target the minimum supported browsers by WordPress. - -Install the module using: `npm install --save-dev --save-exact @wordpress/babel-preset-default` - -You then update `.babelrc` by adding a "presets" section: - -``` -{ - "presets": [ "@wordpress/babel-preset-default" ], - "plugins": [ - [ "@babel/plugin-transform-react-jsx", { - "pragma": "wp.element.createElement" - } ] - ] -} -``` ### Source Control Because a typical `node_modules` folder will contain thousands of files that change with every software update, you should exclude `node_modules/` from your source control. If you ever start from a fresh clone, simply run `npm install` in the same folder your `package.json` is located to pull your required packages. -Likewise, you do not need to include `node_modules` or any of the above configuration files in your plugin because they will be bundled inside the file that webpack builds. **Be sure to enqueue the `block.build.js` file** in your plugin PHP. This is the only JavaScript file needed for your block to run. +Likewise, you do not need to include `node_modules` or any of the above configuration files in your plugin because they will be bundled inside the file that webpack builds. **Be sure to enqueue the `build/index.js` file** in your plugin PHP. This is the only JavaScript file needed for your block to run. ## Summary -Yes, the initial setup is rather tedious, and there are a number of different tools and configs to learn. However, as the quick start alluded to, copying an existing config is the typical way most people start. +Yes, the initial setup is a bit more involved, but the additional features and benefits are usually worth the trade off in setup time. With a setup in place, the standard workflow is: - Install dependencies: `npm install` -- Start development builds: `npm run dev` +- Start development builds: `npm run start` - Develop. Test. Repeat. - Create production build: `npm run build` From 5e74833de6a444b5f30b5e64d607d884489e14bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <iseulde@automattic.com> Date: Mon, 18 Mar 2019 08:28:48 +0100 Subject: [PATCH 680/691] Raw handling: update strikethrough (#14430) --- .../blocks/src/api/raw-handling/phrasing-content-reducer.js | 2 +- packages/blocks/src/api/raw-handling/phrasing-content.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js b/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js index defdbe1eb97dc..5c5ef223298fe 100644 --- a/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js +++ b/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js @@ -23,7 +23,7 @@ export default function( node, doc ) { } if ( textDecorationLine === 'line-through' ) { - wrap( doc.createElement( 'del' ), node ); + wrap( doc.createElement( 's' ), node ); } if ( verticalAlign === 'super' ) { diff --git a/packages/blocks/src/api/raw-handling/phrasing-content.js b/packages/blocks/src/api/raw-handling/phrasing-content.js index c39b3330f1105..028c61e9b8958 100644 --- a/packages/blocks/src/api/raw-handling/phrasing-content.js +++ b/packages/blocks/src/api/raw-handling/phrasing-content.js @@ -6,6 +6,7 @@ import { omit } from 'lodash'; const phrasingContentSchema = { strong: {}, em: {}, + s: {}, del: {}, ins: {}, a: { attributes: [ 'href', 'target', 'rel' ] }, @@ -20,7 +21,7 @@ const phrasingContentSchema = { // Recursion is needed. // Possible: strong > em > strong. // Impossible: strong > strong. -[ 'strong', 'em', 'del', 'ins', 'a', 'code', 'abbr', 'sub', 'sup' ].forEach( ( tag ) => { +[ 'strong', 'em', 's', 'del', 'ins', 'a', 'code', 'abbr', 'sub', 'sup' ].forEach( ( tag ) => { phrasingContentSchema[ tag ].children = omit( phrasingContentSchema, tag ); } ); From fec7ea198b0117979cddfcda499279361e84b9df Mon Sep 17 00:00:00 2001 From: "Nahid F. Mohit" <nfmohit49@gmail.com> Date: Mon, 18 Mar 2019 15:48:39 +0800 Subject: [PATCH 681/691] Add ability to transform [audio] shortcodes to aidio blocks (#14045) --- packages/block-library/src/audio/index.js | 30 +++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/packages/block-library/src/audio/index.js b/packages/block-library/src/audio/index.js index 7528ac483311e..f51937c8d4614 100644 --- a/packages/block-library/src/audio/index.js +++ b/packages/block-library/src/audio/index.js @@ -77,6 +77,36 @@ export const settings = { return block; }, }, + { + type: 'shortcode', + tag: 'audio', + attributes: { + src: { + type: 'string', + shortcode: ( { named: { src } } ) => { + return src; + }, + }, + loop: { + type: 'string', + shortcode: ( { named: { loop } } ) => { + return loop; + }, + }, + autoplay: { + type: 'srting', + shortcode: ( { named: { autoplay } } ) => { + return autoplay; + }, + }, + preload: { + type: 'string', + shortcode: ( { named: { preload } } ) => { + return preload; + }, + }, + }, + }, ], }, From c7801a82a19641d3022bc3e04756d5c20f75da7d Mon Sep 17 00:00:00 2001 From: John <johng75@gmail.com> Date: Mon, 18 Mar 2019 07:49:35 +0000 Subject: [PATCH 682/691] Add greater than symbol to attribute escaping (#9963) --- packages/element/src/test/index.js | 2 +- packages/element/src/test/serialize.js | 2 +- packages/escape-html/CHANGELOG.md | 4 ++++ packages/escape-html/README.md | 20 ++++++++++++++------ packages/escape-html/package.json | 2 +- packages/escape-html/src/escape-greater.js | 15 +++++++++++++++ packages/escape-html/src/index.js | 15 ++++++++++++++- packages/escape-html/src/test/index.js | 13 +++++++++++++ 8 files changed, 63 insertions(+), 10 deletions(-) create mode 100644 packages/escape-html/src/escape-greater.js diff --git a/packages/element/src/test/index.js b/packages/element/src/test/index.js index 5d8417859042d..ef5bd4bb87542 100644 --- a/packages/element/src/test/index.js +++ b/packages/element/src/test/index.js @@ -53,7 +53,7 @@ describe( 'element', () => { }, '<"WordPress" & Friends>' ) ); expect( result ).toBe( - '<a href="/index.php?foo=bar&amp;qux=<&quot;scary&quot;>" style="background-color:red">' + + '<a href="/index.php?foo=bar&amp;qux=<&quot;scary&quot;&gt;" style="background-color:red">' + '&lt;"WordPress" &amp; Friends>' + '</a>' ); diff --git a/packages/element/src/test/serialize.js b/packages/element/src/test/serialize.js index d0f5d9ef32b3a..7fe4251666dec 100644 --- a/packages/element/src/test/serialize.js +++ b/packages/element/src/test/serialize.js @@ -528,7 +528,7 @@ describe( 'renderAttributes()', () => { href: '/index.php?foo=bar&qux=<"scary">', } ); - expect( result ).toBe( ' style="background:url(&quot;foo.png&quot;)" href="/index.php?foo=bar&amp;qux=<&quot;scary&quot;>"' ); + expect( result ).toBe( ' style="background:url(&quot;foo.png&quot;)" href="/index.php?foo=bar&amp;qux=<&quot;scary&quot;&gt;"' ); } ); it( 'should render numeric attributes', () => { diff --git a/packages/escape-html/CHANGELOG.md b/packages/escape-html/CHANGELOG.md index e780b87752277..3d81e8ec35b7b 100644 --- a/packages/escape-html/CHANGELOG.md +++ b/packages/escape-html/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.1.1 (Unreleased) + +- Add fix for WordPress wptexturize greater-than tokenize bug (see https://core.trac.wordpress.org/ticket/45387) + ## 1.0.1 (2018-10-19) ## 1.0.0 (2018-10-18) diff --git a/packages/escape-html/README.md b/packages/escape-html/README.md index d144be1b7de9f..eafa8cf0673b7 100644 --- a/packages/escape-html/README.md +++ b/packages/escape-html/README.md @@ -18,7 +18,7 @@ _This package assumes that your code will run in an **ES2015+** environment. If ### escapeAmpersand -[src/index.js#L28-L30](src/index.js#L28-L30) +[src/index.js#L33-L35](src/index.js#L33-L35) Returns a string with ampersands escaped. Note that this is an imperfect implementation, where only ampersands which do not appear as a pattern of @@ -41,7 +41,7 @@ named references (i.e. ambiguous ampersand) are are still permitted. ### escapeAttribute -[src/index.js#L66-L68](src/index.js#L66-L68) +[src/index.js#L79-L81](src/index.js#L79-L81) Returns an escaped attribute value. @@ -52,6 +52,14 @@ Returns an escaped attribute value. "[...] the text cannot contain an ambiguous ampersand [...] must not contain any literal U+0022 QUOTATION MARK characters (")" +Note we also escape the greater than symbol, as this is used by wptexturize to +split HTML strings. This is a WordPress specific fix + +Note that if a resolution for Trac#45387 comes to fruition, it is no longer +necessary for `__unstableEscapeGreaterThan` to be used. + +See: <https://core.trac.wordpress.org/ticket/45387> + **Parameters** - **value** `string`: Attribute value. @@ -62,7 +70,7 @@ any literal U+0022 QUOTATION MARK characters (")" ### escapeHTML -[src/index.js#L82-L84](src/index.js#L82-L84) +[src/index.js#L95-L97](src/index.js#L95-L97) Returns an escaped HTML element value. @@ -83,7 +91,7 @@ ambiguous ampersand." ### escapeLessThan -[src/index.js#L50-L52](src/index.js#L50-L52) +[src/index.js#L55-L57](src/index.js#L55-L57) Returns a string with less-than sign replaced. @@ -97,7 +105,7 @@ Returns a string with less-than sign replaced. ### escapeQuotationMark -[src/index.js#L39-L41](src/index.js#L39-L41) +[src/index.js#L44-L46](src/index.js#L44-L46) Returns a string with quotation marks replaced. @@ -111,7 +119,7 @@ Returns a string with quotation marks replaced. ### isValidAttributeName -[src/index.js#L93-L95](src/index.js#L93-L95) +[src/index.js#L106-L108](src/index.js#L106-L108) Returns true if the given attribute name is valid, or false otherwise. diff --git a/packages/escape-html/package.json b/packages/escape-html/package.json index 909f2bd4ec1f3..a496c52e213bc 100644 --- a/packages/escape-html/package.json +++ b/packages/escape-html/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/escape-html", - "version": "1.1.0", + "version": "1.1.1", "description": "Escape HTML utils.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/escape-html/src/escape-greater.js b/packages/escape-html/src/escape-greater.js new file mode 100644 index 0000000000000..f761a81e16ae6 --- /dev/null +++ b/packages/escape-html/src/escape-greater.js @@ -0,0 +1,15 @@ +/** + * Returns a string with greater-than sign replaced. + * + * Note that if a resolution for Trac#45387 comes to fruition, it is no longer + * necessary for `__unstableEscapeGreaterThan` to exist. + * + * See: https://core.trac.wordpress.org/ticket/45387 + * + * @param {string} value Original string. + * + * @return {string} Escaped string. + */ +export default function __unstableEscapeGreaterThan( value ) { + return value.replace( />/g, '&gt;' ); +} diff --git a/packages/escape-html/src/index.js b/packages/escape-html/src/index.js index 6b3f74e834564..c29efb94bd2d0 100644 --- a/packages/escape-html/src/index.js +++ b/packages/escape-html/src/index.js @@ -1,3 +1,8 @@ +/** + * Internal dependencies + */ +import __unstableEscapeGreaterThan from './escape-greater'; + /** * Regular expression matching invalid attribute names. * @@ -59,12 +64,20 @@ export function escapeLessThan( value ) { * "[...] the text cannot contain an ambiguous ampersand [...] must not contain * any literal U+0022 QUOTATION MARK characters (")" * + * Note we also escape the greater than symbol, as this is used by wptexturize to + * split HTML strings. This is a WordPress specific fix + * + * Note that if a resolution for Trac#45387 comes to fruition, it is no longer + * necessary for `__unstableEscapeGreaterThan` to be used. + * + * See: https://core.trac.wordpress.org/ticket/45387 + * * @param {string} value Attribute value. * * @return {string} Escaped attribute value. */ export function escapeAttribute( value ) { - return escapeQuotationMark( escapeAmpersand( value ) ); + return __unstableEscapeGreaterThan( escapeQuotationMark( escapeAmpersand( value ) ) ); } /** diff --git a/packages/escape-html/src/test/index.js b/packages/escape-html/src/test/index.js index 65b3e09dfcfe1..9558c58c7e23a 100644 --- a/packages/escape-html/src/test/index.js +++ b/packages/escape-html/src/test/index.js @@ -9,6 +9,14 @@ import { escapeHTML, isValidAttributeName, } from '../'; +import __unstableEscapeGreaterThan from '../escape-greater'; + +function testUnstableEscapeGreaterThan( implementation ) { + it( 'should escape greater than', () => { + const result = implementation( 'Chicken > Ribs' ); + expect( result ).toBe( 'Chicken &gt; Ribs' ); + } ); +} function testEscapeAmpersand( implementation ) { it( 'should escape ampersand', () => { @@ -46,9 +54,14 @@ describe( 'escapeLessThan', () => { testEscapeLessThan( escapeLessThan ); } ); +describe( 'escapeGreaterThan', () => { + testUnstableEscapeGreaterThan( __unstableEscapeGreaterThan ); +} ); + describe( 'escapeAttribute', () => { testEscapeAmpersand( escapeAttribute ); testEscapeQuotationMark( escapeAttribute ); + testUnstableEscapeGreaterThan( escapeAttribute ); } ); describe( 'escapeHTML', () => { From bc8adf7378255c0ed54d72b699c4c74bb2968704 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell@kjellr.com> Date: Mon, 18 Mar 2019 04:01:41 -0400 Subject: [PATCH 683/691] Begin adding support for the `prefers-reduced-motion` setting, Add a11y notes to the animation docs. (#14021) --- assets/stylesheets/_animations.scss | 1 + assets/stylesheets/_mixins.scss | 10 ++++++++++ docs/designers-developers/designers/animation.md | 7 +++++++ packages/components/src/animate/style.scss | 2 ++ packages/components/src/modal/style.scss | 1 + .../src/components/fullscreen-mode/style.scss | 1 + packages/edit-post/src/components/layout/style.scss | 1 + 7 files changed, 23 insertions(+) diff --git a/assets/stylesheets/_animations.scss b/assets/stylesheets/_animations.scss index 755161bc852ff..f856c0bf812d6 100644 --- a/assets/stylesheets/_animations.scss +++ b/assets/stylesheets/_animations.scss @@ -5,4 +5,5 @@ @mixin edit-post__fade-in-animation($speed: 0.2s, $delay: 0s) { animation: edit-post__fade-in-animation $speed ease-out $delay; animation-fill-mode: forwards; + @include reduce-motion; } diff --git a/assets/stylesheets/_mixins.scss b/assets/stylesheets/_mixins.scss index 31134cd44d09b..50bd59d38c355 100644 --- a/assets/stylesheets/_mixins.scss +++ b/assets/stylesheets/_mixins.scss @@ -333,3 +333,13 @@ // icon standards. margin-right: 2px; } + +/** + * Allows users to opt-out of animations via OS-level preferences. + */ + +@mixin reduce-motion { + @media (prefers-reduced-motion: reduce) { + animation-duration: 1ms !important; + } +} diff --git a/docs/designers-developers/designers/animation.md b/docs/designers-developers/designers/animation.md index 66664639d3a23..24948723e0d8c 100644 --- a/docs/designers-developers/designers/animation.md +++ b/docs/designers-developers/designers/animation.md @@ -28,6 +28,13 @@ In creating consistent animations, we have to establish physical rules for how e Reuse animations if one already exists for your task. +## Accessibility Considerations + +- Animations should be subtle. Be cognizent of users with [vestibular disorders triggered by motion](https://www.ncbi.nlm.nih.gov/pubmed/29017000). +- Don't animate elements that are currently reporting content to adaptive technology (e.g., an `aria-live` region that's receiving updates). This can cause confusion wherein the technology tries to parse a region that's actively changing. +- Avoid animations that aren't directly triggered by user behaviors. +- Whenever possible, ensure that animations respect the OS-level "Reduce Motion" settings. This can be done by utilizing the [`prefers-reduce-motion`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion) media query. Gutenberg includes a `@reduce-motion` mixin for this, to be used alongside rules that include a CSS `animate` property. + ## Inventory of Reused Animations The generic `Animate` component is used to animate different parts of the interface. See [the component documentation](/packages/components/src/animate/README.md) for more details about the available animations. diff --git a/packages/components/src/animate/style.scss b/packages/components/src/animate/style.scss index 2d5dfcc9b5abd..1fa4e95d0cf59 100644 --- a/packages/components/src/animate/style.scss +++ b/packages/components/src/animate/style.scss @@ -1,6 +1,7 @@ .components-animate__appear { animation: components-animate__appear-animation 0.1s cubic-bezier(0, 0, 0.2, 1) 0s; animation-fill-mode: forwards; + @include reduce-motion; &.is-from-top, &.is-from-top.is-from-left { @@ -30,6 +31,7 @@ .components-animate__slide-in { animation: components-animate__slide-in-animation 0.1s cubic-bezier(0, 0, 0.2, 1); animation-fill-mode: forwards; + @include reduce-motion; &.is-from-left { transform: translateX(+100%); diff --git a/packages/components/src/modal/style.scss b/packages/components/src/modal/style.scss index 2058155f1f132..5746f8047a03c 100644 --- a/packages/components/src/modal/style.scss +++ b/packages/components/src/modal/style.scss @@ -42,6 +42,7 @@ // Animate the modal frame/contents appearing on the page. animation: components-modal__appear-animation 0.1s ease-out; animation-fill-mode: forwards; + @include reduce-motion; } } diff --git a/packages/edit-post/src/components/fullscreen-mode/style.scss b/packages/edit-post/src/components/fullscreen-mode/style.scss index 0d044161b9e53..9d51282381eb0 100644 --- a/packages/edit-post/src/components/fullscreen-mode/style.scss +++ b/packages/edit-post/src/components/fullscreen-mode/style.scss @@ -26,6 +26,7 @@ body.js.is-fullscreen-mode { .edit-post-header { transform: translateY(-100%); animation: edit-post-fullscreen-mode__slide-in-animation 0.1s forwards; + @include reduce-motion; } } } diff --git a/packages/edit-post/src/components/layout/style.scss b/packages/edit-post/src/components/layout/style.scss index 504b9fbe7ac82..dc40e6321ced3 100644 --- a/packages/edit-post/src/components/layout/style.scss +++ b/packages/edit-post/src/components/layout/style.scss @@ -178,6 +178,7 @@ border-left: $border-width solid $light-gray-500; transform: translateX(+100%); animation: edit-post-post-publish-panel__slide-in-animation 0.1s forwards; + @include reduce-motion; body.is-fullscreen-mode & { top: 0; From 7748b8e82ca00dd1bfda40c48266c8a58729c87f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Mon, 18 Mar 2019 09:31:22 +0100 Subject: [PATCH 684/691] Docs: Include clarification about using @wordpress/babel-plugin-import-jsx-pragma with @wordpress/babel-preset-default (#14482) --- packages/babel-plugin-import-jsx-pragma/README.md | 2 ++ packages/babel-preset-default/CHANGELOG.md | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/babel-plugin-import-jsx-pragma/README.md b/packages/babel-plugin-import-jsx-pragma/README.md index 85d7e69655455..21ad31fab2216 100644 --- a/packages/babel-plugin-import-jsx-pragma/README.md +++ b/packages/babel-plugin-import-jsx-pragma/README.md @@ -30,6 +30,8 @@ module.exports = { }; ``` +_Note:_ `@wordpress/babel-plugin-import-jsx-pragma` is now included in `@wordpress/babel-preset-default` (default preset for WordPress development). If you are using it, you shouldn't need to include this plugin anymore in your Babel config. + ## Options As the `@babel/transform-react-jsx` plugin offers options to customize the `pragma` to which the transform references, there are equivalent options to assign for customizing the imports generated. diff --git a/packages/babel-preset-default/CHANGELOG.md b/packages/babel-preset-default/CHANGELOG.md index 7ff815a2a7db0..ab2a808cdfee4 100644 --- a/packages/babel-preset-default/CHANGELOG.md +++ b/packages/babel-preset-default/CHANGELOG.md @@ -3,7 +3,7 @@ ### Breaking Change - Removed `babel-core` dependency acting as Babel 7 bridge ([#13922](https://github.com/WordPress/gutenberg/pull/13922). Ensure all references to `babel-core` are replaced with `@babel/core` . -- Preset updated to include `@wordpress/babel-plugin-import-jsx-pragma` plugin integration ([#13540](https://github.com/WordPress/gutenberg/pull/13540)). +- Preset updated to include `@wordpress/babel-plugin-import-jsx-pragma` plugin integration ([#13540](https://github.com/WordPress/gutenberg/pull/13540)). It should no longer be explicitly included in your Babel config. ### Bug Fix From 15b20c7ade827b80e3591d382f7ba233c23dca46 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Mon, 18 Mar 2019 08:37:00 +0000 Subject: [PATCH 685/691] Improve openAllBlockInserterCategories function; Fix intermittent failures in inner blocks allowed blocks test. (#14460) --- .../src/open-all-block-inserter-categories.js | 8 ++++---- .../specs/plugins/inner-blocks-allowed-blocks.test.js | 7 +------ 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/packages/e2e-test-utils/src/open-all-block-inserter-categories.js b/packages/e2e-test-utils/src/open-all-block-inserter-categories.js index 3960cbe646c6a..f022a0921ed1a 100644 --- a/packages/e2e-test-utils/src/open-all-block-inserter-categories.js +++ b/packages/e2e-test-utils/src/open-all-block-inserter-categories.js @@ -2,10 +2,10 @@ * Opens all block inserter categories. */ export async function openAllBlockInserterCategories() { - const notOpenCategoryPanels = await page.$$( - '.block-editor-inserter__results .components-panel__body:not(.is-opened)' - ); - for ( const categoryPanel of notOpenCategoryPanels ) { + const notOppenedCategorySelector = '.block-editor-inserter__results .components-panel__body:not(.is-opened)'; + let categoryPanel = await page.$( notOppenedCategorySelector ); + while ( categoryPanel !== null ) { await categoryPanel.click(); + categoryPanel = await page.$( notOppenedCategorySelector ); } } diff --git a/packages/e2e-tests/specs/plugins/inner-blocks-allowed-blocks.test.js b/packages/e2e-tests/specs/plugins/inner-blocks-allowed-blocks.test.js index 78f08c0bb1279..a25f9a81af95c 100644 --- a/packages/e2e-tests/specs/plugins/inner-blocks-allowed-blocks.test.js +++ b/packages/e2e-tests/specs/plugins/inner-blocks-allowed-blocks.test.js @@ -11,12 +11,7 @@ import { openGlobalBlockInserter, } from '@wordpress/e2e-test-utils'; -// Todo: Understand why this test causing intermitent fails on travis. -// Error: ● Allowed Blocks Setting on InnerBlocks › allows all blocks if the allowed blocks setting was not set -// Node is detached from document -// at ElementHandle._scrollIntoViewIfNeeded (../../node_modules/puppeteer/lib/ElementHandle.js:75:13) -// eslint-disable-next-line jest/no-disabled-tests -describe.skip( 'Allowed Blocks Setting on InnerBlocks ', () => { +describe( 'Allowed Blocks Setting on InnerBlocks ', () => { const paragraphSelector = '.block-editor-rich-text__editable.wp-block-paragraph'; beforeAll( async () => { await activatePlugin( 'gutenberg-test-innerblocks-allowed-blocks' ); From 6e340d6a4dbd0daa9d459e09592200ddcb676eb2 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Mon, 18 Mar 2019 10:03:14 +0100 Subject: [PATCH 686/691] Fix the webpack shortcode config (#14485) --- webpack.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/webpack.config.js b/webpack.config.js index 1550b0712ab0d..dba1661ec492f 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -80,6 +80,7 @@ module.exports = { 'dom-ready', 'redux-routine', 'token-list', + 'shortcode', ].map( camelCaseDash ) ), new CopyWebpackPlugin( gutenbergPackages.map( ( packageName ) => ( { From 15b87b0ab608d59f4845db28474b54f223580cd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Mon, 18 Mar 2019 11:17:46 +0100 Subject: [PATCH 687/691] Docs: Polish @wordpress/scripts README and related tutorial (#14484) * Clarify that JSX is not part of ESNext in JS build setup tutorial * Docs: Polish @wordpress/scripts README and related tutorial * Emphasize that build and start scripts should be used with the default config * Add Advanced information subsection for all scripts * Link the existing tutorial for build and start scripts --- .../tutorials/javascript/js-build-setup.md | 14 ++-- packages/scripts/README.md | 66 +++++++++++++++---- 2 files changed, 60 insertions(+), 20 deletions(-) diff --git a/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md b/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md index ebd65dc6a232d..cbebc043b6054 100644 --- a/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md +++ b/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md @@ -1,6 +1,6 @@ # JavaScript Build Setup -This page covers how to set up your development environment to use the ESNext syntax. ESNext is JavaScript code written using features that are only available in a specification greater than ECMAScript 5 (ES5 for short) or that includes a custom syntax such as [JSX](https://reactjs.org/docs/introducing-jsx.html). +This page covers how to set up your development environment to use the ESNext and [JSX](https://reactjs.org/docs/introducing-jsx.html) syntaxes. ESNext is JavaScript code written using features that are only available in a specification greater than ECMAScript 5 (ES5 for short). JSX is a custom syntax extension to JavaScript which helps you to describe what the UI should look like. This documentation covers development for your plugin to work with Gutenberg. If you want to setup a development environment for developing Gutenberg itself, see the [CONTRIBUTING.md](https://github.com/WordPress/gutenberg/blob/master/CONTRIBUTING.md) documentation. @@ -8,9 +8,9 @@ Most browsers can not interpret or run ESNext and JSX syntaxes, so we use a tran There are a few reasons to use ESNext and the extra step. First, it makes for simpler code that is easier to read and write. Using a transformation step allows for tools to optimize the code to work on the widest variety of browsers. Also, by using a build step you can organize your code into smaller modules and files that can be bundled together into a single download. -There are different tools that can perform this transformation or build step, but WordPress uses Webpack and Babel. +There are different tools that can perform this transformation or build step, but WordPress uses webpack and Babel. -[Webpack](https://webpack.js.org/) is a pluggable tool that processes JavaScript creating a compiled bundle that runs in a browser. [Babel](https://babeljs.io/) transforms JavaScript from one format to another. You use Babel as a plugin to Webpack to transform both ESNext and JSX to JavaScript. +[webpack](https://webpack.js.org/) is a pluggable tool that processes JavaScript creating a compiled bundle that runs in a browser. [Babel](https://babeljs.io/) transforms JavaScript from one format to another. You use Babel as a plugin to webpack to transform both ESNext and JSX to JavaScript. The [@wordpress/scripts](https://www.npmjs.com/package/@wordpress/scripts) package abstracts these libraries away to standardize and simplify development, so you won't need to handle the details for configuring those libraries. @@ -20,7 +20,7 @@ For a quick start, you can use one of the examples from the [Gutenberg Examples ## Setup -Both Webpack and Babel are tools written in JavaScript and run using [Node.js](https://nodejs.org/) (node). Node.js is a runtime environment for JavaScript outside of a browser. Simply put, node allows you to run JavaScript code on the command-line. +Both webpack and Babel are tools written in JavaScript and run using [Node.js](https://nodejs.org/) (node). Node.js is a runtime environment for JavaScript outside of a browser. Simply put, node allows you to run JavaScript code on the command-line. First, you need to set up node for your development environment. The steps required change depending on your operating system, but if you have a package manager installed, setup can be as straightforward as: @@ -93,7 +93,7 @@ Also, if you look at package.json file it will include a new section: ## Webpack & Babel -The `@wordpress/scripts` package handles the dependencies and default configuration for Webpack and Babel. The scripts package expects the source file to compile to be found at `src/index.js`, and will save the compiled output to `build/index.js`. +The `@wordpress/scripts` package handles the dependencies and default configuration for webpack and Babel. The scripts package expects the source file to compile to be found at `src/index.js`, and will save the compiled output to `build/index.js`. With that in mind, let's set up a basic block. Create a file at `src/index.js` with the following content: @@ -136,7 +136,7 @@ The start command can be added to the same scripts section of `package.json`: }, ``` -Now, when you run `npm run start` a watcher will run in the terminal. You can then edit away in your text editor; after each save, it will automatically build. You can then use the familiar edit/save/reload development process. +Now, when you run `npm start` a watcher will run in the terminal. You can then edit away in your text editor; after each save, it will automatically build. You can then use the familiar edit/save/reload development process. **Note:** keep an eye on your terminal for any errors. If you make a typo or syntax error, the build will fail and the error will be in the terminal. @@ -154,6 +154,6 @@ Yes, the initial setup is a bit more involved, but the additional features and b With a setup in place, the standard workflow is: - Install dependencies: `npm install` -- Start development builds: `npm run start` +- Start development builds: `npm start` - Develop. Test. Repeat. - Create production build: `npm run build` diff --git a/packages/scripts/README.md b/packages/scripts/README.md index af2b3a125802b..ed548367b7cd5 100644 --- a/packages/scripts/README.md +++ b/packages/scripts/README.md @@ -1,6 +1,6 @@ # Scripts -Collection of reusable scripts for WordPress development. +Collection of reusable scripts for WordPress development. This package also comes with a recommended configuration for every tool integrated to make the process seamless. Command-line interfaces help to turn working with an app into a pleasant experience, but it is still not enough to keep it easy to maintain in the long run. Developers are left on their own to keep all configurations and dependent tools up to date. This problem multiplies when they own more than one project which shares the same setup. Fortunately, there is a pattern that can simplify maintainers life – reusable scripts. This idea boils down to moving all the necessary configurations and scripts to one single tool dependency. In most cases, it should be possible to accomplish all tasks using the default settings, but some customization is allowed, too. With all that in place updating all projects should become a very straightforward task. @@ -23,22 +23,26 @@ _Example:_ ```json { "scripts": { + "build": "wp-scripts run build", "check-engines": "wp-scripts check-engines", "check-licenses": "wp-scripts check-licenses --production", "lint:css": "wp-scripts lint-style '**/*.css'", "lint:js": "wp-scripts lint-js .", "lint:pkg-json": "wp-scripts lint-pkg-json .", + "start": "wp-scripts start", "test:e2e": "wp-scripts test-e2e", "test:unit": "wp-scripts test-unit-js" } } ``` +It might be also a good idea to get familiar with the [JavaScript Build Setup tutorial](/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md) for setting up a development environment to use ESNext syntax. It gives a very in-depth explanation how to use [build](#build) and [start](#start) scripts. + ## Available Scripts ### `build` -Transforms your code according the configuration provided so it's ready for production and optimized for the best performance. It uses [webpack](https://webpack.js.org/) behind the scenes. It'll lookup for a webpack config in the top-level directory of your package and will use it if it finds one. If none is found, it'll use the default config bundled within `@wordpress/scripts` packages. Learn more in the [webpack config](#webpack-config) section. +Transforms your code according the configuration provided so it's ready for production and optimized for the best performance. The entry point for your project's code should be located inside `src/index.js` file. The output generated will be written to `build/index.js` file. It's similar to [start](#start) script which is better suited for development phase. _Example:_ @@ -54,9 +58,13 @@ This is how you execute the script with presented setup: * `npm run build` - builds the code for production. +#### Advanced information + +This script uses [webpack](https://webpack.js.org/) behind the scenes. It'll lookup for a webpack config in the top-level directory of your package and will use it if it finds one. If none is found, it'll use the default config bundled within `@wordpress/scripts` packages. Learn more in the [Advanced Usage](#advanced-usage) section. + ### `check-engines` -Checks if the current `node`, `npm` (or `yarn`) versions match the given [semantic version](https://semver.org/) ranges. If the given version is not satisfied, information about installing the needed version is printed and the program exits with an error code. It uses [check-node-version](https://www.npmjs.com/package/check-node-version) behind the scenes with the recommended configuration provided. You can specify your own ranges as described in [check-node-version docs](https://www.npmjs.com/package/check-node-version). +Checks if the current `node`, `npm` (or `yarn`) versions match the given [semantic version](https://semver.org/) ranges. If the given version is not satisfied, information about installing the needed version is printed and the program exits with an error code. _Example:_ @@ -72,6 +80,10 @@ This is how you execute the script with presented setup: * `npm run check-engines` - checks installed version of `node` and `npm`. +#### Advanced information + +It uses [check-node-version](https://www.npmjs.com/package/check-node-version) behind the scenes with the recommended configuration provided. You can specify your own ranges as described in [check-node-version docs](https://www.npmjs.com/package/check-node-version). Learn more in the [Advanced Usage](#advanced-usage) section. + ### `check-licenses` Validates that all dependencies of a project are compatible with the project's own license. @@ -95,7 +107,7 @@ _Flags_: ### `lint-js` -Helps enforce coding style guidelines for your JavaScript files. It uses [eslint](https://eslint.org/) with the set of recommended rules defined in [@wordpress/eslint-plugin](https://www.npmjs.com/package/@wordpress/eslint-plugin) npm package. You can override default rules with your own as described in [eslint docs](https://eslint.org/docs/rules/). +Helps enforce coding style guidelines for your JavaScript files. _Example:_ @@ -111,9 +123,13 @@ This is how you execute the script with presented setup: * `npm run lint:js` - lints JavaScript files in the entire project's directories. +#### Advanced information + +It uses [eslint](https://eslint.org/) with the set of recommended rules defined in [@wordpress/eslint-plugin](https://www.npmjs.com/package/@wordpress/eslint-plugin) npm package. You can override default rules with your own as described in [eslint docs](https://eslint.org/docs/rules/). Learn more in the [Advanced Usage](#advanced-usage) section. + ### `lint-pkg-json` -Helps enforce standards for your package.json files. It uses [npm-package-json-lint](https://www.npmjs.com/package/npm-package-json-lint) with the set of recommended rules defined in [@wordpress/npm-package-json-lint-config](https://www.npmjs.com/package/@wordpress/npm-package-json-lint-config) npm package. You can override default rules with your own as described in [npm-package-json-lint wiki](https://github.com/tclindner/npm-package-json-lint/wiki). +Helps enforce standards for your `package.json` files. _Example:_ @@ -129,9 +145,13 @@ This is how you execute those scripts using the presented setup: * `npm run lint:pkg-json` - lints `package.json` file in the project's root folder. +#### Advanced information + +It uses [npm-package-json-lint](https://www.npmjs.com/package/npm-package-json-lint) with the set of recommended rules defined in [@wordpress/npm-package-json-lint-config](https://www.npmjs.com/package/@wordpress/npm-package-json-lint-config) npm package. You can override default rules with your own as described in [npm-package-json-lint wiki](https://github.com/tclindner/npm-package-json-lint/wiki). Learn more in the [Advanced Usage](#advanced-usage) section. + ### `lint-style` -Helps enforce coding style guidelines for your style files. It uses [stylelint](https://github.com/stylelint/stylelint) with the [stylelint-config-wordpress](https://github.com/WordPress-Coding-Standards/stylelint-config-wordpress) configuration per the [WordPress CSS Coding Standards](https://make.wordpress.org/core/handbook/best-practices/coding-standards/css/). You can override them with your own rules as described in [stylelint user guide](https://github.com/stylelint/stylelint/docs/user-guide.md). +Helps enforce coding style guidelines for your style files. _Example:_ @@ -147,9 +167,13 @@ This is how you execute the script with presented setup: * `npm run lint:css` - lints CSS files in the whole project's directory. +#### Advanced information + +It uses [stylelint](https://github.com/stylelint/stylelint) with the [stylelint-config-wordpress](https://github.com/WordPress-Coding-Standards/stylelint-config-wordpress) configuration per the [WordPress CSS Coding Standards](https://make.wordpress.org/core/handbook/best-practices/coding-standards/css/). You can override them with your own rules as described in [stylelint user guide](https://github.com/stylelint/stylelint/docs/user-guide.md). Learn more in the [Advanced Usage](#advanced-usage) section. + ### `start` -Transforms your code according the configuration provided so it's ready for development. The script will automatically rebuild if you make changes to the code, and you will see the build errors in the console. It uses [webpack](https://webpack.js.org/) behind the scenes. It'll lookup for a webpack config in the top-level directory of your package and will use it if it finds one. If none is found, it'll use the default config bundled within `@wordpress/scripts` packages. Learn more in the [webpack config](#webpack-config) section. +Transforms your code according the configuration provided so it's ready for development. The script will automatically rebuild if you make changes to the code, and you will see the build errors in the console. The entry point for your project's code should be located inside `src/index.js` file. The output generated will be written to `build/index.js` file. It's similar to [build](#build) script which is better suited for production usage purpose. _Example:_ @@ -165,11 +189,15 @@ This is how you execute the script with presented setup: * `npm start` - starts the build for development. +#### Advanced information + +It uses [webpack](https://webpack.js.org/) behind the scenes. It'll lookup for a webpack config in the top-level directory of your package and will use it if it finds one. If none is found, it'll use the default config bundled within `@wordpress/scripts` packages. Learn more in the [Advanced Usage](#advanced-usage) section. + ### `test-e2e` -Launches the End-To-End (E2E) test runner. It uses [Jest](https://jestjs.io/) behind the scenes and you are able to utilize all of its [CLI options](https://jestjs.io/docs/en/cli.html). You can also run `./node_modules/.bin/wp-scripts test:e2e --help` or `npm run test:e2e:help` (as presented below) to view all of the available options. +Launches the End-To-End (E2E) test runner. Writing tests can be done using [Jest API](https://jestjs.io/docs/en/api) in combination with [Puppeteer API](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md): -Writing tests can be done using Puppeteer API: +> [Jest](https://jestjs.io/) is a delightful JavaScript Testing Framework with a focus on simplicity. > [Puppeteer](https://pptr.dev/) is a Node library which provides a high-level API to control Chrome or Chromium over the [DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/). Puppeteer runs [headless](https://developers.google.com/web/updates/2017/04/headless-chrome) by default, but can be configured to run full (non-headless) Chrome or Chromium. @@ -194,11 +222,15 @@ This script automatically detects the best config to start Puppeteer but sometim We enforce that all tests run serially in the current process using [--runInBand](https://jestjs.io/docs/en/cli#runinband) Jest CLI option to avoid conflicts between tests caused by the fact that they share the same WordPress instance. +#### Advanced information + +It uses [Jest](https://jestjs.io/) behind the scenes and you are able to utilize all of its [CLI options](https://jestjs.io/docs/en/cli.html). You can also run `./node_modules/.bin/wp-scripts test:e2e --help` or `npm run test:e2e:help` (as presented earlier) to view all of the available options. Learn more in the [Advanced Usage](#advanced-usage) section. + ### `test-unit-js` _Alias_: `test-unit-jest` -Launches the unit test runner. It uses [Jest](https://jestjs.io/) behind the scenes and you are able to utilize all of its [CLI options](https://jestjs.io/docs/en/cli.html). You can also run `./node_modules/.bin/wp-scripts test-unit-js --help` or `npm run test:unit:help` (as presented below) to view all of the available options. By default, it uses the set of recommended options defined in [@wordpress/jest-preset-default](https://www.npmjs.com/package/@wordpress/jest-preset-default) npm package. You can override them with your own options as described in [Jest documentation](https://jestjs.io/docs/en/configuration). +Launches the unit test runner. Writing tests can be done using [Jest API](https://jestjs.io/docs/en/api). _Example:_ @@ -218,11 +250,19 @@ This is how you execute those scripts using the presented setup: * `npm run test:unit:help` - prints all available options to configure unit tests runner. * `npm run test:unit:watch` - runs all unit tests in the watch mode. -## webpack config +#### Advanced information + +It uses [Jest](https://jestjs.io/) behind the scenes and you are able to utilize all of its [CLI options](https://jestjs.io/docs/en/cli.html). You can also run `./node_modules/.bin/wp-scripts test-unit-js --help` or `npm run test:unit:help` (as presented earlier) to view all of the available options. By default, it uses the set of recommended options defined in [@wordpress/jest-preset-default](https://www.npmjs.com/package/@wordpress/jest-preset-default) npm package. You can override them with your own options as described in [Jest documentation](https://jestjs.io/docs/en/configuration). Learn more in the [Advanced Usage](#advanced-usage) section. + +## Advanced Usage + +In general, this package should be used with the set of recommended config files. While it is possible to override every single config file provided, if you have to do it, it means that your use case is more complex than anticipated. If that happens, it would be better to avoid using the whole abstraction layer and setup your project with full control over tooling used. + +### Webpack config The `build` and `start` commands use [webpack](https://webpack.js.org/) behind the scenes. webpack is a tool that helps you transform your code into something else. For example: it can take code written in ESNext and output ES5 compatible code that is minified for production. -### Default webpack config +#### Default webpack config `@wordpress/scripts` bundles the default webpack config used as a base by the WordPress editor. These are the defaults: @@ -240,7 +280,7 @@ lodash | `import x from lodash;` | `var x = window.lodash.x;` lodash-es | `import x from lodash-es;` | `var x = window.lodash.x;` WordPress packages | `import x from '@wordpress/package-name` | `var x = window.wp.packageName.x` -### Provide your own webpack config +#### Provide your own webpack config Should there be any situation where you want to provide your own webpack config, you can do so. The `build` and `start` commands will use your provided file when: From 08eafa618c7d87aef95c493624b6d738eeb1a19b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Mon, 18 Mar 2019 12:06:42 +0100 Subject: [PATCH 688/691] Docs: Update @wordpress/scripts with language improvements (#14488) --- .../tutorials/javascript/js-build-setup.md | 2 +- packages/scripts/README.md | 22 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md b/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md index cbebc043b6054..64f513d605cf9 100644 --- a/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md +++ b/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md @@ -10,7 +10,7 @@ There are a few reasons to use ESNext and the extra step. First, it makes for si There are different tools that can perform this transformation or build step, but WordPress uses webpack and Babel. -[webpack](https://webpack.js.org/) is a pluggable tool that processes JavaScript creating a compiled bundle that runs in a browser. [Babel](https://babeljs.io/) transforms JavaScript from one format to another. You use Babel as a plugin to webpack to transform both ESNext and JSX to JavaScript. +[webpack](https://webpack.js.org/) is a pluggable tool that processes JavaScript, creating a compiled bundle that runs in a browser. [Babel](https://babeljs.io/) transforms JavaScript from one format to another. You use Babel as a plugin to webpack to transform both ESNext and JSX to JavaScript. The [@wordpress/scripts](https://www.npmjs.com/package/@wordpress/scripts) package abstracts these libraries away to standardize and simplify development, so you won't need to handle the details for configuring those libraries. diff --git a/packages/scripts/README.md b/packages/scripts/README.md index ed548367b7cd5..111f5c61639c3 100644 --- a/packages/scripts/README.md +++ b/packages/scripts/README.md @@ -1,6 +1,6 @@ # Scripts -Collection of reusable scripts for WordPress development. This package also comes with a recommended configuration for every tool integrated to make the process seamless. +Collection of reusable scripts for WordPress development. For convenience, every tool provided in this package comes with a recommended configuration. Command-line interfaces help to turn working with an app into a pleasant experience, but it is still not enough to keep it easy to maintain in the long run. Developers are left on their own to keep all configurations and dependent tools up to date. This problem multiplies when they own more than one project which shares the same setup. Fortunately, there is a pattern that can simplify maintainers life – reusable scripts. This idea boils down to moving all the necessary configurations and scripts to one single tool dependency. In most cases, it should be possible to accomplish all tasks using the default settings, but some customization is allowed, too. With all that in place updating all projects should become a very straightforward task. @@ -36,13 +36,13 @@ _Example:_ } ``` -It might be also a good idea to get familiar with the [JavaScript Build Setup tutorial](/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md) for setting up a development environment to use ESNext syntax. It gives a very in-depth explanation how to use [build](#build) and [start](#start) scripts. +It might also be a good idea to get familiar with the [JavaScript Build Setup tutorial](/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md) for setting up a development environment to use ESNext syntax. It gives a very in-depth explanation of how to use the [build](#build) and [start](#start) scripts. ## Available Scripts ### `build` -Transforms your code according the configuration provided so it's ready for production and optimized for the best performance. The entry point for your project's code should be located inside `src/index.js` file. The output generated will be written to `build/index.js` file. It's similar to [start](#start) script which is better suited for development phase. +Transforms your code according the configuration provided so it's ready for production and optimized for the best performance. The entry point for your project's code should be located in `src/index.js`. The output generated will be written to `build/index.js`. This script exits after producing a single build. For incremental builds, better suited for development, see the [start](#start) script. _Example:_ @@ -60,7 +60,7 @@ This is how you execute the script with presented setup: #### Advanced information -This script uses [webpack](https://webpack.js.org/) behind the scenes. It'll lookup for a webpack config in the top-level directory of your package and will use it if it finds one. If none is found, it'll use the default config bundled within `@wordpress/scripts` packages. Learn more in the [Advanced Usage](#advanced-usage) section. +This script uses [webpack](https://webpack.js.org/) behind the scenes. It'll look for a webpack config in the top-level directory of your package and will use it if it finds one. If none is found, it'll use the default config provided by `@wordpress/scripts` packages. Learn more in the [Advanced Usage](#advanced-usage) section. ### `check-engines` @@ -173,7 +173,7 @@ It uses [stylelint](https://github.com/stylelint/stylelint) with the [stylelint- ### `start` -Transforms your code according the configuration provided so it's ready for development. The script will automatically rebuild if you make changes to the code, and you will see the build errors in the console. The entry point for your project's code should be located inside `src/index.js` file. The output generated will be written to `build/index.js` file. It's similar to [build](#build) script which is better suited for production usage purpose. +Transforms your code according the configuration provided so it's ready for development. The script will automatically rebuild if you make changes to the code, and you will see the build errors in the console. The entry point for your project's code should be located in `src/index.js`. The output generated will be written to `build/index.js`. For single builds, better suited for production, see the [build](#build) script. _Example:_ @@ -191,11 +191,11 @@ This is how you execute the script with presented setup: #### Advanced information -It uses [webpack](https://webpack.js.org/) behind the scenes. It'll lookup for a webpack config in the top-level directory of your package and will use it if it finds one. If none is found, it'll use the default config bundled within `@wordpress/scripts` packages. Learn more in the [Advanced Usage](#advanced-usage) section. +This script uses [webpack](https://webpack.js.org/) behind the scenes. It'll look for a webpack config in the top-level directory of your package and will use it if it finds one. If none is found, it'll use the default config provided by `@wordpress/scripts` packages. Learn more in the [Advanced Usage](#advanced-usage) section. ### `test-e2e` -Launches the End-To-End (E2E) test runner. Writing tests can be done using [Jest API](https://jestjs.io/docs/en/api) in combination with [Puppeteer API](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md): +Launches the End-To-End (E2E) test runner. Writing tests can be done using the [Jest API](https://jestjs.io/docs/en/api) in combination with the [Puppeteer API](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md): > [Jest](https://jestjs.io/) is a delightful JavaScript Testing Framework with a focus on simplicity. @@ -224,13 +224,13 @@ We enforce that all tests run serially in the current process using [--runInBand #### Advanced information -It uses [Jest](https://jestjs.io/) behind the scenes and you are able to utilize all of its [CLI options](https://jestjs.io/docs/en/cli.html). You can also run `./node_modules/.bin/wp-scripts test:e2e --help` or `npm run test:e2e:help` (as presented earlier) to view all of the available options. Learn more in the [Advanced Usage](#advanced-usage) section. +It uses [Jest](https://jestjs.io/) behind the scenes and you are able to use all of its [CLI options](https://jestjs.io/docs/en/cli.html). You can also run `./node_modules/.bin/wp-scripts test:e2e --help` or `npm run test:e2e:help` (as mentioned above) to view all of the available options. Learn more in the [Advanced Usage](#advanced-usage) section. ### `test-unit-js` _Alias_: `test-unit-jest` -Launches the unit test runner. Writing tests can be done using [Jest API](https://jestjs.io/docs/en/api). +Launches the unit test runner. Writing tests can be done using the [Jest API](https://jestjs.io/docs/en/api). _Example:_ @@ -252,11 +252,11 @@ This is how you execute those scripts using the presented setup: #### Advanced information -It uses [Jest](https://jestjs.io/) behind the scenes and you are able to utilize all of its [CLI options](https://jestjs.io/docs/en/cli.html). You can also run `./node_modules/.bin/wp-scripts test-unit-js --help` or `npm run test:unit:help` (as presented earlier) to view all of the available options. By default, it uses the set of recommended options defined in [@wordpress/jest-preset-default](https://www.npmjs.com/package/@wordpress/jest-preset-default) npm package. You can override them with your own options as described in [Jest documentation](https://jestjs.io/docs/en/configuration). Learn more in the [Advanced Usage](#advanced-usage) section. +It uses [Jest](https://jestjs.io/) behind the scenes and you are able to use all of its [CLI options](https://jestjs.io/docs/en/cli.html). You can also run `./node_modules/.bin/wp-scripts test:unit --help` or `npm run test:unit:help` (as mentioned above) to view all of the available options. By default, it uses the set of recommended options defined in [@wordpress/jest-preset-default](https://www.npmjs.com/package/@wordpress/jest-preset-default) npm package. You can override them with your own options as described in [Jest documentation](https://jestjs.io/docs/en/configuration). Learn more in the [Advanced Usage](#advanced-usage) section. ## Advanced Usage -In general, this package should be used with the set of recommended config files. While it is possible to override every single config file provided, if you have to do it, it means that your use case is more complex than anticipated. If that happens, it would be better to avoid using the whole abstraction layer and setup your project with full control over tooling used. +In general, this package should be used with the set of recommended config files. While it is possible to override every single config file provided, if you have to do it, it means that your use case is more complex than anticipated. If that happens, it would be better to avoid using the whole abstraction layer and set up your project with full control over tooling used. ### Webpack config From a6c4db84762e356138b250f577b58f64fa2fec91 Mon Sep 17 00:00:00 2001 From: Andrew Ozz <azaozz@users.noreply.github.com> Date: Mon, 18 Mar 2019 04:23:54 -0700 Subject: [PATCH 689/691] Fix missing CSS in the Classic Block (#12441) --- .../block-library/src/classic/editor.scss | 241 ++++++++++++------ 1 file changed, 167 insertions(+), 74 deletions(-) diff --git a/packages/block-library/src/classic/editor.scss b/packages/block-library/src/classic/editor.scss index 758609eb9d549..b4427ec2b21ad 100644 --- a/packages/block-library/src/classic/editor.scss +++ b/packages/block-library/src/classic/editor.scss @@ -1,6 +1,4 @@ .wp-block-freeform.block-library-rich-text__tinymce { - overflow: hidden; - p, li { line-height: $editor-line-height; @@ -120,15 +118,100 @@ background-position: center; } - /** - * The following gallery styles were replicated - * from the styles applied in the tinymce skin, - * /wp-includes/js/tinymce/skins/wordpress/wp-content.css. - */ - .wpview-type-gallery::after { - content: ""; - display: table; + /* Remove blue highlighting of selected images in WebKit */ + img::selection { + background-color: transparent; + } + + div.mceTemp { + -ms-user-select: element; + } + + /* Image captions */ + dl.wp-caption { + margin: 0; /* dl browser reset */ + max-width: 100%; + + a, + img { + display: block; + } + + &, + & * { + -webkit-user-drag: none; + } + + .wp-caption-dd { + padding-top: 0.5em; + margin: 0; /* browser dd reset */ + } + } + + /* WP Views */ + .wpview { + width: 99.99%; /* All IE need hasLayout, incl. 11 (ugh, not again!!) */ + position: relative; clear: both; + margin-bottom: 16px; + border: 1px solid transparent; + + iframe { + display: block; + max-width: 100%; + background: transparent; + } + + .mce-shim { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + } + + &[data-mce-selected="2"] .mce-shim { + display: none; + } + + .loading-placeholder { + border: 1px dashed $light-gray-400; + padding: 10px; + } + + .wpview-error { + border: 1px solid $light-gray-400; + padding: 1em 0; + margin: 0; + word-wrap: break-word; + + p { + margin: 0; + text-align: center; + } + } + + &[data-mce-selected] .loading-placeholder, + &[data-mce-selected] .wpview-error { + border-color: transparent; + } + + .dashicons { + display: block; + margin: 0 auto; + width: 32px; + height: 32px; + font-size: 32px; + } + + // The following gallery styles were replicated + // from the styles applied in the tinymce skin, + // /wp-includes/js/tinymce/skins/wordpress/wp-content.css. + &.wpview-type-gallery::after { + content: ""; + display: table; + clear: both; + } } .gallery img[data-mce-selected]:focus { @@ -180,32 +263,7 @@ } } -.block-editor-block-list__layout .block-editor-block-list__block[data-type="core/freeform"] { - - // Not sure why this is necessary, there seems to be a skin file that overrides this upstream. - .mce-btn.mce-active button, - .mce-btn.mce-active:hover button, - .mce-btn.mce-active i, - .mce-btn.mce-active:hover i { - color: $dark-gray-800; - } - - // Prevent toolbar clipping on heading style in RTL languages - .mce-rtl .mce-flow-layout-item.mce-last { - margin-right: 0; - margin-left: 8px; - } - - // Prevent i tags in buttons from picking up theme editor styles. - .mce-btn i { - font-style: normal; - } - - // Adjust padding to not cause a jump. - .mce-toolbar-grp > div { - padding: 1px 3px; - } - +div[data-type="core/freeform"] { .block-editor-block-list__block-edit::before { transition: border-color 0.1s linear, box-shadow 0.1s linear; border: $border-width solid $light-gray-500; @@ -223,11 +281,41 @@ &.is-hovered .block-editor-block-list__breadcrumb { display: none; } + + .editor-block-contextual-toolbar + div { + margin-top: 0; + padding-top: 0; + } + + // Ensure aligned blocks at end are within the selected block. + &.is-selected .block-library-rich-text__tinymce::after { + content: ""; + display: table; + clear: both; + } } -div[data-type="core/freeform"] .block-editor-block-contextual-toolbar + div { - margin-top: 0; - padding-top: 0; +// mce global styles: the toolbars may get appended to <body> +.mce-toolbar-grp { + // Not sure why this is necessary, there seems to be a skin file that + // overrides this upstream. + .mce-btn.mce-active button, + .mce-btn.mce-active:hover button, + .mce-btn.mce-active i, + .mce-btn.mce-active:hover i { + color: $dark-gray-800; + } + + // Prevent toolbar clipping on heading style in RTL languages + .mce-rtl .mce-flow-layout-item.mce-last { + margin-right: 0; + margin-left: 8px; + } + + // Prevent i tags in buttons from picking up theme editor styles. + .mce-btn i { + font-style: normal; + } } .block-library-classic__toolbar { @@ -251,49 +339,54 @@ div[data-type="core/freeform"] .block-editor-block-contextual-toolbar + div { @include break-small() { padding: 0; } -} -.block-library-classic__toolbar:empty { - height: $block-toolbar-height; - background: #f5f5f5; - border-bottom: $border-width solid #e2e4e7; + &:empty { + height: $block-toolbar-height; + background: #f5f5f5; + border-bottom: $border-width solid #e2e4e7; - &::before { - font-family: $default-font; - font-size: $default-font-size; - content: attr(data-placeholder); - color: #555d66; - line-height: 37px; - padding: $block-padding; + &::before { + font-family: $default-font; + font-size: $default-font-size; + content: attr(data-placeholder); + color: #555d66; + line-height: 37px; + padding: $block-padding; + } } -} -// Overwrite inline styles. -.block-library-classic__toolbar .mce-tinymce-inline, -.block-library-classic__toolbar .mce-tinymce-inline > div, -.block-library-classic__toolbar div.mce-toolbar-grp, -.block-library-classic__toolbar div.mce-toolbar-grp > div, -.block-library-classic__toolbar .mce-menubar, -.block-library-classic__toolbar .mce-menubar > div { - height: auto !important; - width: 100% !important; -} + // Overwrite inline styles. + .mce-tinymce-inline, + .mce-tinymce-inline > div, + div.mce-toolbar-grp, + div.mce-toolbar-grp > div, + .mce-menubar, + .mce-menubar > div { + height: auto !important; + width: 100% !important; + } -.block-library-classic__toolbar .mce-container-body.mce-abs-layout { - overflow: visible; -} + .mce-container-body.mce-abs-layout { + overflow: visible; + } -.block-library-classic__toolbar .mce-menubar, -.block-library-classic__toolbar div.mce-toolbar-grp { - position: static; -} + .mce-menubar, + div.mce-toolbar-grp { + position: static; + } -.block-library-classic__toolbar .mce-toolbar-grp .mce-toolbar:not(:first-child) { - display: none; -} + // Adjust padding to not cause a jump. + .mce-toolbar-grp > div { + padding: 1px 3px; + } + + .mce-toolbar-grp .mce-toolbar:not(:first-child) { + display: none; + } -.block-library-classic__toolbar.has-advanced-toolbar .mce-toolbar-grp .mce-toolbar { - display: block; + &.has-advanced-toolbar .mce-toolbar-grp .mce-toolbar { + display: block; + } } // We don't want the ellipsis to overlap the classic toolbar, which it will due to position sticky. From 4a474b862a6384988afba4582911eb4fbfaa182b Mon Sep 17 00:00:00 2001 From: Jorge <jorge.costa@developer.pt> Date: Mon, 18 Mar 2019 11:34:11 +0000 Subject: [PATCH 690/691] Bump plugin version to 5.3.0-rc.1 --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 5676c589b7d07..27b61a263e06e 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,7 +3,7 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the new block editor in core. - * Version: 5.2.0 + * Version: 5.3.0-rc.1 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index f1897ceec693d..08eb1ed8156a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.2.0", + "version": "5.3.0-rc.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 71bf946b7694e..bf8af595067f4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.2.0", + "version": "5.3.0-rc.1", "private": true, "description": "A new WordPress editor experience", "repository": "git+https://github.com/WordPress/gutenberg.git", From 0563f6f8f9cc2912b67ed6d9ba4a3c910b70e71b Mon Sep 17 00:00:00 2001 From: Jorge <jorge.costa@developer.pt> Date: Wed, 20 Mar 2019 10:21:37 +0000 Subject: [PATCH 691/691] Update plugin version to 5.3 --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 27b61a263e06e..483f41ffc709d 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,7 +3,7 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the new block editor in core. - * Version: 5.3.0-rc.1 + * Version: 5.3.0 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index 08eb1ed8156a9..d8bc6f6abec6f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.3.0-rc.1", + "version": "5.3.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index bf8af595067f4..84c9c90c49a7e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.3.0-rc.1", + "version": "5.3.0", "private": true, "description": "A new WordPress editor experience", "repository": "git+https://github.com/WordPress/gutenberg.git",