diff --git a/docs/contributors/git-workflow.md b/docs/contributors/git-workflow.md new file mode 100644 index 00000000000000..f159b46126e617 --- /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 08b47e1777f2a2..7eaa1cb28661e0 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/designers-developers/developers/backward-compatibility/deprecations.md b/docs/designers-developers/developers/backward-compatibility/deprecations.md index 6ff124b27a34fa..62752b2c54d6f0 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/docs/manifest.json b/docs/manifest.json index 35964c417ab5ec..6e6dbd2e40251c 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -413,18 +413,24 @@ "markdown_source": "https://github.com/raw/WordPress/gutenberg/master/docs/contributors/coding-guidelines.md", "parent": "develop" }, - { - "title": "Block Grammar", - "slug": "grammar", - "markdown_source": "https://github.com/raw/WordPress/gutenberg/master/docs/contributors/grammar.md", - "parent": "develop" - }, { "title": "Testing Overview", "slug": "testing-overview", "markdown_source": "https://github.com/raw/WordPress/gutenberg/master/docs/contributors/testing-overview.md", "parent": "develop" }, + { + "title": "Git Workflow", + "slug": "git-workflow", + "markdown_source": "https://github.com/raw/WordPress/gutenberg/master/docs/contributors/git-workflow.md", + "parent": "develop" + }, + { + "title": "Block Grammar", + "slug": "grammar", + "markdown_source": "https://github.com/raw/WordPress/gutenberg/master/docs/contributors/grammar.md", + "parent": "develop" + }, { "title": "Scripts", "slug": "scripts", diff --git a/docs/toc.json b/docs/toc.json index 4e06b65d552ce7..14bed66d0a75e7 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": []} ]}, diff --git a/gutenberg.php b/gutenberg.php index 13f0e148b57de8..4e0cf4bbc32cde 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; } /** diff --git a/lib/client-assets.php b/lib/client-assets.php index 469310edbe118c..96986825e9bc27 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, diff --git a/package-lock.json b/package-lock.json index 30246ca516f389..83491b6775a238 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/block-library/src/categories/edit.js b/packages/block-library/src/categories/edit.js index ad1dea3ec55bf4..aee240e7384e69 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 }`; diff --git a/packages/block-library/src/columns/editor.scss b/packages/block-library/src/columns/editor.scss index ebd2cbf5fdb9d7..cadafcd2ef360b 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 90c61ea5acb0ff..00322b0afe7997 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; - } } } diff --git a/packages/block-library/src/gallery/style.scss b/packages/block-library/src/gallery/style.scss index a62465bb4f225f..2557f727c5c0e9 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; diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index cbcb3a03185f21..8ab944d274fe53 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -2,35 +2,41 @@ * 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 } from '@wordpress/editor'; -import { Toolbar, ToolbarButton, Spinner } from '@wordpress/components'; +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'; -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 ) { super( props ); this.state = { + showSettings: false, progress: 0, isUploadInProgress: false, + isUploadFailed: false, }; this.mediaUpload = this.mediaUpload.bind( this ); @@ -38,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.onImagePressed = this.onImagePressed.bind( this ); } componentDidMount() { @@ -53,6 +60,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 +78,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 +105,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() { @@ -143,6 +168,14 @@ export default class ImageEdit extends React.Component { ); } + const onImageSettingsButtonPressed = () => { + this.setState( { showSettings: true } ); + }; + + const onImageSettingsClose = () => { + this.setState( { showSettings: false } ); + }; + const toolbarEditButton = ( ); + const getInspectorControls = () => ( + + } + > + {} } /> + + ); + const showSpinner = this.state.isUploadInProgress; const opacity = this.state.isUploadInProgress ? 0.3 : 1; const progress = this.state.progress * 100; return ( - - { showSpinner && } - - { toolbarEditButton } - - - { ( 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 ( - - - - ); - } } - - { ( ! RichText.isEmpty( caption ) > 0 || isSelected ) && ( - - setAttributes( { caption: newCaption } ) } + + + { showSpinner && } + + { toolbarEditButton } + + + - - ) } - + + + { ( 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 ( + + { getInspectorControls() } + + { this.state.isUploadFailed && + + + { __( 'Failed to insert media.\nPlease tap for options.' ) } + + } + + + ); + } } + + { ( ! RichText.isEmpty( caption ) > 0 || isSelected ) && ( + + setAttributes( { caption: newCaption } ) } + /> + + ) } + + ); } } 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 00000000000000..833b8126bfc1ad --- /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; +} diff --git a/packages/block-library/src/latest-posts/edit.js b/packages/block-library/src/latest-posts/edit.js index 650d257603e18d..f6bc6953f2955a 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' ] } /> 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 3bc6fa11fb2396..5237c28495ee79 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 254344e50b11d3..14a9d26984956b 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( 'test', [ phrasingContentReducer ], {} ) ).toEqual( 'test' ); } ); - it( 'should remove invalid phrasing content', () => { - expect( deepFilterHTML( '

test

', [ phrasingContentReducer ], { p: {} } ) ).toEqual( '

test

' ); - } ); - it( 'should normalise the rel attribute', () => { const input = 'WordPress'; const output = 'WordPress'; diff --git a/packages/blocks/src/api/raw-handling/test/utils.js b/packages/blocks/src/api/raw-handling/test/utils.js index 66b0e8ffd0a5e0..efaea872497ae9 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 = '

test

test'; expect( removeInvalidHTML( input, schema ) ).toBe( output ); } ); + + it( 'should remove invalid phrasing content', () => { + const input = '

test

'; + const output = '

test

'; + 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 82f0903824a762..d917b681f94568 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/packages/components/src/color-picker/saturation.js b/packages/components/src/color-picker/saturation.js index 8e86655025e920..90c6f8661bb90b 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.' ) } diff --git a/packages/components/src/server-side-render/README.md b/packages/components/src/server-side-render/README.md index 55b0be7630dabb..df9d75dec8fa4f 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 ed11eac1c518bf..71d649dfbf6440 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 ( - + + + ); } else if ( response.error ) { // translators: %s: error message describing the problem const errorMessage = sprintf( __( 'Error loading block: %s' ), response.errorMsg ); return ( - { errorMessage } + + { errorMessage } + ); } else if ( ! response.length ) { return ( - { __( 'No results found.' ) } + + { __( 'No results found.' ) } + ); } return ( - { response } + + { response } + ); } } 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 a71cf13f1e12c0..0465441ec64965 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(); } diff --git a/packages/edit-post/CHANGELOG.md b/packages/edit-post/CHANGELOG.md index ee41b7e7fb9f1e..db8c68da542295 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 019a19b2e518c8..882e97dd7e83c6 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' ) } + ) }
diff --git a/packages/edit-post/src/components/visual-editor/index.js b/packages/edit-post/src/components/visual-editor/index.js index 03dbcdb3f047be..a8cdc7e06f69df 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 ( - + diff --git a/packages/editor/CHANGELOG.md b/packages/editor/CHANGELOG.md index 30e57fd4f033fe..c5365bd4b44df9 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 ec4bbd60db53f7..8436276c8f99b5 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 00000000000000..c2a117ec6816fb --- /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 ( + { + 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 00000000000000..2d2d0510fd3167 --- /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 ( + + ); +} 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 ed2a4039755b1f..9242734d0e9e4a 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, } } /> - + { selectedBlockClientIds.length > 0 && ( { ( { 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 ; +} diff --git a/packages/editor/src/components/index.js b/packages/editor/src/components/index.js index ebd0a59331198c..b02da43dbfe579 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'; diff --git a/packages/editor/src/components/index.native.js b/packages/editor/src/components/index.native.js index d226b4fe46480c..ebb3fb5b3e94ec 100644 --- a/packages/editor/src/components/index.native.js +++ b/packages/editor/src/components/index.native.js @@ -10,3 +10,5 @@ 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'; +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 00000000000000..439e056b931871 --- /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 ( + + + + { text } + + + + ); +} 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 00000000000000..7ad3a2db4b6933 --- /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 ( + + { label } + { value } + + ); +} 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 00000000000000..781eb01337e4c2 --- /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 ( + + + + + + { leftButton } + + + + { this.props.title } + + + + { rightButton } + + + + + { this.props.children } + + + + + ); + } +} + +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 00000000000000..1062ebceaa8e32 --- /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; +} diff --git a/packages/editor/src/editor-styles.scss b/packages/editor/src/editor-styles.scss index e9d58922c13ca3..6715bd4d0c9877 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 { diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 0ab0b2624c58d8..775abcac663058 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 3463e97d84024b..1f001411b73192 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 0318c85c324c9e..5691c3c5d0c2ba 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 00000000000000..7dba90d4059738 --- /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 0933aba1cc826b..fcba80b48203aa 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 00000000000000..394b88728954df --- /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 00000000000000..035c09a8fa767a --- /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 00000000000000..012b7172bdbb0e --- /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 9e297b5b652d20..fe24a86900ee07 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 86d4211b874d5e..8aaf6e8ee7ac67 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", diff --git a/test/integration/blocks-raw-handling.spec.js b/test/integration/blocks-raw-handling.spec.js index 364658b1ebf9fa..4e0b6c4d60b706 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 00000000000000..8a6b117fa6ed55 --- /dev/null +++ b/test/integration/fixtures/google-docs-table-in.html @@ -0,0 +1 @@ +

One

Two

Three

1

2

3

I

II

III

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 00000000000000..697c2d41ea5cd9 --- /dev/null +++ b/test/integration/fixtures/google-docs-table-out.html @@ -0,0 +1,3 @@ + +
OneTwoThree
123
IIIIII
+