diff --git a/alerts/class-alert-trigger-action.php b/alerts/class-alert-trigger-action.php index 445cf6ac9..c461500d2 100644 --- a/alerts/class-alert-trigger-action.php +++ b/alerts/class-alert-trigger-action.php @@ -74,7 +74,7 @@ public function add_fields( $form, $alert = array() ) { 'placeholder' => __( 'Any Action', 'stream' ), ), ); - $form->add_field( 'select2', $args ); + $form->add_field( 'grouped_select', $args ); } /** diff --git a/alerts/class-alert-trigger-author.php b/alerts/class-alert-trigger-author.php index a9d8cf5aa..f00fe41b0 100644 --- a/alerts/class-alert-trigger-author.php +++ b/alerts/class-alert-trigger-author.php @@ -60,16 +60,17 @@ public function add_fields( $form, $alert = array() ) { $value = $alert->alert_meta['trigger_author']; } - $args = array( - 'name' => esc_attr( $this->field_key ), - 'value' => esc_attr( $value ), - 'options' => $this->get_values(), - 'classes' => 'wp_stream_ajax_forward', - 'data' => array( - 'placeholder' => __( 'Any Author', 'stream' ), - ), + $form->add_field( + 'grouped_select', + array( + 'name' => esc_attr( $this->field_key ), + 'value' => esc_attr( $value ), + 'options' => $this->get_values(), + 'data' => array( + 'placeholder' => __( 'Any Author', 'stream' ), + ), + ) ); - $form->add_field( 'select2', $args ); } /** @@ -80,13 +81,6 @@ public function add_fields( $form, $alert = array() ) { public function get_values() { $all_records = array(); - $user_count = count_users(); - $total_users = $user_count['total_users']; - - if ( $total_users > $this->plugin->admin->preload_users_max ) { - return array(); - } - $users = array_map( function ( $user_id ) { return new Author( $user_id ); diff --git a/alerts/class-alert-trigger-context.php b/alerts/class-alert-trigger-context.php index 9f6b16437..2b01f9482 100644 --- a/alerts/class-alert-trigger-context.php +++ b/alerts/class-alert-trigger-context.php @@ -72,7 +72,7 @@ public function add_fields( $form, $alert = array() ) { $context_values = array(); $form->add_field( - 'select2', + 'grouped_select', array( 'name' => 'wp_stream_trigger_connector_or_context', 'options' => $this->get_values(), diff --git a/changelog.md b/changelog.md index c5068bc81..f4ecea5e1 100644 --- a/changelog.md +++ b/changelog.md @@ -9,6 +9,7 @@ ### Enhancements - Add an Outgoing Webhook alert (HTTP POST or PUT, optional headers, JSON body with record-field placeholders). IFTTT is no longer offered when creating a new alert; existing IFTTT alerts still fire. To keep using Maker, configure a webhook whose URL is `https://maker.ifttt.com/trigger/{event}/with/key/{key}`. +- Replace Select2 with native `', + '', esc_attr( $args['name'] ), esc_attr( $args['classes'] ), - esc_attr( $args['value'] ) + esc_attr( $args['value'] ), + $placeholder ); break; case 'hidden': @@ -119,20 +124,28 @@ public function render_field( $field_type, $args, $echo_output = true ) { } $output .= ''; break; - case 'select2': + case 'grouped_select': $values = array(); $multiple = ( $args['multiple'] ) ? ' multiple' : ''; - $output = sprintf( - '', esc_attr( $args['name'] ), esc_attr( $args['classes'] ), $this->prepare_data_attributes_string( $args['data'] ), // The data attributes are escaped in the function. - $multiple + $multiple, + $label ); if ( array_key_exists( 'placeholder', $args['data'] ) && ! $multiple ) { - $output .= ''; + $output .= sprintf( + '', + esc_html( $args['data']['placeholder'] ) + ); } foreach ( $args['options'] as $parent ) { @@ -144,38 +157,36 @@ public function render_field( $field_type, $args, $echo_output = true ) { 'children' => array(), ) ); - if ( empty( $parent['value'] ) ) { + if ( '' === (string) $parent['value'] && empty( $parent['children'] ) ) { continue; } - if ( is_array( $args['value'] ) ) { - $selected = selected( in_array( $parent['value'], $args['value'], true ), true, false ); - } else { - $selected = selected( $args['value'], $parent['value'], false ); - } - $output .= sprintf( - '', - esc_attr( $parent['value'] ), - $selected, - esc_html( $parent['text'] ) - ); - $values[] = $parent['value']; - if ( ! empty( $parent['children'] ) ) { + + // Group header (no value of its own): render children inside an optgroup. + if ( '' === (string) $parent['value'] ) { + $output .= sprintf( + '', + esc_attr( $parent['text'] ) + ); foreach ( $parent['children'] as $child ) { - $output .= sprintf( - '', - esc_attr( $child['value'] ), - selected( $args['value'], $child['value'], false ), - esc_html( $child['text'] ) - ); + $output .= $this->render_select_option( $child, $args['value'] ); $values[] = $child['value']; } $output .= ''; + continue; + } + + // Selectable parent option followed by its children, matching the previous flat markup. + $output .= $this->render_select_option( $parent, $args['value'], 'parent' ); + $values[] = $parent['value']; + foreach ( $parent['children'] as $child ) { + $output .= $this->render_select_option( $child, $args['value'], 'child' ); + $values[] = $child['value']; } } - $selected_values = explode( ',', $args['value'] ); + $selected_values = is_array( $args['value'] ) ? $args['value'] : explode( ',', (string) $args['value'] ); foreach ( $selected_values as $selected_value ) { - if ( ! empty( $selected_value ) && ! in_array( $selected_value, array_map( 'strval', $values ), true ) ) { + if ( ! empty( $selected_value ) && ! in_array( (string) $selected_value, array_map( 'strval', $values ), true ) ) { $output .= sprintf( '', esc_attr( $selected_value ), @@ -210,6 +221,40 @@ public function render_field( $field_type, $args, $echo_output = true ) { echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } + /** + * Render a single ', + $class_attr, + esc_attr( $option['value'] ), + $selected, + esc_html( $option['text'] ) + ); + } + /** * Prepares string with HTML data attributes * diff --git a/classes/class-list-table.php b/classes/class-list-table.php index c7fa9bf60..11ee026ed 100644 --- a/classes/class-list-table.php +++ b/classes/class-list-table.php @@ -713,7 +713,7 @@ public function filter_date( $items ) {
', - esc_attr( $option_key ), - esc_attr( $section ), - esc_attr( $name ), - esc_attr( wp_json_encode( $data_values ) ), - esc_attr( $current_value ), - esc_attr( $class ), - /* translators: %s: the title of the dropdown menu (e.g. "users") */ - sprintf( esc_html__( 'Any %s', 'stream' ), $title ) + $selected_values = is_array( $current_value ) ? $current_value : explode( ',', (string) $current_value ); + $form = new Form_Generator(); + $input_html = $form->render_field( + 'grouped_select', + array( + 'name' => sprintf( '%1$s[%2$s_%3$s]', $option_key, $section, $name ), + 'value' => array_map( 'strval', $selected_values ), + 'options' => self::choices_to_options( $choices ), + 'classes' => $class, + 'data' => array( + 'placeholder' => sprintf( + /* translators: %s: the title of the dropdown menu (e.g. "users") */ + __( 'Any %s', 'stream' ), + $title + ), + ), + ), + false ); $output = sprintf( @@ -298,7 +274,6 @@ public function render_field( $field, $options, $option_key ) { private function render_rule_list( $field, $current_value, $option_key, $section, $name, $description ) { unset( $field ); - $users = count_users(); $form = new Form_Generator(); $output = '

' . esc_html( $description ) . '

'; @@ -340,7 +315,6 @@ private function render_rule_list( $field, $current_value, $option_key, $section foreach ( $current_value['exclude_row'] as $key => $value ) { $exclude_rows[] = $this->render_rule_list_row( $form, - $users, $current_value, $key, $option_key, @@ -369,7 +343,6 @@ private function render_rule_list( $field, $current_value, $option_key, $section * Render a single exclude-rule table row. * * @param Form_Generator $form Form helper. - * @param array $users count_users() payload. * @param array $current_value Stored rule list value. * @param string|int $key Row key. * @param string $option_key Settings option key. @@ -377,58 +350,54 @@ private function render_rule_list( $field, $current_value, $option_key, $section * @param string $name Field name. * @return string */ - private function render_rule_list_row( $form, $users, $current_value, $key, $option_key, $section, $name ) { + private function render_rule_list_row( $form, $current_value, $key, $option_key, $section, $name ) { $author_or_role = isset( $current_value['author_or_role'][ $key ] ) ? $current_value['author_or_role'][ $key ] : ''; $connector = isset( $current_value['connector'][ $key ] ) ? $current_value['connector'][ $key ] : ''; $context = isset( $current_value['context'][ $key ] ) ? $current_value['context'][ $key ] : ''; $action = isset( $current_value['action'][ $key ] ) ? $current_value['action'][ $key ] : ''; $ip_address = isset( $current_value['ip_address'][ $key ] ) ? $current_value['ip_address'][ $key ] : ''; - $author_or_role_values = array(); - $author_or_role_selected = array(); - - foreach ( Settings_Registry::get_roles() as $role_id => $role ) { - $args = array( + $role_options = array(); + foreach ( Settings_Registry::get_roles() as $role_id => $role_label ) { + $role_options[] = array( 'value' => $role_id, - 'text' => $role, + 'text' => $role_label, ); - $count = isset( $users['avail_roles'][ $role_id ] ) ? $users['avail_roles'][ $role_id ] : 0; + } - if ( ! empty( $count ) ) { - /* translators: %d: a number of users (e.g. "42") */ - $args['user_count'] = sprintf( _n( '%d user', '%d users', absint( $count ), 'stream' ), absint( $count ) ); - } + $author_or_role_values = array( + array( + 'text' => __( 'Roles', 'stream' ), + 'children' => $role_options, + ), + array( + 'text' => __( 'Users', 'stream' ), + 'children' => $this->get_exclude_user_options(), + ), + ); - if ( $role_id === $author_or_role ) { - $author_or_role_selected['value'] = $role_id; - $author_or_role_selected['text'] = $role; + // Stored value missing from the lists (e.g. a deleted user): keep it selectable. + if ( '' !== (string) $author_or_role && ctype_digit( (string) $author_or_role ) ) { + $known = array_map( 'strval', array_column( $author_or_role_values[1]['children'], 'value' ) ); + if ( ! in_array( (string) $author_or_role, $known, true ) ) { + $user = get_userdata( (int) $author_or_role ); + $author_or_role_values[1]['children'][] = array( + 'value' => (string) $author_or_role, + 'text' => ( $user && $user->ID ) ? $user->display_name : esc_html__( 'N/A', 'stream' ), + ); } - - $author_or_role_values[] = $args; - } - - if ( empty( $author_or_role_selected ) && is_numeric( $author_or_role ) ) { - $user = new WP_User( $author_or_role ); - $display_name = ( 0 === $user->ID ) ? esc_html__( 'N/A', 'stream' ) : $user->display_name; - $author_or_role_selected = array( - 'value' => $user->ID, - 'text' => $display_name, - ); - $author_or_role_values[] = $author_or_role_selected; } $author_or_role_input = $form->render_field( - 'select2', + 'grouped_select', array( 'name' => esc_attr( sprintf( '%1$s[%2$s_%3$s][%4$s][]', $option_key, $section, $name, 'author_or_role' ) ), + 'value' => (string) $author_or_role, 'options' => $author_or_role_values, 'classes' => 'author_or_role', // Data attributes are escaped in Form_Generator::prepare_data_attributes_string(). 'data' => array( - 'placeholder' => __( 'Any Author or Role', 'stream' ), - 'nonce' => wp_create_nonce( 'stream_get_users' ), - 'selected-id' => isset( $author_or_role_selected['value'] ) ? $author_or_role_selected['value'] : '', - 'selected-text' => isset( $author_or_role_selected['text'] ) ? $author_or_role_selected['text'] : '', + 'placeholder' => __( 'Any Author or Role', 'stream' ), ), ), false @@ -465,9 +434,10 @@ private function render_rule_list_row( $form, $users, $current_value, $key, $opt } $connector_or_context_input = $form->render_field( - 'select2', + 'grouped_select', array( 'name' => esc_attr( sprintf( '%1$s[%2$s_%3$s][%4$s][]', $option_key, $section, $name, 'connector_or_context' ) ), + 'value' => ( '' !== $context ) ? $connector . '-' . $context : $connector, 'options' => $context_values, 'classes' => 'connector_or_context', // Data attributes are escaped in Form_Generator::prepare_data_attributes_string(). @@ -509,7 +479,7 @@ private function render_rule_list_row( $form, $users, $current_value, $key, $opt } $action_input = $form->render_field( - 'select2', + 'grouped_select', array( 'name' => esc_attr( sprintf( '%1$s[%2$s_%3$s][%4$s][]', $option_key, $section, $name, 'action' ) ), 'value' => $action, @@ -524,17 +494,14 @@ private function render_rule_list_row( $form, $users, $current_value, $key, $opt ); $ip_address_input = $form->render_field( - 'select2', + 'text', array( - 'name' => esc_attr( sprintf( '%1$s[%2$s_%3$s][%4$s][]', $option_key, $section, $name, 'ip_address' ) ), - 'value' => $ip_address, - 'classes' => 'ip_address', - // Data attributes are escaped in Form_Generator::prepare_data_attributes_string(). - 'data' => array( + 'name' => esc_attr( sprintf( '%1$s[%2$s_%3$s][%4$s][]', $option_key, $section, $name, 'ip_address' ) ), + 'value' => $ip_address, + 'classes' => 'ip_address', + 'data' => array( 'placeholder' => __( 'Any IP Address', 'stream' ), - 'nonce' => wp_create_nonce( 'stream_get_ips' ), ), - 'multiple' => true, ), false ); @@ -604,6 +571,92 @@ public function get_terms_labels( $column ) { return $return_labels; } + /** + * User options for the exclude-rules author/role select. + * + * Lists every user (plus network super-admins and the WP-CLI pseudo-user), + * mirroring the previous Select2 Ajax dropdown's result set. + * + * @return array + */ + private function get_exclude_user_options() { + $users = get_users( + array( + 'fields' => array( 'ID', 'display_name' ), + ) + ); + + if ( is_multisite() && is_super_admin() ) { + foreach ( get_super_admins() as $login ) { + $super = get_user_by( 'login', $login ); + if ( $super ) { + $users[] = $super; + } + } + } + + $options = array(); + $seen = array(); + + foreach ( $users as $user ) { + $id = (string) $user->ID; + if ( isset( $seen[ $id ] ) ) { + continue; + } + $seen[ $id ] = true; + + $options[] = array( + 'value' => $id, + 'text' => $user->display_name, + ); + } + + $options[] = array( + 'value' => '0', + 'text' => 'WP-CLI', + ); + + return $options; + } + + /** + * Map a settings `choices` array (flat or `label`/`children` groups) to Form_Generator options. + * + * @param array $choices Field choices. + * @return array + */ + private static function choices_to_options( array $choices ): array { + $options = array(); + + foreach ( $choices as $key => $value ) { + if ( ! is_array( $value ) ) { + $options[] = array( + 'value' => (string) $key, + 'text' => $value, + ); + continue; + } + + $children = array(); + if ( isset( $value['children'] ) && is_array( $value['children'] ) ) { + foreach ( $value['children'] as $child_key => $child_value ) { + $children[] = array( + 'value' => (string) $child_key, + 'text' => $child_value, + ); + } + } + + $options[] = array( + 'value' => (string) $key, + 'text' => isset( $value['label'] ) ? $value['label'] : (string) $key, + 'children' => $children, + ); + } + + return $options; + } + /** * Settings API field callback: render and echo field HTML. * diff --git a/classes/class-settings.php b/classes/class-settings.php index e29735dca..e10c3fd6a 100644 --- a/classes/class-settings.php +++ b/classes/class-settings.php @@ -7,8 +7,6 @@ namespace WP_Stream; -use WP_User_Query; - /** * Class - Settings */ @@ -97,190 +95,6 @@ public function __construct( public $plugin ) { 'filter_serialized_labels', ) ); - - // Ajax callback function to search users. - add_action( 'wp_ajax_stream_get_users', array( $this, 'get_users' ) ); - - // Ajax callback function to search IPs. - add_action( 'wp_ajax_stream_get_ips', array( $this, 'get_ips' ) ); - } - - /** - * Ajax callback function to search users, used on exclude setting page - * - * @uses \WP_User_Query - */ - public function get_users() { - if ( ! defined( 'DOING_AJAX' ) || ! current_user_can( $this->plugin->admin->settings_cap ) ) { - return; - } - - check_ajax_referer( 'stream_get_users', 'nonce' ); - - $response = (object) array( - 'status' => false, - 'message' => esc_html__( 'There was an error in the request', 'stream' ), - ); - - $search = ''; - $input = wp_stream_filter_input( INPUT_POST, 'find' ); - - if ( isset( $input['term'] ) ) { - $search = wp_unslash( trim( $input['term'] ) ); - } - - $request = (object) array( - 'find' => $search, - ); - - add_filter( - 'user_search_columns', - array( - $this, - 'add_display_name_search_columns', - ), - 10, - 3 - ); - - $users = new WP_User_Query( - array( - 'search' => "*{$request->find}*", - 'search_columns' => array( - 'user_login', - 'user_nicename', - 'user_email', - 'user_url', - ), - 'orderby' => 'display_name', - 'number' => $this->plugin->admin->preload_users_max, - ) - ); - - remove_filter( - 'user_search_columns', - array( - $this, - 'add_display_name_search_columns', - ), - 10 - ); - - if ( 0 === $users->get_total() ) { - wp_send_json_error( $response ); - } - $users_array = $users->results; - - if ( is_multisite() && is_super_admin() ) { - $super_admins = get_super_admins(); - foreach ( $super_admins as $admin ) { - $user = get_user_by( 'login', $admin ); - $users_array[] = $user; - } - } - - $response->status = true; - $response->message = ''; - $response->roles = $this->registry->get_roles(); - $response->users = array(); - $users_added_to_response = array(); - - foreach ( $users_array as $key => $user ) { - // exclude duplications. - if ( array_key_exists( $user->ID, $users_added_to_response ) ) { - continue; - } else { - $users_added_to_response[ $user->ID ] = true; - } - - $author = new Author( $user->ID ); - - $args = array( - 'id' => $author->ID, - 'text' => $author->display_name, - ); - - $args['tooltip'] = esc_attr( - sprintf( - /* translators: %1$d: user ID, %2$s: username, %3$s: email, %4$s: user role (e.g. "42", "administrator", "foo@bar.com", "subscriber") */ - __( 'ID: %1$d\nUser: %2$s\nEmail: %3$s\nRole: %4$s', 'stream' ), - $author->id, - $author->user_login, - $author->user_email, - ucwords( $author->get_role() ) - ) - ); - - $args['icon'] = $author->get_avatar_src( 32 ); - - $response->users[] = $args; - } - - usort( - $response->users, - function ( $a, $b ) { - return strcmp( $a['text'], $b['text'] ); - } - ); - - if ( empty( $search ) || preg_match( '/wp|cli|system|unknown/i', $search ) ) { - $author = new Author( 0 ); - $response->users[] = array( - 'id' => '0', - 'text' => $author->get_display_name(), - 'icon' => $author->get_avatar_src( 32 ), - 'tooltip' => esc_html__( 'Actions performed by the system when a user is not logged in (e.g. auto site upgrader, or invoking WP-CLI without --user)', 'stream' ), - ); - } - - wp_send_json_success( $response ); - } - - /** - * Ajax callback function to search IP addresses, used on exclude setting page - */ - public function get_ips() { - if ( ! defined( 'DOING_AJAX' ) || ! current_user_can( $this->plugin->admin->settings_cap ) ) { - return; - } - - check_ajax_referer( 'stream_get_ips', 'nonce' ); - - $ips = $this->plugin->db->existing_records( 'ip' ); - $find = wp_stream_filter_input( INPUT_POST, 'find' ); - - if ( isset( $find['term'] ) && '' !== $find['term'] ) { - $ips = array_filter( - $ips, - function ( $ip ) use ( $find ) { - return 0 === strpos( $ip, $find['term'] ); - } - ); - } - - if ( $ips ) { - wp_send_json_success( $ips ); - } else { - wp_send_json_error(); - } - } - - /** - * Filter the columns to search in a WP_User_Query search. - * - * @param array $search_columns Array of column names to be searched. - * @param string $search Text being searched. - * @param \WP_User_Query $query current WP_User_Query instance. - * - * @return array - */ - public function add_display_name_search_columns( $search_columns, $search, $query ) { - unset( $search ); - unset( $query ); - - $search_columns[] = 'display_name'; - - return $search_columns; } /** diff --git a/package-lock.json b/package-lock.json index 44beee2a7..f5f9bf3df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,10 +6,6 @@ "": { "name": "wp-stream", "license": "GPLv2+", - "dependencies": { - "select2": "^4.1.0", - "timeago": "^1.6.7" - }, "devDependencies": { "@playwright/test": "^1.62.1", "@types/node": "^24.13.3", @@ -17,7 +13,6 @@ "@wordpress/env": "^10.39.0", "@wordpress/eslint-plugin": "^25.2.0", "@wordpress/scripts": "^32.2.0", - "copy-webpack-plugin": "^14.0.0", "eslint-plugin-react-hooks": "^7.1.1", "globals": "^17.12.0", "jquery": "4.0.0", @@ -11493,30 +11488,6 @@ "dev": true, "license": "MIT" }, - "node_modules/copy-webpack-plugin": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-14.0.0.tgz", - "integrity": "sha512-3JLW90aBGeaTLpM7mYQKpnVdgsUZRExY55giiZgLuX/xTQRUs1dOCwbBnWnvY6Q6rfZoXMNwzOQJCSZPppfqXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "glob-parent": "^6.0.1", - "normalize-path": "^3.0.0", - "schema-utils": "^4.2.0", - "serialize-javascript": "^7.0.3", - "tinyglobby": "^0.2.12" - }, - "engines": { - "node": ">= 20.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - } - }, "node_modules/core-js": { "version": "3.49.0", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.49.0.tgz", @@ -22300,15 +22271,6 @@ "dev": true, "license": "MIT" }, - "node_modules/select2": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/select2/-/select2-4.1.0.tgz", - "integrity": "sha512-i9KalWOP4/LRRGc8+rj2krNm0ZqP14cV+j1TRCEBSsOhCPkKH8rYZ2MCRXcgvqIqN+llqGci0hj9aVkCMvL0+g==", - "license": "MIT", - "engines": { - "node": ">=24" - } - }, "node_modules/selfsigned": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", @@ -22400,16 +22362,6 @@ "upper-case-first": "^2.0.2" } }, - "node_modules/serialize-javascript": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.5.tgz", - "integrity": "sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=20.0.0" - } - }, "node_modules/serve-index": { "version": "1.9.2", "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.2.tgz", @@ -24397,20 +24349,6 @@ "dev": true, "license": "MIT" }, - "node_modules/timeago": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/timeago/-/timeago-1.6.7.tgz", - "integrity": "sha512-FikcjN98+ij0siKH4VO4dZ358PR3oDDq4Vdl1+sN9gWz1/+JXGr3uZbUShYH/hL7bMhcTpPbplJU5Tej4b4jbQ==", - "dependencies": { - "jquery": ">=1.5.0 <4.0" - } - }, - "node_modules/timeago/node_modules/jquery": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", - "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", - "license": "MIT" - }, "node_modules/tinyglobby": { "version": "0.2.16", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", diff --git a/package.json b/package.json index 96cc2740b..11a005e28 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,6 @@ "@wordpress/env": "^10.39.0", "@wordpress/eslint-plugin": "^25.2.0", "@wordpress/scripts": "^32.2.0", - "copy-webpack-plugin": "^14.0.0", "eslint-plugin-react-hooks": "^7.1.1", "globals": "^17.12.0", "jquery": "4.0.0", @@ -70,10 +69,6 @@ "large-records-generate": "wp-env run cli -- bash -c 'wp db query < wp-content/plugins/stream/local/scripts/large-datasets/bulk-insert-logs.sql'", "large-records-show": "wp-env run cli -- bash -c 'wp db query < wp-content/plugins/stream/local/scripts/large-datasets/show-stream-db-stats.sql'" }, - "dependencies": { - "select2": "^4.1.0", - "timeago": "^1.6.7" - }, "overrides": { "@wordpress/e2e-test-utils-playwright": { "@types/node": "$@types/node" diff --git a/src/css/admin.scss b/src/css/admin.scss index f256865a2..7667ef43d 100644 --- a/src/css/admin.scss +++ b/src/css/admin.scss @@ -20,34 +20,10 @@ overflow: visible; } -.post-type-wp_stream_alerts .select2 .select2-selection, -.stream-exclude-list .select2 .select2-selection{ - border-color: #ccc; - background: #f7f7f7; - -webkit-box-shadow: 0 1px 0 #ccc; - box-shadow: 0 1px 0 #ccc; -} - -.post-type-wp_stream_alerts .select2 .select2-selection--multiple, -.stream-exclude-list .select2 .select2-selection--multiple{ - font-size: 0; - min-height: 28px; -} - -.post-type-wp_stream_alerts .select2-container.select2-container--focus .select2-selection--multiple, -.stream-exclude-list .select2-container.select2-container--focus .select2-selection--multiple{ - border: solid #ccc 1px; -} - -.post-type-wp_stream_alerts .select2-container .select2-selection--multiple .select2-selection__choice, -.stream-exclude-list .select2-container .select2-selection--multiple .select2-selection__choice{ - margin-top: 4px; - margin-bottom: 3px; -} - -.post-type-wp_stream_alerts .select2 .select2-selection .select2-selection__rendered, -.stream-exclude-list .select2 .select2-selection .select2-selection__rendered{ - color: #555; +.post-type-wp_stream_alerts .inline-edit-col select, +.post-type-wp_stream_alerts #add-new-alert select, +.stream-exclude-list select { + max-width: 100%; } #record-query-reset { @@ -233,7 +209,6 @@ more custom columns squeezes the summary column to a narrow strip. vertical-align: middle; } - /* Live Update */ .toplevel_page_wp_stream .stream-live-update-checkbox .spinner { @@ -282,13 +257,6 @@ more custom columns squeezes the summary column to a narrow strip. text-decoration: none; } -.wp_stream_settings .select2.select2-container, -.wp_stream_network_settings .select2.select2-container, -.wp_stream_default_settings .select2.select2-container { - min-width: 160px; - max-width: 100%; -} - .wp_stream_settings .tablenav, .wp_stream_network_settings .tablenav, .wp_stream_default_settings .tablenav { @@ -375,70 +343,6 @@ more custom columns squeezes the summary column to a narrow strip. } -/* Select2 Common */ - -.post-type-wp_stream_alerts li.select2-searching, -.post-type-wp_stream_alerts li.select2-no-results { - background: none; - padding: 7px 7px 0; - color: #999; -} - -.post-type-wp_stream_alerts .select2 .select2-selection .select2-selection__placeholder { - color: #72777c; -} - -.post-type-wp_stream_alerts .select2-results .select2-disabled { - background: transparent; - color: #aaa; -} - -.post-type-wp_stream_alerts .select2 .select2-search--inline { - float: none; - margin-bottom: 4px; - margin-left: 2px; -} - -.post-type-wp_stream_alerts .select2-selection .icon16 { - margin: -3px 1px 0 -3px; - padding: 0; - width: 16px; - height: 16px; -} - -.post-type-wp_stream_alerts .select2-selection .icon16 { - padding-right: 8px; -} - -.post-type-wp_stream_alerts .select2-chosen .icon16:before, -.post-type-wp_stream_alerts .select2-search-choice .icon16:before { - font-size: 15px !important; - color: #656565; -} - -.post-type-wp_stream_alerts .select2-search-choice-close { - -webkit-transition: none; - -moz-transition: none; - -o-transition: all 0 none; - transition: none; -} - -.wp-stream-select2-icon { - position: relative; - top: 3px; - margin-right: 4px; - width: 16px; - height: 16px; -} - -.select2-disabled .wp-stream-select2-icon { - filter: url("data:image/svg+xml;utf8,#grayscale"); /* Firefox 3.5+ */ - filter: gray; /* IE6-9 */ - -webkit-filter: grayscale(100%); /* Chrome 19+ & Safari 6+ */ - -moz-filter: grayscale(100%); /* Firefox < 3.5 */ -} - - /* Exclude List Table */ .stream-exclude-list { @@ -490,8 +394,15 @@ more custom columns squeezes the summary column to a narrow strip. margin-top: 4px; } +.stream-exclude-list tbody td select, +.stream-exclude-list tbody td .stream-user-combobox__input, .stream-exclude-list tbody td .ip_address { width: 100%; + max-width: 100%; +} + +.stream-exclude-list tbody td .stream-user-combobox__input { + margin-bottom: 4px; } .stream-exclude-list tbody td .ip_address.invalid { @@ -503,14 +414,6 @@ more custom columns squeezes the summary column to a narrow strip. position: relative !important; } -.wp_stream_screen .select2-results__option .parent { - font-weight: bold; -} - -.wp_stream_screen .select2-results__option .child { - padding-left: 8px; -} - @media screen and ( max-width: 900px ) { .wp_stream_settings .stream-exclude-list .actions-column, .wp_stream_network_settings .stream-exclude-list .actions-column, diff --git a/src/css/alerts-list.scss b/src/css/alerts-list.scss index f18eab8ed..591c59eda 100644 --- a/src/css/alerts-list.scss +++ b/src/css/alerts-list.scss @@ -20,12 +20,8 @@ color: #555; } -.edit-php.post-type-wp_stream_alerts .inline-edit-col .select2-container { - display: block; -} - #wp_stream_alert_type_form { - max-height: 14em; + max-height: 22em; border: 1px solid #ddd; overflow-y: scroll; padding: 0.2em 0.5em; @@ -45,6 +41,14 @@ font-size: 1em; } +.inline-edit-row fieldset.inline-edit-wp_stream_alerts label span.title { + width: 8em; +} + +.inline-edit-row fieldset.inline-edit-wp_stream_alerts label span.input-text-wrap { + margin-left: 8em; +} + .edit-php.post-type-wp_stream_alerts .misc-pub-section.misc-pub-post-status, .edit-php.post-type-wp_stream_alerts #misc-publishing-actions { padding: 0; @@ -61,30 +65,21 @@ } } -.edit-php.post-type-wp_stream_alerts .select2 .select2-selection { - border-color: #ccc; - background: #f7f7f7; - -webkit-box-shadow: 0 1px 0 #ccc; - box-shadow: 0 1px 0 #ccc; -} .edit-php.post-type-wp_stream_alerts label { font-style: italic; } -.edit-php.post-type-wp_stream_alerts select { - width: auto; -} - -.edit-php.post-type-wp_stream_alerts span.select2-selection__rendered { - font-style: normal; +.edit-php.post-type-wp_stream_alerts .inline-edit-col select, +.edit-php.post-type-wp_stream_alerts #add-new-alert select { + width: 100%; + max-width: 100%; + display: block; + margin-bottom: 6px; } #add-new-alert.inline-edit-row.inline-edit-row-page .inline-edit-col-right { margin-top: 32px; } -.edit-php.post-type-wp_stream_alerts #add-new-alert .select2.select2-container { - display: block; -} .edit-php.post-type-wp_stream_alerts #add-new-alert .inline-edit-add-new-notifications, .edit-php.post-type-wp_stream_alerts #add-new-alert .inline-edit-add-new-status { margin-top: 33px; @@ -93,7 +88,8 @@ margin: .2em 0; line-height: 2.5; } -.post-type-wp_stream_alerts .select2-container { +.post-type-wp_stream_alerts .inline-edit-col select, +.post-type-wp_stream_alerts #add-new-alert select { margin-bottom: 6px; min-width: 165px; } diff --git a/src/js/admin-exclude.js b/src/js/admin-exclude.js index e597caffc..06ce886e4 100644 --- a/src/js/admin-exclude.js +++ b/src/js/admin-exclude.js @@ -12,277 +12,25 @@ import wp_stream_regenerate_alt_rows from './utils/wp-stream-regenerate-alt-rows const $excludeRows = $( '.stream-exclude-list tbody tr:not(.hidden)' ); const $placeholderRow = $( '.stream-exclude-list tr.helper' ); -const initSettingsSelect2 = function( $rowsWithSelect2 ) { - let $input_user; - - $( 'select.select2-select.connector_or_context', $rowsWithSelect2 ).each( - function( k, el ) { - $( el ).select2( - { - allowClear: true, - templateResult( item ) { - if ( typeof item.id === 'undefined' ) { - return item.text; - } - if ( item.id.indexOf( '-' ) === -1 ) { - return $( '' + item.text + '' ); - } - return $( '' + item.text + '' ); - }, - matcher( params, data ) { - const match = $.extend( true, {}, data ); - - if ( null === params.term || $.trim( params.term ) === '' ) { - return match; - } - - const term = params.term.toLowerCase(); - - match.id = match.id.replace( 'blogs', 'sites' ); - if ( match.id.toLowerCase().indexOf( term ) >= 0 ) { - return match; - } - - if ( match.children ) { - for ( let i = match.children.length - 1; i >= 0; i-- ) { - const child = match.children[ i ]; - - // Remove term from results if it doesn't match. - if ( child.id.toLowerCase().indexOf( term ) === -1 ) { - match.children.splice( i, 1 ); - } - } - - if ( match.children.length > 0 ) { - return match; - } - } - - return null; - }, - }, - ).on( - 'change', function() { - const row = $( this ).closest( 'tr' ); - let connector = $( this ).val(); - if ( connector && 0 < connector.indexOf( '-' ) ) { - const connector_split = connector.split( '-' ); - connector = connector_split[ 0 ]; - } - getActions( row, connector ); - }, - ); - }, - ); - - $( 'select.select2-select.action', $rowsWithSelect2 ).each( - function( k, el ) { - $( el ).select2( - { - allowClear: true, - }, - ); - }, - ); - - $( 'select.select2-select.author_or_role', $rowsWithSelect2 ).each( - function( k, el ) { - $input_user = $( el ); - - $input_user.select2( - { - ajax: { - type: 'POST', - url: window.ajaxurl, - dataType: 'json', - quietMillis: 500, - data( term, page ) { - return { - find: term, - limit: 10, - pager: page, - action: 'stream_get_users', - nonce: $input_user.data( 'nonce' ), - }; - }, - processResults( response ) { - const answer = { - results: [ - { text: '', id: '' }, - { text: 'Roles', children: [] }, - { text: 'Users', children: [] }, - ], - }; - - if ( true !== response.success || undefined === response.data || true !== response.data.status ) { - return answer; - } - - if ( undefined === response.data.users || undefined === response.data.roles ) { - return answer; - } - - const roles = []; - - $.each( - response.data.roles, function( id, text ) { - roles.push( - { - id, - text, - }, - ); - }, - ); - - answer.results[ 1 ].children = roles; - answer.results[ 2 ].children = response.data.users; - - // Return the value of more so Select2 knows if more results can be loaded - return answer; - }, - }, - templateResult( object ) { - const $result = $( '
' ).text( object.text ); - - if ( 'undefined' !== typeof object.icon && object.icon ) { - $result.prepend( $( '' ) ); - - // Add more info to the container - $result.attr( 'title', object.tooltip ); - } - - // Add more info to the container - if ( 'undefined' !== typeof object.tooltip ) { - $result.attr( 'title', object.tooltip ); - } else if ( 'undefined' !== typeof object.user_count ) { - $result.attr( 'title', object.user_count ); - } - - return $result; - }, - templateSelection( object ) { - const $result = $( '
' ).text( object.text ); - - if ( $.isNumeric( object.id ) && object.text.indexOf( 'icon-users' ) < 0 ) { - $result.append( $( '' ) ); - } - - return $result; - }, - allowClear: true, - placeholder: $input_user.data( 'placeholder' ), - }, - ).on( - 'change', function() { - const value = $( this ).select2( 'data' ); - - $( this ).data( 'selected-id', value.id ); - $( this ).data( 'selected-text', value.text ); - }, - ); - }, - ); - - $( 'select.select2-select.ip_address', $rowsWithSelect2 ).each( - function( k, el ) { - const $input_ip = $( el ); - let searchTerm = ''; - - $input_ip.select2( - { - ajax: { - type: 'POST', - url: window.ajaxurl, - dataType: 'json', - quietMillis: 500, - data( term ) { - searchTerm = term.term; - return { - find: term, - limit: 10, - action: 'stream_get_ips', - nonce: $input_ip.data( 'nonce' ), - }; - }, - processResults( response ) { - const answer = { results: [] }; - let ip_chunks = []; - - if ( true === response.success && undefined !== response.data ) { - $.each( - response.data, function( key, ip ) { - answer.results.push( - { - id: ip, - text: ip, - }, - ); - }, - ); - } - - if ( undefined === searchTerm ) { - return answer; - } - - ip_chunks = searchTerm.match( /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/ ); - - if ( null === ip_chunks ) { - return answer; - } - - // remove whole match - ip_chunks.shift(); - - ip_chunks = $.grep( - ip_chunks, - function( chunk ) { - const numeric = parseInt( chunk, 10 ); - return numeric <= 255 && numeric.toString() === chunk; - }, - ); - - if ( ip_chunks.length >= 4 ) { - answer.results.push( - { - id: searchTerm, - text: searchTerm, - }, - ); - } - - return answer; - }, - }, - allowClear: false, - multiple: true, - maximumSelectionSize: 1, - placeholder: $input_ip.data( 'placeholder' ), - tags: true, - }, - ); - }, - ).on( +/** + * Bind native exclude-rule controls for one or more rows. + * + * @param {Object} $rows jQuery collection of table rows. + */ +function initExcludeRows( $rows ) { + $( 'select.connector_or_context', $rows ).on( 'change', function() { - $( this ).prev( '.select2-container' ).find( 'input.select2-input' ).blur(); - }, - ); - - $( 'ul.select2-choices, ul.select2-choices li, input.select2-input', '.stream-exclude-list tr:not(.hidden) .ip_address' ).on( - 'mousedown click focus', function() { - const $container = $( this ).closest( '.select2-container' ), - $input = $container.find( 'input.select2-input' ), - value = $container.select2( 'data' ); - - if ( value.length >= 1 ) { - $input.blur(); - return false; + const row = $( this ).closest( 'tr' ); + let connector = $( this ).val(); + if ( connector && 0 < connector.indexOf( '-' ) ) { + const connector_split = connector.split( '-' ); + connector = connector_split[ 0 ]; } + getActions( row, connector ); }, ); - $( '.exclude_rules_remove_rule_row', $rowsWithSelect2 ).on( + $( '.exclude_rules_remove_rule_row', $rows ).on( 'click', function( e ) { const $thisRow = $( this ).closest( 'tr' ); @@ -294,18 +42,11 @@ const initSettingsSelect2 = function( $rowsWithSelect2 ) { e.preventDefault(); }, ); -}; - -initSettingsSelect2( $excludeRows ); +} -$( 'select.select2-select.author_or_role', $excludeRows ).each( - function() { - const $option = $( '' ).val( $( this ).data( 'selected-id' ) ); - $( this ).append( $option ).trigger( 'change' ); - }, -); +initExcludeRows( $excludeRows ); -$( 'select.select2-select.connector_or_context', $excludeRows ).each( +$( 'select.connector_or_context', $excludeRows ).each( function() { const parts = [ $( this ).siblings( '.connector' ).val(), @@ -325,7 +66,7 @@ $( '#exclude_rules_new_rule' ).on( $newRow.removeAttr( 'class' ); $newRow.insertBefore( $placeholderRow ); - initSettingsSelect2( $newRow ); + initExcludeRows( $newRow ); recalculate_rules_found(); recalculate_rules_selected(); }, @@ -341,7 +82,6 @@ $( '#exclude_rules_remove_rules' ).on( } else { $( ':input', selectedRows ).val( '' ); $( selectedRows ).not( ':first' ).remove(); - $( '.select2-select', selectedRows ).select2( 'val', '' ); } $excludeList.find( 'input.cb-select' ).prop( 'checked', false ); @@ -358,31 +98,14 @@ $( '.stream-exclude-list' ).closest( 'form' ).submit( $( this ).find( ':input' ).removeAttr( 'name' ); }, ); - $( '.stream-exclude-list tbody tr:not(.hidden) select.select2-select.connector_or_context', this ).each( + $( '.stream-exclude-list tbody tr:not(.hidden) select.connector_or_context', this ).each( function() { const parts = $( this ).val().split( '-' ); $( this ).siblings( '.connector' ).val( parts[ 0 ] ); - $( this ).siblings( '.context' ).val( parts.slice( 1 ).join( '-' ) ); + $( this ).siblings( '.context' ).val( parts[ 1 ] ); $( this ).removeAttr( 'name' ); }, ); - $( '.stream-exclude-list tbody tr:not(.hidden) select.select2-select.ip_address', this ).each( - function() { - const firstSelected = $( 'option:selected', this ).first(); - - // Ugly hack to ensure we always pass an empty value or the order of rows gets messed up. - if ( ! firstSelected.length ) { - $( this ).append( '' ); - } - - $( 'option:selected:not(:first)', this ).each( - function() { - firstSelected.attr( 'value', firstSelected.attr( 'value' ) + ',' + $( this ).attr( 'value' ) ); - $( this ).removeAttr( 'selected' ); - }, - ); - }, - ); }, ); @@ -395,13 +118,13 @@ $( 'table.stream-exclude-list' ).on( ); function getActions( row, connector ) { - const trigger_action = $( '.select2-select.action', row ), + const trigger_action = $( 'select.action', row ), action_value = trigger_action.val(); trigger_action.empty(); trigger_action.prop( 'disabled', true ); - const placeholder = $( '', $html ); + $this->assertStringNotContainsString( 'value="media" selected="selected"', $html ); + } + + public function test_placeholder_option_carries_text_and_aria_label() { + $html = $this->render_select( + '', + array( + array( + 'value' => 'posts', + 'text' => 'Posts', + ), + ), + array( 'placeholder' => 'Any Context' ) + ); + + $this->assertStringContainsString( 'aria-label="Any Context"', $html ); + $this->assertStringContainsString( '', $html ); + } + + public function test_missing_selected_value_appends_fallback_option() { + $html = $this->render_select( + 'not-an-option', + array( + array( + 'value' => 'posts', + 'text' => 'Posts', + ), + ) + ); + + $this->assertStringContainsString( + '', + $html + ); + } + + public function test_text_field_renders_placeholder_attribute() { + $html = $this->generator->render_field( + 'text', + array( + 'name' => 'test_text', + 'value' => '8.8.8.8', + 'classes' => 'ip_address', + 'data' => array( + 'placeholder' => 'Any IP Address', + ), + ), + false + ); + + $this->assertStringContainsString( 'type="text"', $html ); + $this->assertStringContainsString( 'placeholder="Any IP Address"', $html ); + $this->assertStringContainsString( 'value="8.8.8.8"', $html ); + } +} diff --git a/tests/phpunit/unit/Settings_Renderer_Unit_Test.php b/tests/phpunit/unit/Settings_Renderer_Unit_Test.php index 72975b9f8..a15664783 100644 --- a/tests/phpunit/unit/Settings_Renderer_Unit_Test.php +++ b/tests/phpunit/unit/Settings_Renderer_Unit_Test.php @@ -6,6 +6,8 @@ use PHPUnit\Framework\Attributes\DataProvider; use Yoast\WPTestUtils\BrainMonkey\TestCase; +require_once __DIR__ . '/settings-registry-wp-roles-stub.php'; + class Settings_Renderer_Unit_Test extends TestCase { /** * Renderer under test. @@ -33,14 +35,24 @@ protected function set_up() { Functions\when( 'translate_user_role' )->returnArg(); Functions\when( 'wp_create_nonce' )->justReturn( 'test-nonce' ); Functions\when( 'wp_parse_args' )->alias( array( self::class, 'wp_parse_args_stub' ) ); - Functions\when( 'wp_roles' )->justReturn( null ); - Functions\when( 'count_users' )->justReturn( + if ( ! class_exists( \WP_Roles::class, false ) ) { + class_alias( Settings_Registry_Wp_Roles_Stub::class, 'WP_Roles' ); + } + Functions\when( 'wp_roles' )->justReturn( new Settings_Registry_Wp_Roles_Stub() ); + Functions\when( 'is_multisite' )->justReturn( false ); + Functions\when( 'get_users' )->justReturn( array( - 'avail_roles' => array( - 'administrator' => 1, + (object) array( + 'ID' => 1, + 'display_name' => 'Test Admin', + ), + (object) array( + 'ID' => 2, + 'display_name' => 'Test Editor', ), ) ); + Functions\when( 'get_userdata' )->justReturn( false ); $this->plugin = Mockery::mock( Plugin::class ); $this->plugin->connectors = Mockery::mock( Connectors::class ); @@ -277,6 +289,63 @@ public static function data_render_field_html() { ); } + public function test_rule_list_renders_native_selects_with_roles_and_users() { + $html = $this->renderer->render_field( + $this->make_field( + array( + 'type' => 'rule_list', + 'section' => 'exclude', + 'name' => 'rules', + 'desc' => 'Exclude', + ) + ), + array( + 'exclude_rules' => array( + 'exclude_row' => array( 'row1' => '1' ), + 'author_or_role' => array( 'row1' => '1' ), + 'connector' => array( 'row1' => 'posts' ), + 'context' => array( 'row1' => 'post' ), + 'action' => array( 'row1' => 'updated' ), + 'ip_address' => array( 'row1' => '203.0.113.10' ), + ), + ), + 'wp_stream' + ); + + $this->assertStringContainsString( '', $html ); + $this->assertStringContainsString( '', $html ); + $this->assertStringContainsString( '>Test Admin', $html ); + $this->assertStringContainsString( '>WP-CLI', $html ); + $this->assertStringContainsString( 'value="1" selected="selected"', $html ); + $this->assertStringContainsString( 'value="203.0.113.10"', $html ); + $this->assertStringNotContainsString( 'select2', $html ); + } + + public function test_rule_list_labels_missing_user_as_not_available() { + Functions\when( 'get_userdata' )->justReturn( false ); + + $html = $this->renderer->render_field( + $this->make_field( + array( + 'type' => 'rule_list', + 'section' => 'exclude', + 'name' => 'rules', + 'desc' => 'Exclude', + ) + ), + array( + 'exclude_rules' => array( + 'exclude_row' => array( 'row1' => '1' ), + 'author_or_role' => array( 'row1' => '42' ), + ), + ), + 'wp_stream' + ); + + $this->assertStringContainsString( 'value="42"', $html ); + $this->assertStringContainsString( '>N/A', $html ); + } + public function test_render_field_returns_empty_when_required_keys_missing() { $this->assertSame( '', $this->renderer->render_field( array( 'type' => 'text' ), array(), 'wp_stream' ) ); } diff --git a/ui/js/alerts-list.js b/ui/js/alerts-list.js index aa1e72484..f3480bdd8 100644 --- a/ui/js/alerts-list.js +++ b/ui/js/alerts-list.js @@ -8,9 +8,6 @@ }, ); - // This is done with JS instead of CSS to override the inline styles added by Select2's JS. - $( '.select2-container', '.inline-edit-col' ).css( { width: '100%' } ); - // Re-enable the select all functionality $( '.wp-list-table thead .check-column input[type="checkbox"]' ).on( 'click', diff --git a/webpack.config.js b/webpack.config.js index 62fb444c6..2e2160fc5 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,9 +1,3 @@ -/** - * External dependencies - */ -const path = require( 'path' ); -const CopyPlugin = require( 'copy-webpack-plugin' ); - /** * WordPress dependencies */ @@ -22,35 +16,4 @@ module.exports = { settings: './src/js/settings.js', 'wpseo-admin': './src/js/wpseo-admin.js', }, - plugins: [ - ...defaultConfig.plugins, - new CopyPlugin( { - patterns: [ - { - from: 'node_modules/select2/dist', - // Convert filenames to lowercase. - to( { context, absoluteFilename } ) { - const baseName = path.basename( absoluteFilename ).toLowerCase(); - const relativePath = path.relative( context, path.dirname( absoluteFilename ) ); - - return path.join( 'select2', relativePath, baseName ); - }, - }, - { - from: 'node_modules/timeago/jquery.timeago.js', - to: 'timeago/js/jquery.timeago.js', - }, - { - from: 'node_modules/timeago/locales', - // Convert filenames to lowercase. - to( { context, absoluteFilename } ) { - const baseName = path.basename( absoluteFilename ).toLowerCase(); - const relativePath = path.relative( context, path.dirname( absoluteFilename ) ); - - return path.join( 'timeago', 'js', 'locales', relativePath, baseName ); - }, - }, - ], - } ), - ], };