diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0944d6e47680..5c5f6c90d837 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -402,7 +402,7 @@ importers: version: 3.12.0(@react-spring/web@9.7.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@wordpress/i18n': specifier: ^6.0.0 - version: 6.16.0 + version: 6.15.0 '@wordpress/icons': specifier: ^12.0.0 version: 12.0.0(react@18.3.1) @@ -556,7 +556,7 @@ importers: version: link:../number-formatters '@babel/runtime': specifier: ^7 - version: 7.29.2 + version: 7.28.6 '@wordpress/admin-ui': specifier: 1.10.0 version: 1.10.0(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@17.5.0) @@ -604,7 +604,7 @@ importers: version: 4.2.0(react@18.3.1) react-slider: specifier: 2.0.5 - version: 2.0.5(@babel/runtime@7.29.2)(react@18.3.1) + version: 2.0.5(@babel/runtime@7.28.6)(react@18.3.1) social-logos: specifier: workspace:* version: link:../social-logos @@ -1153,7 +1153,7 @@ importers: dependencies: '@wordpress/date': specifier: ^5.19.0 - version: 5.43.0 + version: 5.42.0 debug: specifier: ^4.4.0 version: 4.4.3 @@ -2766,7 +2766,7 @@ importers: version: 4.43.0 '@wordpress/dom-ready': specifier: ^4.8.1 - version: 4.43.0 + version: 4.42.0 '@wordpress/edit-post': specifier: 8.43.0 version: 8.43.0(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@17.5.0) @@ -2805,7 +2805,7 @@ importers: version: 7.43.0(react@18.3.1) '@wordpress/router': specifier: ^1.8.11 - version: 1.43.0(react@18.3.1) + version: 1.42.0(react@18.3.1) '@wordpress/sync': specifier: 1.43.0 version: 1.43.0 @@ -4265,7 +4265,7 @@ importers: version: link:../../js-packages/webpack-config '@wordpress/a11y': specifier: ^4.40.0 - version: 4.43.0 + version: 4.42.0 '@wordpress/admin-ui': specifier: ^1.9.0 version: 1.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@17.5.0) @@ -4277,10 +4277,10 @@ importers: version: 12.0.0(react@18.3.1) '@wordpress/lazy-editor': specifier: ^1.6.1 - version: 1.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@17.5.0) + version: 1.8.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@17.5.0) '@wordpress/notices': specifier: ^5.40.0 - version: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 5.42.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@wordpress/private-apis': specifier: ^1.43.0 version: 1.43.0 @@ -5144,6 +5144,9 @@ importers: deepmerge: specifier: 4.3.1 version: 4.3.1 + diff: + specifier: ^8.0.4 + version: 8.0.4 email-validator: specifier: 2.0.4 version: 2.0.4 @@ -5831,7 +5834,7 @@ importers: version: 7.29.0 '@babel/preset-env': specifier: ^7.5.5 - version: 7.29.2(@babel/core@7.29.0) + version: 7.29.0(@babel/core@7.29.0) jest: specifier: 30.3.0 version: 30.3.0 @@ -6427,8 +6430,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-define-polyfill-provider@0.6.8': - resolution: {integrity: sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==} + '@babel/helper-define-polyfill-provider@0.6.7': + resolution: {integrity: sha512-6Fqi8MtQ/PweQ9xvux65emkLQ83uB+qAVtfHkC9UodyHMIZdxNI01HjLCLUtybElp2KY2XNE0nOgyP1E1vXw9w==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 @@ -6980,6 +6983,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0 + '@babel/preset-env@7.29.0': + resolution: {integrity: sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/preset-env@7.29.2': resolution: {integrity: sha512-DYD23veRYGvBFhcTY1iUvJnDNpuqNd/BzBwCvzOTKUnJjKg5kpUBh3/u9585Agdkgj+QuygG7jLfOPWMa2KVNw==} engines: {node: '>=6.9.0'} @@ -7003,6 +7012,10 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/runtime@7.28.6': + resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} + engines: {node: '>=6.9.0'} + '@babel/runtime@7.29.2': resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} engines: {node: '>=6.9.0'} @@ -10284,6 +10297,10 @@ packages: webpack-dev-server: optional: true + '@wordpress/a11y@4.42.0': + resolution: {integrity: sha512-7NdHXJt3XDBXujkhoZkuBzZJIY+LrG0r2aPSJVujoHwioBR3V+HgdcGa1xydAnKtdiNnYa8fEdlWqk49EEQ0MA==} + engines: {node: '>=18.12.0', npm: '>=8.19.2'} + '@wordpress/a11y@4.43.0': resolution: {integrity: sha512-AWNmSi+Cjx5m03JhG/XjDqgRufqCFcIpYddWw7/0vR6rMk/DK5O+Jx6yJcJOwgmz2KFSgjMnjFfqbh3EtX8rRg==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -10310,6 +10327,10 @@ packages: resolution: {integrity: sha512-1Tetm4VIEKDIwrCsvDY0KjjrHvkEaSa2Qvld5gguY2ofcIszL3mR0tGezFaaaB14FHd7Zl0MpfpQY3KeQ+BatQ==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} + '@wordpress/asset-loader@1.8.0': + resolution: {integrity: sha512-9qMiFVfwlS/yUOlgHgIry9HWXkDSwKI74HImSiCan4AC3LwsKTXn3slJ4v6FYlr8Q10croCnnmUXWcLgny929w==} + engines: {node: '>=18.12.0', npm: '>=8.19.2'} + '@wordpress/asset-loader@1.9.0': resolution: {integrity: sha512-tJX86ZA32Xiflrz3uPraX2qd6bBlV+m5Y95pWEzzfgEMsI8grUE/btiB8d6uwTenSJmKv6HgguUfNyRZD2Ckpw==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -10441,6 +10462,10 @@ packages: peerDependencies: react: ^18.0.0 + '@wordpress/date@5.42.0': + resolution: {integrity: sha512-iTzp5vkJm3lX2V2vRdu1PK/Z9LNREWGSic5yYNKTxmOXd7FUu93hCb4hfxYReJHRqSq8lQORbvxxF/b3JeM3yA==} + engines: {node: '>=18.12.0', npm: '>=8.19.2'} + '@wordpress/date@5.43.0': resolution: {integrity: sha512-8DiFlE7YzP7F/P59Hr6h5fWJxJlvt6eZgU1C7huM9XhANh8Y3dZfepsySL6K7h1yE66SQDSq07cEefFQgJW31g==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -10455,6 +10480,10 @@ packages: resolution: {integrity: sha512-Pxn+nUmCVAaKBiZun2tEVweVdevMvWFWyCRqIqsAKdWCLsD8Uk6o27EwXc1u8BlO65VmK8D2zF9uWKGKfdZbCw==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} + '@wordpress/dom-ready@4.42.0': + resolution: {integrity: sha512-ujShJCmo9Y6yqX9tD7100M7MwiEPiDsaN2OQzX1vA+2lp099muq73376zv9ISBkK0C8uUbMZzw2B+JTmBiyGtw==} + engines: {node: '>=18.12.0', npm: '>=8.19.2'} + '@wordpress/dom-ready@4.43.0': resolution: {integrity: sha512-Y3oNeAdVzw9tACCgL7HuimhpSlhdU5RfRGtLp2kgewWHl5I6tzfi7XypG7FdBmS+dI+j2SaYYdTNPen/kFsZlA==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -10545,6 +10574,10 @@ packages: react: ^18.0.0 react-dom: ^18.0.0 + '@wordpress/hooks@4.42.0': + resolution: {integrity: sha512-isdznPKo+LEAGrP/o6SnWjxKYKn4KNzb5dmpnYPTbLh13gE/p8KctpLyzMsgR2GBXF8soAL+hpXMxxKoTQSabA==} + engines: {node: '>=18.12.0', npm: '>=8.19.2'} + '@wordpress/hooks@4.43.0': resolution: {integrity: sha512-BY7GPjEwhOlgkavVak40E3RtA8Z9ehydqTZckRoesMRjXYfxKSzr1C1FT4wAPS5uXM1pNlWivfofMaJjVNQu5w==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -10553,6 +10586,11 @@ packages: resolution: {integrity: sha512-z7C782VfH3E5dWYO4VOtN8EEhzfID2kiJmGTINiVPD8kywxp5BsBU2KJSSPvkUjqOCMNJ2XhkYPgADKi9O1U7A==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} + '@wordpress/i18n@6.15.0': + resolution: {integrity: sha512-ZkGJbZIRhtcQmynb1jb+rRXrw9+SSV0y6KE2R4eex6MzFN0PoNKJcjlOtMLiyMsXd5KFYzfzVj14EGsx5XgG/w==} + engines: {node: '>=18.12.0', npm: '>=8.19.2'} + hasBin: true + '@wordpress/i18n@6.16.0': resolution: {integrity: sha512-D8yiDLzOrs9Aa4Cc1nm7m2OMilZeG9Qd7zHauMIDQujwHOe9xrOyH9ppDDko6AAWb+GeUYsf5zf2Efu5saLq0w==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -10621,6 +10659,10 @@ packages: peerDependencies: react: ^18.0.0 + '@wordpress/keycodes@4.42.0': + resolution: {integrity: sha512-QV82RsOYL3qWXxVTU7T6zk5LU1ad4YP6DDH4czQR8mJECoDsblf2gSjYcGDHkPUK30SXJ7/x/ZOnhGkyJSVaKw==} + engines: {node: '>=18.12.0', npm: '>=8.19.2'} + '@wordpress/keycodes@4.43.0': resolution: {integrity: sha512-1F0BS9qGwYFGgMgzXFSSoBdVGqpU1mCA9UVQ1wJxi/qTMIH+sQcvD8KGoSMJLvTDjbiFc4axLilYOL7DJ0EG/A==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -10629,6 +10671,10 @@ packages: resolution: {integrity: sha512-Qnw7m+mY4GXASWMJ+I/SKvTDKu5YkF8QekA7kwbiufw5dbFaYJd0KSZuwwq8tirVHcJPPp8VOOIpy+JrDDwQgA==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} + '@wordpress/lazy-editor@1.8.0': + resolution: {integrity: sha512-FKkrqPOuyakVM+u7sOkXETzCDMqv5+e4WglR4v+YE5ZIW7ew6ApBQdFHayWoSb1Hxemd2PgRkUep4apfeVPxKQ==} + engines: {node: '>=18.12.0', npm: '>=8.19.2'} + '@wordpress/lazy-editor@1.9.0': resolution: {integrity: sha512-snmVcNPK5hUEJIpSEfYpsmICqufdKzQSnITjTRiFODiAp7TowRVoRcoiH4zGgKM97hUWQ8ZHh7AwPMGdmAhMwg==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -10651,6 +10697,12 @@ packages: peerDependencies: react: ^18.0.0 + '@wordpress/notices@5.42.0': + resolution: {integrity: sha512-wWdaojBUvI8TzL7uJJG7nXkzxl57sh9AwV9fpfOqwE4kQkXkDZ1kwJozSN2Rm/3eLMBqQxLjKf4OlOttgmvROg==} + engines: {node: '>=18.12.0', npm: '>=8.19.2'} + peerDependencies: + react: ^18.0.0 + '@wordpress/notices@5.43.0': resolution: {integrity: sha512-EvYerIJQ9wc5tT/ibfKhqP3Ja75JJpcSUc11zaQECdTpG3leXGIsUBgl9GDFbd70GpDj1ZCU7NmVcTYl+y0b7w==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -10733,6 +10785,12 @@ packages: peerDependencies: react: ^18.0.0 + '@wordpress/router@1.42.0': + resolution: {integrity: sha512-M0xexMEsp8X1AovsPiIIgIPXX+i+rnexHXJ+b3I0TsSLlfg5i6QhywaETOBO81QcHFoxkfwSaLJoXJ6VIQMSfA==} + engines: {node: '>=18.12.0', npm: '>=8.19.2'} + peerDependencies: + react: ^18.0.0 + '@wordpress/router@1.43.0': resolution: {integrity: sha512-es85nk0P7hthMnLuLYGgMCiXab20afFj6SIUF85x7rd+fbH4cyB5/BsiRajcgV+UtV6KzgvTj89Q/ALhJQLYJg==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -10820,6 +10878,12 @@ packages: resolution: {integrity: sha512-FFq/KZUlhAszI9y232BpQ83+58CtRV5M0SFuv7pQq+DxNDuWNHp8gjdX9WOnHkO/MrKAyVTI0jX9YoWF2RNEZw==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} + '@wordpress/viewport@6.42.0': + resolution: {integrity: sha512-HvKOUx5ZWLV85TNG4VupvssqcTB5uj1oCLxnpXO+kxUJIYXUADozAlqpNbZlPkp1DqT/ApVnvDtX2ysTpx9GYQ==} + engines: {node: '>=18.12.0', npm: '>=8.19.2'} + peerDependencies: + react: ^18.0.0 + '@wordpress/viewport@6.43.0': resolution: {integrity: sha512-p8z7hAsRxjBhsqK9/Ul7DedoiDkkA62VaGEH9KpZR9HD2MWzpIBTeF6CeXfmI52CAzbb87o6tRy2pyIiG16Z8w==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -11173,8 +11237,8 @@ packages: resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} engines: {node: '>=10', npm: '>=6'} - babel-plugin-polyfill-corejs2@0.4.17: - resolution: {integrity: sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==} + babel-plugin-polyfill-corejs2@0.4.16: + resolution: {integrity: sha512-xaVwwSfebXf0ooE11BJovZYKhFjIvQo7TsyVpETuIeH2JHv0k/T6Y5j22pPTvqYqmpkxdlPAJlyJ0tfOJAoMxw==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 @@ -11188,8 +11252,8 @@ packages: peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - babel-plugin-polyfill-regenerator@0.6.8: - resolution: {integrity: sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==} + babel-plugin-polyfill-regenerator@0.6.7: + resolution: {integrity: sha512-OTYbUlSwXhNgr4g6efMZgsO8//jA61P7ZbRX3iTT53VON8l+WQS8IAUEVo4a4cWknrg2W8Cj4gQhRYNCJ8GkAA==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 @@ -11274,8 +11338,8 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - basic-ftp@5.2.1: - resolution: {integrity: sha512-0yaL8JdxTknKDILitVpfYfV2Ob6yb3udX/hK97M7I3jOeznBNxQPtVvTUtnhUkyHlxFWyr5Lvknmgzoc7jf+1Q==} + basic-ftp@5.2.0: + resolution: {integrity: sha512-VoMINM2rqJwJgfdHq6RiUudKt2BV+FY5ZFezP/ypmwayk68+NzzAQy4XXLlqsGD4MCzq3DrmNFD/uUmBJuGoXw==} engines: {node: '>=10.0.0'} batch@0.6.1: @@ -11743,8 +11807,8 @@ packages: peerDependencies: webpack: ^5.1.0 - core-js-compat@3.49.0: - resolution: {integrity: sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==} + core-js-compat@3.48.0: + resolution: {integrity: sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==} core-js-pure@3.48.0: resolution: {integrity: sha512-1slJgk89tWC51HQ1AEqG+s2VuwpTRr8ocu4n20QUcH1v9lAN0RXen0Q0AABa/DK1I7RrNWLucplOHMx8hfTGTw==} @@ -12173,8 +12237,8 @@ packages: resolution: {integrity: sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==} engines: {node: '>=0.3.1'} - diff@8.0.3: - resolution: {integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==} + diff@8.0.4: + resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} engines: {node: '>=0.3.1'} dns-packet@5.6.1: @@ -15905,8 +15969,8 @@ packages: regjsgen@0.8.0: resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==} - regjsparser@0.13.1: - resolution: {integrity: sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==} + regjsparser@0.13.0: + resolution: {integrity: sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==} hasBin: true rejoinder@2.1.0: @@ -18338,7 +18402,7 @@ snapshots: regexpu-core: 6.4.0 semver: 6.3.1 - '@babel/helper-define-polyfill-provider@0.6.8(@babel/core@7.29.0)': + '@babel/helper-define-polyfill-provider@0.6.7(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 '@babel/helper-compilation-targets': 7.28.6 @@ -18879,9 +18943,9 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-module-imports': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 - babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.0) + babel-plugin-polyfill-corejs2: 0.4.16(@babel/core@7.29.0) babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.29.0) - babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.0) + babel-plugin-polyfill-regenerator: 0.6.7(@babel/core@7.29.0) semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -18948,6 +19012,82 @@ snapshots: '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.28.6 + '@babel/preset-env@7.29.0(@babel/core@7.29.0)': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/core': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.28.5(@babel/core@7.29.0) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0) + '@babel/plugin-syntax-import-assertions': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.29.0) + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-async-generator-functions': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-computed-properties': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) + '@babel/plugin-transform-dotall-regex': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-explicit-resource-management': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-exponentiation-operator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-json-strings': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-modules-systemjs': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-numeric-separator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) + '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-regenerator': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-regexp-modifiers': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-spread': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-property-regex': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-sets-regex': 7.28.6(@babel/core@7.29.0) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.29.0) + babel-plugin-polyfill-corejs2: 0.4.16(@babel/core@7.29.0) + babel-plugin-polyfill-corejs3: 0.14.0(@babel/core@7.29.0) + babel-plugin-polyfill-regenerator: 0.6.7(@babel/core@7.29.0) + core-js-compat: 3.48.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + '@babel/preset-env@7.29.2(@babel/core@7.29.0)': dependencies: '@babel/compat-data': 7.29.0 @@ -19016,10 +19156,10 @@ snapshots: '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.0) '@babel/plugin-transform-unicode-sets-regex': 7.28.6(@babel/core@7.29.0) '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.29.0) - babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.0) + babel-plugin-polyfill-corejs2: 0.4.16(@babel/core@7.29.0) babel-plugin-polyfill-corejs3: 0.14.0(@babel/core@7.29.0) - babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.0) - core-js-compat: 3.49.0 + babel-plugin-polyfill-regenerator: 0.6.7(@babel/core@7.29.0) + core-js-compat: 3.48.0 semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -19054,6 +19194,8 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/runtime@7.28.6': {} + '@babel/runtime@7.29.2': {} '@babel/template@7.28.6': @@ -22069,7 +22211,7 @@ snapshots: '@testing-library/dom@10.4.1': dependencies: '@babel/code-frame': 7.29.0 - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.28.6 '@types/aria-query': 5.0.4 aria-query: 5.3.0 dom-accessibility-api: 0.5.16 @@ -22450,7 +22592,7 @@ snapshots: '@wordpress/data': 10.43.0(react@18.3.1) '@wordpress/element': 6.43.0 '@wordpress/global-styles-engine': 1.10.0(react@18.3.1) - '@wordpress/keycodes': 4.43.0 + '@wordpress/keycodes': 4.42.0 react-autosize-textarea: 7.1.0(patch_hash=5c09e1dee59caaaba3871f9d722f93e56b41169db486b059597e8f8c788aa464)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) transitivePeerDependencies: - '@emotion/is-prop-valid' @@ -23097,6 +23239,11 @@ snapshots: webpack: 5.105.2(webpack-cli@6.0.1) webpack-cli: 6.0.1(webpack@5.105.2) + '@wordpress/a11y@4.42.0': + dependencies: + '@wordpress/dom-ready': 4.43.0 + '@wordpress/i18n': 6.16.0 + '@wordpress/a11y@4.43.0': dependencies: '@wordpress/dom-ready': 4.43.0 @@ -23188,6 +23335,8 @@ snapshots: '@wordpress/i18n': 6.16.0 '@wordpress/url': 4.43.0 + '@wordpress/asset-loader@1.8.0': {} + '@wordpress/asset-loader@1.9.0': {} '@wordpress/autop@4.43.0': {} @@ -23974,7 +24123,7 @@ snapshots: '@wordpress/element': 6.43.0 '@wordpress/i18n': 6.16.0 '@wordpress/icons': 12.0.0(react@18.3.1) - '@wordpress/keycodes': 4.43.0 + '@wordpress/keycodes': 4.42.0 '@wordpress/primitives': 4.43.0(react@18.3.1) '@wordpress/private-apis': 1.43.0 '@wordpress/ui': 0.9.0(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@17.5.0) @@ -23992,7 +24141,7 @@ snapshots: '@floating-ui/react-dom': 2.0.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@use-gesture/react': 10.3.1(react@18.3.1) '@wordpress/date': 5.43.0 - '@wordpress/hooks': 4.43.0 + '@wordpress/hooks': 4.42.0 change-case: 4.1.2 colord: 2.9.3 date-fns: 4.1.0 @@ -24024,7 +24173,7 @@ snapshots: '@wordpress/element': 6.43.0 '@wordpress/i18n': 6.16.0 '@wordpress/icons': 12.0.0(react@18.3.1) - '@wordpress/keycodes': 4.43.0 + '@wordpress/keycodes': 4.42.0 '@wordpress/primitives': 4.43.0(react@18.3.1) '@wordpress/private-apis': 1.43.0 '@wordpress/ui': 0.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@17.5.0) @@ -24042,7 +24191,7 @@ snapshots: '@floating-ui/react-dom': 2.0.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@use-gesture/react': 10.3.1(react@18.3.1) '@wordpress/date': 5.43.0 - '@wordpress/hooks': 4.43.0 + '@wordpress/hooks': 4.42.0 change-case: 4.1.2 colord: 2.9.3 date-fns: 4.1.0 @@ -24163,6 +24312,12 @@ snapshots: - stylelint - supports-color + '@wordpress/date@5.42.0': + dependencies: + '@wordpress/deprecated': 4.43.0 + moment: 2.30.1 + moment-timezone: 0.5.48 + '@wordpress/date@5.43.0': dependencies: '@wordpress/deprecated': 4.43.0 @@ -24178,6 +24333,8 @@ snapshots: dependencies: '@wordpress/hooks': 4.43.0 + '@wordpress/dom-ready@4.42.0': {} + '@wordpress/dom-ready@4.43.0': {} '@wordpress/dom@4.43.0': @@ -24823,10 +24980,20 @@ snapshots: - stylelint - supports-color + '@wordpress/hooks@4.42.0': {} + '@wordpress/hooks@4.43.0': {} '@wordpress/html-entities@4.43.0': {} + '@wordpress/i18n@6.15.0': + dependencies: + '@tannin/sprintf': 1.3.3 + '@wordpress/hooks': 4.42.0 + gettext-parser: 1.4.0 + memize: 2.1.1 + tannin: 1.2.0 + '@wordpress/i18n@6.16.0': dependencies: '@tannin/sprintf': 1.3.3 @@ -24883,7 +25050,7 @@ snapshots: '@wordpress/interface@9.27.0(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@17.5.0)': dependencies: - '@wordpress/a11y': 4.43.0 + '@wordpress/a11y': 4.42.0 '@wordpress/admin-ui': 1.10.0(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@17.5.0) '@wordpress/base-styles': 6.19.0 '@wordpress/components': 32.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -24895,7 +25062,7 @@ snapshots: '@wordpress/icons': 12.0.0(react@18.3.1) '@wordpress/plugins': 7.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@wordpress/preferences': 4.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@wordpress/viewport': 6.43.0(react@18.3.1) + '@wordpress/viewport': 6.42.0(react@18.3.1) clsx: 2.1.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -24968,6 +25135,10 @@ snapshots: '@wordpress/keycodes': 4.43.0 react: 18.3.1 + '@wordpress/keycodes@4.42.0': + dependencies: + '@wordpress/i18n': 6.16.0 + '@wordpress/keycodes@4.43.0': dependencies: '@wordpress/i18n': 6.16.0 @@ -24976,6 +25147,29 @@ snapshots: dependencies: temml: 0.10.34 + '@wordpress/lazy-editor@1.8.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@17.5.0)': + dependencies: + '@wordpress/asset-loader': 1.8.0 + '@wordpress/base-styles': 6.19.0 + '@wordpress/block-editor': 15.16.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@17.5.0) + '@wordpress/blocks': 15.16.0(react@18.3.1) + '@wordpress/components': 32.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@wordpress/core-data': 7.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@17.5.0) + '@wordpress/data': 10.43.0(react@18.3.1) + '@wordpress/editor': 14.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@17.5.0) + '@wordpress/element': 6.43.0 + '@wordpress/global-styles-engine': 1.10.0(react@18.3.1) + '@wordpress/i18n': 6.16.0 + '@wordpress/private-apis': 1.43.0 + transitivePeerDependencies: + - '@emotion/is-prop-valid' + - '@types/react' + - '@types/react-dom' + - react + - react-dom + - stylelint + - supports-color + '@wordpress/lazy-editor@1.9.0(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@17.5.0)': dependencies: '@wordpress/asset-loader': 1.9.0 @@ -25202,6 +25396,18 @@ snapshots: - stylelint - supports-color + '@wordpress/notices@5.42.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@wordpress/a11y': 4.42.0 + '@wordpress/components': 32.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@wordpress/data': 10.43.0(react@18.3.1) + clsx: 2.1.1 + react: 18.3.1 + transitivePeerDependencies: + - '@emotion/is-prop-valid' + - react-dom + - supports-color + '@wordpress/notices@5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@wordpress/a11y': 4.43.0 @@ -25459,6 +25665,16 @@ snapshots: transitivePeerDependencies: - react-dom + '@wordpress/router@1.42.0(react@18.3.1)': + dependencies: + '@wordpress/compose': 7.43.0(react@18.3.1) + '@wordpress/element': 6.43.0 + '@wordpress/private-apis': 1.43.0 + '@wordpress/url': 4.43.0 + history: 5.3.0 + react: 18.3.1 + route-recognizer: 0.3.4 + '@wordpress/router@1.43.0(react@18.3.1)': dependencies: '@wordpress/compose': 7.43.0(react@18.3.1) @@ -25513,7 +25729,7 @@ snapshots: '@wordpress/hooks': 4.43.0 '@wordpress/private-apis': 1.43.0 '@wordpress/undo-manager': 1.43.0 - diff: 8.0.3 + diff: 8.0.4 fast-deep-equal: 3.1.3 lib0: 0.2.99 y-protocols: 1.0.7(yjs@13.6.29) @@ -25597,12 +25813,12 @@ snapshots: '@wordpress/ui@0.9.0(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@17.5.0)': dependencies: '@base-ui/react': 1.3.0(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@wordpress/a11y': 4.43.0 + '@wordpress/a11y': 4.42.0 '@wordpress/compose': 7.43.0(react@18.3.1) '@wordpress/element': 6.43.0 '@wordpress/i18n': 6.16.0 '@wordpress/icons': 12.0.0(react@18.3.1) - '@wordpress/keycodes': 4.43.0 + '@wordpress/keycodes': 4.42.0 '@wordpress/primitives': 4.43.0(react@18.3.1) '@wordpress/private-apis': 1.43.0 '@wordpress/theme': 0.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@17.5.0) @@ -25616,12 +25832,12 @@ snapshots: '@wordpress/ui@0.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@17.5.0)': dependencies: '@base-ui/react': 1.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@wordpress/a11y': 4.43.0 + '@wordpress/a11y': 4.42.0 '@wordpress/compose': 7.43.0(react@18.3.1) '@wordpress/element': 6.43.0 '@wordpress/i18n': 6.16.0 '@wordpress/icons': 12.0.0(react@18.3.1) - '@wordpress/keycodes': 4.43.0 + '@wordpress/keycodes': 4.42.0 '@wordpress/primitives': 4.43.0(react@18.3.1) '@wordpress/private-apis': 1.43.0 '@wordpress/theme': 0.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@17.5.0) @@ -25658,6 +25874,13 @@ snapshots: dependencies: remove-accents: 0.5.0 + '@wordpress/viewport@6.42.0(react@18.3.1)': + dependencies: + '@wordpress/compose': 7.43.0(react@18.3.1) + '@wordpress/data': 10.43.0(react@18.3.1) + '@wordpress/element': 6.43.0 + react: 18.3.1 + '@wordpress/viewport@6.43.0(react@18.3.1)': dependencies: '@wordpress/compose': 7.43.0(react@18.3.1) @@ -26136,11 +26359,11 @@ snapshots: cosmiconfig: 7.1.0 resolve: 1.22.11 - babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.29.0): + babel-plugin-polyfill-corejs2@0.4.16(@babel/core@7.29.0): dependencies: '@babel/compat-data': 7.29.0 '@babel/core': 7.29.0 - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) + '@babel/helper-define-polyfill-provider': 0.6.7(@babel/core@7.29.0) semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -26148,23 +26371,23 @@ snapshots: babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.29.0): dependencies: '@babel/core': 7.29.0 - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) - core-js-compat: 3.49.0 + '@babel/helper-define-polyfill-provider': 0.6.7(@babel/core@7.29.0) + core-js-compat: 3.48.0 transitivePeerDependencies: - supports-color babel-plugin-polyfill-corejs3@0.14.0(@babel/core@7.29.0): dependencies: '@babel/core': 7.29.0 - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) - core-js-compat: 3.49.0 + '@babel/helper-define-polyfill-provider': 0.6.7(@babel/core@7.29.0) + core-js-compat: 3.48.0 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-regenerator@0.6.8(@babel/core@7.29.0): + babel-plugin-polyfill-regenerator@0.6.7(@babel/core@7.29.0): dependencies: '@babel/core': 7.29.0 - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) + '@babel/helper-define-polyfill-provider': 0.6.7(@babel/core@7.29.0) transitivePeerDependencies: - supports-color @@ -26254,7 +26477,7 @@ snapshots: baseline-browser-mapping@2.10.13: {} - basic-ftp@5.2.1: {} + basic-ftp@5.2.0: {} batch@0.6.1: {} @@ -26790,7 +27013,7 @@ snapshots: tinyglobby: 0.2.15 webpack: 5.105.2(webpack-cli@6.0.1) - core-js-compat@3.49.0: + core-js-compat@3.48.0: dependencies: browserslist: 4.28.2 @@ -27208,7 +27431,7 @@ snapshots: diff@4.0.4: {} - diff@8.0.3: {} + diff@8.0.4: {} dns-packet@5.6.1: dependencies: @@ -28344,7 +28567,7 @@ snapshots: get-uri@6.0.5: dependencies: - basic-ftp: 5.2.1 + basic-ftp: 5.2.0 data-uri-to-buffer: 6.0.2 debug: 4.4.3 transitivePeerDependencies: @@ -31530,9 +31753,9 @@ snapshots: optionalDependencies: react-dom: 18.3.1(react@18.3.1) - react-slider@2.0.5(@babel/runtime@7.29.2)(react@18.3.1): + react-slider@2.0.5(@babel/runtime@7.28.6)(react@18.3.1): dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.28.6 prop-types: 15.8.1 react: 18.3.1 @@ -31683,13 +31906,13 @@ snapshots: regenerate: 1.4.2 regenerate-unicode-properties: 10.2.2 regjsgen: 0.8.0 - regjsparser: 0.13.1 + regjsparser: 0.13.0 unicode-match-property-ecmascript: 2.0.0 unicode-match-property-value-ecmascript: 2.2.1 regjsgen@0.8.0: {} - regjsparser@0.13.1: + regjsparser@0.13.0: dependencies: jsesc: 3.1.0 diff --git a/projects/plugins/jetpack/_inc/content-guidelines-ai.js b/projects/plugins/jetpack/_inc/content-guidelines-ai.js new file mode 100644 index 000000000000..59859bb0ae5e --- /dev/null +++ b/projects/plugins/jetpack/_inc/content-guidelines-ai.js @@ -0,0 +1,15 @@ +/** + * Content Guidelines AI — Entry point. + * + * Injects Jetpack AI-powered generate/improve buttons into the + * Content Guidelines admin page (Gutenberg experimental feature). + */ +import './content-guidelines-ai/store'; +import './content-guidelines-ai/style.scss'; +import { startInjection } from './content-guidelines-ai/lib/inject'; + +if ( document.readyState === 'loading' ) { + document.addEventListener( 'DOMContentLoaded', startInjection ); +} else { + startInjection(); +} diff --git a/projects/plugins/jetpack/_inc/content-guidelines-ai.php b/projects/plugins/jetpack/_inc/content-guidelines-ai.php new file mode 100644 index 000000000000..16a7f40610f6 --- /dev/null +++ b/projects/plugins/jetpack/_inc/content-guidelines-ai.php @@ -0,0 +1,92 @@ + array( 'wp-api-fetch', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-notices' ), + 'version' => JETPACK__VERSION, + ); + + wp_enqueue_script( + 'jetpack-content-guidelines-ai', + plugins_url( '_inc/build/content-guidelines-ai.min.js', JETPACK__PLUGIN_FILE ), + $asset['dependencies'], + $asset['version'], + true + ); + + wp_enqueue_style( + 'jetpack-content-guidelines-ai', + plugins_url( '_inc/build/content-guidelines-ai.css', JETPACK__PLUGIN_FILE ), + array( 'wp-components' ), + $asset['version'] + ); + + // Determine AI availability per site type and pass config to JS. + if ( ! class_exists( 'Jetpack_AI_Helper' ) ) { + require_once JETPACK__PLUGIN_DIR . '_inc/lib/class-jetpack-ai-helper.php'; + } + + $host = new Host(); + $is_wpcom = $host->is_wpcom_simple() || $host->is_woa_site(); + $is_connected = $host->is_wpcom_simple() + || ( ( new Connection_Manager( 'jetpack' ) )->has_connected_owner() && ! ( new Status() )->is_offline_mode() ); + $ai_enabled = \Jetpack_AI_Helper::is_enabled(); + + $config = array( + 'available' => $ai_enabled && $is_connected, + 'isConnected' => $is_connected, + ); + + if ( ! $config['available'] ) { + if ( ! $is_connected ) { + // Self-hosted site not connected to WordPress.com. + $config['upgradeUrl'] = admin_url( 'admin.php?page=jetpack#/dashboard' ); + } elseif ( $is_wpcom ) { + // wpcom simple or atomic — needs AI plan. + $config['upgradeUrl'] = Redirect::get_url( 'jetpack-ai-yearly-tier-upgrade-nudge' ); + } else { + // Self-hosted connected — needs Jetpack AI product. + $config['upgradeUrl'] = Redirect::get_url( 'jetpack-ai-upgrade-url-for-jetpack-sites' ); + } + } + + wp_add_inline_script( + 'jetpack-content-guidelines-ai', + sprintf( + 'window.jetpackContentGuidelinesAiConfig = %s;', + wp_json_encode( $config, JSON_HEX_TAG | JSON_HEX_AMP ) + ), + 'before' + ); +} + +add_action( 'admin_enqueue_scripts', 'jetpack_content_guidelines_ai_enqueue_scripts' ); diff --git a/projects/plugins/jetpack/_inc/content-guidelines-ai/components/block-suggestion-actions.jsx b/projects/plugins/jetpack/_inc/content-guidelines-ai/components/block-suggestion-actions.jsx new file mode 100644 index 000000000000..64ade3fc3a7c --- /dev/null +++ b/projects/plugins/jetpack/_inc/content-guidelines-ai/components/block-suggestion-actions.jsx @@ -0,0 +1,69 @@ +import { useDispatch, useSelect } from '@wordpress/data'; +import { useCallback, useEffect, useState } from '@wordpress/element'; +import { acceptBlockSuggestion } from '../lib/dom'; +import { AI_STORE_NAME } from '../store'; +import DiffView from './diff-view'; + +// Renders only the diff view. Accept/Dismiss and Improve buttons live in +// BlockSuggestionButtons, injected as a separate row above the modal action bar. +export default function BlockSuggestionActions( { blockName } ) { + const suggestion = useSelect( + select => select( AI_STORE_NAME ).getSuggestion( blockName ), + [ blockName ] + ); + const blockLoading = useSelect( + select => select( AI_STORE_NAME ).isSectionLoading( blockName ), + [ blockName ] + ); + const { clearSuggestion } = useDispatch( AI_STORE_NAME ); + + const [ original, setOriginal ] = useState( '' ); + const [ textareaHeight, setTextareaHeight ] = useState( null ); + + // Clear stale suggestion when the modal closes (component unmounts). + useEffect( () => { + return () => clearSuggestion( blockName ); + }, [ blockName, clearSuggestion ] ); + + // Toggle shimmer and suggestion classes on the modal. + useEffect( () => { + const modal = document.querySelector( '.block-guideline-modal' ); + if ( ! modal ) { + return; + } + + // Capture textarea content and height before hiding it. + if ( suggestion && ! modal.classList.contains( 'has-jetpack-suggestion' ) ) { + const textarea = modal.querySelector( '.components-textarea-control__input' ); + if ( textarea ) { + setOriginal( textarea.value || '' ); + if ( textarea.offsetHeight > 0 ) { + setTextareaHeight( textarea.offsetHeight ); + } + } + } + + modal.classList.toggle( 'has-jetpack-suggestion', !! suggestion ); + modal.classList.toggle( 'is-jetpack-loading', blockLoading && ! suggestion ); + return () => { + modal.classList.remove( 'has-jetpack-suggestion', 'is-jetpack-loading' ); + }; + }, [ suggestion, blockLoading ] ); + + const handleAccept = useCallback( () => { + acceptBlockSuggestion( blockName, suggestion, clearSuggestion ); + }, [ blockName, suggestion, clearSuggestion ] ); + + if ( ! suggestion ) { + return null; + } + + return ( + + ); +} diff --git a/projects/plugins/jetpack/_inc/content-guidelines-ai/components/block-suggestion-buttons.jsx b/projects/plugins/jetpack/_inc/content-guidelines-ai/components/block-suggestion-buttons.jsx new file mode 100644 index 000000000000..3c46a381f193 --- /dev/null +++ b/projects/plugins/jetpack/_inc/content-guidelines-ai/components/block-suggestion-buttons.jsx @@ -0,0 +1,96 @@ +import { Button } from '@wordpress/components'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { useCallback } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { store as noticesStore } from '@wordpress/notices'; +import { STORE_NAME } from '../constants'; +import { suggestGuidelines } from '../lib/api'; +import { showUnavailableNotice } from '../lib/availability'; +import { acceptBlockSuggestion } from '../lib/dom'; +import { AI_STORE_NAME } from '../store'; + +export default function BlockSuggestionButtons( { blockName } ) { + const { createErrorNotice } = useDispatch( noticesStore ); + const { startSectionLoading, stopSectionLoading, setSuggestion, clearSuggestion } = + useDispatch( AI_STORE_NAME ); + + const blockLoading = useSelect( + select => select( AI_STORE_NAME ).isSectionLoading( blockName ), + [ blockName ] + ); + + const suggestion = useSelect( + select => select( AI_STORE_NAME ).getSuggestion( blockName ), + [ blockName ] + ); + + const saved = useSelect( + select => select( STORE_NAME ).getBlockGuideline( blockName ), + [ blockName ] + ); + + const handleGenerate = useCallback( async () => { + if ( showUnavailableNotice() ) { + return; + } + + const modal = document.querySelector( '.block-guideline-modal' ); + const textarea = modal?.querySelector( '.components-textarea-control__input' ); + const currentText = textarea?.value || ''; + + startSectionLoading( blockName ); + try { + const existingContent = currentText ? { [ blockName ]: currentText } : {}; + const response = await suggestGuidelines( [ blockName ], existingContent ); + const text = response?.suggestions?.[ blockName ]; + if ( text ) { + setSuggestion( blockName, text ); + } + } catch { + createErrorNotice( __( 'Failed to generate guidelines. Please try again.', 'jetpack' ), { + type: 'snackbar', + } ); + } finally { + stopSectionLoading( blockName ); + } + }, [ blockName, startSectionLoading, stopSectionLoading, setSuggestion, createErrorNotice ] ); + + const handleAccept = useCallback( () => { + acceptBlockSuggestion( blockName, suggestion, clearSuggestion ); + }, [ blockName, suggestion, clearSuggestion ] ); + + const handleDismiss = useCallback( () => { + clearSuggestion( blockName ); + }, [ blockName, clearSuggestion ] ); + + if ( suggestion ) { + return ( +
+ + +
+ ); + } + + const generateLabel = __( 'Generate guidelines', 'jetpack' ); + const improveLabel = __( 'Improve guidelines', 'jetpack' ); + const label = saved ? improveLabel : generateLabel; + + return ( +
+ +
+ ); +} diff --git a/projects/plugins/jetpack/_inc/content-guidelines-ai/components/diff-view.jsx b/projects/plugins/jetpack/_inc/content-guidelines-ai/components/diff-view.jsx new file mode 100644 index 000000000000..282003e284ea --- /dev/null +++ b/projects/plugins/jetpack/_inc/content-guidelines-ai/components/diff-view.jsx @@ -0,0 +1,55 @@ +import { useCallback, useMemo } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { diffWords } from 'diff'; + +export default function DiffView( { original, suggestion, onAccept, height } ) { + const diff = useMemo( () => { + if ( ! suggestion ) { + return []; + } + return diffWords( original, suggestion ); + }, [ original, suggestion ] ); + + const handleKeyDown = useCallback( + e => { + if ( e.key === 'Enter' || e.key === ' ' ) { + e.preventDefault(); + onAccept(); + } + }, + [ onAccept ] + ); + + return ( +
+ + { __( 'Changes from current to suggested guidelines:', 'jetpack' ) } + + { diff.map( ( part, i ) => { + if ( part.added ) { + return ( + + { part.value } + + ); + } + if ( part.removed ) { + return ( + + { part.value } + + ); + } + return { part.value }; + } ) } +
+ ); +} diff --git a/projects/plugins/jetpack/_inc/content-guidelines-ai/components/empty-state-banner.jsx b/projects/plugins/jetpack/_inc/content-guidelines-ai/components/empty-state-banner.jsx new file mode 100644 index 000000000000..7d444e695d77 --- /dev/null +++ b/projects/plugins/jetpack/_inc/content-guidelines-ai/components/empty-state-banner.jsx @@ -0,0 +1,66 @@ +import { Button } from '@wordpress/components'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { useCallback } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { closeSmall } from '@wordpress/icons'; +import useGenerateAll from '../hooks/use-generate-all'; +import { AI_STORE_NAME } from '../store'; + +export default function EmptyStateBanner() { + const { generate } = useGenerateAll(); + const { dismissBanner } = useDispatch( AI_STORE_NAME ); + + const dismissed = useSelect( select => select( AI_STORE_NAME ).isBannerDismissed(), [] ); + + const handleDismiss = useCallback( () => { + dismissBanner(); + }, [ dismissBanner ] ); + + const handleGetStarted = useCallback( () => { + dismissBanner(); + generate(); + }, [ dismissBanner, generate ] ); + + if ( dismissed ) { + return null; + } + + return ( +
+
+

{ __( 'Generate your guidelines in seconds', 'jetpack' ) }

+

+ { __( + 'Use Jetpack to analyze your site and create draft guidelines based on your actual content.', + 'jetpack' + ) } +

+
+ + +
+
+ + ); +} diff --git a/projects/plugins/jetpack/_inc/content-guidelines-ai/components/suggest-all-button.jsx b/projects/plugins/jetpack/_inc/content-guidelines-ai/components/suggest-all-button.jsx new file mode 100644 index 000000000000..a26af4917919 --- /dev/null +++ b/projects/plugins/jetpack/_inc/content-guidelines-ai/components/suggest-all-button.jsx @@ -0,0 +1,42 @@ +import { JetpackLogo } from '@automattic/jetpack-components'; +import { Button } from '@wordpress/components'; +import { useSelect } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; +import { STORE_NAME, VALID_SECTIONS } from '../constants'; +import useGenerateAll from '../hooks/use-generate-all'; +import { AI_STORE_NAME } from '../store'; + +export default function SuggestAllButton() { + const { generate, loading } = useGenerateAll(); + + const bannerDismissed = useSelect( select => select( AI_STORE_NAME ).isBannerDismissed(), [] ); + + const allGuidelines = useSelect( select => { + const store = select( STORE_NAME ); + return Object.fromEntries( VALID_SECTIONS.map( slug => [ slug, store.getGuideline( slug ) ] ) ); + }, [] ); + + const allEmpty = VALID_SECTIONS.every( slug => ! allGuidelines[ slug ] ); + + const generateLabel = __( 'Generate guidelines', 'jetpack' ); + const improveLabel = __( 'Improve guidelines', 'jetpack' ); + const label = allEmpty ? generateLabel : improveLabel; + + // Hide when the banner is visible (not yet dismissed). + const hiddenProps = ! bannerDismissed ? { style: { display: 'none' }, 'aria-hidden': true } : {}; + + return ( + + ); +} diff --git a/projects/plugins/jetpack/_inc/content-guidelines-ai/components/suggestion-actions.jsx b/projects/plugins/jetpack/_inc/content-guidelines-ai/components/suggestion-actions.jsx new file mode 100644 index 000000000000..da4f416cc5b9 --- /dev/null +++ b/projects/plugins/jetpack/_inc/content-guidelines-ai/components/suggestion-actions.jsx @@ -0,0 +1,79 @@ +import { Button } from '@wordpress/components'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { useCallback, useEffect, useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { STORE_NAME } from '../constants'; +import { AI_STORE_NAME } from '../store'; +import DiffView from './diff-view'; + +export default function SuggestionActions( { slug } ) { + const suggestion = useSelect( select => select( AI_STORE_NAME ).getSuggestion( slug ), [ slug ] ); + const sectionLoading = useSelect( + select => select( AI_STORE_NAME ).isSectionLoading( slug ), + [ slug ] + ); + const { clearSuggestion } = useDispatch( AI_STORE_NAME ); + const { setGuideline } = useDispatch( STORE_NAME ); + + const [ original, setOriginal ] = useState( '' ); + const [ textareaHeight, setTextareaHeight ] = useState( null ); + + // Direct DOM class manipulation is necessary because this component is rendered in + // a separate React root injected into Gutenberg's page — we can't control classes + // on Gutenberg-owned elements through React props. + useEffect( () => { + const form = document.getElementById( `content-guidelines-${ slug }` ); + if ( ! form ) { + return; + } + + // Capture textarea draft and height before hiding it. + if ( suggestion && ! form.classList.contains( 'has-jetpack-suggestion' ) ) { + const textarea = form.querySelector( 'textarea' ); + if ( textarea ) { + setOriginal( textarea.value || '' ); + if ( textarea.offsetHeight > 0 ) { + setTextareaHeight( textarea.offsetHeight ); + } + } + } + + form.classList.toggle( 'has-jetpack-suggestion', !! suggestion ); + form.classList.toggle( 'is-jetpack-loading', sectionLoading && ! suggestion ); + return () => { + form.classList.remove( 'has-jetpack-suggestion', 'is-jetpack-loading' ); + }; + }, [ slug, suggestion, sectionLoading ] ); + + const handleAccept = useCallback( () => { + setGuideline( slug, suggestion ); + clearSuggestion( slug ); + }, [ slug, suggestion, setGuideline, clearSuggestion ] ); + + const handleDismiss = useCallback( () => { + clearSuggestion( slug ); + }, [ slug, clearSuggestion ] ); + + if ( ! suggestion ) { + return null; + } + + return ( +
+ +
+ + +
+
+ ); +} diff --git a/projects/plugins/jetpack/_inc/content-guidelines-ai/components/suggestion-badge.jsx b/projects/plugins/jetpack/_inc/content-guidelines-ai/components/suggestion-badge.jsx new file mode 100644 index 000000000000..77a332d20b01 --- /dev/null +++ b/projects/plugins/jetpack/_inc/content-guidelines-ai/components/suggestion-badge.jsx @@ -0,0 +1,30 @@ +import { Badge } from '@automattic/jetpack-components'; +import { Spinner } from '@wordpress/components'; +import { useSelect } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; +import { AI_STORE_NAME } from '../store'; + +export default function SuggestionBadge( { slug } ) { + const sectionLoading = useSelect( + select => select( AI_STORE_NAME ).isSectionLoading( slug ), + [ slug ] + ); + const hasSuggestion = useSelect( + select => select( AI_STORE_NAME ).hasSuggestion( slug ), + [ slug ] + ); + + if ( sectionLoading && ! hasSuggestion ) { + return ( + + + + ); + } + + if ( hasSuggestion ) { + return { __( 'Suggestion', 'jetpack' ) }; + } + + return null; +} diff --git a/projects/plugins/jetpack/_inc/content-guidelines-ai/components/upgrade-notice.jsx b/projects/plugins/jetpack/_inc/content-guidelines-ai/components/upgrade-notice.jsx new file mode 100644 index 000000000000..e9705980a7df --- /dev/null +++ b/projects/plugins/jetpack/_inc/content-guidelines-ai/components/upgrade-notice.jsx @@ -0,0 +1,39 @@ +import { Button, Notice } from '@wordpress/components'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { useCallback } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { config } from '../constants'; +import { AI_STORE_NAME } from '../store'; + +export default function UpgradeNotice() { + const visible = useSelect( select => select( AI_STORE_NAME ).isUpgradeNoticeVisible(), [] ); + const { hideUpgradeNotice } = useDispatch( AI_STORE_NAME ); + + const handleDismiss = useCallback( () => { + hideUpgradeNotice(); + }, [ hideUpgradeNotice ] ); + + if ( ! visible ) { + return null; + } + + return ( + +

+ { __( + "You've reached your limit for AI-generated suggestions. Upgrade to improve your guidelines, generate images, and unlock the full Jetpack AI Assistant.", + 'jetpack' + ) } +

+ { config.upgradeUrl && ( + + ) } +
+ ); +} diff --git a/projects/plugins/jetpack/_inc/content-guidelines-ai/constants.js b/projects/plugins/jetpack/_inc/content-guidelines-ai/constants.js new file mode 100644 index 000000000000..56b343fbb514 --- /dev/null +++ b/projects/plugins/jetpack/_inc/content-guidelines-ai/constants.js @@ -0,0 +1,4 @@ +export const STORE_NAME = 'core/content-guidelines'; +export const VALID_SECTIONS = [ 'site', 'copy', 'images', 'additional' ]; +export const API_PATH = '/wpcom/v2/jetpack-ai/suggest-guidelines'; +export const config = window.jetpackContentGuidelinesAiConfig || {}; diff --git a/projects/plugins/jetpack/_inc/content-guidelines-ai/hooks/use-generate-all.js b/projects/plugins/jetpack/_inc/content-guidelines-ai/hooks/use-generate-all.js new file mode 100644 index 000000000000..70a4589e0445 --- /dev/null +++ b/projects/plugins/jetpack/_inc/content-guidelines-ai/hooks/use-generate-all.js @@ -0,0 +1,57 @@ +import { useDispatch, useSelect } from '@wordpress/data'; +import { useCallback } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { store as noticesStore } from '@wordpress/notices'; +import { STORE_NAME, VALID_SECTIONS } from '../constants'; +import { suggestGuidelines } from '../lib/api'; +import { showUnavailableNotice } from '../lib/availability'; +import { AI_STORE_NAME } from '../store'; + +/** + * Hook that returns a callback to generate suggestions for all sections. + * + * @return {{ generate: Function, loading: boolean }} Generate callback and loading state. + */ +export default function useGenerateAll() { + const { createErrorNotice } = useDispatch( noticesStore ); + const { startLoading, stopLoading, setSuggestion } = useDispatch( AI_STORE_NAME ); + const loading = useSelect( select => select( AI_STORE_NAME ).isLoading(), [] ); + + const allGuidelines = useSelect( select => { + const store = select( STORE_NAME ); + return Object.fromEntries( VALID_SECTIONS.map( slug => [ slug, store.getGuideline( slug ) ] ) ); + }, [] ); + + const generate = useCallback( async () => { + if ( showUnavailableNotice() ) { + return; + } + + startLoading(); + try { + const existingContent = Object.fromEntries( + VALID_SECTIONS.filter( slug => allGuidelines[ slug ] ).map( slug => [ + slug, + allGuidelines[ slug ], + ] ) + ); + + const response = await suggestGuidelines( VALID_SECTIONS, existingContent ); + const suggestions = response?.suggestions || {}; + + for ( const slug of VALID_SECTIONS ) { + if ( suggestions[ slug ] ) { + setSuggestion( slug, suggestions[ slug ] ); + } + } + } catch { + createErrorNotice( __( 'Failed to generate guidelines. Please try again.', 'jetpack' ), { + type: 'snackbar', + } ); + } finally { + stopLoading(); + } + }, [ allGuidelines, startLoading, stopLoading, setSuggestion, createErrorNotice ] ); + + return { generate, loading }; +} diff --git a/projects/plugins/jetpack/_inc/content-guidelines-ai/lib/api.js b/projects/plugins/jetpack/_inc/content-guidelines-ai/lib/api.js new file mode 100644 index 000000000000..ab715ae5b54f --- /dev/null +++ b/projects/plugins/jetpack/_inc/content-guidelines-ai/lib/api.js @@ -0,0 +1,61 @@ +import apiFetch from '@wordpress/api-fetch'; +import { API_PATH, VALID_SECTIONS } from '../constants'; + +/** + * Generate or improve guidelines for the given sections/blocks. + * + * Translates between the internal format used by our components and the + * API's categories-based format: + * + * API request: { categories: { site: {}, copy: { guidelines: "..." }, blocks: { "core/paragraph": {} } } } + * API response: { site: { guidelines: "..." }, blocks: { "core/paragraph": { guidelines: "..." } } } + * + * @param {string[]} slugs - Section slugs or block names to generate. + * @param {Object.} [existingContent] - Existing content keyed by slug. + * @return {Promise} Response with `suggestions` keyed by slug. + */ +export async function suggestGuidelines( slugs, existingContent = {} ) { + // Build categories object for the API. + // Standard sections go as top-level keys, block names go under `blocks`. + const categories = {}; + const blockEntries = {}; + + for ( const slug of slugs ) { + const existing = existingContent[ slug ]; + const entry = existing ? { guidelines: existing } : {}; + + if ( VALID_SECTIONS.includes( slug ) ) { + categories[ slug ] = entry; + } else { + blockEntries[ slug ] = entry; + } + } + + if ( Object.keys( blockEntries ).length > 0 ) { + categories.blocks = blockEntries; + } + + const response = await apiFetch( { + path: API_PATH, + method: 'POST', + data: { categories }, + } ); + + // Normalize API response to { suggestions: { slug: text } }. + const suggestions = {}; + for ( const slug of slugs ) { + if ( VALID_SECTIONS.includes( slug ) ) { + const guidelines = response?.[ slug ]?.guidelines; + if ( guidelines ) { + suggestions[ slug ] = guidelines; + } + } else { + const guidelines = response?.blocks?.[ slug ]?.guidelines; + if ( guidelines ) { + suggestions[ slug ] = guidelines; + } + } + } + + return { suggestions }; +} diff --git a/projects/plugins/jetpack/_inc/content-guidelines-ai/lib/availability.js b/projects/plugins/jetpack/_inc/content-guidelines-ai/lib/availability.js new file mode 100644 index 000000000000..9eb1497a1f43 --- /dev/null +++ b/projects/plugins/jetpack/_inc/content-guidelines-ai/lib/availability.js @@ -0,0 +1,17 @@ +import { dispatch } from '@wordpress/data'; +import { config } from '../constants'; +import { AI_STORE_NAME } from '../store'; + +/** + * Check if Jetpack AI is unavailable. If so, show the upgrade notice. + * + * @return {boolean} True if AI is unavailable. + */ +export function showUnavailableNotice() { + if ( config.available ) { + return false; + } + + dispatch( AI_STORE_NAME ).showUpgradeNotice(); + return true; +} diff --git a/projects/plugins/jetpack/_inc/content-guidelines-ai/lib/dom.js b/projects/plugins/jetpack/_inc/content-guidelines-ai/lib/dom.js new file mode 100644 index 000000000000..f3459f0da03b --- /dev/null +++ b/projects/plugins/jetpack/_inc/content-guidelines-ai/lib/dom.js @@ -0,0 +1,31 @@ +/** + * Programmatically set a React-controlled textarea's value. + * Uses the native setter so React's synthetic onChange fires. + * + * @param {HTMLTextAreaElement} textarea - The textarea element. + * @param {string} value - The new value. + */ +export function setTextareaValue( textarea, value ) { + const setter = Object.getOwnPropertyDescriptor( + window.HTMLTextAreaElement.prototype, + 'value' + ).set; + setter.call( textarea, value ); + textarea.dispatchEvent( new Event( 'input', { bubbles: true } ) ); +} + +/** + * Accept a block suggestion: write text to the modal textarea and clear the store. + * + * @param {string} blockName - Block name key in the store. + * @param {string} suggestion - Suggestion text to write. + * @param {Function} clearSuggestion - Store action to clear the suggestion. + */ +export function acceptBlockSuggestion( blockName, suggestion, clearSuggestion ) { + const modal = document.querySelector( '.block-guideline-modal' ); + const textarea = modal?.querySelector( '.components-textarea-control__input' ); + if ( textarea ) { + setTextareaValue( textarea, suggestion ); + } + clearSuggestion( blockName ); +} diff --git a/projects/plugins/jetpack/_inc/content-guidelines-ai/lib/inject.js b/projects/plugins/jetpack/_inc/content-guidelines-ai/lib/inject.js new file mode 100644 index 000000000000..86c6faedd883 --- /dev/null +++ b/projects/plugins/jetpack/_inc/content-guidelines-ai/lib/inject.js @@ -0,0 +1,276 @@ +import { createRoot, createElement } from '@wordpress/element'; +import BlockSuggestionActions from '../components/block-suggestion-actions'; +import BlockSuggestionButtons from '../components/block-suggestion-buttons'; +import EmptyStateBanner from '../components/empty-state-banner'; +import SectionGenerateButton from '../components/section-generate-button'; +import SuggestAllButton from '../components/suggest-all-button'; +import SuggestionActions from '../components/suggestion-actions'; +import SuggestionBadge from '../components/suggestion-badge'; +import UpgradeNotice from '../components/upgrade-notice'; +import { VALID_SECTIONS } from '../constants'; + +// Each injection point tracks both the DOM container and its React root. +// Before re-injecting, we unmount the old root to ensure proper cleanup +// of effects and subscriptions. We verify containers via isConnected since +// Gutenberg's removes/re-adds the main screen DOM when +// navigating to revision history and back. + +const slots = { + header: { container: null, root: null }, + 'upgrade-notice': { container: null, root: null }, + banner: { container: null, root: null }, +}; + +for ( const slug of VALID_SECTIONS ) { + slots[ `badge-${ slug }` ] = { container: null, root: null }; + slots[ `actions-${ slug }` ] = { container: null, root: null }; + slots[ `button-${ slug }` ] = { container: null, root: null }; +} + +slots[ 'block-actions' ] = { container: null, root: null }; +slots[ 'block-suggestion-buttons' ] = { container: null, root: null }; + +/** + * Inject a React component into the DOM, reusing or replacing the slot. + * + * @param {string} key - Slot key in the slots map. + * @param {Function} findParent - Returns { parent, before, className } or null. + * @param {Function} Component - React component to render. + * @param {Object} [props] - Props to pass to the component. + */ +function inject( key, findParent, Component, props ) { + const slot = slots[ key ]; + + // Already injected and still in DOM — nothing to do. + if ( slot.container?.isConnected ) { + return; + } + + // Container was removed — unmount the old root to clean up effects. + if ( slot.root ) { + slot.root.unmount(); + slot.root = null; + slot.container = null; + } + + const target = findParent(); + if ( ! target ) { + return; + } + + const { parent, before, className, tag } = target; + const container = document.createElement( tag || 'div' ); + container.className = className; + + if ( before ) { + parent.insertBefore( container, before ); + } else { + parent.appendChild( container ); + } + + const root = createRoot( container ); + root.render( createElement( Component, props ) ); + + slot.container = container; + slot.root = root; +} + +/** + * Extract the block name from the block guideline modal. + * In editing mode, reads the disabled TextControl and matches against block types. + * In creating mode, reads the ComboboxControl's selected value. + */ +function getBlockNameFromModal( modal ) { + const { select } = wp.data; + const blockTypes = select( 'core/blocks' ).getBlockTypes(); + + // Editing mode: disabled input shows block title. + const disabledInput = modal.querySelector( 'input[disabled]' ); + if ( disabledInput?.value ) { + return blockTypes.find( b => b.title === disabledInput.value )?.name; + } + + // Creating mode: combobox with selected value. + const combobox = modal.querySelector( 'input[role="combobox"]' ); + if ( combobox?.value ) { + return blockTypes.find( b => b.title === combobox.value )?.name; + } + + return null; +} + +function runAll() { + // Header button. + inject( + 'header', + () => { + const actionsSlot = document.querySelector( '.admin-ui-page__header-actions' ); + return actionsSlot + ? { parent: actionsSlot, className: 'jetpack-content-guidelines-ai__header-container' } + : null; + }, + SuggestAllButton + ); + + // Upgrade notice — shown above the guideline list when AI is unavailable. + inject( + 'upgrade-notice', + () => { + const list = document.querySelector( '.content-guidelines__list' ); + return list + ? { + parent: list.parentElement, + before: list, + className: 'jetpack-content-guidelines-ai__upgrade-notice-container', + } + : null; + }, + UpgradeNotice + ); + + // Empty state banner. + inject( + 'banner', + () => { + const list = document.querySelector( '.content-guidelines__list' ); + return list + ? { + parent: list.parentElement, + before: list, + className: 'jetpack-content-guidelines-ai__banner-container', + } + : null; + }, + EmptyStateBanner + ); + + // Per-section injections. + for ( const slug of VALID_SECTIONS ) { + const form = document.getElementById( `content-guidelines-${ slug }` ); + if ( ! form ) { + continue; + } + + // Badge in accordion header. + inject( + `badge-${ slug }`, + () => { + const accordion = form.closest( '.content-guidelines__accordion' ); + const trigger = accordion?.querySelector( '.content-guidelines__accordion-trigger' ); + const hStack = trigger?.firstElementChild; + if ( ! hStack ) { + return null; + } + return { + parent: hStack, + before: hStack.lastElementChild, + className: 'jetpack-content-guidelines-ai__badge-container', + tag: 'span', + }; + }, + SuggestionBadge, + { slug } + ); + + // Suggestion actions (diff + accept/dismiss) at top of form. + inject( + `actions-${ slug }`, + () => { + const vStack = form.firstElementChild; + return vStack + ? { + parent: vStack, + before: vStack.firstChild, + className: 'jetpack-content-guidelines-ai__actions-container', + } + : null; + }, + SuggestionActions, + { slug } + ); + + // Per-section generate button next to save. + inject( + `button-${ slug }`, + () => { + const saveButton = form.querySelector( '.save-button' ); + const hStack = saveButton?.parentElement; + return hStack + ? { + parent: hStack, + className: 'jetpack-content-guidelines-ai__section-button-container', + } + : null; + }, + SectionGenerateButton, + { slug } + ); + } + + // Block guideline modal injections. + const blockModal = document.querySelector( '.block-guideline-modal' ); + const blockName = blockModal ? getBlockNameFromModal( blockModal ) : null; + + if ( blockName ) { + // Suggestion actions (diff + accept/dismiss) inside textarea wrapper, + // after the label but before the