From 6f699b7097caae4c327f6cd02882b13ee8237063 Mon Sep 17 00:00:00 2001 From: James LePage <36246732+Jameswlepage@users.noreply.github.com> Date: Thu, 18 Dec 2025 16:09:40 -0500 Subject: [PATCH 1/3] Add MCP (Model Context Protocol) experiment Adds MCP server integration for WordPress: - Expose WordPress capabilities to AI assistants - REST API for MCP tool discovery - Admin UI for server configuration and testing --- docs/experiments/mcp.md | 40 + includes/Experiment_Loader.php | 1 + includes/Experiments/MCP/Admin_Page.php | 131 ++ includes/Experiments/MCP/MCP.php | 63 + includes/Experiments/MCP/Manager.php | 943 +++++++++ .../Experiments/MCP/REST/MCP_Controller.php | 227 ++ src/admin/_common.scss | 150 ++ src/admin/_dataviews.scss | 26 + .../components/ProviderTooltipContent.tsx | 81 + src/admin/components/icons/AiIcon.tsx | 26 + src/admin/components/icons/AnthropicIcon.tsx | 22 + src/admin/components/icons/CloudflareIcon.tsx | 29 + src/admin/components/icons/DeepSeekIcon.tsx | 22 + src/admin/components/icons/FalIcon.tsx | 26 + src/admin/components/icons/GoogleIcon.tsx | 22 + src/admin/components/icons/GrokIcon.tsx | 22 + src/admin/components/icons/GroqIcon.tsx | 22 + .../components/icons/HuggingFaceIcon.tsx | 45 + src/admin/components/icons/McpIcon.tsx | 25 + src/admin/components/icons/OllamaIcon.tsx | 25 + src/admin/components/icons/OpenAiIcon.tsx | 22 + src/admin/components/icons/OpenRouterIcon.tsx | 25 + src/admin/components/icons/XaiIcon.tsx | 22 + src/admin/components/icons/index.ts | 14 + src/admin/components/provider-icons.tsx | 56 + src/admin/hooks/usePersistedView.ts | 99 + src/admin/mcp-server/_dataviews-base.css | 1847 +++++++++++++++++ .../mcp-server/components/ConfigGenerator.tsx | 143 ++ .../components/ServerStatusCard.tsx | 108 + .../mcp-server/components/StatusBadge.tsx | 42 + .../components/TestConnectionPanel.tsx | 75 + .../mcp-server/components/ToolsTable.tsx | 235 +++ src/admin/mcp-server/index.tsx | 554 +++++ src/admin/mcp-server/style.scss | 228 ++ src/admin/mcp-server/types.ts | 75 + webpack.config.js | 5 + 36 files changed, 5498 insertions(+) create mode 100644 docs/experiments/mcp.md create mode 100644 includes/Experiments/MCP/Admin_Page.php create mode 100644 includes/Experiments/MCP/MCP.php create mode 100644 includes/Experiments/MCP/Manager.php create mode 100644 includes/Experiments/MCP/REST/MCP_Controller.php create mode 100644 src/admin/_common.scss create mode 100644 src/admin/_dataviews.scss create mode 100644 src/admin/components/ProviderTooltipContent.tsx create mode 100644 src/admin/components/icons/AiIcon.tsx create mode 100644 src/admin/components/icons/AnthropicIcon.tsx create mode 100644 src/admin/components/icons/CloudflareIcon.tsx create mode 100644 src/admin/components/icons/DeepSeekIcon.tsx create mode 100644 src/admin/components/icons/FalIcon.tsx create mode 100644 src/admin/components/icons/GoogleIcon.tsx create mode 100644 src/admin/components/icons/GrokIcon.tsx create mode 100644 src/admin/components/icons/GroqIcon.tsx create mode 100644 src/admin/components/icons/HuggingFaceIcon.tsx create mode 100644 src/admin/components/icons/McpIcon.tsx create mode 100644 src/admin/components/icons/OllamaIcon.tsx create mode 100644 src/admin/components/icons/OpenAiIcon.tsx create mode 100644 src/admin/components/icons/OpenRouterIcon.tsx create mode 100644 src/admin/components/icons/XaiIcon.tsx create mode 100644 src/admin/components/icons/index.ts create mode 100644 src/admin/components/provider-icons.tsx create mode 100644 src/admin/hooks/usePersistedView.ts create mode 100644 src/admin/mcp-server/_dataviews-base.css create mode 100644 src/admin/mcp-server/components/ConfigGenerator.tsx create mode 100644 src/admin/mcp-server/components/ServerStatusCard.tsx create mode 100644 src/admin/mcp-server/components/StatusBadge.tsx create mode 100644 src/admin/mcp-server/components/TestConnectionPanel.tsx create mode 100644 src/admin/mcp-server/components/ToolsTable.tsx create mode 100644 src/admin/mcp-server/index.tsx create mode 100644 src/admin/mcp-server/style.scss create mode 100644 src/admin/mcp-server/types.ts diff --git a/docs/experiments/mcp.md b/docs/experiments/mcp.md new file mode 100644 index 00000000..18655ace --- /dev/null +++ b/docs/experiments/mcp.md @@ -0,0 +1,40 @@ +# MCP Experiment + +## Purpose + +The MCP experiment surfaces a control panel where site owners can provision one or more Model Context Protocol (MCP) servers, expose hand-picked abilities, generate ready-to-use client configuration, and validate connectivity without leaving wp-admin. + +## Prerequisites + +- WordPress 6.8+ +- PHP 7.4+ +- The `wordpress/mcp-adapter`, `wordpress/abilities-api`, and `wordpress/wp-ai-client` Composer dependencies installed (`composer install`) +- Built admin assets (`npm run build` or `npm run start` during development) +- An Application Password for the administrator who will authenticate remote clients (`Users → Profile → Application Passwords`) + +## UI Overview + +1. **Status & Transports** – Shows whether MCP is globally enabled, then for the selected server displays its REST endpoint (`/wp-json/{namespace}/{route}`) plus the `wp mcp-adapter serve --server=` STDIO command. Copy buttons keep the values aligned with the current site URL. +2. **Client Configuration Generator** – Provides JSON templates for Claude Desktop, Cursor, and a generic MCP client. The templates embed the site’s REST endpoint and highlight the `MCP_HEADERS` variable that should contain a Base64-encoded Application Password credential. +3. **Servers Toolbar** – Administrators can switch between existing servers or create new ones. Each server tracks its own route namespace/slug, transport list, and ability allow-list. +4. **Exposed Abilities Table** – Lists every registered ability (with category + provider badges) and lets administrators toggle whether it’s available for the selected server. When a server’s allow-list is empty it automatically falls back to “all MCP-public abilities”. +4. **Connection Test** – Issues a lightweight HTTP request against the endpoint and reports the HTTP status code so admins can confirm routing/authentication before wiring an external client. + +## Implementation Notes + +- `WordPress\AI\Experiments\MCP\Manager` owns all configuration: it stores server definitions in `ai_mcp_servers`, migrates the legacy `ai_mcp_enabled_tools` option, initializes the adapter, and registers each server (optionally passing custom allow-lists) during the `mcp_adapter_init` hook. +- REST routes live under `ai/v1/mcp` via `WordPress\AI\Experiments\MCP\REST\MCP_Controller`. Endpoints include overview (`GET /ai/v1/mcp?server_id=...`), global enable toggle, server CRUD, per-server tool updates, and connection tests. +- The React application is built from `src/admin/mcp-server` and enqueued from `WordPress\AI\Experiments\MCP\Admin_Page` (top-level **MCP** menu). Assets compile to `build/admin/mcp-server.js` and `build/admin/style-mcp-server.css`. +- Client-side data hydrates via `window.aiMcpServerSettings`, which now only contains REST routing + nonce metadata; the UI fetches its state from the overview endpoint on load and whenever the selected server changes. + +## Manual Testing Checklist + +1. Run `composer install` and `npm install` if dependencies are missing. +2. Build the assets with `npm run build` (or `npm run start` while iterating). +3. Navigate to the top-level **MCP** menu as an administrator. Confirm the status card shows “Running” once `/wp-json/{namespace}/{route}` is reachable. +4. Use the server selector to add an additional server. Verify a new REST route slug is generated and that it appears in the status card + CLI copy helper. +5. Toggle abilities for each server and ensure the REST response updates immediately (the allow-list lives inside `ai_mcp_servers`). +6. Use the **Copy URL** and **Copy Command** buttons and verify clipboard contents. +7. Use the **Client configuration** selector to copy the Claude Desktop template, then spot-check that it includes the site’s REST URL. +8. Click **Test connection** for each server. When authenticated, the notice should report the HTTP status code (401 is acceptable if Application Password headers weren’t supplied). If HTTPS fails locally, the fallback should retry with HTTP. +9. Create an Application Password for your admin user, launch `wp mcp-adapter serve --server=`, and paste one of the generated templates into Claude Desktop or Cursor to confirm MCP clients can connect end-to-end. diff --git a/includes/Experiment_Loader.php b/includes/Experiment_Loader.php index c7223b27..26e151df 100644 --- a/includes/Experiment_Loader.php +++ b/includes/Experiment_Loader.php @@ -107,6 +107,7 @@ private function get_default_experiments(): array { \WordPress\AI\Experiments\Image_Generation\Image_Generation::class, \WordPress\AI\Experiments\Title_Generation\Title_Generation::class, \WordPress\AI\Experiments\Excerpt_Generation\Excerpt_Generation::class, + \WordPress\AI\Experiments\MCP\MCP::class, ); /** diff --git a/includes/Experiments/MCP/Admin_Page.php b/includes/Experiments/MCP/Admin_Page.php new file mode 100644 index 00000000..f0b74441 --- /dev/null +++ b/includes/Experiments/MCP/Admin_Page.php @@ -0,0 +1,131 @@ + +
+
+
+
+ + + + + + +
+

