From fbc69aee527a17c0096af832ce3b0e808dcc3d0a Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 21 Mar 2023 14:33:27 +0100 Subject: [PATCH 01/37] Bootstrap the commands package --- docs/manifest.json | 6 + lib/client-assets.php | 11 +- package-lock.json | 250 ++++++++++++++++++ package.json | 1 + packages/commands/.npmrc | 0 packages/commands/CHANGELOG.md | 5 + packages/commands/README.md | 41 +++ packages/commands/package.json | 42 +++ .../commands/src/components/command-menu.js | 60 +++++ packages/commands/src/components/style.scss | 157 +++++++++++ packages/commands/src/hooks/use-command.js | 32 +++ packages/commands/src/index.js | 2 + packages/commands/src/store/actions.js | 41 +++ packages/commands/src/store/index.js | 28 ++ packages/commands/src/store/reducer.js | 28 ++ packages/commands/src/store/selectors.js | 9 + packages/commands/src/style.scss | 1 + packages/edit-site/package.json | 1 + .../edit-site/src/components/layout/index.js | 2 + 19 files changed, 716 insertions(+), 1 deletion(-) create mode 100644 packages/commands/.npmrc create mode 100644 packages/commands/CHANGELOG.md create mode 100644 packages/commands/README.md create mode 100644 packages/commands/package.json create mode 100644 packages/commands/src/components/command-menu.js create mode 100644 packages/commands/src/components/style.scss create mode 100644 packages/commands/src/hooks/use-command.js create mode 100644 packages/commands/src/index.js create mode 100644 packages/commands/src/store/actions.js create mode 100644 packages/commands/src/store/index.js create mode 100644 packages/commands/src/store/reducer.js create mode 100644 packages/commands/src/store/selectors.js create mode 100644 packages/commands/src/style.scss diff --git a/docs/manifest.json b/docs/manifest.json index b1dfb002ac3141..d81cc4b8adf4ee 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -1469,6 +1469,12 @@ "markdown_source": "../packages/browserslist-config/README.md", "parent": "packages" }, + { + "title": "@wordpress/commands", + "slug": "packages-commands", + "markdown_source": "../packages/commands/README.md", + "parent": "packages" + }, { "title": "@wordpress/components", "slug": "packages-components", diff --git a/lib/client-assets.php b/lib/client-assets.php index 0f6e64c27c0144..e0d5468674aa48 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -385,11 +385,20 @@ function gutenberg_register_packages_styles( $styles ) { ); $styles->add_data( 'wp-list-reusable-block', 'rtl', 'replace' ); + gutenberg_override_style( + $styles, + 'wp-commands', + gutenberg_url( 'build/commands/style.css' ), + array(), + $version + ); + $styles->add_data( 'wp-commands', 'rtl', 'replace' ); + gutenberg_override_style( $styles, 'wp-edit-site', gutenberg_url( 'build/edit-site/style.css' ), - array( 'wp-components', 'wp-block-editor', 'wp-editor', 'wp-edit-blocks' ), + array( 'wp-components', 'wp-block-editor', 'wp-editor', 'wp-edit-blocks', 'wp-commands' ), $version ); $styles->add_data( 'wp-edit-site', 'rtl', 'replace' ); diff --git a/package-lock.json b/package-lock.json index 0b2d2e23394a47..5abb343401e12b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7188,6 +7188,164 @@ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.6.0.tgz", "integrity": "sha512-cPqjjzuFWNK3BSKLm0abspP0sp/IGOli4p5I5fKFAzdS8fvjdOwDCfZqAaIiXd9lPkOWi3SUUfZof3hEb7J/uw==" }, + "@radix-ui/primitive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.0.tgz", + "integrity": "sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==", + "requires": { + "@babel/runtime": "^7.13.10" + } + }, + "@radix-ui/react-compose-refs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz", + "integrity": "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==", + "requires": { + "@babel/runtime": "^7.13.10" + } + }, + "@radix-ui/react-context": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.0.tgz", + "integrity": "sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==", + "requires": { + "@babel/runtime": "^7.13.10" + } + }, + "@radix-ui/react-dialog": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.0.0.tgz", + "integrity": "sha512-Yn9YU+QlHYLWwV1XfKiqnGVpWYWk6MeBVM6x/bcoyPvxgjQGoeT35482viLPctTMWoMw0PoHgqfSox7Ig+957Q==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.0", + "@radix-ui/react-compose-refs": "1.0.0", + "@radix-ui/react-context": "1.0.0", + "@radix-ui/react-dismissable-layer": "1.0.0", + "@radix-ui/react-focus-guards": "1.0.0", + "@radix-ui/react-focus-scope": "1.0.0", + "@radix-ui/react-id": "1.0.0", + "@radix-ui/react-portal": "1.0.0", + "@radix-ui/react-presence": "1.0.0", + "@radix-ui/react-primitive": "1.0.0", + "@radix-ui/react-slot": "1.0.0", + "@radix-ui/react-use-controllable-state": "1.0.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.4" + } + }, + "@radix-ui/react-dismissable-layer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.0.tgz", + "integrity": "sha512-n7kDRfx+LB1zLueRDvZ1Pd0bxdJWDUZNQ/GWoxDn2prnuJKRdxsjulejX/ePkOsLi2tTm6P24mDqlMSgQpsT6g==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.0", + "@radix-ui/react-compose-refs": "1.0.0", + "@radix-ui/react-primitive": "1.0.0", + "@radix-ui/react-use-callback-ref": "1.0.0", + "@radix-ui/react-use-escape-keydown": "1.0.0" + } + }, + "@radix-ui/react-focus-guards": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.0.tgz", + "integrity": "sha512-UagjDk4ijOAnGu4WMUPj9ahi7/zJJqNZ9ZAiGPp7waUWJO0O1aWXi/udPphI0IUjvrhBsZJGSN66dR2dsueLWQ==", + "requires": { + "@babel/runtime": "^7.13.10" + } + }, + "@radix-ui/react-focus-scope": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.0.tgz", + "integrity": "sha512-C4SWtsULLGf/2L4oGeIHlvWQx7Rf+7cX/vKOAD2dXW0A1b5QXwi3wWeaEgW+wn+SEVrraMUk05vLU9fZZz5HbQ==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.0", + "@radix-ui/react-primitive": "1.0.0", + "@radix-ui/react-use-callback-ref": "1.0.0" + } + }, + "@radix-ui/react-id": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.0.tgz", + "integrity": "sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-layout-effect": "1.0.0" + } + }, + "@radix-ui/react-portal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.0.tgz", + "integrity": "sha512-a8qyFO/Xb99d8wQdu4o7qnigNjTPG123uADNecz0eX4usnQEj7o+cG4ZX4zkqq98NYekT7UoEQIjxBNWIFuqTA==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-primitive": "1.0.0" + } + }, + "@radix-ui/react-presence": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.0.tgz", + "integrity": "sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.0", + "@radix-ui/react-use-layout-effect": "1.0.0" + } + }, + "@radix-ui/react-primitive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.0.tgz", + "integrity": "sha512-EyXe6mnRlHZ8b6f4ilTDrXmkLShICIuOTTj0GX4w1rp+wSxf3+TD05u1UOITC8VsJ2a9nwHvdXtOXEOl0Cw/zQ==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-slot": "1.0.0" + } + }, + "@radix-ui/react-slot": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.0.tgz", + "integrity": "sha512-3mrKauI/tWXo1Ll+gN5dHcxDPdm/Df1ufcDLCecn+pnCIVcdWE7CujXo8QaXOWRJyZyQWWbpB8eFwHzWXlv5mQ==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.0" + } + }, + "@radix-ui/react-use-callback-ref": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz", + "integrity": "sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==", + "requires": { + "@babel/runtime": "^7.13.10" + } + }, + "@radix-ui/react-use-controllable-state": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.0.tgz", + "integrity": "sha512-FohDoZvk3mEXh9AWAVyRTYR4Sq7/gavuofglmiXB2g1aKyboUD4YtgWxKj8O5n+Uak52gXQ4wKz5IFST4vtJHg==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-callback-ref": "1.0.0" + } + }, + "@radix-ui/react-use-escape-keydown": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.0.tgz", + "integrity": "sha512-JwfBCUIfhXRxKExgIqGa4CQsiMemo1Xt0W/B4ei3fpzpvPENKpMKQ8mZSB6Acj3ebrAEgi2xiQvcI1PAAodvyg==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-callback-ref": "1.0.0" + } + }, + "@radix-ui/react-use-layout-effect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.0.tgz", + "integrity": "sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==", + "requires": { + "@babel/runtime": "^7.13.10" + } + }, "@react-native-clipboard/clipboard": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@react-native-clipboard/clipboard/-/clipboard-1.9.0.tgz", @@ -16887,6 +17045,17 @@ "version": "file:packages/browserslist-config", "dev": true }, + "@wordpress/commands": { + "version": "file:packages/commands", + "requires": { + "@babel/runtime": "^7.16.0", + "@wordpress/data": "file:packages/data", + "@wordpress/element": "file:packages/element", + "@wordpress/i18n": "file:packages/i18n", + "cmdk": "^0.2.0", + "rememo": "^4.0.0" + } + }, "@wordpress/components": { "version": "file:packages/components", "requires": { @@ -17253,6 +17422,7 @@ "@wordpress/block-editor": "file:packages/block-editor", "@wordpress/block-library": "file:packages/block-library", "@wordpress/blocks": "file:packages/blocks", + "@wordpress/commands": "file:packages/commands", "@wordpress/components": "file:packages/components", "@wordpress/compose": "file:packages/compose", "@wordpress/core-data": "file:packages/core-data", @@ -25316,6 +25486,14 @@ } } }, + "aria-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.3.tgz", + "integrity": "sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==", + "requires": { + "tslib": "^2.0.0" + } + }, "aria-query": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", @@ -28714,6 +28892,15 @@ "mkdirp-infer-owner": "^2.0.0" } }, + "cmdk": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-0.2.0.tgz", + "integrity": "sha512-JQpKvEOb86SnvMZbYaFKYhvzFntWBeSZdyii0rZPhKJj9uwJBxu4DaVYDrRN7r3mPop56oPhRw+JYWTKs66TYw==", + "requires": { + "@radix-ui/react-dialog": "1.0.0", + "command-score": "0.1.2" + } + }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -28856,6 +29043,11 @@ "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==" }, + "command-score": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/command-score/-/command-score-0.1.2.tgz", + "integrity": "sha512-VtDvQpIJBvBatnONUsPzXYFVKQQAhuf3XTNOAsdBxCNO/QCtUUd8LSgjn0GVarBkCad6aJCZfXgrjYbl/KRr7w==" + }, "commander": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-9.2.0.tgz", @@ -30996,6 +31188,11 @@ "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", "dev": true }, + "detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" + }, "detect-package-manager": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/detect-package-manager/-/detect-package-manager-2.0.1.tgz", @@ -35182,6 +35379,11 @@ } } }, + "get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==" + }, "get-own-enumerable-property-symbols": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", @@ -49609,6 +49811,27 @@ "integrity": "sha512-PgidR3wST3dDYKr6b4pJoqQFpPGNKDSCDx4cZoshjXipw3LzO7mG1My2pwEzz2JVkF+inx3xRpDeQLFQGH/hsQ==", "dev": true }, + "react-remove-scroll": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.4.tgz", + "integrity": "sha512-xGVKJJr0SJGQVirVFAUZ2k1QLyO6m+2fy0l8Qawbp5Jgrv3DeLalrfMNBFSlmz5kriGGzsVBtGVnf4pTKIhhWA==", + "requires": { + "react-remove-scroll-bar": "^2.3.3", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + } + }, + "react-remove-scroll-bar": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz", + "integrity": "sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==", + "requires": { + "react-style-singleton": "^2.2.1", + "tslib": "^2.0.0" + } + }, "react-shallow-renderer": { "version": "16.15.0", "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", @@ -49637,6 +49860,16 @@ "throttle-debounce": "^3.0.1" } }, + "react-style-singleton": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", + "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", + "requires": { + "get-nonce": "^1.0.0", + "invariant": "^2.2.4", + "tslib": "^2.0.0" + } + }, "react-test-renderer": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-18.2.0.tgz", @@ -56141,6 +56374,14 @@ "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" }, + "use-callback-ref": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.0.tgz", + "integrity": "sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==", + "requires": { + "tslib": "^2.0.0" + } + }, "use-lilius": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/use-lilius/-/use-lilius-2.0.1.tgz", @@ -56161,6 +56402,15 @@ "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.1.tgz", "integrity": "sha512-oFfsyun+bP7RX8X2AskHNTxu+R3QdE/RC5IefMbqptmACAA/gfol1KDD5KRzPsGMa62sWxGZw+Ui43u6x4ddoQ==" }, + "use-sidecar": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", + "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", + "requires": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + } + }, "use-subscription": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/use-subscription/-/use-subscription-1.5.0.tgz", diff --git a/package.json b/package.json index 186ff4749cf5b8..78185fbb5b285b 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "@wordpress/block-serialization-default-parser": "file:packages/block-serialization-default-parser", "@wordpress/block-serialization-spec-parser": "file:packages/block-serialization-spec-parser", "@wordpress/blocks": "file:packages/blocks", + "@wordpress/commands": "file:packages/commands", "@wordpress/components": "file:packages/components", "@wordpress/compose": "file:packages/compose", "@wordpress/core-data": "file:packages/core-data", diff --git a/packages/commands/.npmrc b/packages/commands/.npmrc new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/packages/commands/CHANGELOG.md b/packages/commands/CHANGELOG.md new file mode 100644 index 00000000000000..e04ce921cdfdc4 --- /dev/null +++ b/packages/commands/CHANGELOG.md @@ -0,0 +1,5 @@ + + +## Unreleased + +Initial release. diff --git a/packages/commands/README.md b/packages/commands/README.md new file mode 100644 index 00000000000000..b56dd2fccc8c62 --- /dev/null +++ b/packages/commands/README.md @@ -0,0 +1,41 @@ +# Keyboard Shortcuts + +Commands is a generic package that allows registering and modifying commands to be displayed using the commands menu (Also called cmd+k). + +## Installation + +Install the module + +```bash +npm install @wordpress/commands --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 such language features and APIs, you should include [the polyfill shipped in `@wordpress/babel-preset-default`](https://github.com/WordPress/gutenberg/tree/HEAD/packages/babel-preset-default#polyfill) in your code._ + +## API + + + +### CommandMenu + +Undocumented declaration. + +### useCommand + +Attach a command to the Global command menu. + +_Parameters_ + +- _name_ `string`: Command name. +- _label_ `string`: Command label. +- _callback_ `Function`: Command callback. + + + +## Contributing to this package + +This is an individual package that's part of the Gutenberg project. The project is organized as a monorepo. It's made up of multiple self-contained software packages, each with a specific purpose. The packages in this monorepo are published to [npm](https://www.npmjs.com/) and used by [WordPress](https://make.wordpress.org/core/) as well as other software projects. + +To find out more about contributing to this package or Gutenberg as a whole, please read the project's main [contributor guide](https://github.com/WordPress/gutenberg/tree/HEAD/CONTRIBUTING.md). + +

Code is Poetry.

