From ba153594f7462086962c4723bcb7eed5c731fcfe Mon Sep 17 00:00:00 2001 From: Ankur Modi Date: Thu, 14 Nov 2024 12:03:35 +0530 Subject: [PATCH 01/37] Update gpl license from gpl-2 to gpl-3 #39 --- package.json | 2 +- perform.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 9aa010b..2710873 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "1.3.1", "description": "Web Performance Optimization Plugin for WordPress", "homepage": "https://github.com/performwp/perform#readme", - "license": "GPL-2.0-or-later", + "license": "GPL-3.0-or-later", "author": { "name": "PerformWP", "email": "hello@performwp.com", diff --git a/perform.php b/perform.php index d314d71..6c59a09 100644 --- a/perform.php +++ b/perform.php @@ -20,7 +20,7 @@ * Version: 1.3.1 * Author: Mehul Gohil * Author URI: https://mehulgohil.com/ - * License: GPLv2 or later + * License: GPLv3 or later * License URI: http://www.gnu.org/licenses/gpl-2.0.html * Text Domain: perform * Domain Path: /languages From ed1d72f8cc47e394669e9a6311601591fb6754a5 Mon Sep 17 00:00:00 2001 From: Ankur Modi Date: Thu, 14 Nov 2024 13:31:56 +0530 Subject: [PATCH 02/37] Added blueprint.json #40 --- assets/blueprints/blueprint.json | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 assets/blueprints/blueprint.json diff --git a/assets/blueprints/blueprint.json b/assets/blueprints/blueprint.json new file mode 100644 index 0000000..7b58ede --- /dev/null +++ b/assets/blueprints/blueprint.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://playground.wordpress.net/blueprint-schema.json", + "landingPage": "/wp-admin/options-general.php?page=perform", + "features": { + "networking": true + }, + "preferredVersions": { + "php": "7.4", + "wp": "6.5" + }, + "steps": [ + { + "step": "login", + "username": "admin", + "password": "password" + }, + { + "step": "installPlugin", + "pluginZipFile": { + "resource": "wordpress.org\/plugins", + "slug": "perform" + }, + "options": { + "activate": true + } + } + ] +} \ No newline at end of file From eacba36fe239f9707c4691845fe8ff589748f5d2 Mon Sep 17 00:00:00 2001 From: Mehul Gohil Date: Wed, 21 May 2025 15:50:40 +0530 Subject: [PATCH 03/37] Update assets/blueprints/blueprint.json Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- assets/blueprints/blueprint.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/blueprints/blueprint.json b/assets/blueprints/blueprint.json index 7b58ede..cfd963d 100644 --- a/assets/blueprints/blueprint.json +++ b/assets/blueprints/blueprint.json @@ -5,7 +5,7 @@ "networking": true }, "preferredVersions": { - "php": "7.4", + "php": "8.1", "wp": "6.5" }, "steps": [ From 40dc5ad98b48379c4d1458d46996c724722c7745 Mon Sep 17 00:00:00 2001 From: Mehul Gohil Date: Wed, 25 Jun 2025 16:09:01 +0530 Subject: [PATCH 04/37] change git repo to org repo --- webpack.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpack.config.js b/webpack.config.js index bfea00c..afb4aaa 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -57,7 +57,7 @@ if ( inProduction ) { destFile: 'languages/perform.pot', relativeTo: './', src: [ './**/*.php', '!./includes/libraries/**/*', '!./vendor/**/*' ], - bugReport: 'https://github.com/mehul0810/perform/issues/new', + bugReport: 'https://github.com/performwp/perform/issues/new', team: 'PerformWP ', } ); } From d648a6760ac2caabb073741407893190ce9b8703 Mon Sep 17 00:00:00 2001 From: Mehul Gohil Date: Wed, 25 Jun 2025 16:09:18 +0530 Subject: [PATCH 05/37] updated package lock json --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index fb856a6..0887577 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,13 @@ { "name": "perform", - "version": "1.3.1", + "version": "1.4.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "perform", - "version": "1.3.1", - "license": "GPL-2.0-or-later", + "version": "1.4.1", + "license": "GPL-3.0-or-later", "devDependencies": { "@wordpress/scripts": "^27.1.0", "clean-webpack-plugin": "^4.0.0", From e6227d0e046255d179701c30d8895b56c30838c6 Mon Sep 17 00:00:00 2001 From: Mehul Gohil Date: Tue, 8 Jul 2025 19:01:50 +0530 Subject: [PATCH 06/37] add settings app using react --- assets/src/js/admin/SettingsApp.jsx | 13 + assets/src/js/admin/main.js | 14 + package-lock.json | 1352 ++++++++++++++++++++++++++- package.json | 2 + src/Admin/Settings/Menu.php | 5 +- webpack.config.js | 2 +- 6 files changed, 1368 insertions(+), 20 deletions(-) create mode 100644 assets/src/js/admin/SettingsApp.jsx diff --git a/assets/src/js/admin/SettingsApp.jsx b/assets/src/js/admin/SettingsApp.jsx new file mode 100644 index 0000000..cf9bd52 --- /dev/null +++ b/assets/src/js/admin/SettingsApp.jsx @@ -0,0 +1,13 @@ +import { Panel, PanelBody, Button } from '@wordpress/components'; +import { createElement } from '@wordpress/element'; + +const SettingsApp = () => ( + + +

This is the WordPress-powered settings app. Add your fields here.

