Changeset 3458971
- Timestamp:
- 02/11/2026 01:02:27 PM (7 weeks ago)
- Location:
- bettercx-widget/trunk
- Files:
-
- 8 added
- 14 edited
-
assets/admin.css (modified) (1 diff)
-
assets/admin.js (modified) (1 diff)
-
assets/bettercx-widget.esm.js (modified) (1 diff)
-
assets/index.esm.js (modified) (1 diff)
-
assets/p-2877dab7.system.entry.js (added)
-
assets/p-29aba404.js (added)
-
assets/p-51b4eee3.entry.js (added)
-
assets/p-798e9c30.entry.js (added)
-
assets/p-7b9255d8.system.entry.js (added)
-
assets/p-B2BYj5Em.system.js (added)
-
assets/p-BTyCVrZy.js (added)
-
assets/p-V8up-zPo.system.js (modified) (1 diff)
-
assets/p-aSnaIZqK.system.js (added)
-
bettercx-widget.php (modified) (14 diffs)
-
readme.txt (modified) (3 diffs)
-
src/components/bcx-chat-list/bcx-chat-list.scss (modified) (7 diffs)
-
src/components/bcx-chat-list/bcx-chat-list.tsx (modified) (3 diffs)
-
src/components/bcx-message-composer/bcx-message-composer.scss (modified) (1 diff)
-
src/components/bcx-product-slider/bcx-product-slider.scss (modified) (2 diffs)
-
src/components/bcx-product-slider/bcx-product-slider.tsx (modified) (6 diffs)
-
src/components/bettercx-widget/bettercx-widget.scss (modified) (38 diffs)
-
src/components/bettercx-widget/bettercx-widget.tsx (modified) (8 diffs)
Legend:
- Unmodified
- Added
- Removed
-
bettercx-widget/trunk/assets/admin.css
r3370223 r3458971 88 88 } 89 89 90 #test-connection {91 margin-left: 10px;92 }93 94 #test-connection:disabled {95 opacity: 0.6;96 cursor: not-allowed;97 } -
bettercx-widget/trunk/assets/admin.js
r3370223 r3458971 1 1 jQuery(document).ready(function ($) { 2 // Test connection functionality3 $('#test-connection').on('click', function (e) {4 e.preventDefault();5 6 var button = $(this);7 var originalText = button.text();8 9 button.prop('disabled', true).text('Testing...');10 11 $.ajax({12 url: bettercxWidgetAdmin.ajaxUrl,13 type: 'POST',14 data: {15 action: 'bettercx_test_connection',16 nonce: bettercxWidgetAdmin.nonce,17 },18 success: function (response) {19 if (response.success) {20 alert('Connection test successful!');21 } else {22 alert('Connection test failed: ' + response.data);23 }24 },25 error: function () {26 alert('Connection test failed: Network error');27 },28 complete: function () {29 button.prop('disabled', false).text(originalText);30 },31 });32 });33 34 2 // Form validation 35 3 $('#bettercx-widget-settings-form').on('submit', function (e) { -
bettercx-widget/trunk/assets/bettercx-widget.esm.js
r3435673 r3458971 1 import{p as e,g as a,b as t}from"./p-BnsX22WT.js";export{s as setNonce}from"./p-BnsX22WT.js";(()=>{const a=import.meta.url,s={};return""!==a&&(s.resourcesUrl=new URL(".",a).href),e(s)})().then((async e=>(await a(),t([["p- c0e0975b",[[257,"bcx-chat-list",{apiService:[16,"api-service"],language:[1],theme:[1],chats:[32],selectedChatId:[32],messages:[32],isLoading:[32],isLoadingMore:[32],hasMore:[32],currentPage:[32],error:[32]},null,{language:["onLanguageChange"]}],[257,"bcx-message-composer",{disabled:[4],loading:[4],placeholder:[1],maxLength:[2,"max-length"],theme:[1],isAttachmentsDisabled:[4,"is-attachments-disabled"],message:[32],images:[32]}],[257,"bcx-product-slider",{products:[16],language:[1],showAfterStreaming:[4,"show-after-streaming"],currentIndex:[32],isVisible:[32]},null,{products:["onProductsChange"],showAfterStreaming:["onShowAfterStreamingChange"]}]]],["p-66ae8630",[[257,"bettercx-widget",{publicKey:[1,"public-key"],theme:[1],debug:[4],baseUrl:[1,"base-url"],aiServiceUrl:[1,"ai-service-url"],autoInit:[4,"auto-init"],position:[1],language:[1],embedded:[4],embeddedSize:[1,"embedded-size"],embeddedPlacement:[1,"embedded-placement"],isAttachmentsDisabled:[4,"is-attachments-disabled"],state:[32],timeUpdateTrigger:[32],isDropdownOpen:[32],isFullscreen:[32],showChatList:[32],showDelayMessage:[32],delayMessageType:[32],open:[64],close:[64],toggle:[64],sendMessage:[64]},null,{publicKey:["onPublicKeyChange"],theme:["onThemeChange"],language:["onLanguageChange"],"state.messages":["onMessagesChange"],embedded:["onEmbeddedChange"],embeddedSize:["onEmbeddedSizeChange"],embeddedPlacement:["onEmbeddedPlacementChange"]}]]]],e))));1 import{p as e,g as a,b as t}from"./p-BnsX22WT.js";export{s as setNonce}from"./p-BnsX22WT.js";(()=>{const a=import.meta.url,s={};return""!==a&&(s.resourcesUrl=new URL(".",a).href),e(s)})().then((async e=>(await a(),t([["p-51b4eee3",[[257,"bcx-chat-list",{apiService:[16,"api-service"],language:[1],theme:[1],chats:[32],selectedChatId:[32],messages:[32],isLoading:[32],isLoadingMore:[32],hasMore:[32],currentPage:[32],error:[32]},null,{language:["onLanguageChange"]}],[257,"bcx-message-composer",{disabled:[4],loading:[4],placeholder:[1],maxLength:[2,"max-length"],theme:[1],isAttachmentsDisabled:[4,"is-attachments-disabled"],message:[32],images:[32]}],[257,"bcx-product-slider",{products:[16],language:[1],showAfterStreaming:[4,"show-after-streaming"],currentIndex:[32],isVisible:[32]},null,{products:["onProductsChange"],showAfterStreaming:["onShowAfterStreamingChange"]}]]],["p-798e9c30",[[257,"bettercx-widget",{publicKey:[1,"public-key"],theme:[1],debug:[4],baseUrl:[1,"base-url"],aiServiceUrl:[1,"ai-service-url"],autoInit:[4,"auto-init"],position:[1],language:[1],embedded:[4],embeddedSize:[1,"embedded-size"],embeddedPlacement:[1,"embedded-placement"],isAttachmentsDisabled:[4,"is-attachments-disabled"],state:[32],timeUpdateTrigger:[32],isDropdownOpen:[32],isFullscreen:[32],showChatList:[32],showDelayMessage:[32],delayMessageType:[32],open:[64],close:[64],toggle:[64],sendMessage:[64]},null,{publicKey:["onPublicKeyChange"],theme:["onThemeChange"],language:["onLanguageChange"],"state.messages":["onMessagesChange"],embedded:["onEmbeddedChange"],embeddedSize:["onEmbeddedSizeChange"],embeddedPlacement:["onEmbeddedPlacementChange"]}]]]],e)))); -
bettercx-widget/trunk/assets/index.esm.js
r3435673 r3458971 1 export{a as ApiService,A as AuthService,B as BetterCXWidget,T as ThemeService}from"./p- DC9T6BNW.js";import"./p-BnsX22WT.js";import"./p-BU1bGO0l.js";function e(e,r,t){return(e||"")+(r?` ${r}`:"")+(t?` ${t}`:"")}export{e as format}1 export{a as ApiService,A as AuthService,B as BetterCXWidget,T as ThemeService}from"./p-BTyCVrZy.js";import"./p-BnsX22WT.js";import"./p-BU1bGO0l.js";function e(e,r,t){return(e||"")+(r?` ${r}`:"")+(t?` ${t}`:"")}export{e as format} -
bettercx-widget/trunk/assets/p-V8up-zPo.system.js
r3435673 r3458971 1 var __awaiter=this&&this.__awaiter||function(e,t,n,a){function i(e){return e instanceof n?e:new n((function(t){t(e)}))}return new(n||(n=Promise))((function(n,r){function s(e){try{c(a.next(e))}catch(e){r(e)}}function o(e){try{c(a["throw"](e))}catch(e){r(e)}}function c(e){e.done?n(e.value):i(e.value).then(s,o)}c((a=a.apply(e,t||[])).next())}))};var __generator=this&&this.__generator||function(e,t){var n={label:0,sent:function(){if(r[0]&1)throw r[1];return r[1]},trys:[],ops:[]},a,i,r,s;return s={next:o(0),throw:o(1),return:o(2)},typeof Symbol==="function"&&(s[Symbol.iterator]=function(){return this}),s;function o(e){return function(t){return c([e,t])}}function c(o){if(a)throw new TypeError("Generator is already executing.");while(s&&(s=0,o[0]&&(n=0)),n)try{if(a=1,i&&(r=o[0]&2?i["return"]:o[0]?i["throw"]||((r=i["return"])&&r.call(i),0):i.next)&&!(r=r.call(i,o[1])).done)return r;if(i=0,r)o=[o[0]&2,r.value];switch(o[0]){case 0:case 1:r=o;break;case 4:n.label++;return{value:o[1],done:false};case 5:n.label++;i=o[1];o=[0];continue;case 7:o=n.ops.pop();n.trys.pop();continue;default:if(!(r=n.trys,r=r.length>0&&r[r.length-1])&&(o[0]===6||o[0]===2)){n=0;continue}if(o[0]===3&&(!r||o[1]>r[0]&&o[1]<r[3])){n.label=o[1];break}if(o[0]===6&&n.label<r[1]){n.label=r[1];r=o;break}if(r&&n.label<r[2]){n.label=r[2];n.ops.push(o);break}if(r[2])n.ops.pop();n.trys.pop();continue}o=t.call(e,n)}catch(e){o=[6,e];i=0}finally{a=r=0}if(o[0]&5)throw o[1];return{value:o[0]?o[1]:void 0,done:true}}};System.register(["./p-Cbgoi924.system.js"],(function(e,t){"use strict";var n,a,i;return{setters:[function(t){n=t.p;a=t.g;i=t.b;e("setNonce",t.s)}],execute:function(){var e=this;var r=function(){var e=t.meta.url;var a={};if(e!==""){a.resourcesUrl=new URL(".",e).href}return n(a)};r().then((function(t){return __awaiter(e,void 0,void 0,(function(){return __generator(this,(function(e){switch(e.label){case 0:return[4,a()];case 1:e.sent();return[2,i([["p- 5d3a139e.system",[[257,"bcx-chat-list",{apiService:[16,"api-service"],language:[1],theme:[1],chats:[32],selectedChatId:[32],messages:[32],isLoading:[32],isLoadingMore:[32],hasMore:[32],currentPage:[32],error:[32]},null,{language:["onLanguageChange"]}],[257,"bcx-message-composer",{disabled:[4],loading:[4],placeholder:[1],maxLength:[2,"max-length"],theme:[1],isAttachmentsDisabled:[4,"is-attachments-disabled"],message:[32],images:[32]}],[257,"bcx-product-slider",{products:[16],language:[1],showAfterStreaming:[4,"show-after-streaming"],currentIndex:[32],isVisible:[32]},null,{products:["onProductsChange"],showAfterStreaming:["onShowAfterStreamingChange"]}]]],["p-05e57c9d.system",[[257,"bettercx-widget",{publicKey:[1,"public-key"],theme:[1],debug:[4],baseUrl:[1,"base-url"],aiServiceUrl:[1,"ai-service-url"],autoInit:[4,"auto-init"],position:[1],language:[1],embedded:[4],embeddedSize:[1,"embedded-size"],embeddedPlacement:[1,"embedded-placement"],isAttachmentsDisabled:[4,"is-attachments-disabled"],state:[32],timeUpdateTrigger:[32],isDropdownOpen:[32],isFullscreen:[32],showChatList:[32],showDelayMessage:[32],delayMessageType:[32],open:[64],close:[64],toggle:[64],sendMessage:[64]},null,{publicKey:["onPublicKeyChange"],theme:["onThemeChange"],language:["onLanguageChange"],"state.messages":["onMessagesChange"],embedded:["onEmbeddedChange"],embeddedSize:["onEmbeddedSizeChange"],embeddedPlacement:["onEmbeddedPlacementChange"]}]]]],t)]}}))}))}))}}}));1 var __awaiter=this&&this.__awaiter||function(e,t,n,a){function i(e){return e instanceof n?e:new n((function(t){t(e)}))}return new(n||(n=Promise))((function(n,r){function s(e){try{c(a.next(e))}catch(e){r(e)}}function o(e){try{c(a["throw"](e))}catch(e){r(e)}}function c(e){e.done?n(e.value):i(e.value).then(s,o)}c((a=a.apply(e,t||[])).next())}))};var __generator=this&&this.__generator||function(e,t){var n={label:0,sent:function(){if(r[0]&1)throw r[1];return r[1]},trys:[],ops:[]},a,i,r,s;return s={next:o(0),throw:o(1),return:o(2)},typeof Symbol==="function"&&(s[Symbol.iterator]=function(){return this}),s;function o(e){return function(t){return c([e,t])}}function c(o){if(a)throw new TypeError("Generator is already executing.");while(s&&(s=0,o[0]&&(n=0)),n)try{if(a=1,i&&(r=o[0]&2?i["return"]:o[0]?i["throw"]||((r=i["return"])&&r.call(i),0):i.next)&&!(r=r.call(i,o[1])).done)return r;if(i=0,r)o=[o[0]&2,r.value];switch(o[0]){case 0:case 1:r=o;break;case 4:n.label++;return{value:o[1],done:false};case 5:n.label++;i=o[1];o=[0];continue;case 7:o=n.ops.pop();n.trys.pop();continue;default:if(!(r=n.trys,r=r.length>0&&r[r.length-1])&&(o[0]===6||o[0]===2)){n=0;continue}if(o[0]===3&&(!r||o[1]>r[0]&&o[1]<r[3])){n.label=o[1];break}if(o[0]===6&&n.label<r[1]){n.label=r[1];r=o;break}if(r&&n.label<r[2]){n.label=r[2];n.ops.push(o);break}if(r[2])n.ops.pop();n.trys.pop();continue}o=t.call(e,n)}catch(e){o=[6,e];i=0}finally{a=r=0}if(o[0]&5)throw o[1];return{value:o[0]?o[1]:void 0,done:true}}};System.register(["./p-Cbgoi924.system.js"],(function(e,t){"use strict";var n,a,i;return{setters:[function(t){n=t.p;a=t.g;i=t.b;e("setNonce",t.s)}],execute:function(){var e=this;var r=function(){var e=t.meta.url;var a={};if(e!==""){a.resourcesUrl=new URL(".",e).href}return n(a)};r().then((function(t){return __awaiter(e,void 0,void 0,(function(){return __generator(this,(function(e){switch(e.label){case 0:return[4,a()];case 1:e.sent();return[2,i([["p-7b9255d8.system",[[257,"bcx-chat-list",{apiService:[16,"api-service"],language:[1],theme:[1],chats:[32],selectedChatId:[32],messages:[32],isLoading:[32],isLoadingMore:[32],hasMore:[32],currentPage:[32],error:[32]},null,{language:["onLanguageChange"]}],[257,"bcx-message-composer",{disabled:[4],loading:[4],placeholder:[1],maxLength:[2,"max-length"],theme:[1],isAttachmentsDisabled:[4,"is-attachments-disabled"],message:[32],images:[32]}],[257,"bcx-product-slider",{products:[16],language:[1],showAfterStreaming:[4,"show-after-streaming"],currentIndex:[32],isVisible:[32]},null,{products:["onProductsChange"],showAfterStreaming:["onShowAfterStreamingChange"]}]]],["p-2877dab7.system",[[257,"bettercx-widget",{publicKey:[1,"public-key"],theme:[1],debug:[4],baseUrl:[1,"base-url"],aiServiceUrl:[1,"ai-service-url"],autoInit:[4,"auto-init"],position:[1],language:[1],embedded:[4],embeddedSize:[1,"embedded-size"],embeddedPlacement:[1,"embedded-placement"],isAttachmentsDisabled:[4,"is-attachments-disabled"],state:[32],timeUpdateTrigger:[32],isDropdownOpen:[32],isFullscreen:[32],showChatList:[32],showDelayMessage:[32],delayMessageType:[32],open:[64],close:[64],toggle:[64],sendMessage:[64]},null,{publicKey:["onPublicKeyChange"],theme:["onThemeChange"],language:["onLanguageChange"],"state.messages":["onMessagesChange"],embedded:["onEmbeddedChange"],embeddedSize:["onEmbeddedSizeChange"],embeddedPlacement:["onEmbeddedPlacementChange"]}]]]],t)]}}))}))}))}}})); -
bettercx-widget/trunk/bettercx-widget.php
r3445478 r3458971 4 4 * Plugin URI: https://wordpress.org/plugins/bettercx-widget/ 5 5 * Description: Professional AI-powered chat widget for BetterCX platform. Seamlessly integrate intelligent customer support into any website with full WordPress compatibility. Fully functional out of the box with no trial limitations. 6 * Version: 1.0.2 16 * Version: 1.0.22 7 7 * Author: BetterCX 8 8 * Author URI: https://bettercx.ai … … 16 16 * 17 17 * @package BetterCX_Widget 18 * @version 1.0.2 118 * @version 1.0.22 19 19 * @author BetterCX 20 20 * @license GPLv2+ … … 37 37 38 38 // Define plugin constants 39 define('BETTERCX_WIDGET_VERSION', '1.0.2 1');39 define('BETTERCX_WIDGET_VERSION', '1.0.22'); 40 40 define('BETTERCX_WIDGET_PLUGIN_FILE', __FILE__); 41 41 define('BETTERCX_WIDGET_PLUGIN_DIR', plugin_dir_path(__FILE__)); … … 74 74 75 75 /** 76 * Request-level cache for page_has_bettercx_shortcode() to avoid repeated lookups. 77 * 78 * @since 1.0.0 79 * @var bool|null 80 */ 81 private static $page_has_shortcode_cache = null; 82 83 /** 76 84 * Get plugin instance 77 85 * … … 122 130 // AJAX handlers 123 131 add_action('wp_ajax_bettercx_save_settings', array($this, 'ajax_save_settings')); 124 add_action('wp_ajax_bettercx_test_connection', array($this, 'ajax_test_connection'));125 126 132 // Allow custom elements in content 127 133 add_filter('wp_kses_allowed_html', array($this, 'allow_custom_elements'), 10, 2); … … 214 220 */ 215 221 public function enqueue_scripts() { 216 // Only load if widget is enabled and we have a public key 217 if (!$this->settings['widget_enabled'] || empty($this->settings['public_key'])) { 222 $has_public_key = !empty($this->settings['public_key']); 223 $show_globally = !empty($this->settings['widget_enabled']); 224 $show_via_shortcode = $this->page_has_bettercx_shortcode(); 225 226 // Load assets when widget is shown globally and/or this page contains the shortcode. 227 if (!$has_public_key || (!$show_globally && !$show_via_shortcode)) { 218 228 return; 219 229 } … … 322 332 'nonce' => wp_create_nonce('bettercx_widget_admin_nonce'), 323 333 'strings' => array( 324 'testConnectionSuccess' => __('Connection test successful!', 'bettercx-widget'),325 'testConnectionError' => __('Connection test failed. Please check your settings.', 'bettercx-widget'),326 334 'settingsSaved' => __('Settings saved successfully!', 'bettercx-widget'), 327 335 'settingsError' => __('Error saving settings. Please try again.', 'bettercx-widget'), … … 553 561 $sanitized = array(); 554 562 563 // Defensive: ensure we have an array (WordPress sends array; avoid overwriting with partial data) 564 if (!is_array($input)) { 565 return get_option('bettercx_widget_settings', $this->get_default_settings()); 566 } 567 555 568 if (isset($input['public_key'])) { 556 569 $sanitized['public_key'] = sanitize_text_field($input['public_key']); … … 589 602 $sanitized['enable_analytics'] = isset($input['enable_analytics']) ? (bool) $input['enable_analytics'] : false; 590 603 $sanitized['privacy_consent'] = isset($input['privacy_consent']) ? (bool) $input['privacy_consent'] : false; 591 $sanitized['widget_enabled'] = isset($input['widget_enabled']) ? (bool) $input['widget_enabled'] : true; 604 // Unchecked checkboxes are not sent in POST; treat missing as false so "Enable Widget" can be turned off. 605 $sanitized['widget_enabled'] = !empty($input['widget_enabled']); 592 606 $sanitized['logged_in_only'] = isset($input['logged_in_only']) ? (bool) $input['logged_in_only'] : false; 593 607 $sanitized['is_attachments_disabled'] = isset($input['is_attachments_disabled']) ? (bool) $input['is_attachments_disabled'] : false; … … 605 619 } 606 620 607 return $sanitized; 621 // Merge with existing options so we never drop keys (e.g. after plugin update or partial form submit). 622 $existing = get_option('bettercx_widget_settings', array()); 623 if (!is_array($existing)) { 624 $existing = array(); 625 } 626 return array_merge($existing, $sanitized); 608 627 } 609 628 … … 687 706 688 707 /** 708 * Check if the current singular page contains the BetterCX shortcode. 709 * Supports classic post_content and Elementor (_elementor_data) for page builders. 710 * Result is cached for the current request to avoid repeated lookups. 711 * 712 * @since 1.0.0 713 * @return bool True if the shortcode is present, false otherwise. 714 */ 715 private function page_has_bettercx_shortcode() { 716 if (self::$page_has_shortcode_cache !== null) { 717 return self::$page_has_shortcode_cache; 718 } 719 720 self::$page_has_shortcode_cache = false; 721 722 if (!is_singular()) { 723 return self::$page_has_shortcode_cache; 724 } 725 726 $post_id = get_the_ID(); 727 if (!$post_id || !is_numeric($post_id)) { 728 return self::$page_has_shortcode_cache; 729 } 730 731 $post = get_post($post_id); 732 if (!$post || !isset($post->post_content) || !is_string($post->post_content)) { 733 // Fallback: check Elementor data only (e.g. Elementor-built page with no classic content). 734 $elementor_data = get_post_meta($post_id, '_elementor_data', true); 735 if (is_string($elementor_data) && $elementor_data !== '' && strpos($elementor_data, 'bettercx_widget') !== false) { 736 self::$page_has_shortcode_cache = true; 737 } 738 return self::$page_has_shortcode_cache; 739 } 740 741 if (has_shortcode($post->post_content, 'bettercx_widget')) { 742 self::$page_has_shortcode_cache = true; 743 return self::$page_has_shortcode_cache; 744 } 745 746 $elementor_data = get_post_meta($post_id, '_elementor_data', true); 747 if (is_string($elementor_data) && $elementor_data !== '' && strpos($elementor_data, 'bettercx_widget') !== false) { 748 self::$page_has_shortcode_cache = true; 749 } 750 751 return self::$page_has_shortcode_cache; 752 } 753 754 /** 689 755 * Render widget in footer 690 756 * … … 702 768 } 703 769 704 // Check if widget should be rendered (not already rendered via shortcode)705 if ( is_singular() && has_shortcode(get_post()->post_content, 'bettercx_widget')) {770 // Do not render global widget on pages that already output the shortcode (avoids duplicate widget). 771 if ($this->page_has_bettercx_shortcode()) { 706 772 return; 707 773 } … … 863 929 864 930 /** 865 * AJAX handler for testing connection866 *867 * @since 1.0.0868 */869 public function ajax_test_connection() {870 check_ajax_referer('bettercx_widget_admin_nonce', 'nonce');871 872 if (!current_user_can('manage_options')) {873 wp_die(esc_html__('Insufficient permissions.', 'bettercx-widget'));874 }875 876 $public_key = isset($_POST['public_key']) ? sanitize_text_field(wp_unslash($_POST['public_key'])) : '';877 878 // Test connection logic here879 $response = wp_remote_get('https://api.bettercx.ai/api/widgets/session/create/', array(880 'timeout' => 10,881 'headers' => array(882 'Content-Type' => 'application/json',883 ),884 'body' => json_encode(array(885 'widget_key' => $public_key,886 'origin' => home_url(),887 )),888 ));889 890 if (is_wp_error($response)) {891 wp_send_json_error(__('Connection test failed: ', 'bettercx-widget') . $response->get_error_message());892 }893 894 $status_code = wp_remote_retrieve_response_code($response);895 if ($status_code === 200) {896 wp_send_json_success(__('Connection test successful!', 'bettercx-widget'));897 } else {898 wp_send_json_error(__('Connection test failed. Please check your public key.', 'bettercx-widget'));899 }900 }901 902 /**903 931 * Add privacy policy content 904 932 * … … 1125 1153 <h2><?php esc_html_e('BetterCX Widget', 'bettercx-widget'); ?></h2> 1126 1154 <p><?php esc_html_e('Professional AI-powered chat widget for your WordPress site.', 'bettercx-widget'); ?></p> 1127 </div>1128 <div class="bettercx-widget-admin-actions">1129 <button type="button" id="test-connection" class="button button-secondary">1130 <?php esc_html_e('Test Connection', 'bettercx-widget'); ?>1131 </button>1132 1155 </div> 1133 1156 </div> -
bettercx-widget/trunk/readme.txt
r3445478 r3458971 5 5 Tested up to: 6.8 6 6 Requires PHP: 7.4 7 Stable tag: 1.0.2 17 Stable tag: 1.0.22 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 248 248 == Changelog == 249 249 250 = 1.0.22 = 251 * Fixed "Enable Widget" checkbox: unchecked state now correctly saves and disables the global widget (previously reverted to checked) 252 * Shortcode detection now works on Elementor-built pages: widget no longer appears twice when shortcode is used in Elementor (global footer widget is skipped on shortcode pages) 253 * Scripts and styles now load on pages that use only the shortcode (no global widget), so embedded/shortcode-only usage works reliably 254 * Settings save now merges with existing options to avoid losing any setting on form submit; improved backward compatibility 255 * Removed "Test Connection" button from admin (request sent from admin URL; connection is validated on frontend) 256 * Internal: added request-cached helper for shortcode detection; defensive sanitization and option merge for stability 257 250 258 = 1.0.21 = 251 259 * Optimized animations for improved performance and smoother user experience … … 399 407 == Upgrade Notice == 400 408 409 = 1.0.22 = 410 Update: "Enable Widget" checkbox now saves correctly when unchecked. Shortcode-only and Elementor pages work properly (no duplicate widget, scripts load). Test Connection button removed from admin. 411 401 412 = 1.0.21 = 402 413 Update: Optimized animations for improved performance and smoother user experience. Enhanced animation rendering with better frame rates and reduced CPU usage. Improved widget responsiveness during animations across all devices. -
bettercx-widget/trunk/src/components/bcx-chat-list/bcx-chat-list.scss
r3417078 r3458971 165 165 background: var(--bcx-bg-secondary, #f9f9f9); 166 166 cursor: pointer; 167 transition: all0.3s cubic-bezier(0.16, 1, 0.3, 1);167 transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.3s cubic-bezier(0.16, 1, 0.3, 1), border-color 0.3s cubic-bezier(0.16, 1, 0.3, 1), background 0.3s cubic-bezier(0.16, 1, 0.3, 1); 168 168 text-align: left; 169 169 font-family: inherit; … … 173 173 flex-shrink: 0; /* Prevent items from shrinking */ 174 174 min-height: fit-content; /* Ensure items maintain their natural height */ 175 /* AUDYT: chat list items - usunięto will-change i contain (zbyt wiele elementów) */ 176 /* AUDYT: usunięto backface-visibility - animacja 2D (slide) */ 175 177 176 178 &::before { … … 193 195 background: var(--bcx-bg-tertiary, #f3f4f6); 194 196 border-color: color-mix(in srgb, var(--bcx-primary, #007bff) 30%, transparent); 195 transform: translate Y(-2px);197 transform: translate3d(0, -2px, 0); /* OPTYMALIZACJA: translate3d zamiast translateY */ 196 198 box-shadow: 197 199 0 8px 16px color-mix(in srgb, var(--bcx-text-primary, #1f2937) 8%, transparent), … … 209 211 210 212 &:active { 211 transform: translate Y(0);213 transform: translate3d(0, 0, 0); /* OPTYMALIZACJA: translate3d zamiast translateY */ 212 214 box-shadow: 213 215 0 4px 8px color-mix(in srgb, var(--bcx-text-primary, #1f2937) 6%, transparent), … … 393 395 max-width: 85%; 394 396 animation: bcx-message-slide-in 0.25s cubic-bezier(0.25, 0.46, 0.45, 0.94); 397 /* AUDYT: usunięto will-change, contain, backface-visibility - pojedyncze wiadomości */ 395 398 396 399 &--user { … … 499 502 from { 500 503 opacity: 0; 501 transform: translate Y(10px);504 transform: translate3d(0, 10px, 0); /* OPTYMALIZACJA: translate3d zamiast translateY */ 502 505 } 503 506 to { 504 507 opacity: 1; 505 transform: translate Y(0);508 transform: translate3d(0, 0, 0); /* OPTYMALIZACJA: translate3d zamiast translateY */ 506 509 } 507 510 } … … 509 512 @keyframes bcx-spin { 510 513 0% { 511 transform: rotate(0deg);514 transform: translate3d(0, 0, 0) rotate(0deg); /* AUDYT: translate3d zamiast translateZ(0) dla spójności */ 512 515 } 513 516 100% { 514 transform: rotate(360deg);517 transform: translate3d(0, 0, 0) rotate(360deg); /* AUDYT: translate3d zamiast translateZ(0) dla spójności */ 515 518 } 516 519 } -
bettercx-widget/trunk/src/components/bcx-chat-list/bcx-chat-list.tsx
r3417078 r3458971 54 54 // Setup infinite scroll after messages are loaded 55 55 if (this.selectedChatId) { 56 setTimeout(() => this.setupInfiniteScroll(), 100); 56 // OPTYMALIZACJA: Użycie requestAnimationFrame zamiast setTimeout 57 requestAnimationFrame(() => { 58 requestAnimationFrame(() => { 59 this.setupInfiniteScroll(); 60 }); 61 }); 57 62 } 58 63 } … … 61 66 // Re-setup infinite scroll when selected chat changes 62 67 if (this.selectedChatId && this.messages.length > 0) { 63 setTimeout(() => this.setupInfiniteScroll(), 100); 68 // OPTYMALIZACJA: Użycie requestAnimationFrame zamiast setTimeout 69 requestAnimationFrame(() => { 70 requestAnimationFrame(() => { 71 this.setupInfiniteScroll(); 72 }); 73 }); 64 74 } 65 75 } … … 94 104 await this.loadMessages(chatId, 1); 95 105 96 // Setup infinite scroll after messages are loaded 97 setTimeout(() => this.setupInfiniteScroll(), 100); 106 // OPTYMALIZACJA: Użycie requestAnimationFrame zamiast setTimeout 107 requestAnimationFrame(() => { 108 requestAnimationFrame(() => { 109 this.setupInfiniteScroll(); 110 }); 111 }); 98 112 } 99 113 -
bettercx-widget/trunk/src/components/bcx-message-composer/bcx-message-composer.scss
r3435658 r3458971 267 267 @keyframes bcx-composer-spin { 268 268 from { 269 transform: rotate(0deg);269 transform: translate3d(0, 0, 0) rotate(0deg); /* AUDYT: translate3d zamiast translateZ(0) dla spójności */ 270 270 } 271 271 to { 272 transform: rotate(360deg);272 transform: translate3d(0, 0, 0) rotate(360deg); /* AUDYT: translate3d zamiast translateZ(0) dla spójności */ 273 273 } 274 274 } -
bettercx-widget/trunk/src/components/bcx-product-slider/bcx-product-slider.scss
r3417078 r3458971 61 61 display: flex; 62 62 transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1); 63 will-change: transform; 63 will-change: transform; /* AUDYT: slider track - główny element animacji, zostawiamy */ 64 64 cursor: grab; 65 contain: layout style paint;65 contain: layout paint; /* AUDYT: usunięto nieistniejące 'style', zostawiamy contain dla izolacji */ 66 66 -webkit-overflow-scrolling: touch; 67 67 -webkit-user-select: none; … … 72 72 margin: 0; 73 73 padding: 0; 74 transform: translateZ(0); 75 /* AUDYT: usunięto backface-visibility - animacja 2D (slide) */ 74 76 75 77 &:active { -
bettercx-widget/trunk/src/components/bcx-product-slider/bcx-product-slider.tsx
r3417078 r3458971 21 21 if (this.products.length > 0) { 22 22 this.currentIndex = 0; 23 // Use setTimeout to ensure DOM is updated24 setTimeout(() => {23 // OPTYMALIZACJA: Użycie requestAnimationFrame zamiast setTimeout dla lepszej synchronizacji z renderowaniem 24 requestAnimationFrame(() => { 25 25 this.animateToSlide(0); 26 } , 0);26 }); 27 27 } 28 28 } … … 46 46 private currentX: number = 0; 47 47 private initialTranslate: number = 0; 48 // AUDYT: Cleanup dla requestAnimationFrame - zapobieganie memory leaks 49 private animationRafId: number | null = null; 48 50 49 51 componentDidLoad() { … … 64 66 disconnectedCallback() { 65 67 this.cleanupTouchHandlers(); 68 // AUDYT: Cleanup requestAnimationFrame - zapobieganie memory leaks 69 if (this.animationRafId !== null) { 70 cancelAnimationFrame(this.animationRafId); 71 this.animationRafId = null; 72 } 66 73 } 67 74 … … 173 180 private updateSliderPosition(translate: number) { 174 181 if (!this.sliderRef) return; 175 this.sliderRef.style.transform = `translateX(${translate}%)`; 182 // OPTYMALIZACJA: Użycie translate3d zamiast translateX dla GPU acceleration 183 this.sliderRef.style.transform = `translate3d(${translate}%, 0, 0)`; 176 184 } 177 185 … … 191 199 if (!this.sliderRef || this.products.length <= 1) return; 192 200 201 // AUDYT: Cleanup poprzedniej animacji jeśli istnieje 202 if (this.animationRafId !== null) { 203 cancelAnimationFrame(this.animationRafId); 204 this.animationRafId = null; 205 } 206 193 207 const translatePercentage = -index * (100 / this.products.length); 194 208 209 // OPTYMALIZACJA: Użycie translate3d zamiast translateX dla GPU acceleration 195 210 this.sliderRef.style.transition = 'transform 0.3s cubic-bezier(0.16, 1, 0.3, 1)'; 196 this.sliderRef.style.transform = `translateX(${translatePercentage}%)`; 197 198 setTimeout(() => { 199 if (this.sliderRef) { 200 this.sliderRef.style.transition = ''; 211 this.sliderRef.style.transform = `translate3d(${translatePercentage}%, 0, 0)`; 212 213 // OPTYMALIZACJA: Użycie requestAnimationFrame zamiast setTimeout 214 const startTime = performance.now(); 215 const checkComplete = (currentTime: number) => { 216 if (currentTime - startTime >= 300) { 217 if (this.sliderRef) { 218 this.sliderRef.style.transition = ''; 219 } 220 this.animationRafId = null; // AUDYT: Cleanup po zakończeniu 221 } else { 222 this.animationRafId = requestAnimationFrame(checkComplete); 201 223 } 202 }, 300); 224 }; 225 this.animationRafId = requestAnimationFrame(checkComplete); 203 226 } 204 227 … … 249 272 style={{ 250 273 width: `${this.products.length * 100}%`, 251 transform: this.products.length > 1 ? `translate X(-${this.currentIndex * (100 / this.products.length)}%)` : 'none',274 transform: this.products.length > 1 ? `translate3d(-${this.currentIndex * (100 / this.products.length)}%, 0, 0)` : 'none', 252 275 }} 253 276 > -
bettercx-widget/trunk/src/components/bettercx-widget/bettercx-widget.scss
r3435673 r3458971 201 201 0 6px 12px rgba(0, 0, 0, 0.08), 202 202 0 0 0 1px rgba(255, 255, 255, 0.12); 203 transition: allvar(--bcx-transition-normal);203 transition: transform var(--bcx-transition-normal), box-shadow var(--bcx-transition-normal), background var(--bcx-transition-normal); 204 204 position: relative; 205 205 z-index: 1; 206 206 backdrop-filter: blur(24px); /* Increased blur for more glassmorphism */ 207 207 -webkit-backdrop-filter: blur(24px); 208 /* AUDYT: toggle button - główny element interakcji, zostawiamy will-change i contain */ 209 transform: translateZ(0); 210 will-change: transform; 211 contain: layout paint; /* AUDYT: usunięto nieistniejące 'style' */ 208 212 209 213 :host(.dark) & { … … 227 231 228 232 &:hover { 229 transform: translate Y(-2px) scale(1.04); /* Reduced lift effect to prevent overlap*/233 transform: translate3d(0, -2px, 0) scale(1.04); /* OPTYMALIZACJA: translate3d zamiast translateY */ 230 234 box-shadow: 231 235 0 28px 56px rgba(0, 0, 0, 0.2), … … 252 256 253 257 &:active { 254 transform: translate Y(-1px) scale(1.02); /* Reduced active state to prevent overlap*/258 transform: translate3d(0, -1px, 0) scale(1.02); /* OPTYMALIZACJA: translate3d zamiast translateY */ 255 259 box-shadow: 256 260 0 16px 32px rgba(0, 0, 0, 0.15), … … 415 419 -webkit-backdrop-filter: blur(32px); 416 420 transform-origin: bottom right; 421 /* OPTYMALIZACJA: GPU acceleration i izolacja renderingu - tylko na głównym kontenerze */ 422 transform: translateZ(0); 423 will-change: transform, opacity; 424 contain: layout paint; /* AUDYT: usunięto nieistniejące 'style' z contain */ 417 425 418 426 :host(.dark) & { … … 498 506 min-width: 0; // Allows flex items to shrink below their content size 499 507 transform: translateX(0); 500 backface-visibility: hidden;508 /* AUDYT: usunięto backface-visibility - animacja 2D (flex-direction change) */ 501 509 -webkit-font-smoothing: antialiased; 502 510 position: relative; … … 513 521 width 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94); 514 522 flex-shrink: 0; 515 transform: translate X(0) translateZ(0);523 transform: translate3d(0, 0, 0); /* AUDYT: translate3d zamiast translateX + translateZ */ 516 524 will-change: transform, justify-content, width; 517 backface-visibility: hidden;525 /* AUDYT: usunięto backface-visibility - animacja 2D */ 518 526 position: relative; 519 527 } … … 526 534 max-height: 0; 527 535 opacity: 0; 528 transform: translate Y(-8px) translateZ(0);536 transform: translate3d(0, -8px, 0); /* AUDYT: translate3d zamiast translateY + translateZ */ 529 537 visibility: hidden; 530 538 pointer-events: none; 531 539 margin-top: 0; 532 540 will-change: max-height, opacity, transform, visibility; 533 backface-visibility: hidden;534 contain: layout style paint;541 /* AUDYT: usunięto backface-visibility - animacja 2D (fade/slide) */ 542 contain: layout paint; /* AUDYT: usunięto nieistniejące 'style' */ 535 543 transition: 536 544 opacity 0.15s cubic-bezier(0.4, 0, 1, 1) 0s, … … 557 565 white-space: nowrap; 558 566 flex-shrink: 0; 559 transform: translate X(0) translateZ(0);567 transform: translate3d(0, 0, 0); /* AUDYT: translate3d zamiast translateX + translateZ */ 560 568 will-change: transform, font-size; 561 backface-visibility: hidden;569 /* AUDYT: usunięto backface-visibility - animacja 2D */ 562 570 -webkit-font-smoothing: antialiased; 563 571 text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); … … 580 588 transform: translateX(0) translateZ(0); 581 589 will-change: transform; 582 backface-visibility: hidden;590 /* AUDYT: usunięto backface-visibility - animacja 2D (scale) */ 583 591 box-shadow: 584 592 0 2px 4px rgba(0, 0, 0, 0.1), … … 604 612 /* Subtle hover effect */ 605 613 &:hover { 606 transform: translate X(0) translateZ(0) scale(1.02);614 transform: translate3d(0, 0, 0) scale(1.02); /* AUDYT: translate3d zamiast translateX + translateZ */ 607 615 box-shadow: 608 616 0 3px 6px rgba(0, 0, 0, 0.12), … … 647 655 opacity: 0.95; 648 656 transition: opacity 0.2s cubic-bezier(0.4, 0, 1, 1); 649 will-change: opacity;657 /* AUDYT: usunięto will-change: opacity - zbyt drobny element, przeglądarka zoptymalizuje */ 650 658 text-shadow: 0 1px 2px rgba(0, 0, 0, 0.08); 651 659 letter-spacing: -0.01em; … … 659 667 opacity: 0.8; 660 668 transition: opacity 0.2s cubic-bezier(0.4, 0, 1, 1); 661 will-change: opacity;669 /* AUDYT: usunięto will-change: opacity - zbyt drobny element */ 662 670 text-shadow: 0 1px 1px rgba(0, 0, 0, 0.06); 663 671 letter-spacing: 0.01em; … … 703 711 max-height: 200px; 704 712 opacity: 1; 705 transform: translate Y(0) translateZ(0);713 transform: translate3d(0, 0, 0); /* AUDYT: translate3d zamiast translateY + translateZ */ 706 714 visibility: visible; 707 715 pointer-events: auto; … … 758 766 max-height: 0; 759 767 opacity: 0; 760 transform: translate Y(-8px) translateZ(0);768 transform: translate3d(0, -8px, 0); /* AUDYT: translate3d zamiast translateY + translateZ */ 761 769 visibility: hidden; 762 770 pointer-events: none; … … 915 923 backdrop-filter: blur(12px); 916 924 -webkit-backdrop-filter: blur(12px); 925 /* AUDYT: dropdown - główny element UI, zostawiamy will-change */ 917 926 will-change: transform, opacity; 918 927 … … 1476 1485 max-width: 85%; 1477 1486 animation: bcx-message-appear 0.25s cubic-bezier(0.25, 0.46, 0.45, 0.94); /* Premium message appearance */ 1487 /* AUDYT: usunięto will-change i contain z pojedynczych wiadomości - przeglądarka zoptymalizuje sama */ 1488 /* AUDYT: usunięto backface-visibility - animacja 2D (slide/fade) nie potrzebuje */ 1478 1489 1479 1490 &--user { 1480 1491 align-self: flex-end; 1481 1492 animation: bcx-message-slide-in-right 0.25s cubic-bezier(0.25, 0.46, 0.45, 0.94); 1493 /* AUDYT: usunięto will-change - pojedyncze wiadomości nie potrzebują */ 1482 1494 1483 1495 .bcx-widget__message-content { … … 1714 1726 gap: 0; 1715 1727 animation: bcx-message-slide-in-left 0.25s cubic-bezier(0.25, 0.46, 0.45, 0.94); 1728 /* AUDYT: usunięto will-change - pojedyncze wiadomości nie potrzebują */ 1716 1729 1717 1730 .bcx-widget__message-content { … … 2061 2074 margin: 0; 2062 2075 cursor: pointer; 2063 transition: all0.3s cubic-bezier(0.16, 1, 0.3, 1);2076 transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.3s cubic-bezier(0.16, 1, 0.3, 1), border-color 0.3s cubic-bezier(0.16, 1, 0.3, 1), background 0.3s cubic-bezier(0.16, 1, 0.3, 1); 2064 2077 text-align: left; 2065 2078 font-family: inherit; 2066 2079 position: relative; 2067 2080 overflow: hidden; 2068 will-change: transform, box-shadow, border-color; 2081 /* AUDYT: FAQ items - usunięto will-change i contain z pojedynczych elementów */ 2082 /* AUDYT: usunięto backface-visibility - animacja 2D (slide) */ 2069 2083 2070 2084 // Animation only when parent has --animate class … … 2091 2105 background: var(--bcx-bg-tertiary); 2092 2106 border-color: color-mix(in srgb, var(--bcx-primary) 30%, transparent); 2093 transform: translate Y(-2px);2107 transform: translate3d(0, -2px, 0); /* OPTYMALIZACJA: translate3d zamiast translateY */ 2094 2108 box-shadow: 2095 2109 0 8px 16px color-mix(in srgb, var(--bcx-text-primary) 8%, transparent), … … 2111 2125 2112 2126 &:active { 2113 transform: translate Y(0);2127 transform: translate3d(0, 0, 0); /* OPTYMALIZACJA: translate3d zamiast translateY */ 2114 2128 box-shadow: 2115 2129 0 4px 8px color-mix(in srgb, var(--bcx-text-primary) 6%, transparent), … … 2264 2278 box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); 2265 2279 flex-shrink: 0; /* Prevent dots from shrinking */ 2280 /* AUDYT: usunięto will-change i backface-visibility - zbyt drobne elementy */ 2281 transform: translateZ(0); 2266 2282 2267 2283 &:nth-child(1) { … … 2295 2311 width: 100%; 2296 2312 box-sizing: border-box; 2313 /* AUDYT: usunięto will-change - zbyt drobny element */ 2297 2314 } 2298 2315 … … 2462 2479 animation: bcx-spin 1s linear infinite; 2463 2480 margin-bottom: var(--bcx-space-4); 2481 /* AUDYT: spinner - mały element, usunięto will-change i backface-visibility */ 2482 transform: translateZ(0); 2464 2483 2465 2484 &--small { … … 2474 2493 0% { 2475 2494 opacity: 0; 2476 transform: translateY(32px) scale(0.92); 2477 filter: blur(6px) brightness(0.8); 2495 transform: translate3d(0, 32px, 0) scale(0.92); 2478 2496 } 2479 2497 20% { 2480 2498 opacity: 0.3; 2481 transform: translateY(20px) scale(0.95); 2482 filter: blur(4px) brightness(0.9); 2499 transform: translate3d(0, 20px, 0) scale(0.95); 2483 2500 } 2484 2501 40% { 2485 2502 opacity: 0.6; 2486 transform: translateY(12px) scale(0.97); 2487 filter: blur(2px) brightness(0.95); 2503 transform: translate3d(0, 12px, 0) scale(0.97); 2488 2504 } 2489 2505 60% { 2490 2506 opacity: 0.8; 2491 transform: translateY(6px) scale(0.99); 2492 filter: blur(1px) brightness(0.98); 2507 transform: translate3d(0, 6px, 0) scale(0.99); 2493 2508 } 2494 2509 80% { 2495 2510 opacity: 0.95; 2496 transform: translateY(2px) scale(1.005); 2497 filter: blur(0.5px) brightness(1); 2511 transform: translate3d(0, 2px, 0) scale(1.005); 2498 2512 } 2499 2513 100% { 2500 2514 opacity: 1; 2501 transform: translateY(0) scale(1); 2502 filter: blur(0) brightness(1); 2515 transform: translate3d(0, 0, 0) scale(1); 2503 2516 } 2504 2517 } … … 2507 2520 0% { 2508 2521 opacity: 0; 2509 transform: translateY(24px) scale(0.94); 2510 filter: blur(4px) brightness(0.9); 2522 transform: translate3d(0, 24px, 0) scale(0.94); 2511 2523 } 2512 2524 15% { 2513 2525 opacity: 0.2; 2514 transform: translateY(18px) scale(0.96); 2515 filter: blur(3px) brightness(0.92); 2526 transform: translate3d(0, 18px, 0) scale(0.96); 2516 2527 } 2517 2528 30% { 2518 2529 opacity: 0.4; 2519 transform: translateY(12px) scale(0.97); 2520 filter: blur(2px) brightness(0.95); 2530 transform: translate3d(0, 12px, 0) scale(0.97); 2521 2531 } 2522 2532 50% { 2523 2533 opacity: 0.7; 2524 transform: translateY(6px) scale(0.98); 2525 filter: blur(1px) brightness(0.98); 2534 transform: translate3d(0, 6px, 0) scale(0.98); 2526 2535 } 2527 2536 70% { 2528 2537 opacity: 0.85; 2529 transform: translateY(2px) scale(0.99); 2530 filter: blur(0.5px) brightness(0.99); 2538 transform: translate3d(0, 2px, 0) scale(0.99); 2531 2539 } 2532 2540 85% { 2533 2541 opacity: 0.95; 2534 transform: translateY(1px) scale(1.005); 2535 filter: blur(0.2px) brightness(1); 2542 transform: translate3d(0, 1px, 0) scale(1.005); 2536 2543 } 2537 2544 100% { 2538 2545 opacity: 1; 2539 transform: translateY(0) scale(1); 2540 filter: blur(0) brightness(1); 2546 transform: translate3d(0, 0, 0) scale(1); 2541 2547 } 2542 2548 } … … 2545 2551 0% { 2546 2552 opacity: 0; 2547 transform: translateY(16px) scale(0.92); 2548 filter: blur(3px); 2553 transform: translate3d(0, 16px, 0) scale(0.92); 2549 2554 } 2550 2555 30% { 2551 2556 opacity: 0.4; 2552 transform: translateY(8px) scale(0.96); 2553 filter: blur(1px); 2557 transform: translate3d(0, 8px, 0) scale(0.96); 2554 2558 } 2555 2559 60% { 2556 2560 opacity: 0.8; 2557 transform: translateY(2px) scale(0.99); 2558 filter: blur(0.5px); 2561 transform: translate3d(0, 2px, 0) scale(0.99); 2559 2562 } 2560 2563 100% { 2561 2564 opacity: 1; 2562 transform: translateY(0) scale(1); 2563 filter: blur(0); 2565 transform: translate3d(0, 0, 0) scale(1); 2564 2566 } 2565 2567 } … … 2569 2571 80%, 2570 2572 100% { 2571 transform: scale(0.85);2573 transform: translate3d(0, 0, 0) scale(0.85); /* AUDYT: translate3d zamiast translateZ(0) dla spójności */ 2572 2574 opacity: 0.5; 2573 2575 } 2574 2576 40% { 2575 transform: scale(1.15);2577 transform: translate3d(0, 0, 0) scale(1.15); /* AUDYT: translate3d zamiast translateZ(0) dla spójności */ 2576 2578 opacity: 1; 2577 2579 } … … 2592 2594 0%, 2593 2595 100% { 2594 transform: scale(1);2596 transform: translate3d(0, 0, 0) scale(1); /* AUDYT: translate3d zamiast translateZ(0) dla spójności */ 2595 2597 box-shadow: 2596 2598 0 20px 40px rgba(0, 0, 0, 0.15), … … 2600 2602 } 2601 2603 50% { 2602 transform: scale(1.02); /* Reduced scale to prevent overlap*/2604 transform: translate3d(0, 0, 0) scale(1.02); /* AUDYT: translate3d zamiast translateZ(0) dla spójności */ 2603 2605 box-shadow: 2604 2606 0 24px 48px rgba(0, 0, 0, 0.18), … … 2613 2615 0% { 2614 2616 opacity: 0; 2615 transform: scale(0.92) translateY(24px); 2616 filter: blur(4px) brightness(0.8); 2617 transform: translate3d(0, 24px, 0) scale(0.92); 2617 2618 } 2618 2619 30% { 2619 2620 opacity: 0.4; 2620 transform: scale(0.96) translateY(12px); 2621 filter: blur(2px) brightness(0.9); 2621 transform: translate3d(0, 12px, 0) scale(0.96); 2622 2622 } 2623 2623 60% { 2624 2624 opacity: 0.8; 2625 transform: scale(0.99) translateY(4px); 2626 filter: blur(1px) brightness(0.95); 2625 transform: translate3d(0, 4px, 0) scale(0.99); 2627 2626 } 2628 2627 100% { 2629 2628 opacity: 1; 2630 transform: scale(1) translateY(0); 2631 filter: blur(0) brightness(1); 2629 transform: translate3d(0, 0, 0) scale(1); 2632 2630 } 2633 2631 } … … 2636 2634 0% { 2637 2635 opacity: 1; 2638 transform: translateY(0) scale(1); 2639 filter: blur(0) brightness(1); 2636 transform: translate3d(0, 0, 0) scale(1); 2640 2637 } 2641 2638 20% { 2642 2639 opacity: 0.8; 2643 transform: translateY(4px) scale(0.99); 2644 filter: blur(1px) brightness(0.95); 2640 transform: translate3d(0, 4px, 0) scale(0.99); 2645 2641 } 2646 2642 40% { 2647 2643 opacity: 0.6; 2648 transform: translateY(12px) scale(0.97); 2649 filter: blur(2px) brightness(0.9); 2644 transform: translate3d(0, 12px, 0) scale(0.97); 2650 2645 } 2651 2646 60% { 2652 2647 opacity: 0.4; 2653 transform: translateY(20px) scale(0.95); 2654 filter: blur(3px) brightness(0.85); 2648 transform: translate3d(0, 20px, 0) scale(0.95); 2655 2649 } 2656 2650 80% { 2657 2651 opacity: 0.2; 2658 transform: translateY(28px) scale(0.93); 2659 filter: blur(4px) brightness(0.8); 2652 transform: translate3d(0, 28px, 0) scale(0.93); 2660 2653 } 2661 2654 100% { 2662 2655 opacity: 0; 2663 transform: translateY(32px) scale(0.92); 2664 filter: blur(6px) brightness(0.7); 2656 transform: translate3d(0, 32px, 0) scale(0.92); 2665 2657 } 2666 2658 } … … 2668 2660 @keyframes bcx-question-click { 2669 2661 0% { 2670 transform: scale(1);2662 transform: translate3d(0, 0, 0) scale(1); /* AUDYT: translate3d zamiast translateZ(0) dla spójności */ 2671 2663 } 2672 2664 50% { 2673 transform: scale(0.95);2665 transform: translate3d(0, 0, 0) scale(0.95); /* AUDYT: translate3d zamiast translateZ(0) dla spójności */ 2674 2666 } 2675 2667 100% { 2676 transform: scale(1);2668 transform: translate3d(0, 0, 0) scale(1); /* AUDYT: translate3d zamiast translateZ(0) dla spójności */ 2677 2669 } 2678 2670 } … … 2681 2673 0%, 2682 2674 100% { 2683 transform: scale(1);2684 filter: brightness(1);2675 transform: translate3d(0, 0, 0) scale(1); /* AUDYT: translate3d zamiast translateZ(0) dla spójności */ 2676 opacity: 1; 2685 2677 } 2686 2678 50% { 2687 transform: scale(1.005);2688 filter: brightness(1.02);2679 transform: translate3d(0, 0, 0) scale(1.005); /* AUDYT: translate3d zamiast translateZ(0) dla spójności */ 2680 opacity: 1.02; 2689 2681 } 2690 2682 } … … 2693 2685 0% { 2694 2686 opacity: 0; 2695 transform: translateY(24px) scale(0.92); 2696 filter: blur(4px); 2687 transform: translate3d(0, 24px, 0) scale(0.92); 2697 2688 } 2698 2689 30% { 2699 2690 opacity: 0.6; 2700 transform: translateY(12px) scale(0.96); 2701 filter: blur(2px); 2691 transform: translate3d(0, 12px, 0) scale(0.96); 2702 2692 } 2703 2693 60% { 2704 2694 opacity: 0.8; 2705 transform: translateY(4px) scale(0.99); 2706 filter: blur(1px); 2695 transform: translate3d(0, 4px, 0) scale(0.99); 2707 2696 } 2708 2697 100% { 2709 2698 opacity: 1; 2710 transform: translateY(0) scale(1); 2711 filter: blur(0); 2699 transform: translate3d(0, 0, 0) scale(1); 2712 2700 } 2713 2701 } … … 2734 2722 0% { 2735 2723 opacity: 0; 2736 transform: translateX(40px) translateY(16px) scale(0.92); 2737 filter: blur(3px) brightness(0.8); 2724 transform: translate3d(40px, 16px, 0) scale(0.92); 2738 2725 } 2739 2726 20% { 2740 2727 opacity: 0.3; 2741 transform: translateX(24px) translateY(12px) scale(0.95); 2742 filter: blur(2px) brightness(0.85); 2728 transform: translate3d(24px, 12px, 0) scale(0.95); 2743 2729 } 2744 2730 40% { 2745 2731 opacity: 0.6; 2746 transform: translateX(12px) translateY(8px) scale(0.97); 2747 filter: blur(1px) brightness(0.9); 2732 transform: translate3d(12px, 8px, 0) scale(0.97); 2748 2733 } 2749 2734 60% { 2750 2735 opacity: 0.8; 2751 transform: translateX(4px) translateY(4px) scale(0.99); 2752 filter: blur(0.5px) brightness(0.95); 2736 transform: translate3d(4px, 4px, 0) scale(0.99); 2753 2737 } 2754 2738 80% { 2755 2739 opacity: 0.95; 2756 transform: translateX(1px) translateY(1px) scale(1.005); 2757 filter: blur(0.2px) brightness(0.98); 2740 transform: translate3d(1px, 1px, 0) scale(1.005); 2758 2741 } 2759 2742 100% { 2760 2743 opacity: 1; 2761 transform: translateX(0) translateY(0) scale(1); 2762 filter: blur(0) brightness(1); 2744 transform: translate3d(0, 0, 0) scale(1); 2763 2745 } 2764 2746 } … … 2767 2749 0% { 2768 2750 opacity: 0; 2769 transform: translateX(-40px) translateY(16px) scale(0.92); 2770 filter: blur(3px) brightness(0.8); 2751 transform: translate3d(-40px, 16px, 0) scale(0.92); 2771 2752 } 2772 2753 20% { 2773 2754 opacity: 0.3; 2774 transform: translateX(-24px) translateY(12px) scale(0.95); 2775 filter: blur(2px) brightness(0.85); 2755 transform: translate3d(-24px, 12px, 0) scale(0.95); 2776 2756 } 2777 2757 40% { 2778 2758 opacity: 0.6; 2779 transform: translateX(-12px) translateY(8px) scale(0.97); 2780 filter: blur(1px) brightness(0.9); 2759 transform: translate3d(-12px, 8px, 0) scale(0.97); 2781 2760 } 2782 2761 60% { 2783 2762 opacity: 0.8; 2784 transform: translateX(-4px) translateY(4px) scale(0.99); 2785 filter: blur(0.5px) brightness(0.95); 2763 transform: translate3d(-4px, 4px, 0) scale(0.99); 2786 2764 } 2787 2765 80% { 2788 2766 opacity: 0.95; 2789 transform: translateX(-1px) translateY(1px) scale(1.005); 2790 filter: blur(0.2px) brightness(0.98); 2767 transform: translate3d(-1px, 1px, 0) scale(1.005); 2791 2768 } 2792 2769 100% { 2793 2770 opacity: 1; 2794 transform: translateX(0) translateY(0) scale(1); 2795 filter: blur(0) brightness(1); 2771 transform: translate3d(0, 0, 0) scale(1); 2796 2772 } 2797 2773 } … … 2799 2775 @keyframes bcx-spin { 2800 2776 0% { 2801 transform: rotate(0deg);2777 transform: translate3d(0, 0, 0) rotate(0deg); /* AUDYT: translate3d zamiast translateZ(0) dla spójności */ 2802 2778 } 2803 2779 100% { 2804 transform: rotate(360deg);2780 transform: translate3d(0, 0, 0) rotate(360deg); /* AUDYT: translate3d zamiast translateZ(0) dla spójności */ 2805 2781 } 2806 2782 } … … 3100 3076 padding: var(--bcx-space-4); 3101 3077 overflow: hidden; 3078 /* AUDYT: ping message - główny element UI, zostawiamy will-change */ 3102 3079 will-change: transform, opacity; 3103 3080 -
bettercx-widget/trunk/src/components/bettercx-widget/bettercx-widget.tsx
r3435658 r3458971 89 89 // Delay message handling 90 90 private delayMessageTimeout: ReturnType<typeof setTimeout> | null = null; 91 92 // AUDYT: Cleanup dla requestAnimationFrame - zapobieganie memory leaks 93 private typingRafId: number | null = null; 94 private faqAnimationRafId: number | null = null; 91 95 @State() private showDelayMessage: boolean = false; 92 96 @State() private delayMessageType: 'selecting' | 'thinking' = null; // 'selecting' = no AI messages, 'thinking' = has AI messages … … 172 176 this.isProgrammaticScroll = true; 173 177 this.messagesEndRef.scrollIntoView({ behavior: 'auto', block: 'end', inline: 'nearest' }); 174 setTimeout(() => { 175 this.isProgrammaticScroll = false; 176 }, 50); 178 // OPTYMALIZACJA: Użycie requestAnimationFrame zamiast setTimeout 179 requestAnimationFrame(() => { 180 requestAnimationFrame(() => { 181 this.isProgrammaticScroll = false; 182 }); 183 }); 177 184 } 178 185 }); … … 194 201 clearTimeout(this.delayMessageTimeout); 195 202 this.delayMessageTimeout = null; 203 } 204 205 // AUDYT: Cleanup requestAnimationFrame - zapobieganie memory leaks 206 if (this.typingRafId !== null) { 207 cancelAnimationFrame(this.typingRafId); 208 this.typingRafId = null; 209 } 210 if (this.faqAnimationRafId !== null) { 211 cancelAnimationFrame(this.faqAnimationRafId); 212 this.faqAnimationRafId = null; 196 213 } 197 214 } … … 989 1006 inline: 'nearest', 990 1007 }); 991 // Reset flag after scroll completes (smooth scroll takes ~300-500ms) 992 setTimeout( 993 () => { 1008 // OPTYMALIZACJA: Użycie requestAnimationFrame zamiast setTimeout dla instant scroll 1009 // Dla smooth scroll używamy krótszego opóźnienia z RAF 1010 if (smooth) { 1011 // Smooth scroll trwa ~300-500ms, więc używamy multiple RAF cycles 1012 requestAnimationFrame(() => { 1013 requestAnimationFrame(() => { 1014 requestAnimationFrame(() => { 1015 requestAnimationFrame(() => { 1016 this.isProgrammaticScroll = false; 1017 }); 1018 }); 1019 }); 1020 }); 1021 } else { 1022 requestAnimationFrame(() => { 994 1023 this.isProgrammaticScroll = false; 995 }, 996 smooth ? 600 : 50, 997 ); 1024 }); 1025 } 998 1026 } 999 1027 } … … 1032 1060 }); 1033 1061 } 1034 // Reset flag after instant scroll 1035 setTimeout(() => { 1036 this.isProgrammaticScroll = false; 1037 }, 100); 1062 // OPTYMALIZACJA: Użycie requestAnimationFrame zamiast setTimeout 1063 requestAnimationFrame(() => { 1064 requestAnimationFrame(() => { 1065 this.isProgrammaticScroll = false; 1066 }); 1067 }); 1038 1068 }); 1039 1069 } … … 1089 1119 1090 1120 private applyColorsToMessageComposer() { 1091 setTimeout(() => { 1092 const messageComposer = this.el.querySelector('bcx-message-composer'); 1093 if (messageComposer) { 1094 const primaryColor = this.el.style.getPropertyValue('--bcx-primary') || '#007bff'; 1095 const secondaryColor = this.el.style.getPropertyValue('--bcx-secondary') || '#6c757d'; 1096 const backgroundColor = this.el.style.getPropertyValue('--bcx-background') || '#ffffff'; 1097 const textColor = this.el.style.getPropertyValue('--bcx-text') || '#212529'; 1098 1099 messageComposer.style.setProperty('--bcx-primary', primaryColor); 1100 messageComposer.style.setProperty('--bcx-secondary', secondaryColor); 1101 messageComposer.style.setProperty('--bcx-background', backgroundColor); 1102 messageComposer.style.setProperty('--bcx-text', textColor); 1103 } 1104 }, 100); 1121 // OPTYMALIZACJA: Użycie requestAnimationFrame zamiast setTimeout dla lepszej synchronizacji z renderowaniem 1122 requestAnimationFrame(() => { 1123 requestAnimationFrame(() => { 1124 const messageComposer = this.el.querySelector('bcx-message-composer'); 1125 if (messageComposer) { 1126 const primaryColor = this.el.style.getPropertyValue('--bcx-primary') || '#007bff'; 1127 const secondaryColor = this.el.style.getPropertyValue('--bcx-secondary') || '#6c757d'; 1128 const backgroundColor = this.el.style.getPropertyValue('--bcx-background') || '#ffffff'; 1129 const textColor = this.el.style.getPropertyValue('--bcx-text') || '#212529'; 1130 1131 messageComposer.style.setProperty('--bcx-primary', primaryColor); 1132 messageComposer.style.setProperty('--bcx-secondary', secondaryColor); 1133 messageComposer.style.setProperty('--bcx-background', backgroundColor); 1134 messageComposer.style.setProperty('--bcx-text', textColor); 1135 } 1136 }); 1137 }); 1105 1138 } 1106 1139 … … 1135 1168 let currentText = ''; 1136 1169 let index = 0; 1137 const typingSpeed = 25; 1138 1139 const typeNextCharacter = () => { 1170 const typingSpeed = 25; // milliseconds per character 1171 let lastTime = 0; 1172 1173 // AUDYT: Cleanup poprzedniej animacji jeśli istnieje 1174 if (this.typingRafId !== null) { 1175 cancelAnimationFrame(this.typingRafId); 1176 this.typingRafId = null; 1177 } 1178 1179 // OPTYMALIZACJA: Użycie requestAnimationFrame zamiast setTimeout dla płynniejszych animacji 1180 const typeNextCharacter = (currentTime: number) => { 1140 1181 if (index < fullText.length) { 1141 currentText += fullText[index]; 1142 index++; 1143 1144 const updatedMessage = { ...assistantMessage, content: currentText }; 1145 const updatedMessages = [...this.state.messages]; 1146 updatedMessages[updatedMessages.length - 1] = updatedMessage; 1147 1148 this.setState({ 1149 messages: updatedMessages, 1150 }); 1151 1152 setTimeout(typeNextCharacter, typingSpeed); 1182 // Throttle updates to match typingSpeed using RAF 1183 if (currentTime - lastTime >= typingSpeed) { 1184 currentText += fullText[index]; 1185 index++; 1186 lastTime = currentTime; 1187 1188 const updatedMessage = { ...assistantMessage, content: currentText }; 1189 const updatedMessages = [...this.state.messages]; 1190 updatedMessages[updatedMessages.length - 1] = updatedMessage; 1191 1192 this.setState({ 1193 messages: updatedMessages, 1194 }); 1195 } 1196 1197 this.typingRafId = requestAnimationFrame(typeNextCharacter); 1153 1198 } else { 1199 // AUDYT: Cleanup po zakończeniu animacji 1200 if (this.typingRafId !== null) { 1201 cancelAnimationFrame(this.typingRafId); 1202 this.typingRafId = null; 1203 } 1154 1204 this.emitEvent('message-received', assistantMessage as unknown as Record<string, unknown>); 1155 1205 } 1156 1206 }; 1157 1207 1158 setTimeout(typeNextCharacter, 200); 1208 // Start animation after initial delay using RAF 1209 requestAnimationFrame(() => { 1210 requestAnimationFrame(() => { 1211 lastTime = performance.now(); 1212 this.typingRafId = requestAnimationFrame(typeNextCharacter); 1213 }); 1214 }); 1159 1215 } 1160 1216 … … 1516 1572 ref={el => { 1517 1573 if (el && !this.faqsHaveBeenShown) { 1518 // Set flag after animation completes to prevent re-animation on rerenders 1574 // AUDYT: Cleanup poprzedniej animacji jeśli istnieje 1575 if (this.faqAnimationRafId !== null) { 1576 cancelAnimationFrame(this.faqAnimationRafId); 1577 this.faqAnimationRafId = null; 1578 } 1579 1580 // OPTYMALIZACJA: Użycie requestAnimationFrame zamiast setTimeout 1519 1581 // Animation duration: 0.4s (container) + 0.5s (items) = ~600ms total 1520 setTimeout(() => { 1521 this.faqsHaveBeenShown = true; 1522 }, 600); 1582 // Używamy multiple RAF cycles aby zsynchronizować z animacją 1583 const startTime = performance.now(); 1584 const checkComplete = (currentTime: number) => { 1585 if (currentTime - startTime >= 600) { 1586 this.faqsHaveBeenShown = true; 1587 this.faqAnimationRafId = null; // AUDYT: Cleanup po zakończeniu 1588 } else { 1589 this.faqAnimationRafId = requestAnimationFrame(checkComplete); 1590 } 1591 }; 1592 this.faqAnimationRafId = requestAnimationFrame(checkComplete); 1523 1593 } 1524 1594 }}
Note: See TracChangeset
for help on using the changeset viewer.