Changeset 3402632
- Timestamp:
- 11/25/2025 03:00:58 PM (4 months ago)
- Location:
- bettercx-widget/trunk
- Files:
-
- 11 added
- 9 edited
-
assets/bettercx-widget.esm.js (modified) (1 diff)
-
assets/index.esm.js (modified) (1 diff)
-
assets/p-43f268bb.entry.js (added)
-
assets/p-65e11e89.entry.js (added)
-
assets/p-887c5563.system.entry.js (added)
-
assets/p-B7XTg7r_.system.js (modified) (1 diff)
-
assets/p-Be7T8ATp.js (added)
-
assets/p-BxBqyhWA.system.js (added)
-
assets/p-C_NDxfuU.js (added)
-
assets/p-DHSoJoxm.system.js (added)
-
assets/p-ba1adebc.system.entry.js (added)
-
assets/p-w4c8J7H6.system.js (added)
-
assets/p-zbQHIKau.system.js (added)
-
bettercx-widget.php (modified) (3 diffs)
-
readme.txt (modified) (5 diffs)
-
src/components/bcx-product-slider/bcx-product-slider.tsx (modified) (4 diffs)
-
src/components/bettercx-widget/bettercx-widget.scss (modified) (1 diff)
-
src/components/bettercx-widget/bettercx-widget.tsx (modified) (10 diffs)
-
src/types/api.ts (modified) (1 diff)
-
src/utils/product-parser.ts (added)
Legend:
- Unmodified
- Added
- Removed
-
bettercx-widget/trunk/assets/bettercx-widget.esm.js
r3385343 r3402632 1 import{p as e,g as a,b as t}from"./p-BTuzHDoC.js";export{s as setNonce}from"./p-BTuzHDoC.js";(()=>{const a=import.meta.url,s={};return""!==a&&(s.resourcesUrl=new URL(".",a).href),e(s)})().then((async e=>(await a(),t([["p-73aa3697",[[257,"bcx-message-composer",{disabled:[4],loading:[4],placeholder:[1],maxLength:[2,"max-length"],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- 339847ab",[[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],state:[32],open:[64],close:[64],toggle:[64],sendMessage:[64]},null,{publicKey:["onPublicKeyChange"]}]]]],e))));1 import{p as e,g as a,b as t}from"./p-BTuzHDoC.js";export{s as setNonce}from"./p-BTuzHDoC.js";(()=>{const a=import.meta.url,s={};return""!==a&&(s.resourcesUrl=new URL(".",a).href),e(s)})().then((async e=>(await a(),t([["p-73aa3697",[[257,"bcx-message-composer",{disabled:[4],loading:[4],placeholder:[1],maxLength:[2,"max-length"],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-43f268bb",[[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],state:[32],open:[64],close:[64],toggle:[64],sendMessage:[64]},null,{publicKey:["onPublicKeyChange"]}]]]],e)))); -
bettercx-widget/trunk/assets/index.esm.js
r3385343 r3402632 1 export{a as ApiService,A as AuthService,B as BetterCXWidget,T as ThemeService}from"./p- DOTTKS3U.js";import"./p-BTuzHDoC.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-Be7T8ATp.js";import"./p-BTuzHDoC.js";function e(e,r,t){return(e||"")+(r?` ${r}`:"")+(t?` ${t}`:"")}export{e as format} -
bettercx-widget/trunk/assets/p-B7XTg7r_.system.js
r3385343 r3402632 1 var __awaiter=this&&this.__awaiter||function(e,t,n,r){function i(e){return e instanceof n?e:new n((function(t){t(e)}))}return new(n||(n=Promise))((function(n,o){function u(e){try{c(r.next(e))}catch(e){o(e)}}function a(e){try{c(r["throw"](e))}catch(e){o(e)}}function c(e){e.done?n(e.value):i(e.value).then(u,a)}c((r=r.apply(e,t||[])).next())}))};var __generator=this&&this.__generator||function(e,t){var n={label:0,sent:function(){if(o[0]&1)throw o[1];return o[1]},trys:[],ops:[]},r,i,o,u;return u={next:a(0),throw:a(1),return:a(2)},typeof Symbol==="function"&&(u[Symbol.iterator]=function(){return this}),u;function a(e){return function(t){return c([e,t])}}function c(a){if(r)throw new TypeError("Generator is already executing.");while(u&&(u=0,a[0]&&(n=0)),n)try{if(r=1,i&&(o=a[0]&2?i["return"]:a[0]?i["throw"]||((o=i["return"])&&o.call(i),0):i.next)&&!(o=o.call(i,a[1])).done)return o;if(i=0,o)a=[a[0]&2,o.value];switch(a[0]){case 0:case 1:o=a;break;case 4:n.label++;return{value:a[1],done:false};case 5:n.label++;i=a[1];a=[0];continue;case 7:a=n.ops.pop();n.trys.pop();continue;default:if(!(o=n.trys,o=o.length>0&&o[o.length-1])&&(a[0]===6||a[0]===2)){n=0;continue}if(a[0]===3&&(!o||a[1]>o[0]&&a[1]<o[3])){n.label=a[1];break}if(a[0]===6&&n.label<o[1]){n.label=o[1];o=a;break}if(o&&n.label<o[2]){n.label=o[2];n.ops.push(a);break}if(o[2])n.ops.pop();n.trys.pop();continue}a=t.call(e,n)}catch(e){a=[6,e];i=0}finally{r=o=0}if(a[0]&5)throw a[1];return{value:a[0]?a[1]:void 0,done:true}}};System.register(["./p-eV7FkxIV.system.js"],(function(e,t){"use strict";var n,r,i;return{setters:[function(t){n=t.p;r=t.g;i=t.b;e("setNonce",t.s)}],execute:function(){var e=this;var o=function(){var e=t.meta.url;var r={};if(e!==""){r.resourcesUrl=new URL(".",e).href}return n(r)};o().then((function(t){return __awaiter(e,void 0,void 0,(function(){return __generator(this,(function(e){switch(e.label){case 0:return[4,r()];case 1:e.sent();return[2,i([["p-65f90db1.system",[[257,"bcx-message-composer",{disabled:[4],loading:[4],placeholder:[1],maxLength:[2,"max-length"],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- e1740149.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],state:[32],open:[64],close:[64],toggle:[64],sendMessage:[64]},null,{publicKey:["onPublicKeyChange"]}]]]],t)]}}))}))}))}}}));1 var __awaiter=this&&this.__awaiter||function(e,t,n,r){function i(e){return e instanceof n?e:new n((function(t){t(e)}))}return new(n||(n=Promise))((function(n,o){function u(e){try{c(r.next(e))}catch(e){o(e)}}function a(e){try{c(r["throw"](e))}catch(e){o(e)}}function c(e){e.done?n(e.value):i(e.value).then(u,a)}c((r=r.apply(e,t||[])).next())}))};var __generator=this&&this.__generator||function(e,t){var n={label:0,sent:function(){if(o[0]&1)throw o[1];return o[1]},trys:[],ops:[]},r,i,o,u;return u={next:a(0),throw:a(1),return:a(2)},typeof Symbol==="function"&&(u[Symbol.iterator]=function(){return this}),u;function a(e){return function(t){return c([e,t])}}function c(a){if(r)throw new TypeError("Generator is already executing.");while(u&&(u=0,a[0]&&(n=0)),n)try{if(r=1,i&&(o=a[0]&2?i["return"]:a[0]?i["throw"]||((o=i["return"])&&o.call(i),0):i.next)&&!(o=o.call(i,a[1])).done)return o;if(i=0,o)a=[a[0]&2,o.value];switch(a[0]){case 0:case 1:o=a;break;case 4:n.label++;return{value:a[1],done:false};case 5:n.label++;i=a[1];a=[0];continue;case 7:a=n.ops.pop();n.trys.pop();continue;default:if(!(o=n.trys,o=o.length>0&&o[o.length-1])&&(a[0]===6||a[0]===2)){n=0;continue}if(a[0]===3&&(!o||a[1]>o[0]&&a[1]<o[3])){n.label=a[1];break}if(a[0]===6&&n.label<o[1]){n.label=o[1];o=a;break}if(o&&n.label<o[2]){n.label=o[2];n.ops.push(a);break}if(o[2])n.ops.pop();n.trys.pop();continue}a=t.call(e,n)}catch(e){a=[6,e];i=0}finally{r=o=0}if(a[0]&5)throw a[1];return{value:a[0]?a[1]:void 0,done:true}}};System.register(["./p-eV7FkxIV.system.js"],(function(e,t){"use strict";var n,r,i;return{setters:[function(t){n=t.p;r=t.g;i=t.b;e("setNonce",t.s)}],execute:function(){var e=this;var o=function(){var e=t.meta.url;var r={};if(e!==""){r.resourcesUrl=new URL(".",e).href}return n(r)};o().then((function(t){return __awaiter(e,void 0,void 0,(function(){return __generator(this,(function(e){switch(e.label){case 0:return[4,r()];case 1:e.sent();return[2,i([["p-65f90db1.system",[[257,"bcx-message-composer",{disabled:[4],loading:[4],placeholder:[1],maxLength:[2,"max-length"],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-887c5563.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],state:[32],open:[64],close:[64],toggle:[64],sendMessage:[64]},null,{publicKey:["onPublicKeyChange"]}]]]],t)]}}))}))}))}}})); -
bettercx-widget/trunk/bettercx-widget.php
r3385343 r3402632 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. 56 * Version: 1.0.6 7 7 * Author: BetterCX 8 8 * Author URI: https://bettercx.ai … … 16 16 * 17 17 * @package BetterCX_Widget 18 * @version 1.0. 518 * @version 1.0.6 19 19 * @author BetterCX 20 20 * @license GPLv2+ … … 37 37 38 38 // Define plugin constants 39 define('BETTERCX_WIDGET_VERSION', '1.0. 5');39 define('BETTERCX_WIDGET_VERSION', '1.0.6'); 40 40 define('BETTERCX_WIDGET_PLUGIN_FILE', __FILE__); 41 41 define('BETTERCX_WIDGET_PLUGIN_DIR', plugin_dir_path(__FILE__)); -
bettercx-widget/trunk/readme.txt
r3385343 r3402632 5 5 Tested up to: 6.8 6 6 Requires PHP: 7.4 7 Stable tag: 1.0. 57 Stable tag: 1.0.6 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 244 244 245 245 == Changelog == 246 247 = 1.0.6 = 248 * Added support for multiple trigger messages with URL-based matching 249 * Trigger messages now automatically selected based on current page URL 250 * Improved default message handling - only displays on matching website origins 251 * Fixed ping message close button click reliability on desktop and mobile 252 * Removed rotation animation from close button for cleaner interaction 253 * Enhanced touch event handling for better mobile responsiveness 254 * Improved event propagation to prevent click blocking issues 255 * Simplified close button styling for better accessibility 246 256 247 257 = 1.0.5 = … … 301 311 == Upgrade Notice == 302 312 313 = 1.0.6 = 314 Important update: Enhanced trigger messages system with URL-based matching. Fixed mobile and desktop click issues on the ping message close button. Improved overall reliability and user experience. 315 303 316 = 1.0.5 = 304 317 Product parsing update: Added markdown-style product link parsing from streamed messages. Fixed mobile click issues and improved text formatting after product removal. … … 545 558 546 559 = Last Updated = 547 202 4-01-15560 2025-11-25 548 561 549 562 = Version = 550 1.0. 5563 1.0.6 551 564 552 565 = Minimum WordPress Version = … … 563 576 564 577 = Stable Tag = 565 1.0. 5578 1.0.6 566 579 567 580 = Development Version = 568 1.0. 5581 1.0.6 569 582 570 583 = Requires at least = -
bettercx-widget/trunk/src/components/bcx-product-slider/bcx-product-slider.tsx
r3385343 r3402632 93 93 if (this.products.length <= 1) return; 94 94 95 this.isDragging = true; 96 this.startX = e.touches[0].clientX; 95 // Only start dragging if touch moves (don't interfere with click) 96 const touch = e.touches[0]; 97 this.startX = touch.clientX; 97 98 this.initialTranslate = this.getCurrentTranslate(); 98 e.preventDefault();99 // Don't set isDragging yet - wait for touchmove 99 100 }; 100 101 101 102 private handleTouchMove = (e: TouchEvent) => { 102 if ( !this.isDragging ||this.products.length <= 1) return;103 if (this.products.length <= 1) return; 103 104 104 105 this.currentX = e.touches[0].clientX; 105 const diff = this.currentX - this.startX; 106 const diff = Math.abs(this.currentX - this.startX); 107 108 // Only start dragging if touch moves significantly (to distinguish from click) 109 if (!this.isDragging && diff > 10) { 110 this.isDragging = true; 111 } 112 113 if (!this.isDragging) return; 114 106 115 const containerWidth = this.sliderRef.parentElement?.offsetWidth || 0; 107 const diffPercentage = ( diff/ containerWidth) * 100;116 const diffPercentage = ((this.currentX - this.startX) / containerWidth) * 100; 108 117 const newTranslate = this.initialTranslate + diffPercentage; 109 118 … … 113 122 114 123 private handleTouchEnd = () => { 115 if (!this.isDragging || this.products.length <= 1) return; 116 124 if (this.products.length <= 1) return; 125 126 const wasDragging = this.isDragging; 117 127 this.isDragging = false; 118 this.snapToNearestSlide(); 128 129 // Only snap to slide if user was actually dragging 130 if (wasDragging) { 131 this.snapToNearestSlide(); 132 } 119 133 }; 120 134 … … 196 210 } 197 211 198 private handleProductClick = (product: Product) => { 199 // Open product URL in new tab 200 window.open(product.product_url, '_blank', 'noopener,noreferrer'); 212 private handleProductClick = (product: Product, e: Event) => { 213 // Prevent event from bubbling to prevent conflict with touch handlers 214 e.stopPropagation(); 215 216 // Only navigate if not dragging (user is actually clicking, not swiping) 217 if (!this.isDragging) { 218 // Open product URL in new tab 219 window.open(product.product_url, '_blank', 'noopener,noreferrer'); 220 } 201 221 }; 202 222 … … 233 253 > 234 254 {this.products.map((product, index) => ( 235 <div key={index} class="bcx-product-slider__card" style={{ width: `${100 / this.products.length}%` }} onClick={ () => this.handleProductClick(product)}>255 <div key={index} class="bcx-product-slider__card" style={{ width: `${100 / this.products.length}%` }} onClick={e => this.handleProductClick(product, e)}> 236 256 <div class="bcx-product-slider__card-image"> 237 257 <img -
bettercx-widget/trunk/src/components/bettercx-widget/bettercx-widget.scss
r3385343 r3402632 1953 1953 .bcx-widget__ping-close { 1954 1954 position: absolute; 1955 top: 0px; 1956 right: 0px; 1957 transform: translate(25%, -25%); 1958 width: 26px; 1959 height: 26px; 1955 top: var(--bcx-space-2); 1956 right: var(--bcx-space-2); 1957 width: 28px; 1958 height: 28px; 1959 min-width: 28px; 1960 min-height: 28px; 1960 1961 border: none; 1961 background: linear-gradient(135deg, #ff6b6b, #ff8e8e);1962 color: white;1962 background: color-mix(in srgb, var(--bcx-text-primary) 8%, transparent); 1963 color: var(--bcx-text-secondary); 1963 1964 cursor: pointer; 1964 border-radius: 50%;1965 border-radius: var(--bcx-radius-full); 1965 1966 display: flex; 1966 1967 align-items: center; 1967 1968 justify-content: center; 1968 transition: all var(--bcx-transition-normal); 1969 transition: 1970 background-color var(--bcx-transition-fast), 1971 color var(--bcx-transition-fast); 1969 1972 z-index: 10; 1970 1971 /* Elegant hover effect */ 1973 touch-action: manipulation; 1974 -webkit-tap-highlight-color: transparent; 1975 padding: 0; 1976 margin: 0; 1977 pointer-events: auto; 1978 1979 /* Simple hover effect */ 1972 1980 &:hover { 1973 background: linear-gradient(135deg, #ff5252, #ff7979);1974 transform: translateY(-1px) scale(1.05) translate(25%, -25%);1981 background: color-mix(in srgb, var(--bcx-text-primary) 12%, transparent); 1982 color: var(--bcx-text-primary); 1975 1983 } 1976 1984 1977 1985 /* Active state */ 1978 1986 &:active { 1979 transform: translateY(0) scale(0.95); 1980 transition: all var(--bcx-transition-fast); 1981 } 1982 1983 /* Focus state */ 1987 background: color-mix(in srgb, var(--bcx-text-primary) 16%, transparent); 1988 } 1989 1990 /* Focus state - simple outline, no transform */ 1984 1991 &:focus { 1985 outline: 2px solid rgba(255, 107, 107, 0.4);1992 outline: 2px solid color-mix(in srgb, var(--bcx-primary-500) 30%, transparent); 1986 1993 outline-offset: 2px; 1994 } 1995 1996 /* Focus visible for keyboard navigation */ 1997 &:focus:not(:focus-visible) { 1998 outline: none; 1987 1999 } 1988 2000 1989 2001 /* Icon styling */ 1990 2002 svg { 1991 width: 14px; 1992 height: 14px; 1993 stroke-width: 2.5; 1994 transition: transform var(--bcx-transition-fast); 1995 } 1996 1997 &:hover svg { 1998 transform: rotate(90deg); 2003 width: 16px; 2004 height: 16px; 2005 stroke-width: 2; 2006 flex-shrink: 0; 2007 pointer-events: none; 1999 2008 } 2000 2009 } -
bettercx-widget/trunk/src/components/bettercx-widget/bettercx-widget.tsx
r3385343 r3402632 4 4 import { ThemeService } from '../../services/theme.service'; 5 5 import { WidgetState, WidgetEvent, ChatMessage, Product } from '../../types/api'; 6 import { parseProducts, removeProductsFromText } from '../../utils/product-parser'; 6 7 7 8 @Component({ … … 135 136 } 136 137 137 const triggerMessage = ('trigger_message' in sessionData ? sessionData.trigger_message : undefined) as string | undefined; 138 const triggerMessages = ('trigger_messages' in sessionData ? sessionData.trigger_messages : []) as Array<{ 139 id: number; 140 url: string; 141 message: string; 142 is_default: boolean; 143 trigger_after_seconds: number; 144 created_at: string; 145 updated_at: string; 146 }>; 138 147 const agentName = ('agent_name' in sessionData ? sessionData.agent_name : undefined) as string | undefined; 148 149 // Select the appropriate trigger message based on current URL 150 const selectedTriggerMessage = this.selectTriggerMessage(triggerMessages); 139 151 140 152 this.setState({ … … 150 162 logo: ('logo' in sessionData ? sessionData.logo : undefined) as string | undefined, 151 163 welcomeMessage: welcomeMessage, 152 triggerMessage: triggerMessage, 164 triggerMessages: triggerMessages, 165 selectedTriggerMessage: selectedTriggerMessage, 153 166 agentName: agentName, 154 167 messages: initialMessages, … … 156 169 157 170 // Start ping message timer if trigger message exists 158 if ( triggerMessage && triggerMessage.trim()) {159 this.startPingMessageTimer( );171 if (selectedTriggerMessage && selectedTriggerMessage.message && selectedTriggerMessage.message.trim()) { 172 this.startPingMessageTimer(selectedTriggerMessage); 160 173 } 161 174 this.emitEvent('session-created', { origin }); … … 271 284 272 285 if (assistantMessage) { 286 // Just accumulate content during streaming 273 287 assistantMessage.content += chunk.content; 288 274 289 this.setState({ 275 290 messages: [...this.state.messages.slice(0, -1), { ...assistantMessage }], … … 300 315 this.setState({ isTyping: true }); 301 316 } 317 } 318 } 319 320 // After streaming is complete, parse products from full content 321 if (assistantMessage) { 322 // Parse products from the complete content 323 const parsedProducts = parseProducts(assistantMessage.content); 324 325 if (parsedProducts.length > 0) { 326 if (!assistantMessage.products) { 327 assistantMessage.products = []; 328 } 329 // Convert ProductInfo to Product format and add to message 330 const products = parsedProducts.map(p => ({ 331 product_name: p.product_name, 332 image_url: p.image_url, 333 product_url: p.product_url, 334 })); 335 assistantMessage.products = [...assistantMessage.products, ...products]; 336 337 // Remove product markdown from content 338 assistantMessage.content = removeProductsFromText(assistantMessage.content); 302 339 } 303 340 } … … 599 636 > 600 637 {/* Ping Message */} 601 {this.state.showPingMessage && this.state. triggerMessage && (638 {this.state.showPingMessage && this.state.selectedTriggerMessage && this.state.selectedTriggerMessage.message && ( 602 639 <div class="bcx-widget__ping-message" data-adblock-bypass="true"> 603 640 <div class="bcx-widget__ping-content"> … … 616 653 )} 617 654 <div class="bcx-widget__ping-text"> 618 <div class="bcx-widget__ping-message-text">{this.state. triggerMessage}</div>655 <div class="bcx-widget__ping-message-text">{this.state.selectedTriggerMessage.message}</div> 619 656 <div class="bcx-widget__ping-status"> 620 657 <span class="bcx-widget__ping-status-dot"></span> … … 622 659 </div> 623 660 </div> 624 <button class="bcx-widget__ping-close" onClick={this.handlePingMessageClose} aria-label="Close ping message" data-adblock-bypass="true"> 661 <button 662 class="bcx-widget__ping-close" 663 onClick={e => { 664 e.preventDefault(); 665 e.stopPropagation(); 666 this.handlePingMessageClose(); 667 }} 668 onTouchEnd={e => { 669 e.preventDefault(); 670 e.stopPropagation(); 671 this.handlePingMessageClose(); 672 }} 673 aria-label="Close ping message" 674 data-adblock-bypass="true" 675 type="button" 676 > 625 677 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 626 678 <line x1="18" y1="6" x2="6" y2="18"></line> … … 897 949 } 898 950 899 private startPingMessageTimer() { 951 /** 952 * Select the appropriate trigger message based on current page URL 953 * Matches URL patterns and falls back to default message only if origin matches 954 */ 955 private selectTriggerMessage( 956 triggerMessages: Array<{ 957 id: number; 958 url: string; 959 message: string; 960 is_default: boolean; 961 trigger_after_seconds: number; 962 created_at: string; 963 updated_at: string; 964 }>, 965 ): 966 | { 967 id: number; 968 url: string; 969 message: string; 970 is_default: boolean; 971 trigger_after_seconds: number; 972 created_at: string; 973 updated_at: string; 974 } 975 | undefined { 976 if (!triggerMessages || triggerMessages.length === 0) { 977 return undefined; 978 } 979 980 const currentPath = window.location.pathname; 981 const currentOrigin = window.location.origin; 982 983 // Check if current origin matches any trigger message origin 984 let originMatches = false; 985 const defaultMessage = triggerMessages.find(msg => msg.is_default); 986 987 // First, try to find a match for the current full URL (exact match) 988 for (const triggerMessage of triggerMessages) { 989 if (triggerMessage.is_default) { 990 continue; // Skip defaults in first pass 991 } 992 993 try { 994 const triggerUrl = new URL(triggerMessage.url); 995 const triggerOrigin = triggerUrl.origin; 996 const triggerPath = triggerUrl.pathname; 997 998 // Track if origin matches (for default fallback check) 999 if (triggerOrigin === currentOrigin) { 1000 originMatches = true; 1001 1002 // If trigger path is just "/" or empty, it matches homepage 1003 if (triggerPath === '/' || triggerPath === '') { 1004 if (currentPath === '/' || currentPath === '') { 1005 return triggerMessage; 1006 } 1007 } else { 1008 // Check if current path starts with trigger path (for subpage matching like "/cart" matching "/cart/checkout") 1009 if (currentPath === triggerPath || currentPath.startsWith(triggerPath + '/')) { 1010 return triggerMessage; 1011 } 1012 } 1013 } 1014 } catch { 1015 // Invalid URL format, skip this trigger message 1016 continue; 1017 } 1018 } 1019 1020 // Check if default message origin matches current origin 1021 if (defaultMessage) { 1022 try { 1023 const defaultUrl = new URL(defaultMessage.url); 1024 const defaultOrigin = defaultUrl.origin; 1025 if (defaultOrigin === currentOrigin) { 1026 originMatches = true; 1027 } 1028 } catch { 1029 // Invalid URL format, skip 1030 } 1031 } 1032 1033 // If no exact match found but origin matches, return the default message 1034 // If origin doesn't match (different website), return undefined 1035 if (originMatches && defaultMessage) { 1036 return defaultMessage; 1037 } 1038 1039 return undefined; 1040 } 1041 1042 private startPingMessageTimer(triggerMessage: { 1043 id: number; 1044 url: string; 1045 message: string; 1046 is_default: boolean; 1047 trigger_after_seconds: number; 1048 created_at: string; 1049 updated_at: string; 1050 }) { 900 1051 // Clear any existing timer 901 1052 this.clearPingMessageTimer(); 902 1053 903 // Set timer for 15 seconds 1054 // Use trigger_after_seconds from the message (convert to milliseconds) 1055 const delayMs = triggerMessage.trigger_after_seconds * 1000; 1056 904 1057 this.pingMessageTimeout = setTimeout(() => { 905 1058 // Only show ping message if chat is not open and not already shown 906 if (!this.state.isOpen && !this.state.showPingMessage && this.state. triggerMessage) {1059 if (!this.state.isOpen && !this.state.showPingMessage && this.state.selectedTriggerMessage) { 907 1060 this.setState({ showPingMessage: true }); 908 1061 } 909 }, 12500);1062 }, delayMs); 910 1063 } 911 1064 -
bettercx-widget/trunk/src/types/api.ts
r3385343 r3402632 146 146 logo?: string; 147 147 welcomeMessage?: string; 148 triggerMessage?: string; // Added for the ping message feature 148 triggerMessages?: Array<{ 149 id: number; 150 url: string; 151 message: string; 152 is_default: boolean; 153 trigger_after_seconds: number; 154 created_at: string; 155 updated_at: string; 156 }>; // Array of trigger messages from backend 157 selectedTriggerMessage?: { 158 id: number; 159 url: string; 160 message: string; 161 is_default: boolean; 162 trigger_after_seconds: number; 163 created_at: string; 164 updated_at: string; 165 }; // The trigger message selected based on URL matching 149 166 showPingMessage?: boolean; // Added to control ping message visibility 150 167 agentName?: string; // Added for custom agent name from backend
Note: See TracChangeset
for help on using the changeset viewer.