+ +
+
+); + +export default SettingsApp; diff --git a/assets/src/js/admin/main.js b/assets/src/js/admin/main.js index 62df6f6..fba9851 100644 --- a/assets/src/js/admin/main.js +++ b/assets/src/js/admin/main.js @@ -1,3 +1,17 @@ +import { render } from '@wordpress/element'; +import SettingsApp from './SettingsApp'; + +document.addEventListener('DOMContentLoaded', () => { + const el = document.getElementById('perform-settings-page'); + if (el) { + render( + , + el + ); + } +}); + + document.addEventListener( 'DOMContentLoaded', () => { const saveBtn = document.getElementById( 'perform-save-settings' ); const formElement = document.getElementById( 'perform-admin-settings-form' ); diff --git a/package-lock.json b/package-lock.json index 0887577..c153a5a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,8 @@ "version": "1.4.1", "license": "GPL-3.0-or-later", "devDependencies": { + "@wordpress/components": "^29.12.0", + "@wordpress/element": "^6.26.0", "@wordpress/scripts": "^27.1.0", "clean-webpack-plugin": "^4.0.0", "copy-webpack-plugin": "^12.0.2", @@ -46,6 +48,47 @@ "node": ">=6.0.0" } }, + "node_modules/@ariakit/core": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.4.15.tgz", + "integrity": "sha512-vvxmZvkNhiisKM+Y1TbGMUfVVchV/sWu9F0xw0RYADXcimWPK31dd9JnIZs/OQ5pwAryAHmERHwuGQVESkSjwQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@ariakit/react": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/@ariakit/react/-/react-0.4.17.tgz", + "integrity": "sha512-HQaIboE2axtlncJz1hRTaiQfJ1GGjhdtNcAnPwdjvl2RybfmlHowIB+HTVBp36LzroKPs/M4hPCxk7XTaqRZGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ariakit/react-core": "0.4.17" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ariakit" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@ariakit/react-core": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/@ariakit/react-core/-/react-core-0.4.17.tgz", + "integrity": "sha512-kFF6n+gC/5CRQIyaMTFoBPio2xUe0k9rZhMNdUobWRmc/twfeLVkODx+8UVYaNyKilTge8G0JFqwvFKku/jKEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ariakit/core": "0.4.15", + "@floating-ui/dom": "^1.0.0", + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.24.2", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", @@ -1945,10 +1988,11 @@ "dev": true }, "node_modules/@babel/runtime": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz", - "integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.7.tgz", + "integrity": "sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==", "dev": true, + "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -3038,6 +3082,196 @@ "node": ">=10.0.0" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@emotion/babel-plugin/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/css": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/css/-/css-11.13.5.tgz", + "integrity": "sha512-wQdD0Xhkn3Qy2VNcIzbLP9MR8TafI0MJb7BEAXKp+w4+XqErksWR4OXomuDzPsN4InLdGhVe6EYcn2ZIUCpB8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", + "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@emotion/styled": { + "version": "11.14.1", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", + "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "dev": true, + "license": "MIT" + }, "node_modules/@es-joy/jsdoccomment": { "version": "0.41.0", "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.41.0.tgz", @@ -3187,6 +3421,48 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.2.tgz", + "integrity": "sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.2.tgz", + "integrity": "sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.2", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.4.tgz", + "integrity": "sha512-JbbpPhp38UmXDDAu60RJmbeme37Jbgsm7NrHGgzYYFKmblzRUh6Pa641dII6LsjwF4XlScDrde2UAzDo/b9KPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.2" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", @@ -4710,6 +4986,20 @@ "@types/node": "*" } }, + "node_modules/@types/gradient-parser": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@types/gradient-parser/-/gradient-parser-0.1.3.tgz", + "integrity": "sha512-XDbrTSBlQV9nxE1GiDL3FaOPy4G/KaJkhDutBX48Kg8CYZMBARyyDFGCWfWJn4pobmInmwud1xxH7VJMAr0CKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/highlight-words-core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/highlight-words-core/-/highlight-words-core-1.2.1.tgz", + "integrity": "sha512-9VZUA5omXBfn+hDxFjUDu1FOJTBM3LmvqfDey+Z6Aa8B8/JmF5SMj6FBrjfgJ/Q3YXOZd3qyTDfJyMZSs/wCUA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/http-errors": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", @@ -4836,6 +5126,13 @@ "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", "dev": true }, + "node_modules/@types/mousetrap": { + "version": "1.6.15", + "resolved": "https://registry.npmjs.org/@types/mousetrap/-/mousetrap-1.6.15.tgz", + "integrity": "sha512-qL0hyIMNPow317QWW/63RvL1x5MVMV+Ru3NaY9f/CuEpCqrmb7WeuK2071ZY5hczOnm38qExWM2i2WtkXLSqFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "20.12.4", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.4.tgz", @@ -4866,6 +5163,13 @@ "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", "dev": true }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/q": { "version": "1.5.8", "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.8.tgz", @@ -4884,6 +5188,27 @@ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "dev": true }, + "node_modules/@types/react": { + "version": "18.3.23", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz", + "integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, "node_modules/@types/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", @@ -5362,6 +5687,26 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "dev": true }, + "node_modules/@use-gesture/core": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.3.1.tgz", + "integrity": "sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@use-gesture/react": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/@use-gesture/react/-/react-10.3.1.tgz", + "integrity": "sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@use-gesture/core": "10.3.1" + }, + "peerDependencies": { + "react": ">= 16.8.0" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", @@ -5552,6 +5897,58 @@ } } }, + "node_modules/@wordpress/a11y": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@wordpress/a11y/-/a11y-4.26.0.tgz", + "integrity": "sha512-O5/y1Ljg7jOKULfn3iKgQ8mxRG2Htdr8WnkXepSEpWnFjb9agSplQPAUfELCejDR8d4bPUOJcJXtGnHh7T6hCw==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "7.25.7", + "@wordpress/dom-ready": "^4.26.0", + "@wordpress/i18n": "^5.26.0" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/a11y/node_modules/@wordpress/hooks": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-4.26.0.tgz", + "integrity": "sha512-pYbk2Oz4EbFge2AYnCeaLXKOP9JOleJDw3qTn8NY863ufKqU2i4Ttu3lYjJPk/+YIE3LZ7bdUtYypD1cltWVcg==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "7.25.7" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/a11y/node_modules/@wordpress/i18n": { + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-5.26.0.tgz", + "integrity": "sha512-YHzaUWlCuN2ynl47qbsdMkTGtP52+E1giDOdWBgUaSexUYjbeFxKFUzRMB0Wuh1psL80+VzvJOH/mU440KAJnA==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "7.25.7", + "@wordpress/hooks": "^4.26.0", + "gettext-parser": "^1.3.1", + "memize": "^2.1.0", + "sprintf-js": "^1.1.1", + "tannin": "^1.2.0" + }, + "bin": { + "pot-to-php": "tools/pot-to-php.js" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, "node_modules/@wordpress/api-fetch": { "version": "6.52.0", "resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-6.52.0.tgz", @@ -5616,6 +6013,299 @@ "node": ">=14" } }, + "node_modules/@wordpress/components": { + "version": "29.12.0", + "resolved": "https://registry.npmjs.org/@wordpress/components/-/components-29.12.0.tgz", + "integrity": "sha512-jE96pUj84OZya54VusRdEIdTiLjbe2Qst3GbHZcQpA5GiSkPBmGjKWpO6FxR7kRDT4GMnZoVxgtV6xJk4IaNQw==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@ariakit/react": "^0.4.15", + "@babel/runtime": "7.25.7", + "@emotion/cache": "^11.7.1", + "@emotion/css": "^11.7.1", + "@emotion/react": "^11.7.1", + "@emotion/serialize": "^1.0.2", + "@emotion/styled": "^11.6.0", + "@emotion/utils": "^1.0.0", + "@floating-ui/react-dom": "^2.0.8", + "@types/gradient-parser": "0.1.3", + "@types/highlight-words-core": "1.2.1", + "@use-gesture/react": "^10.3.1", + "@wordpress/a11y": "^4.26.0", + "@wordpress/compose": "^7.26.0", + "@wordpress/date": "^5.26.0", + "@wordpress/deprecated": "^4.26.0", + "@wordpress/dom": "^4.26.0", + "@wordpress/element": "^6.26.0", + "@wordpress/escape-html": "^3.26.0", + "@wordpress/hooks": "^4.26.0", + "@wordpress/html-entities": "^4.26.0", + "@wordpress/i18n": "^5.26.0", + "@wordpress/icons": "^10.26.0", + "@wordpress/is-shallow-equal": "^5.26.0", + "@wordpress/keycodes": "^4.26.0", + "@wordpress/primitives": "^4.26.0", + "@wordpress/private-apis": "^1.26.0", + "@wordpress/rich-text": "^7.26.0", + "@wordpress/warning": "^3.26.0", + "change-case": "^4.1.2", + "clsx": "^2.1.1", + "colord": "^2.7.0", + "date-fns": "^3.6.0", + "deepmerge": "^4.3.0", + "fast-deep-equal": "^3.1.3", + "framer-motion": "^11.1.9", + "gradient-parser": "1.0.2", + "highlight-words-core": "^1.2.2", + "is-plain-object": "^5.0.0", + "memize": "^2.1.0", + "path-to-regexp": "^6.2.1", + "re-resizable": "^6.4.0", + "react-colorful": "^5.3.1", + "remove-accents": "^0.5.0", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@wordpress/components/node_modules/@wordpress/hooks": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-4.26.0.tgz", + "integrity": "sha512-pYbk2Oz4EbFge2AYnCeaLXKOP9JOleJDw3qTn8NY863ufKqU2i4Ttu3lYjJPk/+YIE3LZ7bdUtYypD1cltWVcg==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "7.25.7" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/components/node_modules/@wordpress/i18n": { + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-5.26.0.tgz", + "integrity": "sha512-YHzaUWlCuN2ynl47qbsdMkTGtP52+E1giDOdWBgUaSexUYjbeFxKFUzRMB0Wuh1psL80+VzvJOH/mU440KAJnA==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "7.25.7", + "@wordpress/hooks": "^4.26.0", + "gettext-parser": "^1.3.1", + "memize": "^2.1.0", + "sprintf-js": "^1.1.1", + "tannin": "^1.2.0" + }, + "bin": { + "pot-to-php": "tools/pot-to-php.js" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/components/node_modules/@wordpress/keycodes": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-4.26.0.tgz", + "integrity": "sha512-R+mKsQoHdqxnay2f5DOMbqCP0BnKwgWNGoBGCazviy0gfesvEbNPpOOtJS8magujILSgg6tQ4UQcz5Y0bx7Dig==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "7.25.7", + "@wordpress/i18n": "^5.26.0" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/components/node_modules/@wordpress/warning": { + "version": "3.26.0", + "resolved": "https://registry.npmjs.org/@wordpress/warning/-/warning-3.26.0.tgz", + "integrity": "sha512-7vVvrG29eMaH7lxr5ZYDPUMalACZoBqblK8UzZBunXROXmiBfhhZPylfj9DK4wxrfyvhsLWnvewHWxim/pZ3Zg==", + "dev": true, + "license": "GPL-2.0-or-later", + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/components/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@wordpress/components/node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@wordpress/components/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@wordpress/compose": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@wordpress/compose/-/compose-7.26.0.tgz", + "integrity": "sha512-1od3cGyMLVlHI/x29ua3vJR2+x6OcFg//87LsNZ+EbVIH4wxIJZK5BhIUfN4AcvmzNX+22jsKDlj1wvwPh7a/w==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "7.25.7", + "@types/mousetrap": "^1.6.8", + "@wordpress/deprecated": "^4.26.0", + "@wordpress/dom": "^4.26.0", + "@wordpress/element": "^6.26.0", + "@wordpress/is-shallow-equal": "^5.26.0", + "@wordpress/keycodes": "^4.26.0", + "@wordpress/priority-queue": "^3.26.0", + "@wordpress/undo-manager": "^1.26.0", + "change-case": "^4.1.2", + "clipboard": "^2.0.11", + "mousetrap": "^1.6.5", + "use-memo-one": "^1.1.1" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, + "node_modules/@wordpress/compose/node_modules/@wordpress/hooks": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-4.26.0.tgz", + "integrity": "sha512-pYbk2Oz4EbFge2AYnCeaLXKOP9JOleJDw3qTn8NY863ufKqU2i4Ttu3lYjJPk/+YIE3LZ7bdUtYypD1cltWVcg==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "7.25.7" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/compose/node_modules/@wordpress/i18n": { + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-5.26.0.tgz", + "integrity": "sha512-YHzaUWlCuN2ynl47qbsdMkTGtP52+E1giDOdWBgUaSexUYjbeFxKFUzRMB0Wuh1psL80+VzvJOH/mU440KAJnA==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "7.25.7", + "@wordpress/hooks": "^4.26.0", + "gettext-parser": "^1.3.1", + "memize": "^2.1.0", + "sprintf-js": "^1.1.1", + "tannin": "^1.2.0" + }, + "bin": { + "pot-to-php": "tools/pot-to-php.js" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/compose/node_modules/@wordpress/keycodes": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-4.26.0.tgz", + "integrity": "sha512-R+mKsQoHdqxnay2f5DOMbqCP0BnKwgWNGoBGCazviy0gfesvEbNPpOOtJS8magujILSgg6tQ4UQcz5Y0bx7Dig==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "7.25.7", + "@wordpress/i18n": "^5.26.0" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/data": { + "version": "10.26.0", + "resolved": "https://registry.npmjs.org/@wordpress/data/-/data-10.26.0.tgz", + "integrity": "sha512-lT6VMSRkaXr08no3PlKaO/dzraQctZ0ZZQ9U+HPjMyyD+GPDxbNiY8ydaQcxJ4RV4hiu65yE2Qxq/FYf3tdSsQ==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "7.25.7", + "@wordpress/compose": "^7.26.0", + "@wordpress/deprecated": "^4.26.0", + "@wordpress/element": "^6.26.0", + "@wordpress/is-shallow-equal": "^5.26.0", + "@wordpress/priority-queue": "^3.26.0", + "@wordpress/private-apis": "^1.26.0", + "@wordpress/redux-routine": "^5.26.0", + "deepmerge": "^4.3.0", + "equivalent-key-map": "^0.2.2", + "is-plain-object": "^5.0.0", + "is-promise": "^4.0.0", + "redux": "^5.0.1", + "rememo": "^4.0.2", + "use-memo-one": "^1.1.1" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, + "node_modules/@wordpress/data/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@wordpress/date": { + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/@wordpress/date/-/date-5.26.0.tgz", + "integrity": "sha512-UgiOgtZTn1QSnNN9iaoGzJzb8TL6sFMcST0Y8nD8tdpaVRCFKCb13m9YKf2a+KHKqZiYnHvg3Re2CV33r8fMMw==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "7.25.7", + "@wordpress/deprecated": "^4.26.0", + "moment": "^2.29.4", + "moment-timezone": "^0.5.40" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, "node_modules/@wordpress/dependency-extraction-webpack-plugin": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@wordpress/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-5.6.0.tgz", @@ -5631,6 +6321,64 @@ "webpack": "^5.0.0" } }, + "node_modules/@wordpress/deprecated": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@wordpress/deprecated/-/deprecated-4.26.0.tgz", + "integrity": "sha512-frBJqCKlMrKeUcVgbp2hq7MKeCg7hQCCMCTTqDXaRMa0+oYDPFKx3gPx529uSMOWxkzJHz+mdr5lHrFzElM6eQ==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "7.25.7", + "@wordpress/hooks": "^4.26.0" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/deprecated/node_modules/@wordpress/hooks": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-4.26.0.tgz", + "integrity": "sha512-pYbk2Oz4EbFge2AYnCeaLXKOP9JOleJDw3qTn8NY863ufKqU2i4Ttu3lYjJPk/+YIE3LZ7bdUtYypD1cltWVcg==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "7.25.7" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/dom": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@wordpress/dom/-/dom-4.26.0.tgz", + "integrity": "sha512-JDo392C1XkWzebxw/W+sILlk8m6Q1JOhZIiTH7tNV7Pzqm3wiCQZX0GjwfMjRD2CTTAvfhEjPEgNF1gB1iO7Uw==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "7.25.7", + "@wordpress/deprecated": "^4.26.0" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/dom-ready": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@wordpress/dom-ready/-/dom-ready-4.26.0.tgz", + "integrity": "sha512-VfKcvB2XtXZHluits/RY9O4VhpZ+YG5SQVN07SwbtSg1vaFJSkUnp31qOsLuVrFyhiUUYkZ/liCpO/vbalZqxQ==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "7.25.7" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, "node_modules/@wordpress/e2e-test-utils-playwright": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils-playwright/-/e2e-test-utils-playwright-0.23.0.tgz", @@ -5654,6 +6402,51 @@ "@playwright/test": ">=1" } }, + "node_modules/@wordpress/element": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-6.26.0.tgz", + "integrity": "sha512-IlzQE7oVG4fuwRA5N7vhnr57kvf1HS08kwJwP+EC/olREnFEi8XOIeDa7rAEVXNAx2xeoLKQ6+K7Banp7+c6GA==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "7.25.7", + "@types/react": "^18.2.79", + "@types/react-dom": "^18.2.25", + "@wordpress/escape-html": "^3.26.0", + "change-case": "^4.1.2", + "is-plain-object": "^5.0.0", + "react": "^18.3.0", + "react-dom": "^18.3.0" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/element/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@wordpress/escape-html": { + "version": "3.26.0", + "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-3.26.0.tgz", + "integrity": "sha512-SQfSmUOMP32duStoxvrkydCtD/ELyNXpAwkE414swo8AQAKxBJMQDYE3PZy1uZ6YCtbSX7EHHAX9G1EeoHUzgg==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "7.25.7" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, "node_modules/@wordpress/eslint-plugin": { "version": "17.12.0", "resolved": "https://registry.npmjs.org/@wordpress/eslint-plugin/-/eslint-plugin-17.12.0.tgz", @@ -5752,6 +6545,20 @@ "node": ">=12" } }, + "node_modules/@wordpress/html-entities": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@wordpress/html-entities/-/html-entities-4.26.0.tgz", + "integrity": "sha512-4/rwRcMT8IxbiN2SEFZOqpSyy2Yd82JTksqwcC8orjfj5u662KaEzGSD4/4PYtxn9pxTYQaUc3Ap+A9NN1MXDg==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "7.25.7" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, "node_modules/@wordpress/i18n": { "version": "4.55.0", "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-4.55.0.tgz", @@ -5769,7 +6576,37 @@ "pot-to-php": "tools/pot-to-php.js" }, "engines": { - "node": ">=12" + "node": ">=12" + } + }, + "node_modules/@wordpress/icons": { + "version": "10.26.0", + "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-10.26.0.tgz", + "integrity": "sha512-7XPcJbvy4s8USfcuMxdVE6qTaEYzRv0+TZa6Epbe61HFrvaMl9X0Mr+jcCyQ7qBp4jKHfHInWfywNeYOxc5SMg==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "7.25.7", + "@wordpress/element": "^6.26.0", + "@wordpress/primitives": "^4.26.0" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/is-shallow-equal": { + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/@wordpress/is-shallow-equal/-/is-shallow-equal-5.26.0.tgz", + "integrity": "sha512-X9MjEk0h0tyOFfthGTq/aWF0FMeWSnBFUkeatVMCjXA4RzeOwLFM6j1aqnhdIvt2vAbiFlyKCmXBsdEj8/NHSg==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "7.25.7" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" } }, "node_modules/@wordpress/jest-console": { @@ -5858,6 +6695,161 @@ "prettier": ">=3" } }, + "node_modules/@wordpress/primitives": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@wordpress/primitives/-/primitives-4.26.0.tgz", + "integrity": "sha512-vmqKlqQxyv9XDKeIntd70SpRJeU0uXWj6iQDZmmbsOcDWL3UNIOFeN5dB25vDeyoseQ+r+JNnoU+hq7cpQa/8Q==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "7.25.7", + "@wordpress/element": "^6.26.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, + "node_modules/@wordpress/priority-queue": { + "version": "3.26.0", + "resolved": "https://registry.npmjs.org/@wordpress/priority-queue/-/priority-queue-3.26.0.tgz", + "integrity": "sha512-BuBch5kypjoVdyqPLHipajoMgpe9BhTIpqGuIrD1KcjP29mfYFVzfqu3PeowAVrPm8rQrf7PtlYDgm2td0vVLQ==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "7.25.7", + "requestidlecallback": "^0.3.0" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/private-apis": { + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-1.26.0.tgz", + "integrity": "sha512-0ZlOD/FYS2Izr+0S05/c3X+Mt+pxdRhwe3SxrfdnFzoKCuyYZ1j/Z7I0r17AsQL1y3S4RBl5QoWoKpK6FdjzzQ==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "7.25.7" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/redux-routine": { + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/@wordpress/redux-routine/-/redux-routine-5.26.0.tgz", + "integrity": "sha512-wiy7bzpWjFiJOxVanJKA72ggOQ8d1NntJjcX6+HSxWife7/oHWcRwUFgOvAZN0UngBnRz84SVEgBP0xp7OeWwQ==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "7.25.7", + "is-plain-object": "^5.0.0", + "is-promise": "^4.0.0", + "rungen": "^0.3.2" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + }, + "peerDependencies": { + "redux": ">=4" + } + }, + "node_modules/@wordpress/redux-routine/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@wordpress/rich-text": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@wordpress/rich-text/-/rich-text-7.26.0.tgz", + "integrity": "sha512-W4ydPAV2Er8OBEDM7pDHkaVosQrx9gfV3SDXy+2OHV+OEq9uN/1N/Y8ZZmijdo2ivPEqYzJGR30zpBqBUXf8Kg==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "7.25.7", + "@wordpress/a11y": "^4.26.0", + "@wordpress/compose": "^7.26.0", + "@wordpress/data": "^10.26.0", + "@wordpress/deprecated": "^4.26.0", + "@wordpress/element": "^6.26.0", + "@wordpress/escape-html": "^3.26.0", + "@wordpress/i18n": "^5.26.0", + "@wordpress/keycodes": "^4.26.0", + "memize": "^2.1.0" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, + "node_modules/@wordpress/rich-text/node_modules/@wordpress/hooks": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-4.26.0.tgz", + "integrity": "sha512-pYbk2Oz4EbFge2AYnCeaLXKOP9JOleJDw3qTn8NY863ufKqU2i4Ttu3lYjJPk/+YIE3LZ7bdUtYypD1cltWVcg==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "7.25.7" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/rich-text/node_modules/@wordpress/i18n": { + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-5.26.0.tgz", + "integrity": "sha512-YHzaUWlCuN2ynl47qbsdMkTGtP52+E1giDOdWBgUaSexUYjbeFxKFUzRMB0Wuh1psL80+VzvJOH/mU440KAJnA==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "7.25.7", + "@wordpress/hooks": "^4.26.0", + "gettext-parser": "^1.3.1", + "memize": "^2.1.0", + "sprintf-js": "^1.1.1", + "tannin": "^1.2.0" + }, + "bin": { + "pot-to-php": "tools/pot-to-php.js" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/rich-text/node_modules/@wordpress/keycodes": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-4.26.0.tgz", + "integrity": "sha512-R+mKsQoHdqxnay2f5DOMbqCP0BnKwgWNGoBGCazviy0gfesvEbNPpOOtJS8magujILSgg6tQ4UQcz5Y0bx7Dig==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "7.25.7", + "@wordpress/i18n": "^5.26.0" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, "node_modules/@wordpress/scripts": { "version": "27.6.0", "resolved": "https://registry.npmjs.org/@wordpress/scripts/-/scripts-27.6.0.tgz", @@ -6159,6 +7151,21 @@ "stylelint": "^14.2" } }, + "node_modules/@wordpress/undo-manager": { + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@wordpress/undo-manager/-/undo-manager-1.26.0.tgz", + "integrity": "sha512-irdyF9ngehjVf1E8YbbwAr91S34DFduDUOe0/U9sdFkrYAaVRPVtWtXgE/T6bVVTQYW3m+x32qUusLXkYAZjZg==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "7.25.7", + "@wordpress/is-shallow-equal": "^5.26.0" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, "node_modules/@wordpress/url": { "version": "3.56.0", "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.56.0.tgz", @@ -7012,6 +8019,39 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-plugin-macros/node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/babel-plugin-polyfill-corejs2": { "version": "0.4.10", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.10.tgz", @@ -8416,6 +9456,18 @@ "webpack": ">=4.0.0 <6.0.0" } }, + "node_modules/clipboard": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz", + "integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==", + "dev": true, + "license": "MIT", + "dependencies": { + "good-listener": "^1.2.2", + "select": "^1.1.2", + "tiny-emitter": "^2.0.0" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -8455,6 +9507,16 @@ "mimic-response": "^1.0.0" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -9618,6 +10680,13 @@ "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", "dev": true }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, + "license": "MIT" + }, "node_modules/currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", @@ -9723,6 +10792,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/debounce": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", @@ -10248,6 +11328,13 @@ "node": ">=0.4.0" } }, + "node_modules/delegate": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", + "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==", + "dev": true, + "license": "MIT" + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -10644,6 +11731,13 @@ "node": ">=4" } }, + "node_modules/equivalent-key-map": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/equivalent-key-map/-/equivalent-key-map-0.2.2.tgz", + "integrity": "sha512-xvHeyCDbZzkpN4VHQj/n+j2lOwL0VWszG30X4cOrc9Y7Tuo2qCdZK/0AMod23Z5dCtNUbaju6p0rwOhHUk05ew==", + "dev": true, + "license": "MIT" + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -12586,6 +13680,13 @@ "find-process": "bin/find-process.js" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "dev": true, + "license": "MIT" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -12781,6 +13882,34 @@ "node": ">=0.10.0" } }, + "node_modules/framer-motion": { + "version": "11.18.2", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.18.2.tgz", + "integrity": "sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "motion-dom": "^11.18.1", + "motion-utils": "^11.18.1", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -13323,6 +14452,16 @@ "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==", "dev": true }, + "node_modules/good-listener": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", + "integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==", + "dev": true, + "license": "MIT", + "dependencies": { + "delegate": "^3.1.2" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -13393,6 +14532,15 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, + "node_modules/gradient-parser": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/gradient-parser/-/gradient-parser-1.0.2.tgz", + "integrity": "sha512-gR6nY33xC9yJoH4wGLQtZQMXDi6RI3H37ERu7kQCVUzlXjNedpZM7xcA489Opwbq0BSGohtWGsWsntupmxelMg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -13640,6 +14788,30 @@ "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==", "dev": true }, + "node_modules/highlight-words-core": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/highlight-words-core/-/highlight-words-core-1.2.3.tgz", + "integrity": "sha512-m1O9HW3/GNHxzSIXWw1wCNXXsgLlxrP0OI6+ycGUhiUHkikqW3OrwVHz+lxeNBe5yqLESdIcj8PowHQ2zLvUvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, "node_modules/homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -15453,6 +16625,13 @@ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", "dev": true }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "dev": true, + "license": "MIT" + }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -18162,6 +19341,53 @@ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "dev": true }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/moment-timezone": { + "version": "0.5.48", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz", + "integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "moment": "^2.29.4" + }, + "engines": { + "node": "*" + } + }, + "node_modules/motion-dom": { + "version": "11.18.1", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.18.1.tgz", + "integrity": "sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "motion-utils": "^11.18.1" + } + }, + "node_modules/motion-utils": { + "version": "11.18.1", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.18.1.tgz", + "integrity": "sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mousetrap": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/mousetrap/-/mousetrap-1.6.5.tgz", + "integrity": "sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA==", + "dev": true, + "license": "Apache-2.0 WITH LLVM-exception" + }, "node_modules/mrmime": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", @@ -21580,11 +22806,23 @@ "node": ">=0.10.0" } }, + "node_modules/re-resizable": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-6.11.2.tgz", + "integrity": "sha512-2xI2P3OHs5qw7K0Ud1aLILK6MQxW50TcO+DetD9eIV58j84TqYeHoZcL9H4GXFXXIh7afhH8mv5iUCXII7OW7A==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "react": "^16.13.1 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "dev": true, + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" }, @@ -21592,18 +22830,29 @@ "node": ">=0.10.0" } }, + "node_modules/react-colorful": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz", + "integrity": "sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" + "scheduler": "^0.23.2" }, "peerDependencies": { - "react": "^18.2.0" + "react": "^18.3.1" } }, "node_modules/react-is": { @@ -21821,6 +23070,13 @@ "node": ">=8" } }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "dev": true, + "license": "MIT" + }, "node_modules/reflect.getprototypeof": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", @@ -21944,6 +23200,13 @@ "jsesc": "bin/jsesc" } }, + "node_modules/rememo": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/rememo/-/rememo-4.0.2.tgz", + "integrity": "sha512-NVfSP9NstE3QPNs/TnegQY0vnJnstKQSpcrsI2kBTB3dB2PkdfKdTa+abbjMIDqpc63fE5LfjLgfMst0ULMFxQ==", + "dev": true, + "license": "MIT" + }, "node_modules/remove-accents": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz", @@ -21989,6 +23252,13 @@ "node": ">= 0.10" } }, + "node_modules/requestidlecallback": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/requestidlecallback/-/requestidlecallback-0.3.0.tgz", + "integrity": "sha512-TWHFkT7S9p7IxLC5A1hYmAYQx2Eb9w1skrXmQ+dS1URyvR8tenMLl4lHbqEOUnpEYxNKpkVMXUgknVpBZWXXfQ==", + "dev": true, + "license": "MIT" + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -22334,6 +23604,13 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rungen": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/rungen/-/rungen-0.3.2.tgz", + "integrity": "sha512-zWl10xu2D7zoR8zSC2U6bg5bYF6T/Wk7rxwp8IPaJH7f0Ge21G03kNHVgHR7tyVkSSfAOG0Rqf/Cl38JftSmtw==", + "dev": true, + "license": "MIT" + }, "node_modules/rxjs": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", @@ -22487,11 +23764,11 @@ } }, "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" } @@ -22533,6 +23810,13 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, + "node_modules/select": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", + "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==", + "dev": true, + "license": "MIT" + }, "node_modules/select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -24008,6 +25292,13 @@ "node": ">=0.10.0" } }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "dev": true, + "license": "MIT" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -24410,6 +25701,13 @@ "integrity": "sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A==", "dev": true }, + "node_modules/tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==", + "dev": true, + "license": "MIT" + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -25117,6 +26415,26 @@ "node": ">=0.10.0" } }, + "node_modules/use-memo-one": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz", + "integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index ff2cb76..c00dd00 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,8 @@ "plugin-zip": "wp-scripts plugin-zip" }, "devDependencies": { + "@wordpress/components": "^29.12.0", + "@wordpress/element": "^6.26.0", "@wordpress/scripts": "^27.1.0", "clean-webpack-plugin": "^4.0.0", "copy-webpack-plugin": "^12.0.2", diff --git a/src/Admin/Settings/Menu.php b/src/Admin/Settings/Menu.php index 9a55c0f..9a4850f 100644 --- a/src/Admin/Settings/Menu.php +++ b/src/Admin/Settings/Menu.php @@ -17,10 +17,10 @@ class Menu extends Api { /** * Tabs for the settings page. - * + * * @since 2.0.0 * @access public - * + * * @var array */ public $tabs = []; @@ -94,6 +94,7 @@ public function render_settings_page_header() { return; } ?> +
<?php esc_attr_e( 'PerformWP', 'perform' ); ?> diff --git a/webpack.config.js b/webpack.config.js index afb4aaa..9591d31 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -17,7 +17,7 @@ const config = { entry: { ...defaultConfig.entry, perform: [ './assets/src/js/frontend/main.js', './assets/src/css/frontend/main.css'], - admin: [ './assets/src/css/admin/admin.css', './assets/src/js/admin/main.js' ], + admin: [ './assets/src/css/admin/admin.css', './assets/src/js/admin/main.js'], }, output: { ...defaultConfig.output, From 1c9cb066554521962c2fb5a7c4bf9853fdd2c076 Mon Sep 17 00:00:00 2001 From: Mehul Gohil Date: Tue, 8 Jul 2025 19:33:00 +0530 Subject: [PATCH 07/37] added settings header --- assets/src/css/_mixins.css | 3 ++- assets/src/css/admin/settings.css | 23 +++++++++++++++++++ assets/src/js/admin/SettingsApp.jsx | 16 ++++++++----- assets/src/js/admin/SettingsHeader.jsx | 31 ++++++++++++++++++++++++++ config/constants.php | 5 +++++ src/Admin/Actions.php | 10 +++++++++ src/Admin/Settings/Menu.php | 2 +- 7 files changed, 82 insertions(+), 8 deletions(-) create mode 100644 assets/src/js/admin/SettingsHeader.jsx diff --git a/assets/src/css/_mixins.css b/assets/src/css/_mixins.css index 07bcb16..d3560fc 100644 --- a/assets/src/css/_mixins.css +++ b/assets/src/css/_mixins.css @@ -5,4 +5,5 @@ --menu-color: #f0f0f1; --menu-hover-color: #f5f5f5; --text-color: #333333; -} \ No newline at end of file + --white-color: #ffffff; +} diff --git a/assets/src/css/admin/settings.css b/assets/src/css/admin/settings.css index ffa3167..6bccb6b 100644 --- a/assets/src/css/admin/settings.css +++ b/assets/src/css/admin/settings.css @@ -1,3 +1,26 @@ +.perform-settings-page { + margin-left: -20px; +} + +.perform-settings-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 24px; + background-color: var(--white-color); + padding: 0px 20px; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); +} + +.perform-plugin-version { + font-size: 10px; + color: var(--white-color); + background-color: var(--primary-color); + padding: 3px 8px; + border-radius: 5px; +} + + .perform-admin-settings-wrap { display: flex; } diff --git a/assets/src/js/admin/SettingsApp.jsx b/assets/src/js/admin/SettingsApp.jsx index cf9bd52..a1459e3 100644 --- a/assets/src/js/admin/SettingsApp.jsx +++ b/assets/src/js/admin/SettingsApp.jsx @@ -1,13 +1,17 @@ import { Panel, PanelBody, Button } from '@wordpress/components'; import { createElement } from '@wordpress/element'; +import SettingsHeader from './SettingsHeader'; const SettingsApp = () => ( - - -

This is the WordPress-powered settings app. Add your fields here.

- -
-
+ <> + + + +

This is the WordPress-powered settings app. Add your fields here.

+ +
+
+ ); export default SettingsApp; diff --git a/assets/src/js/admin/SettingsHeader.jsx b/assets/src/js/admin/SettingsHeader.jsx new file mode 100644 index 0000000..84ebbcd --- /dev/null +++ b/assets/src/js/admin/SettingsHeader.jsx @@ -0,0 +1,31 @@ +import { Button } from '@wordpress/components'; + +const DOCS_URL = window.performwpSettings?.docsUrl || '#'; +const VERSION = window.performwpSettings?.version || ''; +const LOGO_URL = window.performwpSettings?.logoUrl || ''; + +const SettingsHeader = () => ( +
+ PerformWP +
+ +
{VERSION}
+
+
+); + +export default SettingsHeader; diff --git a/config/constants.php b/config/constants.php index 8d4f849..cd88fd5 100644 --- a/config/constants.php +++ b/config/constants.php @@ -28,3 +28,8 @@ if ( ! defined( 'PERFORM_PLUGIN_URL' ) ) { define( 'PERFORM_PLUGIN_URL', plugin_dir_url( PERFORM_PLUGIN_FILE ) ); } + +// Define plugin docs URL +if ( ! defined( 'PERFORM_PLUGIN_DOCS_URL' ) ) { + define( 'PERFORM_PLUGIN_DOCS_URL', 'https://performwp.com/docs/' ); +} diff --git a/src/Admin/Actions.php b/src/Admin/Actions.php index a4c46bb..72b64b9 100644 --- a/src/Admin/Actions.php +++ b/src/Admin/Actions.php @@ -44,6 +44,16 @@ public function registerAssets() { wp_register_script( 'perform-admin', PERFORM_PLUGIN_URL . 'assets/dist/js/admin.min.js', '', PERFORM_VERSION ); wp_enqueue_script( 'perform-admin' ); + + wp_localize_script( + 'perform-admin', + 'performwpSettings', + [ + 'version' => defined('PERFORM_VERSION') ? PERFORM_VERSION : '', + 'docsUrl' => defined('PERFORM_PLUGIN_DOCS_URL') ? PERFORM_PLUGIN_DOCS_URL : 'https://performwp.com/docs/', + 'logoUrl' => plugins_url( 'assets/dist/images/logo.png', PERFORM_PLUGIN_FILE ), + ] + ); } /** diff --git a/src/Admin/Settings/Menu.php b/src/Admin/Settings/Menu.php index 9a4850f..47c1027 100644 --- a/src/Admin/Settings/Menu.php +++ b/src/Admin/Settings/Menu.php @@ -94,7 +94,7 @@ public function render_settings_page_header() { return; } ?> -
+
<?php esc_attr_e( 'PerformWP', 'perform' ); ?> From b39cf871345fc9ee7266964f7d9625b7a766e94b Mon Sep 17 00:00:00 2001 From: Mehul Gohil Date: Tue, 8 Jul 2025 19:50:08 +0530 Subject: [PATCH 08/37] added settings navigation --- assets/src/css/admin/settings.css | 5 +++++ assets/src/js/admin/SettingsApp.jsx | 23 ++++++++++------------- assets/src/js/admin/SettingsNav.jsx | 24 ++++++++++++++++++++++++ src/Admin/Actions.php | 20 ++++++++++++-------- src/Admin/Settings/Menu.php | 22 +--------------------- src/Includes/Helpers.php | 21 +++++++++++++++++++++ 6 files changed, 73 insertions(+), 42 deletions(-) create mode 100644 assets/src/js/admin/SettingsNav.jsx diff --git a/assets/src/css/admin/settings.css b/assets/src/css/admin/settings.css index 6bccb6b..37d9787 100644 --- a/assets/src/css/admin/settings.css +++ b/assets/src/css/admin/settings.css @@ -20,6 +20,11 @@ border-radius: 5px; } +.perform-settings-tab-panel { + background-color: var(--bg-color); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); + margin-bottom: 20px; +} .perform-admin-settings-wrap { display: flex; diff --git a/assets/src/js/admin/SettingsApp.jsx b/assets/src/js/admin/SettingsApp.jsx index a1459e3..dcf676d 100644 --- a/assets/src/js/admin/SettingsApp.jsx +++ b/assets/src/js/admin/SettingsApp.jsx @@ -1,17 +1,14 @@ -import { Panel, PanelBody, Button } from '@wordpress/components'; -import { createElement } from '@wordpress/element'; import SettingsHeader from './SettingsHeader'; +import SettingsNav from './SettingsNav'; -const SettingsApp = () => ( - <> - - - -

This is the WordPress-powered settings app. Add your fields here.

- -
-
- -); +const SettingsApp = () => { + const tabs = window.performwpSettings?.tabs || {}; + return ( + <> + + + + ); +}; export default SettingsApp; diff --git a/assets/src/js/admin/SettingsNav.jsx b/assets/src/js/admin/SettingsNav.jsx new file mode 100644 index 0000000..93a6e9c --- /dev/null +++ b/assets/src/js/admin/SettingsNav.jsx @@ -0,0 +1,24 @@ +import { TabPanel } from '@wordpress/components'; + +const SettingsNav = ({ tabs = {}, currentTab, onTabChange }) => { + const tabKeys = Object.keys(tabs); + if (!tabKeys.length) return null; + + const tabPanelTabs = tabKeys.map((slug) => ({ + name: slug, + title: tabs[slug], + })); + + return ( + + {() => null} + + ); +}; + +export default SettingsNav; diff --git a/src/Admin/Actions.php b/src/Admin/Actions.php index 72b64b9..cafb265 100644 --- a/src/Admin/Actions.php +++ b/src/Admin/Actions.php @@ -39,6 +39,9 @@ public function __construct() { * @return void */ public function registerAssets() { + // Loads the WordPress components styles. + wp_enqueue_style( 'wp-components' ); + wp_register_style( 'perform-admin', PERFORM_PLUGIN_URL . 'assets/dist/css/admin.css', '', PERFORM_VERSION ); wp_enqueue_style( 'perform-admin' ); @@ -46,14 +49,15 @@ public function registerAssets() { wp_enqueue_script( 'perform-admin' ); wp_localize_script( - 'perform-admin', - 'performwpSettings', - [ - 'version' => defined('PERFORM_VERSION') ? PERFORM_VERSION : '', - 'docsUrl' => defined('PERFORM_PLUGIN_DOCS_URL') ? PERFORM_PLUGIN_DOCS_URL : 'https://performwp.com/docs/', - 'logoUrl' => plugins_url( 'assets/dist/images/logo.png', PERFORM_PLUGIN_FILE ), - ] - ); + 'perform-admin', + 'performwpSettings', + [ + 'version' => defined('PERFORM_VERSION') ? PERFORM_VERSION : '', + 'docsUrl' => defined('PERFORM_PLUGIN_DOCS_URL') ? PERFORM_PLUGIN_DOCS_URL : 'https://performwp.com/docs/', + 'logoUrl' => plugins_url( 'assets/dist/images/logo.png', PERFORM_PLUGIN_FILE ), + 'tabs' => \Perform\Includes\Helpers::get_settings_tabs(), + ] + ); } /** diff --git a/src/Admin/Settings/Menu.php b/src/Admin/Settings/Menu.php index 47c1027..d4fe80e 100644 --- a/src/Admin/Settings/Menu.php +++ b/src/Admin/Settings/Menu.php @@ -34,33 +34,13 @@ class Menu extends Api { */ public function __construct() { $this->prefix = 'perform_'; - $this->tabs = $this->initialize_tabs(); + $this->tabs = Helpers::get_settings_tabs(); add_action( 'admin_menu', [ $this, 'register_admin_menu' ], 9 ); add_action( 'in_admin_header', [ $this, 'render_settings_page_header' ] ); add_action( 'wp_ajax_perform_save_settings', [ $this, 'save_settings' ] ); } - /** - * Initialize tabs for the settings page. - * - * @return array - */ - private function initialize_tabs() { - $tabs = [ - 'general' => esc_html__( 'General', 'perform' ), - 'ssl' => esc_html__( 'SSL', 'perform' ), - 'cdn' => esc_html__( 'CDN', 'perform' ), - 'advanced' => esc_html__( 'Advanced', 'perform' ), - ]; - - if ( Helpers::is_woocommerce_active() ) { - $tabs['woocommerce'] = esc_html__( 'WooCommerce', 'perform' ); - } - - return $tabs; - } - /** * Register Admin Menu * diff --git a/src/Includes/Helpers.php b/src/Includes/Helpers.php index 514da6f..4b5e690 100644 --- a/src/Includes/Helpers.php +++ b/src/Includes/Helpers.php @@ -208,6 +208,27 @@ public static function get_settings() { return get_option( 'perform_settings' ); } + /** + * Get settings tabs for the settings page. + * + * @since 2.0.0 + * @access public + * + * @return array + */ + public static function get_settings_tabs() { + $tabs = [ + 'general' => esc_html__( 'General', 'perform' ), + 'ssl' => esc_html__( 'SSL', 'perform' ), + 'cdn' => esc_html__( 'CDN', 'perform' ), + 'advanced' => esc_html__( 'Advanced', 'perform' ), + ]; + if ( self::is_woocommerce_active() ) { + $tabs['woocommerce'] = esc_html__( 'WooCommerce', 'perform' ); + } + return $tabs; + } + /** * Compress HTML. * From 188b1522e98250bc12b9417e87f73991fcaf503c Mon Sep 17 00:00:00 2001 From: Mehul Gohil Date: Tue, 8 Jul 2025 22:41:47 +0530 Subject: [PATCH 09/37] loading of section and fields --- assets/src/js/admin/Card.jsx | 13 + assets/src/js/admin/SettingsApp.jsx | 3 +- assets/src/js/admin/SettingsNav.jsx | 53 ++- src/Admin/Actions.php | 1 + src/Admin/Settings/Menu.php | 484 ++-------------------------- src/Includes/Helpers.php | 40 +++ 6 files changed, 132 insertions(+), 462 deletions(-) create mode 100644 assets/src/js/admin/Card.jsx diff --git a/assets/src/js/admin/Card.jsx b/assets/src/js/admin/Card.jsx new file mode 100644 index 0000000..a16253e --- /dev/null +++ b/assets/src/js/admin/Card.jsx @@ -0,0 +1,13 @@ +import { Card, CardHeader, CardBody } from '@wordpress/components'; + +const SettingsCard = ({ title, description, children }) => ( + + + {title} + + {description &&

{description}

} + {children} +
+); + +export default SettingsCard; diff --git a/assets/src/js/admin/SettingsApp.jsx b/assets/src/js/admin/SettingsApp.jsx index dcf676d..f839c1f 100644 --- a/assets/src/js/admin/SettingsApp.jsx +++ b/assets/src/js/admin/SettingsApp.jsx @@ -2,11 +2,10 @@ import SettingsHeader from './SettingsHeader'; import SettingsNav from './SettingsNav'; const SettingsApp = () => { - const tabs = window.performwpSettings?.tabs || {}; return ( <> - + ); }; diff --git a/assets/src/js/admin/SettingsNav.jsx b/assets/src/js/admin/SettingsNav.jsx index 93a6e9c..8b81b43 100644 --- a/assets/src/js/admin/SettingsNav.jsx +++ b/assets/src/js/admin/SettingsNav.jsx @@ -1,7 +1,12 @@ -import { TabPanel } from '@wordpress/components'; +import { TabPanel, Card, CardHeader, CardBody } from '@wordpress/components'; +import { useState, useMemo } from '@wordpress/element'; -const SettingsNav = ({ tabs = {}, currentTab, onTabChange }) => { +const SettingsNav = () => { + const tabs = window.performwpSettings?.tabs || {}; + const fields = window.performwpSettings?.fields || {}; const tabKeys = Object.keys(tabs); + const [activeTab, setActiveTab] = useState(tabKeys[0] || ''); + if (!tabKeys.length) return null; const tabPanelTabs = tabKeys.map((slug) => ({ @@ -9,15 +14,43 @@ const SettingsNav = ({ tabs = {}, currentTab, onTabChange }) => { title: tabs[slug], })); + // Memoize cards/sections for the current tab + const cards = useMemo(() => fields[activeTab] || [], [fields, activeTab]); + return ( - - {() => null} - + <> + + {() => null} + +
+ {cards.map((card, idx) => ( + + + {card.title} + + {card.description && ( + +

{card.description}

+
+ )} + {card.fields && card.fields.length > 0 && ( + +
    + {card.fields.map((field, fidx) => ( +
  • {field.name}
  • + ))} +
+
+ )} +
+ ))} +
+ ); }; diff --git a/src/Admin/Actions.php b/src/Admin/Actions.php index cafb265..650f45a 100644 --- a/src/Admin/Actions.php +++ b/src/Admin/Actions.php @@ -56,6 +56,7 @@ public function registerAssets() { 'docsUrl' => defined('PERFORM_PLUGIN_DOCS_URL') ? PERFORM_PLUGIN_DOCS_URL : 'https://performwp.com/docs/', 'logoUrl' => plugins_url( 'assets/dist/images/logo.png', PERFORM_PLUGIN_FILE ), 'tabs' => \Perform\Includes\Helpers::get_settings_tabs(), + 'fields' => \Perform\Includes\Helpers::get_settings_fields(), // Expose fields to JS ] ); } diff --git a/src/Admin/Settings/Menu.php b/src/Admin/Settings/Menu.php index d4fe80e..85613cc 100644 --- a/src/Admin/Settings/Menu.php +++ b/src/Admin/Settings/Menu.php @@ -39,6 +39,7 @@ public function __construct() { add_action( 'admin_menu', [ $this, 'register_admin_menu' ], 9 ); add_action( 'in_admin_header', [ $this, 'render_settings_page_header' ] ); add_action( 'wp_ajax_perform_save_settings', [ $this, 'save_settings' ] ); + add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_assets' ] ); // Added for React settings localization } /** @@ -144,456 +145,7 @@ public function render_settings_page() { * @return void */ public function render_left_section() { - $utm_args = [ - 'utm_source' => 'admin-settings', - 'utm_medium' => 'plugin', - 'utm_campaign' => 'perform', - ]; - - $fields = [ - 'general' => [ - [ - 'id' => 'disable_emojis', - 'type' => 'checkbox', - 'name' => esc_html__( 'Disable Emoji\'s', 'perform' ), - 'desc' => esc_html__( 'Enabling this will disable the usage of emoji\'s in WordPress Posts, Pages, and Custom Post Types.', 'perform' ), - 'help_link' => esc_url( - add_query_arg( - $utm_args, - 'https://performwp.com/docs/disable-emojis' - ) - ), - ], - [ - 'id' => 'disable_embeds', - 'type' => 'checkbox', - 'name' => esc_html__( 'Disable Embeds', 'perform' ), - 'desc' => esc_html__( 'Removes WordPress Embed JavaScript file (wp-embed.min.js).', 'perform' ), - 'help_link' => esc_url( - add_query_arg( - $utm_args, - 'https://performwp.com/docs/disable-embeds' - ) - ), - ], - [ - 'id' => 'remove_query_strings', - 'type' => 'checkbox', - 'name' => esc_html__( 'Remove Query Strings', 'perform' ), - 'desc' => esc_html__( 'Remove query strings from static resources (CSS, JS).', 'perform' ), - 'help_link' => esc_url( - add_query_arg( - $utm_args, - 'https://performwp.com/docs/remove-query-strings' - ) - ), - ], - [ - 'id' => 'disable_xmlrpc', - 'type' => 'checkbox', - 'name' => esc_html__( 'Disable XML-RPC', 'perform' ), - 'desc' => esc_html__( 'Disables WordPress XML-RPC functionality.', 'perform' ), - 'help_link' => esc_url( - add_query_arg( - $utm_args, - 'https://performwp.com/docs/disable-xmlrpc' - ) - ), - ], - [ - 'id' => 'remove_jquery_migrate', - 'type' => 'checkbox', - 'name' => esc_html__( 'Remove jQuery Migrate', 'perform' ), - 'desc' => esc_html__( 'Removes jQuery Migrate JS file (jquery-migrate.min.js).', 'perform' ), - 'help_link' => esc_url( - add_query_arg( - $utm_args, - 'https://performwp.com/docs/remove-jquery-migrate' - ) - ), - ], - [ - 'id' => 'hide_wp_version', - 'type' => 'checkbox', - 'name' => esc_html__( 'Hide WP Version', 'perform' ), - 'desc' => esc_html__( 'Removes WordPress version generator meta tag.', 'perform' ), - 'help_link' => esc_url( - add_query_arg( - $utm_args, - 'https://performwp.com/docs/hide-wp-version' - ) - ), - ], - [ - 'id' => 'remove_wlwmanifest_link', - 'type' => 'checkbox', - 'name' => esc_html__( 'Remove wlwmanifest Link', 'perform' ), - 'desc' => esc_html__( 'Remove wlwmanifest link tag. It is usually used to support Windows Live Writer.', 'perform' ), - 'help_link' => esc_url( - add_query_arg( - $utm_args, - 'https://performwp.com/docs/remove-wlwmanifest-link' - ) - ), - ], - [ - 'id' => 'remove_rsd_link', - 'type' => 'checkbox', - 'name' => esc_html__( 'Remove RSD Link', 'perform' ), - 'desc' => esc_html__( 'Remove RSD (Real Simple Discovery) link tag.', 'perform' ), - 'help_link' => esc_url( - add_query_arg( - $utm_args, - 'https://performwp.com/docs/remove-rsd-link' - ) - ), - ], - [ - 'id' => 'remove_shortlink', - 'type' => 'checkbox', - 'name' => esc_html__( 'Remove Shortlink', 'perform' ), - 'desc' => esc_html__( 'Remove Shortlink link tag.', 'perform' ), - 'help_link' => esc_url( - add_query_arg( - $utm_args, - 'https://performwp.com/docs/remove-shortlink' - ) - ), - ], - [ - 'id' => 'disable_rss_feeds', - 'type' => 'checkbox', - 'name' => esc_html__( 'Disable RSS Feeds', 'perform' ), - 'desc' => esc_html__( 'Disable WordPress generated RSS feeds and 301 redirect URL to parent.', 'perform' ), - 'help_link' => esc_url( - add_query_arg( - $utm_args, - 'https://performwp.com/docs/disable-rss-feeds' - ) - ), - ], - [ - 'id' => 'remove_feed_links', - 'type' => 'checkbox', - 'name' => esc_html__( 'Remove RSS Feed Links', 'perform' ), - 'desc' => esc_html__( 'Disable WordPress generated RSS feed link tags.', 'perform' ), - 'help_link' => esc_url( - add_query_arg( - $utm_args, - 'https://performwp.com/docs/remove-rss-feed-links' - ) - ), - ], - [ - 'id' => 'remove_rest_api_links', - 'type' => 'checkbox', - 'name' => esc_html__( 'Remove REST API Links', 'perform' ), - 'desc' => esc_html__( 'Removes REST API link tag from the front end and the REST API header link from page requests.', 'perform' ), - 'help_link' => esc_url( - add_query_arg( - $utm_args, - 'https://performwp.com/docs/remove-rest-api-links' - ) - ), - ], - [ - 'id' => 'disable_self_pingbacks', - 'type' => 'checkbox', - 'name' => esc_html__( 'Disable Self Pingbacks', 'perform' ), - 'desc' => esc_html__( 'Disable Self Pingbacks (generated when linking to an article on your own blog).', 'perform' ), - 'help_link' => esc_url( - add_query_arg( - $utm_args, - 'https://performwp.com/docs/disable-self-pingbacks' - ) - ), - ], - [ - 'id' => 'disable_dashicons', - 'type' => 'checkbox', - 'name' => esc_html__( 'Disable Dashicons', 'perform' ), - 'desc' => esc_html__( 'Disables dashicons js on the front end when not logged in.', 'perform' ), - 'help_link' => esc_url( - add_query_arg( - $utm_args, - 'https://performwp.com/docs/disable-dashicons' - ) - ), - ], - [ - 'id' => 'disable_password_strength_meter', - 'type' => 'checkbox', - 'name' => esc_html__( 'Disable Password Strength Meter', 'perform' ), - 'desc' => esc_html__( 'Removes WordPress and WooCommerce Password Strength Meter scripts from non essential pages.', 'perform' ), - 'help_link' => esc_url( - add_query_arg( - $utm_args, - 'https://performwp.com/docs/disable-password-strength-meter' - ) - ), - ], - [ - 'id' => 'disable_heartbeat', - 'type' => 'select', - 'name' => esc_html__( 'Disable Heartbeat', 'perform' ), - 'options' => [ - '' => esc_html__( 'Default', 'perform' ), - 'disable_everywhere' => esc_html__( 'Disable Everywhere', 'perform' ), - 'allow_posts' => esc_html__( 'Only Allow When Editing Posts/Pages', 'perform' ), - ], - 'desc' => esc_html__( 'Disable WordPress Heartbeat everywhere or in certain areas (used for auto saving and revision tracking).', 'perform' ), - 'help_link' => esc_url( - add_query_arg( - $utm_args, - 'https://performwp.com/docs/disable-heartbeat' - ) - ), - ], - [ - 'id' => 'heartbeat_frequency', - 'type' => 'select', - 'name' => esc_html__( 'Heartbeat Frequency', 'perform' ), - 'options' => [ - '' => sprintf( esc_html__( '%s Seconds', 'perform' ), '15' ) . ' (' . esc_html__( 'Default', 'perform' ) . ')', - '30' => sprintf( esc_html__( '%s Seconds', 'perform' ), '30' ), - '45' => sprintf( esc_html__( '%s Seconds', 'perform' ), '45' ), - '60' => sprintf( esc_html__( '%s Seconds', 'perform' ), '60' ), - ], - 'desc' => esc_html__( 'Disable WordPress Heartbeat everywhere or in certain areas (used for auto saving and revision tracking).', 'perform' ), - 'help_link' => esc_url( - add_query_arg( - $utm_args, - 'https://performwp.com/docs/disable-heartbeat' - ) - ), - ], - [ - 'id' => 'limit_post_revisions', - 'type' => 'select', - 'name' => esc_html__( 'Limit Post Revisions', 'perform' ), - 'options' => [ - '' => esc_html__( 'Default', 'perform' ), - 'false' => esc_html__( 'Disable Post Revisions', 'perform' ), - '1' => '1', - '2' => '2', - '3' => '3', - '4' => '4', - '5' => '5', - '10' => '10', - '15' => '15', - '20' => '20', - '25' => '25', - '30' => '30', - ], - 'desc' => esc_html__( 'Limits the maximum amount of revisions that are allowed for posts and pages.', 'perform' ), - 'help_link' => esc_url( - add_query_arg( - $utm_args, - 'https://performwp.com/docs/limit-post-revisions' - ) - ), - ], - [ - 'id' => 'autosave_interval', - 'type' => 'select', - 'name' => esc_html__( 'Autosave Interval', 'perform' ), - 'options' => [ - '' => esc_html__( '1 Minute', 'perform' ) . ' (' . esc_html__( 'Default', 'perform' ) . ')', - '120' => sprintf( esc_html__( '%s Minutes', 'perform' ), '2' ), - '180' => sprintf( esc_html__( '%s Minutes', 'perform' ), '3' ), - '240' => sprintf( esc_html__( '%s Minutes', 'perform' ), '4' ), - '300' => sprintf( esc_html__( '%s Minutes', 'perform' ), '5' ), - ], - 'desc' => esc_html__( 'Controls how often WordPress will auto save posts and pages while editing.', 'perform' ), - 'help_link' => esc_url( - add_query_arg( - $utm_args, - 'https://performwp.com/docs/autosave-intervals' - ) - ), - ], - ], - 'ssl' => [ - [ - 'id' => 'enable_ssl', - 'type' => 'checkbox', - 'name' => esc_html__( 'Enable SSL', 'perform' ), - 'desc' => esc_html__( 'Enabling this setting will let you automatically redirect visitors to the SSL enabled URL of your website.', 'perform' ), - 'help_link' => esc_url( - add_query_arg( - $utm_args, - 'https://performwp.com/docs/auto-ssl-redirect' - ) - ), - ], - ], - 'cdn' => [ - [ - 'id' => 'enable_cdn', - 'type' => 'checkbox', - 'name' => esc_html__( 'Enable CDN Rewrite', 'perform' ), - 'desc' => esc_html__( 'Enables rewriting of your site URLs with your CDN URLs which can be configured below.', 'perform' ), - 'help_link' => esc_url( - add_query_arg( - $utm_args, - 'https://performwp.com/docs/cdn-rewrite' - ) - ), - ], - [ - 'id' => 'cdn_url', - 'type' => 'url', - 'name' => esc_html__( 'CDN URL', 'perform' ), - 'desc' => esc_html__( 'Enter your CDN URL without the trailing backslash. Example: https://cdn.example.com', 'perform' ), - 'help_link' => esc_url( - add_query_arg( - $utm_args, - 'https://performwp.com/docs/cdn-rewrite' - ) - ), - ], - [ - 'id' => 'cdn_directories', - 'type' => 'text', - 'placeholder' => 'wp-content, wp-includes', - 'name' => esc_html__( 'Included Directories', 'perform' ), - 'desc' => esc_html__( 'Enter any directories you would like to be included in CDN rewriting, separated by commas (,). Default: wp-content,wp-includes', 'perform' ), - 'help_link' => esc_url( - add_query_arg( - $utm_args, - 'https://performwp.com/docs/cdn-rewrite' - ) - ), - ], - [ - 'id' => 'cdn_exclusions', - 'type' => 'text', - 'placeholder' => '.php', - 'name' => esc_html__( 'CDN Exclusions', 'perform' ), - 'desc' => esc_html__( 'Enter any directories or file extensions you would like to be excluded from CDN rewriting, separated by commas (,). Default: .php', 'perform' ), - 'help_link' => esc_url( - add_query_arg( - $utm_args, - 'https://performwp.com/docs/cdn-rewrite' - ) - ), - ], - ], - 'advanced' => [ - [ - 'id' => 'enable_navigation_menu_cache', - 'type' => 'checkbox', - 'name' => esc_html__( 'Enable Menu Cache', 'perform' ), - 'desc' => esc_html__( 'Enables the Navigation Menu Cache which will provide you the ability to cache all the menus on your WordPress site to reduce the time taken by outputting the menu\'s.', 'perform' ), - 'help_link' => esc_url( - add_query_arg( - $utm_args, - 'https://performwp.com/docs/navigation-menu-cache' - ) - ), - ], - [ - 'id' => 'enable_assets_manager', - 'type' => 'checkbox', - 'name' => esc_html__( 'Enable Assets Manager', 'perform' ), - 'desc' => esc_html__( 'Enables the Assets Manager which will provide you the ability to enable or disable CSS and JS files on per-page basis.', 'perform' ), - 'help_link' => esc_url( - add_query_arg( - $utm_args, - 'https://performwp.com/docs/assets-manager' - ) - ), - ], - [ - 'id' => 'dns_prefetch', - 'type' => 'textarea', - 'data_type' => 'one_per_line', - 'name' => esc_html__( 'DNS Prefetch', 'perform' ), - 'desc' => esc_html__( 'Resolve domain names before a user clicks. Format: //domain.tld (one per line)', 'perform' ), - 'help_link' => esc_url( - add_query_arg( - $utm_args, - 'https://performwp.com/docs/dns-prefetch' - ) - ), - ], - [ - 'id' => 'preconnect', - 'type' => 'textarea', - 'name' => esc_html__( 'Preconnect', 'perform' ), - 'desc' => esc_html__( 'Preconnect allows the browser to set up early connections before an HTTP request, eliminating roundtrip latency and saving time for users. Format: scheme://domain.tld (one per line)', 'perform' ), - 'help_link' => esc_url( - add_query_arg( - $utm_args, - 'https://performwp.com/docs/preconnect' - ) - ), - ], - [ - 'id' => 'remove_data_on_uninstall', - 'type' => 'checkbox', - 'name' => esc_html__( 'Remove Data on Uninstall', 'perform' ), - 'desc' => esc_html__( 'When enabled, this will cause all the options data to be removed from your database when the plugin is uninstalled.', 'perform' ), - 'help_link' => esc_url( - add_query_arg( - $utm_args, - 'https://performwp.com/docs/clean-uninstall' - ) - ), - ], - ], - 'woocommerce' => [ - [ - 'id' => 'disable_woocommerce_assets', - 'type' => 'checkbox', - 'name' => esc_html__( 'Disable Default Assets', 'perform' ), - 'desc' => esc_html__( 'Disables WooCommerce default scripts and styles except on product, cart, and checkout pages.', 'perform' ), - 'help_link' => esc_url( - add_query_arg( - $utm_args, - 'https://performwp.com/docs/disable-woocommerce-assets' - ) - ), - ], - [ - 'id' => 'disable_woocommerce_cart_fragmentation', - 'type' => 'checkbox', - 'name' => esc_html__( 'Disable Cart Fragmentation', 'perform' ), - 'desc' => esc_html__( 'Completely disables WooCommerce cart fragmentation script.', 'perform' ), - 'help_link' => esc_url( - add_query_arg( - $utm_args, - 'https://performwp.com/docs/disable-woocommerce-cart-fragmentation' - ) - ), - ], - [ - 'id' => 'disable_woocommerce_status', - 'type' => 'checkbox', - 'name' => esc_html__( 'Disable Status Meta-box', 'perform' ), - 'desc' => esc_html__( 'Disables WooCommerce status meta-box from the WP Admin Dashboard.', 'perform' ), - 'help_link' => esc_url( - add_query_arg( - $utm_args, - 'https://performwp.com/docs/disable-woocommerce-status' - ) - ), - ], - [ - 'id' => 'disable_woocommerce_widgets', - 'type' => 'checkbox', - 'name' => esc_html__( 'Disable Widgets', 'perform' ), - 'desc' => esc_html__( 'Disables all WooCommerce widgets.', 'perform' ), - 'help_link' => esc_url( - add_query_arg( - $utm_args, - 'https://performwp.com/docs/disable-widgets' - ) - ), - ], - ], - - ]; + $fields = Helpers::get_settings_fields(); $this->render_fields( $fields ); } @@ -622,6 +174,38 @@ public function render_right_section() { echo ob_get_clean(); } + /** + * Enqueue admin assets and localize settings data for React app. + */ + public function enqueue_admin_assets() { + $screen = get_current_screen(); + if ( 'settings_page_perform_settings' !== $screen->id ) { + return; + } + + // Enqueue your React app script here if not already done. + wp_enqueue_script( + 'perform-admin-settings', + PERFORM_PLUGIN_URL . 'assets/dist/js/admin-settings.js', + [ 'wp-element', 'wp-components', 'wp-i18n' ], + PERFORM_VERSION, + true + ); + + // Localize settings data, including fields. + wp_localize_script( + 'perform-admin-settings', + 'performwpSettings', + [ + 'version' => PERFORM_VERSION, + 'docsUrl' => 'https://performwp.com/docs/', + 'logoUrl' => PERFORM_PLUGIN_URL . 'assets/dist/images/logo.png', + 'tabs' => Helpers::get_settings_tabs(), + 'fields' => Helpers::get_settings_fields(), // Expose fields here + ] + ); + } + /** * Save Admin Settings. * diff --git a/src/Includes/Helpers.php b/src/Includes/Helpers.php index 4b5e690..8ceee2c 100644 --- a/src/Includes/Helpers.php +++ b/src/Includes/Helpers.php @@ -229,6 +229,46 @@ public static function get_settings_tabs() { return $tabs; } + /** + * Get settings fields for the settings page, grouped by tab and card. + * + * @since 2.0.0 + * @access public + * + * @return array + */ + public static function get_settings_fields() { + $utm_args = [ + 'utm_source' => 'admin-settings', + 'utm_medium' => 'plugin', + 'utm_campaign' => 'perform', + ]; + return [ + 'general' => [ + [ + 'title' => esc_html__('General Settings', 'perform'), + 'description' => esc_html__('General performance and cleanup options.', 'perform'), + 'fields' => [ + [ + 'id' => 'disable_emojis', + 'type' => 'checkbox', + 'name' => esc_html__( 'Disable Emoji\'s', 'perform' ), + 'desc' => esc_html__( 'Enabling this will disable the usage of emoji\'s in WordPress Posts, Pages, and Custom Post Types.', 'perform' ), + 'help_link' => esc_url( + add_query_arg( + $utm_args, + 'https://performwp.com/docs/disable-emojis' + ) + ), + ], + // ...existing general fields... + ] + ] + ], + // ...other tabs (ssl, cdn, advanced, woocommerce) with similar card structure... + ]; + } + /** * Compress HTML. * From d749b53067966998bc11c4e85f42235335df8718 Mon Sep 17 00:00:00 2001 From: Mehul Gohil Date: Tue, 8 Jul 2025 23:05:20 +0530 Subject: [PATCH 10/37] show fields with relevant components --- assets/src/js/admin/SettingsNav.jsx | 42 +++++++-- src/Includes/Helpers.php | 141 ++++++++++++++++++++++++++-- 2 files changed, 170 insertions(+), 13 deletions(-) diff --git a/assets/src/js/admin/SettingsNav.jsx b/assets/src/js/admin/SettingsNav.jsx index 8b81b43..a1025ce 100644 --- a/assets/src/js/admin/SettingsNav.jsx +++ b/assets/src/js/admin/SettingsNav.jsx @@ -1,11 +1,39 @@ -import { TabPanel, Card, CardHeader, CardBody } from '@wordpress/components'; +import { TabPanel, Card, CardHeader, CardBody, ToggleControl, TextControl } from '@wordpress/components'; import { useState, useMemo } from '@wordpress/element'; +const FIELD_COMPONENTS = { + toggle: ToggleControl, + text: TextControl, +}; + +const renderField = (field, value, onChange) => { + const { type, id, name, desc, ...rest } = field; + const Component = FIELD_COMPONENTS[type] || null; + if (!Component) return
Unsupported field type: {type}
; + + // Map props for each field type + const props = { + key: id, + label: name, + help: desc, + value: value, + onChange: (val) => onChange(id, val), + ...rest, + }; + if (type === 'toggle') { + props.checked = !!value; + delete props.value; + props.onChange = (checked) => onChange(id, checked); + } + return ; +}; + const SettingsNav = () => { const tabs = window.performwpSettings?.tabs || {}; const fields = window.performwpSettings?.fields || {}; const tabKeys = Object.keys(tabs); const [activeTab, setActiveTab] = useState(tabKeys[0] || ''); + const [fieldValues, setFieldValues] = useState({}); if (!tabKeys.length) return null; @@ -17,6 +45,10 @@ const SettingsNav = () => { // Memoize cards/sections for the current tab const cards = useMemo(() => fields[activeTab] || [], [fields, activeTab]); + const handleFieldChange = (id, value) => { + setFieldValues((prev) => ({ ...prev, [id]: value })); + }; + return ( <> { )} {card.fields && card.fields.length > 0 && ( -
    - {card.fields.map((field, fidx) => ( -
  • {field.name}
  • - ))} -
+ {card.fields.map((field) => + renderField(field, fieldValues[field.id], handleFieldChange) + )}
)} diff --git a/src/Includes/Helpers.php b/src/Includes/Helpers.php index 8ceee2c..e4219f7 100644 --- a/src/Includes/Helpers.php +++ b/src/Includes/Helpers.php @@ -218,7 +218,7 @@ public static function get_settings() { */ public static function get_settings_tabs() { $tabs = [ - 'general' => esc_html__( 'General', 'perform' ), + 'bloat' => esc_html__( 'Bloat', 'perform' ), 'ssl' => esc_html__( 'SSL', 'perform' ), 'cdn' => esc_html__( 'CDN', 'perform' ), 'advanced' => esc_html__( 'Advanced', 'perform' ), @@ -244,14 +244,14 @@ public static function get_settings_fields() { 'utm_campaign' => 'perform', ]; return [ - 'general' => [ + 'bloat' => [ [ - 'title' => esc_html__('General Settings', 'perform'), - 'description' => esc_html__('General performance and cleanup options.', 'perform'), + 'title' => esc_html__('Bloat Settings', 'perform'), + 'description' => esc_html__('Settings to reduce bloat and improve performance.', 'perform'), 'fields' => [ [ 'id' => 'disable_emojis', - 'type' => 'checkbox', + 'type' => 'toggle', 'name' => esc_html__( 'Disable Emoji\'s', 'perform' ), 'desc' => esc_html__( 'Enabling this will disable the usage of emoji\'s in WordPress Posts, Pages, and Custom Post Types.', 'perform' ), 'help_link' => esc_url( @@ -261,11 +261,138 @@ public static function get_settings_fields() { ) ), ], - // ...existing general fields... + [ + 'id' => 'disable_embeds', + 'type' => 'toggle', + 'name' => esc_html__( 'Disable Embeds', 'perform' ), + 'desc' => esc_html__( 'Enabling this will disable the usage of embeds in WordPress Posts, Pages, and Custom Post Types.', 'perform' ), + 'help_link' => esc_url( + add_query_arg( + $utm_args, + 'https://performwp.com/docs/disable-embeds' + ) + ), + ], + + ] + ] + ], + 'ssl' => [ + [ + 'title' => esc_html__('SSL Settings', 'perform'), + 'description' => esc_html__('Settings to manage SSL and HTTPS related configurations.', 'perform'), + 'fields' => [ + [ + 'id' => 'force_ssl', + 'type' => 'toggle', + 'name' => esc_html__( 'Force SSL', 'perform' ), + 'desc' => esc_html__( 'Enabling this will force all traffic to use SSL.', 'perform' ), + 'help_link' => esc_url( + add_query_arg( + $utm_args, + 'https://performwp.com/docs/force-ssl' + ) + ), + ], + ] + ] + ], + 'cdn' => [ + [ + 'title' => esc_html__('CDN Settings', 'perform'), + 'description' => esc_html__('Settings to manage CDN configurations.', 'perform'), + 'fields' => [ + [ + 'id' => 'enable_cdn', + 'type' => 'toggle', + 'name' => esc_html__( 'Enable CDN', 'perform' ), + 'desc' => esc_html__( 'Enabling this will allow you to use a CDN for your static assets.', 'perform' ), + 'help_link' => esc_url( + add_query_arg( + $utm_args, + 'https://performwp.com/docs/cdn-manager' + ) + ), + ], + [ + 'id' => 'cdn_url', + 'type' => 'text', + 'name' => esc_html__( 'CDN URL', 'perform' ), + 'desc' => esc_html__( 'Enter the URL of your CDN provider.', 'perform' ), + 'help_link' => esc_url( + add_query_arg( + $utm_args, + 'https://performwp.com/docs/cdn-manager' + ) + ), + ], + ] + ] + ], + 'advanced' => [ + [ + 'title' => esc_html__('Advanced Settings', 'perform'), + 'description' => esc_html__('Settings for advanced configurations.', 'perform'), + 'fields' => [ + [ + 'id' => 'enable_assets_manager', + 'type' => 'toggle', + 'name' => esc_html__( 'Enable Assets Manager', 'perform' ), + 'desc' => esc_html__( 'Enabling this will allow you to manage your assets more effectively.', 'perform' ), + 'help_link' => esc_url( + add_query_arg( + $utm_args, + 'https://performwp.com/docs/assets-manager' + ) + ), + ], + [ + 'id' => 'enable_menu_cache', + 'type' => 'toggle', + 'name' => esc_html__( 'Enable Menu Cache', 'perform' ), + 'desc' => esc_html__( 'Enabling this will cache your menu items for better performance.', 'perform' ), + 'help_link' => esc_url( + add_query_arg( + $utm_args, + 'https://performwp.com/docs/menu-cache' + ) + ), + ], + ] + ] + ], + 'woocommerce' => [ + [ + 'title' => esc_html__('WooCommerce Settings', 'perform'), + 'description' => esc_html__('Settings specific to WooCommerce.', 'perform'), + 'fields' => [ + [ + 'id' => 'enable_woocommerce_manager', + 'type' => 'toggle', + 'name' => esc_html__( 'Enable WooCommerce Manager', 'perform' ), + 'desc' => esc_html__( 'Enabling this will allow you to manage WooCommerce specific settings.', 'perform' ), + 'help_link' => esc_url( + add_query_arg( + $utm_args, + 'https://performwp.com/docs/woocommerce-manager' + ) + ), + ], + [ + 'id' => 'woocommerce_cache', + 'type' => 'toggle', + 'name' => esc_html__( 'Enable WooCommerce Cache', 'perform' ), + 'desc' => esc_html__( 'Enabling this will cache WooCommerce pages for better performance.', 'perform' ), + 'help_link' => esc_url( + add_query_arg( + $utm_args, + 'https://performwp.com/docs/woocommerce-cache' + ) + ), + ], ] ] ], - // ...other tabs (ssl, cdn, advanced, woocommerce) with similar card structure... ]; } From 0b2e42e39a1b49ce4eecc4b11721fbc87315dc4a Mon Sep 17 00:00:00 2001 From: Mehul Gohil Date: Wed, 9 Jul 2025 15:17:06 +0530 Subject: [PATCH 11/37] added support for some more settings --- assets/src/css/admin/settings.css | 7 + assets/src/js/admin/SettingsNav.jsx | 12 +- src/Includes/Helpers.php | 250 +++++++++++++++++++++++++++- 3 files changed, 258 insertions(+), 11 deletions(-) diff --git a/assets/src/css/admin/settings.css b/assets/src/css/admin/settings.css index 37d9787..0d047cf 100644 --- a/assets/src/css/admin/settings.css +++ b/assets/src/css/admin/settings.css @@ -26,6 +26,13 @@ margin-bottom: 20px; } + +.perform-card-title, +.perform-card-description { + margin: 0; +} + + .perform-admin-settings-wrap { display: flex; } diff --git a/assets/src/js/admin/SettingsNav.jsx b/assets/src/js/admin/SettingsNav.jsx index a1025ce..ac59646 100644 --- a/assets/src/js/admin/SettingsNav.jsx +++ b/assets/src/js/admin/SettingsNav.jsx @@ -62,14 +62,12 @@ const SettingsNav = () => {
{cards.map((card, idx) => ( - - {card.title} + +

{card.title}

+ {card.description && ( +

{card.description}

+ )}
- {card.description && ( - -

{card.description}

-
- )} {card.fields && card.fields.length > 0 && ( {card.fields.map((field) => diff --git a/src/Includes/Helpers.php b/src/Includes/Helpers.php index e4219f7..0fac7d0 100644 --- a/src/Includes/Helpers.php +++ b/src/Includes/Helpers.php @@ -246,14 +246,14 @@ public static function get_settings_fields() { return [ 'bloat' => [ [ - 'title' => esc_html__('Bloat Settings', 'perform'), - 'description' => esc_html__('Settings to reduce bloat and improve performance.', 'perform'), + 'title' => esc_html__('Assets Optimization', 'perform'), + 'description' => esc_html__('Fine-tune how WordPress loads scripts and assets across your site. Disabling unnecessary features reduces HTTP requests, removes bloat, and improves load times for your visitors.', 'perform'), 'fields' => [ [ 'id' => 'disable_emojis', 'type' => 'toggle', - 'name' => esc_html__( 'Disable Emoji\'s', 'perform' ), - 'desc' => esc_html__( 'Enabling this will disable the usage of emoji\'s in WordPress Posts, Pages, and Custom Post Types.', 'perform' ), + 'name' => __( 'Disable Emoji\'s', 'perform' ), + 'desc' => __( 'Enabling this will disable the usage of emoji\'s in WordPress Posts, Pages, and Custom Post Types.', 'perform' ), 'help_link' => esc_url( add_query_arg( $utm_args, @@ -273,6 +273,248 @@ public static function get_settings_fields() { ) ), ], + ], + ], + [ + 'title' => esc_html__('Bloat Settings', 'perform'), + 'description' => esc_html__('Settings to reduce bloat and improve performance.', 'perform'), + 'fields' => [ + [ + 'id' => 'remove_query_strings', + 'type' => 'toggle', + 'name' => esc_html__( 'Remove Query Strings', 'perform' ), + 'desc' => esc_html__( 'Remove query strings from static resources (CSS, JS).', 'perform' ), + 'help_link' => esc_url( + add_query_arg( + $utm_args, + 'https://performwp.com/docs/remove-query-strings' + ) + ), + ], + [ + 'id' => 'disable_xmlrpc', + 'type' => 'toggle', + 'name' => esc_html__( 'Disable XML-RPC', 'perform' ), + 'desc' => esc_html__( 'Disables WordPress XML-RPC functionality.', 'perform' ), + 'help_link' => esc_url( + add_query_arg( + $utm_args, + 'https://performwp.com/docs/disable-xmlrpc' + ) + ), + ], + [ + 'id' => 'remove_jquery_migrate', + 'type' => 'toggle', + 'name' => esc_html__( 'Remove jQuery Migrate', 'perform' ), + 'desc' => esc_html__( 'Removes jQuery Migrate JS file (jquery-migrate.min.js).', 'perform' ), + 'help_link' => esc_url( + add_query_arg( + $utm_args, + 'https://performwp.com/docs/remove-jquery-migrate' + ) + ), + ], + [ + 'id' => 'hide_wp_version', + 'type' => 'toggle', + 'name' => esc_html__( 'Hide WP Version', 'perform' ), + 'desc' => esc_html__( 'Removes WordPress version generator meta tag.', 'perform' ), + 'help_link' => esc_url( + add_query_arg( + $utm_args, + 'https://performwp.com/docs/hide-wp-version' + ) + ), + ], + [ + 'id' => 'remove_wlwmanifest_link', + 'type' => 'toggle', + 'name' => esc_html__( 'Remove wlwmanifest Link', 'perform' ), + 'desc' => esc_html__( 'Remove wlwmanifest link tag. It is usually used to support Windows Live Writer.', 'perform' ), + 'help_link' => esc_url( + add_query_arg( + $utm_args, + 'https://performwp.com/docs/remove-wlwmanifest-link' + ) + ), + ], + [ + 'id' => 'remove_rsd_link', + 'type' => 'toggle', + 'name' => esc_html__( 'Remove RSD Link', 'perform' ), + 'desc' => esc_html__( 'Remove RSD (Real Simple Discovery) link tag.', 'perform' ), + 'help_link' => esc_url( + add_query_arg( + $utm_args, + 'https://performwp.com/docs/remove-rsd-link' + ) + ), + ], + [ + 'id' => 'remove_shortlink', + 'type' => 'toggle', + 'name' => esc_html__( 'Remove Shortlink', 'perform' ), + 'desc' => esc_html__( 'Remove Shortlink link tag.', 'perform' ), + 'help_link' => esc_url( + add_query_arg( + $utm_args, + 'https://performwp.com/docs/remove-shortlink' + ) + ), + ], + [ + 'id' => 'disable_rss_feeds', + 'type' => 'toggle', + 'name' => esc_html__( 'Disable RSS Feeds', 'perform' ), + 'desc' => esc_html__( 'Disable WordPress generated RSS feeds and 301 redirect URL to parent.', 'perform' ), + 'help_link' => esc_url( + add_query_arg( + $utm_args, + 'https://performwp.com/docs/disable-rss-feeds' + ) + ), + ], + [ + 'id' => 'remove_feed_links', + 'type' => 'toggle', + 'name' => esc_html__( 'Remove RSS Feed Links', 'perform' ), + 'desc' => esc_html__( 'Disable WordPress generated RSS feed link tags.', 'perform' ), + 'help_link' => esc_url( + add_query_arg( + $utm_args, + 'https://performwp.com/docs/remove-rss-feed-links' + ) + ), + ], + [ + 'id' => 'remove_rest_api_links', + 'type' => 'toggle', + 'name' => esc_html__( 'Remove REST API Links', 'perform' ), + 'desc' => esc_html__( 'Removes REST API link tag from the front end and the REST API header link from page requests.', 'perform' ), + 'help_link' => esc_url( + add_query_arg( + $utm_args, + 'https://performwp.com/docs/remove-rest-api-links' + ) + ), + ], + [ + 'id' => 'disable_self_pingbacks', + 'type' => 'toggle', + 'name' => esc_html__( 'Disable Self Pingbacks', 'perform' ), + 'desc' => esc_html__( 'Disable Self Pingbacks (generated when linking to an article on your own blog).', 'perform' ), + 'help_link' => esc_url( + add_query_arg( + $utm_args, + 'https://performwp.com/docs/disable-self-pingbacks' + ) + ), + ], + [ + 'id' => 'disable_dashicons', + 'type' => 'toggle', + 'name' => esc_html__( 'Disable Dashicons', 'perform' ), + 'desc' => esc_html__( 'Disables dashicons js on the front end when not logged in.', 'perform' ), + 'help_link' => esc_url( + add_query_arg( + $utm_args, + 'https://performwp.com/docs/disable-dashicons' + ) + ), + ], + [ + 'id' => 'disable_password_strength_meter', + 'type' => 'toggle', + 'name' => esc_html__( 'Disable Password Strength Meter', 'perform' ), + 'desc' => esc_html__( 'Removes WordPress and WooCommerce Password Strength Meter scripts from non essential pages.', 'perform' ), + 'help_link' => esc_url( + add_query_arg( + $utm_args, + 'https://performwp.com/docs/disable-password-strength-meter' + ) + ), + ], + [ + 'id' => 'disable_heartbeat', + 'type' => 'select', + 'name' => esc_html__( 'Disable Heartbeat', 'perform' ), + 'options' => [ + '' => esc_html__( 'Default', 'perform' ), + 'disable_everywhere' => esc_html__( 'Disable Everywhere', 'perform' ), + 'allow_posts' => esc_html__( 'Only Allow When Editing Posts/Pages', 'perform' ), + ], + 'desc' => esc_html__( 'Disable WordPress Heartbeat everywhere or in certain areas (used for auto saving and revision tracking).', 'perform' ), + 'help_link' => esc_url( + add_query_arg( + $utm_args, + 'https://performwp.com/docs/disable-heartbeat' + ) + ), + ], + [ + 'id' => 'heartbeat_frequency', + 'type' => 'select', + 'name' => esc_html__( 'Heartbeat Frequency', 'perform' ), + 'options' => [ + '' => sprintf( esc_html__( '%s Seconds', 'perform' ), '15' ) . ' (' . esc_html__( 'Default', 'perform' ) . ')', + '30' => sprintf( esc_html__( '%s Seconds', 'perform' ), '30' ), + '45' => sprintf( esc_html__( '%s Seconds', 'perform' ), '45' ), + '60' => sprintf( esc_html__( '%s Seconds', 'perform' ), '60' ), + ], + 'desc' => esc_html__( 'Disable WordPress Heartbeat everywhere or in certain areas (used for auto saving and revision tracking).', 'perform' ), + 'help_link' => esc_url( + add_query_arg( + $utm_args, + 'https://performwp.com/docs/disable-heartbeat' + ) + ), + ], + [ + 'id' => 'limit_post_revisions', + 'type' => 'select', + 'name' => esc_html__( 'Limit Post Revisions', 'perform' ), + 'options' => [ + '' => esc_html__( 'Default', 'perform' ), + 'false' => esc_html__( 'Disable Post Revisions', 'perform' ), + '1' => '1', + '2' => '2', + '3' => '3', + '4' => '4', + '5' => '5', + '10' => '10', + '15' => '15', + '20' => '20', + '25' => '25', + '30' => '30', + ], + 'desc' => esc_html__( 'Limits the maximum amount of revisions that are allowed for posts and pages.', 'perform' ), + 'help_link' => esc_url( + add_query_arg( + $utm_args, + 'https://performwp.com/docs/limit-post-revisions' + ) + ), + ], + [ + 'id' => 'autosave_interval', + 'type' => 'select', + 'name' => esc_html__( 'Autosave Interval', 'perform' ), + 'options' => [ + '' => esc_html__( '1 Minute', 'perform' ) . ' (' . esc_html__( 'Default', 'perform' ) . ')', + '120' => sprintf( esc_html__( '%s Minutes', 'perform' ), '2' ), + '180' => sprintf( esc_html__( '%s Minutes', 'perform' ), '3' ), + '240' => sprintf( esc_html__( '%s Minutes', 'perform' ), '4' ), + '300' => sprintf( esc_html__( '%s Minutes', 'perform' ), '5' ), + ], + 'desc' => esc_html__( 'Controls how often WordPress will auto save posts and pages while editing.', 'perform' ), + 'help_link' => esc_url( + add_query_arg( + $utm_args, + 'https://performwp.com/docs/autosave-intervals' + ) + ), + ], ] ] From 52c6fa87edcbe13b04ca70d7c0dab9cc3e4c960b Mon Sep 17 00:00:00 2001 From: Mehul Gohil Date: Tue, 28 Oct 2025 23:16:05 +0530 Subject: [PATCH 12/37] implemented new settings ui --- assets/src/css/admin/settings.css | 4 + assets/src/js/admin/Footer.jsx | 34 +++ assets/src/js/admin/SettingsApp.jsx | 132 ++++++++- assets/src/js/admin/SettingsNav.jsx | 140 ++++++---- languages/perform.pot | 414 ++++++++++++++-------------- src/Admin/Actions.php | 3 +- src/Admin/Settings/Menu.php | 32 ++- src/Includes/Helpers.php | 103 ++++--- 8 files changed, 564 insertions(+), 298 deletions(-) create mode 100644 assets/src/js/admin/Footer.jsx diff --git a/assets/src/css/admin/settings.css b/assets/src/css/admin/settings.css index 0d047cf..313ca5f 100644 --- a/assets/src/css/admin/settings.css +++ b/assets/src/css/admin/settings.css @@ -32,6 +32,10 @@ margin: 0; } +.perform-settings-content { + padding: 0 20px; +} + .perform-admin-settings-wrap { display: flex; diff --git a/assets/src/js/admin/Footer.jsx b/assets/src/js/admin/Footer.jsx new file mode 100644 index 0000000..babfb03 --- /dev/null +++ b/assets/src/js/admin/Footer.jsx @@ -0,0 +1,34 @@ +import { Button, Spinner } from '@wordpress/components'; +import { useEffect } from '@wordpress/element'; + +const Footer = ({ dirty, saving, message, onSave }) => { + // message: { text, type } where type is 'success' | 'error' | '' + return ( +
+
+ {message && message.text && ( +
+ {message.text} +
+ )} + +
+ ); +}; + +export default Footer; diff --git a/assets/src/js/admin/SettingsApp.jsx b/assets/src/js/admin/SettingsApp.jsx index f839c1f..a4eabb5 100644 --- a/assets/src/js/admin/SettingsApp.jsx +++ b/assets/src/js/admin/SettingsApp.jsx @@ -1,11 +1,141 @@ import SettingsHeader from './SettingsHeader'; import SettingsNav from './SettingsNav'; +import Footer from './Footer'; +import { useState, useEffect, useMemo, useRef } from '@wordpress/element'; const SettingsApp = () => { + const tabs = window.performwpSettings?.tabs || {}; + const fields = window.performwpSettings?.fields || {}; + const initialValues = useMemo(() => { + // Build a map of field id => saved value (if present) or default value (empty string or false) + const saved = window.performwpSettings?.saved || {}; + const values = {}; + Object.keys(fields).forEach((tab) => { + fields[tab].forEach((card) => { + (card.fields || []).forEach((f) => { + const savedVal = saved && Object.prototype.hasOwnProperty.call(saved, f.id) ? saved[f.id] : undefined; + if (typeof savedVal !== 'undefined') { + values[f.id] = savedVal; + } else { + values[f.id] = f.default ?? (f.type === 'toggle' ? false : ''); + } + }); + }); + }); + return values; + }, [fields]); + + const [fieldValues, setFieldValues] = useState(initialValues); + const [saving, setSaving] = useState(false); + const [message, setMessage] = useState(null); + const [activeTab, setActiveTab] = useState(Object.keys(tabs)[0] || ''); + const messageTimerRef = useRef(null); + + // dirty detection + const dirty = useMemo(() => { + return Object.keys(fieldValues).some((k) => fieldValues[k] !== initialValues[k]); + }, [fieldValues, initialValues]); + + const handleFieldChange = (id, value) => { + setFieldValues((prev) => ({ ...prev, [id]: value })); + }; + + const handleSave = async () => { + setSaving(true); + setMessage(null); + try { + const res = await fetch(ajaxurl, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' + }, + body: new URLSearchParams({ + action: 'perform_save_settings', + nonce: window.performwpSettings?.nonce || '', + data: JSON.stringify(fieldValues) + }) + }); + const json = await res.json(); + if ( json && json.success ) { + setMessage({ text: json.data?.message || 'Settings saved.', type: 'success' }); + // update initialValues snapshot + // mutate initialValues object won't update memo, so reset by rebuild: setFieldValues equals current, but we need to reset initialValues - simplest approach: set initial snapshot to current by resetting via a state. + // We'll set the initialValues by replacing the state used for comparison: emulate by setting all initialValues to current values via a ref - but here we'll just clear dirty by resetting initialValues via resetting fieldValues baseline. + // For simplicity, update initialValues by assigning to window.performwpSettings._initial = fieldValues (not ideal), but we can update local initialValues via a small trick: setFieldValues to same and update a savedSnapshot state. + // Implement savedSnapshot state instead. + } else { + setMessage({ text: (json && json.data && json.data.message) || 'Save failed.', type: 'error' }); + } + } catch (e) { + setMessage({ text: e.message || 'Save failed.', type: 'error' }); + } finally { + setSaving(false); + } + }; + + // Add a savedSnapshot state to serve as baseline for dirty calculation + const [savedSnapshot, setSavedSnapshot] = useState(initialValues); + + useEffect(() => { + // when initialValues changes (first render) set snapshot + setSavedSnapshot(initialValues); + setFieldValues(initialValues); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [initialValues]); + + // recompute dirty based on savedSnapshot + const isDirty = useMemo(() => { + return Object.keys(fieldValues).some((k) => fieldValues[k] !== savedSnapshot[k]); + }, [fieldValues, savedSnapshot]); + + // update savedSnapshot on successful save by watching message success + useEffect(() => { + if (message && message.type === 'success') { + setSavedSnapshot(fieldValues); + } + }, [message, fieldValues]); + + // Auto-dismiss message after 5 seconds + useEffect(() => { + if (!message || !message.text) return; + // Clear previous timer + if (messageTimerRef.current) { + clearTimeout(messageTimerRef.current); + messageTimerRef.current = null; + } + messageTimerRef.current = setTimeout(() => { + setMessage(null); + messageTimerRef.current = null; + }, 5000); + + return () => { + if (messageTimerRef.current) { + clearTimeout(messageTimerRef.current); + messageTimerRef.current = null; + } + }; + }, [message]); + + // Clear timer on unmount + useEffect(() => () => { + if (messageTimerRef.current) { + clearTimeout(messageTimerRef.current); + messageTimerRef.current = null; + } + }, []); + return ( <> - + +