diff --git a/packages/commands/package.json b/packages/commands/package.json new file mode 100644 index 00000000000000..038f34606dea46 --- /dev/null +++ b/packages/commands/package.json @@ -0,0 +1,42 @@ +{ + "name": "@wordpress/commands", + "version": "0.1.0", + "description": "Handles the commands menu.", + "author": "The WordPress Contributors", + "license": "GPL-2.0-or-later", + "keywords": [ + "wordpress", + "gutenberg", + "commands", + "k" + ], + "homepage": "https://github.com/WordPress/gutenberg/tree/HEAD/packages/commands/README.md", + "repository": { + "type": "git", + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/commands" + }, + "bugs": { + "url": "https://github.com/WordPress/gutenberg/issues" + }, + "engines": { + "node": ">=12" + }, + "main": "build/index.js", + "module": "build-module/index.js", + "react-native": "src/index", + "dependencies": { + "@babel/runtime": "^7.16.0", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "cmdk": "^0.2.0", + "rememo": "^4.0.0" + }, + "peerDependencies": { + "react": "^18.0.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/commands/src/components/command-menu.js b/packages/commands/src/components/command-menu.js new file mode 100644 index 00000000000000..624b49474fc84a --- /dev/null +++ b/packages/commands/src/components/command-menu.js @@ -0,0 +1,60 @@ +/** + * External dependencies + */ +import { Command } from 'cmdk'; + +/** + * WordPress dependencies + */ +import { useSelect } from '@wordpress/data'; +import { useState, useEffect } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { store as commandsStore } from '../store'; + +export function CommandMenu() { + const commands = useSelect( ( select ) => + select( commandsStore ).getCommands() + ); + + const [ open, setOpen ] = useState( false ); + + // Toggle the menu when ⌘K is pressed + useEffect( () => { + const down = ( e ) => { + if ( e.key === 'k' && e.metaKey ) { + setOpen( ( prevOpen ) => ! prevOpen ); + } + }; + + document.addEventListener( 'keydown', down ); + return () => document.removeEventListener( 'keydown', down ); + }, [] ); + + return ( + + + + No results found. + + Apple + { commands.map( ( command ) => ( + + { command.label } + + ) ) } + + + ); +} diff --git a/packages/commands/src/components/style.scss b/packages/commands/src/components/style.scss new file mode 100644 index 00000000000000..303d2f3fef57d5 --- /dev/null +++ b/packages/commands/src/components/style.scss @@ -0,0 +1,157 @@ +:root { + --cmdk-shadow: 0 16px 70px rgba(0, 0, 0, 0.2); +} + +[cmdk-dialog] { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; +} + +[cmdk-root] { + max-width: 640px; + width: 100%; + padding: 8px; + background: $white; + border-radius: 12px; + overflow: hidden; + font-family: var(--font-sans); + border: 1px solid var(--gray6); + box-shadow: var(--cmdk-shadow); + transition: transform 100ms ease; +} + +[cmdk-linear-badge] { + height: 24px; + padding: 0 8px; + font-size: 12px; + color: $gray-800; + background: $gray-300; + border-radius: 4px; + width: fit-content; + display: flex; + align-items: center; + margin: 16px 16px 0; +} + +[cmdk-linear-shortcuts] { + display: flex; + margin-left: auto; + gap: 8px; + + kbd { + font-size: 13px; + color: $gray-800; + } +} + +[cmdk-input] { + border: none; + width: 100%; + font-size: 18px; + padding: 20px; + outline: none; + color: $gray-900; + border-radius: 0; + caret-color: #6e5ed2; + margin: 0; + + &::placeholder { + color: var(--gray9); + } + &:focus { + border-bottom: 1px solid $gray-300; + box-shadow: none; + } +} + +[cmdk-item] { + content-visibility: auto; + + cursor: pointer; + height: 48px; + font-size: 14px; + display: flex; + align-items: center; + gap: 12px; + padding: 0 16px; + color: $gray-900; + user-select: none; + will-change: background, color; + transition: all 150ms ease; + transition-property: none; + position: relative; + + &[aria-selected="true"] { + background: $gray-100; + + svg { + color: $gray-900; + } + + &::after { + content: ""; + position: absolute; + left: 0; + z-index: 123; + width: 3px; + height: 100%; + background: #5f6ad2; + } + } + + &[aria-disabled="true"] { + color: $gray-600; + cursor: not-allowed; + } + + &:active { + transition-property: background; + background: $gray-300; + } + + & + [cmdk-item] { + margin-top: 4px; + } + + svg, + .dashicon { + width: 16px; + height: 16px; + color: $gray-800; + } +} + +[cmdk-list] { + min-height: 300px; + max-height: 400px; + overflow: auto; + overscroll-behavior: contain; + transition: 100ms ease; + transition-property: height; +} + +[cmdk-group-heading] { + user-select: none; + font-size: 12px; + color: $gray-800; + padding: 16px; + display: flex; + align-items: center; +} + +[cmdk-empty] { + font-size: 14px; + display: flex; + align-items: center; + justify-content: center; + height: 64px; + white-space: pre-wrap; + color: $gray-800; +} diff --git a/packages/commands/src/hooks/use-command.js b/packages/commands/src/hooks/use-command.js new file mode 100644 index 00000000000000..0ca5749bae3ed1 --- /dev/null +++ b/packages/commands/src/hooks/use-command.js @@ -0,0 +1,32 @@ +/** + * WordPress dependencies + */ +import { useEffect, useRef } from '@wordpress/element'; +import { useDispatch } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { store as commandsStore } from '../store'; + +/** + * Attach a command to the Global command menu. + * + * @param {string} name Command name. + * @param {string} label Command label. + * @param {Function} callback Command callback. + */ +export default function useCommand( name, label, callback ) { + const { registerCommand, unregisterCommand } = useDispatch( commandsStore ); + const currentCallback = useRef( callback ); + useEffect( () => { + currentCallback.current = callback; + }, [ callback ] ); + + useEffect( () => { + registerCommand( { name, label, callback: currentCallback.current } ); + return () => { + unregisterCommand( name ); + }; + }, [ name, label, registerCommand, unregisterCommand ] ); +} diff --git a/packages/commands/src/index.js b/packages/commands/src/index.js new file mode 100644 index 00000000000000..6329b31d6c61ac --- /dev/null +++ b/packages/commands/src/index.js @@ -0,0 +1,2 @@ +export { default as useCommand } from './hooks/use-command'; +export { CommandMenu } from './components/command-menu'; diff --git a/packages/commands/src/store/actions.js b/packages/commands/src/store/actions.js new file mode 100644 index 00000000000000..e50efbdc52c70f --- /dev/null +++ b/packages/commands/src/store/actions.js @@ -0,0 +1,41 @@ +/** @typedef {import('@wordpress/keycodes').WPKeycodeModifier} WPKeycodeModifier */ + +/** + * Configuration of a registered keyboard shortcut. + * + * @typedef {Object} WPCommandConfig + * + * @property {string} name Command name. + * @property {string} label Command label. + * @property {Function} callback Command callback. + */ + +/** + * Returns an action object used to register a new command. + * + * @param {WPCommandConfig} config Command config. + * + * @return {Object} action. + */ +export function registerCommand( { name, label, callback } ) { + return { + type: 'REGISTER_COMMAND', + name, + label, + callback, + }; +} + +/** + * Returns an action object used to unregister a command. + * + * @param {string} name Command name. + * + * @return {Object} action. + */ +export function unregisterCommand( name ) { + return { + type: 'UNREGISTER_COMMAND', + name, + }; +} diff --git a/packages/commands/src/store/index.js b/packages/commands/src/store/index.js new file mode 100644 index 00000000000000..38db89576ab871 --- /dev/null +++ b/packages/commands/src/store/index.js @@ -0,0 +1,28 @@ +/** + * WordPress dependencies + */ +import { createReduxStore, register } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import reducer from './reducer'; +import * as actions from './actions'; +import * as selectors from './selectors'; + +const STORE_NAME = 'core/commands'; + +/** + * Store definition for the commands namespace. + * + * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/data/README.md#createReduxStore + * + * @type {Object} + */ +export const store = createReduxStore( STORE_NAME, { + reducer, + actions, + selectors, +} ); + +register( store ); diff --git a/packages/commands/src/store/reducer.js b/packages/commands/src/store/reducer.js new file mode 100644 index 00000000000000..dbd928f36f612b --- /dev/null +++ b/packages/commands/src/store/reducer.js @@ -0,0 +1,28 @@ +/** + * Reducer returning the registered commands + * + * @param {Object} state Current state. + * @param {Object} action Dispatched action. + * + * @return {Object} Updated state. + */ +function reducer( state = {}, action ) { + switch ( action.type ) { + case 'REGISTER_COMMAND': + return { + ...state, + [ action.name ]: { + name: action.name, + label: action.label, + callback: action.callback, + }, + }; + case 'UNREGISTER_COMMAND': + const { [ action.name ]: actionName, ...remainingState } = state; + return remainingState; + } + + return state; +} + +export default reducer; diff --git a/packages/commands/src/store/selectors.js b/packages/commands/src/store/selectors.js new file mode 100644 index 00000000000000..589c45a68a5e77 --- /dev/null +++ b/packages/commands/src/store/selectors.js @@ -0,0 +1,9 @@ +/** + * External dependencies + */ +import createSelector from 'rememo'; + +export const getCommands = createSelector( + ( state ) => Object.values( state ), + ( state ) => [ state ] +); diff --git a/packages/commands/src/style.scss b/packages/commands/src/style.scss new file mode 100644 index 00000000000000..e8cce79ec9f956 --- /dev/null +++ b/packages/commands/src/style.scss @@ -0,0 +1 @@ +@import "./components/style.scss"; diff --git a/packages/edit-site/package.json b/packages/edit-site/package.json index 13429eb1963f8a..ac36c756f9f3a1 100644 --- a/packages/edit-site/package.json +++ b/packages/edit-site/package.json @@ -32,6 +32,7 @@ "@wordpress/block-editor": "file:../block-editor", "@wordpress/block-library": "file:../block-library", "@wordpress/blocks": "file:../blocks", + "@wordpress/commands": "file:../commands", "@wordpress/components": "file:../components", "@wordpress/compose": "file:../compose", "@wordpress/core-data": "file:../core-data", diff --git a/packages/edit-site/src/components/layout/index.js b/packages/edit-site/src/components/layout/index.js index 2df7c5aa3c4820..26063bbb712da9 100644 --- a/packages/edit-site/src/components/layout/index.js +++ b/packages/edit-site/src/components/layout/index.js @@ -22,6 +22,7 @@ import { __ } from '@wordpress/i18n'; import { useState, useRef } from '@wordpress/element'; import { NavigableRegion } from '@wordpress/interface'; import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; +import { CommandMenu } from '@wordpress/commands'; /** * Internal dependencies @@ -123,6 +124,7 @@ export default function Layout() { return ( <> + { fullResizer } From 5a4db2ebc6ac75f01148a08dd15a106b04225324 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 23 Mar 2023 14:39:10 +0100 Subject: [PATCH 02/37] Add commands loaders --- packages/commands/README.md | 4 +- .../commands/src/components/command-menu.js | 79 ++++++++++++++----- packages/commands/src/hooks/use-command.js | 29 ++++--- packages/commands/src/store/actions.js | 46 ++++++++++- packages/commands/src/store/reducer.js | 57 +++++++++++-- packages/commands/src/store/selectors.js | 9 ++- 6 files changed, 183 insertions(+), 41 deletions(-) diff --git a/packages/commands/README.md b/packages/commands/README.md index b56dd2fccc8c62..ab27db25b55f7c 100644 --- a/packages/commands/README.md +++ b/packages/commands/README.md @@ -26,9 +26,7 @@ Attach a command to the Global command menu. _Parameters_ -- _name_ `string`: Command name. -- _label_ `string`: Command label. -- _callback_ `Function`: Command callback. +- _command_ `import('../store/actions').WPCommandConfig`: command config. diff --git a/packages/commands/src/components/command-menu.js b/packages/commands/src/components/command-menu.js index 624b49474fc84a..dd9e90cd3086c8 100644 --- a/packages/commands/src/components/command-menu.js +++ b/packages/commands/src/components/command-menu.js @@ -7,7 +7,7 @@ import { Command } from 'cmdk'; * WordPress dependencies */ import { useSelect } from '@wordpress/data'; -import { useState, useEffect } from '@wordpress/element'; +import { useState, useEffect, useRef } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; /** @@ -15,12 +15,63 @@ import { __ } from '@wordpress/i18n'; */ import { store as commandsStore } from '../store'; -export function CommandMenu() { - const commands = useSelect( ( select ) => - select( commandsStore ).getCommands() +function CommandsPerPage( { navigateToPage, loader, commands } ) { + const { isLoading, loaderCommands } = loader(); + const allCommands = [ ...commands, ...loaderCommands ]; + + return ( + + { ! isLoading && ! allCommands?.length && ( + { __( 'No results found.' ) } + ) } + + { isLoading && ( + { __( 'Hang on…' ) } + ) } + + Apple + { allCommands.map( ( command ) => ( + command.callback( { navigateToPage } ) } + > + { command.label } + + ) ) } + ); +} +export function CommandMenu() { const [ open, setOpen ] = useState( false ); + const [ pages, setPages ] = useState( [] ); + const navigateToPage = ( newPage ) => setPages( [ ...pages, newPage ] ); + const currentPage = pages.length ? pages[ pages.length - 1 ] : null; + const { commands, loader } = useSelect( + ( select ) => { + const { getCommands, getLoader } = select( commandsStore ); + return { + commands: getCommands( currentPage ), + loader: getLoader( currentPage ), + }; + }, + [ currentPage ] + ); + + // loader is actually a custom hook + // so to avoid breaking the rules of hooks + // the CommandsPerPage component need to be + // remounted on each loader change + // We use the key state to make sure we do that properly. + const currentLoader = useRef( loader ); + const [ key, setKey ] = useState( 0 ); + useEffect( () => { + if ( currentLoader.current !== loader ) { + currentLoader.current = loader; + setKey( ( prevKey ) => prevKey + 1 ); + } + }, [ loader ] ); // Toggle the menu when ⌘K is pressed useEffect( () => { @@ -41,20 +92,12 @@ export function CommandMenu() { label={ __( 'Global Command Menu' ) } > - - No results found. - - Apple - { commands.map( ( command ) => ( - - { command.label } - - ) ) } - + ); } diff --git a/packages/commands/src/hooks/use-command.js b/packages/commands/src/hooks/use-command.js index 0ca5749bae3ed1..98830734bcda40 100644 --- a/packages/commands/src/hooks/use-command.js +++ b/packages/commands/src/hooks/use-command.js @@ -12,21 +12,30 @@ import { store as commandsStore } from '../store'; /** * Attach a command to the Global command menu. * - * @param {string} name Command name. - * @param {string} label Command label. - * @param {Function} callback Command callback. + * @param {import('../store/actions').WPCommandConfig} command command config. */ -export default function useCommand( name, label, callback ) { +export default function useCommand( command ) { const { registerCommand, unregisterCommand } = useDispatch( commandsStore ); - const currentCallback = useRef( callback ); + const currentCallback = useRef( command.callback ); useEffect( () => { - currentCallback.current = callback; - }, [ callback ] ); + currentCallback.current = command.callback; + }, [ command.callback ] ); useEffect( () => { - registerCommand( { name, label, callback: currentCallback.current } ); + registerCommand( { + name: command.name, + page: command.page, + label: command.label, + callback: currentCallback.current, + } ); return () => { - unregisterCommand( name ); + unregisterCommand( command.name ); }; - }, [ name, label, registerCommand, unregisterCommand ] ); + }, [ + command.name, + command.label, + command.page, + registerCommand, + unregisterCommand, + ] ); } diff --git a/packages/commands/src/store/actions.js b/packages/commands/src/store/actions.js index e50efbdc52c70f..6dd50f3db3aa8e 100644 --- a/packages/commands/src/store/actions.js +++ b/packages/commands/src/store/actions.js @@ -7,9 +7,23 @@ * * @property {string} name Command name. * @property {string} label Command label. + * @property {string=} page Command page. * @property {Function} callback Command callback. */ +/** + * @typedef {(search: string) => WPCommandConfig[]} WPCommandLoaderHook hoo + */ + +/** + * Command loader config. + * + * @typedef {Object} WPCommandLoaderConfig + * + * @property {string=} page Command loader page. + * @property {WPCommandLoaderHook} hook Command loader hook. + */ + /** * Returns an action object used to register a new command. * @@ -17,12 +31,13 @@ * * @return {Object} action. */ -export function registerCommand( { name, label, callback } ) { +export function registerCommand( { name, label, callback, page = null } ) { return { type: 'REGISTER_COMMAND', name, label, callback, + page, }; } @@ -39,3 +54,32 @@ export function unregisterCommand( name ) { name, }; } + +/** + * Register command loader. + * + * @param {WPCommandLoaderConfig} config Command loader config. + * + * @return {Object} action. + */ +export function registerCommandLoader( { page, hook } ) { + return { + type: 'REGISTER_COMMAND_LOADER', + page, + hook, + }; +} + +/** + * Unregister command loader hook. + * + * @param {string} page Command loader page. + * + * @return {Object} action. + */ +export function unregisterCommandLoader( page ) { + return { + type: 'UNREGISTER_COMMAND_LOADER', + page, + }; +} diff --git a/packages/commands/src/store/reducer.js b/packages/commands/src/store/reducer.js index dbd928f36f612b..63e1306f9fafee 100644 --- a/packages/commands/src/store/reducer.js +++ b/packages/commands/src/store/reducer.js @@ -1,3 +1,8 @@ +/** + * WordPress dependencies + */ +import { combineReducers } from '@wordpress/data'; + /** * Reducer returning the registered commands * @@ -6,23 +11,61 @@ * * @return {Object} Updated state. */ -function reducer( state = {}, action ) { +function commands( state = {}, action ) { switch ( action.type ) { case 'REGISTER_COMMAND': return { ...state, - [ action.name ]: { - name: action.name, - label: action.label, - callback: action.callback, + [ action.page ]: { + ...state[ action.page ], + [ action.name ]: { + name: action.name, + label: action.label, + page: action.page, + callback: action.callback, + }, }, }; - case 'UNREGISTER_COMMAND': - const { [ action.name ]: actionName, ...remainingState } = state; + case 'UNREGISTER_COMMAND': { + const { [ action.name ]: _, ...remainingState } = + state?.[ action.page ]; + return { + ...state, + [ action.page ]: remainingState, + }; + } + } + + return state; +} + +/** + * Reducer returning the command loaders + * + * @param {Object} state Current state. + * @param {Object} action Dispatched action. + * + * @return {Object} Updated state. + */ +function commandLoaders( state = {}, action ) { + switch ( action.type ) { + case 'REGISTER_COMMAND_LOADER': + return { + ...state, + [ action.page ]: action.hook, + }; + case 'UNREGISTER_COMMAND_LOADER': { + const { [ action.page ]: _, ...remainingState } = state; return remainingState; + } } return state; } +const reducer = combineReducers( { + commands, + commandLoaders, +} ); + export default reducer; diff --git a/packages/commands/src/store/selectors.js b/packages/commands/src/store/selectors.js index 589c45a68a5e77..d843fc2d40522f 100644 --- a/packages/commands/src/store/selectors.js +++ b/packages/commands/src/store/selectors.js @@ -4,6 +4,11 @@ import createSelector from 'rememo'; export const getCommands = createSelector( - ( state ) => Object.values( state ), - ( state ) => [ state ] + ( state, page ) => Object.values( state.commands[ page ] ), + ( state, page ) => [ state.commands[ page ] ] +); + +export const getCommandLoader = createSelector( + ( state, page ) => Object.values( state.commandLoaders[ page ] ), + ( state, page ) => [ state.commandLoaders[ page ] ] ); From 2f2cceafb4668ae4220091d886b9651e256acecc Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Fri, 24 Mar 2023 10:44:11 +0100 Subject: [PATCH 03/37] Add navigation commands --- packages/commands/README.md | 8 +++ .../commands/src/components/command-menu.js | 16 +++-- .../commands/src/hooks/use-command-loader.js | 29 ++++++++ packages/commands/src/index.js | 1 + packages/commands/src/store/selectors.js | 6 +- .../edit-site/src/components/layout/index.js | 2 + .../hooks/commands/use-navigation-commands.js | 70 +++++++++++++++++++ 7 files changed, 123 insertions(+), 9 deletions(-) create mode 100644 packages/commands/src/hooks/use-command-loader.js create mode 100644 packages/edit-site/src/hooks/commands/use-navigation-commands.js diff --git a/packages/commands/README.md b/packages/commands/README.md index ab27db25b55f7c..eebf64a06c2a9c 100644 --- a/packages/commands/README.md +++ b/packages/commands/README.md @@ -28,6 +28,14 @@ _Parameters_ - _command_ `import('../store/actions').WPCommandConfig`: command config. +### useCommandLoader + +Attach a command loader to the Global command menu. + +_Parameters_ + +- _loader_ `import('../store/actions').WPCommandLoaderConfig`: command loader config. + ## Contributing to this package diff --git a/packages/commands/src/components/command-menu.js b/packages/commands/src/components/command-menu.js index dd9e90cd3086c8..ca8001cbc6afc3 100644 --- a/packages/commands/src/components/command-menu.js +++ b/packages/commands/src/components/command-menu.js @@ -15,8 +15,9 @@ import { __ } from '@wordpress/i18n'; */ import { store as commandsStore } from '../store'; -function CommandsPerPage( { navigateToPage, loader, commands } ) { - const { isLoading, loaderCommands } = loader(); +function CommandsPerPage( { search, navigateToPage, loader, commands } ) { + const { isLoading, commands: loaderCommands = [] } = + loader( { search } ) ?? {}; const allCommands = [ ...commands, ...loaderCommands ]; return ( @@ -29,12 +30,11 @@ function CommandsPerPage( { navigateToPage, loader, commands } ) { { __( 'Hang on…' ) } ) } - Apple { allCommands.map( ( command ) => ( command.callback( { navigateToPage } ) } + onSelect={ () => command.callback( { navigateToPage } ) } > { command.label } @@ -44,16 +44,17 @@ function CommandsPerPage( { navigateToPage, loader, commands } ) { } export function CommandMenu() { + const [ search, setSearch ] = useState( '' ); const [ open, setOpen ] = useState( false ); const [ pages, setPages ] = useState( [] ); const navigateToPage = ( newPage ) => setPages( [ ...pages, newPage ] ); const currentPage = pages.length ? pages[ pages.length - 1 ] : null; const { commands, loader } = useSelect( ( select ) => { - const { getCommands, getLoader } = select( commandsStore ); + const { getCommands, getCommandLoader } = select( commandsStore ); return { commands: getCommands( currentPage ), - loader: getLoader( currentPage ), + loader: getCommandLoader( currentPage ), }; }, [ currentPage ] @@ -91,12 +92,13 @@ export function CommandMenu() { onOpenChange={ setOpen } label={ __( 'Global Command Menu' ) } > - + ); diff --git a/packages/commands/src/hooks/use-command-loader.js b/packages/commands/src/hooks/use-command-loader.js new file mode 100644 index 00000000000000..31c5dfa65dcc59 --- /dev/null +++ b/packages/commands/src/hooks/use-command-loader.js @@ -0,0 +1,29 @@ +/** + * WordPress dependencies + */ +import { useEffect } from '@wordpress/element'; +import { useDispatch } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { store as commandsStore } from '../store'; + +/** + * Attach a command loader to the Global command menu. + * + * @param {import('../store/actions').WPCommandLoaderConfig} loader command loader config. + */ +export default function useCommandLoader( { page, hook } ) { + const { registerCommandLoader, unregisterCommandLoader } = + useDispatch( commandsStore ); + useEffect( () => { + registerCommandLoader( { + page, + hook, + } ); + return () => { + unregisterCommandLoader( page ); + }; + }, [ page, hook, registerCommandLoader, unregisterCommandLoader ] ); +} diff --git a/packages/commands/src/index.js b/packages/commands/src/index.js index 6329b31d6c61ac..da243add477f76 100644 --- a/packages/commands/src/index.js +++ b/packages/commands/src/index.js @@ -1,2 +1,3 @@ export { default as useCommand } from './hooks/use-command'; +export { default as useCommandLoader } from './hooks/use-command-loader'; export { CommandMenu } from './components/command-menu'; diff --git a/packages/commands/src/store/selectors.js b/packages/commands/src/store/selectors.js index d843fc2d40522f..70dee7c7d0ca87 100644 --- a/packages/commands/src/store/selectors.js +++ b/packages/commands/src/store/selectors.js @@ -3,12 +3,14 @@ */ import createSelector from 'rememo'; +const noop = () => {}; + export const getCommands = createSelector( - ( state, page ) => Object.values( state.commands[ page ] ), + ( state, page ) => Object.values( state.commands[ page ] ?? {} ), ( state, page ) => [ state.commands[ page ] ] ); export const getCommandLoader = createSelector( - ( state, page ) => Object.values( state.commandLoaders[ page ] ), + ( state, page ) => state.commandLoaders[ page ] ?? noop, ( state, page ) => [ state.commandLoaders[ page ] ] ); diff --git a/packages/edit-site/src/components/layout/index.js b/packages/edit-site/src/components/layout/index.js index 26063bbb712da9..1185d7f28e5b58 100644 --- a/packages/edit-site/src/components/layout/index.js +++ b/packages/edit-site/src/components/layout/index.js @@ -43,6 +43,7 @@ import { unlock } from '../../private-apis'; import SavePanel from '../save-panel'; import KeyboardShortcutsRegister from '../keyboard-shortcuts/register'; import KeyboardShortcutsGlobal from '../keyboard-shortcuts/global'; +import { useNavigationCommands } from '../../hooks/commands/use-navigation-commands'; const ANIMATION_DURATION = 0.5; const emptyResizeHandleStyles = { @@ -61,6 +62,7 @@ export default function Layout() { // This ensures the edited entity id and type are initialized properly. useInitEditedEntityFromURL(); useSyncCanvasModeWithURL(); + useNavigationCommands(); const hubRef = useRef(); const { params } = useLocation(); diff --git a/packages/edit-site/src/hooks/commands/use-navigation-commands.js b/packages/edit-site/src/hooks/commands/use-navigation-commands.js new file mode 100644 index 00000000000000..a4d4152fb14140 --- /dev/null +++ b/packages/edit-site/src/hooks/commands/use-navigation-commands.js @@ -0,0 +1,70 @@ +/** + * WordPress dependencies + */ +import { useCommand, useCommandLoader } from '@wordpress/commands'; +import { __ } from '@wordpress/i18n'; +import { useMemo } from '@wordpress/element'; +import { useSelect } from '@wordpress/data'; +import { store as coreStore } from '@wordpress/core-data'; + +/** + * Internal dependencies + */ +import { useHistory } from '../../components/routes'; + +function useNavigationCommandLoader( { search } ) { + const history = useHistory(); + const { pages, isLoading } = useSelect( + ( select ) => { + const { getEntityRecords } = select( coreStore ); + const query = { + search, + per_page: 20, + orderby: 'date', + }; + return { + pages: getEntityRecords( 'postType', 'page', query ), + isLoading: ! select( coreStore ).hasFinishedResolution( + 'getEntityRecords', + [ 'postType', 'page', query ] + ), + }; + }, + [ search ] + ); + + const commands = useMemo( () => { + return ( pages ?? [] ).map( ( page ) => { + return { + name: page.title?.rendered + ' ' + page.id, + label: page.title?.rendered, + callback: () => { + history.push( { + postType: 'page', + postId: page.id, + } ); + }, + }; + } ); + }, [ pages, history ] ); + + return { + commands, + isLoading, + }; +} + +export function useNavigationCommands() { + useCommand( { + name: 'core/edit-site/navigate', + label: __( 'Navigate to a page' ), + callback: ( { navigateToPage } ) => { + navigateToPage( 'core/edit-site/navigate' ); + }, + } ); + + useCommandLoader( { + page: 'core/edit-site/navigate', + hook: useNavigationCommandLoader, + } ); +} From 055ff3024d154983f4316b4e584b3b4bb112338e Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 27 Mar 2023 09:58:45 +0100 Subject: [PATCH 04/37] Add commands to navigate to new post/page --- .../edit-site/src/components/layout/index.js | 4 +-- .../edit-site/src/hooks/commands/index.js | 10 ++++++ .../hooks/commands/use-wp-admin-commands.js | 36 +++++++++++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 packages/edit-site/src/hooks/commands/index.js create mode 100644 packages/edit-site/src/hooks/commands/use-wp-admin-commands.js diff --git a/packages/edit-site/src/components/layout/index.js b/packages/edit-site/src/components/layout/index.js index 1185d7f28e5b58..3354cbfe98577f 100644 --- a/packages/edit-site/src/components/layout/index.js +++ b/packages/edit-site/src/components/layout/index.js @@ -43,7 +43,7 @@ import { unlock } from '../../private-apis'; import SavePanel from '../save-panel'; import KeyboardShortcutsRegister from '../keyboard-shortcuts/register'; import KeyboardShortcutsGlobal from '../keyboard-shortcuts/global'; -import { useNavigationCommands } from '../../hooks/commands/use-navigation-commands'; +import { useCommands } from '../../hooks/commands'; const ANIMATION_DURATION = 0.5; const emptyResizeHandleStyles = { @@ -62,7 +62,7 @@ export default function Layout() { // This ensures the edited entity id and type are initialized properly. useInitEditedEntityFromURL(); useSyncCanvasModeWithURL(); - useNavigationCommands(); + useCommands(); const hubRef = useRef(); const { params } = useLocation(); diff --git a/packages/edit-site/src/hooks/commands/index.js b/packages/edit-site/src/hooks/commands/index.js new file mode 100644 index 00000000000000..3396f12232b8d9 --- /dev/null +++ b/packages/edit-site/src/hooks/commands/index.js @@ -0,0 +1,10 @@ +/** + * Internal dependencies + */ +import { useNavigationCommands } from './use-navigation-commands'; +import { useWPAdminCommands } from './use-wp-admin-commands'; + +export function useCommands() { + useWPAdminCommands(); + useNavigationCommands(); +} diff --git a/packages/edit-site/src/hooks/commands/use-wp-admin-commands.js b/packages/edit-site/src/hooks/commands/use-wp-admin-commands.js new file mode 100644 index 00000000000000..0c30c9cdd18af0 --- /dev/null +++ b/packages/edit-site/src/hooks/commands/use-wp-admin-commands.js @@ -0,0 +1,36 @@ +/** + * WordPress dependencies + */ +import { useCommand } from '@wordpress/commands'; +import { __ } from '@wordpress/i18n'; +import { useSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { store as editSiteStore } from '../../store'; +import { unlock } from '../../private-apis'; + +export function useWPAdminCommands() { + const newPostLink = useSelect( ( select ) => { + select( editSiteStore ).getEditedPostType(); + const { getSettings } = unlock( select( editSiteStore ) ); + return getSettings().newPostLink ?? 'post-new.php'; + }, [] ); + + useCommand( { + name: 'core/wp-admin/create-post', + label: __( 'Create a new post' ), + callback: () => { + document.location.href = newPostLink; + }, + } ); + + useCommand( { + name: 'core/wp-admin/create-page', + label: __( 'Create a new page' ), + callback: () => { + document.location.href = newPostLink + '?post_type=page'; + }, + } ); +} From 635e86efc9009dd9b00fd1820e7485bc37a027f8 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 27 Mar 2023 20:56:32 +0100 Subject: [PATCH 05/37] Use our own Modal component and tweak style --- lib/client-assets.php | 2 +- package-lock.json | 1 + packages/commands/package.json | 1 + .../commands/src/components/command-menu.js | 42 ++- packages/commands/src/components/style.scss | 250 +++++++++--------- 5 files changed, 152 insertions(+), 144 deletions(-) diff --git a/lib/client-assets.php b/lib/client-assets.php index e0d5468674aa48..630c598637b594 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -389,7 +389,7 @@ function gutenberg_register_packages_styles( $styles ) { $styles, 'wp-commands', gutenberg_url( 'build/commands/style.css' ), - array(), + array( 'wp-components' ), $version ); $styles->add_data( 'wp-commands', 'rtl', 'replace' ); diff --git a/package-lock.json b/package-lock.json index 5abb343401e12b..978cdafed439a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17049,6 +17049,7 @@ "version": "file:packages/commands", "requires": { "@babel/runtime": "^7.16.0", + "@wordpress/components": "file:packages/components", "@wordpress/data": "file:packages/data", "@wordpress/element": "file:packages/element", "@wordpress/i18n": "file:packages/i18n", diff --git a/packages/commands/package.json b/packages/commands/package.json index 038f34606dea46..b5dfa7e5397c89 100644 --- a/packages/commands/package.json +++ b/packages/commands/package.json @@ -27,6 +27,7 @@ "react-native": "src/index", "dependencies": { "@babel/runtime": "^7.16.0", + "@wordpress/components": "file:../components", "@wordpress/data": "file:../data", "@wordpress/element": "file:../element", "@wordpress/i18n": "file:../i18n", diff --git a/packages/commands/src/components/command-menu.js b/packages/commands/src/components/command-menu.js index ca8001cbc6afc3..4d29c97b0e6363 100644 --- a/packages/commands/src/components/command-menu.js +++ b/packages/commands/src/components/command-menu.js @@ -9,6 +9,7 @@ import { Command } from 'cmdk'; import { useSelect } from '@wordpress/data'; import { useState, useEffect, useRef } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; +import { Modal } from '@wordpress/components'; /** * Internal dependencies @@ -86,20 +87,35 @@ export function CommandMenu() { return () => document.removeEventListener( 'keydown', down ); }, [] ); + if ( ! open ) { + return false; + } + return ( - setOpen( false ) } + __experimentalHideHeader + focusOnMount="firstElement" > - - - +
+ +
+ +
+ +
+
+ ); } diff --git a/packages/commands/src/components/style.scss b/packages/commands/src/components/style.scss index 303d2f3fef57d5..7ed637a383bff8 100644 --- a/packages/commands/src/components/style.scss +++ b/packages/commands/src/components/style.scss @@ -1,157 +1,147 @@ -:root { - --cmdk-shadow: 0 16px 70px rgba(0, 0, 0, 0.2); -} - -[cmdk-dialog] { - position: fixed; - top: 0; - left: 0; - width: 100vw; - height: 100vh; - display: flex; - align-items: center; - justify-content: center; - z-index: 9999; -} - -[cmdk-root] { - max-width: 640px; +// dirty hack to clean up modal +.commands-command-menu { + padding: 0; width: 100%; - padding: 8px; - background: $white; - border-radius: 12px; - overflow: hidden; - font-family: var(--font-sans); - border: 1px solid var(--gray6); - box-shadow: var(--cmdk-shadow); - transition: transform 100ms ease; + max-width: 680px; + .components-modal__content { + margin: 0; + padding: 0; + } } -[cmdk-linear-badge] { - height: 24px; - padding: 0 8px; - font-size: 12px; - color: $gray-800; - background: $gray-300; - border-radius: 4px; - width: fit-content; +.commands-command-menu__header { display: flex; align-items: center; - margin: 16px 16px 0; + border-bottom: 1px solid $gray-200; } -[cmdk-linear-shortcuts] { - display: flex; - margin-left: auto; - gap: 8px; - - kbd { - font-size: 13px; +.commands-command-menu__container { + [cmdk-linear-badge] { + height: 24px; + padding: 0 8px; + font-size: 12px; color: $gray-800; + background: $gray-300; + border-radius: 4px; + width: fit-content; + display: flex; + align-items: center; + margin: 16px 16px 0; } -} -[cmdk-input] { - border: none; - width: 100%; - font-size: 18px; - padding: 20px; - outline: none; - color: $gray-900; - border-radius: 0; - caret-color: #6e5ed2; - margin: 0; - - &::placeholder { - color: var(--gray9); + [cmdk-linear-shortcuts] { + display: flex; + margin-left: auto; + gap: 8px; + + kbd { + font-size: 13px; + color: $gray-800; + } } - &:focus { - border-bottom: 1px solid $gray-300; - box-shadow: none; + + [cmdk-input] { + border: none; + width: 100%; + font-size: 18px; + padding: 20px; + outline: none; + color: $gray-900; + border-radius: 0; + caret-color: #6e5ed2; + margin: 0; + + &::placeholder { + color: var(--gray9); + } + &:focus { + border-bottom: 1px solid $gray-300; + box-shadow: none; + } } -} -[cmdk-item] { - content-visibility: auto; + [cmdk-item] { + content-visibility: auto; + + cursor: pointer; + height: 48px; + font-size: 14px; + display: flex; + align-items: center; + gap: 12px; + padding: 0 16px; + color: $gray-900; + user-select: none; + will-change: background, color; + transition: all 150ms ease; + transition-property: none; + position: relative; + + &[aria-selected="true"] { + background: $gray-100; + + svg { + color: $gray-900; + } + + &::after { + content: ""; + position: absolute; + left: 0; + z-index: 123; + width: 3px; + height: 100%; + background: #5f6ad2; + } + } - cursor: pointer; - height: 48px; - font-size: 14px; - display: flex; - align-items: center; - gap: 12px; - padding: 0 16px; - color: $gray-900; - user-select: none; - will-change: background, color; - transition: all 150ms ease; - transition-property: none; - position: relative; - - &[aria-selected="true"] { - background: $gray-100; - - svg { - color: $gray-900; + &[aria-disabled="true"] { + color: $gray-600; + cursor: not-allowed; } - &::after { - content: ""; - position: absolute; - left: 0; - z-index: 123; - width: 3px; - height: 100%; - background: #5f6ad2; + &:active { + transition-property: background; + background: $gray-300; } - } - &[aria-disabled="true"] { - color: $gray-600; - cursor: not-allowed; - } + & + [cmdk-item] { + margin-top: 4px; + } - &:active { - transition-property: background; - background: $gray-300; + svg, + .dashicon { + width: 16px; + height: 16px; + color: $gray-800; + } } - & + [cmdk-item] { - margin-top: 4px; + [cmdk-list] { + min-height: 300px; + max-height: 400px; + overflow: auto; + overscroll-behavior: contain; + transition: 100ms ease; + transition-property: height; } - svg, - .dashicon { - width: 16px; - height: 16px; + [cmdk-group-heading] { + user-select: none; + font-size: 12px; color: $gray-800; + padding: 16px; + display: flex; + align-items: center; } -} - -[cmdk-list] { - min-height: 300px; - max-height: 400px; - overflow: auto; - overscroll-behavior: contain; - transition: 100ms ease; - transition-property: height; -} - -[cmdk-group-heading] { - user-select: none; - font-size: 12px; - color: $gray-800; - padding: 16px; - display: flex; - align-items: center; -} -[cmdk-empty] { - font-size: 14px; - display: flex; - align-items: center; - justify-content: center; - height: 64px; - white-space: pre-wrap; - color: $gray-800; + [cmdk-empty] { + font-size: 14px; + display: flex; + align-items: center; + justify-content: center; + height: 64px; + white-space: pre-wrap; + color: $gray-800; + } } From 66a54798d0dea4671872ef546e8ff3534858f6f9 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 28 Mar 2023 10:23:51 +0100 Subject: [PATCH 06/37] Add back button --- package-lock.json | 1 + packages/commands/package.json | 1 + .../commands/src/components/command-menu.js | 28 +++++++++++++++++-- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 978cdafed439a7..b7af084d26a60b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17053,6 +17053,7 @@ "@wordpress/data": "file:packages/data", "@wordpress/element": "file:packages/element", "@wordpress/i18n": "file:packages/i18n", + "@wordpress/icons": "file:packages/icons", "cmdk": "^0.2.0", "rememo": "^4.0.0" } diff --git a/packages/commands/package.json b/packages/commands/package.json index b5dfa7e5397c89..889366abf731b5 100644 --- a/packages/commands/package.json +++ b/packages/commands/package.json @@ -31,6 +31,7 @@ "@wordpress/data": "file:../data", "@wordpress/element": "file:../element", "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", "cmdk": "^0.2.0", "rememo": "^4.0.0" }, diff --git a/packages/commands/src/components/command-menu.js b/packages/commands/src/components/command-menu.js index 4d29c97b0e6363..0ded91cf7ba021 100644 --- a/packages/commands/src/components/command-menu.js +++ b/packages/commands/src/components/command-menu.js @@ -9,7 +9,8 @@ import { Command } from 'cmdk'; import { useSelect } from '@wordpress/data'; import { useState, useEffect, useRef } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { Modal } from '@wordpress/components'; +import { Modal, Button } from '@wordpress/components'; +import { Icon, chevronLeft } from '@wordpress/icons'; /** * Internal dependencies @@ -60,6 +61,8 @@ export function CommandMenu() { }, [ currentPage ] ); + const goBack = () => + setPages( ( currentPages ) => currentPages.slice( 0, -1 ) ); // loader is actually a custom hook // so to avoid breaking the rules of hooks @@ -87,6 +90,12 @@ export function CommandMenu() { return () => document.removeEventListener( 'keydown', down ); }, [] ); + useEffect( () => { + if ( ! open ) { + setPages( [] ); + } + }, [ open ] ); + if ( ! open ) { return false; } @@ -96,15 +105,30 @@ export function CommandMenu() { className="commands-command-menu" onRequestClose={ () => setOpen( false ) } __experimentalHideHeader - focusOnMount="firstElement" >
+ { pages.length > 0 && ( + + ) } { + if ( + event.key === 'Backspace' && + search === '' + ) { + goBack(); + } + } } />
Date: Tue, 28 Mar 2023 11:04:49 +0100 Subject: [PATCH 07/37] Better loading state --- .../commands/src/components/command-menu.js | 43 +++++++++++-------- packages/commands/src/components/style.scss | 15 +++++++ 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/packages/commands/src/components/command-menu.js b/packages/commands/src/components/command-menu.js index 0ded91cf7ba021..30b55b6de236b0 100644 --- a/packages/commands/src/components/command-menu.js +++ b/packages/commands/src/components/command-menu.js @@ -9,7 +9,7 @@ import { Command } from 'cmdk'; import { useSelect } from '@wordpress/data'; import { useState, useEffect, useRef } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { Modal, Button } from '@wordpress/components'; +import { Modal, Button, Spinner } from '@wordpress/components'; import { Icon, chevronLeft } from '@wordpress/icons'; /** @@ -23,25 +23,30 @@ function CommandsPerPage( { search, navigateToPage, loader, commands } ) { const allCommands = [ ...commands, ...loaderCommands ]; return ( - - { ! isLoading && ! allCommands?.length && ( - { __( 'No results found.' ) } - ) } + <> + + { isLoading && ( + + + + ) } + { ! isLoading && ! allCommands?.length && ( + { __( 'No results found.' ) } + ) } - { isLoading && ( - { __( 'Hang on…' ) } - ) } - - { allCommands.map( ( command ) => ( - command.callback( { navigateToPage } ) } - > - { command.label } - - ) ) } - + { allCommands.map( ( command ) => ( + + command.callback( { navigateToPage } ) + } + > + { command.label } + + ) ) } + + ); } diff --git a/packages/commands/src/components/style.scss b/packages/commands/src/components/style.scss index 7ed637a383bff8..912153e58eb715 100644 --- a/packages/commands/src/components/style.scss +++ b/packages/commands/src/components/style.scss @@ -144,4 +144,19 @@ white-space: pre-wrap; color: $gray-800; } + + [cmdk-list-sizer] { + position: relative; + } + + [cmdk-loading] { + position: absolute; + top: 0; + left: 0; + right: 0; + display: flex; + align-items: center; + justify-content: center; + min-height: 150px; + } } From d47ebd099d1d8d745b276d7149b7f4ee12ef4356 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Wed, 29 Mar 2023 09:54:49 +0100 Subject: [PATCH 08/37] Add page specific placeholders --- .../commands/src/components/command-menu.js | 8 +++--- .../commands/src/hooks/use-command-loader.js | 11 ++++++-- packages/commands/src/store/actions.js | 8 +++--- packages/commands/src/store/reducer.js | 25 +++++++++++++++++++ packages/commands/src/store/selectors.js | 4 +++ .../hooks/commands/use-navigation-commands.js | 1 + 6 files changed, 49 insertions(+), 8 deletions(-) diff --git a/packages/commands/src/components/command-menu.js b/packages/commands/src/components/command-menu.js index 30b55b6de236b0..29b3e426569000 100644 --- a/packages/commands/src/components/command-menu.js +++ b/packages/commands/src/components/command-menu.js @@ -56,12 +56,14 @@ export function CommandMenu() { const [ pages, setPages ] = useState( [] ); const navigateToPage = ( newPage ) => setPages( [ ...pages, newPage ] ); const currentPage = pages.length ? pages[ pages.length - 1 ] : null; - const { commands, loader } = useSelect( + const { commands, loader, placeholder } = useSelect( ( select ) => { - const { getCommands, getCommandLoader } = select( commandsStore ); + const { getCommands, getCommandLoader, getPagePlaceholder } = + select( commandsStore ); return { commands: getCommands( currentPage ), loader: getCommandLoader( currentPage ), + placeholder: getPagePlaceholder( currentPage ), }; }, [ currentPage ] @@ -125,7 +127,7 @@ export function CommandMenu() { autoFocus value={ search } onValueChange={ setSearch } - placeholder={ __( 'Ask anything' ) } + placeholder={ placeholder ?? __( 'Ask anything' ) } onKeyDown={ ( event ) => { if ( event.key === 'Backspace' && diff --git a/packages/commands/src/hooks/use-command-loader.js b/packages/commands/src/hooks/use-command-loader.js index 31c5dfa65dcc59..b082ed9f8f4911 100644 --- a/packages/commands/src/hooks/use-command-loader.js +++ b/packages/commands/src/hooks/use-command-loader.js @@ -14,16 +14,23 @@ import { store as commandsStore } from '../store'; * * @param {import('../store/actions').WPCommandLoaderConfig} loader command loader config. */ -export default function useCommandLoader( { page, hook } ) { +export default function useCommandLoader( { page, hook, placeholder } ) { const { registerCommandLoader, unregisterCommandLoader } = useDispatch( commandsStore ); useEffect( () => { registerCommandLoader( { page, hook, + placeholder, } ); return () => { unregisterCommandLoader( page ); }; - }, [ page, hook, registerCommandLoader, unregisterCommandLoader ] ); + }, [ + page, + hook, + placeholder, + registerCommandLoader, + unregisterCommandLoader, + ] ); } diff --git a/packages/commands/src/store/actions.js b/packages/commands/src/store/actions.js index 6dd50f3db3aa8e..d0f66cb378e255 100644 --- a/packages/commands/src/store/actions.js +++ b/packages/commands/src/store/actions.js @@ -20,8 +20,9 @@ * * @typedef {Object} WPCommandLoaderConfig * - * @property {string=} page Command loader page. - * @property {WPCommandLoaderHook} hook Command loader hook. + * @property {string=} page Command loader page. + * @property {string=} placeholder Command page placeholder. + * @property {WPCommandLoaderHook} hook Command loader hook. */ /** @@ -62,11 +63,12 @@ export function unregisterCommand( name ) { * * @return {Object} action. */ -export function registerCommandLoader( { page, hook } ) { +export function registerCommandLoader( { page, hook, placeholder } ) { return { type: 'REGISTER_COMMAND_LOADER', page, hook, + placeholder, }; } diff --git a/packages/commands/src/store/reducer.js b/packages/commands/src/store/reducer.js index 63e1306f9fafee..93a085d5b13b25 100644 --- a/packages/commands/src/store/reducer.js +++ b/packages/commands/src/store/reducer.js @@ -63,9 +63,34 @@ function commandLoaders( state = {}, action ) { return state; } +/** + * Reducer returning the page placeholders + * + * @param {Object} state Current state. + * @param {Object} action Dispatched action. + * + * @return {Object} Updated state. + */ +function placeholders( state = {}, action ) { + switch ( action.type ) { + case 'REGISTER_COMMAND_LOADER': + return { + ...state, + [ action.page ]: action.placeholder, + }; + case 'UNREGISTER_COMMAND_LOADER': { + const { [ action.page ]: _, ...remainingState } = state; + return remainingState; + } + } + + return state; +} + const reducer = combineReducers( { commands, commandLoaders, + placeholders, } ); export default reducer; diff --git a/packages/commands/src/store/selectors.js b/packages/commands/src/store/selectors.js index 70dee7c7d0ca87..110951ebc5cb28 100644 --- a/packages/commands/src/store/selectors.js +++ b/packages/commands/src/store/selectors.js @@ -14,3 +14,7 @@ export const getCommandLoader = createSelector( ( state, page ) => state.commandLoaders[ page ] ?? noop, ( state, page ) => [ state.commandLoaders[ page ] ] ); + +export function getPagePlaceholder( state, page ) { + return state.placeholders[ page ]; +} diff --git a/packages/edit-site/src/hooks/commands/use-navigation-commands.js b/packages/edit-site/src/hooks/commands/use-navigation-commands.js index a4d4152fb14140..199c10e17710a2 100644 --- a/packages/edit-site/src/hooks/commands/use-navigation-commands.js +++ b/packages/edit-site/src/hooks/commands/use-navigation-commands.js @@ -66,5 +66,6 @@ export function useNavigationCommands() { useCommandLoader( { page: 'core/edit-site/navigate', hook: useNavigationCommandLoader, + placeholder: __( 'Search for a page…' ), } ); } From 31b156c89380a8ea5d8d729dd5099efc6e8993a8 Mon Sep 17 00:00:00 2001 From: James Koster Date: Wed, 29 Mar 2023 14:11:03 +0100 Subject: [PATCH 09/37] Variables --- packages/commands/src/components/style.scss | 38 ++++++--------------- 1 file changed, 11 insertions(+), 27 deletions(-) diff --git a/packages/commands/src/components/style.scss b/packages/commands/src/components/style.scss index 912153e58eb715..4d3a9a229d1a52 100644 --- a/packages/commands/src/components/style.scss +++ b/packages/commands/src/components/style.scss @@ -26,16 +26,15 @@ width: fit-content; display: flex; align-items: center; - margin: 16px 16px 0; + margin: $grid-unit-20 $grid-unit-20 0; } [cmdk-linear-shortcuts] { display: flex; margin-left: auto; - gap: 8px; + gap: $grid-unit-10; kbd { - font-size: 13px; color: $gray-800; } } @@ -43,12 +42,10 @@ [cmdk-input] { border: none; width: 100%; - font-size: 18px; - padding: 20px; + padding: $grid-unit-15 $grid-unit-20; outline: none; color: $gray-900; border-radius: 0; - caret-color: #6e5ed2; margin: 0; &::placeholder { @@ -64,12 +61,11 @@ content-visibility: auto; cursor: pointer; - height: 48px; - font-size: 14px; + height: $grid-unit-60; display: flex; align-items: center; - gap: 12px; - padding: 0 16px; + gap: $grid-unit-15; + padding: 0 $grid-unit-20; color: $gray-900; user-select: none; will-change: background, color; @@ -83,16 +79,6 @@ svg { color: $gray-900; } - - &::after { - content: ""; - position: absolute; - left: 0; - z-index: 123; - width: 3px; - height: 100%; - background: #5f6ad2; - } } &[aria-disabled="true"] { @@ -106,13 +92,13 @@ } & + [cmdk-item] { - margin-top: 4px; + margin-top: $grid-unit-05; } svg, .dashicon { - width: 16px; - height: 16px; + width: $grid-unit-20; + height: $grid-unit-20; color: $gray-800; } } @@ -128,19 +114,17 @@ [cmdk-group-heading] { user-select: none; - font-size: 12px; color: $gray-800; - padding: 16px; + padding: $grid-unit-20; display: flex; align-items: center; } [cmdk-empty] { - font-size: 14px; display: flex; align-items: center; justify-content: center; - height: 64px; + height: $grid-unit-80; white-space: pre-wrap; color: $gray-800; } From a54b91a037be125c5b6fbb14a28618ff8388c8e4 Mon Sep 17 00:00:00 2001 From: James Koster Date: Wed, 29 Mar 2023 14:45:55 +0100 Subject: [PATCH 10/37] General styling --- packages/commands/src/components/style.scss | 40 +++++++++++++++------ 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/packages/commands/src/components/style.scss b/packages/commands/src/components/style.scss index 4d3a9a229d1a52..20588d5736b24e 100644 --- a/packages/commands/src/components/style.scss +++ b/packages/commands/src/components/style.scss @@ -3,6 +3,7 @@ padding: 0; width: 100%; max-width: 680px; + .components-modal__content { margin: 0; padding: 0; @@ -12,7 +13,21 @@ .commands-command-menu__header { display: flex; align-items: center; - border-bottom: 1px solid $gray-200; + margin: $grid-unit-15 $grid-unit-15 0 $grid-unit-15; + + .components-button { + height: $grid-unit-70; + width: $grid-unit-70; + border: 1px solid $gray-600; + border-right: 0; + justify-content: center; + border-radius: $radius-block-ui 0 0 $radius-block-ui; + + & + [cmdk-input] { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + } } .commands-command-menu__container { @@ -40,26 +55,29 @@ } [cmdk-input] { - border: none; + border: 1px solid $gray-600; width: 100%; padding: $grid-unit-15 $grid-unit-20; + min-height: $grid-unit-70; outline: none; color: $gray-900; - border-radius: 0; margin: 0; + border-radius: $radius-block-ui; &::placeholder { - color: var(--gray9); + color: $gray-600; } + &:focus { - border-bottom: 1px solid $gray-300; - box-shadow: none; + outline: 2px solid transparent; + border-color: var(--wp-admin-theme-color); + box-shadow: 0 0 0 1px var(--wp-admin-theme-color); } } [cmdk-item] { content-visibility: auto; - + border-radius: $radius-block-ui; cursor: pointer; height: $grid-unit-60; display: flex; @@ -74,10 +92,11 @@ position: relative; &[aria-selected="true"] { - background: $gray-100; + background: rgba(var(--wp-admin-theme-color--rgb), 0.04); + color: var(--wp-admin-theme-color); svg { - color: $gray-900; + color: var(--wp-admin-theme-color); } } @@ -88,7 +107,7 @@ &:active { transition-property: background; - background: $gray-300; + background: rgba(var(--wp-admin-theme-color--rgb), 0.08); } & + [cmdk-item] { @@ -110,6 +129,7 @@ overscroll-behavior: contain; transition: 100ms ease; transition-property: height; + margin: $grid-unit-15; } [cmdk-group-heading] { From f1f3d3bd2a1e1005f7d26e1b32caa886b0d36fc2 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 30 Mar 2023 10:30:40 +0100 Subject: [PATCH 11/37] Update the architecture of the command loader --- .../commands/src/components/command-menu.js | 141 ++++++++++-------- packages/commands/src/components/style.scss | 16 +- .../commands/src/hooks/use-command-loader.js | 16 +- packages/commands/src/hooks/use-command.js | 6 +- packages/commands/src/store/actions.js | 32 ++-- packages/commands/src/store/reducer.js | 43 ++---- packages/commands/src/store/selectors.js | 29 ++-- .../hooks/commands/use-navigation-commands.js | 14 +- 8 files changed, 138 insertions(+), 159 deletions(-) diff --git a/packages/commands/src/components/command-menu.js b/packages/commands/src/components/command-menu.js index 29b3e426569000..3eeb48c3e56e53 100644 --- a/packages/commands/src/components/command-menu.js +++ b/packages/commands/src/components/command-menu.js @@ -9,38 +9,31 @@ import { Command } from 'cmdk'; import { useSelect } from '@wordpress/data'; import { useState, useEffect, useRef } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { Modal, Button, Spinner } from '@wordpress/components'; -import { Icon, chevronLeft } from '@wordpress/icons'; +import { Modal } from '@wordpress/components'; /** * Internal dependencies */ import { store as commandsStore } from '../store'; -function CommandsPerPage( { search, navigateToPage, loader, commands } ) { - const { isLoading, commands: loaderCommands = [] } = - loader( { search } ) ?? {}; - const allCommands = [ ...commands, ...loaderCommands ]; +function CommandMenuLoader( { search, hook } ) { + const { isLoading, commands = [] } = hook( { search } ) ?? {}; return ( <> { isLoading && ( - - - + { __( 'Searching…' ) } ) } - { ! isLoading && ! allCommands?.length && ( + { ! isLoading && ! commands?.length && ( { __( 'No results found.' ) } ) } - { allCommands.map( ( command ) => ( + { commands.map( ( command ) => ( - command.callback( { navigateToPage } ) - } + onSelect={ () => command.callback() } > { command.label } @@ -50,40 +43,73 @@ function CommandsPerPage( { search, navigateToPage, loader, commands } ) { ); } -export function CommandMenu() { - const [ search, setSearch ] = useState( '' ); - const [ open, setOpen ] = useState( false ); - const [ pages, setPages ] = useState( [] ); - const navigateToPage = ( newPage ) => setPages( [ ...pages, newPage ] ); - const currentPage = pages.length ? pages[ pages.length - 1 ] : null; - const { commands, loader, placeholder } = useSelect( - ( select ) => { - const { getCommands, getCommandLoader, getPagePlaceholder } = - select( commandsStore ); - return { - commands: getCommands( currentPage ), - loader: getCommandLoader( currentPage ), - placeholder: getPagePlaceholder( currentPage ), - }; - }, - [ currentPage ] - ); - const goBack = () => - setPages( ( currentPages ) => currentPages.slice( 0, -1 ) ); - +export function CommandMenuLoaderWrapper( { hook, search } ) { // loader is actually a custom hook // so to avoid breaking the rules of hooks // the CommandsPerPage component need to be // remounted on each loader change // We use the key state to make sure we do that properly. - const currentLoader = useRef( loader ); + const currentLoader = useRef( hook ); const [ key, setKey ] = useState( 0 ); useEffect( () => { - if ( currentLoader.current !== loader ) { - currentLoader.current = loader; + if ( currentLoader.current !== hook ) { + currentLoader.current = hook; setKey( ( prevKey ) => prevKey + 1 ); } - }, [ loader ] ); + }, [ hook ] ); + + return ( + + ); +} + +export function CommandMenuGroup( { group, search } ) { + const { commands, loaders } = useSelect( + ( select ) => { + const { getCommands, getCommandLoaders } = select( commandsStore ); + return { + commands: getCommands( group ), + loaders: getCommandLoaders( group ), + }; + }, + [ group ] + ); + + return ( + + { commands.map( ( command ) => ( + command.callback() } + > + { command.label } + + ) ) } + { loaders.map( ( loader ) => ( + + ) ) } + + ); +} + +export function CommandMenu() { + const [ search, setSearch ] = useState( '' ); + const [ open, setOpen ] = useState( false ); + const { groups } = useSelect( ( select ) => { + const { getGroups } = select( commandsStore ); + return { + groups: getGroups(), + }; + }, [] ); // Toggle the menu when ⌘K is pressed useEffect( () => { @@ -97,12 +123,6 @@ export function CommandMenu() { return () => document.removeEventListener( 'keydown', down ); }, [] ); - useEffect( () => { - if ( ! open ) { - setPages( [] ); - } - }, [ open ] ); - if ( ! open ) { return false; } @@ -116,35 +136,24 @@ export function CommandMenu() {
- { pages.length > 0 && ( - - ) } { - if ( - event.key === 'Backspace' && - search === '' - ) { - goBack(); - } - } } + placeholder={ __( 'Ask anything' ) } />
- + + { groups.map( ( group ) => ( + + ) ) } +
diff --git a/packages/commands/src/components/style.scss b/packages/commands/src/components/style.scss index 20588d5736b24e..d3af6742121a68 100644 --- a/packages/commands/src/components/style.scss +++ b/packages/commands/src/components/style.scss @@ -122,7 +122,7 @@ } } - [cmdk-list] { + [cmdk-root] > [cmdk-list] { min-height: 300px; max-height: 400px; overflow: auto; @@ -134,10 +134,11 @@ [cmdk-group-heading] { user-select: none; - color: $gray-800; + color: $gray-700; padding: $grid-unit-20; display: flex; align-items: center; + font-size: 12px; } [cmdk-empty] { @@ -152,15 +153,4 @@ [cmdk-list-sizer] { position: relative; } - - [cmdk-loading] { - position: absolute; - top: 0; - left: 0; - right: 0; - display: flex; - align-items: center; - justify-content: center; - min-height: 150px; - } } diff --git a/packages/commands/src/hooks/use-command-loader.js b/packages/commands/src/hooks/use-command-loader.js index b082ed9f8f4911..ac81873c01a2ea 100644 --- a/packages/commands/src/hooks/use-command-loader.js +++ b/packages/commands/src/hooks/use-command-loader.js @@ -14,23 +14,17 @@ import { store as commandsStore } from '../store'; * * @param {import('../store/actions').WPCommandLoaderConfig} loader command loader config. */ -export default function useCommandLoader( { page, hook, placeholder } ) { +export default function useCommandLoader( { name, group, hook } ) { const { registerCommandLoader, unregisterCommandLoader } = useDispatch( commandsStore ); useEffect( () => { registerCommandLoader( { - page, + name, + group, hook, - placeholder, } ); return () => { - unregisterCommandLoader( page ); + unregisterCommandLoader( name, group ); }; - }, [ - page, - hook, - placeholder, - registerCommandLoader, - unregisterCommandLoader, - ] ); + }, [ name, group, hook, registerCommandLoader, unregisterCommandLoader ] ); } diff --git a/packages/commands/src/hooks/use-command.js b/packages/commands/src/hooks/use-command.js index 98830734bcda40..6da87e645a2585 100644 --- a/packages/commands/src/hooks/use-command.js +++ b/packages/commands/src/hooks/use-command.js @@ -24,17 +24,17 @@ export default function useCommand( command ) { useEffect( () => { registerCommand( { name: command.name, - page: command.page, + group: command.group, label: command.label, callback: currentCallback.current, } ); return () => { - unregisterCommand( command.name ); + unregisterCommand( command.name, command.group ); }; }, [ command.name, command.label, - command.page, + command.group, registerCommand, unregisterCommand, ] ); diff --git a/packages/commands/src/store/actions.js b/packages/commands/src/store/actions.js index d0f66cb378e255..a2dfe526700a2b 100644 --- a/packages/commands/src/store/actions.js +++ b/packages/commands/src/store/actions.js @@ -7,7 +7,7 @@ * * @property {string} name Command name. * @property {string} label Command label. - * @property {string=} page Command page. + * @property {string=} group Command group. * @property {Function} callback Command callback. */ @@ -20,9 +20,9 @@ * * @typedef {Object} WPCommandLoaderConfig * - * @property {string=} page Command loader page. - * @property {string=} placeholder Command page placeholder. - * @property {WPCommandLoaderHook} hook Command loader hook. + * @property {string} name Command loader name. + * @property {string=} group Command loader group. + * @property {WPCommandLoaderHook} hook Command loader hook. */ /** @@ -32,27 +32,29 @@ * * @return {Object} action. */ -export function registerCommand( { name, label, callback, page = null } ) { +export function registerCommand( { name, label, callback, group = '' } ) { return { type: 'REGISTER_COMMAND', name, label, callback, - page, + group, }; } /** * Returns an action object used to unregister a command. * - * @param {string} name Command name. + * @param {string} name Command name. + * @param {string} group Command group. * * @return {Object} action. */ -export function unregisterCommand( name ) { +export function unregisterCommand( name, group ) { return { type: 'UNREGISTER_COMMAND', name, + group, }; } @@ -63,25 +65,27 @@ export function unregisterCommand( name ) { * * @return {Object} action. */ -export function registerCommandLoader( { page, hook, placeholder } ) { +export function registerCommandLoader( { name, group = '', hook } ) { return { type: 'REGISTER_COMMAND_LOADER', - page, + name, + group, hook, - placeholder, }; } /** * Unregister command loader hook. * - * @param {string} page Command loader page. + * @param {string} name Command loader name. + * @param {string} group Command loader group. * * @return {Object} action. */ -export function unregisterCommandLoader( page ) { +export function unregisterCommandLoader( name, group ) { return { type: 'UNREGISTER_COMMAND_LOADER', - page, + name, + group, }; } diff --git a/packages/commands/src/store/reducer.js b/packages/commands/src/store/reducer.js index 93a085d5b13b25..8d86ffb15ba1c5 100644 --- a/packages/commands/src/store/reducer.js +++ b/packages/commands/src/store/reducer.js @@ -16,22 +16,22 @@ function commands( state = {}, action ) { case 'REGISTER_COMMAND': return { ...state, - [ action.page ]: { - ...state[ action.page ], + [ action.group ]: { + ...state[ action.group ], [ action.name ]: { name: action.name, label: action.label, - page: action.page, + group: action.group, callback: action.callback, }, }, }; case 'UNREGISTER_COMMAND': { const { [ action.name ]: _, ...remainingState } = - state?.[ action.page ]; + state?.[ action.group ]; return { ...state, - [ action.page ]: remainingState, + [ action.group ]: remainingState, }; } } @@ -52,31 +52,13 @@ function commandLoaders( state = {}, action ) { case 'REGISTER_COMMAND_LOADER': return { ...state, - [ action.page ]: action.hook, - }; - case 'UNREGISTER_COMMAND_LOADER': { - const { [ action.page ]: _, ...remainingState } = state; - return remainingState; - } - } - - return state; -} - -/** - * Reducer returning the page placeholders - * - * @param {Object} state Current state. - * @param {Object} action Dispatched action. - * - * @return {Object} Updated state. - */ -function placeholders( state = {}, action ) { - switch ( action.type ) { - case 'REGISTER_COMMAND_LOADER': - return { - ...state, - [ action.page ]: action.placeholder, + [ action.group ]: { + ...state[ action.group ], + [ action.name ]: { + name: action.name, + hook: action.hook, + }, + }, }; case 'UNREGISTER_COMMAND_LOADER': { const { [ action.page ]: _, ...remainingState } = state; @@ -90,7 +72,6 @@ function placeholders( state = {}, action ) { const reducer = combineReducers( { commands, commandLoaders, - placeholders, } ); export default reducer; diff --git a/packages/commands/src/store/selectors.js b/packages/commands/src/store/selectors.js index 110951ebc5cb28..b8ad97fb0d4963 100644 --- a/packages/commands/src/store/selectors.js +++ b/packages/commands/src/store/selectors.js @@ -3,18 +3,27 @@ */ import createSelector from 'rememo'; -const noop = () => {}; +function unique( array ) { + return array.filter( + ( value, index, current ) => current.indexOf( value ) === index + ); +} +export const getGroups = createSelector( + ( state ) => + unique( + Object.keys( state.commands ).concat( + Object.keys( state.commandLoaders ) + ) + ), + ( state ) => [ state.commands, state.commandLoaders ] +); export const getCommands = createSelector( - ( state, page ) => Object.values( state.commands[ page ] ?? {} ), - ( state, page ) => [ state.commands[ page ] ] + ( state, group ) => Object.values( state.commands[ group ] ?? {} ), + ( state, group ) => [ state.commands[ group ] ] ); -export const getCommandLoader = createSelector( - ( state, page ) => state.commandLoaders[ page ] ?? noop, - ( state, page ) => [ state.commandLoaders[ page ] ] +export const getCommandLoaders = createSelector( + ( state, group ) => Object.values( state.commandLoaders[ group ] ?? {} ), + ( state, group ) => [ state.commandLoaders[ group ] ] ); - -export function getPagePlaceholder( state, page ) { - return state.placeholders[ page ]; -} diff --git a/packages/edit-site/src/hooks/commands/use-navigation-commands.js b/packages/edit-site/src/hooks/commands/use-navigation-commands.js index 199c10e17710a2..e1a6ce8b868521 100644 --- a/packages/edit-site/src/hooks/commands/use-navigation-commands.js +++ b/packages/edit-site/src/hooks/commands/use-navigation-commands.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { useCommand, useCommandLoader } from '@wordpress/commands'; +import { useCommandLoader } from '@wordpress/commands'; import { __ } from '@wordpress/i18n'; import { useMemo } from '@wordpress/element'; import { useSelect } from '@wordpress/data'; @@ -55,17 +55,9 @@ function useNavigationCommandLoader( { search } ) { } export function useNavigationCommands() { - useCommand( { - name: 'core/edit-site/navigate', - label: __( 'Navigate to a page' ), - callback: ( { navigateToPage } ) => { - navigateToPage( 'core/edit-site/navigate' ); - }, - } ); - useCommandLoader( { - page: 'core/edit-site/navigate', + name: 'core/edit-site/navigate', + group: __( 'Pages' ), hook: useNavigationCommandLoader, - placeholder: __( 'Search for a page…' ), } ); } From 319b9ae534c4af52058bcd04e55f05e4cc754e08 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 30 Mar 2023 10:36:50 +0100 Subject: [PATCH 12/37] Add posts, templates and template parts --- .../hooks/commands/use-navigation-commands.js | 105 +++++++++++------- 1 file changed, 65 insertions(+), 40 deletions(-) diff --git a/packages/edit-site/src/hooks/commands/use-navigation-commands.js b/packages/edit-site/src/hooks/commands/use-navigation-commands.js index e1a6ce8b868521..238b3abf44a5ef 100644 --- a/packages/edit-site/src/hooks/commands/use-navigation-commands.js +++ b/packages/edit-site/src/hooks/commands/use-navigation-commands.js @@ -12,52 +12,77 @@ import { store as coreStore } from '@wordpress/core-data'; */ import { useHistory } from '../../components/routes'; -function useNavigationCommandLoader( { search } ) { - const history = useHistory(); - const { pages, isLoading } = useSelect( - ( select ) => { - const { getEntityRecords } = select( coreStore ); - const query = { - search, - per_page: 20, - orderby: 'date', - }; - return { - pages: getEntityRecords( 'postType', 'page', query ), - isLoading: ! select( coreStore ).hasFinishedResolution( - 'getEntityRecords', - [ 'postType', 'page', query ] - ), - }; - }, - [ search ] - ); +const getNavigationCommandLoaderPerPostType = ( postType ) => + function useNavigationCommandLoader( { search } ) { + const history = useHistory(); + const { records, isLoading } = useSelect( + ( select ) => { + const { getEntityRecords } = select( coreStore ); + const query = { + search, + per_page: 20, + orderby: 'date', + }; + return { + records: getEntityRecords( 'postType', postType, query ), + isLoading: ! select( coreStore ).hasFinishedResolution( + 'getEntityRecords', + [ 'postType', postType, query ] + ), + }; + }, + [ search ] + ); - const commands = useMemo( () => { - return ( pages ?? [] ).map( ( page ) => { - return { - name: page.title?.rendered + ' ' + page.id, - label: page.title?.rendered, - callback: () => { - history.push( { - postType: 'page', - postId: page.id, - } ); - }, - }; - } ); - }, [ pages, history ] ); + const commands = useMemo( () => { + return ( records ?? [] ).map( ( record ) => { + return { + name: record.title?.rendered + ' ' + record.id, + label: record.title?.rendered, + callback: () => { + history.push( { + postType, + postId: record.id, + } ); + }, + }; + } ); + }, [ records, history ] ); - return { - commands, - isLoading, + return { + commands, + isLoading, + }; }; -} + +const usePageNavigationCommandLoader = + getNavigationCommandLoaderPerPostType( 'page' ); +const usePostNavigationCommandLoader = + getNavigationCommandLoaderPerPostType( 'post' ); +const useTemplateNavigationCommandLoader = + getNavigationCommandLoaderPerPostType( 'wp_template' ); +const useTemplatePartNavigationCommandLoader = + getNavigationCommandLoaderPerPostType( 'wp_template_part' ); export function useNavigationCommands() { useCommandLoader( { - name: 'core/edit-site/navigate', + name: 'core/edit-site/navigate-pages', group: __( 'Pages' ), - hook: useNavigationCommandLoader, + hook: usePageNavigationCommandLoader, + } ); + useCommandLoader( { + name: 'core/edit-site/navigate-posts', + group: __( 'Posts' ), + hook: usePostNavigationCommandLoader, + } ); + useCommandLoader( { + name: 'core/edit-site/navigate-templates', + group: __( 'Templates' ), + hook: useTemplateNavigationCommandLoader, + } ); + useCommandLoader( { + name: 'core/edit-site/navigate-template-parts', + group: __( 'Template Parts' ), + hook: useTemplatePartNavigationCommandLoader, } ); } From ad7562764a19acc6ab7169f8c5f7982f96219e80 Mon Sep 17 00:00:00 2001 From: James Koster Date: Thu, 30 Mar 2023 14:16:31 +0100 Subject: [PATCH 13/37] Placeholder --- packages/commands/src/components/command-menu.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/commands/src/components/command-menu.js b/packages/commands/src/components/command-menu.js index 3eeb48c3e56e53..f9704640554e91 100644 --- a/packages/commands/src/components/command-menu.js +++ b/packages/commands/src/components/command-menu.js @@ -142,7 +142,9 @@ export function CommandMenu() { autoFocus value={ search } onValueChange={ setSearch } - placeholder={ __( 'Ask anything' ) } + placeholder={ __( + 'Search for content and templates, or try commands like "Create…"' + ) } />
From a5730da272d7161315e268ed07025e35de3113da Mon Sep 17 00:00:00 2001 From: James Koster Date: Thu, 30 Mar 2023 14:35:33 +0100 Subject: [PATCH 14/37] Style tweaks * Decrease item height * Increase heading spacing --- packages/commands/src/components/style.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/commands/src/components/style.scss b/packages/commands/src/components/style.scss index d3af6742121a68..290dd70d15b357 100644 --- a/packages/commands/src/components/style.scss +++ b/packages/commands/src/components/style.scss @@ -79,7 +79,7 @@ content-visibility: auto; border-radius: $radius-block-ui; cursor: pointer; - height: $grid-unit-60; + height: $grid-unit-50; display: flex; align-items: center; gap: $grid-unit-15; @@ -135,7 +135,7 @@ [cmdk-group-heading] { user-select: none; color: $gray-700; - padding: $grid-unit-20; + padding: $grid-unit-20 $grid-unit-20 $grid-unit-10; display: flex; align-items: center; font-size: 12px; From 399440019d63c18220112b20b33740cf470763bd Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Fri, 31 Mar 2023 12:53:27 +0100 Subject: [PATCH 15/37] Add empty state --- .../commands/src/components/command-menu.js | 33 +++++++++++++++---- packages/commands/src/components/style.scss | 4 +++ 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/packages/commands/src/components/command-menu.js b/packages/commands/src/components/command-menu.js index f9704640554e91..0b88c1fc8b7903 100644 --- a/packages/commands/src/components/command-menu.js +++ b/packages/commands/src/components/command-menu.js @@ -7,7 +7,7 @@ import { Command } from 'cmdk'; * WordPress dependencies */ import { useSelect } from '@wordpress/data'; -import { useState, useEffect, useRef } from '@wordpress/element'; +import { useState, useEffect, useRef, useCallback } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { Modal } from '@wordpress/components'; @@ -16,8 +16,11 @@ import { Modal } from '@wordpress/components'; */ import { store as commandsStore } from '../store'; -function CommandMenuLoader( { search, hook } ) { +function CommandMenuLoader( { name, search, hook, setLoader } ) { const { isLoading, commands = [] } = hook( { search } ) ?? {}; + useEffect( () => { + setLoader( name, isLoading ); + }, [ setLoader, name, isLoading ] ); return ( <> @@ -25,9 +28,6 @@ function CommandMenuLoader( { search, hook } ) { { isLoading && ( { __( 'Searching…' ) } ) } - { ! isLoading && ! commands?.length && ( - { __( 'No results found.' ) } - ) } { commands.map( ( command ) => ( ); } -export function CommandMenuGroup( { group, search } ) { +export function CommandMenuGroup( { group, search, setLoader } ) { const { commands, loaders } = useSelect( ( select ) => { const { getCommands, getCommandLoaders } = select( commandsStore ); @@ -95,6 +96,7 @@ export function CommandMenuGroup( { group, search } ) { key={ loader.name } hook={ loader.hook } search={ search } + setLoader={ setLoader } /> ) ) } @@ -110,6 +112,7 @@ export function CommandMenu() { groups: getGroups(), }; }, [] ); + const [ loaders, setLoaders ] = useState( {} ); // Toggle the menu when ⌘K is pressed useEffect( () => { @@ -123,9 +126,19 @@ export function CommandMenu() { return () => document.removeEventListener( 'keydown', down ); }, [] ); + const setLoader = useCallback( + ( name, value ) => + setLoaders( ( current ) => ( { + ...current, + [ name ]: value, + } ) ), + [] + ); + if ( ! open ) { return false; } + const isLoading = Object.values( loaders ).some( Boolean ); return ( + { ! isLoading && ( + + { __( 'No results found.' ) } + + ) } { groups.map( ( group ) => ( ) ) } diff --git a/packages/commands/src/components/style.scss b/packages/commands/src/components/style.scss index 290dd70d15b357..b6100b7fd78bd2 100644 --- a/packages/commands/src/components/style.scss +++ b/packages/commands/src/components/style.scss @@ -150,6 +150,10 @@ color: $gray-800; } + [cmdk-loading] { + padding: $grid-unit-20; + } + [cmdk-list-sizer] { position: relative; } From 24b6d2df81c1d6037d2f2f2f0dba752086287582 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Fri, 31 Mar 2023 13:00:45 +0100 Subject: [PATCH 16/37] Close command center on click --- .../commands/src/components/command-menu.js | 16 ++++--- .../hooks/commands/use-navigation-commands.js | 44 +++++++++++-------- 2 files changed, 35 insertions(+), 25 deletions(-) diff --git a/packages/commands/src/components/command-menu.js b/packages/commands/src/components/command-menu.js index 0b88c1fc8b7903..99967c85274b13 100644 --- a/packages/commands/src/components/command-menu.js +++ b/packages/commands/src/components/command-menu.js @@ -16,7 +16,7 @@ import { Modal } from '@wordpress/components'; */ import { store as commandsStore } from '../store'; -function CommandMenuLoader( { name, search, hook, setLoader } ) { +function CommandMenuLoader( { name, search, hook, setLoader, close } ) { const { isLoading, commands = [] } = hook( { search } ) ?? {}; useEffect( () => { setLoader( name, isLoading ); @@ -33,7 +33,7 @@ function CommandMenuLoader( { name, search, hook, setLoader } ) { command.callback() } + onSelect={ () => command.callback( { close } ) } > { command.label } @@ -43,7 +43,7 @@ function CommandMenuLoader( { name, search, hook, setLoader } ) { ); } -export function CommandMenuLoaderWrapper( { hook, search, setLoader } ) { +export function CommandMenuLoaderWrapper( { hook, search, setLoader, close } ) { // loader is actually a custom hook // so to avoid breaking the rules of hooks // the CommandsPerPage component need to be @@ -64,11 +64,12 @@ export function CommandMenuLoaderWrapper( { hook, search, setLoader } ) { hook={ currentLoader.current } search={ search } setLoader={ setLoader } + close={ close } /> ); } -export function CommandMenuGroup( { group, search, setLoader } ) { +export function CommandMenuGroup( { group, search, setLoader, close } ) { const { commands, loaders } = useSelect( ( select ) => { const { getCommands, getCommandLoaders } = select( commandsStore ); @@ -86,7 +87,7 @@ export function CommandMenuGroup( { group, search, setLoader } ) { command.callback() } + onSelect={ () => command.callback( { close } ) } > { command.label } @@ -97,6 +98,7 @@ export function CommandMenuGroup( { group, search, setLoader } ) { hook={ loader.hook } search={ search } setLoader={ setLoader } + close={ close } /> ) ) } @@ -134,6 +136,7 @@ export function CommandMenu() { } ) ), [] ); + const close = () => setOpen( false ); if ( ! open ) { return false; @@ -143,7 +146,7 @@ export function CommandMenu() { return ( setOpen( false ) } + onRequestClose={ close } __experimentalHideHeader >
@@ -172,6 +175,7 @@ export function CommandMenu() { group={ group } search={ search } setLoader={ setLoader } + close={ close } /> ) ) } diff --git a/packages/edit-site/src/hooks/commands/use-navigation-commands.js b/packages/edit-site/src/hooks/commands/use-navigation-commands.js index 238b3abf44a5ef..cfb0e297f54e08 100644 --- a/packages/edit-site/src/hooks/commands/use-navigation-commands.js +++ b/packages/edit-site/src/hooks/commands/use-navigation-commands.js @@ -14,36 +14,42 @@ import { useHistory } from '../../components/routes'; const getNavigationCommandLoaderPerPostType = ( postType ) => function useNavigationCommandLoader( { search } ) { - const history = useHistory(); - const { records, isLoading } = useSelect( - ( select ) => { - const { getEntityRecords } = select( coreStore ); - const query = { - search, - per_page: 20, - orderby: 'date', - }; - return { - records: getEntityRecords( 'postType', postType, query ), - isLoading: ! select( coreStore ).hasFinishedResolution( - 'getEntityRecords', - [ 'postType', postType, query ] - ), - }; - }, - [ search ] + const supportsSearch = ! [ 'wp_template', 'wp_template_part' ].includes( + postType ); + const deps = supportsSearch ? [ search ] : []; + const history = useHistory(); + const { records, isLoading } = useSelect( ( select ) => { + const { getEntityRecords } = select( coreStore ); + const query = supportsSearch + ? { + search, + per_page: 20, + orderby: 'date', + } + : { + per_page: -1, + }; + return { + records: getEntityRecords( 'postType', postType, query ), + isLoading: ! select( coreStore ).hasFinishedResolution( + 'getEntityRecords', + [ 'postType', postType, query ] + ), + }; + }, deps ); const commands = useMemo( () => { return ( records ?? [] ).map( ( record ) => { return { name: record.title?.rendered + ' ' + record.id, label: record.title?.rendered, - callback: () => { + callback: ( { close } ) => { history.push( { postType, postId: record.id, } ); + close(); }, }; } ); From 8deb50ff040e91062f8cb9dd36942bfaf518bd1d Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Fri, 31 Mar 2023 13:06:41 +0100 Subject: [PATCH 17/37] Do not change the canvas mode --- .../src/hooks/commands/use-navigation-commands.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/edit-site/src/hooks/commands/use-navigation-commands.js b/packages/edit-site/src/hooks/commands/use-navigation-commands.js index cfb0e297f54e08..96f6008e568a4c 100644 --- a/packages/edit-site/src/hooks/commands/use-navigation-commands.js +++ b/packages/edit-site/src/hooks/commands/use-navigation-commands.js @@ -10,6 +10,8 @@ import { store as coreStore } from '@wordpress/core-data'; /** * Internal dependencies */ +import { store as editSiteStore } from '../../store'; +import { unlock } from '../../private-apis'; import { useHistory } from '../../components/routes'; const getNavigationCommandLoaderPerPostType = ( postType ) => @@ -19,7 +21,7 @@ const getNavigationCommandLoaderPerPostType = ( postType ) => ); const deps = supportsSearch ? [ search ] : []; const history = useHistory(); - const { records, isLoading } = useSelect( ( select ) => { + const { canvasMode, records, isLoading } = useSelect( ( select ) => { const { getEntityRecords } = select( coreStore ); const query = supportsSearch ? { @@ -36,6 +38,7 @@ const getNavigationCommandLoaderPerPostType = ( postType ) => 'getEntityRecords', [ 'postType', postType, query ] ), + canvasMode: unlock( select( editSiteStore ) ).getCanvasMode(), }; }, deps ); @@ -48,6 +51,8 @@ const getNavigationCommandLoaderPerPostType = ( postType ) => history.push( { postType, postId: record.id, + canvas: + canvasMode === 'edit' ? canvasMode : undefined, } ); close(); }, From c366b31546bb76ff593e84701aa80580c09cca58 Mon Sep 17 00:00:00 2001 From: James Koster Date: Fri, 31 Mar 2023 19:09:59 +0100 Subject: [PATCH 18/37] Make group headings sticky --- packages/commands/src/components/style.scss | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/commands/src/components/style.scss b/packages/commands/src/components/style.scss index b6100b7fd78bd2..0aaa2acf50b54a 100644 --- a/packages/commands/src/components/style.scss +++ b/packages/commands/src/components/style.scss @@ -135,10 +135,16 @@ [cmdk-group-heading] { user-select: none; color: $gray-700; - padding: $grid-unit-20 $grid-unit-20 $grid-unit-10; + padding: $grid-unit-30 $grid-unit-20 $grid-unit-20; display: flex; align-items: center; - font-size: 12px; + font-size: 11px; + text-transform: uppercase; + font-weight: 500; + position: sticky; + top: 0; + background: $white; + z-index: 1; } [cmdk-empty] { From 6a446d7f65600b400755a670c4e45fa8a7d1858b Mon Sep 17 00:00:00 2001 From: Saxon Fletcher Date: Mon, 3 Apr 2023 09:48:45 +1000 Subject: [PATCH 19/37] fixed top position for commands --- packages/commands/src/components/command-menu.js | 1 + packages/commands/src/components/style.scss | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/packages/commands/src/components/command-menu.js b/packages/commands/src/components/command-menu.js index 99967c85274b13..3d6883024cba9c 100644 --- a/packages/commands/src/components/command-menu.js +++ b/packages/commands/src/components/command-menu.js @@ -146,6 +146,7 @@ export function CommandMenu() { return ( diff --git a/packages/commands/src/components/style.scss b/packages/commands/src/components/style.scss index 0aaa2acf50b54a..cdbed8e2d4747b 100644 --- a/packages/commands/src/components/style.scss +++ b/packages/commands/src/components/style.scss @@ -3,6 +3,8 @@ padding: 0; width: 100%; max-width: 680px; + position: relative; + top: 15%; .components-modal__content { margin: 0; @@ -10,6 +12,11 @@ } } +.commands-command-menu__overlay { + display: block; + align-items: start; +} + .commands-command-menu__header { display: flex; align-items: center; From 0363e5010d63cd6f644d73b0de6f4c3436573467 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 3 Apr 2023 08:57:59 +0100 Subject: [PATCH 20/37] Update Typo Co-authored-by: Daniel Richards --- packages/commands/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/commands/README.md b/packages/commands/README.md index eebf64a06c2a9c..b1f3de1ef9d063 100644 --- a/packages/commands/README.md +++ b/packages/commands/README.md @@ -1,4 +1,4 @@ -# Keyboard Shortcuts +# Commands Commands is a generic package that allows registering and modifying commands to be displayed using the commands menu (Also called cmd+k). From 02e209880f70a8a2f807a34df6bd59fc2492c25b Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 3 Apr 2023 09:11:10 +0100 Subject: [PATCH 21/37] Small fix --- .../edit-site/src/hooks/commands/use-navigation-commands.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/edit-site/src/hooks/commands/use-navigation-commands.js b/packages/edit-site/src/hooks/commands/use-navigation-commands.js index 96f6008e568a4c..765caf1418a9cd 100644 --- a/packages/edit-site/src/hooks/commands/use-navigation-commands.js +++ b/packages/edit-site/src/hooks/commands/use-navigation-commands.js @@ -26,7 +26,7 @@ const getNavigationCommandLoaderPerPostType = ( postType ) => const query = supportsSearch ? { search, - per_page: 20, + per_page: 10, orderby: 'date', } : { @@ -43,7 +43,7 @@ const getNavigationCommandLoaderPerPostType = ( postType ) => }, deps ); const commands = useMemo( () => { - return ( records ?? [] ).map( ( record ) => { + return ( records ?? [] ).slice( 0, 10 ).map( ( record ) => { return { name: record.title?.rendered + ' ' + record.id, label: record.title?.rendered, @@ -58,7 +58,7 @@ const getNavigationCommandLoaderPerPostType = ( postType ) => }, }; } ); - }, [ records, history ] ); + }, [ records, history, canvasMode ] ); return { commands, From 3ae0f23559ba8adda05803f02fe33fbca913008a Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 3 Apr 2023 09:40:43 +0100 Subject: [PATCH 22/37] Allow providing post titles --- .../commands/src/components/command-menu.js | 12 ++- .../hooks/commands/use-wp-admin-commands.js | 82 ++++++++++++++----- 2 files changed, 70 insertions(+), 24 deletions(-) diff --git a/packages/commands/src/components/command-menu.js b/packages/commands/src/components/command-menu.js index 3d6883024cba9c..4f40f62ff1358e 100644 --- a/packages/commands/src/components/command-menu.js +++ b/packages/commands/src/components/command-menu.js @@ -32,7 +32,7 @@ function CommandMenuLoader( { name, search, hook, setLoader, close } ) { { commands.map( ( command ) => ( command.callback( { close } ) } > { command.label } @@ -86,7 +86,7 @@ export function CommandMenuGroup( { group, search, setLoader, close } ) { { commands.map( ( command ) => ( command.callback( { close } ) } > { command.label } @@ -151,7 +151,13 @@ export function CommandMenu() { __experimentalHideHeader >
- + { + if ( value.includes( s ) ) return 1; + return 0; + } } + >
{ - select( editSiteStore ).getEditedPostType(); - const { getSettings } = unlock( select( editSiteStore ) ); - return getSettings().newPostLink ?? 'post-new.php'; - }, [] ); +const getWPAdminCreateCommandLoader = ( postType ) => + function useCreateCommandLoader( { search } ) { + let label; + if ( postType === 'post' ) { + label = __( 'Create a new post' ); + } else if ( postType === 'page' ) { + label = __( 'Create a new page' ); + } else { + throw 'unsupported post type ' + postType; + } + const hasRecordTitle = + !! search && ! label.toLowerCase().includes( search.toLowerCase() ); + if ( postType === 'post' && hasRecordTitle ) { + /* translators: %s: Post title placeholder */ + label = sprintf( __( 'Create a new post "%s"' ), search ); + } else if ( postType === 'page' && hasRecordTitle ) { + /* translators: %s: Page title placeholder */ + label = sprintf( __( 'Create a new page "%s"' ), search ); + } - useCommand( { - name: 'core/wp-admin/create-post', - label: __( 'Create a new post' ), - callback: () => { - document.location.href = newPostLink; - }, - } ); + const newPostLink = useSelect( ( select ) => { + select( editSiteStore ).getEditedPostType(); + const { getSettings } = unlock( select( editSiteStore ) ); + return getSettings().newPostLink ?? 'post-new.php'; + }, [] ); + + const commands = useMemo( + () => [ + { + name: 'core/wp-admin/create-' + postType, + label, + callback: () => { + document.location.href = addQueryArgs( newPostLink, { + post_type: postType, + post_title: hasRecordTitle ? search : undefined, + } ); + }, + }, + ], + [ newPostLink, hasRecordTitle, search, label ] + ); + + return { + isLoading: false, + commands, + }; + }; - useCommand( { - name: 'core/wp-admin/create-page', - label: __( 'Create a new page' ), - callback: () => { - document.location.href = newPostLink + '?post_type=page'; - }, +const useCreatePostLoader = getWPAdminCreateCommandLoader( 'post' ); +const useCreatePageLoader = getWPAdminCreateCommandLoader( 'page' ); + +export function useWPAdminCommands() { + useCommandLoader( { + name: 'core/wp-admin/create-post-loader', + hook: useCreatePostLoader, + } ); + useCommandLoader( { + name: 'core/wp-admin/create-page-loader', + hook: useCreatePageLoader, } ); } From c59ee409d7d13792cb8c4c1d599e7325da8bb7ae Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 3 Apr 2023 10:05:19 +0100 Subject: [PATCH 23/37] Highlight the searched text --- packages/commands/src/components/command-menu.js | 16 +++++++++++++--- packages/commands/src/components/style.scss | 6 ++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/commands/src/components/command-menu.js b/packages/commands/src/components/command-menu.js index 4f40f62ff1358e..a52fb42ef2b238 100644 --- a/packages/commands/src/components/command-menu.js +++ b/packages/commands/src/components/command-menu.js @@ -9,7 +9,7 @@ import { Command } from 'cmdk'; import { useSelect } from '@wordpress/data'; import { useState, useEffect, useRef, useCallback } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { Modal } from '@wordpress/components'; +import { Modal, TextHighlight } from '@wordpress/components'; /** * Internal dependencies @@ -35,7 +35,12 @@ function CommandMenuLoader( { name, search, hook, setLoader, close } ) { value={ command.label ?? command.name } onSelect={ () => command.callback( { close } ) } > - { command.label } + + + ) ) } @@ -89,7 +94,12 @@ export function CommandMenuGroup( { group, search, setLoader, close } ) { value={ command.label ?? command.name } onSelect={ () => command.callback( { close } ) } > - { command.label } + + + ) ) } { loaders.map( ( loader ) => ( diff --git a/packages/commands/src/components/style.scss b/packages/commands/src/components/style.scss index cdbed8e2d4747b..e04d597b21263d 100644 --- a/packages/commands/src/components/style.scss +++ b/packages/commands/src/components/style.scss @@ -171,3 +171,9 @@ position: relative; } } + +.commands-command-menu__item mark { + color: inherit; + background: unset; + font-weight: bold; +} From 9b237ad0788086f7ff3232419f77fcc2ffc6dbf0 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 3 Apr 2023 10:11:32 +0100 Subject: [PATCH 24/37] Remove useless custom search --- packages/commands/src/components/command-menu.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/commands/src/components/command-menu.js b/packages/commands/src/components/command-menu.js index a52fb42ef2b238..b8e06c051de161 100644 --- a/packages/commands/src/components/command-menu.js +++ b/packages/commands/src/components/command-menu.js @@ -161,13 +161,7 @@ export function CommandMenu() { __experimentalHideHeader >
- { - if ( value.includes( s ) ) return 1; - return 0; - } } - > +
Date: Mon, 3 Apr 2023 10:20:40 +0100 Subject: [PATCH 25/37] Make the API private for now --- packages/commands/README.md | 16 ++------------ packages/commands/package.json | 1 + packages/commands/src/index.js | 3 +-- packages/commands/src/private-apis.js | 22 +++++++++++++++++++ .../hooks/commands/use-navigation-commands.js | 4 +++- .../hooks/commands/use-wp-admin-commands.js | 4 +++- packages/private-apis/src/implementation.js | 1 + 7 files changed, 33 insertions(+), 18 deletions(-) create mode 100644 packages/commands/src/private-apis.js diff --git a/packages/commands/README.md b/packages/commands/README.md index b1f3de1ef9d063..3113c540e4dd05 100644 --- a/packages/commands/README.md +++ b/packages/commands/README.md @@ -20,21 +20,9 @@ _This package assumes that your code will run in an **ES2015+** environment. If Undocumented declaration. -### useCommand +### privateApis -Attach a command to the Global command menu. - -_Parameters_ - -- _command_ `import('../store/actions').WPCommandConfig`: command config. - -### useCommandLoader - -Attach a command loader to the Global command menu. - -_Parameters_ - -- _loader_ `import('../store/actions').WPCommandLoaderConfig`: command loader config. +Undocumented declaration. diff --git a/packages/commands/package.json b/packages/commands/package.json index 889366abf731b5..8aae71ff97a1e2 100644 --- a/packages/commands/package.json +++ b/packages/commands/package.json @@ -32,6 +32,7 @@ "@wordpress/element": "file:../element", "@wordpress/i18n": "file:../i18n", "@wordpress/icons": "file:../icons", + "@wordpress/private-apis": "file:../private-apis", "cmdk": "^0.2.0", "rememo": "^4.0.0" }, diff --git a/packages/commands/src/index.js b/packages/commands/src/index.js index da243add477f76..fbac76197f0b24 100644 --- a/packages/commands/src/index.js +++ b/packages/commands/src/index.js @@ -1,3 +1,2 @@ -export { default as useCommand } from './hooks/use-command'; -export { default as useCommandLoader } from './hooks/use-command-loader'; export { CommandMenu } from './components/command-menu'; +export { privateApis } from './private-apis'; diff --git a/packages/commands/src/private-apis.js b/packages/commands/src/private-apis.js new file mode 100644 index 00000000000000..ef81227bdb7206 --- /dev/null +++ b/packages/commands/src/private-apis.js @@ -0,0 +1,22 @@ +/** + * WordPress dependencies + */ +import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '@wordpress/private-apis'; + +/** + * Internal dependencies + */ +import { default as useCommand } from './hooks/use-command'; +import { default as useCommandLoader } from './hooks/use-command-loader'; + +export const { lock, unlock } = + __dangerousOptInToUnstableAPIsOnlyForCoreModules( + 'I know using unstable features means my plugin or theme will inevitably break on the next WordPress release.', + '@wordpress/commands' + ); + +export const privateApis = {}; +lock( privateApis, { + useCommand, + useCommandLoader, +} ); diff --git a/packages/edit-site/src/hooks/commands/use-navigation-commands.js b/packages/edit-site/src/hooks/commands/use-navigation-commands.js index 765caf1418a9cd..dd099c29d91713 100644 --- a/packages/edit-site/src/hooks/commands/use-navigation-commands.js +++ b/packages/edit-site/src/hooks/commands/use-navigation-commands.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { useCommandLoader } from '@wordpress/commands'; +import { privateApis } from '@wordpress/commands'; import { __ } from '@wordpress/i18n'; import { useMemo } from '@wordpress/element'; import { useSelect } from '@wordpress/data'; @@ -14,6 +14,8 @@ import { store as editSiteStore } from '../../store'; import { unlock } from '../../private-apis'; import { useHistory } from '../../components/routes'; +const { useCommandLoader } = unlock( privateApis ); + const getNavigationCommandLoaderPerPostType = ( postType ) => function useNavigationCommandLoader( { search } ) { const supportsSearch = ! [ 'wp_template', 'wp_template_part' ].includes( diff --git a/packages/edit-site/src/hooks/commands/use-wp-admin-commands.js b/packages/edit-site/src/hooks/commands/use-wp-admin-commands.js index 7a9e0000cc8956..117c1514ea3712 100644 --- a/packages/edit-site/src/hooks/commands/use-wp-admin-commands.js +++ b/packages/edit-site/src/hooks/commands/use-wp-admin-commands.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { useCommandLoader } from '@wordpress/commands'; +import { privateApis } from '@wordpress/commands'; import { __, sprintf } from '@wordpress/i18n'; import { useSelect } from '@wordpress/data'; import { addQueryArgs } from '@wordpress/url'; @@ -13,6 +13,8 @@ import { useMemo } from '@wordpress/element'; import { store as editSiteStore } from '../../store'; import { unlock } from '../../private-apis'; +const { useCommandLoader } = unlock( privateApis ); + const getWPAdminCreateCommandLoader = ( postType ) => function useCreateCommandLoader( { search } ) { let label; diff --git a/packages/private-apis/src/implementation.js b/packages/private-apis/src/implementation.js index c37ae7b227bc9e..4bda8dd039eb2d 100644 --- a/packages/private-apis/src/implementation.js +++ b/packages/private-apis/src/implementation.js @@ -13,6 +13,7 @@ const CORE_MODULES_USING_PRIVATE_APIS = [ '@wordpress/block-editor', '@wordpress/block-library', '@wordpress/blocks', + '@wordpress/commands', '@wordpress/components', '@wordpress/customize-widgets', '@wordpress/data', From 1cbad9d335ec36639e9c9c60cb3fc712730e2236 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 3 Apr 2023 11:31:50 +0100 Subject: [PATCH 26/37] Add a couple e2e tests --- .../specs/site-editor/command-center.spec.js | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 test/e2e/specs/site-editor/command-center.spec.js diff --git a/test/e2e/specs/site-editor/command-center.spec.js b/test/e2e/specs/site-editor/command-center.spec.js new file mode 100644 index 00000000000000..cb42c87e7502a1 --- /dev/null +++ b/test/e2e/specs/site-editor/command-center.spec.js @@ -0,0 +1,53 @@ +/** + * WordPress dependencies + */ +const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); + +test.describe( 'Site editor command center', () => { + test.beforeAll( async ( { requestUtils } ) => { + await requestUtils.activateTheme( 'emptytheme' ); + } ); + + test.afterAll( async ( { requestUtils } ) => { + await requestUtils.activateTheme( 'twentytwentyone' ); + } ); + + test( 'Open the command center and navigate to the page create page', async ( { + admin, + page, + } ) => { + // Navigate to a template. + await admin.visitSiteEditor(); + await page.keyboard.press( 'Meta+k' ); + const newPageButton = page.locator( + 'role=option[name="Create a new page"i]' + ); + await expect( newPageButton ).toBeVisible(); + + // Type a random post title + await page.keyboard.type( 'E2E Test Post' ); + await page.click( + 'role=option[name="Create a new post \\"E2E Test Post\\""i]' + ); + + await page.waitForSelector( 'iframe[name="editor-canvas"]' ); + const frame = page.frame( 'editor-canvas' ); + const postTitleInput = frame.locator( + 'role=textbox[name=/Add title/i]' + ); + await expect( postTitleInput ).toHaveText( 'E2E Test Post' ); + } ); + + test( 'Open the command center and navigate to a template', async ( { + admin, + page, + } ) => { + // Navigate to a template. + await admin.visitSiteEditor(); + await page.keyboard.press( 'Meta+k' ); + + await page.keyboard.type( 'index' ); + await page.click( 'role=option[name="index"i]' ); + await expect( page.locator( 'h2' ) ).toHaveText( 'Index' ); + } ); +} ); From 2c23eb928831483441eb9ca7a45877d64c728f17 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 3 Apr 2023 11:44:06 +0100 Subject: [PATCH 27/37] Fix lock file --- package-lock.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package-lock.json b/package-lock.json index b7af084d26a60b..811af9f2c77b3a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17054,6 +17054,7 @@ "@wordpress/element": "file:packages/element", "@wordpress/i18n": "file:packages/i18n", "@wordpress/icons": "file:packages/icons", + "@wordpress/private-apis": "file:packages/private-apis", "cmdk": "^0.2.0", "rememo": "^4.0.0" } From 38ad2ceadb9a711f2be90f1d552b4d02a70e3066 Mon Sep 17 00:00:00 2001 From: James Koster Date: Mon, 3 Apr 2023 18:10:10 +0100 Subject: [PATCH 28/37] font weight --- packages/commands/src/components/style.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/commands/src/components/style.scss b/packages/commands/src/components/style.scss index e04d597b21263d..1847c136252a67 100644 --- a/packages/commands/src/components/style.scss +++ b/packages/commands/src/components/style.scss @@ -175,5 +175,5 @@ .commands-command-menu__item mark { color: inherit; background: unset; - font-weight: bold; + font-weight: 600; } From 6997207b4640fbc2dbc58bed50c9407580976617 Mon Sep 17 00:00:00 2001 From: Miguel Fonseca Date: Tue, 4 Apr 2023 10:43:48 +0100 Subject: [PATCH 29/37] Prevent browser default on Meta+K In Firefox, Meta+K pulls up the browser's search box. --- packages/commands/src/components/command-menu.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/commands/src/components/command-menu.js b/packages/commands/src/components/command-menu.js index b8e06c051de161..17a2d51ffa7c44 100644 --- a/packages/commands/src/components/command-menu.js +++ b/packages/commands/src/components/command-menu.js @@ -126,16 +126,17 @@ export function CommandMenu() { }, [] ); const [ loaders, setLoaders ] = useState( {} ); - // Toggle the menu when ⌘K is pressed + // Toggle the menu when Meta-K is pressed useEffect( () => { - const down = ( e ) => { + const toggleOnMetaK = ( e ) => { if ( e.key === 'k' && e.metaKey ) { setOpen( ( prevOpen ) => ! prevOpen ); + e.preventDefault(); } }; - document.addEventListener( 'keydown', down ); - return () => document.removeEventListener( 'keydown', down ); + document.addEventListener( 'keydown', toggleOnMetaK ); + return () => document.removeEventListener( 'keydown', toggleOnMetaK ); }, [] ); const setLoader = useCallback( From f155e526325afbba8806189d62f06352e483e48b Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 4 Apr 2023 10:45:32 +0100 Subject: [PATCH 30/37] Fix unregistering command loaders --- packages/commands/src/store/reducer.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/commands/src/store/reducer.js b/packages/commands/src/store/reducer.js index 8d86ffb15ba1c5..6b4a65b9132e0d 100644 --- a/packages/commands/src/store/reducer.js +++ b/packages/commands/src/store/reducer.js @@ -61,8 +61,12 @@ function commandLoaders( state = {}, action ) { }, }; case 'UNREGISTER_COMMAND_LOADER': { - const { [ action.page ]: _, ...remainingState } = state; - return remainingState; + const { [ action.name ]: _, ...remainingState } = + state?.[ action.group ]; + return { + ...state, + [ action.group ]: remainingState, + }; } } From ca57c7db186da55cb3f67ae04c5441428088a51d Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 4 Apr 2023 12:27:09 +0100 Subject: [PATCH 31/37] Fix comment typos --- packages/commands/src/components/command-menu.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/commands/src/components/command-menu.js b/packages/commands/src/components/command-menu.js index 17a2d51ffa7c44..f30bb593741c90 100644 --- a/packages/commands/src/components/command-menu.js +++ b/packages/commands/src/components/command-menu.js @@ -49,10 +49,10 @@ function CommandMenuLoader( { name, search, hook, setLoader, close } ) { } export function CommandMenuLoaderWrapper( { hook, search, setLoader, close } ) { - // loader is actually a custom hook + // The "hook" prop is actually a custom React hook // so to avoid breaking the rules of hooks - // the CommandsPerPage component need to be - // remounted on each loader change + // the CommandMenuLoaderWrapper component need to be + // remounted on each hook prop change // We use the key state to make sure we do that properly. const currentLoader = useRef( hook ); const [ key, setKey ] = useState( 0 ); From e238211a1ccd8be77e7ce37ef3076d8821744e10 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 4 Apr 2023 14:02:37 +0100 Subject: [PATCH 32/37] Remove leftovers --- packages/edit-site/src/components/site-hub/index.js | 1 - packages/edit-site/src/hooks/commands/use-wp-admin-commands.js | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/edit-site/src/components/site-hub/index.js b/packages/edit-site/src/components/site-hub/index.js index 6fd7f90fc4ef56..850d6233829aef 100644 --- a/packages/edit-site/src/components/site-hub/index.js +++ b/packages/edit-site/src/components/site-hub/index.js @@ -29,7 +29,6 @@ const HUB_ANIMATION_DURATION = 0.3; const SiteHub = forwardRef( ( props, ref ) => { const { canvasMode, dashboardLink } = useSelect( ( select ) => { - select( editSiteStore ).getEditedPostType(); const { getCanvasMode, getSettings } = unlock( select( editSiteStore ) ); diff --git a/packages/edit-site/src/hooks/commands/use-wp-admin-commands.js b/packages/edit-site/src/hooks/commands/use-wp-admin-commands.js index 117c1514ea3712..d7aaf00b9cc9f9 100644 --- a/packages/edit-site/src/hooks/commands/use-wp-admin-commands.js +++ b/packages/edit-site/src/hooks/commands/use-wp-admin-commands.js @@ -36,7 +36,6 @@ const getWPAdminCreateCommandLoader = ( postType ) => } const newPostLink = useSelect( ( select ) => { - select( editSiteStore ).getEditedPostType(); const { getSettings } = unlock( select( editSiteStore ) ); return getSettings().newPostLink ?? 'post-new.php'; }, [] ); From ec903f51d6d16ebc402a649a5ddcca04d835f0c5 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 4 Apr 2023 14:54:36 +0100 Subject: [PATCH 33/37] Reset search on close --- packages/commands/src/components/command-menu.js | 5 ++++- .../edit-site/src/hooks/commands/use-navigation-commands.js | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/commands/src/components/command-menu.js b/packages/commands/src/components/command-menu.js index f30bb593741c90..f8fa04b4d7a0f7 100644 --- a/packages/commands/src/components/command-menu.js +++ b/packages/commands/src/components/command-menu.js @@ -147,7 +147,10 @@ export function CommandMenu() { } ) ), [] ); - const close = () => setOpen( false ); + const close = () => { + setSearch( '' ); + setOpen( false ); + }; if ( ! open ) { return false; diff --git a/packages/edit-site/src/hooks/commands/use-navigation-commands.js b/packages/edit-site/src/hooks/commands/use-navigation-commands.js index dd099c29d91713..5e857dd991177a 100644 --- a/packages/edit-site/src/hooks/commands/use-navigation-commands.js +++ b/packages/edit-site/src/hooks/commands/use-navigation-commands.js @@ -27,9 +27,9 @@ const getNavigationCommandLoaderPerPostType = ( postType ) => const { getEntityRecords } = select( coreStore ); const query = supportsSearch ? { - search, + search: !! search ? search : undefined, per_page: 10, - orderby: 'date', + orderby: search ? 'relevance' : 'date', } : { per_page: -1, From 172d6cfe02c177d5d2a1ba8c5ce3ac99ad2a6f24 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 6 Apr 2023 09:48:55 +0100 Subject: [PATCH 34/37] Replace create with add --- .../commands/src/components/command-menu.js | 2 +- .../hooks/commands/use-wp-admin-commands.js | 26 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/commands/src/components/command-menu.js b/packages/commands/src/components/command-menu.js index f8fa04b4d7a0f7..a00be674933d38 100644 --- a/packages/commands/src/components/command-menu.js +++ b/packages/commands/src/components/command-menu.js @@ -174,7 +174,7 @@ export function CommandMenu() { value={ search } onValueChange={ setSearch } placeholder={ __( - 'Search for content and templates, or try commands like "Create…"' + 'Search for content and templates, or try commands like "Add…"' ) } />
diff --git a/packages/edit-site/src/hooks/commands/use-wp-admin-commands.js b/packages/edit-site/src/hooks/commands/use-wp-admin-commands.js index d7aaf00b9cc9f9..4ab181ef42b803 100644 --- a/packages/edit-site/src/hooks/commands/use-wp-admin-commands.js +++ b/packages/edit-site/src/hooks/commands/use-wp-admin-commands.js @@ -15,13 +15,13 @@ import { unlock } from '../../private-apis'; const { useCommandLoader } = unlock( privateApis ); -const getWPAdminCreateCommandLoader = ( postType ) => - function useCreateCommandLoader( { search } ) { +const getWPAdminAddCommandLoader = ( postType ) => + function useAddCommandLoader( { search } ) { let label; if ( postType === 'post' ) { - label = __( 'Create a new post' ); + label = __( 'Add a new post' ); } else if ( postType === 'page' ) { - label = __( 'Create a new page' ); + label = __( 'Add a new page' ); } else { throw 'unsupported post type ' + postType; } @@ -29,10 +29,10 @@ const getWPAdminCreateCommandLoader = ( postType ) => !! search && ! label.toLowerCase().includes( search.toLowerCase() ); if ( postType === 'post' && hasRecordTitle ) { /* translators: %s: Post title placeholder */ - label = sprintf( __( 'Create a new post "%s"' ), search ); + label = sprintf( __( 'Add a new post "%s"' ), search ); } else if ( postType === 'page' && hasRecordTitle ) { /* translators: %s: Page title placeholder */ - label = sprintf( __( 'Create a new page "%s"' ), search ); + label = sprintf( __( 'Add a new page "%s"' ), search ); } const newPostLink = useSelect( ( select ) => { @@ -43,7 +43,7 @@ const getWPAdminCreateCommandLoader = ( postType ) => const commands = useMemo( () => [ { - name: 'core/wp-admin/create-' + postType, + name: 'core/wp-admin/add-' + postType, label, callback: () => { document.location.href = addQueryArgs( newPostLink, { @@ -62,16 +62,16 @@ const getWPAdminCreateCommandLoader = ( postType ) => }; }; -const useCreatePostLoader = getWPAdminCreateCommandLoader( 'post' ); -const useCreatePageLoader = getWPAdminCreateCommandLoader( 'page' ); +const useAddPostLoader = getWPAdminAddCommandLoader( 'post' ); +const useAddPageLoader = getWPAdminAddCommandLoader( 'page' ); export function useWPAdminCommands() { useCommandLoader( { - name: 'core/wp-admin/create-post-loader', - hook: useCreatePostLoader, + name: 'core/wp-admin/add-post-loader', + hook: useAddPostLoader, } ); useCommandLoader( { - name: 'core/wp-admin/create-page-loader', - hook: useCreatePageLoader, + name: 'core/wp-admin/add-page-loader', + hook: useAddPageLoader, } ); } From 8cb81b75b934627dad21646ac56a18d44366521b Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 6 Apr 2023 09:53:20 +0100 Subject: [PATCH 35/37] Use an experimental flag --- lib/experimental/editor-settings.php | 3 +++ lib/experiments-page.php | 12 ++++++++++++ packages/edit-site/src/components/layout/index.js | 2 +- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/experimental/editor-settings.php b/lib/experimental/editor-settings.php index eb01be26372ee4..a700a0f484371d 100644 --- a/lib/experimental/editor-settings.php +++ b/lib/experimental/editor-settings.php @@ -83,6 +83,9 @@ function gutenberg_enable_experiments() { if ( $gutenberg_experiments && array_key_exists( 'gutenberg-color-randomizer', $gutenberg_experiments ) ) { wp_add_inline_script( 'wp-block-editor', 'window.__experimentalEnableColorRandomizer = true', 'before' ); } + if ( $gutenberg_experiments && array_key_exists( 'gutenberg-command-center', $gutenberg_experiments ) ) { + wp_add_inline_script( 'wp-edit-site', 'window.__experimentalEnableCommandCenter = true', 'before' ); + } if ( $gutenberg_experiments && array_key_exists( 'gutenberg-group-grid-variation', $gutenberg_experiments ) ) { wp_add_inline_script( 'wp-block-editor', 'window.__experimentalEnableGroupGridVariation = true', 'before' ); } diff --git a/lib/experiments-page.php b/lib/experiments-page.php index 131c864527d4b0..5ba1830bb4e003 100644 --- a/lib/experiments-page.php +++ b/lib/experiments-page.php @@ -65,6 +65,18 @@ function gutenberg_initialize_experiments_settings() { ) ); + add_settings_field( + 'gutenberg-command-center', + __( 'Command center ', 'gutenberg' ), + 'gutenberg_display_experiment_field', + 'gutenberg-experiments', + 'gutenberg_experiments_section', + array( + 'label' => __( 'Test the command center; Open it using cmd + k in the site editor.', 'gutenberg' ), + 'id' => 'gutenberg-command-center', + ) + ); + add_settings_field( 'gutenberg-group-grid-variation', __( 'Grid variation for Group block ', 'gutenberg' ), diff --git a/packages/edit-site/src/components/layout/index.js b/packages/edit-site/src/components/layout/index.js index 3354cbfe98577f..7ea454da4f150a 100644 --- a/packages/edit-site/src/components/layout/index.js +++ b/packages/edit-site/src/components/layout/index.js @@ -126,7 +126,7 @@ export default function Layout() { return ( <> - + { window?.__experimentalEnableCommandCenter && } { fullResizer } From 4d3beca192485bb64a352b4fc886bbf5b749c4a9 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 6 Apr 2023 10:08:43 +0100 Subject: [PATCH 36/37] Fix empty pages and duplication in selected items --- packages/commands/src/components/command-menu.js | 4 ++-- .../edit-site/src/hooks/commands/use-navigation-commands.js | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/commands/src/components/command-menu.js b/packages/commands/src/components/command-menu.js index a00be674933d38..6ced4ced90424a 100644 --- a/packages/commands/src/components/command-menu.js +++ b/packages/commands/src/components/command-menu.js @@ -32,7 +32,7 @@ function CommandMenuLoader( { name, search, hook, setLoader, close } ) { { commands.map( ( command ) => ( command.callback( { close } ) } > @@ -91,7 +91,7 @@ export function CommandMenuGroup( { group, search, setLoader, close } ) { { commands.map( ( command ) => ( command.callback( { close } ) } > diff --git a/packages/edit-site/src/hooks/commands/use-navigation-commands.js b/packages/edit-site/src/hooks/commands/use-navigation-commands.js index 5e857dd991177a..995d16d5013966 100644 --- a/packages/edit-site/src/hooks/commands/use-navigation-commands.js +++ b/packages/edit-site/src/hooks/commands/use-navigation-commands.js @@ -48,7 +48,9 @@ const getNavigationCommandLoaderPerPostType = ( postType ) => return ( records ?? [] ).slice( 0, 10 ).map( ( record ) => { return { name: record.title?.rendered + ' ' + record.id, - label: record.title?.rendered, + label: record.title?.rendered + ? record.title?.rendered + : __( '(no title)' ), callback: ( { close } ) => { history.push( { postType, From 55e94f7ed4989159e4cb3689de17fce602c6823b Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 6 Apr 2023 10:16:59 +0100 Subject: [PATCH 37/37] Skip command center tests until the experimental flag is removed --- test/e2e/specs/site-editor/command-center.spec.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/test/e2e/specs/site-editor/command-center.spec.js b/test/e2e/specs/site-editor/command-center.spec.js index cb42c87e7502a1..936c8838db426e 100644 --- a/test/e2e/specs/site-editor/command-center.spec.js +++ b/test/e2e/specs/site-editor/command-center.spec.js @@ -12,12 +12,14 @@ test.describe( 'Site editor command center', () => { await requestUtils.activateTheme( 'twentytwentyone' ); } ); - test( 'Open the command center and navigate to the page create page', async ( { - admin, + test.beforeEach( async ( { admin } ) => { + // Navigate to the site editor. + await admin.visitSiteEditor(); + } ); + + test.skip( 'Open the command center and navigate to the page create page', async ( { page, } ) => { - // Navigate to a template. - await admin.visitSiteEditor(); await page.keyboard.press( 'Meta+k' ); const newPageButton = page.locator( 'role=option[name="Create a new page"i]' @@ -38,12 +40,9 @@ test.describe( 'Site editor command center', () => { await expect( postTitleInput ).toHaveText( 'E2E Test Post' ); } ); - test( 'Open the command center and navigate to a template', async ( { - admin, + test.skip( 'Open the command center and navigate to a template', async ( { page, } ) => { - // Navigate to a template. - await admin.visitSiteEditor(); await page.keyboard.press( 'Meta+k' ); await page.keyboard.type( 'index' );