+
+ +
+ +
+
+ +
+
+
+
+
+ array( + 'nonce' => wp_create_nonce( 'wp_rest' ), + 'root' => esc_url_raw( rest_url() ), + 'routes' => array( + 'overview' => 'ai/v1/mcp', + 'enabled' => 'ai/v1/mcp/enabled', + 'server' => 'ai/v1/mcp/server', + 'addServer' => 'ai/v1/mcp/server/add', + 'tools' => 'ai/v1/mcp/tools', + 'test' => 'ai/v1/mcp/test', + ), + ), + 'profileUrl' => esc_url_raw( admin_url( 'profile.php#application-passwords-section' ) ), + ) + ); + } +} diff --git a/includes/Experiments/MCP/MCP.php b/includes/Experiments/MCP/MCP.php new file mode 100644 index 00000000..9cff7515 --- /dev/null +++ b/includes/Experiments/MCP/MCP.php @@ -0,0 +1,63 @@ + 'mcp', + 'label' => __( 'MCP', 'ai' ), + 'description' => __( 'Manage Model Context Protocol servers and client access.', 'ai' ), + ); + } + + /** + * {@inheritDoc} + */ + public function register(): void { + $this->manager = new Manager(); + $this->manager->init(); + + if ( is_admin() ) { + $page = new Admin_Page( $this->manager ); + $page->init(); + } + } + + /** + * {@inheritDoc} + */ + public function get_entry_points(): array { + return array( + array( + 'label' => __( 'Dashboard', 'ai' ), + 'url' => admin_url( 'admin.php?page=ai-mcp' ), + 'type' => 'dashboard', + ), + ); + } +} diff --git a/includes/Experiments/MCP/Manager.php b/includes/Experiments/MCP/Manager.php new file mode 100644 index 00000000..f52da7e1 --- /dev/null +++ b/includes/Experiments/MCP/Manager.php @@ -0,0 +1,943 @@ + esc_html__( 'MCP Adapter', 'ai' ), + 'description' => esc_html__( 'Built-in abilities required for MCP discovery and execution.', 'ai' ), + ) + ); + } + + /** + * Register the core MCP adapter abilities when Abilities API is available. + */ + public function register_adapter_abilities(): void { + if ( ! function_exists( 'wp_get_ability' ) ) { + return; + } + + $this->register_adapter_category(); + + $this->maybe_register_adapter_ability( 'mcp-adapter/discover-abilities', DiscoverAbilitiesAbility::class ); + $this->maybe_register_adapter_ability( 'mcp-adapter/get-ability-info', GetAbilityInfoAbility::class ); + $this->maybe_register_adapter_ability( 'mcp-adapter/execute-ability', ExecuteAbilityAbility::class ); + } + + /** + * Register an adapter ability if it is not already available. + * + * @param string $ability_name Ability identifier. + * @param string $ability_class Fully-qualified ability class name. + */ + private function maybe_register_adapter_ability( string $ability_name, string $ability_class ): void { + if ( function_exists( 'wp_has_ability' ) && wp_has_ability( $ability_name ) ) { + return; + } + + if ( ! class_exists( $ability_class ) || ! is_callable( array( $ability_class, 'register' ) ) ) { + return; + } + + $ability_class::register(); + } + + /** + * Instantiate the adapter so servers register on REST requests. + */ + public function bootstrap_adapter(): void { + if ( ! class_exists( McpAdapter::class ) ) { + return; + } + + McpAdapter::instance()->init(); + } + + /** + * Register each configured server with the adapter. + */ + public function register_servers(): void { + if ( ! $this->is_enabled() ) { + return; + } + + $adapter = McpAdapter::instance(); + $servers = $this->get_servers(); + + foreach ( $servers as $server ) { + if ( empty( $server['enabled'] ) ) { + continue; + } + + $transports = $this->map_transports_to_classes( $server['transports'] ?? array( 'http' ) ); + $tools = $this->resolve_tools_for_server( $server ); + + try { + $adapter->create_server( + $server['id'], + $server['route_namespace'], + $server['route'], + $server['name'], + $server['description'] ?? '', + self::DEFAULT_VERSION, + $transports, + ErrorLogMcpErrorHandler::class, + NullMcpObservabilityHandler::class, + $tools + ); + } catch ( \Throwable $t ) { + // Surface via admin notices/logging hooks. + do_action( + 'ai_mcp_server_registration_failed', + array( + 'server' => $server['id'], + 'error' => $t->getMessage(), + ) + ); + } + } + } + + /** + * Wire REST endpoints. + */ + public function register_rest_routes(): void { + $controller = new MCP_Controller( $this ); + $controller->register_routes(); + } + + /** + * Determine if MCP is globally enabled. + */ + public function is_enabled(): bool { + return (bool) get_option( self::OPTION_ENABLED, true ); + } + + /** + * Toggle MCP globally. + */ + public function set_enabled( bool $enabled ): void { + update_option( self::OPTION_ENABLED, $enabled, false ); + } + + /** + * Retrieve all server configs, ensuring a default entry exists. + * + * @return array> + */ + public function get_servers(): array { + $config = get_option( self::OPTION_SERVERS, null ); + + if ( ! is_array( $config ) || empty( $config ) ) { + $config = array( $this->get_default_server_id() => $this->build_default_server_config() ); + update_option( self::OPTION_SERVERS, $config, false ); + } + + return array_map( array( $this, 'sanitize_server_config' ), $config ); + } + + /** + * Replace the stored server configs. + * + * @param array> $servers Server definitions keyed by ID. + */ + public function save_servers( array $servers ): void { + $sanitized = array(); + + foreach ( $servers as $server ) { + if ( empty( $server['id'] ) ) { + continue; + } + + $sanitized[ $server['id'] ] = $this->sanitize_server_config( $server ); + } + + update_option( self::OPTION_SERVERS, $sanitized, false ); + } + + /** + * Get a single server configuration by ID. + */ + public function get_server( string $server_id ): ?array { + $servers = $this->get_servers(); + + return $servers[ $server_id ] ?? null; + } + + /** + * Add a new server definition. + * + * @param array $data Server data. + * @return array Newly created server configuration. + */ + public function add_server( array $data ): array { + $servers = $this->get_servers(); + + $name = sanitize_text_field( $data['name'] ?? esc_html__( 'New MCP Server', 'ai' ) ); + $route_namespace = sanitize_key( $data['route_namespace'] ?? 'mcp' ); + $route = $this->unique_route_slug( $data['route'] ?? $name, $servers ); + $id = $this->unique_server_id( $route ); + $description = sanitize_text_field( $data['description'] ?? '' ); + $transports = $this->sanitize_transports( $data['transports'] ?? array( 'http' ) ); + $server_config = array( + 'id' => $id, + 'name' => $name, + 'description' => $description, + 'route_namespace' => $route_namespace, + 'route' => $route, + 'enabled' => true, + 'transports' => $transports, + 'tools' => array(), + ); + + $servers[ $id ] = $server_config; + $this->save_servers( $servers ); + + return $server_config; + } + + /** + * Update an existing server config. + * + * @param string $server_id Server identifier. + * @param array $data Fields to override. + * @return array|null Updated server config. + */ + public function update_server( string $server_id, array $data ): ?array { + $servers = $this->get_servers(); + + if ( empty( $servers[ $server_id ] ) ) { + return null; + } + + $current = $servers[ $server_id ]; + + if ( array_key_exists( 'name', $data ) ) { + $current['name'] = sanitize_text_field( (string) $data['name'] ); + } + + if ( array_key_exists( 'description', $data ) ) { + $current['description'] = sanitize_text_field( (string) $data['description'] ); + } + + if ( array_key_exists( 'route', $data ) ) { + $current['route'] = $this->unique_route_slug( (string) $data['route'], $servers, $server_id ); + } + + if ( array_key_exists( 'route_namespace', $data ) ) { + $current['route_namespace'] = sanitize_key( (string) $data['route_namespace'] ); + } + + if ( array_key_exists( 'enabled', $data ) ) { + $current['enabled'] = (bool) $data['enabled']; + } + + if ( array_key_exists( 'transports', $data ) ) { + $current['transports'] = $this->sanitize_transports( $data['transports'] ); + } + + if ( array_key_exists( 'tools', $data ) && is_array( $data['tools'] ) ) { + $current['tools'] = array_values( + array_unique( + array_map( + static fn( $name ) => sanitize_text_field( (string) $name ), + $data['tools'] + ) + ) + ); + } + + $servers[ $server_id ] = $current; + $this->save_servers( $servers ); + + return $current; + } + + /** + * Build overview payload consumed by the React UI. + * + * @param string|null $requested_server Server ID to focus on. + * @return array + */ + public function build_overview_payload( ?string $requested_server = null ): array { + $servers = $this->get_servers(); + $active_server = $requested_server && isset( $servers[ $requested_server ] ) + ? $servers[ $requested_server ] + : reset( $servers ); + + if ( ! $active_server ) { + $active_server = $this->build_default_server_config(); + } + + $active_id = $active_server['id']; + $runtime_map = $this->get_runtime_servers_map(); + + return array( + 'enabled' => $this->is_enabled(), + 'servers' => $this->summarize_servers( $servers, $runtime_map ), + 'activeServerId' => $active_id, + 'activeServer' => $this->format_server_for_response( $active_server, $runtime_map[ $active_id ] ?? null ), + 'tools' => $this->get_available_tools( $active_server ), + 'configTemplates'=> $this->get_client_templates( $active_server ), + ); + } + + /** + * Return the available tools list decorated for the UI. + * + * @param array $server Server config. + * @return array> + */ + public function get_available_tools( array $server ): array { + if ( ! function_exists( 'wp_get_abilities' ) ) { + return array(); + } + + $abilities = wp_get_abilities(); + $enabled = $server['tools'] ?? array(); + $enabled_lookup = array_fill_keys( $enabled, true ); + + $items = array(); + + /** @var \WP_Ability $ability */ + foreach ( $abilities as $ability ) { + $meta = method_exists( $ability, 'get_meta' ) ? $ability->get_meta() : array(); + + $items[] = array( + 'name' => $ability->get_name(), + 'label' => $ability->get_label(), + 'description' => $ability->get_description(), + 'category' => array( + 'slug' => $ability->get_category(), + 'label' => $this->get_category_label( $ability->get_category() ), + ), + 'isPublic' => (bool) ( $meta['mcp']['public'] ?? false ), + 'enabled' => empty( $enabled ) + ? (bool) ( $meta['mcp']['public'] ?? false ) + : isset( $enabled_lookup[ $ability->get_name() ] ), + ); + } + + usort( + $items, + static fn( array $a, array $b ) => strcasecmp( $a['label'], $b['label'] ) + ); + + return $items; + } + + /** + * Update the allow-list for a server. + * + * @param string $server_id Server ID. + * @param array $tools Ability names. + * @return array|null Updated server config. + */ + public function update_server_tools( string $server_id, array $tools ): ?array { + return $this->update_server( + $server_id, + array( + 'tools' => $tools, + ) + ); + } + + /** + * Return copy/paste templates for client configuration. + * + * @param array $server Server data. + * @return array> + */ + public function get_client_templates( array $server ): array { + $endpoint = $this->build_endpoint_from_config( $server ); + + if ( ! $endpoint ) { + return array(); + } + + $mcp_remote_config = array( + 'mcpServers' => array( + 'wordpress' => array( + 'command' => 'npx', + 'args' => array( 'mcp-remote', $endpoint ), + 'env' => array( + 'MCP_HEADERS' => 'Authorization: Basic ', + ), + ), + ), + ); + + return array( + 'claude-desktop' => array( + 'id' => 'claude-desktop', + 'fileName' => 'claude_desktop_config.json', + 'content' => wp_json_encode( + $mcp_remote_config, + JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES + ), + ), + 'cursor' => array( + 'id' => 'cursor', + 'fileName' => '.cursor/mcp.json', + 'content' => wp_json_encode( + $mcp_remote_config, + JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES + ), + ), + 'windsurf' => array( + 'id' => 'windsurf', + 'fileName' => 'mcp_config.json (Windsurf)', + 'content' => wp_json_encode( + $mcp_remote_config, + JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES + ), + ), + 'vscode' => array( + 'id' => 'vscode', + 'fileName' => '.vscode/mcp.json', + 'content' => wp_json_encode( + array( + 'servers' => array( + 'wordpress' => array( + 'type' => 'http', + 'url' => $endpoint, + 'headers' => array( + 'Authorization' => 'Basic ', + ), + ), + ), + ), + JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES + ), + ), + 'jetbrains' => array( + 'id' => 'jetbrains', + 'fileName' => 'mcp.json (JetBrains)', + 'content' => wp_json_encode( + $mcp_remote_config, + JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES + ), + ), + 'generic' => array( + 'id' => 'generic', + 'fileName' => 'mcp-server.json (Generic)', + 'content' => wp_json_encode( + array( + 'endpoint' => $endpoint, + 'transports' => $server['transports'] ?? array( 'http' ), + 'headersHint' => 'Authorization: Basic ', + ), + JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES + ), + ), + ); + } + + /** + * Test the HTTP endpoint for a given server. + * + * @param string $server_id Server ID. + * @param array $args Optional request overrides. + * @return array + */ + public function test_http_endpoint( string $server_id, array $args = array() ): array { + $server = $this->get_server( $server_id ); + + if ( ! $server ) { + return array( + 'success' => false, + 'code' => null, + 'message' => esc_html__( 'Server not found.', 'ai' ), + ); + } + + $endpoint = $this->build_endpoint_from_config( $server ); + + if ( ! $endpoint ) { + return array( + 'success' => false, + 'code' => null, + 'message' => esc_html__( 'No endpoint is available for this server yet.', 'ai' ), + ); + } + + $method = strtoupper( $args['method'] ?? 'GET' ); + $headers = is_array( $args['headers'] ?? null ) ? $args['headers'] : array(); + $sanitized_headers = array(); + + foreach ( $headers as $key => $value ) { + $sanitized_headers[ sanitize_text_field( (string) $key ) ] = sanitize_text_field( (string) $value ); + } + + $body = $args['body'] ?? null; + + $attempts = array( $endpoint ); + $scheme = wp_parse_url( $endpoint, PHP_URL_SCHEME ); + + if ( 'https' === $scheme ) { + $attempts[] = set_url_scheme( $endpoint, 'http' ); + } + + foreach ( $attempts as $url ) { + $request_args = array( + 'method' => $method, + 'timeout' => 10, + 'headers' => $sanitized_headers, + 'sslverify' => true, + ); + + if ( is_array( $body ) ) { + $request_args['body'] = wp_json_encode( $body ); + $request_args['headers']['Content-Type'] = 'application/json'; + } elseif ( is_string( $body ) && '' !== $body ) { + $request_args['body'] = $body; + } + + $response = wp_remote_request( $url, $request_args ); + + if ( is_wp_error( $response ) ) { + $message = $response->get_error_message(); + // On HTTPS connection failures, retry with HTTP once. + if ( 'https' === $scheme && false !== strpos( $message, 'cURL error 7' ) && $url === $endpoint ) { + continue; + } + + return array( + 'success' => false, + 'code' => null, + 'message' => $message, + ); + } + + $code = wp_remote_retrieve_response_code( $response ); + $body_str = wp_remote_retrieve_body( $response ); + + return array( + 'success' => $code >= 200 && $code < 400, + 'code' => $code, + 'message' => esc_html__( 'Endpoint responded.', 'ai' ), + 'body' => $body_str ? substr( (string) $body_str, 0, 400 ) : '', + ); + } + + return array( + 'success' => false, + 'code' => null, + 'message' => esc_html__( 'Unable to reach the endpoint.', 'ai' ), + ); + } + + /** + * Format servers for sidebar list. + * + * @param array> $servers Server configs. + * @param array $runtime_map Adapter runtime map. + * @return array> + */ + private function summarize_servers( array $servers, array $runtime_map ): array { + $items = array(); + + foreach ( $servers as $server ) { + $runtime = $runtime_map[ $server['id'] ] ?? null; + $items[] = array( + 'id' => $server['id'], + 'name' => $server['name'], + 'description' => $server['description'] ?? '', + 'enabled' => (bool) ( $server['enabled'] ?? true ), + 'status' => $this->determine_status( $server, $runtime ), + ); + } + + return $items; + } + + /** + * Format a server for REST responses. + * + * @param array $server Configured server. + * @param McpServer|null $runtime Runtime instance. + * @return array + */ + private function format_server_for_response( array $server, ?McpServer $runtime ): array { + return array( + 'id' => $server['id'], + 'name' => $server['name'], + 'description' => $server['description'] ?? '', + 'route_namespace' => $server['route_namespace'], + 'route' => $server['route'], + 'transports' => $server['transports'] ?? array( 'http' ), + 'enabled' => (bool) ( $server['enabled'] ?? true ), + 'http_endpoint' => $runtime ? $this->build_runtime_endpoint( $runtime ) : $this->build_endpoint_from_config( $server ), + 'cli_command' => $this->build_cli_command( $server ), + 'status' => $this->determine_status( $server, $runtime ), + 'has_route' => null !== $runtime, + ); + } + + /** + * Figure out the status label for a server. + * + * Note: When a server is enabled but has no runtime instance, it means + * either the server was just enabled via API (runtime will exist on next + * request) or it's still initializing. We return 'running' for enabled + * servers since they will be running once the page reloads. + */ + private function determine_status( array $server, ?McpServer $runtime ): string { + if ( empty( $server['enabled'] ) || ! $this->is_enabled() ) { + return 'disabled'; + } + + return 'running'; + } + + /** + * Resolve which tools should be registered for a server. + * + * @param array $server Server definition. + * @return array + */ + private function resolve_tools_for_server( array $server ): array { + $tools = $server['tools'] ?? array(); + + if ( ! empty( $tools ) ) { + return array_values( + array_unique( + array_merge( + $this->system_tool_names(), + array_map( 'sanitize_text_field', $tools ) + ) + ) + ); + } + + return array_values( + array_unique( + array_merge( + $this->system_tool_names(), + $this->discover_public_tools() + ) + ) + ); + } + + /** + * Discover abilities marked as MCP-public. + * + * @return array + */ + private function discover_public_tools(): array { + if ( ! function_exists( 'wp_get_abilities' ) ) { + return array(); + } + + $names = array(); + + foreach ( wp_get_abilities() as $ability ) { + $meta = method_exists( $ability, 'get_meta' ) ? $ability->get_meta() : array(); + + if ( ! empty( $meta['mcp']['public'] ) ) { + $names[] = $ability->get_name(); + } + } + + return $names; + } + + /** + * Build CLI command helper. + */ + private function build_cli_command( array $server ): string { + return sprintf( + 'wp mcp-adapter serve --server=%s', + $server['id'] + ); + } + + /** + * Build endpoint string from runtime instance. + */ + private function build_runtime_endpoint( McpServer $server ): string { + $namespace = trim( $server->get_server_route_namespace(), '/' ); + $route = trim( $server->get_server_route(), '/' ); + + return rest_url( "{$namespace}/{$route}" ); + } + + /** + * Build endpoint string from stored config. + */ + private function build_endpoint_from_config( array $server ): string { + $namespace = trim( $server['route_namespace'], '/' ); + $route = trim( $server['route'], '/' ); + + return rest_url( "{$namespace}/{$route}" ); + } + + /** + * Map transport slugs to class names. + * + * @param array $transports Transport identifiers. + * @return array + */ + private function map_transports_to_classes( array $transports ): array { + $map = array( + 'http' => HttpTransport::class, + ); + + $classes = array(); + foreach ( $transports as $transport ) { + $slug = strtolower( (string) $transport ); + if ( isset( $map[ $slug ] ) ) { + $classes[] = $map[ $slug ]; + } + } + + return $classes ?: array( HttpTransport::class ); + } + + /** + * Provide canonical list of system tool names that must remain available. + * + * @return array + */ + private function system_tool_names(): array { + return array( + 'mcp-adapter/discover-abilities', + 'mcp-adapter/get-ability-info', + 'mcp-adapter/execute-ability', + ); + } + + /** + * Get runtime servers keyed by ID. + * + * @return array + */ + private function get_runtime_servers_map(): array { + if ( ! class_exists( McpAdapter::class ) ) { + return array(); + } + + $map = array(); + $adapter = McpAdapter::instance(); + + foreach ( $adapter->get_servers() as $server ) { + $map[ $server->get_server_id() ] = $server; + } + + return $map; + } + + /** + * Sanitize transport list. + * + * @param mixed $transports Raw transports. + * @return array + */ + private function sanitize_transports( $transports ): array { + if ( ! is_array( $transports ) ) { + return array( 'http' ); + } + + $valid = array( 'http' ); + + $list = array(); + foreach ( $transports as $transport ) { + $slug = strtolower( sanitize_key( (string) $transport ) ); + if ( in_array( $slug, $valid, true ) ) { + $list[] = $slug; + } + } + + return $list ?: array( 'http' ); + } + + /** + * Generate a default server configuration. + * + * @return array + */ + private function build_default_server_config(): array { + $legacy_tools = get_option( self::OPTION_LEGACY_TOOLS, array() ); + + if ( ! empty( $legacy_tools ) ) { + delete_option( self::OPTION_LEGACY_TOOLS ); + } + + return array( + 'id' => $this->get_default_server_id(), + 'name' => esc_html__( 'Patient', 'ai' ), + 'description' => esc_html__( 'Automatically exposes public abilities over HTTP.', 'ai' ), + 'route_namespace' => 'mcp', + 'route' => 'default-server', + 'enabled' => true, + 'transports' => array( 'http' ), + 'tools' => array_map( 'sanitize_text_field', (array) $legacy_tools ), + ); + } + + /** + * Unique ID for the default server. + */ + private function get_default_server_id(): string { + return 'ai-mcp-default'; + } + + /** + * Ensure server config has required fields and sanitized values. + * + * @param array $server Raw config. + * @return array + */ + private function sanitize_server_config( array $server ): array { + return array( + 'id' => sanitize_key( $server['id'] ?? $this->unique_server_id( 'server' ) ), + 'name' => sanitize_text_field( $server['name'] ?? esc_html__( 'Patient', 'ai' ) ), + 'description' => sanitize_text_field( $server['description'] ?? '' ), + 'route_namespace' => sanitize_key( $server['route_namespace'] ?? 'mcp' ), + 'route' => sanitize_key( $server['route'] ?? 'default-server' ), + 'enabled' => (bool) ( $server['enabled'] ?? true ), + 'transports' => $this->sanitize_transports( $server['transports'] ?? array( 'http' ) ), + 'tools' => array_map( + static fn( $name ) => sanitize_text_field( (string) $name ), + is_array( $server['tools'] ?? null ) ? (array) $server['tools'] : array() + ), + ); + } + + /** + * Generate a unique server ID. + */ + private function unique_server_id( string $seed ): string { + return sanitize_key( 'ai-mcp-' . $seed . '-' . wp_generate_password( 4, false ) ); + } + + /** + * Generate a unique route slug. + * + * @param string $value Desired slug. + * @param array $existing All servers. + * @param string|null $current_id Current server ID (when updating). + * @return string + */ + private function unique_route_slug( string $value, array $existing, ?string $current_id = null ): string { + $base = sanitize_key( $value ); + + if ( '' === $base ) { + $base = 'server'; + } + + $slug = $base; + $counter = 1; + + $conflicts = array(); + foreach ( $existing as $id => $server ) { + if ( $current_id && $id === $current_id ) { + continue; + } + + $conflicts[ $server['route'] ] = true; + } + + while ( isset( $conflicts[ $slug ] ) ) { + $slug = $base . '-' . $counter; + ++$counter; + } + + return $slug; + } + + /** + * Helper to get ability category labels. + */ + private function get_category_label( string $slug ): string { + if ( function_exists( 'wp_has_ability_category' ) && ! wp_has_ability_category( $slug ) ) { + return $slug; + } + + $category = function_exists( 'wp_get_ability_category' ) ? wp_get_ability_category( $slug ) : null; + + return $category ? $category->get_label() : $slug; + } +} diff --git a/includes/Experiments/MCP/REST/MCP_Controller.php b/includes/Experiments/MCP/REST/MCP_Controller.php new file mode 100644 index 00000000..b7ed66e4 --- /dev/null +++ b/includes/Experiments/MCP/REST/MCP_Controller.php @@ -0,0 +1,227 @@ +namespace = 'ai/v1'; + $this->rest_base = 'mcp'; + $this->manager = $manager; + } + + /** + * Register routes. + */ + public function register_routes(): void { + register_rest_route( + $this->namespace, + '/' . $this->rest_base, + array( + array( + 'methods' => 'GET', + 'callback' => array( $this, 'get_overview' ), + 'permission_callback' => array( $this, 'permissions_check' ), + 'args' => array( + 'server_id' => array( + 'type' => 'string', + ), + ), + ), + ) + ); + + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/enabled', + array( + array( + 'methods' => 'POST', + 'callback' => array( $this, 'update_enabled' ), + 'permission_callback' => array( $this, 'permissions_check' ), + 'args' => array( + 'enabled' => array( + 'type' => 'boolean', + 'required' => true, + ), + ), + ), + ) + ); + + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/server', + array( + array( + 'methods' => 'POST', + 'callback' => array( $this, 'save_server' ), + 'permission_callback' => array( $this, 'permissions_check' ), + ), + ) + ); + + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/server/add', + array( + array( + 'methods' => 'POST', + 'callback' => array( $this, 'add_server' ), + 'permission_callback' => array( $this, 'permissions_check' ), + ), + ) + ); + + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/tools', + array( + array( + 'methods' => 'POST', + 'callback' => array( $this, 'update_tools' ), + 'permission_callback' => array( $this, 'permissions_check' ), + 'args' => array( + 'serverId' => array( + 'type' => 'string', + 'required' => true, + ), + 'tools' => array( + 'type' => 'array', + 'required' => true, + 'items' => array( 'type' => 'string' ), + ), + ), + ), + ) + ); + + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/test', + array( + array( + 'methods' => 'POST', + 'callback' => array( $this, 'test_connection' ), + 'permission_callback' => array( $this, 'permissions_check' ), + 'args' => array( + 'serverId' => array( + 'type' => 'string', + 'required' => true, + ), + ), + ), + ) + ); + } + + /** + * Capability check. + */ + public function permissions_check(): bool { + return current_user_can( 'manage_options' ); + } + + /** + * Overview endpoint. + */ + public function get_overview( WP_REST_Request $request ): WP_REST_Response { + $server_id = $request->get_param( 'server_id' ); + + return rest_ensure_response( $this->manager->build_overview_payload( $server_id ) ); + } + + /** + * Toggle global enable flag. + */ + public function update_enabled( WP_REST_Request $request ) { + $enabled = (bool) $request->get_param( 'enabled' ); + $this->manager->set_enabled( $enabled ); + + return $this->get_overview( $request ); + } + + /** + * Create a server. + */ + public function add_server( WP_REST_Request $request ) { + $data = (array) $request->get_param( 'server' ); + $this->manager->add_server( $data ); + + return $this->get_overview( $request ); + } + + /** + * Update server fields. + */ + public function save_server( WP_REST_Request $request ) { + $server = (array) $request->get_param( 'server' ); + $id = sanitize_text_field( (string) ( $server['id'] ?? '' ) ); + + if ( '' === $id ) { + return new WP_Error( 'ai_mcp_missing_id', __( 'Server ID is required.', 'ai' ) ); + } + + $this->manager->update_server( $id, $server ); + + $request->set_param( 'server_id', $id ); + + return $this->get_overview( $request ); + } + + /** + * Update ability allow-list for a server. + */ + public function update_tools( WP_REST_Request $request ) { + $server_id = sanitize_text_field( (string) $request->get_param( 'serverId' ) ); + $tools = (array) $request->get_param( 'tools' ); + + $updated = $this->manager->update_server_tools( $server_id, $tools ); + + if ( null === $updated ) { + return new WP_Error( 'ai_mcp_server_missing', __( 'Server not found.', 'ai' ) ); + } + + $request->set_param( 'server_id', $server_id ); + + return $this->get_overview( $request ); + } + + /** + * Test endpoint connectivity for a server. + */ + public function test_connection( WP_REST_Request $request ): WP_REST_Response { + $server_id = sanitize_text_field( (string) $request->get_param( 'serverId' ) ); + $result = $this->manager->test_http_endpoint( $server_id ); + + return rest_ensure_response( $result ); + } +} diff --git a/src/admin/_common.scss b/src/admin/_common.scss new file mode 100644 index 00000000..1094ab4d --- /dev/null +++ b/src/admin/_common.scss @@ -0,0 +1,150 @@ +/** + * Common Admin Page Styles + * + * Shared styles for AI admin pages including page headers with icons. + * + * @package WordPress\AI + */ + +/* Remove default .wrap top margin/padding for AI pages */ +.wrap.ai-mcp-server, +.wrap.ai-request-logs, +.wrap.ai-experiments-page { + margin-top: 0; + padding-top: 0; +} + +/* Force AI admin screens to use white page backgrounds */ +$ai-admin-white-pages: ( + 'settings_page_ai-request-logs', + 'settings_page_wp-ai-client', + 'settings_page_ai-experiments', + 'toplevel_page_ai-mcp' +); + +@each $page-class in $ai-admin-white-pages { + body.#{$page-class}, + body.#{$page-class} #wpwrap, + body.#{$page-class} #wpcontent, + body.#{$page-class} #wpbody, + body.#{$page-class} #wpbody-content { + background-color: #fff; + } +} + +/* Full-width page header (privacy-style) */ +.ai-admin-header { + background: #fff; + border-bottom: 1px solid #dcdcde; + margin: 0 0 1.5rem; + padding: 16px 20px; + + // Extend to full width by pulling out of .wrap padding + .wrap > & { + margin-left: -20px; + margin-right: -20px; + + @media screen and (max-width: 782px) { + margin-left: -10px; + margin-right: -10px; + padding-left: 10px; + padding-right: 10px; + } + } +} + +/* Card border-radius standardization (4px) */ +.ai-mcp-server, +.ai-request-logs { + .components-card { + border-radius: 4px; + } +} + +.ai-admin-header__inner { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.75rem; + max-width: 1400px; +} + +.ai-admin-header__left { + display: flex; + align-items: center; + gap: 0.75rem; +} + +.ai-admin-header__right { + display: flex; + align-items: center; + gap: 1rem; + + // Header toggle needs proper alignment + .components-toggle-control { + margin: 0; + + .components-base-control__field { + margin-bottom: 0; + } + } +} + +.ai-admin-header__icon { + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + width: 36px; + height: 36px; + padding: 6px; + background: #f6f7f7; + border-radius: 8px; + color: #1d2327; + + svg { + width: 100%; + height: 100%; + } +} + +.ai-admin-header__title { + h1 { + margin: 0; + padding: 0; + font-size: 23px; + font-weight: 600; + line-height: 1.3; + } +} + +/* Legacy page header with icon (for inline use) */ +.ai-page-header { + display: flex; + align-items: center; + gap: 0.75rem; + margin-bottom: 1.5rem; + + h1 { + margin: 0; + padding: 0; + } +} + +.ai-page-header__icon { + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + width: 36px; + height: 36px; + padding: 6px; + background: #fff; + border-radius: 8px; + color: #1d2327; + + svg { + width: 100%; + height: 100%; + } +} diff --git a/src/admin/_dataviews.scss b/src/admin/_dataviews.scss new file mode 100644 index 00000000..2f295bbe --- /dev/null +++ b/src/admin/_dataviews.scss @@ -0,0 +1,26 @@ +// DataViews base styles (from @wordpress/dataviews package) +// Copied from node_modules/@wordpress/dataviews/build-style/style.css +@import './_dataviews-base.css'; + +// Project-specific overrides + +// Reduce default padding on edges (default is 24px) +.components-card__body:has(> .dataviews-wrapper) { + .dataviews__view-actions, + .dataviews-filters__container, + .dataviews-footer, + .dataviews-loading, + .dataviews-no-results { + padding-inline: 16px; + } + + .dataviews-view-table tr th:first-child, + .dataviews-view-table tr td:first-child { + padding-inline-start: 16px; + } + + .dataviews-view-table tr th:last-child, + .dataviews-view-table tr td:last-child { + padding-inline-end: 16px; + } +} diff --git a/src/admin/components/ProviderTooltipContent.tsx b/src/admin/components/ProviderTooltipContent.tsx new file mode 100644 index 00000000..49f48a9e --- /dev/null +++ b/src/admin/components/ProviderTooltipContent.tsx @@ -0,0 +1,81 @@ +/** + * WordPress dependencies + */ +import { __, sprintf } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import type { ProviderMetadata } from '../types/providers'; + +interface ProviderTooltipContentProps { + metadata: ProviderMetadata; + activeModel?: string | null; +} + +const ProviderTooltipContent: React.FC< ProviderTooltipContentProps > = ( { + metadata, + activeModel, +} ) => { + const topModels = metadata.models?.slice( 0, 4 ) ?? []; + + return ( +
+
+
+ { metadata.name } + + { metadata.type === 'client' + ? __( 'Local', 'ai' ) + : __( 'Cloud', 'ai' ) } + +
+ { activeModel && ( + + { sprintf( + /* translators: %s: AI model name. */ + __( 'Requested model: %s', 'ai' ), + activeModel + ) } + + ) } + { metadata.tooltip && ( +

{ metadata.tooltip }

+ ) } + { topModels.length > 0 && ( +
+ + { __( 'Available models', 'ai' ) } + +
    + { topModels.map( ( model ) => ( +
  • + { model.name } + { model.capabilities?.length > 0 && ( + + { model.capabilities.join( ', ' ) } + + ) } +
  • + ) ) } +
+
+ ) } +
+ { metadata.url && ( + + ) } +
+ ); +}; + +export default ProviderTooltipContent; diff --git a/src/admin/components/icons/AiIcon.tsx b/src/admin/components/icons/AiIcon.tsx new file mode 100644 index 00000000..f1daf850 --- /dev/null +++ b/src/admin/components/icons/AiIcon.tsx @@ -0,0 +1,26 @@ +/** + * External dependencies + */ +import type { SVGProps } from 'react'; + +/** + * AI Icon - Cauldron with sparkles design used for AI features. + */ +const AiIcon = ( props: SVGProps< SVGSVGElement > ) => ( + + + + + + + +); + +export default AiIcon; diff --git a/src/admin/components/icons/AnthropicIcon.tsx b/src/admin/components/icons/AnthropicIcon.tsx new file mode 100644 index 00000000..d64f80bd --- /dev/null +++ b/src/admin/components/icons/AnthropicIcon.tsx @@ -0,0 +1,22 @@ +/** + * External dependencies + */ +import type { SVGProps } from 'react'; + +/** + * Anthropic (Claude) Icon + */ +const AnthropicIcon = ( props: SVGProps< SVGSVGElement > ) => ( + + + +); + +export default AnthropicIcon; diff --git a/src/admin/components/icons/CloudflareIcon.tsx b/src/admin/components/icons/CloudflareIcon.tsx new file mode 100644 index 00000000..804c6573 --- /dev/null +++ b/src/admin/components/icons/CloudflareIcon.tsx @@ -0,0 +1,29 @@ +/** + * External dependencies + */ +import type { SVGProps } from 'react'; + +/** + * Cloudflare Icon + */ +const CloudflareIcon = ( props: SVGProps< SVGSVGElement > ) => ( + + + + +); + +export default CloudflareIcon; diff --git a/src/admin/components/icons/DeepSeekIcon.tsx b/src/admin/components/icons/DeepSeekIcon.tsx new file mode 100644 index 00000000..effcfe19 --- /dev/null +++ b/src/admin/components/icons/DeepSeekIcon.tsx @@ -0,0 +1,22 @@ +/** + * External dependencies + */ +import type { SVGProps } from 'react'; + +/** + * DeepSeek Icon + */ +const DeepSeekIcon = ( props: SVGProps< SVGSVGElement > ) => ( + + + +); + +export default DeepSeekIcon; diff --git a/src/admin/components/icons/FalIcon.tsx b/src/admin/components/icons/FalIcon.tsx new file mode 100644 index 00000000..e5f43adb --- /dev/null +++ b/src/admin/components/icons/FalIcon.tsx @@ -0,0 +1,26 @@ +/** + * External dependencies + */ +import type { SVGProps } from 'react'; + +/** + * Fal.ai Icon + */ +const FalIcon = ( props: SVGProps< SVGSVGElement > ) => ( + + + +); + +export default FalIcon; diff --git a/src/admin/components/icons/GoogleIcon.tsx b/src/admin/components/icons/GoogleIcon.tsx new file mode 100644 index 00000000..5ec6b0ee --- /dev/null +++ b/src/admin/components/icons/GoogleIcon.tsx @@ -0,0 +1,22 @@ +/** + * External dependencies + */ +import type { SVGProps } from 'react'; + +/** + * Google Icon + */ +const GoogleIcon = ( props: SVGProps< SVGSVGElement > ) => ( + + + +); + +export default GoogleIcon; diff --git a/src/admin/components/icons/GrokIcon.tsx b/src/admin/components/icons/GrokIcon.tsx new file mode 100644 index 00000000..e6fcb836 --- /dev/null +++ b/src/admin/components/icons/GrokIcon.tsx @@ -0,0 +1,22 @@ +/** + * External dependencies + */ +import type { SVGProps } from 'react'; + +/** + * Grok Icon + */ +const GrokIcon = ( props: SVGProps< SVGSVGElement > ) => ( + + + +); + +export default GrokIcon; diff --git a/src/admin/components/icons/GroqIcon.tsx b/src/admin/components/icons/GroqIcon.tsx new file mode 100644 index 00000000..4f87cfd2 --- /dev/null +++ b/src/admin/components/icons/GroqIcon.tsx @@ -0,0 +1,22 @@ +/** + * External dependencies + */ +import type { SVGProps } from 'react'; + +/** + * Groq Icon + */ +const GroqIcon = ( props: SVGProps< SVGSVGElement > ) => ( + + + +); + +export default GroqIcon; diff --git a/src/admin/components/icons/HuggingFaceIcon.tsx b/src/admin/components/icons/HuggingFaceIcon.tsx new file mode 100644 index 00000000..ba89ab6d --- /dev/null +++ b/src/admin/components/icons/HuggingFaceIcon.tsx @@ -0,0 +1,45 @@ +/** + * External dependencies + */ +import type { SVGProps } from 'react'; + +/** + * Hugging Face Icon + */ +const HuggingFaceIcon = ( props: SVGProps< SVGSVGElement > ) => ( + + + + + + + + +); + +export default HuggingFaceIcon; diff --git a/src/admin/components/icons/McpIcon.tsx b/src/admin/components/icons/McpIcon.tsx new file mode 100644 index 00000000..abe8496a --- /dev/null +++ b/src/admin/components/icons/McpIcon.tsx @@ -0,0 +1,25 @@ +/** + * External dependencies + */ +import type { SVGProps } from 'react'; + +/** + * MCP Icon - Model Context Protocol logo. + */ +const McpIcon = ( props: SVGProps< SVGSVGElement > ) => ( + + Model Context Protocol + + + +); + +export default McpIcon; diff --git a/src/admin/components/icons/OllamaIcon.tsx b/src/admin/components/icons/OllamaIcon.tsx new file mode 100644 index 00000000..eb7211df --- /dev/null +++ b/src/admin/components/icons/OllamaIcon.tsx @@ -0,0 +1,25 @@ +/** + * External dependencies + */ +import type { SVGProps } from 'react'; + +/** + * Ollama Icon + */ +const OllamaIcon = ( props: SVGProps< SVGSVGElement > ) => ( + + + +); + +export default OllamaIcon; diff --git a/src/admin/components/icons/OpenAiIcon.tsx b/src/admin/components/icons/OpenAiIcon.tsx new file mode 100644 index 00000000..6d4803c3 --- /dev/null +++ b/src/admin/components/icons/OpenAiIcon.tsx @@ -0,0 +1,22 @@ +/** + * External dependencies + */ +import type { SVGProps } from 'react'; + +/** + * OpenAI Icon + */ +const OpenAiIcon = ( props: SVGProps< SVGSVGElement > ) => ( + + + +); + +export default OpenAiIcon; diff --git a/src/admin/components/icons/OpenRouterIcon.tsx b/src/admin/components/icons/OpenRouterIcon.tsx new file mode 100644 index 00000000..1d769c21 --- /dev/null +++ b/src/admin/components/icons/OpenRouterIcon.tsx @@ -0,0 +1,25 @@ +/** + * External dependencies + */ +import type { SVGProps } from 'react'; + +/** + * OpenRouter Icon + */ +const OpenRouterIcon = ( props: SVGProps< SVGSVGElement > ) => ( + + + +); + +export default OpenRouterIcon; diff --git a/src/admin/components/icons/XaiIcon.tsx b/src/admin/components/icons/XaiIcon.tsx new file mode 100644 index 00000000..aeca554c --- /dev/null +++ b/src/admin/components/icons/XaiIcon.tsx @@ -0,0 +1,22 @@ +/** + * External dependencies + */ +import type { SVGProps } from 'react'; + +/** + * xAI Icon + */ +const XaiIcon = ( props: SVGProps< SVGSVGElement > ) => ( + + + +); + +export default XaiIcon; diff --git a/src/admin/components/icons/index.ts b/src/admin/components/icons/index.ts new file mode 100644 index 00000000..a950307d --- /dev/null +++ b/src/admin/components/icons/index.ts @@ -0,0 +1,14 @@ +export { default as AiIcon } from './AiIcon'; +export { default as AnthropicIcon } from './AnthropicIcon'; +export { default as CloudflareIcon } from './CloudflareIcon'; +export { default as DeepSeekIcon } from './DeepSeekIcon'; +export { default as FalIcon } from './FalIcon'; +export { default as GoogleIcon } from './GoogleIcon'; +export { default as GroqIcon } from './GroqIcon'; +export { default as GrokIcon } from './GrokIcon'; +export { default as HuggingFaceIcon } from './HuggingFaceIcon'; +export { default as McpIcon } from './McpIcon'; +export { default as OpenAiIcon } from './OpenAiIcon'; +export { default as OpenRouterIcon } from './OpenRouterIcon'; +export { default as OllamaIcon } from './OllamaIcon'; +export { default as XaiIcon } from './XaiIcon'; diff --git a/src/admin/components/provider-icons.tsx b/src/admin/components/provider-icons.tsx new file mode 100644 index 00000000..c7580348 --- /dev/null +++ b/src/admin/components/provider-icons.tsx @@ -0,0 +1,56 @@ +/** + * External dependencies + */ +import type { ComponentType, SVGProps } from 'react'; + +/** + * Internal dependencies + */ +import { + AiIcon, + AnthropicIcon, + CloudflareIcon, + DeepSeekIcon, + FalIcon, + GoogleIcon, + GrokIcon, + GroqIcon, + HuggingFaceIcon, + OllamaIcon, + OpenAiIcon, + OpenRouterIcon, + XaiIcon, +} from './icons'; + +const ICON_COMPONENTS: Record< string, ComponentType< SVGProps< SVGSVGElement > > > = + Object.freeze( { + anthropic: AnthropicIcon, + openai: OpenAiIcon, + google: GoogleIcon, + fal: FalIcon, + 'fal-ai': FalIcon, + deepseek: DeepSeekIcon, + cloudflare: CloudflareIcon, + huggingface: HuggingFaceIcon, + ollama: OllamaIcon, + openrouter: OpenRouterIcon, + groq: GroqIcon, + grok: GrokIcon, + xai: XaiIcon, + default: AiIcon, + } ); + +export const getProviderIconComponent = ( + iconKey?: string, + fallbackKey?: string +): ComponentType< SVGProps< SVGSVGElement > > => { + const normalized = + ( iconKey || fallbackKey || '' ).toLowerCase().replace( /\s+/g, '' ); + + return ( + ICON_COMPONENTS[ normalized ] || + ICON_COMPONENTS[ iconKey || '' ] || + ICON_COMPONENTS[ fallbackKey || '' ] || + ICON_COMPONENTS.default + ); +}; diff --git a/src/admin/hooks/usePersistedView.ts b/src/admin/hooks/usePersistedView.ts new file mode 100644 index 00000000..7305648f --- /dev/null +++ b/src/admin/hooks/usePersistedView.ts @@ -0,0 +1,99 @@ +/** + * WordPress dependencies + */ +import type { View } from '@wordpress/dataviews'; +import { useView } from '@wordpress/views'; + +/** + * External dependencies + */ +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; + +interface PersistedViewReturn< T extends View > { + view: T; + setView: ( next: T | ( ( prev: T ) => T ) ) => void; + resetView: () => void; + isModified: boolean; +} + +const VIEW_KIND = 'root'; +const VIEW_NAME = 'ai-admin'; + +/** + * Thin wrapper around `@wordpress/views` that mimics the previous + * usePersistedView signature so existing DataViews components can persist + * their view configuration using WordPress preferences (available in 6.9+). + */ +export function usePersistedView< T extends View >( + slug: string, + defaultView: T +): PersistedViewReturn< T > { + const defaultQuery = useMemo( + () => ( { + page: defaultView.page ?? 1, + search: defaultView.search ?? '', + } ), + [ defaultView.page, defaultView.search ] + ); + + const [ queryParams, setQueryParams ] = useState( defaultQuery ); + + // Keep query params in sync if defaults change (should be rare). + useEffect( () => { + setQueryParams( ( previous ) => ( { + page: previous.page ?? defaultQuery.page, + search: + typeof previous.search === 'string' + ? previous.search + : defaultQuery.search, + } ) ); + }, [ defaultQuery.page, defaultQuery.search ] ); + + const { view, updateView, resetToDefault, isModified } = useView( { + kind: VIEW_KIND, + name: VIEW_NAME, + slug, + defaultView, + queryParams, + onChangeQueryParams: ( params ) => { + setQueryParams( { + page: + typeof params.page === 'number' + ? params.page + : defaultQuery.page, + search: + typeof params.search === 'string' + ? params.search + : defaultQuery.search, + } ); + }, + } ); + + const latestViewRef = useRef< View >( view ); + useEffect( () => { + latestViewRef.current = view; + }, [ view ] ); + + const setView = useCallback( + ( next: T | ( ( prev: T ) => T ) ) => { + updateView( + typeof next === 'function' + ? ( next as ( prev: T ) => T )( latestViewRef.current as T ) + : next + ); + }, + [ updateView ] + ); + + const resetView = useCallback( () => { + resetToDefault(); + setQueryParams( defaultQuery ); + }, [ resetToDefault, defaultQuery ] ); + + return { + view: view as T, + setView, + resetView, + isModified, + }; +} diff --git a/src/admin/mcp-server/_dataviews-base.css b/src/admin/mcp-server/_dataviews-base.css new file mode 100644 index 00000000..404347e1 --- /dev/null +++ b/src/admin/mcp-server/_dataviews-base.css @@ -0,0 +1,1847 @@ +/** + * Colors + */ +/** + * SCSS Variables. + * + * Please use variables from this sheet to ensure consistency across the UI. + * Don't add to this sheet unless you're pretty sure the value will be reused in many places. + * For example, don't add rules to this sheet that affect block visuals. It's purely for UI. + */ +/** + * Fonts & basic variables. + */ +/** + * Typography + */ +/** + * Grid System. + * https://make.wordpress.org/design/2019/10/31/proposal-a-consistent-spacing-system-for-wordpress/ + */ +/** + * Radius scale. + */ +/** + * Elevation scale. + */ +/** + * Dimensions. + */ +/** + * Mobile specific styles + */ +/** + * Editor styles. + */ +/** + * Block & Editor UI. + */ +/** + * Block paddings. + */ +/** + * React Native specific. + * These variables do not appear to be used anywhere else. + */ +/** + * Typography + */ +/** + * Breakpoints & Media Queries + */ +/** +* Converts a hex value into the rgb equivalent. +* +* @param {string} hex - the hexadecimal value to convert +* @return {string} comma separated rgb values +*/ +/** + * Long content fade mixin + * + * Creates a fading overlay to signify that the content is longer + * than the space allows. + */ +/** + * Breakpoint mixins + */ +/** + * Focus styles. + */ +/** + * Applies editor left position to the selector passed as argument + */ +/** + * Styles that are reused verbatim in a few places + */ +/** + * Allows users to opt-out of animations via OS-level preferences. + */ +/** + * Reset default styles for JavaScript UI based pages. + * This is a WP-admin agnostic reset + */ +/** + * Reset the WP Admin page styles for Gutenberg-like pages. + */ +.dataviews-wrapper, +.dataviews-picker-wrapper { + height: 100%; + overflow: auto; + box-sizing: border-box; + scroll-padding-bottom: 64px; + /* stylelint-disable-next-line property-no-unknown -- '@container' not globally permitted */ + container: dataviews-wrapper/inline-size; + display: flex; + flex-direction: column; + font-size: 13px; + line-height: 1.4; +} + +.dataviews__view-actions, +.dataviews-filters__container { + box-sizing: border-box; + padding: 16px 48px; + flex-shrink: 0; + position: sticky; + left: 0; +} +@media not (prefers-reduced-motion) { + .dataviews__view-actions, + .dataviews-filters__container { + transition: padding ease-out 0.1s; + } +} + +.dataviews-no-results, +.dataviews-loading { + padding: 0 48px; + flex-grow: 1; + display: flex; + align-items: center; + justify-content: center; +} +@media not (prefers-reduced-motion) { + .dataviews-no-results, + .dataviews-loading { + transition: padding ease-out 0.1s; + } +} + +.dataviews-loading-more { + text-align: center; +} + +@container (max-width: 430px) { + .dataviews__view-actions, + .dataviews-filters__container { + padding: 12px 24px; + } + .dataviews-no-results, + .dataviews-loading { + padding-left: 24px; + padding-right: 24px; + } +} +.dataviews-title-field { + font-size: 13px; + font-weight: 499; + color: #2f2f2f; + text-overflow: ellipsis; + white-space: nowrap; + width: 100%; +} +.dataviews-title-field a { + text-decoration: none; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + display: block; + flex-grow: 0; + color: #2f2f2f; +} +.dataviews-title-field a:hover { + color: var(--wp-admin-theme-color); +} +.dataviews-title-field a:focus { + color: var(--wp-admin-theme-color--rgb); + box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color, #007cba); + border-radius: 2px; +} +.dataviews-title-field button.components-button.is-link { + text-decoration: none; + font-weight: inherit; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + display: block; + width: 100%; + color: #1e1e1e; +} +.dataviews-title-field button.components-button.is-link:hover { + color: var(--wp-admin-theme-color); +} + +.dataviews-title-field--clickable { + cursor: pointer; + color: #2f2f2f; +} +.dataviews-title-field--clickable:hover { + color: var(--wp-admin-theme-color); +} +.dataviews-title-field--clickable:focus { + color: var(--wp-admin-theme-color--rgb); + box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color, #007cba); + border-radius: 2px; +} + +/** + * Applying a consistent 24px padding when DataViews are placed within cards. + */ +.components-card__body:has(> .dataviews-wrapper), +.components-card__body:has(> .dataviews-picker-wrapper) { + padding: 8px 0 0; + overflow: hidden; +} +.components-card__body:has(> .dataviews-wrapper) .dataviews__view-actions, +.components-card__body:has(> .dataviews-wrapper) .dataviews-filters__container, +.components-card__body:has(> .dataviews-wrapper) .dataviews-footer, +.components-card__body:has(> .dataviews-wrapper) .dataviews-view-grid, +.components-card__body:has(> .dataviews-wrapper) .dataviews-loading, +.components-card__body:has(> .dataviews-wrapper) .dataviews-no-results, +.components-card__body:has(> .dataviews-picker-wrapper) .dataviews__view-actions, +.components-card__body:has(> .dataviews-picker-wrapper) .dataviews-filters__container, +.components-card__body:has(> .dataviews-picker-wrapper) .dataviews-footer, +.components-card__body:has(> .dataviews-picker-wrapper) .dataviews-view-grid, +.components-card__body:has(> .dataviews-picker-wrapper) .dataviews-loading, +.components-card__body:has(> .dataviews-picker-wrapper) .dataviews-no-results { + padding-inline: 24px; +} +.components-card__body:has(> .dataviews-wrapper) .dataviews-view-table tr td:first-child, +.components-card__body:has(> .dataviews-wrapper) .dataviews-view-table tr th:first-child, +.components-card__body:has(> .dataviews-picker-wrapper) .dataviews-view-table tr td:first-child, +.components-card__body:has(> .dataviews-picker-wrapper) .dataviews-view-table tr th:first-child { + padding-inline-start: 24px; +} +.components-card__body:has(> .dataviews-wrapper) .dataviews-view-table tr td:last-child, +.components-card__body:has(> .dataviews-wrapper) .dataviews-view-table tr th:last-child, +.components-card__body:has(> .dataviews-picker-wrapper) .dataviews-view-table tr td:last-child, +.components-card__body:has(> .dataviews-picker-wrapper) .dataviews-view-table tr th:last-child { + padding-inline-end: 24px; +} + +.dataviews-bulk-actions-footer__item-count { + color: #1e1e1e; + font-weight: 499; + font-size: 11px; + text-transform: uppercase; +} + +.dataviews-bulk-actions-footer__container { + margin-right: auto; + min-height: 32px; +} + +.dataviews-filters__button { + position: relative; +} + +.dataviews-filters__container { + padding-top: 0; +} + +.dataviews-filters__reset-button.dataviews-filters__reset-button[aria-disabled=true], .dataviews-filters__reset-button.dataviews-filters__reset-button[aria-disabled=true]:hover { + opacity: 0; +} +.dataviews-filters__reset-button.dataviews-filters__reset-button[aria-disabled=true]:focus { + opacity: 1; +} + +.dataviews-filters__summary-popover { + font-size: 13px; + line-height: 1.4; +} +.dataviews-filters__summary-popover .components-popover__content { + width: 100%; + min-width: 230px; + max-width: 250px; + border-radius: 4px; +} +.dataviews-filters__summary-popover.components-dropdown__content .components-popover__content { + padding: 0; +} + +.dataviews-filters__summary-operators-container { + padding: 8px 16px; +} +.dataviews-filters__summary-operators-container:has(+ .dataviews-filters__search-widget-listbox), .dataviews-filters__summary-operators-container:has(+ .dataviews-filters__search-widget-no-elements), .dataviews-filters__summary-operators-container:has(+ .dataviews-filters__user-input-widget) { + border-bottom: 1px solid #e0e0e0; +} +.dataviews-filters__summary-operators-container:empty { + display: none; +} +.dataviews-filters__summary-operators-container .dataviews-filters__summary-operators-filter-name { + color: #757575; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + flex-shrink: 0; /* Prevents this element from shrinking */ + max-width: calc(100% - 55px); +} +.dataviews-filters__summary-operators-container .dataviews-filters__summary-operators-filter-select { + width: 100%; + white-space: nowrap; + overflow: hidden; +} + +.dataviews-filters__summary-chip-container { + position: relative; + white-space: pre-wrap; +} +.dataviews-filters__summary-chip-container .dataviews-filters__summary-chip { + border-radius: 16px; + border: 1px solid transparent; + cursor: pointer; + padding: 4px 12px; + min-height: 32px; + background: #f0f0f0; + color: #2f2f2f; + position: relative; + display: flex; + align-items: center; + box-sizing: border-box; +} +.dataviews-filters__summary-chip-container .dataviews-filters__summary-chip.is-not-clickable { + cursor: default; +} +.dataviews-filters__summary-chip-container .dataviews-filters__summary-chip.has-reset { + padding-inline-end: 28px; +} +.dataviews-filters__summary-chip-container .dataviews-filters__summary-chip:hover:not(.dataviews-filters__summary-chip-container .dataviews-filters__summary-chip.is-not-clickable), .dataviews-filters__summary-chip-container .dataviews-filters__summary-chip:focus-visible, .dataviews-filters__summary-chip-container .dataviews-filters__summary-chip[aria-expanded=true] { + background: #e0e0e0; + color: #1e1e1e; +} +.dataviews-filters__summary-chip-container .dataviews-filters__summary-chip.has-values { + color: var(--wp-admin-theme-color); + background: rgba(var(--wp-admin-theme-color--rgb), 0.04); +} +.dataviews-filters__summary-chip-container .dataviews-filters__summary-chip.has-values:hover, .dataviews-filters__summary-chip-container .dataviews-filters__summary-chip.has-values[aria-expanded=true] { + background: rgba(var(--wp-admin-theme-color--rgb), 0.12); +} +.dataviews-filters__summary-chip-container .dataviews-filters__summary-chip:focus-visible { + outline: none; + box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); +} +.dataviews-filters__summary-chip-container .dataviews-filters__summary-chip .dataviews-filters-__summary-filter-text-name { + font-weight: 499; +} +.dataviews-filters__summary-chip-container .dataviews-filters__summary-chip-remove { + width: 24px; + height: 24px; + border-radius: 50%; + border: 0; + padding: 0; + position: absolute; + right: 4px; + top: 50%; + transform: translateY(-50%); + display: flex; + align-items: center; + justify-content: center; + background: transparent; + cursor: pointer; +} +.dataviews-filters__summary-chip-container .dataviews-filters__summary-chip-remove svg { + fill: #757575; +} +.dataviews-filters__summary-chip-container .dataviews-filters__summary-chip-remove:hover, .dataviews-filters__summary-chip-container .dataviews-filters__summary-chip-remove:focus { + background: #e0e0e0; +} +.dataviews-filters__summary-chip-container .dataviews-filters__summary-chip-remove:hover svg, .dataviews-filters__summary-chip-container .dataviews-filters__summary-chip-remove:focus svg { + fill: #1e1e1e; +} +.dataviews-filters__summary-chip-container .dataviews-filters__summary-chip-remove.has-values svg { + fill: var(--wp-admin-theme-color); +} +.dataviews-filters__summary-chip-container .dataviews-filters__summary-chip-remove.has-values:hover { + background: rgba(var(--wp-admin-theme-color--rgb), 0.08); +} +.dataviews-filters__summary-chip-container .dataviews-filters__summary-chip-remove:focus-visible { + outline: none; + box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); +} + +.dataviews-filters__search-widget-filter-combobox-list { + max-height: 184px; + padding: 4px; + overflow: auto; + border-top: 1px solid #e0e0e0; +} +.dataviews-filters__search-widget-filter-combobox-list .dataviews-filters__search-widget-filter-combobox-item-value [data-user-value] { + font-weight: 600; +} + +.dataviews-filters__search-widget-listbox { + padding: 4px; + overflow: auto; +} + +.dataviews-filters__search-widget-listitem { + display: flex; + align-items: center; + gap: 8px; + border-radius: 2px; + box-sizing: border-box; + padding: 4px 12px; + cursor: default; + min-height: 32px; + font-family: -apple-system, "system-ui", "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + font-weight: 400; + font-size: 13px; + line-height: 20px; +} +.dataviews-filters__search-widget-listitem:last-child { + margin-block-end: 0; +} +.dataviews-filters__search-widget-listitem:hover, .dataviews-filters__search-widget-listitem[data-active-item], .dataviews-filters__search-widget-listitem:focus { + background-color: var(--wp-admin-theme-color); + color: #fff; +} +.dataviews-filters__search-widget-listitem:hover .dataviews-filters__search-widget-listitem-description, .dataviews-filters__search-widget-listitem[data-active-item] .dataviews-filters__search-widget-listitem-description, .dataviews-filters__search-widget-listitem:focus .dataviews-filters__search-widget-listitem-description { + color: #fff; +} +.dataviews-filters__search-widget-listitem:hover .dataviews-filters__search-widget-listitem-single-selection, .dataviews-filters__search-widget-listitem[data-active-item] .dataviews-filters__search-widget-listitem-single-selection, .dataviews-filters__search-widget-listitem:focus .dataviews-filters__search-widget-listitem-single-selection { + border-color: var(--wp-admin-theme-color-darker-20, #183ad6); + background: #fff; +} +.dataviews-filters__search-widget-listitem:hover .dataviews-filters__search-widget-listitem-single-selection.is-selected, .dataviews-filters__search-widget-listitem[data-active-item] .dataviews-filters__search-widget-listitem-single-selection.is-selected, .dataviews-filters__search-widget-listitem:focus .dataviews-filters__search-widget-listitem-single-selection.is-selected { + border-color: var(--wp-admin-theme-color-darker-20, #183ad6); + background: var(--wp-admin-theme-color-darker-20, #183ad6); +} +.dataviews-filters__search-widget-listitem:hover .dataviews-filters__search-widget-listitem-multi-selection, .dataviews-filters__search-widget-listitem[data-active-item] .dataviews-filters__search-widget-listitem-multi-selection, .dataviews-filters__search-widget-listitem:focus .dataviews-filters__search-widget-listitem-multi-selection { + border-color: var(--wp-admin-theme-color-darker-20, #183ad6); +} +.dataviews-filters__search-widget-listitem:hover .dataviews-filters__search-widget-listitem-multi-selection.is-selected, .dataviews-filters__search-widget-listitem[data-active-item] .dataviews-filters__search-widget-listitem-multi-selection.is-selected, .dataviews-filters__search-widget-listitem:focus .dataviews-filters__search-widget-listitem-multi-selection.is-selected { + border-color: var(--wp-admin-theme-color-darker-20, #183ad6); + background: var(--wp-admin-theme-color-darker-20, #183ad6); +} +.dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-description { + display: block; + overflow: hidden; + text-overflow: ellipsis; + font-size: 12px; + line-height: 16px; + color: #757575; +} +.dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-single-selection { + border: 1px solid #1e1e1e; + margin-right: 12px; + transition: none; + border-radius: 50%; + width: 24px; + height: 24px; + min-width: 24px; + max-width: 24px; + position: relative; +} +@media not (prefers-reduced-motion) { + .dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-single-selection { + transition: box-shadow 0.1s linear; + } +} +@media (min-width: 600px) { + .dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-single-selection { + height: 16px; + width: 16px; + min-width: 16px; + max-width: 16px; + } +} +.dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-single-selection:checked::before { + box-sizing: inherit; + width: 12px; + height: 12px; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + margin: 0; + background-color: #fff; + border: 4px solid #fff; +} +@media (min-width: 600px) { + .dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-single-selection:checked::before { + width: 8px; + height: 8px; + } +} +.dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-single-selection:focus { + box-shadow: 0 0 0 2px #fff, 0 0 0 4px var(--wp-admin-theme-color); + outline: 2px solid transparent; +} +.dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-single-selection:checked { + background: var(--wp-admin-theme-color); + border: none; +} +.dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-single-selection { + margin: 0; + padding: 0; +} +.dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-single-selection.is-selected { + background: var(--wp-admin-theme-color, #3858e9); + border-color: var(--wp-admin-theme-color, #3858e9); +} +.dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-single-selection.is-selected::before { + content: ""; + border-radius: 50%; + box-sizing: inherit; + width: 12px; + height: 12px; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + margin: 0; + background-color: #fff; + border: 4px solid #fff; +} +@media (min-width: 600px) { + .dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-single-selection.is-selected::before { + width: 8px; + height: 8px; + } +} +.dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-multi-selection { + --checkbox-size: 24px; + border: 1px solid #1e1e1e; + margin-right: 12px; + transition: none; + border-radius: 2px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + padding: 6px 8px; + /* Fonts smaller than 16px causes mobile safari to zoom. */ + font-size: 16px; + /* Override core line-height. To be reviewed. */ + line-height: normal; + box-shadow: 0 0 0 transparent; + border-radius: 2px; + border: 1px solid #949494; +} +@media not (prefers-reduced-motion) { + .dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-multi-selection { + transition: box-shadow 0.1s linear; + } +} +@media (min-width: 600px) { + .dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-multi-selection { + font-size: 13px; + /* Override core line-height. To be reviewed. */ + line-height: normal; + } +} +.dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-multi-selection:focus { + border-color: var(--wp-admin-theme-color); + box-shadow: 0 0 0 0.5px var(--wp-admin-theme-color); + outline: 2px solid transparent; +} +.dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-multi-selection::-webkit-input-placeholder { + color: rgba(30, 30, 30, 0.62); +} +.dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-multi-selection::-moz-placeholder { + color: rgba(30, 30, 30, 0.62); +} +.dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-multi-selection:-ms-input-placeholder { + color: rgba(30, 30, 30, 0.62); +} +.dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-multi-selection:focus { + box-shadow: 0 0 0 2px #fff, 0 0 0 4px var(--wp-admin-theme-color); + outline: 2px solid transparent; +} +.dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-multi-selection:checked { + background: var(--wp-admin-theme-color); + border-color: var(--wp-admin-theme-color); +} +.dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-multi-selection:checked::-ms-check { + opacity: 0; +} +.dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-multi-selection:checked::before, .dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-multi-selection[aria-checked=mixed]::before { + margin: -3px -5px; + color: #fff; +} +@media (min-width: 782px) { + .dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-multi-selection:checked::before, .dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-multi-selection[aria-checked=mixed]::before { + margin: -4px 0 0 -5px; + } +} +.dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-multi-selection[aria-checked=mixed] { + background: var(--wp-admin-theme-color); + border-color: var(--wp-admin-theme-color); +} +.dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-multi-selection[aria-checked=mixed]::before { + content: "\f460"; + float: left; + display: inline-block; + vertical-align: middle; + width: 16px; + /* stylelint-disable-next-line font-family-no-missing-generic-family-keyword -- dashicons don't need a generic family keyword. */ + font: normal 30px/1 dashicons; + speak: none; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +@media (min-width: 782px) { + .dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-multi-selection[aria-checked=mixed]::before { + float: none; + font-size: 21px; + } +} +.dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-multi-selection[aria-disabled=true], .dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-multi-selection:disabled { + background: #f0f0f0; + border-color: #ddd; + cursor: default; + opacity: 1; +} +.dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-multi-selection { + position: relative; + background: #fff; + color: #1e1e1e; + margin: 0; + padding: 0; + width: var(--checkbox-size); + height: var(--checkbox-size); +} +@media (min-width: 600px) { + .dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-multi-selection { + --checkbox-size: 16px; + } +} +@media not (prefers-reduced-motion) { + .dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-multi-selection { + transition: 0.1s border-color ease-in-out; + } +} +.dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-multi-selection.is-selected { + background: var(--wp-admin-theme-color, #3858e9); + border-color: var(--wp-admin-theme-color, #3858e9); +} +.dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-multi-selection.is-selected svg { + --checkmark-size: var(--checkbox-size); + fill: #fff; + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + width: var(--checkmark-size); + height: var(--checkmark-size); +} +@media (min-width: 600px) { + .dataviews-filters__search-widget-listitem .dataviews-filters__search-widget-listitem-multi-selection.is-selected svg { + --checkmark-size: calc(var(--checkbox-size) + 4px); + } +} + +.dataviews-filters__search-widget-filter-combobox__wrapper { + position: relative; + padding: 8px; +} +.dataviews-filters__search-widget-filter-combobox__wrapper .dataviews-filters__search-widget-filter-combobox__input { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + padding: 6px 8px; + /* Fonts smaller than 16px causes mobile safari to zoom. */ + font-size: 16px; + /* Override core line-height. To be reviewed. */ + line-height: normal; + box-shadow: 0 0 0 transparent; + border-radius: 2px; + border: 1px solid #949494; +} +@media not (prefers-reduced-motion) { + .dataviews-filters__search-widget-filter-combobox__wrapper .dataviews-filters__search-widget-filter-combobox__input { + transition: box-shadow 0.1s linear; + } +} +@media (min-width: 600px) { + .dataviews-filters__search-widget-filter-combobox__wrapper .dataviews-filters__search-widget-filter-combobox__input { + font-size: 13px; + /* Override core line-height. To be reviewed. */ + line-height: normal; + } +} +.dataviews-filters__search-widget-filter-combobox__wrapper .dataviews-filters__search-widget-filter-combobox__input:focus { + border-color: var(--wp-admin-theme-color); + box-shadow: 0 0 0 0.5px var(--wp-admin-theme-color); + outline: 2px solid transparent; +} +.dataviews-filters__search-widget-filter-combobox__wrapper .dataviews-filters__search-widget-filter-combobox__input::-webkit-input-placeholder { + color: rgba(30, 30, 30, 0.62); +} +.dataviews-filters__search-widget-filter-combobox__wrapper .dataviews-filters__search-widget-filter-combobox__input::-moz-placeholder { + color: rgba(30, 30, 30, 0.62); +} +.dataviews-filters__search-widget-filter-combobox__wrapper .dataviews-filters__search-widget-filter-combobox__input:-ms-input-placeholder { + color: rgba(30, 30, 30, 0.62); +} +.dataviews-filters__search-widget-filter-combobox__wrapper .dataviews-filters__search-widget-filter-combobox__input { + display: block; + padding: 0 8px 0 32px; + width: 100%; + height: 32px; + margin-left: 0; + margin-right: 0; + /* Fonts smaller than 16px causes mobile safari to zoom. */ + font-size: 16px; +} +@media (min-width: 600px) { + .dataviews-filters__search-widget-filter-combobox__wrapper .dataviews-filters__search-widget-filter-combobox__input { + font-size: 13px; + } +} +.dataviews-filters__search-widget-filter-combobox__wrapper .dataviews-filters__search-widget-filter-combobox__input:focus { + background: #fff; + box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); +} +.dataviews-filters__search-widget-filter-combobox__wrapper .dataviews-filters__search-widget-filter-combobox__input::placeholder { + color: #757575; +} +.dataviews-filters__search-widget-filter-combobox__wrapper .dataviews-filters__search-widget-filter-combobox__input::-webkit-search-decoration, .dataviews-filters__search-widget-filter-combobox__wrapper .dataviews-filters__search-widget-filter-combobox__input::-webkit-search-cancel-button, .dataviews-filters__search-widget-filter-combobox__wrapper .dataviews-filters__search-widget-filter-combobox__input::-webkit-search-results-button, .dataviews-filters__search-widget-filter-combobox__wrapper .dataviews-filters__search-widget-filter-combobox__input::-webkit-search-results-decoration { + -webkit-appearance: none; +} +.dataviews-filters__search-widget-filter-combobox__wrapper .dataviews-filters__search-widget-filter-combobox__icon { + position: absolute; + inset-inline-start: 12px; + top: 0; + bottom: 0; + display: flex; + align-items: center; + justify-content: center; + width: 24px; +} +.dataviews-filters__search-widget-filter-combobox__wrapper .dataviews-filters__search-widget-filter-combobox__icon:dir(ltr) { + transform: scaleX(-1); +} + +.dataviews-filters__container-visibility-toggle { + position: relative; + flex-shrink: 0; +} + +.dataviews-filters-toggle__count { + position: absolute; + top: 0; + right: 0; + transform: translate(50%, -50%); + background: var(--wp-admin-theme-color, #3858e9); + height: 16px; + min-width: 16px; + line-height: 16px; + padding: 0 4px; + text-align: center; + border-radius: 8px; + font-size: 11px; + outline: var(--wp-admin-border-width-focus) solid #fff; + color: #fff; + box-sizing: border-box; +} + +.dataviews-search { + width: fit-content; +} + +.dataviews-filters__user-input-widget { + padding: 16px; +} +.dataviews-filters__user-input-widget .components-input-control__prefix { + padding-left: 8px; +} + +.dataviews-filters__search-widget-no-elements { + display: flex; + align-items: center; + justify-content: center; + padding: 16px; +} + +.dataviews-footer { + position: sticky; + bottom: 0; + left: 0; + background-color: #fff; + padding: 12px 48px; + border-top: 1px solid #f0f0f0; + flex-shrink: 0; +} +@media not (prefers-reduced-motion) { + .dataviews-footer { + transition: padding ease-out 0.1s; + } +} +.dataviews-footer { + z-index: 2; +} + +@container (max-width: 430px) { + .dataviews-footer { + padding: 12px 24px; + } +} +@container (max-width: 560px) { + .dataviews-footer { + flex-direction: column !important; + } + .dataviews-footer .dataviews-bulk-actions-footer__container { + width: 100%; + } + .dataviews-footer .dataviews-bulk-actions-footer__item-count { + flex-grow: 1; + } + .dataviews-footer .dataviews-pagination { + width: 100%; + justify-content: space-between; + } +} +.dataviews-pagination__page-select { + font-size: 11px; + font-weight: 499; + text-transform: uppercase; +} +@media (min-width: 600px) { + .dataviews-pagination__page-select .components-select-control__input { + font-size: 11px !important; + font-weight: 499; + } +} + +.dataviews-action-modal { + z-index: 1000001; +} + +.dataviews-picker-footer__bulk-selection { + align-self: flex-start; + height: 32px; +} + +.dataviews-picker-footer__actions { + align-self: flex-end; +} + +.dataviews-selection-checkbox { + --checkbox-input-size: 24px; +} +@media (min-width: 600px) { + .dataviews-selection-checkbox { + --checkbox-input-size: 16px; + } +} +.dataviews-selection-checkbox { + line-height: 0; + flex-shrink: 0; +} +.dataviews-selection-checkbox .components-checkbox-control__input-container { + margin: 0; +} + +.dataviews-view-config { + width: 320px; + /* stylelint-disable-next-line property-no-unknown -- the linter needs to be updated to accepted the container-type property */ + container-type: inline-size; + font-size: 13px; + line-height: 1.4; +} + +.dataviews-config__popover.is-expanded .dataviews-config__popover-content-wrapper { + overflow-y: scroll; + height: 100%; +} +.dataviews-config__popover.is-expanded .dataviews-config__popover-content-wrapper .dataviews-view-config { + width: auto; +} + +.dataviews-view-config__sort-direction .components-toggle-group-control-option-base { + text-transform: uppercase; +} + +.dataviews-settings-section__title.dataviews-settings-section__title { + line-height: 24px; + font-size: 15px; +} + +.dataviews-settings-section__sidebar { + grid-column: span 4; +} + +.dataviews-settings-section__content, +.dataviews-settings-section__content > * { + grid-column: span 8; +} + +.dataviews-settings-section__content .is-divided-in-two { + display: contents; +} +.dataviews-settings-section__content .is-divided-in-two > * { + grid-column: span 4; +} + +.dataviews-settings-section:has(.dataviews-settings-section__content:empty) { + display: none; +} + +@container (max-width: 500px) { + .dataviews-settings-section.dataviews-settings-section { + grid-template-columns: repeat(2, 1fr); + } + .dataviews-settings-section.dataviews-settings-section .dataviews-settings-section__sidebar { + grid-column: span 2; + } + .dataviews-settings-section.dataviews-settings-section .dataviews-settings-section__content { + grid-column: span 2; + } +} +.dataviews-view-config__label { + text-wrap: nowrap; +} + +.dataviews-view-grid-items { + margin-bottom: auto; + display: grid; + gap: 32px; + grid-template-rows: max-content; + grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); + padding: 0 48px 24px; + container-type: inline-size; + /** + * Breakpoints were adjusted from media queries breakpoints to account for + * the sidebar width. This was done to match the existing styles we had. + */ +} +@container (max-width: 430px) { + .dataviews-view-grid-items { + padding-left: 24px; + padding-right: 24px; + } +} +@media not (prefers-reduced-motion) { + .dataviews-view-grid-items { + transition: padding ease-out 0.1s; + } +} + +.dataviews-view-grid .dataviews-view-grid__card { + height: 100%; + justify-content: flex-start; + position: relative; +} +.dataviews-view-grid .dataviews-view-grid__card .dataviews-view-grid__title-actions { + padding: 8px 0 4px; +} +.dataviews-view-grid .dataviews-view-grid__card .dataviews-view-grid__title-field { + min-height: 24px; + overflow: hidden; + align-content: center; + text-align: start; +} +.dataviews-view-grid .dataviews-view-grid__card .dataviews-view-grid__title-field--clickable { + width: fit-content; +} +.dataviews-view-grid .dataviews-view-grid__card.is-selected .dataviews-view-grid__fields .dataviews-view-grid__field .dataviews-view-grid__field-value { + color: #1e1e1e; +} +.dataviews-view-grid .dataviews-view-grid__card.is-selected .dataviews-view-grid__media::after, +.dataviews-view-grid .dataviews-view-grid__card .dataviews-view-grid__media:focus::after { + background-color: rgba(var(--wp-admin-theme-color--rgb), 0.08); +} +.dataviews-view-grid .dataviews-view-grid__card.is-selected .dataviews-view-grid__media::after { + box-shadow: inset 0 0 0 1px var(--wp-admin-theme-color); +} +.dataviews-view-grid .dataviews-view-grid__card .dataviews-view-grid__media:focus::after { + box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); +} +.dataviews-view-grid .dataviews-view-grid__media { + width: 100%; + aspect-ratio: 1/1; + background-color: #fff; + border-radius: 4px; + overflow: hidden; + position: relative; +} +.dataviews-view-grid .dataviews-view-grid__media img { + object-fit: cover; + width: 100%; + height: 100%; +} +.dataviews-view-grid .dataviews-view-grid__media::after { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1); + border-radius: 4px; + pointer-events: none; +} +.dataviews-view-grid .dataviews-view-grid__fields { + position: relative; + font-size: 12px; + line-height: 16px; +} +.dataviews-view-grid .dataviews-view-grid__fields:not(:empty) { + padding: 0 0 12px; +} +.dataviews-view-grid .dataviews-view-grid__fields .dataviews-view-grid__field-value:not(:empty) { + min-height: 24px; + line-height: 20px; + padding-top: 2px; +} +.dataviews-view-grid .dataviews-view-grid__fields .dataviews-view-grid__field { + min-height: 24px; + align-items: center; +} +.dataviews-view-grid .dataviews-view-grid__fields .dataviews-view-grid__field .dataviews-view-grid__field-name { + width: 35%; + color: #757575; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.dataviews-view-grid .dataviews-view-grid__fields .dataviews-view-grid__field .dataviews-view-grid__field-value { + width: 65%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.dataviews-view-grid .dataviews-view-grid__fields .dataviews-view-grid__field:not(:has(.dataviews-view-grid__field-value:not(:empty))) { + display: none; +} +.dataviews-view-grid .dataviews-view-grid__badge-fields:not(:empty) { + padding-bottom: 12px; +} + +.dataviews-view-grid__field-value:empty, +.dataviews-view-grid__field:empty { + display: none; +} + +.dataviews-view-grid__card .dataviews-selection-checkbox { + position: absolute; + top: -9999em; + left: 8px; + z-index: 1; + opacity: 0; +} +@media not (prefers-reduced-motion) { + .dataviews-view-grid__card .dataviews-selection-checkbox { + transition: opacity 0.1s linear; + } +} +@media (hover: none) { + .dataviews-view-grid__card .dataviews-selection-checkbox { + opacity: 1; + top: 8px; + } +} + +.dataviews-view-grid__card:hover .dataviews-selection-checkbox, +.dataviews-view-grid__card:focus-within .dataviews-selection-checkbox, +.dataviews-view-grid__card.is-selected .dataviews-selection-checkbox { + opacity: 1; + top: 8px; +} + +.dataviews-view-grid__card .dataviews-view-grid__media-actions { + position: absolute; + top: 4px; + opacity: 0; + right: 4px; +} +.dataviews-view-grid__card .dataviews-view-grid__media-actions .dataviews-all-actions-button { + background-color: #fff; +} +@media not (prefers-reduced-motion) { + .dataviews-view-grid__card .dataviews-view-grid__media-actions { + transition: opacity 0.1s linear; + } +} +@media (hover: none) { + .dataviews-view-grid__card .dataviews-view-grid__media-actions { + opacity: 1; + top: 4px; + } +} + +.dataviews-view-grid__card:hover .dataviews-view-grid__media-actions, +.dataviews-view-grid__card:focus-within .dataviews-view-grid__media-actions, +.dataviews-view-grid__card .dataviews-view-grid__media-actions:has(.dataviews-all-actions-button[aria-expanded=true]) { + opacity: 1; +} + +.dataviews-view-grid__media--clickable { + cursor: pointer; +} + +.dataviews-view-grid__group-header { + font-size: 15px; + font-weight: 499; + color: #1e1e1e; + margin: 0 0 8px 0; + padding: 0 48px; + container-type: inline-size; +} +@container (max-width: 430px) { + .dataviews-view-grid__group-header { + padding-left: 24px; + padding-right: 24px; + } +} + +div.dataviews-view-list { + list-style-type: none; +} + +.dataviews-view-list { + margin: 0 0 auto; +} +.dataviews-view-list div[role=row], +.dataviews-view-list div[role=article] { + margin: 0; + border-top: 1px solid #f0f0f0; +} +.dataviews-view-list div[role=row] .dataviews-view-list__item-wrapper, +.dataviews-view-list div[role=article] .dataviews-view-list__item-wrapper { + position: relative; + padding: 16px 24px; + box-sizing: border-box; +} +.dataviews-view-list div[role=row] .dataviews-view-list__item-actions, +.dataviews-view-list div[role=article] .dataviews-view-list__item-actions { + display: flex; + width: max-content; + flex: 0 0 auto; + gap: 4px; +} +.dataviews-view-list div[role=row] .dataviews-view-list__item-actions .components-button, +.dataviews-view-list div[role=article] .dataviews-view-list__item-actions .components-button { + position: relative; + z-index: 1; +} +.dataviews-view-list div[role=row] .dataviews-view-list__item-actions > div, +.dataviews-view-list div[role=article] .dataviews-view-list__item-actions > div { + height: 24px; +} +.dataviews-view-list div[role=row] .dataviews-view-list__item-actions > :not(:last-child), +.dataviews-view-list div[role=article] .dataviews-view-list__item-actions > :not(:last-child) { + flex: 0; + overflow: hidden; + width: 0; +} +.dataviews-view-list div[role=row]:where(.is-selected, .is-hovered, :focus-within) .dataviews-view-list__item-actions > :not(:last-child), +.dataviews-view-list div[role=article]:where(.is-selected, .is-hovered, :focus-within) .dataviews-view-list__item-actions > :not(:last-child) { + flex-basis: min-content; + width: auto; + overflow: unset; +} +@media (hover: none) { + .dataviews-view-list div[role=row] .dataviews-view-list__item-actions > :not(:last-child), + .dataviews-view-list div[role=article] .dataviews-view-list__item-actions > :not(:last-child) { + flex-basis: min-content; + width: auto; + overflow: unset; + } +} +.dataviews-view-list div[role=row].is-selected.is-selected, +.dataviews-view-list div[role=article].is-selected.is-selected { + border-top: 1px solid rgba(var(--wp-admin-theme-color--rgb), 0.12); +} +.dataviews-view-list div[role=row].is-selected.is-selected + div[role=row], .dataviews-view-list div[role=row].is-selected.is-selected + div[role=article], +.dataviews-view-list div[role=article].is-selected.is-selected + div[role=row], +.dataviews-view-list div[role=article].is-selected.is-selected + div[role=article] { + border-top: 1px solid rgba(var(--wp-admin-theme-color--rgb), 0.12); +} +.dataviews-view-list div[role=row]:not(.is-selected) .dataviews-view-list__title-field, +.dataviews-view-list div[role=article]:not(.is-selected) .dataviews-view-list__title-field { + color: #1e1e1e; +} +.dataviews-view-list div[role=row]:not(.is-selected):hover, .dataviews-view-list div[role=row]:not(.is-selected).is-hovered, .dataviews-view-list div[role=row]:not(.is-selected):focus-within, +.dataviews-view-list div[role=article]:not(.is-selected):hover, +.dataviews-view-list div[role=article]:not(.is-selected).is-hovered, +.dataviews-view-list div[role=article]:not(.is-selected):focus-within { + color: var(--wp-admin-theme-color); + background-color: #f8f8f8; +} +.dataviews-view-list div[role=row]:not(.is-selected):hover .dataviews-view-list__title-field, +.dataviews-view-list div[role=row]:not(.is-selected):hover .dataviews-view-list__fields, .dataviews-view-list div[role=row]:not(.is-selected).is-hovered .dataviews-view-list__title-field, +.dataviews-view-list div[role=row]:not(.is-selected).is-hovered .dataviews-view-list__fields, .dataviews-view-list div[role=row]:not(.is-selected):focus-within .dataviews-view-list__title-field, +.dataviews-view-list div[role=row]:not(.is-selected):focus-within .dataviews-view-list__fields, +.dataviews-view-list div[role=article]:not(.is-selected):hover .dataviews-view-list__title-field, +.dataviews-view-list div[role=article]:not(.is-selected):hover .dataviews-view-list__fields, +.dataviews-view-list div[role=article]:not(.is-selected).is-hovered .dataviews-view-list__title-field, +.dataviews-view-list div[role=article]:not(.is-selected).is-hovered .dataviews-view-list__fields, +.dataviews-view-list div[role=article]:not(.is-selected):focus-within .dataviews-view-list__title-field, +.dataviews-view-list div[role=article]:not(.is-selected):focus-within .dataviews-view-list__fields { + color: var(--wp-admin-theme-color); +} +.dataviews-view-list div[role=row].is-selected .dataviews-view-list__item-wrapper, +.dataviews-view-list div[role=row].is-selected:focus-within .dataviews-view-list__item-wrapper, +.dataviews-view-list div[role=article].is-selected .dataviews-view-list__item-wrapper, +.dataviews-view-list div[role=article].is-selected:focus-within .dataviews-view-list__item-wrapper { + background-color: rgba(var(--wp-admin-theme-color--rgb), 0.04); + color: #1e1e1e; +} +.dataviews-view-list div[role=row].is-selected .dataviews-view-list__item-wrapper .dataviews-view-list__title-field, +.dataviews-view-list div[role=row].is-selected .dataviews-view-list__item-wrapper .dataviews-view-list__fields, +.dataviews-view-list div[role=row].is-selected:focus-within .dataviews-view-list__item-wrapper .dataviews-view-list__title-field, +.dataviews-view-list div[role=row].is-selected:focus-within .dataviews-view-list__item-wrapper .dataviews-view-list__fields, +.dataviews-view-list div[role=article].is-selected .dataviews-view-list__item-wrapper .dataviews-view-list__title-field, +.dataviews-view-list div[role=article].is-selected .dataviews-view-list__item-wrapper .dataviews-view-list__fields, +.dataviews-view-list div[role=article].is-selected:focus-within .dataviews-view-list__item-wrapper .dataviews-view-list__title-field, +.dataviews-view-list div[role=article].is-selected:focus-within .dataviews-view-list__item-wrapper .dataviews-view-list__fields { + color: var(--wp-admin-theme-color); +} +.dataviews-view-list .dataviews-view-list__item { + position: absolute; + z-index: 1; + inset: 0; + scroll-margin: 8px 0; + appearance: none; + border: none; + background: none; + padding: 0; + cursor: pointer; +} +.dataviews-view-list .dataviews-view-list__item:focus-visible { + outline: none; +} +.dataviews-view-list .dataviews-view-list__item:focus-visible::before { + position: absolute; + content: ""; + inset: var(--wp-admin-border-width-focus); + box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); + border-radius: 2px; + outline: 2px solid transparent; +} +.dataviews-view-list .dataviews-view-list__title-field { + flex: 1; + min-height: 24px; + line-height: 24px; + overflow: hidden; +} +.dataviews-view-list .dataviews-view-list__title-field:has(a, button) { + z-index: 1; +} +.dataviews-view-list .dataviews-view-list__media-wrapper { + width: 52px; + height: 52px; + overflow: hidden; + position: relative; + flex-shrink: 0; + background-color: #fff; + border-radius: 4px; +} +.dataviews-view-list .dataviews-view-list__media-wrapper img { + width: 100%; + height: 100%; + object-fit: cover; +} +.dataviews-view-list .dataviews-view-list__media-wrapper::after { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1); + border-radius: 4px; +} +.dataviews-view-list .dataviews-view-list__field-wrapper { + min-height: 52px; + flex-grow: 1; +} +.dataviews-view-list .dataviews-view-list__fields { + color: #757575; + display: flex; + gap: 12px; + row-gap: 4px; + flex-wrap: wrap; + font-size: 12px; +} +.dataviews-view-list .dataviews-view-list__fields:empty { + display: none; +} +.dataviews-view-list .dataviews-view-list__fields .dataviews-view-list__field:has(.dataviews-view-list__field-value:empty) { + display: none; +} +.dataviews-view-list .dataviews-view-list__fields .dataviews-view-list__field-value { + min-height: 24px; + line-height: 20px; + display: flex; + align-items: center; +} +.dataviews-view-list + .dataviews-pagination { + justify-content: space-between; +} + +.dataviews-view-list__group-header { + font-size: 15px; + font-weight: 499; + color: #1e1e1e; + margin: 0 0 8px 0; + padding: 0 24px; +} + +.dataviews-view-table { + width: 100%; + text-indent: 0; + border-color: inherit; + border-collapse: collapse; + position: relative; + color: #757575; + margin-bottom: auto; +} +.dataviews-view-table th { + text-align: left; + color: #1e1e1e; + font-weight: normal; + font-size: 13px; +} +.dataviews-view-table td, +.dataviews-view-table th { + padding: 12px; +} +.dataviews-view-table td.dataviews-view-table__actions-column, +.dataviews-view-table th.dataviews-view-table__actions-column { + text-align: right; +} +.dataviews-view-table td.dataviews-view-table__actions-column--sticky, +.dataviews-view-table th.dataviews-view-table__actions-column--sticky { + position: sticky; + right: 0; + background-color: #fff; +} +.dataviews-view-table td.dataviews-view-table__actions-column--stuck::after, +.dataviews-view-table th.dataviews-view-table__actions-column--stuck::after { + display: block; + content: ""; + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 1px; + background-color: #f0f0f0; +} +.dataviews-view-table td.dataviews-view-table__checkbox-column, +.dataviews-view-table th.dataviews-view-table__checkbox-column { + padding-right: 0; +} +.dataviews-view-table td.dataviews-view-table__checkbox-column .dataviews-view-table__cell-content-wrapper, +.dataviews-view-table th.dataviews-view-table__checkbox-column .dataviews-view-table__cell-content-wrapper { + max-width: auto; + min-width: auto; +} +.dataviews-view-table tr { + border-top: 1px solid #f0f0f0; +} +.dataviews-view-table tr .dataviews-view-table-header-button { + gap: 4px; +} +.dataviews-view-table tr td:first-child, +.dataviews-view-table tr th:first-child { + padding-left: 48px; +} +.dataviews-view-table tr td:first-child .dataviews-view-table-header-button, +.dataviews-view-table tr th:first-child .dataviews-view-table-header-button { + margin-left: -8px; +} +.dataviews-view-table tr td:last-child, +.dataviews-view-table tr th:last-child { + padding-right: 48px; +} +.dataviews-view-table tr:last-child { + border-bottom: 0; +} +.dataviews-view-table tr.is-hovered, .dataviews-view-table tr.is-hovered .dataviews-view-table__actions-column--sticky { + background-color: #f8f8f8; +} +.dataviews-view-table tr .components-checkbox-control__input.components-checkbox-control__input { + opacity: 0; +} +.dataviews-view-table tr .components-checkbox-control__input.components-checkbox-control__input:checked, .dataviews-view-table tr .components-checkbox-control__input.components-checkbox-control__input:indeterminate, .dataviews-view-table tr .components-checkbox-control__input.components-checkbox-control__input:focus { + opacity: 1; +} +.dataviews-view-table tr .dataviews-item-actions .components-button:not(.dataviews-all-actions-button) { + opacity: 0; +} +.dataviews-view-table tr:focus-within .components-checkbox-control__input, +.dataviews-view-table tr:focus-within .dataviews-item-actions .components-button:not(.dataviews-all-actions-button), .dataviews-view-table tr.is-hovered .components-checkbox-control__input, +.dataviews-view-table tr.is-hovered .dataviews-item-actions .components-button:not(.dataviews-all-actions-button), .dataviews-view-table tr:hover .components-checkbox-control__input, +.dataviews-view-table tr:hover .dataviews-item-actions .components-button:not(.dataviews-all-actions-button) { + opacity: 1; +} +@media (hover: none) { + .dataviews-view-table tr .components-checkbox-control__input.components-checkbox-control__input, + .dataviews-view-table tr .dataviews-item-actions .components-button:not(.dataviews-all-actions-button) { + opacity: 1; + } +} +.dataviews-view-table tr.is-selected { + background-color: rgba(var(--wp-admin-theme-color--rgb), 0.04); + color: #757575; +} +.dataviews-view-table tr.is-selected, .dataviews-view-table tr.is-selected + tr { + border-top: 1px solid rgba(var(--wp-admin-theme-color--rgb), 0.12); +} +.dataviews-view-table tr.is-selected:hover { + background-color: rgba(var(--wp-admin-theme-color--rgb), 0.08); +} +.dataviews-view-table tr.is-selected .dataviews-view-table__actions-column--sticky { + background-color: color-mix(in srgb, rgb(var(--wp-admin-theme-color--rgb)) 4%, #fff); +} +.dataviews-view-table tr.is-selected:hover .dataviews-view-table__actions-column--sticky { + background-color: color-mix(in srgb, rgb(var(--wp-admin-theme-color--rgb)) 8%, #fff); +} +.dataviews-view-table thead { + position: sticky; + inset-block-start: 0; + z-index: 1; +} +.dataviews-view-table thead tr { + border: 0; +} +.dataviews-view-table thead tr .components-checkbox-control__input.components-checkbox-control__input { + opacity: 1; +} +.dataviews-view-table thead th { + background-color: #fff; + padding-top: 8px; + padding-bottom: 8px; + padding-left: 12px; + font-size: 11px; + text-transform: uppercase; + font-weight: 499; +} +.dataviews-view-table thead th:has(.dataviews-view-table-header-button):not(:first-child) { + padding-left: 4px; +} +.dataviews-view-table tbody td { + vertical-align: top; +} +.dataviews-view-table tbody .dataviews-view-table__cell-content-wrapper { + min-height: 32px; + display: flex; + align-items: center; + white-space: nowrap; +} +.dataviews-view-table tbody .dataviews-view-table__cell-content-wrapper.dataviews-view-table__cell-align-end { + justify-content: flex-end; +} +.dataviews-view-table tbody .dataviews-view-table__cell-content-wrapper.dataviews-view-table__cell-align-center { + justify-content: center; +} +.dataviews-view-table tbody .components-v-stack > .dataviews-view-table__cell-content-wrapper:not(:first-child) { + min-height: 0; +} +.dataviews-view-table .dataviews-view-table-header-button { + padding: 4px 8px; + font-size: 11px; + text-transform: uppercase; + font-weight: 499; +} +.dataviews-view-table .dataviews-view-table-header-button:not(:hover) { + color: #1e1e1e; +} +.dataviews-view-table .dataviews-view-table-header-button span { + speak: none; +} +.dataviews-view-table .dataviews-view-table-header-button span:empty { + display: none; +} +.dataviews-view-table .dataviews-view-table-header { + padding-left: 4px; +} +.dataviews-view-table .dataviews-view-table__actions-column { + width: auto; + white-space: nowrap; +} +.dataviews-view-table:has(tr.is-selected) .components-checkbox-control__input { + opacity: 1; +} +.dataviews-view-table.has-compact-density thead th:has(.dataviews-view-table-header-button):not(:first-child) { + padding-left: 0; +} +.dataviews-view-table.has-compact-density td, +.dataviews-view-table.has-compact-density th { + padding: 4px 8px; +} +.dataviews-view-table.has-comfortable-density td, +.dataviews-view-table.has-comfortable-density th { + padding: 16px 12px; +} +.dataviews-view-table.has-compact-density td.dataviews-view-table__checkbox-column, +.dataviews-view-table.has-compact-density th.dataviews-view-table__checkbox-column, .dataviews-view-table.has-comfortable-density td.dataviews-view-table__checkbox-column, +.dataviews-view-table.has-comfortable-density th.dataviews-view-table__checkbox-column { + padding-right: 0; +} + +@container (max-width: 430px) { + .dataviews-view-table tr td:first-child, + .dataviews-view-table tr th:first-child { + padding-left: 24px; + } + .dataviews-view-table tr td:last-child, + .dataviews-view-table tr th:last-child { + padding-right: 24px; + } +} +.dataviews-view-table-selection-checkbox { + --checkbox-input-size: 24px; +} +@media (min-width: 600px) { + .dataviews-view-table-selection-checkbox { + --checkbox-input-size: 16px; + } +} + +.dataviews-column-primary__media { + max-width: 60px; + overflow: hidden; + position: relative; + flex-shrink: 0; + background-color: #fff; + border-radius: 4px; +} +.dataviews-column-primary__media img { + width: 100%; + height: 100%; + object-fit: cover; +} +.dataviews-column-primary__media::after { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1); + border-radius: 4px; +} + +.dataviews-view-table__cell-content-wrapper:not(.dataviews-column-primary__media), +.dataviews-view-table__primary-column-content:not(.dataviews-column-primary__media) { + min-width: 15ch; + max-width: 80ch; +} + +.dataviews-view-table__group-header-row .dataviews-view-table__group-header-cell { + font-weight: 499; + padding: 12px 48px; + color: #1e1e1e; +} + +/* Column width intents via colgroup: make non-primary columns shrink-to-fit */ +.dataviews-view-table col[class^=dataviews-view-table__col-]:not(.dataviews-view-table__col-primary) { + width: 1%; +} + +.dataviews-view-picker-grid .dataviews-view-picker-grid__card { + height: 100%; + justify-content: flex-start; + position: relative; +} +.dataviews-view-picker-grid .dataviews-view-picker-grid__card .dataviews-view-picker-grid__title-actions { + padding: 8px 0 4px; +} +.dataviews-view-picker-grid .dataviews-view-picker-grid__card .dataviews-view-picker-grid__title-field { + min-height: 24px; + overflow: hidden; + align-content: center; + text-align: start; +} +.dataviews-view-picker-grid .dataviews-view-picker-grid__card .dataviews-view-picker-grid__title-field--clickable { + width: fit-content; +} +.dataviews-view-picker-grid .dataviews-view-picker-grid__card.is-selected .dataviews-view-picker-grid__fields .dataviews-view-picker-grid__field .dataviews-view-picker-grid__field-value { + color: #1e1e1e; +} +.dataviews-view-picker-grid .dataviews-view-picker-grid__card.is-selected .dataviews-view-picker-grid__media::after, +.dataviews-view-picker-grid .dataviews-view-picker-grid__card .dataviews-view-picker-grid__media:focus::after { + background-color: rgba(var(--wp-admin-theme-color--rgb), 0.08); +} +.dataviews-view-picker-grid .dataviews-view-picker-grid__card.is-selected .dataviews-view-picker-grid__media::after { + box-shadow: inset 0 0 0 1px var(--wp-admin-theme-color); +} +.dataviews-view-picker-grid .dataviews-view-picker-grid__card .dataviews-view-picker-grid__media:focus::after { + box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); +} +.dataviews-view-picker-grid:focus-visible[aria-activedescendant] { + outline: none; +} +.dataviews-view-picker-grid:focus-visible [data-active-item=true] { + outline: 2px solid var(--wp-admin-theme-color); +} +.dataviews-view-picker-grid .dataviews-selection-checkbox { + top: 8px !important; +} +.dataviews-view-picker-grid .dataviews-selection-checkbox input { + pointer-events: none; +} +.dataviews-view-picker-grid .dataviews-view-picker-grid__media { + width: 100%; + aspect-ratio: 1/1; + background-color: #fff; + border-radius: 4px; + position: relative; +} +.dataviews-view-picker-grid .dataviews-view-picker-grid__media img { + object-fit: cover; + width: 100%; + height: 100%; +} +.dataviews-view-picker-grid .dataviews-view-picker-grid__media::after { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1); + border-radius: 4px; + pointer-events: none; +} +.dataviews-view-picker-grid .dataviews-view-picker-grid__fields { + position: relative; + font-size: 12px; + line-height: 16px; +} +.dataviews-view-picker-grid .dataviews-view-picker-grid__fields:not(:empty) { + padding: 0 0 12px; +} +.dataviews-view-picker-grid .dataviews-view-picker-grid__fields .dataviews-view-picker-grid__field-value:not(:empty) { + min-height: 24px; + line-height: 20px; + padding-top: 2px; +} +.dataviews-view-picker-grid .dataviews-view-picker-grid__fields .dataviews-view-picker-grid__field { + min-height: 24px; + align-items: center; +} +.dataviews-view-picker-grid .dataviews-view-picker-grid__fields .dataviews-view-picker-grid__field .dataviews-view-picker-grid__field-name { + width: 35%; + color: #757575; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.dataviews-view-picker-grid .dataviews-view-picker-grid__fields .dataviews-view-picker-grid__field .dataviews-view-picker-grid__field-value { + width: 65%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.dataviews-view-picker-grid .dataviews-view-picker-grid__fields .dataviews-view-picker-grid__field:not(:has(.dataviews-view-picker-grid__field-value:not(:empty))) { + display: none; +} +.dataviews-view-picker-grid .dataviews-view-picker-grid__badge-fields:not(:empty) { + padding-bottom: 12px; +} + +.dataviews-view-picker-grid__field-value:empty, +.dataviews-view-picker-grid__field:empty { + display: none; +} + +.dataviews-view-picker-grid__card .dataviews-selection-checkbox { + position: absolute; + top: -9999em; + left: 8px; + z-index: 1; +} +@media (hover: none) { + .dataviews-view-picker-grid__card .dataviews-selection-checkbox { + top: 8px; + } +} + +.dataviews-view-picker-grid__card:hover .dataviews-selection-checkbox, +.dataviews-view-picker-grid__card:focus-within .dataviews-selection-checkbox, +.dataviews-view-picker-grid__card.is-selected .dataviews-selection-checkbox { + top: 8px; +} + +.dataviews-view-picker-grid__media--clickable { + cursor: pointer; +} + +.dataviews-view-picker-grid-group__header { + font-size: 15px; + font-weight: 499; + color: #1e1e1e; + margin: 0 0 8px 0; + padding: 0 48px; +} + +.dataviews-view-picker-table tbody:focus-visible[aria-activedescendant] { + outline: none; +} +.dataviews-view-picker-table tbody:focus-visible [data-active-item=true] { + outline: 2px solid var(--wp-admin-theme-color); +} +.dataviews-view-picker-table .dataviews-selection-checkbox .components-checkbox-control__input.components-checkbox-control__input { + pointer-events: none; + opacity: 1; +} +.dataviews-view-picker-table .dataviews-view-table__row { + cursor: pointer; +} +.dataviews-view-picker-table .dataviews-view-table__row.is-selected { + background-color: rgba(var(--wp-admin-theme-color--rgb), 0.04); +} +.dataviews-view-picker-table .dataviews-view-table__row.is-hovered { + background-color: rgba(var(--wp-admin-theme-color--rgb), 0.08); +} +.dataviews-view-picker-table .dataviews-view-table__row.is-selected.is-hovered { + background-color: rgba(var(--wp-admin-theme-color--rgb), 0.12); +} + +.dataviews-controls__datetime { + border: none; + padding: 0; +} + +.dataviews-controls__relative-date-number, +.dataviews-controls__relative-date-unit { + flex: 1 1 50%; +} + +.dataviews-controls__date input[type=date]::-webkit-inner-spin-button, +.dataviews-controls__date input[type=date]::-webkit-calendar-picker-indicator { + display: none; + -webkit-appearance: none; +} + +.dataviews-controls__date-preset { + border: 1px solid #ddd; +} +.dataviews-controls__date-preset:active { + background-color: #000; +} + +.dataforms-layouts-panel__field { + width: 100%; + min-height: 32px; + justify-content: flex-start !important; + align-items: flex-start !important; +} + +.dataforms-layouts-panel__field-label { + width: 38%; + flex-shrink: 0; + min-height: 32px; + display: flex; + align-items: center; + line-height: 20px; + hyphens: auto; +} +.dataforms-layouts-panel__field-label--label-position-side { + align-self: center; +} + +.dataforms-layouts-panel__field-control { + flex-grow: 1; + min-height: 32px; + display: flex; + align-items: center; +} +.dataforms-layouts-panel__field-control .components-button { + max-width: 100%; + text-align: left; + white-space: normal; + text-wrap: balance; + text-wrap: pretty; + min-height: 32px; +} +.dataforms-layouts-panel__field-control.components-button.is-link[aria-disabled=true] { + text-decoration: none; +} +.dataforms-layouts-panel__field-control .components-dropdown { + max-width: 100%; +} + +.dataforms-layouts-panel__field-dropdown .components-popover__content { + min-width: 320px; + padding: 16px; +} + +.dataforms-layouts-panel__dropdown-header { + margin-bottom: 16px; +} + +.dataforms-layouts-panel__modal-footer { + margin-top: 16px; +} + +.components-popover.components-dropdown__content.dataforms-layouts-panel__field-dropdown { + z-index: 159990; +} + +.dataforms-layouts-regular__field { + width: 100%; + min-height: 32px; + justify-content: flex-start !important; + align-items: flex-start !important; +} + +.dataforms-layouts-regular__field-label { + width: 38%; + flex-shrink: 0; + min-height: 32px; + display: flex; + align-items: center; + line-height: 20px; + hyphens: auto; +} +.dataforms-layouts-regular__field-label--label-position-side { + align-self: center; +} + +.dataforms-layouts-regular__field-control { + flex-grow: 1; + min-height: 32px; + display: flex; + align-items: center; +} + +.dataforms-layouts-card__field-header-label { + font-family: -apple-system, "system-ui", "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + font-weight: 499; + font-size: 15px; + line-height: 20px; +} + +.dataforms-layouts-card__field { + width: 100%; +} + +.dataforms-layouts-card__field-description { + color: #757575; + display: block; + font-size: 13px; + margin-bottom: 16px; +} + +.dataforms-layouts-card__field-summary { + display: flex; + flex-direction: row; + gap: 16px; +} + +.dataforms-layouts-details__content { + padding-top: 12px; +} + +.dataforms-layouts-row__field-control { + width: 100%; +} + +.dataforms-layouts__wrapper { + font-family: -apple-system, "system-ui", "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + font-weight: 400; + font-size: 13px; + line-height: 20px; +} \ No newline at end of file diff --git a/src/admin/mcp-server/components/ConfigGenerator.tsx b/src/admin/mcp-server/components/ConfigGenerator.tsx new file mode 100644 index 00000000..a96cc544 --- /dev/null +++ b/src/admin/mcp-server/components/ConfigGenerator.tsx @@ -0,0 +1,143 @@ +/** + * WordPress dependencies + */ +import { + Button, + Card, + CardBody, + CardHeader, + SelectControl, + TextareaControl, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +/** + * External dependencies + */ +import React, { useEffect, useMemo, useState } from 'react'; + +/** + * Internal dependencies + */ +import type { ConfigTemplate, CopyHandler } from '../types'; + +interface ConfigGeneratorProps { + templates: Record< string, ConfigTemplate >; + onCopy: CopyHandler; +} + +const ConfigGenerator: React.FC< ConfigGeneratorProps > = ( { + templates, + onCopy, +} ) => { + const templateEntries = useMemo( + () => Object.values( templates ), + [ templates ] + ); + const hasTemplates = templateEntries.length > 0; + const [ selected, setSelected ] = useState( + templateEntries[ 0 ]?.id ?? '' + ); + + useEffect( () => { + if ( ! templateEntries.length ) { + return; + } + + if ( ! templateEntries.find( ( tpl ) => tpl.id === selected ) ) { + setSelected( templateEntries[ 0 ].id ); + } + }, [ selected, templateEntries ] ); + + const activeTemplate = useMemo( () => { + if ( ! hasTemplates ) { + return null; + } + + return ( + templateEntries.find( ( tpl ) => tpl.id === selected ) ?? + templateEntries[ 0 ] + ); + }, [ hasTemplates, selected, templateEntries ] ); + + const handleCopy = () => { + if ( activeTemplate ) { + onCopy( + activeTemplate.content, + __( 'Client configuration', 'ai' ) + ); + } + }; + + const handleDownload = () => { + if ( ! activeTemplate ) { + return; + } + + const blob = new Blob( [ activeTemplate.content ], { + type: 'application/json', + } ); + const url = URL.createObjectURL( blob ); + const anchor = document.createElement( 'a' ); + anchor.href = url; + anchor.download = activeTemplate.fileName; + anchor.click(); + URL.revokeObjectURL( url ); + }; + + return ( + + +

{ __( 'Client configuration', 'ai' ) }

+
+ + { hasTemplates ? ( + <> + + setSelected( value ) + } + options={ templateEntries.map( ( tpl ) => ( { + label: tpl.fileName, + value: tpl.id, + } ) ) } + __nextHasNoMarginBottom + __next40pxDefaultSize + /> + +
+ + +
+ + ) : ( +

+ { __( + 'Once the MCP HTTP endpoint is available we will generate ready-to-use config snippets for Claude Desktop, Cursor, and other clients.', + 'ai' + ) } +

+ ) } +
+
+ ); +}; + +export default ConfigGenerator; diff --git a/src/admin/mcp-server/components/ServerStatusCard.tsx b/src/admin/mcp-server/components/ServerStatusCard.tsx new file mode 100644 index 00000000..8f3bf5b1 --- /dev/null +++ b/src/admin/mcp-server/components/ServerStatusCard.tsx @@ -0,0 +1,108 @@ +/** + * WordPress dependencies + */ +import { Button, Card, CardBody, CardHeader } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +/** + * External dependencies + */ +import React from 'react'; + +/** + * Internal dependencies + */ +import type { CopyHandler, ServerDetails } from '../types'; + +interface ServerStatusCardProps { + server: ServerDetails; + onCopy: CopyHandler; + profileUrl: string; +} + +const ServerStatusCard: React.FC< ServerStatusCardProps > = ( { + server, + onCopy, + profileUrl, +} ) => { + const endpoint = server.http_endpoint ?? ''; + const cliCommand = server.cli_command ?? ''; + + return ( + + +
+
+

{ server.name }

+
+
+
+ +
+
+ { __( 'HTTP endpoint', 'ai' ) } +
+
+ + { endpoint || __( 'Not available yet', 'ai' ) } + + +
+
+ +
+
+ { __( 'WP-CLI (STDIO)', 'ai' ) } +
+
+ + { cliCommand || + __( + 'CLI command will appear once the server starts.', + 'ai' + ) } + + +
+
+ +
+
+ { __( 'REST route', 'ai' ) } +
+ { `/${ server.route_namespace }/${ server.route }` } +
+ +

+ { __( + 'Use an Application Password when connecting Claude Desktop, Cursor, or other MCP clients.', + 'ai' + ) } +

+ +
+
+ ); +}; + +export default ServerStatusCard; diff --git a/src/admin/mcp-server/components/StatusBadge.tsx b/src/admin/mcp-server/components/StatusBadge.tsx new file mode 100644 index 00000000..f374ea7f --- /dev/null +++ b/src/admin/mcp-server/components/StatusBadge.tsx @@ -0,0 +1,42 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +/** + * External dependencies + */ +import React from 'react'; + +type StatusKey = 'running' | 'initializing' | 'disabled'; + +const statusLabelMap: Record< StatusKey, string > = { + running: __( 'Running', 'ai' ), + initializing: __( 'Starting…', 'ai' ), + disabled: __( 'Disabled', 'ai' ), +}; + +interface StatusBadgeProps { + status: StatusKey; +} + +export const getStatusLabel = ( status: StatusKey ): string => + statusLabelMap[ status ] ?? statusLabelMap.initializing; + +const StatusBadge: React.FC< StatusBadgeProps > = ( { status } ) => { + const dotClass = [ + 'ai-mcp-server__status-dot', + status === 'running' ? 'ai-mcp-server__status-dot--running' : '', + status === 'disabled' ? 'ai-mcp-server__status-dot--disabled' : '', + ] + .filter( Boolean ) + .join( ' ' ); + + return ( + + + { getStatusLabel( status ) } + + ); +}; + +export default StatusBadge; diff --git a/src/admin/mcp-server/components/TestConnectionPanel.tsx b/src/admin/mcp-server/components/TestConnectionPanel.tsx new file mode 100644 index 00000000..ecf64c67 --- /dev/null +++ b/src/admin/mcp-server/components/TestConnectionPanel.tsx @@ -0,0 +1,75 @@ +/** + * WordPress dependencies + */ +import { + Button, + Card, + CardBody, + CardHeader, + Notice, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +/** + * External dependencies + */ +import React from 'react'; + +/** + * Internal dependencies + */ +import type { TestResult } from '../types'; + +interface TestConnectionPanelProps { + testing: boolean; + result: TestResult | null; + onTest: () => void; +} + +const TestConnectionPanel: React.FC< TestConnectionPanelProps > = ( { + testing, + result, + onTest, +} ) => ( + + +

{ __( 'Connection test', 'ai' ) }

+
+ +

+ { __( + 'Verify that the MCP HTTP endpoint is reachable from WordPress. For external clients you may still need to configure authentication.', + 'ai' + ) } +

+ + + { result && ( + +

+ { result.message } +

+

+ { __( 'Response code:', 'ai' ) }{ ' ' } + { result.code ?? __( 'Unavailable', 'ai' ) } +

+ { result.body &&
{ result.body }
} +
+ ) } +
+
+); + +export default TestConnectionPanel; diff --git a/src/admin/mcp-server/components/ToolsTable.tsx b/src/admin/mcp-server/components/ToolsTable.tsx new file mode 100644 index 00000000..c84d6451 --- /dev/null +++ b/src/admin/mcp-server/components/ToolsTable.tsx @@ -0,0 +1,235 @@ +/** + * WordPress dependencies + */ +import { + Card, + CardBody, + CardHeader, + Notice, + ToggleControl, +} from '@wordpress/components'; +import { DataViews, filterSortAndPaginate } from '@wordpress/dataviews/wp'; +import type { DataViewField, View } from '@wordpress/dataviews'; +import { __ } from '@wordpress/i18n'; +/** + * External dependencies + */ +import React, { useEffect, useMemo } from 'react'; + +/** + * Internal dependencies + */ +import { usePersistedView } from '../../hooks/usePersistedView'; +import type { ToolSummary } from '../types'; + +interface ToolsTableProps { + tools: ToolSummary[]; + saving: boolean; + globalEnabled: boolean; + serverEnabled: boolean; + onToggle: ( name: string, next: boolean ) => void; +} + +const ToolsTable: React.FC< ToolsTableProps > = ( { + tools, + saving, + globalEnabled, + serverEnabled, + onToggle, +} ) => { + const canEditTools = globalEnabled && serverEnabled; + const categoryOptions = useMemo( () => { + const seen = new Map< string, { label: string; value: string } >(); + tools.forEach( ( tool ) => { + if ( ! seen.has( tool.category.slug ) ) { + seen.set( tool.category.slug, { + label: tool.category.label, + value: tool.category.slug, + } ); + } + } ); + return Array.from( seen.values() ); + }, [ tools ] ); + + const fields = useMemo< DataViewField< ToolSummary >[] >( + () => [ + { + id: 'enabled', + label: __( 'Expose via MCP', 'ai' ), + type: 'text', + getValue: ( { item } ) => + item.enabled ? 'exposed' : 'not_exposed', + elements: [ + { label: __( 'Exposed', 'ai' ), value: 'exposed' }, + { label: __( 'Not exposed', 'ai' ), value: 'not_exposed' }, + ], + filterBy: { + operators: [ 'is' ], + }, + enableSorting: true, + enableHiding: false, + render: ( { item } ) => ( + + onToggle( item.name, value ) + } + disabled={ saving || ! canEditTools } + __nextHasNoMarginBottom + /> + ), + }, + { + id: 'ability', + label: __( 'Ability', 'ai' ), + type: 'text', + enableGlobalSearch: true, + getValue: ( { item } ) => item.label, + render: ( { item } ) => ( +
+ { item.label } + { item.description && ( +

{ item.description }

+ ) } + { item.name } +
+ ), + }, + { + id: 'category', + label: __( 'Category', 'ai' ), + type: 'text', + getValue: ( { item } ) => item.category.slug, + elements: categoryOptions, + filterBy: + categoryOptions.length > 0 + ? { operators: [ 'is' ] } + : false, + render: ( { item } ) => item.category.label, + }, + { + id: 'isPublic', + label: __( 'Public', 'ai' ), + type: 'boolean', + getValue: ( { item } ) => item.isPublic, + render: ( { item } ) => + item.isPublic ? __( 'Yes', 'ai' ) : __( 'No', 'ai' ), + filterBy: false, + }, + ], + [ categoryOptions, onToggle, saving, canEditTools ] + ); + + const initialFields = useMemo( + () => fields.map( ( field ) => field.id ), + [ fields ] + ); + + const defaultView: View = useMemo( + () => ( { + type: 'table', + search: '', + page: 1, + perPage: 10, + fields: initialFields, + sort: { + field: 'ability', + direction: 'asc', + }, + filters: [], + } ), + [ initialFields ] + ); + + const { view, setView } = usePersistedView( 'mcp-tools', defaultView ); + + useEffect( () => { + setView( ( previous ) => { + const availableFieldIds = fields.map( ( field ) => field.id ); + const previousFields = previous.fields ?? availableFieldIds; + const visibleSet = new Set( previousFields ); + const nextFields = availableFieldIds.filter( ( id ) => + visibleSet.has( id ) + ); + return { + ...previous, + fields: nextFields, + }; + } ); + }, [ fields, setView ] ); + + const { data: filteredTools, paginationInfo } = useMemo( () => { + const result = filterSortAndPaginate( tools, view, fields ); + return ( + result ?? { + data: tools, + paginationInfo: { totalItems: tools.length, totalPages: 1 }, + } + ); + }, [ tools, view, fields ] ); + + const hasActiveFilters = + Boolean( view.search ) || ( view.filters?.length ?? 0 ) > 0; + + const handleViewChange = ( nextView: View ) => { + setView( ( previous ) => ( { + ...previous, + ...nextView, + layout: nextView.layout ?? previous.layout, + } ) ); + }; + + return ( + + +

{ __( 'Exposed abilities', 'ai' ) }

+
+ + { ! canEditTools && ( + + { ! globalEnabled + ? __( + 'Enable MCP globally to edit which abilities are exposed.', + 'ai' + ) + : __( + 'Enable the selected server to edit which abilities are exposed.', + 'ai' + ) } + + ) } + + item.name } + defaultLayouts={ { + table: { + layout: { + density: 'comfortable', + enableMoving: false, + }, + }, + } } + isLoading={ false } + paginationInfo={ paginationInfo } + empty={ +

+ { hasActiveFilters + ? __( 'No abilities match your filters.', 'ai' ) + : __( + 'Enable experiments to register more abilities for MCP clients.', + 'ai' + ) } +

+ } + searchLabel={ __( 'Search abilities', 'ai' ) } + /> +
+
+ ); +}; + +export default ToolsTable; diff --git a/src/admin/mcp-server/index.tsx b/src/admin/mcp-server/index.tsx new file mode 100644 index 00000000..ab6455d6 --- /dev/null +++ b/src/admin/mcp-server/index.tsx @@ -0,0 +1,554 @@ +/** + * Internal dependencies + */ +import './style.scss'; + +/** + * WordPress dependencies + */ +import apiFetch from '@wordpress/api-fetch'; +import { + Button, + Dropdown, + MenuGroup, + MenuItem, + Notice, + Spinner, + ToggleControl, + Tooltip, +} from '@wordpress/components'; +import { dispatch } from '@wordpress/data'; +import { chevronDown, pencil, plus } from '@wordpress/icons'; +import { store as noticesStore } from '@wordpress/notices'; +import { __, sprintf } from '@wordpress/i18n'; +/** + * External dependencies + */ +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { createRoot } from 'react-dom/client'; +import { createPortal } from 'react-dom'; + +import ConfigGenerator from './components/ConfigGenerator'; +import ServerStatusCard from './components/ServerStatusCard'; +import StatusBadge, { getStatusLabel } from './components/StatusBadge'; +import TestConnectionPanel from './components/TestConnectionPanel'; +import ToolsTable from './components/ToolsTable'; +import type { + ConfigTemplate, + LocalizedSettings, + McpOverview, + ServerSummary, + TestResult, + ToolSummary, +} from './types'; + +const settings: LocalizedSettings = window.aiMcpServerSettings; + +apiFetch.use( apiFetch.createNonceMiddleware( settings.rest.nonce ) ); +apiFetch.use( apiFetch.createRootURLMiddleware( settings.rest.root ) ); + +const showNotice = ( + status: 'success' | 'error' | 'warning', + message: string +) => + dispatch( noticesStore ).createNotice( status, message, { + type: 'snackbar', + } ); + +const getErrorMessage = ( error: unknown ): string => { + if ( typeof error === 'string' ) { + return error; + } + + if ( error && typeof error === 'object' && 'message' in error ) { + return String( ( error as { message: string } ).message ); + } + + return __( 'Something went wrong. Please try again.', 'ai' ); +}; + +const getEnabledToolNames = ( tools: ToolSummary[] ): string[] => + tools.filter( ( tool ) => tool.enabled ).map( ( tool ) => tool.name ); + +const App: React.FC = () => { + const [ data, setData ] = useState< McpOverview | null >( null ); + const [ selectedServerId, setSelectedServerId ] = useState< string | null >( + null + ); + const [ loading, setLoading ] = useState( true ); + const [ error, setError ] = useState< string | null >( null ); + const [ savingTools, setSavingTools ] = useState( false ); + const [ savingGlobal, setSavingGlobal ] = useState( false ); + const [ savingServer, setSavingServer ] = useState( false ); + const [ testing, setTesting ] = useState( false ); + const [ testResult, setTestResult ] = useState< TestResult | null >( null ); + + const fetchOverview = useCallback( async ( serverId?: string ) => { + setLoading( true ); + try { + const path = + settings.rest.routes.overview + + ( serverId + ? `?server_id=${ encodeURIComponent( serverId ) }` + : '' ); + const response = ( await apiFetch( { path } ) ) as McpOverview; + setData( response ); + setSelectedServerId( response.activeServerId ); + setError( null ); + } catch ( apiError ) { + setError( getErrorMessage( apiError ) ); + } finally { + setLoading( false ); + } + }, [] ); + + useEffect( () => { + fetchOverview(); + }, [ fetchOverview ] ); + + const handleToggleGlobal = async ( nextValue: boolean ) => { + setSavingGlobal( true ); + try { + const response = ( await apiFetch( { + path: settings.rest.routes.enabled, + method: 'POST', + data: { enabled: nextValue, server_id: selectedServerId }, + } ) ) as McpOverview; + setData( response ); + setSelectedServerId( response.activeServerId ); + showNotice( + nextValue ? 'success' : 'warning', + nextValue + ? __( 'MCP enabled.', 'ai' ) + : __( 'MCP disabled.', 'ai' ) + ); + } catch ( apiError ) { + const message = getErrorMessage( apiError ); + showNotice( 'error', message ); + setError( message ); + } finally { + setSavingGlobal( false ); + } + }; + + const handleToggleServerEnabled = async ( nextValue: boolean ) => { + if ( ! selectedServerId ) { + return; + } + + setSavingServer( true ); + try { + const response = ( await apiFetch( { + path: settings.rest.routes.server, + method: 'POST', + data: { + server: { + id: selectedServerId, + enabled: nextValue, + }, + }, + } ) ) as McpOverview; + setData( response ); + setSelectedServerId( response.activeServerId ); + showNotice( + nextValue ? 'success' : 'warning', + nextValue + ? __( 'Server enabled.', 'ai' ) + : __( 'Server disabled.', 'ai' ) + ); + } catch ( apiError ) { + const message = getErrorMessage( apiError ); + showNotice( 'error', message ); + } finally { + setSavingServer( false ); + } + }; + + const handleRenameServer = async ( newName: string ) => { + if ( ! selectedServerId ) { + return; + } + + try { + const response = ( await apiFetch( { + path: settings.rest.routes.server, + method: 'POST', + data: { + server: { + id: selectedServerId, + name: newName, + }, + }, + } ) ) as McpOverview; + setData( response ); + setSelectedServerId( response.activeServerId ); + showNotice( 'success', __( 'Server renamed.', 'ai' ) ); + } catch ( apiError ) { + const message = getErrorMessage( apiError ); + showNotice( 'error', message ); + throw apiError; + } + }; + + const handleToggleTool = async ( name: string, nextValue: boolean ) => { + if ( savingTools ) { + return; + } + + if ( ! data?.activeServerId ) { + return; + } + + const currentNames = getEnabledToolNames( data.tools ); + const payload = nextValue + ? Array.from( new Set( [ ...currentNames, name ] ) ) + : currentNames.filter( ( toolName ) => toolName !== name ); + + setSavingTools( true ); + try { + const response = ( await apiFetch( { + path: settings.rest.routes.tools, + method: 'POST', + data: { tools: payload, serverId: data.activeServerId }, + } ) ) as McpOverview; + setData( response ); + setSelectedServerId( response.activeServerId ); + } catch ( apiError ) { + const message = getErrorMessage( apiError ); + showNotice( 'error', message ); + } finally { + setSavingTools( false ); + } + }; + + const handleCopy = async ( value: string, label: string ) => { + if ( ! value ) { + return; + } + + try { + if ( navigator.clipboard?.writeText ) { + await navigator.clipboard.writeText( value ); + } else { + const temp = document.createElement( 'textarea' ); + temp.value = value; + document.body.appendChild( temp ); + temp.select(); + document.execCommand( 'copy' ); + document.body.removeChild( temp ); + } + + showNotice( + 'success', + sprintf( + /* translators: %s: label for the value that was copied. */ + __( '%s copied to clipboard.', 'ai' ), + label + ) + ); + } catch ( copyError ) { + showNotice( + 'error', + sprintf( + /* translators: %s: label for the value that failed to copy. */ + __( 'Could not copy %s.', 'ai' ), + label + ) + ); + } + }; + + const handleTestConnection = async () => { + if ( ! data?.activeServerId ) { + return; + } + + setTesting( true ); + try { + const response = ( await apiFetch( { + path: settings.rest.routes.test, + method: 'POST', + data: { serverId: data.activeServerId }, + } ) ) as TestResult; + setTestResult( response ); + showNotice( + response.success ? 'success' : 'error', + response.message + ); + } catch ( apiError ) { + const message = getErrorMessage( apiError ); + setTestResult( { success: false, code: null, message } ); + showNotice( 'error', message ); + } finally { + setTesting( false ); + } + }; + + const templates = useMemo( + () => + ( data?.configTemplates ?? {} ) as Record< string, ConfigTemplate >, + [ data?.configTemplates ] + ); + + const handleSelectServer = async ( serverId: string ) => { + if ( ! serverId ) { + return; + } + + setSelectedServerId( serverId ); + await fetchOverview( serverId ); + }; + + const handleAddServer = async () => { + // eslint-disable-next-line no-alert + const name = window.prompt( + __( 'Enter a name for the new server:', 'ai' ) + ); + + if ( ! name ) { + return; + } + + try { + const response = ( await apiFetch( { + path: settings.rest.routes.addServer, + method: 'POST', + data: { + server: { name }, + }, + } ) ) as McpOverview; + setData( response ); + setSelectedServerId( response.activeServerId ); + showNotice( 'success', __( 'Server created.', 'ai' ) ); + } catch ( apiError ) { + showNotice( 'error', getErrorMessage( apiError ) ); + } + }; + + const activeServer = data?.activeServer ?? null; + const activeStatus = ( activeServer?.status ?? 'initializing' ) as + | 'running' + | 'initializing' + | 'disabled'; + const globalStatus = ! ( data?.enabled ?? true ) + ? 'disabled' + : activeStatus; + const serverOptions = ( data?.servers ?? [] ).map( + ( server: ServerSummary ) => { + const showStatus = server.status !== 'running'; + const label = showStatus + ? sprintf( + /* translators: 1: Server name, 2: server status label. */ + __( '%1$s (%2$s)', 'ai' ), + server.name, + getStatusLabel( server.status ) + ) + : server.name; + return { + label, + value: server.id, + }; + } + ); + + const handleHeaderRename = async () => { + if ( ! activeServer ) { + return; + } + + // eslint-disable-next-line no-alert + const nextName = window.prompt( + __( 'Enter a new name for this server:', 'ai' ), + activeServer.name + ); + + if ( ! nextName ) { + return; + } + + const trimmed = nextName.trim(); + if ( ! trimmed || trimmed === activeServer.name ) { + return; + } + + await handleRenameServer( trimmed ); + }; + + const currentServerLabel = + activeServer?.name ?? + ( serverOptions.length > 0 + ? __( 'Select a server', 'ai' ) + : __( 'No servers available', 'ai' ) ); + const canSwitchServers = serverOptions.length > 0; + + // Get portal mount points for header elements (rendered by PHP) + const headerServerSelectorMount = document.getElementById( + 'ai-mcp-header-server-selector' + ); + const headerControlsMount = document.getElementById( + 'ai-mcp-header-controls' + ); + + return ( +
+ { /* Portal: Server selector in PHP header */ } + { headerServerSelectorMount && + ! loading && + createPortal( +
+ + { currentServerLabel } + +
, + headerServerSelectorMount + ) } + + { /* Portal: Header controls (global toggle, server toggle, add button) */ } + { headerControlsMount && + ! loading && + createPortal( + <> + + + +
+ ); +}; + +const mountNode = document.getElementById( 'ai-mcp-server-root' ); + +if ( mountNode ) { + const root = createRoot( mountNode ); + root.render( ); +} diff --git a/src/admin/mcp-server/style.scss b/src/admin/mcp-server/style.scss new file mode 100644 index 00000000..3846df06 --- /dev/null +++ b/src/admin/mcp-server/style.scss @@ -0,0 +1,228 @@ +@use '../dataviews'; +@use '../common'; + +.ai-mcp-server { + &__layout { + display: grid; + grid-template-columns: minmax( 0, 2fr ) minmax( 280px, 1fr ); + gap: 24px; + align-items: flex-start; + + @media ( max-width: 1200px ) { + grid-template-columns: 1fr; + } + } + + &__main, + &__sidebar { + display: flex; + flex-direction: column; + gap: 24px; + } + + &__server-description { + margin: 0; + color: #50575e; + } + + &__header-switcher { + display: inline-flex; + align-items: center; + gap: 8px; + margin-left: 16px; + flex-wrap: wrap; + + .ai-mcp-server__status-text strong { + font-size: 12px; + font-weight: 500; + } + } + + &__header-server-name { + font-size: 16px; + font-weight: 600; + color: #1e1e1e; + } + + &__header-icon-button.components-button { + min-width: 28px; + height: 28px; + padding: 0; + border-radius: 999px; + } + + &__header-dropdown { + .components-popover__content { + min-width: 220px; + } + + .components-menu-item.is-selected { + font-weight: 600; + } + } + + &__card { + code { + word-break: break-all; + } + + // Card body flex layout for non-dataviews cards + .components-card__body:not(:has(> .dataviews-wrapper)) { + display: flex; + flex-direction: column; + gap: 16px; + align-items: flex-start; + + // Allow button rows and form elements to stretch + .ai-mcp-server__button-row, + .components-select-control, + .components-textarea-control { + align-self: stretch; + } + } + } + + &__card-heading { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + width: 100%; + + @media ( max-width: 600px ) { + flex-direction: column; + align-items: flex-start; + } + } + + &__server-name { + display: flex; + align-items: center; + gap: 12px; + flex: 1; + min-width: 0; + + h3 { + margin: 0; + font-size: 14px; + font-weight: 600; + } + } + + &__status-text { + display: flex; + align-items: center; + gap: 8px; + } + + &__status-dot { + width: 10px; + height: 10px; + border-radius: 999px; + background-color: #949494; + + &--running { + background-color: #1a7f37; + } + + &--disabled { + background-color: #d63638; + } + } + + &__field { + margin-bottom: 16px; + + label { + display: block; + font-weight: 600; + margin-bottom: 4px; + } + } + + &__field-row { + display: flex; + gap: 12px; + align-items: center; + flex-wrap: wrap; + + code { + flex: 1; + background: #f6f7f7; + padding: 6px 8px; + border-radius: 4px; + } + } + + &__hint { + margin-top: 0; + color: #50575e; + } + + &__button-row { + display: flex; + gap: 12px; + flex-wrap: wrap; + } + + &__tools { + margin-bottom: 24px; + + .dataviews__layout { + font-size: 13px; + } + + .ai-mcp-server__tool-info { + max-width: 100%; + overflow: hidden; + + strong { + display: block; + font-weight: 600; + margin-bottom: 2px; + line-height: 1.4; + color: #1e1e1e; + } + + .description { + margin: 2px 0 4px; + color: #50575e; + font-size: 12px; + line-height: 1.4; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + word-break: break-word; + } + + code { + display: inline-block; + background: #f0f0f0; + padding: 2px 6px; + border-radius: 3px; + font-size: 11px; + color: #50575e; + word-break: break-all; + } + } + } + + &__loading { + display: flex; + align-items: center; + gap: 12px; + } + + &__test-result { + margin-top: 16px; + + pre { + background: #f6f7f7; + padding: 12px; + border-radius: 4px; + max-height: 160px; + overflow: auto; + } + } +} diff --git a/src/admin/mcp-server/types.ts b/src/admin/mcp-server/types.ts new file mode 100644 index 00000000..a2bd86ee --- /dev/null +++ b/src/admin/mcp-server/types.ts @@ -0,0 +1,75 @@ +export interface ServerSummary { + id: string; + name: string; + description: string; + enabled: boolean; + status: 'running' | 'initializing' | 'disabled'; +} + +export interface ServerDetails extends ServerSummary { + route_namespace: string; + route: string; + transports: string[]; + http_endpoint: string | null; + cli_command: string | null; + has_route: boolean; + enabled: boolean; +} + +export interface ToolSummary { + name: string; + label: string; + description: string; + category: { + slug: string; + label: string; + }; + isPublic: boolean; + enabled: boolean; +} + +export interface ConfigTemplate { + id: string; + fileName: string; + content: string; +} + +export interface McpOverview { + enabled: boolean; + servers: ServerSummary[]; + activeServerId: string; + activeServer: ServerDetails | null; + tools: ToolSummary[]; + configTemplates: Record< string, ConfigTemplate >; +} + +export interface TestResult { + success: boolean; + code: number | null; + message: string; + body?: string; +} + +export interface LocalizedSettings { + rest: { + nonce: string; + root: string; + routes: { + overview: string; + enabled: string; + server: string; + addServer: string; + tools: string; + test: string; + }; + }; + profileUrl: string; +} + +declare global { + interface Window { + aiMcpServerSettings: LocalizedSettings; + } +} + +export type CopyHandler = ( value: string, label: string ) => void; diff --git a/webpack.config.js b/webpack.config.js index 49a296e9..dfb5eedf 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -29,6 +29,11 @@ module.exports = { 'src/experiments/title-generation', 'index.tsx' ), + 'admin/mcp-server': path.resolve( + process.cwd(), + 'src/admin/mcp-server', + 'index.tsx' + ), }, plugins: [ From c27eb7790b632052a1f28845fb806234b2f27957 Mon Sep 17 00:00:00 2001 From: James LePage <36246732+Jameswlepage@users.noreply.github.com> Date: Fri, 19 Dec 2025 11:39:35 -0500 Subject: [PATCH 2/3] Fix PHPCS issues in MCP experiment - Remove constructor property promotion (PHP 7.4 compatibility) - Use early exit pattern for cleaner code flow - Fix use statement alphabetical ordering - Remove unused imports - Replace short ternaries with full ternaries - Add phpcs:ignore for intentional timeout setting --- includes/Experiments/MCP/Admin_Page.php | 25 +++++-- includes/Experiments/MCP/MCP.php | 8 ++- includes/Experiments/MCP/Manager.php | 90 +++++++++++++------------ 3 files changed, 72 insertions(+), 51 deletions(-) diff --git a/includes/Experiments/MCP/Admin_Page.php b/includes/Experiments/MCP/Admin_Page.php index f0b74441..1730f7bd 100644 --- a/includes/Experiments/MCP/Admin_Page.php +++ b/includes/Experiments/MCP/Admin_Page.php @@ -13,7 +13,6 @@ use function __; use function add_action; -use function add_options_page; use function admin_url; use function current_user_can; use function esc_html_e; @@ -31,7 +30,21 @@ class Admin_Page { private const PAGE_SLUG = 'ai-mcp'; private const MENU_ICON = 'data:image/svg+xml;base64,PHN2ZyBmaWxsPSJjdXJyZW50Q29sb3IiIGZpbGwtcnVsZT0iZXZlbm9kZCIgaGVpZ2h0PSIxZW0iIHN0eWxlPSJmbGV4Om5vbmU7bGluZS1oZWlnaHQ6MSIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMWVtIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjx0aXRsZT5Nb2RlbENvbnRleHRQcm90b2NvbDwvdGl0bGU+PHBhdGggZD0iTTE1LjY4OCAyLjM0M2EyLjU4OCAyLjU4OCAwIDAwLTMuNjEgMGwtOS42MjYgOS40NGEuODYzLjg2MyAwIDAxLTEuMjAzIDAgLjgyMy44MjMgMCAwMTAtMS4xOGw5LjYyNi05LjQ0YTQuMzEzIDQuMzEzIDAgMDE2LjAxNiAwIDQuMTE2IDQuMTE2IDAgMDExLjIwNCAzLjU0IDQuMyA0LjMgMCAwMTMuNjA5IDEuMThsLjA1LjA1YTQuMTE1IDQuMTE1IDAgMDEwIDUuOWwtOC43MDYgOC41MzdhLjI3NC4yNzQgMCAwMDAgLjM5M2wxLjc4OCAxLjc1NGEuODIzLjgyMyAwIDAxMCAxLjE4Ljg2My44NjMgMCAwMS0xLjIwMyAwbC0xLjc4OC0xLjc1M2ExLjkyIDEuOTIgMCAwMTAtMi43NTRsOC43MDYtOC41MzhhMi40NyAyLjQ3IDAgMDAwLTMuNTRsLS4wNS0uMDQ5YTIuNTg4IDIuNTg4IDAgMDAtMy42MDctLjAwM2wtNy4xNzIgNy4wMzQtLjAwMi4wMDItLjA5OC4wOTdhLjg2My44NjMgMCAwMS0xLjIwNCAwIC44MjMuODIzIDAgMDEwLTEuMThsNy4yNzMtNy4xMzNhMi40NyAyLjQ3IDAgMDAtLjAwMy0zLjUzN3oiPjwvcGF0aD48cGF0aCBkPSJNMTQuNDg1IDQuNzAzYS44MjMuODIzIDAgMDAwLTEuMTguODYzLjg2MyAwIDAwLTEuMjA0IDBsLTcuMTE5IDYuOTgyYTQuMTE1IDQuMTE1IDAgMDAwIDUuOSA0LjMxNCA0LjMxNCAwIDAwNi4wMTYgMGw3LjEyLTYuOTgyYS44MjMuODIzIDAgMDAwLTEuMTguODYzLjg2MyAwIDAwLTEuMjA0IDBsLTcuMTE5IDYuOTgyYTIuNTg4IDIuNTg4IDAgMDEtMy42MSAwIDIuNDcgMi40NyAwIDAxMC0zLjU0bDcuMTItNi45ODJ6Ij48L3BhdGg+PC9zdmc+'; - public function __construct( private Manager $manager ) {} + /** + * Manager instance. + * + * @var \WordPress\AI\Experiments\MCP\Manager + */ + private Manager $manager; + + /** + * Constructor. + * + * @param \WordPress\AI\Experiments\MCP\Manager $manager Manager instance. + */ + public function __construct( Manager $manager ) { + $this->manager = $manager; + } /** * Hook admin actions. @@ -54,9 +67,11 @@ public function register_menu(): void { 58 ); - if ( $page_hook ) { - add_action( "load-{$page_hook}", array( $this, 'on_load' ) ); + if ( ! $page_hook ) { + return; } + + add_action( "load-{$page_hook}", array( $this, 'on_load' ) ); } /** @@ -112,7 +127,7 @@ public function enqueue_assets(): void { 'mcp_server', 'McpServerSettings', array( - 'rest' => array( + 'rest' => array( 'nonce' => wp_create_nonce( 'wp_rest' ), 'root' => esc_url_raw( rest_url() ), 'routes' => array( diff --git a/includes/Experiments/MCP/MCP.php b/includes/Experiments/MCP/MCP.php index 9cff7515..bb103b12 100644 --- a/includes/Experiments/MCP/MCP.php +++ b/includes/Experiments/MCP/MCP.php @@ -42,10 +42,12 @@ public function register(): void { $this->manager = new Manager(); $this->manager->init(); - if ( is_admin() ) { - $page = new Admin_Page( $this->manager ); - $page->init(); + if ( ! is_admin() ) { + return; } + + $page = new Admin_Page( $this->manager ); + $page->init(); } /** diff --git a/includes/Experiments/MCP/Manager.php b/includes/Experiments/MCP/Manager.php index f52da7e1..86224ee3 100644 --- a/includes/Experiments/MCP/Manager.php +++ b/includes/Experiments/MCP/Manager.php @@ -22,28 +22,26 @@ use function add_action; use function add_filter; use function array_key_exists; -use function esc_html__; use function do_action; +use function esc_html__; use function function_exists; use function get_option; use function is_array; use function reset; -use function rest_get_server; use function rest_url; use function sanitize_key; use function sanitize_text_field; use function set_url_scheme; use function update_option; use function wp_generate_password; -use function wp_get_ability; -use function wp_get_ability_category; use function wp_get_abilities; +use function wp_get_ability_category; use function wp_json_encode; use function wp_parse_url; +use function wp_register_ability_category; use function wp_remote_request; use function wp_remote_retrieve_body; use function wp_remote_retrieve_response_code; -use function wp_register_ability_category; /** * Coordinates MCP adapter bootstrapping, configuration persistence, and REST data. @@ -52,10 +50,10 @@ */ class Manager { - private const OPTION_ENABLED = 'ai_mcp_server_enabled'; - private const OPTION_SERVERS = 'ai_mcp_servers'; + private const OPTION_ENABLED = 'ai_mcp_server_enabled'; + private const OPTION_SERVERS = 'ai_mcp_servers'; private const OPTION_LEGACY_TOOLS = 'ai_mcp_enabled_tools'; - private const DEFAULT_VERSION = 'v1.0.0'; + private const DEFAULT_VERSION = 'v1.0.0'; /** * Bootstrap hooks. @@ -254,13 +252,13 @@ public function get_server( string $server_id ): ?array { public function add_server( array $data ): array { $servers = $this->get_servers(); - $name = sanitize_text_field( $data['name'] ?? esc_html__( 'New MCP Server', 'ai' ) ); - $route_namespace = sanitize_key( $data['route_namespace'] ?? 'mcp' ); - $route = $this->unique_route_slug( $data['route'] ?? $name, $servers ); - $id = $this->unique_server_id( $route ); - $description = sanitize_text_field( $data['description'] ?? '' ); - $transports = $this->sanitize_transports( $data['transports'] ?? array( 'http' ) ); - $server_config = array( + $name = sanitize_text_field( $data['name'] ?? esc_html__( 'New MCP Server', 'ai' ) ); + $route_namespace = sanitize_key( $data['route_namespace'] ?? 'mcp' ); + $route = $this->unique_route_slug( $data['route'] ?? $name, $servers ); + $id = $this->unique_server_id( $route ); + $description = sanitize_text_field( $data['description'] ?? '' ); + $transports = $this->sanitize_transports( $data['transports'] ?? array( 'http' ) ); + $server_config = array( 'id' => $id, 'name' => $name, 'description' => $description, @@ -354,12 +352,12 @@ public function build_overview_payload( ?string $requested_server = null ): arra $runtime_map = $this->get_runtime_servers_map(); return array( - 'enabled' => $this->is_enabled(), - 'servers' => $this->summarize_servers( $servers, $runtime_map ), - 'activeServerId' => $active_id, - 'activeServer' => $this->format_server_for_response( $active_server, $runtime_map[ $active_id ] ?? null ), - 'tools' => $this->get_available_tools( $active_server ), - 'configTemplates'=> $this->get_client_templates( $active_server ), + 'enabled' => $this->is_enabled(), + 'servers' => $this->summarize_servers( $servers, $runtime_map ), + 'activeServerId' => $active_id, + 'activeServer' => $this->format_server_for_response( $active_server, $runtime_map[ $active_id ] ?? null ), + 'tools' => $this->get_available_tools( $active_server ), + 'configTemplates' => $this->get_client_templates( $active_server ), ); } @@ -457,7 +455,7 @@ public function get_client_templates( array $server ): array { JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ), ), - 'cursor' => array( + 'cursor' => array( 'id' => 'cursor', 'fileName' => '.cursor/mcp.json', 'content' => wp_json_encode( @@ -465,7 +463,7 @@ public function get_client_templates( array $server ): array { JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ), ), - 'windsurf' => array( + 'windsurf' => array( 'id' => 'windsurf', 'fileName' => 'mcp_config.json (Windsurf)', 'content' => wp_json_encode( @@ -473,15 +471,15 @@ public function get_client_templates( array $server ): array { JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ), ), - 'vscode' => array( + 'vscode' => array( 'id' => 'vscode', 'fileName' => '.vscode/mcp.json', 'content' => wp_json_encode( array( 'servers' => array( 'wordpress' => array( - 'type' => 'http', - 'url' => $endpoint, + 'type' => 'http', + 'url' => $endpoint, 'headers' => array( 'Authorization' => 'Basic ', ), @@ -491,7 +489,7 @@ public function get_client_templates( array $server ): array { JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ), ), - 'jetbrains' => array( + 'jetbrains' => array( 'id' => 'jetbrains', 'fileName' => 'mcp.json (JetBrains)', 'content' => wp_json_encode( @@ -499,7 +497,7 @@ public function get_client_templates( array $server ): array { JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ), ), - 'generic' => array( + 'generic' => array( 'id' => 'generic', 'fileName' => 'mcp-server.json (Generic)', 'content' => wp_json_encode( @@ -542,8 +540,8 @@ public function test_http_endpoint( string $server_id, array $args = array() ): ); } - $method = strtoupper( $args['method'] ?? 'GET' ); - $headers = is_array( $args['headers'] ?? null ) ? $args['headers'] : array(); + $method = strtoupper( $args['method'] ?? 'GET' ); + $headers = is_array( $args['headers'] ?? null ) ? $args['headers'] : array(); $sanitized_headers = array(); foreach ( $headers as $key => $value ) { @@ -562,7 +560,7 @@ public function test_http_endpoint( string $server_id, array $args = array() ): foreach ( $attempts as $url ) { $request_args = array( 'method' => $method, - 'timeout' => 10, + 'timeout' => 10, // phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout -- Intentional for connection testing. 'headers' => $sanitized_headers, 'sslverify' => true, ); @@ -612,7 +610,7 @@ public function test_http_endpoint( string $server_id, array $args = array() ): * Format servers for sidebar list. * * @param array> $servers Server configs. - * @param array $runtime_map Adapter runtime map. + * @param array $runtime_map Adapter runtime map. * @return array> */ private function summarize_servers( array $servers, array $runtime_map ): array { @@ -636,7 +634,7 @@ private function summarize_servers( array $servers, array $runtime_map ): array * Format a server for REST responses. * * @param array $server Configured server. - * @param McpServer|null $runtime Runtime instance. + * @param \WP\MCP\Core\McpServer|null $runtime Runtime instance. * @return array */ private function format_server_for_response( array $server, ?McpServer $runtime ): array { @@ -663,7 +661,7 @@ private function format_server_for_response( array $server, ?McpServer $runtime * request) or it's still initializing. We return 'running' for enabled * servers since they will be running once the page reloads. */ - private function determine_status( array $server, ?McpServer $runtime ): string { + private function determine_status( array $server, ?McpServer $_runtime ): string { if ( empty( $server['enabled'] ) || ! $this->is_enabled() ) { return 'disabled'; } @@ -716,9 +714,11 @@ private function discover_public_tools(): array { foreach ( wp_get_abilities() as $ability ) { $meta = method_exists( $ability, 'get_meta' ) ? $ability->get_meta() : array(); - if ( ! empty( $meta['mcp']['public'] ) ) { - $names[] = $ability->get_name(); + if ( empty( $meta['mcp']['public'] ) ) { + continue; } + + $names[] = $ability->get_name(); } return $names; @@ -768,12 +768,14 @@ private function map_transports_to_classes( array $transports ): array { $classes = array(); foreach ( $transports as $transport ) { $slug = strtolower( (string) $transport ); - if ( isset( $map[ $slug ] ) ) { - $classes[] = $map[ $slug ]; + if ( ! isset( $map[ $slug ] ) ) { + continue; } + + $classes[] = $map[ $slug ]; } - return $classes ?: array( HttpTransport::class ); + return ! empty( $classes ) ? $classes : array( HttpTransport::class ); } /** @@ -792,7 +794,7 @@ private function system_tool_names(): array { /** * Get runtime servers keyed by ID. * - * @return array + * @return array */ private function get_runtime_servers_map(): array { if ( ! class_exists( McpAdapter::class ) ) { @@ -825,12 +827,14 @@ private function sanitize_transports( $transports ): array { $list = array(); foreach ( $transports as $transport ) { $slug = strtolower( sanitize_key( (string) $transport ) ); - if ( in_array( $slug, $valid, true ) ) { - $list[] = $slug; + if ( ! in_array( $slug, $valid, true ) ) { + continue; } + + $list[] = $slug; } - return $list ?: array( 'http' ); + return ! empty( $list ) ? $list : array( 'http' ); } /** From 0bb4efe08894bb8525f6ff123f717af30c894c2b Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Mon, 2 Feb 2026 16:59:16 -0700 Subject: [PATCH 3/3] Add required NPM packages --- package-lock.json | 732 ++++++++++++++++++++++++++++------------------ package.json | 2 + 2 files changed, 451 insertions(+), 283 deletions(-) diff --git a/package-lock.json b/package-lock.json index 957738ab..21568db0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@wordpress/components": "^30.7.0", "@wordpress/compose": "^7.34.0", "@wordpress/data": "^10.34.0", + "@wordpress/dataviews": "^11.3.0", "@wordpress/edit-post": "^8.36.0", "@wordpress/editor": "^14.34.0", "@wordpress/element": "^6.34.0", @@ -21,6 +22,7 @@ "@wordpress/icons": "^11.1.0", "@wordpress/notices": "^5.35.0", "@wordpress/plugins": "^7.34.0", + "@wordpress/views": "^1.6.0", "react": "^18.3.1" }, "devDependencies": { @@ -2058,6 +2060,73 @@ "node": ">=6.9.0" } }, + "node_modules/@base-ui/react": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@base-ui/react/-/react-1.1.0.tgz", + "integrity": "sha512-ikcJRNj1mOiF2HZ5jQHrXoVoHcNHdBU5ejJljcBl+VTLoYXR6FidjTN86GjO6hyshi6TZFuNvv0dEOgaOFv6Lw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@base-ui/utils": "0.2.4", + "@floating-ui/react-dom": "^2.1.6", + "@floating-ui/utils": "^0.2.10", + "reselect": "^5.1.1", + "tabbable": "^6.4.0", + "use-sync-external-store": "^1.6.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17 || ^18 || ^19", + "react": "^17 || ^18 || ^19", + "react-dom": "^17 || ^18 || ^19" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@base-ui/react/node_modules/@floating-ui/react-dom": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.7.tgz", + "integrity": "sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.5" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@base-ui/utils": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@base-ui/utils/-/utils-0.2.4.tgz", + "integrity": "sha512-smZwpMhjO29v+jrZusBSc5T+IJ3vBb9cjIiBjtKcvWmRj9Z4DWGVR3efr1eHR56/bqY5a4qyY9ElkOY5ljo3ng==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@floating-ui/utils": "^0.2.10", + "reselect": "^5.1.1", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "@types/react": "^17 || ^18 || ^19", + "react": "^17 || ^18 || ^19", + "react-dom": "^17 || ^18 || ^19" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@bcoe/v8-coverage": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", @@ -2069,7 +2138,7 @@ "version": "2.0.7", "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.0.7.tgz", "integrity": "sha512-RbxnxAMf89Tp1dLhXMS7ceft/PGsDl1Ip7T20z5nZ+pwIAsQ1p2izPjVG69oCLv/jfQ7HDPHTWK0c9rcAWXN3A==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@cacheable/utils": "^2.3.3", @@ -2082,7 +2151,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.0.tgz", "integrity": "sha512-KT01GjzV6AQD5+IYrcpoYLkCu1Jod3nau1Z7EsEuViO3TZGRacSbO9MfHmbJ1WaOXFtWLxPVj169cn2WNKPkIg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "hashery": "^1.2.0", @@ -2099,7 +2168,7 @@ "version": "5.5.5", "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.5.5.tgz", "integrity": "sha512-FA5LmZVF1VziNc0bIdCSA1IoSVnDCqE8HJIZZv2/W8YmoAM50+tnUgJR/gQZwEeIMleuIOnRnHA/UaZRNeV4iQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@keyv/serialize": "^1.1.1" @@ -2109,7 +2178,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.3.3.tgz", "integrity": "sha512-JsXDL70gQ+1Vc2W/KUFfkAJzgb4puKwwKehNLuB+HrNKWf91O736kGfxn4KujXCCSuh6mRRL4XEB0PkAFjWS0A==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "hashery": "^1.3.0", @@ -2120,7 +2189,7 @@ "version": "5.5.5", "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.5.5.tgz", "integrity": "sha512-FA5LmZVF1VziNc0bIdCSA1IoSVnDCqE8HJIZZv2/W8YmoAM50+tnUgJR/gQZwEeIMleuIOnRnHA/UaZRNeV4iQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@keyv/serialize": "^1.1.1" @@ -2202,7 +2271,7 @@ "version": "3.0.5", "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -2225,7 +2294,7 @@ "version": "1.0.23", "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.23.tgz", "integrity": "sha512-YEmgyklR6l/oKUltidNVYdjSmLSW88vMsKx0pmiS3r71s8ZZRpd8A0Yf0U+6p/RzElmMnPBv27hNWjDQMSZRtQ==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -2245,7 +2314,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -2311,7 +2380,7 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.2.1.tgz", "integrity": "sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg==", - "dev": true, + "devOptional": true, "license": "MIT", "funding": { "type": "github", @@ -2647,21 +2716,21 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", - "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz", + "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==", "license": "MIT", "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "node_modules/@floating-ui/dom": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", - "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.5.tgz", + "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==", "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.7.3", + "@floating-ui/core": "^1.7.4", "@floating-ui/utils": "^0.2.10" } }, @@ -4084,7 +4153,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@kwsites/file-exists": { @@ -4138,7 +4207,7 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", @@ -4152,7 +4221,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 8" @@ -4162,7 +4231,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", @@ -8352,13 +8421,13 @@ } }, "node_modules/@wordpress/a11y": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/@wordpress/a11y/-/a11y-4.37.0.tgz", - "integrity": "sha512-OxJL0sBNy2IwFkrLv0X9tOgmdHbvgVajciN8T73S6jTh96iOmdISSb3n+I9fc81X0BB03rv4dh8q6zBb/67U6A==", + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@wordpress/a11y/-/a11y-4.39.0.tgz", + "integrity": "sha512-uFy3FIF6MOo67tTVC2SaNyBQbFafu+DRirt2/IUQlY7w2MOiXWPaQFi3Oyy81gc8TfsooSFBlK4lrujd7O4gEw==", "license": "GPL-2.0-or-later", "dependencies": { - "@wordpress/dom-ready": "^4.37.0", - "@wordpress/i18n": "^6.10.0" + "@wordpress/dom-ready": "^4.39.0", + "@wordpress/i18n": "^6.12.0" }, "engines": { "node": ">=18.12.0", @@ -8692,9 +8761,9 @@ } }, "node_modules/@wordpress/base-styles": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/@wordpress/base-styles/-/base-styles-6.13.0.tgz", - "integrity": "sha512-+APLd5GqzzJ/atVVs3LGPcCRRy8mVfVQi1QY+cseNAQbRe4LvsDarLbzkblWEwuksxgUGmVGDC3fDNxrwszJ2A==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@wordpress/base-styles/-/base-styles-6.15.0.tgz", + "integrity": "sha512-AJ569cP6WqG0/RWx+x7MDNFxnCrZ5Pz1dHCLSE+7B0lLMXh3RGC1+Fj0YIZtUQ2ULLf2I3LRn6kuHdwyWNGiNA==", "license": "GPL-2.0-or-later", "engines": { "node": ">=18.12.0", @@ -9180,19 +9249,19 @@ } }, "node_modules/@wordpress/compose": { - "version": "7.37.0", - "resolved": "https://registry.npmjs.org/@wordpress/compose/-/compose-7.37.0.tgz", - "integrity": "sha512-MF3HETEL/gd7AGZ8dmswZujx/vCUD2JtJEHDb0bW+h5JE4xi/RJOP+Nh5K4LxFS7wKBPga8yGU8m+8QXH44R+g==", + "version": "7.39.0", + "resolved": "https://registry.npmjs.org/@wordpress/compose/-/compose-7.39.0.tgz", + "integrity": "sha512-9VyaqEDojUEnXKVxNamamEyYrWuI9vc2r33iV7hAe+8W0ISujRAFXp/baDiSOlYAcsbauRD4yAkEXuM0C4Vyhg==", "license": "GPL-2.0-or-later", "dependencies": { "@types/mousetrap": "^1.6.8", - "@wordpress/deprecated": "^4.37.0", - "@wordpress/dom": "^4.37.0", - "@wordpress/element": "^6.37.0", - "@wordpress/is-shallow-equal": "^5.37.0", - "@wordpress/keycodes": "^4.37.0", - "@wordpress/priority-queue": "^3.37.0", - "@wordpress/undo-manager": "^1.37.0", + "@wordpress/deprecated": "^4.39.0", + "@wordpress/dom": "^4.39.0", + "@wordpress/element": "^6.39.0", + "@wordpress/is-shallow-equal": "^5.39.0", + "@wordpress/keycodes": "^4.39.0", + "@wordpress/priority-queue": "^3.39.0", + "@wordpress/undo-manager": "^1.39.0", "change-case": "^4.1.2", "clipboard": "^2.0.11", "mousetrap": "^1.6.5", @@ -9244,18 +9313,18 @@ } }, "node_modules/@wordpress/data": { - "version": "10.37.0", - "resolved": "https://registry.npmjs.org/@wordpress/data/-/data-10.37.0.tgz", - "integrity": "sha512-6bKkEoD5WR/lCmJogx9WxgldhMQPvgV1TlCIXhx6xp9uVzqjjgdRmSwZ8IJR13QQ9GGHn7vWb59GtW4lF2FMNA==", + "version": "10.39.0", + "resolved": "https://registry.npmjs.org/@wordpress/data/-/data-10.39.0.tgz", + "integrity": "sha512-L04X/Vawzx6RgvvSQga8JhvPxr1A6seBrWJoRBBP7+1zdCV7nmmR8339yTEPRnCH6m70qb0xSwTByUmTzWYzLg==", "license": "GPL-2.0-or-later", "dependencies": { - "@wordpress/compose": "^7.37.0", - "@wordpress/deprecated": "^4.37.0", - "@wordpress/element": "^6.37.0", - "@wordpress/is-shallow-equal": "^5.37.0", - "@wordpress/priority-queue": "^3.37.0", - "@wordpress/private-apis": "^1.37.0", - "@wordpress/redux-routine": "^5.37.0", + "@wordpress/compose": "^7.39.0", + "@wordpress/deprecated": "^4.39.0", + "@wordpress/element": "^6.39.0", + "@wordpress/is-shallow-equal": "^5.39.0", + "@wordpress/priority-queue": "^3.39.0", + "@wordpress/private-apis": "^1.39.0", + "@wordpress/redux-routine": "^5.39.0", "deepmerge": "^4.3.0", "equivalent-key-map": "^0.2.2", "is-plain-object": "^5.0.0", @@ -9273,27 +9342,29 @@ } }, "node_modules/@wordpress/dataviews": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@wordpress/dataviews/-/dataviews-11.1.0.tgz", - "integrity": "sha512-gu5UzMH4jSOH9wXT/3C7tRoo8eTLsqgoAq38ND3ERFqwO9hEPmkPRK+4JFW6mkckTHjSOc173cJXrV2wIXd2+Q==", + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/@wordpress/dataviews/-/dataviews-11.3.0.tgz", + "integrity": "sha512-y3IT733p3ji7WoXm67WNWvy79bnSML3cMo2qvL1XPLl0cI1p6V7P2WC3TGRJdFKQl72I0Lph8pnRPtlUHJwwkw==", "license": "GPL-2.0-or-later", "dependencies": { "@ariakit/react": "^0.4.15", - "@wordpress/base-styles": "^6.13.0", - "@wordpress/components": "^31.0.0", - "@wordpress/compose": "^7.37.0", - "@wordpress/data": "^10.37.0", - "@wordpress/date": "^5.37.0", - "@wordpress/deprecated": "^4.37.0", - "@wordpress/dom": "^4.37.0", - "@wordpress/element": "^6.37.0", - "@wordpress/i18n": "^6.10.0", - "@wordpress/icons": "^11.4.0", - "@wordpress/keycodes": "^4.37.0", - "@wordpress/primitives": "^4.37.0", - "@wordpress/private-apis": "^1.37.0", - "@wordpress/url": "^4.37.0", - "@wordpress/warning": "^3.37.0", + "@wordpress/base-styles": "^6.15.0", + "@wordpress/components": "^32.1.0", + "@wordpress/compose": "^7.39.0", + "@wordpress/data": "^10.39.0", + "@wordpress/date": "^5.39.0", + "@wordpress/deprecated": "^4.39.0", + "@wordpress/dom": "^4.39.0", + "@wordpress/element": "^6.39.0", + "@wordpress/i18n": "^6.12.0", + "@wordpress/icons": "^11.6.0", + "@wordpress/keycodes": "^4.39.0", + "@wordpress/primitives": "^4.39.0", + "@wordpress/private-apis": "^1.39.0", + "@wordpress/theme": "^0.6.0", + "@wordpress/ui": "^0.6.0", + "@wordpress/url": "^4.39.0", + "@wordpress/warning": "^3.39.0", "clsx": "^2.1.1", "colord": "^2.7.0", "date-fns": "^4.1.0", @@ -9311,9 +9382,9 @@ } }, "node_modules/@wordpress/dataviews/node_modules/@wordpress/components": { - "version": "31.0.0", - "resolved": "https://registry.npmjs.org/@wordpress/components/-/components-31.0.0.tgz", - "integrity": "sha512-2Oz4r+4KCDTV5fMC2l9bDYfRAsLQLs29fLB4PelckW+X3KhhFM6gz5vx5N7hO3WLY19Fn6qKZgtgJjOKWG7+1w==", + "version": "32.1.0", + "resolved": "https://registry.npmjs.org/@wordpress/components/-/components-32.1.0.tgz", + "integrity": "sha512-Q4dUTWhVqV4pgW3AX9DaAwC4qsG5xiU1x9zVjo8Tm/B275hSWjumqN+spEe97T1QWx7b2RBAWK/OKVaIjkePTw==", "license": "GPL-2.0-or-later", "dependencies": { "@ariakit/react": "^0.4.15", @@ -9327,28 +9398,30 @@ "@floating-ui/react-dom": "2.0.8", "@types/gradient-parser": "1.1.0", "@types/highlight-words-core": "1.2.1", + "@types/react": "^18.3.27", "@use-gesture/react": "^10.3.1", - "@wordpress/a11y": "^4.37.0", - "@wordpress/base-styles": "^6.13.0", - "@wordpress/compose": "^7.37.0", - "@wordpress/date": "^5.37.0", - "@wordpress/deprecated": "^4.37.0", - "@wordpress/dom": "^4.37.0", - "@wordpress/element": "^6.37.0", - "@wordpress/escape-html": "^3.37.0", - "@wordpress/hooks": "^4.37.0", - "@wordpress/html-entities": "^4.37.0", - "@wordpress/i18n": "^6.10.0", - "@wordpress/icons": "^11.4.0", - "@wordpress/is-shallow-equal": "^5.37.0", - "@wordpress/keycodes": "^4.37.0", - "@wordpress/primitives": "^4.37.0", - "@wordpress/private-apis": "^1.37.0", - "@wordpress/rich-text": "^7.37.0", - "@wordpress/warning": "^3.37.0", + "@wordpress/a11y": "^4.39.0", + "@wordpress/base-styles": "^6.15.0", + "@wordpress/compose": "^7.39.0", + "@wordpress/date": "^5.39.0", + "@wordpress/deprecated": "^4.39.0", + "@wordpress/dom": "^4.39.0", + "@wordpress/element": "^6.39.0", + "@wordpress/escape-html": "^3.39.0", + "@wordpress/hooks": "^4.39.0", + "@wordpress/html-entities": "^4.39.0", + "@wordpress/i18n": "^6.12.0", + "@wordpress/icons": "^11.6.0", + "@wordpress/is-shallow-equal": "^5.39.0", + "@wordpress/keycodes": "^4.39.0", + "@wordpress/primitives": "^4.39.0", + "@wordpress/private-apis": "^1.39.0", + "@wordpress/rich-text": "^7.39.0", + "@wordpress/warning": "^3.39.0", "change-case": "^4.1.2", "clsx": "^2.1.1", "colord": "^2.7.0", + "csstype": "^3.2.3", "date-fns": "^3.6.0", "deepmerge": "^4.3.0", "fast-deep-equal": "^3.1.3", @@ -9359,7 +9432,7 @@ "memize": "^2.1.0", "path-to-regexp": "^6.2.1", "re-resizable": "^6.4.0", - "react-colorful": "^5.3.1", + "react-colorful": "^5.6.1", "react-day-picker": "^9.7.0", "remove-accents": "^0.5.0", "uuid": "^9.0.1" @@ -9394,12 +9467,12 @@ } }, "node_modules/@wordpress/date": { - "version": "5.37.0", - "resolved": "https://registry.npmjs.org/@wordpress/date/-/date-5.37.0.tgz", - "integrity": "sha512-T5YF5WLQu71bgw/KXhKcIqIRmAyf6nCq7J448MZlPIr0M9DAVPARXCOxYA4t/FW3PzwpFUARIuO2aNXTTO+nsA==", + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/@wordpress/date/-/date-5.39.0.tgz", + "integrity": "sha512-Wy8z+I0ZQjSTP/S4M9JqaDEUuucxGy70g+I8UCOFRgDFIO5v4hZVKo1uOH0NO5gGdlhmg4Scb9l9uzYVp8oqmg==", "license": "GPL-2.0-or-later", "dependencies": { - "@wordpress/deprecated": "^4.37.0", + "@wordpress/deprecated": "^4.39.0", "moment": "^2.29.4", "moment-timezone": "^0.5.40" }, @@ -9433,12 +9506,12 @@ "license": "BSD" }, "node_modules/@wordpress/deprecated": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/@wordpress/deprecated/-/deprecated-4.37.0.tgz", - "integrity": "sha512-QCV1akN9TXq7uRMsFQh0NyO4oHZvNP5NJWp1MSia1iqq8yLhMjcLaXVvMTnnJ2rQnVey0V0600f3BZZUyiZtEA==", + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@wordpress/deprecated/-/deprecated-4.39.0.tgz", + "integrity": "sha512-/vTXUsh2MGOuh8nhzgpBKIKsbgdtgjE2hFiCPw8rP4wwEbKUlxhNdtZdqkaumNtZywfHbRUBWO9xTpAVX17XfA==", "license": "GPL-2.0-or-later", "dependencies": { - "@wordpress/hooks": "^4.37.0" + "@wordpress/hooks": "^4.39.0" }, "engines": { "node": ">=18.12.0", @@ -9446,12 +9519,12 @@ } }, "node_modules/@wordpress/dom": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/@wordpress/dom/-/dom-4.37.0.tgz", - "integrity": "sha512-OY78iz+3bkkjOygE7pZ8Z4gSIfm+d72W6WOx5/LCuaiCKfZN2RvKzbS9r9ML5/gGgeWmhTIbwSMx6YSBIBXsXw==", + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@wordpress/dom/-/dom-4.39.0.tgz", + "integrity": "sha512-PCZVqTIV3V797rjWxZYoBJ+5g8girPZB0GzKZWxgsB6Y/+bS5OvBCJ70FgeZwO7oReo7ktXVNz/ZQMb5JsPosw==", "license": "GPL-2.0-or-later", "dependencies": { - "@wordpress/deprecated": "^4.37.0" + "@wordpress/deprecated": "^4.39.0" }, "engines": { "node": ">=18.12.0", @@ -9459,9 +9532,9 @@ } }, "node_modules/@wordpress/dom-ready": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/@wordpress/dom-ready/-/dom-ready-4.37.0.tgz", - "integrity": "sha512-igored8VegL2n/koKIyUhgPLhUfTa4N6zWO4gZpyeznr49M5wP/9Ak/tvIljUts9McHc2CVnCYMjZV0Zyz2aWg==", + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@wordpress/dom-ready/-/dom-ready-4.39.0.tgz", + "integrity": "sha512-qHhRnlSK0E2GTMo1D2gOtcr9FW11HG8X8lZLkmf3N0zhjem+MP7G15jFB7wophpypL3plnBHMxiDD6qUHh9dSg==", "license": "GPL-2.0-or-later", "engines": { "node": ">=18.12.0", @@ -9729,14 +9802,14 @@ } }, "node_modules/@wordpress/element": { - "version": "6.37.0", - "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-6.37.0.tgz", - "integrity": "sha512-8+hvjtbsPX1Jz55a5uJi6o8jNOaGlAUwV55lUJsH+iE3OHA6PyE6r9atosGRRHvfXPDlKA5ckfbrtoh7h586GA==", + "version": "6.39.0", + "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-6.39.0.tgz", + "integrity": "sha512-yVEjTddIVEsYjwRCY1pEzav/dGVH9S1xfwYwRyGsUGOQw5hXtIVnTbCIfQ2t3OCOGgSIYEh2ZmsSt0eTxZvtBw==", "license": "GPL-2.0-or-later", "dependencies": { - "@types/react": "^18.2.79", - "@types/react-dom": "^18.2.25", - "@wordpress/escape-html": "^3.37.0", + "@types/react": "^18.3.27", + "@types/react-dom": "^18.3.1", + "@wordpress/escape-html": "^3.39.0", "change-case": "^4.1.2", "is-plain-object": "^5.0.0", "react": "^18.3.0", @@ -9776,9 +9849,9 @@ } }, "node_modules/@wordpress/escape-html": { - "version": "3.37.0", - "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-3.37.0.tgz", - "integrity": "sha512-hJ2yytDPaZ7Gx+Zj+1iUBzZYED+323MTFbkpydJecWA48K+cNyutEEuHPi9bzmJXirI0YSnkN5i1tZoYQPTiGQ==", + "version": "3.39.0", + "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-3.39.0.tgz", + "integrity": "sha512-YhAmZYQsOnnfafMwInzy/M1AR77O59u4aaIVSqiQjvCLkY+BQABsU6AizDckCg57mF2SoDnARl6xQY/7FCwEmw==", "license": "GPL-2.0-or-later", "engines": { "node": ">=18.12.0", @@ -10083,9 +10156,9 @@ } }, "node_modules/@wordpress/hooks": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-4.37.0.tgz", - "integrity": "sha512-MJpPAT7hQZS5JBnQm4/f5bHSETofGOw5zt7/mNoSEby5z3yTiIyEmBmzNo4Lu1xzIiU+g0OGmkBaOvn42LBibg==", + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-4.39.0.tgz", + "integrity": "sha512-FTKdGF5jHHmC8GSO6/ATQqh1IFQeDwapRtlp7t4VaTGwZtX+uzawgq/7QDIhFi3cfg9hNsFF0CSFp/Ul3nEeUA==", "license": "GPL-2.0-or-later", "engines": { "node": ">=18.12.0", @@ -10093,9 +10166,9 @@ } }, "node_modules/@wordpress/html-entities": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/@wordpress/html-entities/-/html-entities-4.37.0.tgz", - "integrity": "sha512-d3uaAoGs20xpvdOTWlpTbxO4a8YwKYyBjoNhkL+w9qAg8NqLk9r2Z1pTj4EbYF9iS6SorDUdnXsTMuZ0/pm4Sw==", + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@wordpress/html-entities/-/html-entities-4.39.0.tgz", + "integrity": "sha512-gKpDo9vXxws4L03x6JYal8zrv+HjtQ4KMicpUdsHY8hhH5Asid2UGOBkCA1pQ1j9KVSWWj+3RsF0Ay1lem06Fw==", "license": "GPL-2.0-or-later", "engines": { "node": ">=18.12.0", @@ -10103,13 +10176,13 @@ } }, "node_modules/@wordpress/i18n": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-6.10.0.tgz", - "integrity": "sha512-5tLAtnRQNxzA/d0GvVWCyo34Jb18w7xWTnup8hlh1+ehp7ZYTWR1QJihzVAteHoyrxAbTmzzsKyNtr8m+4ZpSQ==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-6.12.0.tgz", + "integrity": "sha512-KMleg8p/HtnoX1d/WoRDI51VTZsA4RGNUvBYn+Cc3avaeeNKROb91+viMcOc8NHuLplEzl7zH9/mrOSs9aY3rg==", "license": "GPL-2.0-or-later", "dependencies": { "@tannin/sprintf": "^1.3.2", - "@wordpress/hooks": "^4.37.0", + "@wordpress/hooks": "^4.39.0", "gettext-parser": "^1.3.1", "memize": "^2.1.0", "tannin": "^1.2.0" @@ -10123,13 +10196,13 @@ } }, "node_modules/@wordpress/icons": { - "version": "11.4.0", - "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-11.4.0.tgz", - "integrity": "sha512-3sis5bwTyDOrLy83Mp799NNJuEgbTOWwHpxnGNmabEDA3BbBYPnr0soSGSFe201nqNKiVwL/TcpRaQ91SoEHTQ==", + "version": "11.6.0", + "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-11.6.0.tgz", + "integrity": "sha512-X3Tp3ARJWRokFOTJ1SJQef3J+bykyZHuHL6NSaMnEtbQzs7Tre+3HEghP5S5K/AGnLr+RvpGnwEN0uNy53uIiA==", "license": "GPL-2.0-or-later", "dependencies": { - "@wordpress/element": "^6.37.0", - "@wordpress/primitives": "^4.37.0" + "@wordpress/element": "^6.39.0", + "@wordpress/primitives": "^4.39.0" }, "engines": { "node": ">=18.12.0", @@ -10283,9 +10356,9 @@ } }, "node_modules/@wordpress/is-shallow-equal": { - "version": "5.37.0", - "resolved": "https://registry.npmjs.org/@wordpress/is-shallow-equal/-/is-shallow-equal-5.37.0.tgz", - "integrity": "sha512-aOW5Yw0uiuekmVb3KAkoWnCopBIOUOiL4XcSWAcSgRxUmtxOg6CE7M9mb5LTI35MUeQj0yay3NVyIXf5Z16LMA==", + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/@wordpress/is-shallow-equal/-/is-shallow-equal-5.39.0.tgz", + "integrity": "sha512-v/yDkQj/VpdR8y4b0xNTeuFkfdU2AYf/0YABbry6GB6zG+mkPgi2+cglQ681V621korKOX6JRAsMQuRX6D2UNA==", "license": "GPL-2.0-or-later", "engines": { "node": ">=18.12.0", @@ -10347,12 +10420,12 @@ } }, "node_modules/@wordpress/keycodes": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-4.37.0.tgz", - "integrity": "sha512-VPysLigCr6J15oMkI5YLbIM7n9D9uNTtbJpw8/SgX4gOaamfH3nH/hUJeV440JlshwX13p+hPILHq4zpOoBntg==", + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-4.39.0.tgz", + "integrity": "sha512-RN7Py7vvvmBOGuRM8X4hHOvXXG57jWDW9pIXUnVGc33EvTrwtnr16f8Xt4nKWs8L/UNUdrrAtA2HAeqRkdxr+A==", "license": "GPL-2.0-or-later", "dependencies": { - "@wordpress/i18n": "^6.10.0" + "@wordpress/i18n": "^6.12.0" }, "engines": { "node": ">=18.12.0", @@ -10780,21 +10853,21 @@ } }, "node_modules/@wordpress/preferences": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/@wordpress/preferences/-/preferences-4.37.0.tgz", - "integrity": "sha512-+GOmfe+i47SA74zDi+j6jYxoI0sZv2056tDbf+uGBF8rOEceQaLEhs+24fY/shAJV3Umf09yltXph5kdfs05/w==", + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@wordpress/preferences/-/preferences-4.39.0.tgz", + "integrity": "sha512-4rUwrdglMKxGrru5l/JTEKsv7/ncsVQFxMtlG+VmFV2f/x/WboDDz2VGQwRO2/bOTyOM4KZJpZxeJ7E1Lo2B0A==", "license": "GPL-2.0-or-later", "dependencies": { - "@wordpress/a11y": "^4.37.0", - "@wordpress/base-styles": "^6.13.0", - "@wordpress/components": "^31.0.0", - "@wordpress/compose": "^7.37.0", - "@wordpress/data": "^10.37.0", - "@wordpress/deprecated": "^4.37.0", - "@wordpress/element": "^6.37.0", - "@wordpress/i18n": "^6.10.0", - "@wordpress/icons": "^11.4.0", - "@wordpress/private-apis": "^1.37.0", + "@wordpress/a11y": "^4.39.0", + "@wordpress/base-styles": "^6.15.0", + "@wordpress/components": "^32.1.0", + "@wordpress/compose": "^7.39.0", + "@wordpress/data": "^10.39.0", + "@wordpress/deprecated": "^4.39.0", + "@wordpress/element": "^6.39.0", + "@wordpress/i18n": "^6.12.0", + "@wordpress/icons": "^11.6.0", + "@wordpress/private-apis": "^1.39.0", "clsx": "^2.1.1" }, "engines": { @@ -10807,9 +10880,9 @@ } }, "node_modules/@wordpress/preferences/node_modules/@wordpress/components": { - "version": "31.0.0", - "resolved": "https://registry.npmjs.org/@wordpress/components/-/components-31.0.0.tgz", - "integrity": "sha512-2Oz4r+4KCDTV5fMC2l9bDYfRAsLQLs29fLB4PelckW+X3KhhFM6gz5vx5N7hO3WLY19Fn6qKZgtgJjOKWG7+1w==", + "version": "32.1.0", + "resolved": "https://registry.npmjs.org/@wordpress/components/-/components-32.1.0.tgz", + "integrity": "sha512-Q4dUTWhVqV4pgW3AX9DaAwC4qsG5xiU1x9zVjo8Tm/B275hSWjumqN+spEe97T1QWx7b2RBAWK/OKVaIjkePTw==", "license": "GPL-2.0-or-later", "dependencies": { "@ariakit/react": "^0.4.15", @@ -10823,28 +10896,30 @@ "@floating-ui/react-dom": "2.0.8", "@types/gradient-parser": "1.1.0", "@types/highlight-words-core": "1.2.1", + "@types/react": "^18.3.27", "@use-gesture/react": "^10.3.1", - "@wordpress/a11y": "^4.37.0", - "@wordpress/base-styles": "^6.13.0", - "@wordpress/compose": "^7.37.0", - "@wordpress/date": "^5.37.0", - "@wordpress/deprecated": "^4.37.0", - "@wordpress/dom": "^4.37.0", - "@wordpress/element": "^6.37.0", - "@wordpress/escape-html": "^3.37.0", - "@wordpress/hooks": "^4.37.0", - "@wordpress/html-entities": "^4.37.0", - "@wordpress/i18n": "^6.10.0", - "@wordpress/icons": "^11.4.0", - "@wordpress/is-shallow-equal": "^5.37.0", - "@wordpress/keycodes": "^4.37.0", - "@wordpress/primitives": "^4.37.0", - "@wordpress/private-apis": "^1.37.0", - "@wordpress/rich-text": "^7.37.0", - "@wordpress/warning": "^3.37.0", + "@wordpress/a11y": "^4.39.0", + "@wordpress/base-styles": "^6.15.0", + "@wordpress/compose": "^7.39.0", + "@wordpress/date": "^5.39.0", + "@wordpress/deprecated": "^4.39.0", + "@wordpress/dom": "^4.39.0", + "@wordpress/element": "^6.39.0", + "@wordpress/escape-html": "^3.39.0", + "@wordpress/hooks": "^4.39.0", + "@wordpress/html-entities": "^4.39.0", + "@wordpress/i18n": "^6.12.0", + "@wordpress/icons": "^11.6.0", + "@wordpress/is-shallow-equal": "^5.39.0", + "@wordpress/keycodes": "^4.39.0", + "@wordpress/primitives": "^4.39.0", + "@wordpress/private-apis": "^1.39.0", + "@wordpress/rich-text": "^7.39.0", + "@wordpress/warning": "^3.39.0", "change-case": "^4.1.2", "clsx": "^2.1.1", "colord": "^2.7.0", + "csstype": "^3.2.3", "date-fns": "^3.6.0", "deepmerge": "^4.3.0", "fast-deep-equal": "^3.1.3", @@ -10855,7 +10930,7 @@ "memize": "^2.1.0", "path-to-regexp": "^6.2.1", "re-resizable": "^6.4.0", - "react-colorful": "^5.3.1", + "react-colorful": "^5.6.1", "react-day-picker": "^9.7.0", "remove-accents": "^0.5.0", "uuid": "^9.0.1" @@ -10884,12 +10959,12 @@ } }, "node_modules/@wordpress/primitives": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/@wordpress/primitives/-/primitives-4.37.0.tgz", - "integrity": "sha512-iPpiS1tu1U5cXxVW6CQ45rqdnIc4Ev5FGyicuuaru0wboC+d2CwoNHxPFDOOpGL16yp8OfSDA13vQY3MJHw7QA==", + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@wordpress/primitives/-/primitives-4.39.0.tgz", + "integrity": "sha512-RV9s+KzyuS8p0YKczLecmhFAtOHIWkCToHyDSmRf8N3XOy4id/T0+B5SJ2nMZJleG1oYINf5lrVcccqP2VmFJg==", "license": "GPL-2.0-or-later", "dependencies": { - "@wordpress/element": "^6.37.0", + "@wordpress/element": "^6.39.0", "clsx": "^2.1.1" }, "engines": { @@ -10901,9 +10976,9 @@ } }, "node_modules/@wordpress/priority-queue": { - "version": "3.37.0", - "resolved": "https://registry.npmjs.org/@wordpress/priority-queue/-/priority-queue-3.37.0.tgz", - "integrity": "sha512-9psU2Sb498WvZNpZkXS0m7JlFdOAp4Ohcj1BfRDDITyWH1xkRhjbp91sPsZGYtaJuDTxa8QEMyb2SSNCqcsRbQ==", + "version": "3.39.0", + "resolved": "https://registry.npmjs.org/@wordpress/priority-queue/-/priority-queue-3.39.0.tgz", + "integrity": "sha512-IyiB5y55dIYJZnC5Vl3v2cKuRDR1hYSEA++fijpD4AJZTu+2bhtdN9PnmFE9T25WGzWqnfJEdBSHPglby9MhRA==", "license": "GPL-2.0-or-later", "dependencies": { "requestidlecallback": "^0.3.0" @@ -10914,9 +10989,9 @@ } }, "node_modules/@wordpress/private-apis": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-1.37.0.tgz", - "integrity": "sha512-BR5GEHontWnza1tfBm2aX6/GjCZ1xZRrRNN1P0oj9xuvtut3YzCr//pZuyQ+P5maByDthUZjNrvN3UEF1iucbA==", + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-1.39.0.tgz", + "integrity": "sha512-ssMAzKtjPV/T1IEFWNSVeaecKG3mhRHYHPMy2Ajh7V8RpOCyYBOZ48kZGyOwd25uzQX5k634tmfW27qJ/SMVQA==", "license": "GPL-2.0-or-later", "engines": { "node": ">=18.12.0", @@ -10924,9 +10999,9 @@ } }, "node_modules/@wordpress/redux-routine": { - "version": "5.37.0", - "resolved": "https://registry.npmjs.org/@wordpress/redux-routine/-/redux-routine-5.37.0.tgz", - "integrity": "sha512-gavsOxTobcOquwl9Kpra0qR30H0vQPK1Rw+K7GDK9bNyJ+/9t4Jio0F6uVN3uQg48h/HomgRX86KCrTET9ntNA==", + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/@wordpress/redux-routine/-/redux-routine-5.39.0.tgz", + "integrity": "sha512-XvAn9I/rfVWpv9KSKLc24mYqfVHJ4o9PiwcHQb6FyJyeVvCbrHpTFN1iAaXSsBSTCs6mMVKNuZFZv7Sg7c++bg==", "license": "GPL-2.0-or-later", "dependencies": { "is-plain-object": "^5.0.0", @@ -11033,19 +11108,20 @@ } }, "node_modules/@wordpress/rich-text": { - "version": "7.37.0", - "resolved": "https://registry.npmjs.org/@wordpress/rich-text/-/rich-text-7.37.0.tgz", - "integrity": "sha512-uvLLVg77F4meoMMYjtdYcvo4eQxj3mKH8f9dKnkCrOyKOKNCdV5wHDUYWrySkgrAyoBJjTm8hZKK/U3adlPgEg==", + "version": "7.39.0", + "resolved": "https://registry.npmjs.org/@wordpress/rich-text/-/rich-text-7.39.0.tgz", + "integrity": "sha512-4wpQyyaZ9vmInRk0oiBpPFmLtGn6w+ejrQvaCeDeOAeA+IYdEvrTMEbGp7NhYZ9llZvEqEQ8/yNcoAczE4DLSg==", "license": "GPL-2.0-or-later", "dependencies": { - "@wordpress/a11y": "^4.37.0", - "@wordpress/compose": "^7.37.0", - "@wordpress/data": "^10.37.0", - "@wordpress/deprecated": "^4.37.0", - "@wordpress/element": "^6.37.0", - "@wordpress/escape-html": "^3.37.0", - "@wordpress/i18n": "^6.10.0", - "@wordpress/keycodes": "^4.37.0", + "@wordpress/a11y": "^4.39.0", + "@wordpress/compose": "^7.39.0", + "@wordpress/data": "^10.39.0", + "@wordpress/deprecated": "^4.39.0", + "@wordpress/dom": "^4.39.0", + "@wordpress/element": "^6.39.0", + "@wordpress/escape-html": "^3.39.0", + "@wordpress/i18n": "^6.12.0", + "@wordpress/keycodes": "^4.39.0", "colord": "2.9.3", "memize": "^2.1.0" }, @@ -11366,6 +11442,32 @@ "npm": ">=8.19.2" } }, + "node_modules/@wordpress/theme": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@wordpress/theme/-/theme-0.6.0.tgz", + "integrity": "sha512-n/O1djUn+jny46JyqCwD77nPV4zCUBIn+0ICp8fDYLXpQ7FfCfCrEfhlkQkcVB45KC1iu6IMAsLOA/9hzavHoQ==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@wordpress/element": "^6.39.0", + "@wordpress/private-apis": "^1.39.0", + "colorjs.io": "^0.6.0", + "memize": "^2.1.0" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0", + "stylelint": "^16.8.2" + }, + "peerDependenciesMeta": { + "stylelint": { + "optional": true + } + } + }, "node_modules/@wordpress/token-list": { "version": "3.37.0", "resolved": "https://registry.npmjs.org/@wordpress/token-list/-/token-list-3.37.0.tgz", @@ -11376,13 +11478,38 @@ "npm": ">=8.19.2" } }, + "node_modules/@wordpress/ui": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@wordpress/ui/-/ui-0.6.0.tgz", + "integrity": "sha512-KV7JkQ4bvuCWZ2+82jnhzthomCIS7wO8+6cSrkhQUgeVbp0TdGCXCFHwKXfw1O/P/S6IClqpTFgInXFz/gA1DQ==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@base-ui/react": "^1.0.0", + "@wordpress/a11y": "^4.39.0", + "@wordpress/element": "^6.39.0", + "@wordpress/i18n": "^6.12.0", + "@wordpress/icons": "^11.6.0", + "@wordpress/primitives": "^4.39.0", + "@wordpress/private-apis": "^1.39.0", + "@wordpress/theme": "^0.6.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/@wordpress/undo-manager": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@wordpress/undo-manager/-/undo-manager-1.37.0.tgz", - "integrity": "sha512-grx0GdEHMgIBj8RHym+FcK/hB4wksQ/ErStFFRCIDhew1i5wAF/boNkxBoGgI42yO5ofSAolcTlGgRCpwTzG5g==", + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/@wordpress/undo-manager/-/undo-manager-1.39.0.tgz", + "integrity": "sha512-LcCqVZk3K6tltAuUB1gXyo7lAbS48WP/RsRLrm4GBchY+kQwUT+kme30zCrRfsEJJaYCtfcGo1POIgda/HwHpw==", "license": "GPL-2.0-or-later", "dependencies": { - "@wordpress/is-shallow-equal": "^5.37.0" + "@wordpress/is-shallow-equal": "^5.39.0" }, "engines": { "node": ">=18.12.0", @@ -11416,9 +11543,9 @@ } }, "node_modules/@wordpress/url": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-4.37.0.tgz", - "integrity": "sha512-8ofI1OzPON9twQIPczG5WfAtef5hhfZY+FB6cPmwDT5BftQGGO/+v0cHge7pgrRQftSzj4iQ+fQXIdEpIFacgw==", + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-4.39.0.tgz", + "integrity": "sha512-pWOtqLcApB1EZnD2am/vMHxkCLgHzpysUypP8N8jz+USdLyOKy7vaY3v94+HAL1M9HBoT9VpllWEg+LxsO/eqQ==", "license": "GPL-2.0-or-later", "dependencies": { "remove-accents": "^0.5.0" @@ -11446,10 +11573,27 @@ "react": "^18.0.0" } }, + "node_modules/@wordpress/views": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@wordpress/views/-/views-1.6.0.tgz", + "integrity": "sha512-M1GTCktDgi7dtS1jdWByFUfVrcFl/eoMzhMACVZEGw3r/KAhRSP8AqVG2B+nRCFUFMa/YUAdsHSDdwOoOZotYw==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@wordpress/data": "^10.39.0", + "@wordpress/dataviews": "^11.3.0", + "@wordpress/element": "^6.39.0", + "@wordpress/preferences": "^4.39.0", + "dequal": "^2.0.3" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, "node_modules/@wordpress/warning": { - "version": "3.37.0", - "resolved": "https://registry.npmjs.org/@wordpress/warning/-/warning-3.37.0.tgz", - "integrity": "sha512-oXWyKiYJIa9SuPRNEJiOWn2Qk0RzfxOsDqXcus1OL44swCRtSM+ypm16CJpRhZpMUcsJ6d23PBxTC97C/iiJpQ==", + "version": "3.39.0", + "resolved": "https://registry.npmjs.org/@wordpress/warning/-/warning-3.39.0.tgz", + "integrity": "sha512-NV2WoU4XxTqZU/3k2D5m5cHIw/uM2dlDgW5u1U5fmFIYLGg43WWMlSHQEu6F4tm7fqFt7k4qwT/FymucqHYp7Q==", "license": "GPL-2.0-or-later", "engines": { "node": ">=18.12.0", @@ -11831,7 +11975,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -11841,7 +11985,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -11980,7 +12124,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -12140,7 +12284,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -12725,7 +12869,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -12862,7 +13006,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.3.1.tgz", "integrity": "sha512-yr+FSHWn1ZUou5LkULX/S+jhfgfnLbuKQjE40tyEd4fxGZVMbBL5ifno0J0OauykS8UiCSgHi+DV/YD+rjFxFg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@cacheable/memory": "^2.0.6", @@ -12905,7 +13049,7 @@ "version": "5.5.5", "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.5.5.tgz", "integrity": "sha512-FA5LmZVF1VziNc0bIdCSA1IoSVnDCqE8HJIZZv2/W8YmoAM50+tnUgJR/gQZwEeIMleuIOnRnHA/UaZRNeV4iQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@keyv/serialize": "^1.1.1" @@ -13439,7 +13583,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -13452,7 +13596,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/colord": { @@ -13468,6 +13612,16 @@ "dev": true, "license": "MIT" }, + "node_modules/colorjs.io": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.6.1.tgz", + "integrity": "sha512-8lyR2wHzuIykCpqHKgluGsqQi5iDm3/a2IgP2GBZrasn2sBRkE4NOGsglZxWLs/jZQoNkmA/KM/8NV16rLUdBg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/color" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -13916,7 +14070,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.2.3.tgz", "integrity": "sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=12 || >=16" @@ -13992,7 +14146,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "mdn-data": "2.12.2", @@ -14019,7 +14173,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "cssesc": "bin/cssesc" @@ -14642,7 +14796,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "path-type": "^4.0.0" @@ -14971,7 +15125,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6" @@ -16435,7 +16589,7 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -16452,7 +16606,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -16479,7 +16633,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -16496,7 +16650,7 @@ "version": "1.0.16", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 4.9.1" @@ -16506,7 +16660,7 @@ "version": "1.20.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -16591,7 +16745,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -16768,7 +16922,7 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/follow-redirects": { @@ -17359,7 +17513,7 @@ "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "array-union": "^2.1.0", @@ -17380,7 +17534,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/good-listener": { @@ -17503,7 +17657,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -17571,7 +17725,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.4.0.tgz", "integrity": "sha512-Wn2i1In6XFxl8Az55kkgnFRiAlIAushzh26PTjL2AKtQcEfXrcLa7Hn5QOWGZEf3LU057P9TwwZjFyxfS1VuvQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "hookified": "^1.14.0" @@ -17649,7 +17803,7 @@ "version": "1.15.0", "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.0.tgz", "integrity": "sha512-51w+ZZGt7Zw5q7rM3nC4t3aLn/xvKDETsXqMczndvwyVQhAHfUmUuFBRFcos8Iyebtk7OAE9dL26wFNzZVVOkw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/hosted-git-info": { @@ -17745,7 +17899,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -17962,7 +18116,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 4" @@ -18100,7 +18254,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.8.19" @@ -18138,7 +18292,7 @@ "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/internal-slot": { @@ -18438,7 +18592,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -18464,7 +18618,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -18504,7 +18658,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -18601,7 +18755,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -18883,7 +19037,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/isobject": { @@ -20077,7 +20231,7 @@ "version": "0.37.0", "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.37.0.tgz", "integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/language-subtag-registry": { @@ -20507,7 +20661,7 @@ "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/lodash.uniq": { @@ -20819,7 +20973,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", - "dev": true, + "devOptional": true, "license": "MIT", "funding": { "type": "github", @@ -20830,7 +20984,7 @@ "version": "2.12.2", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", - "dev": true, + "devOptional": true, "license": "CC0-1.0" }, "node_modules/mdurl": { @@ -20961,7 +21115,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 8" @@ -20988,7 +21142,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -21439,7 +21593,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -22370,7 +22524,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -23197,14 +23351,14 @@ "version": "0.1.6", "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.6.tgz", "integrity": "sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/postcss-safe-parser": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz", "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", - "dev": true, + "devOptional": true, "funding": [ { "type": "opencollective", @@ -23616,7 +23770,7 @@ "version": "0.5.3", "resolved": "https://registry.npmjs.org/qified/-/qified-0.5.3.tgz", "integrity": "sha512-kXuQdQTB6oN3KhI6V4acnBSZx8D2I4xzZvn9+wFLLFCoBNQY/sFnCW6c43OL7pOQ2HvGV4lnWIXNmgfp7cTWhQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "hookified": "^1.13.0" @@ -24216,7 +24370,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -24260,6 +24414,12 @@ "dev": true, "license": "MIT" }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", @@ -24328,7 +24488,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -24402,7 +24562,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "iojs": ">=1.0.0", @@ -24547,7 +24707,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -25498,7 +25658,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, + "devOptional": true, "license": "ISC", "engines": { "node": ">=14" @@ -25598,7 +25758,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -25608,7 +25768,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -26008,7 +26168,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -26046,7 +26206,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/string.prototype.includes": { @@ -26166,7 +26326,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -26303,7 +26463,7 @@ "version": "16.26.1", "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.26.1.tgz", "integrity": "sha512-v20V59/crfc8sVTAtge0mdafI3AdnzQ2KsWe6v523L4OA1bJO02S7MO2oyXDCS6iWb9ckIPnqAFVItqSBQr7jw==", - "dev": true, + "devOptional": true, "funding": [ { "type": "opencollective", @@ -26458,7 +26618,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.3.tgz", "integrity": "sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -26482,7 +26642,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -26505,21 +26665,21 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, + "devOptional": true, "license": "Python-2.0" }, "node_modules/stylelint/node_modules/balanced-match": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/stylelint/node_modules/cosmiconfig": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "env-paths": "^2.2.1", @@ -26546,7 +26706,7 @@ "version": "11.1.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.1.tgz", "integrity": "sha512-TPVFSDE7q91Dlk1xpFLvFllf8r0HyOMOlnWy7Z2HBku5H3KhIeOGInexrIeg2D64DosVB/JXkrrk6N/7Wriq4A==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "flat-cache": "^6.1.19" @@ -26556,7 +26716,7 @@ "version": "6.1.19", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.19.tgz", "integrity": "sha512-l/K33newPTZMTGAnnzaiqSl6NnH7Namh8jBNjrgjprWxGmZUuxx/sJNIRaijOh3n7q7ESbhNZC+pvVZMFdeU4A==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "cacheable": "^2.2.0", @@ -26568,7 +26728,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "global-prefix": "^3.0.0" @@ -26581,7 +26741,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ini": "^1.3.5", @@ -26596,7 +26756,7 @@ "version": "7.0.5", "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 4" @@ -26606,7 +26766,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -26619,7 +26779,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -26629,7 +26789,7 @@ "version": "13.2.0", "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=18" @@ -26642,7 +26802,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -26656,7 +26816,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -26669,7 +26829,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4", @@ -26689,7 +26849,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -26702,7 +26862,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0", @@ -26738,7 +26898,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", - "dev": true + "devOptional": true }, "node_modules/svgo": { "version": "3.3.2", @@ -26820,11 +26980,17 @@ "url": "https://opencollective.com/synckit" } }, + "node_modules/tabbable": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz", + "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", + "license": "MIT" + }, "node_modules/table": { "version": "6.9.0", "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "dependencies": { "ajv": "^8.0.1", @@ -26841,7 +27007,7 @@ "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -26858,7 +27024,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/tannin": { @@ -27302,7 +27468,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" diff --git a/package.json b/package.json index 33d47f65..675f6b11 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "@wordpress/components": "^30.7.0", "@wordpress/compose": "^7.34.0", "@wordpress/data": "^10.34.0", + "@wordpress/dataviews": "^11.3.0", "@wordpress/edit-post": "^8.36.0", "@wordpress/editor": "^14.34.0", "@wordpress/element": "^6.34.0", @@ -60,6 +61,7 @@ "@wordpress/icons": "^11.1.0", "@wordpress/notices": "^5.35.0", "@wordpress/plugins": "^7.34.0", + "@wordpress/views": "^1.6.0", "react": "^18.3.1" }, "overrides": {