1(function() { 2 const chatTemplate = ` 3 <style> 4 #liveChatBubble { 5 position: fixed; 6 bottom: 25px; 7 left: 25px; 8 z-index: 9998; 9 background: #ff4444; 10 color: #fff; 11 width: 54px; 12 height: 54px; 13 border-radius: 50%; 14 display: flex; 15 align-items: center; 16 justify-content: center; 17 font-size: 22px; 18 cursor: pointer; 19 border: 2px solid #11120f; 20 box-shadow: 3px 3px 0px #11120f; 21 user-select: none; 22 transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.2s ease, background-color 0.2s ease, bottom 0.2s ease; 23 } 24 #liveChatBubble:hover { 25 transform: translateY(-4px) scale(1.08); 26 box-shadow: 5px 5px 0px #11120f; 27 background: #ff2a2a; 28 } 29 #liveChatBubble:active { 30 transform: translate(1px, 1px) scale(0.94); 31 box-shadow: 1px 1px 0px #11120f; 32 } 33 #liveChatBubble.is-open { 34 background: #11120f; 35 } 36 #liveChatBubble #chatBubbleIcon { 37 display: inline-block; 38 transition: transform 0.28s cubic-bezier(0.34, 1.56, 0.64, 1); 39 } 40 #liveChatBubble.is-open #chatBubbleIcon { 41 transform: rotate(90deg) scale(1.1); 42 } 43 44 #liveChatWidget { 45 display: none; 46 position: fixed; 47 bottom: 90px; 48 left: 25px; 49 width: min(340px, calc(100vw - 30px)); 50 height: 480px; 51 background: #f8f6ef; 52 border: 2px solid #11120f; 53 box-shadow: 6px 6px 0px #11120f; 54 z-index: 9999; 55 flex-direction: column; 56 border-radius: 4px; 57 overflow: hidden; 58 font-family: 'Cairo', 'NotoColorEmoji', sans-serif; 59 box-sizing: border-box; 60 transform-origin: bottom left; 61 } 62 #liveChatWidget.chat-anim-in { 63 display: flex !important; 64 animation: chatWidgetPopIn 0.3s cubic-bezier(0.34, 1.56, 0.64, 1) forwards; 65 } 66 #liveChatWidget.chat-anim-out { 67 display: flex !important; 68 animation: chatWidgetPopOut 0.22s cubic-bezier(0.16, 1, 0.3, 1) forwards; 69 } 70 71 @media (max-width: 800px) { 72 #liveChatBubble { 73 bottom: 95px !important; 74 left: 18px !important; 75 width: 48px !important; 76 height: 48px !important; 77 font-size: 20px !important; 78 } 79 #liveChatWidget { 80 bottom: 155px !important; 81 left: 15px !important; 82 right: 15px !important; 83 width: calc(100vw - 30px) !important; 84 height: min(440px, calc(100dvh - 175px)) !important; 85 transform-origin: bottom center; 86 } 87 } 88 89 .chat-btn-action { 90 transition: transform 0.18s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.18s ease, background-color 0.18s ease; 91 } 92 .chat-btn-action:hover { 93 transform: translateY(-2px); 94 box-shadow: 3px 3px 0px #11120f !important; 95 } 96 .chat-btn-action:active { 97 transform: translate(1px, 1px); 98 box-shadow: 0px 0px 0px #11120f !important; 99 } 100 101 .rating-btn { 102 background: #fff; 103 border: 1px solid #11120f; 104 font-size: 20px; 105 padding: 4px 8px; 106 cursor: pointer; 107 border-radius: 4px; 108 box-shadow: 1px 1px 0px #11120f; 109 transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1), background-color 0.18s ease; 110 } 111 .rating-btn:hover, .rating-btn.active { 112 transform: translateY(-3px) scale(1.22); 113 background: #fee2e2; 114 border-color: #ff4444; 115 box-shadow: 2px 2px 0px #11120f; 116 } 117 118 .typing-wave-dot { 119 display: inline-block; 120 animation: typingWave 1.2s infinite ease-in-out; 121 } 122 @keyframes typingWave { 123 0%, 60%, 100% { transform: translateY(0); } 124 30% { transform: translateY(-4px); } 125 } 126 127 .chat-msg-bubble { 128 animation: bubblePopIn 0.28s cubic-bezier(0.34, 1.56, 0.64, 1) forwards; 129 transition: transform 0.15s ease; 130 } 131 .chat-msg-bubble:hover { transform: translateY(-1px); } 132 133 .chat-input-focus { 134 transition: border-color 0.2s ease, box-shadow 0.2s ease; 135 } 136 .chat-input-focus:focus { 137 border-color: #ff4444 !important; 138 box-shadow: inset 1px 1px 2px rgba(0,0,0,0.08), 0 0 0 2px #fee2e2 !important; 139 } 140 141 @keyframes chatWidgetPopIn { 142 0% { opacity: 0; transform: scale(0.85) translateY(24px); } 143 100% { opacity: 1; transform: scale(1) translateY(0); } 144 } 145 @keyframes chatWidgetPopOut { 146 0% { opacity: 1; transform: scale(1) translateY(0); } 147 100% { opacity: 0; transform: scale(0.88) translateY(20px); } 148 } 149 @keyframes bubblePopIn { 150 0% { opacity: 0; transform: scale(0.82) translateY(12px); } 151 100% { opacity: 1; transform: scale(1) translateY(0); } 152 } 153 @keyframes leadBoxFade { 154 from { opacity: 0; transform: translateY(8px); } 155 to { opacity: 1; transform: translateY(0); } 156 } 157 </style> 158 159 <div id="liveChatBubble" onclick="toggleLiveChat()">
160 <span id="chatBubbleIcon">ð¬</span> 161 </div> 162 163 <div id="liveChatWidget"> 164 <div style="background: #11120f; color: #fff; padding: 12px 16px; display: flex; justify-content: space-between; align-items: center; border-bottom: 2px solid #11120f; flex-shrink: 0;"> 165 <div> 166 <b style="font-size: 13px; display: block; line-height: 1.2;">Ù Ø³Ø¹ÙØ¯ عبداÙÙÙ</b> 167 <small id="chatStatusText" style="color: #4ade80; font-size: 10px; font-weight: 600;">â Ù ØªØµÙ ÙØ¬Ø§Ùز ÙÙØ±Ø¯</small> 168 </div> 169 <span onclick="toggleLiveChat()" style="cursor: pointer; font-size: 18px; font-weight: 700; color: #fff; padding: 0 4px; transition: transform 0.18s ease;" onmouseenter="this.style.transform='rotate(90deg)'" onmouseleave="this.style.transform='none'">â</span> 170 </div> 171 172 <div id="leadFormBox" style="display: none; padding: 18px; background: #f8f6ef; flex-direction: column; gap: 10px; justify-content: center; flex: 1; animation: leadBoxFade 0.25s ease;"> 173 <p id="leadGreetingTitle" style="font-size: 13px; font-weight: 700; margin: 0 0 4px; color: #11120f;">Ù Ø±ØØ¨Ø§Ù بÙ! ð</p> 174 <p id="leadGreetingDesc" style="font-size: 11.5px; color: #6c6b64; margin: 0 0 8px; line-height: 1.6;">ÙØ³Ø¹Ø¯ÙÙ Ø§ÙØªÙØ§ØµÙ Ù Ø¹ÙØ Ø£Ø¯Ø®Ù Ø¨ÙØ§Ùات٠ÙÙØ¨Ø¯Ø¡ Ù٠تابعة Ø§Ø³ØªÙØ³Ø§Ø±Ù:</p> 175 <input type="text" id="leadName" class="chat-input-focus" placeholder="اس٠٠اÙÙØ±ÙÙ " style="padding: 10px; border: 1px solid #11120f; font-family: inherit; font-size: 12px; outline: none; background: #fff; color: #11120f; border-radius: 3px;"> 176 <input type="email" id="leadEmail" class="chat-input-focus" placeholder="Ø§ÙØ¨Ø±Ùد Ø§ÙØ¥ÙÙØªØ±ÙÙÙ (Ø§Ø®ØªÙØ§Ø±Ù)" style="padding: 10px; border: 1px solid #11120f; font-family: inherit; font-size: 12px; outline: none; background: #fff; color: #11120f; border-radius: 3px;"> 177 <button onclick="startChatWithLead()" class="chat-btn-action" style="background: #ff4444; color: #fff; border: 1px solid #11120f; padding: 10px; font-size: 12px; font-weight: 700; cursor: pointer; font-family: inherit; box-shadow: 2px 2px 0px #11120f; border-radius: 3px;">بدء اÙÙ ØØ§Ø¯Ø«Ø© ð</button> 178 <button onclick="skipLeadAndStart()" style="background: transparent; color: #6c6b64; border: none; font-size: 11px; cursor: pointer; text-decoration: underline; font-family: inherit; margin-top: 4px; transition: color 0.15s;" onmouseenter="this.style.color='#11120f'" onmouseleave="this.style.color='#6c6b64'">ØªØ®Ø·Ù ÙØ§Ùبدء ÙØ²Ø§Ø¦Ø±</button> 179 </div> 180 181 <div id="liveChatLogs" style="flex: 1; min-height: 0; padding: 14px; overflow-y: auto; display: flex; flex-direction: column; gap: 10px; background: #eeeae1; scroll-behavior: smooth;"></div> 182 183 <div id="typingIndicator" style="display: none; padding: 5px 14px; font-size: 11px; font-weight: 600; color: #15803d; background: #dcfce7; border-top: 1px solid #86efac;"> 184 <span class="typing-wave-dot">âï¸</span> Ù Ø³Ø¹ÙØ¯ ÙÙØªØ¨ Ø±Ø¯ÙØ§Ù Ø§ÙØ¢Ù... 185 </div> 186 187 <form id="liveChatForm" onsubmit="sendLiveClientMsg(event)" style="display: flex; border-top: 2px solid #11120f; background: #fff; margin: 0; flex-shrink: 0;"> 188 <input type="text" id="liveChatInput" placeholder="Ø§ÙØªØ¨ Ø±Ø³Ø§ÙØªÙ ÙÙØ§..." required autocomplete="off" oninput="handleVisitorTyping()" style="flex: 1; border: none; padding: 12px 14px; font-family: inherit; font-size: 13px; outline: none; background: transparent; color: #11120f;"> 189 <button type="submit" id="liveChatSendBtn" style="background: #ff4444; color: #fff; border: none; border-right: 2px solid #11120f; padding: 0 18px; font-weight: 700; font-size: 13px; cursor: pointer; font-family: inherit; transition: background 0.15s ease;" onmouseenter="this.style.background='#ff2a2a'" onmouseleave="this.style.background='#ff4444'">إرساÙ</button> 190 </form> 191 192 <div id="chatEndedBar" style="display: none; padding: 12px 14px; background: #f8f6ef; border-top: 2px solid #11120f; text-align: center; flex-direction: column; gap: 6px; flex-shrink: 0; max-height: 220px; overflow-y: auto;"> 193 <div id="ratingSection" style="display: flex; flex-direction: column; gap: 5px;">
194 <span style="font-size: 11.5px; font-weight: 700; color: #11120f;">ÙÙÙ ÙØ§Ùت ØªØ¬Ø±Ø¨ØªÙ Ù Ø¹ÙØ§Ø â</span> 195 <div style="display: flex; justify-content: center; gap: 8px;"> 196 <button type="button" class="rating-btn" onclick="submitRating(5, this)" title="٠٠تاز">ð</button> 197 <button type="button" class="rating-btn" onclick="submitRating(4, this)" title="Ø¬ÙØ¯">ð</button> 198 <button type="button" class="rating-btn" onclick="submitRating(3, this)" title="عادÙ">ð</button> 199 <button type="button" class="rating-btn" onclick="submitRating(1, this)" title="Ø³ÙØ¡">ð</button> 200 </div> 201 <div id="feedbackBox" style="display: none; margin-top: 4px;"> 202 <input type="text" id="visitorFeedbackText" class="chat-input-focus" placeholder="Ù ÙØ§ØØ¸Ø© إضاÙÙØ© (Ø§Ø®ØªÙØ§Ø±Ù)..." style="width: 100%; padding: 6px 8px; border: 1px solid #11120f; font-family: inherit; font-size: 11px; outline: none; background: #fff; box-sizing: border-box; border-radius: 3px;"> 203 <button onclick="saveFeedbackText()" class="chat-btn-action" style="width: 100%; margin-top: 4px; background: #11120f; color: #fff; border: none; padding: 5px; font-size: 10.5px; font-weight: 700; cursor: pointer; font-family: inherit; border-radius: 3px;">إرسا٠اÙÙ ÙØ§ØØ¸Ø© â</button> 204 </div> 205 <span id="ratingStatusText" style="font-size: 10.5px; color: #2e7d32; font-weight: 700; display: none;">Ø´ÙØ±Ø§Ù ÙØªÙÙÙÙ Ù! â¤ï¸</span> 206 </div> 207 208 <div style="display: flex; gap: 6px; margin-top: 6px;"> 209 <button onclick="sendTranscriptViaEmail()" class="chat-btn-action" style="flex: 1; background: #fff; color: #11120f; border: 1px solid #11120f; padding: 6px; font-size: 10.5px; font-weight: 700; cursor: pointer; font-family: inherit; box-shadow: 1px 1px 0px #11120f; border-radius: 3px;">Ø¥Ø±Ø³Ø§Ù ÙØ³Ø®Ø© ÙØ¥ÙÙ ÙÙÙ âï¸</button> 210 <button onclick="startNewChatFlow()" class="chat-btn-action" style="flex: 1; background: #ff4444; color: #fff; border: 1px solid #11120f; padding: 6px; font-size: 10.5px; font-weight: 700; cursor: pointer; font-family: inherit; box-shadow: 1px 1px 0px #11120f; border-radius: 3px;">Ù ØØ§Ø¯Ø«Ø© Ø¬Ø¯ÙØ¯Ø© ð</button> 211 </div> 212 </div> 213 </div> 214 `; 215 216 function mount() { 217 if (document.getElementById('liveChatContainer')) return; 218 const el = document.createElement('div'); 219 el.id = 'liveChatContainer'; 220 el.innerHTML = chatTemplate; 221 document.body.appendChild(el); 222 } 223 224 function ensureDependencies() { 225 if (!window.PocketBase) { 226 const s = document.createElement('script'); 227 s.src = 'https://cdnjs.cloudflare.com/ajax/libs/pocketbase/0.21.3/pocketbase.umd.js'; 228 s.onload = () =>
228 { initClientApp(); }; 229 document.head.appendChild(s); 230 } else { 231 initClientApp(); 232 } 233 } 234 235 if (document.readyState === 'loading') { 236 document.addEventListener('DOMContentLoaded', () => { mount(); ensureDependencies(); }); 237 } else { 238 mount(); 239 ensureDependencies(); 240 } 241})(); 242 243let pbClient; 244let activeChatId = localStorage.getItem('pb_chat_session_id'); 245let renderedIds = new Set(); 246let clientLastDate = null; 247let isHostOnline = true; 248let currentChatCachedData = null; 249let visitorTypingTimeout = null; 250 251function initClientApp() { 252 if (typeof PocketBase === 'undefined') return; 253 pbClient = new PocketBase('https://api.msaood.com'); 254 pbClient.autoCancellation(false); 255 checkAdminOnlineStatus(); 256 if (activeChatId) { 257 subscribeToCurrentChat(); 258 } 259} 260 261function formatMessageText(rawText) { 262 const div = document.createElement('div'); 263 div.innerText = rawText || ''; 264 let safeText = div.innerHTML; 265 266 const urlRegex = /(https?:\/\/[^\s]+)/g; 267 safeText = safeText.replace(urlRegex, (url) => `<a href="${url}" target="_blank" rel="noopener noreferrer" style="color: inherit; text-decoration: underline; font-weight: 700;">${url} â</a>`); 268 269 const emailRegex = /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/g; 270 safeText = safeText.replace(emailRegex, (email) => `<a href="mailto:${email}" style="color: inherit; text-decoration: underline; font-weight: 700;">${email}</a>`); 271 272 return safeText; 273} 274 275function formatDateLabel(dateStr) { 276 const d = new Date(dateStr); 277 const today = new Date(); 278 const yesterday = new Date(); 279 yesterday.setDate(today.getDate() - 1); 280 281 if (d.toDateString() === today.toDateString()) return 'اÙÙÙÙ '; 282 if (d.toDateString() === yesterday.toDateString()) return 'أ٠س'; 283 return d.toLocaleDateString('ar-SA', { day: 'numeric', month: 'short' }); 284} 285 286window.toggleLiveChat = function() { 287 const widget = document.getElementById('liveChatWidget'); 288 const bubble = document.getElementById('liveChatBubble'); 289 const icon = document.getElementById('chatBubbleIcon'); 290 if (!widget) return; 291 292 const isOpen = widget.classList.contains('chat-anim-in'); 293 294 if (isOpen) { 295 widget.classList.remove('chat-anim-in'); 296 widget.classList.add('chat-anim-out'); 297 if (bubble) bubble.classList.remove('is-open'); 298 if (icon) icon.innerText = 'ð¬'; 299 setTimeout(() => { 300 widget.classList.remove('chat-anim-out'); 301 widget.style.display = 'none'; 302 }, 200); 303 } else { 304 widget.classList.remove('chat-anim-out'); 305 widget.classList.add('chat-anim-in'); 306 if (bubble) bubble.classList.add('is-open'); 307 if (icon) icon.innerText = 'â'; 308 checkAdminOnlineStatus(); 309 checkAndInitSession(); 310 } 311}; 312 313async function checkAdminOnlineStatus() { 314 if (!pbClient) return; 315 try { 316 const list = await pbClient.collection('admin_settings').getList(1, 1, { requestKey: null }); 317 if (list.items.length > 0) {
318 isHostOnline = list.items[0].is_online; 319 } 320 } catch (_) {} 321 322 const status = document.getElementById('chatStatusText'); 323 const desc = document.getElementById('leadGreetingDesc'); 324 if (isHostOnline) { 325 if (status) { status.innerText = 'â Ù ØªØµÙ ÙØ¬Ø§Ùز ÙÙØ±Ø¯'; status.style.color = '#4ade80'; } 326 if (desc) desc.innerText = 'ÙØ³Ø¹Ø¯ÙÙ Ø§ÙØªÙØ§ØµÙ Ù Ø¹ÙØ ØªÙØ¶Ù Ø¨Ø¥Ø¯Ø®Ø§Ù Ø¨ÙØ§ÙØ§ØªÙ ÙØ¨Ø¯Ø¡ اÙÙ ØØ§Ø¯Ø«Ø©:'; 327 } else { 328 if (status) { status.innerText = 'â ØºÙر Ù ØªØµÙ ØØ§ÙÙØ§Ù'; status.style.color = '#cbd5e1'; } 329 if (desc) desc.innerText = 'Ø£ÙØ§ ØºÙØ± Ù ØªØµÙ ØØ§ÙÙØ§Ù. Ø§ØªØ±Ù Ø±Ø³Ø§ÙØªÙ ÙØ¥ÙÙ ÙÙÙ ÙØ³Ø£Ø¹Ø§Ùد Ø§ÙØ±Ø¯ عÙÙÙ ÙÙØ± Ø¹ÙØ¯ØªÙ! ð¬'; 330 } 331} 332 333async function checkAndInitSession() { 334 if (!pbClient) initClientApp(); 335 if (activeChatId && pbClient) { 336 try { 337 const chat = await pbClient.collection('chats').getOne(activeChatId, { requestKey: null }); 338 currentChatCachedData = chat; 339 if (chat.status === 'closed' || chat.status === 'banned') { 340 showChatEndedState(); 341 await loadPastMessages(); 342 return; 343 } 344 showChatInterface(); 345 await loadPastMessages(); 346 subscribeToCurrentChat(); 347 return; 348 } catch (_) { 349 // إذا ÙØ§Ùت Ø§ÙØ¬Ùسة Ù ØØ°ÙÙØ© Ø£Ù ØºÙØ± ØµØ§ÙØØ©Ø Ù Ø³ØÙا ÙÙØ±Ø§Ù ÙØªØ¬Ùب تعÙÙ٠اÙÙØ§Ø¬ÙØ© 350 localStorage.removeItem('pb_chat_session_id'); 351 activeChatId = null; 352 currentChatCachedData = null; 353 } 354 } 355 showLeadForm(); 356} 357 358function showLeadForm() { 359 const formBox = document.getElementById('leadFormBox'); 360 if (formBox) formBox.style.display = 'flex'; 361 const logs = document.getElementById('liveChatLogs'); 362 if (logs) logs.style.display = 'none'; 363 const form = document.getElementById('liveChatForm'); 364 if (form) form.style.display = 'none'; 365 const ended = document.getElementById('chatEndedBar'); 366 if (ended) ended.style.display = 'none'; 367 const typing = document.getElementById('typingIndicator'); 368 if (typing) typing.style.display = 'none'; 369} 370 371function showChatInterface() { 372 const formBox = document.getElementById('leadFormBox'); 373 if (formBox) formBox.style.display = 'none'; 374 const logs = document.getElementById('liveChatLogs'); 375 if (logs) logs.style.display = 'flex'; 376 const form = document.getElementById('liveChatForm'); 377 if (form) form.style.display = 'flex'; 378 const ended = document.getElementById('chatEndedBar'); 379 if (ended) ended.style.display = 'none'; 380 setTimeout(() => { 381 const input = document.getElementById('liveChatInput'); 382 if (input) input.focus(); 383 }, 100); 384} 385 386function showChatEndedState() { 387 const formBox = document.getElementById('leadFormBox'); 388 if (formBox) formBox.style.display = 'none'; 389 const logs = document.getElementById('liveChatLogs'); 390 if (logs) logs.style.display = 'flex'; 391 const form = document.getElementById('liveChatForm'); 392 if (form) form.style.display = 'none'; 393 const ended = document.getElementById('chatEndedBar'); 394 if (ended) ended.style.display = 'flex'; 395 const typing = document.getElementById('typingIndicator'); 396 if (typing) typing.style.display = 'none'; 397 const status = document.getElementById('chatStatusText'); 398 if (status) { 399 status.innerText = 'â Ø§ÙÙ ØØ§Ø¯Ø«Ø© ٠غÙÙØ©'; 400 status.style.color = '#6c6b64'; 401 } 402} 403 404function handleVisitorTyping() { 405 if (!activeChatId || !pbClient) return; 406 pbClient.collection('chats').update(activeChatId, { is_visitor_typing: true }, { requestKey: null }).catch(() => {}); 407 408 if (visitorTypingTimeout) clearTimeout(visitorTypingTimeout); 409 visitorTypingTimeout = setTimeout(() => { 410 if (activeChatId && pbClient) { 411 pbClient.collection('chats').update(activeChatId, { is_visitor_typing: false }, { requestKey: null }).catch(() => {}); 412 } 413 }, 2500); 414} 415 416window.submitRating = async function(val, btn) { 417 if (!activeChatId || !pbClient) return; 418 document.querySelectorAll('.rating-btn').forEach(b => b.classList.remove('active')); 419 btn.classList.add('active'); 420 421 try { 422 await pbClient.collection('chats').update(activeChatId, { rating: val }, { requestKey: null }); 423 document.getElementById('feedbackBox').style.display = 'block'; 424 const status = document.getElementById('ratingStatusText'); 425 status.style.display = 'block'; 426 status.innerText = 'ت٠تسجÙÙ Ø§ÙØªÙÙÙÙ Ø Ø´ÙØ±Ø§Ù ÙÙ! â'; 427 } catch (e) { 428 console.error('ÙØ´Ù ØÙظ Ø§ÙØªÙÙÙÙ :', e); 429 } 430}; 431 432window.saveFeedbackText = async function() { 433 if (!activeChatId || !pbClient) return; 434 const txt = document.getElementById('visitorFeedbackText').value.trim(); 435 if (!txt) return; 436 437 try { 438 await pbClient.collection('chats').update(activeChatId, { feedback: txt }, { requestKey: null }); 439 const status = document.getElementById('ratingStatusText'); 440 status.innerText = 'ØªÙ Ø§Ø³ØªÙØ§Ù Ù ÙØ§ØØ¸ØªÙ Ø¨ÙØ¬Ø§Ø! â¤ï¸'; 441 document.getElementById('feedbackBox').style.display = 'none'; 442 } catch (e) { 443 console.error('ÙØ´Ù ØÙظ اÙÙ ÙØ§ØØ¸Ø©:', e); 444 } 445}; 446 447window.sendTranscriptViaEmail = async function() { 448 if (!activeChatId || !pbClient) return; 449 try { 450 let email = currentChatCachedData ? currentChatCachedData.visitor_email : ''; 451 if (!email) { 452 email = prompt('Ø£Ø¯Ø®Ù Ø¨Ø±ÙØ¯Ù Ø§ÙØ¥ÙÙØªØ±ÙÙÙ ÙØªØ¬ÙÙØ² ÙØ³Ø®Ø© اÙÙ ØØ§Ø¯Ø«Ø©:'); 453 if (!email) return; 454 } 455 456 const res = await pbClient.collection('chat_messages').getList(1, 50, { 457 filter: `chat = "${activeChatId}"`, 458 sort: 'created', 459 requestKey: null 460 }); 461 462 let bodyText = `Ø³Ø¬Ù Ù ØØ§Ø¯Ø«ØªÙ ٠ع Ù Ø³Ø¹ÙØ¯ عبداÙÙÙ:\n\n`;
463 res.items.forEach(m => { 464 const senderName = m.sender === 'admin' ? 'Ù Ø³Ø¹ÙØ¯' : 'Ø£ÙØª'; 465 const time = new Date(m.created).toLocaleTimeString('ar-SA', { hour: '2-digit', minute: '2-digit' }); 466 bodyText += `[${time}] ${senderName}: ${m.text}\n`; 467 }); 468 469 bodyText += `\n---\nرابط اÙÙ ÙÙØ¹: https://msaood.com`; 470 471 const mailtoUri = `mailto:${encodeURIComponent(email)}?subject=${encodeURIComponent('ÙØ³Ø®Ø© Ù ØØ§Ø¯Ø«Ø© Ø§ÙØ¯Ø¹Ù اÙ٠باشر â Ù Ø³Ø¹ÙØ¯ عبداÙÙÙ')}&body=${encodeURIComponent(bodyText)}`; 472 window.location.href = mailtoUri; 473 } catch (e) { 474 alert('تعذر استخراج Ø§ÙØ³Ø¬Ù.'); 475 } 476}; 477 478window.startNewChatFlow = function() { 479 if (activeChatId && pbClient) { 480 pbClient.collection('chats').unsubscribe(activeChatId).catch(() => {}); 481 } 482 if (pbClient) pbClient.collection('chat_messages').unsubscribe('*').catch(() => {}); 483 localStorage.removeItem('pb_chat_session_id'); 484 activeChatId = null; 485 currentChatCachedData = null; 486 resetLogsUI(); 487 checkAdminOnlineStatus(); 488 document.getElementById('leadName').value = ''; 489 document.getElementById('leadEmail').value = ''; 490 document.getElementById('feedbackBox').style.display = 'none'; 491 document.getElementById('ratingStatusText').style.display = 'none'; 492 showLeadForm(); 493}; 494 495window.startChatWithLead = async function() { 496 const name = document.getElementById('leadName').value.trim(); 497 const email = document.getElementById('leadEmail').value.trim(); 498 await createNewChatSession(name, email); 499 showChatInterface(); 500}; 501 502window.skipLeadAndStart = async function() { 503 await createNewChatSession(); 504 showChatInterface(); 505}; 506 507function resetLogsUI() { 508 renderedIds.clear(); 509 clientLastDate = null; 510 const logs = document.getElementById('liveChatLogs'); 511 if (logs) logs.innerHTML = ''; 512} 513 514async function createNewChatSession(name = '', email = '') { 515 if (!pbClient) initClientApp(); 516 try { 517 const chat = await pbClient.collection('chats').create({ 518 session_id: 'guest_' + Math.random().toString(36).substring(2, 10), 519 visitor_name: name, 520 visitor_email: email, 521 page_url: window.location.pathname, 522 status: 'active', 523 is_typing: false, 524 is_visitor_typing: false 525 }, { requestKey: null }); 526 527 activeChatId = chat.id; 528 currentChatCachedData = chat; 529 localStorage.setItem('pb_chat_session_id', activeChatId); 530 resetLogsUI(); 531 532 const visitorDisplayName = name ? `ÙØ§ ${name}` : 'بÙ'; 533 const autoWelcomeText = isHostOnline 534 ? `Ø£ÙÙØ§Ù ${visitorDisplayName}! Ù Ø¹Ù Ù Ø³Ø¹ÙØ¯ عبداÙÙÙ. ØªÙØ¶Ù Ø¨Ø§Ø³ØªÙØ³Ø§Ø±Ù ÙØ¬Ø§Ø±Ù Ø§ÙØ±Ø¯ عÙÙÙ ÙÙØ±Ø§Ù. ð` 535 : `Ø£ÙÙØ§Ù ${visitorDisplayName}! Ø£ÙØ§ ØºÙØ± Ù ØªØµÙ ØØ§ÙÙØ§Ù عÙÙ ÙÙØØ© اÙÙ ØØ§Ø¯Ø«Ø§Øª. ØªÙ Ø§Ø³ØªÙØ§Ù Ø±Ø³Ø§ÙØªÙ ÙØ³Ø£ØªÙاص٠٠ع٠عبر Ø§ÙØ¥ÙÙ ÙÙ ÙÙ Ø£ÙØ±Ø¨ ÙÙØª بإذ٠اÙÙÙ. ð¬`; 536 537 await pbClient.collection('chat_messages').create({ 538 chat: activeChatId, 539 sender: 'admin', 540 text: autoWelcomeText 541 }, { requestKey: null }); 542 543 await loadPastMessages(); 544 subscribeToCurrentChat(); 545 } catch (e) { 546 console.error('ÙØ´Ù Ø¥ÙØ´Ø§Ø¡ Ø§ÙØ¬Ùسة:', e); 547 } 548} 549 550async function loadPastMessages() { 551 if (!activeChatId || !pbClient) return; 552 try { 553 const res = await pbClient.collection('chat_messages').getList(1, 50, { 554 filter: `chat = "${activeChatId}"`, 555 sort: 'created', 556 requestKey: null 557 }); 558 resetLogsUI(); 559 res.items.forEach(msg => { 560 renderedIds.add(msg.id); 561 renderMessageBubble(msg.text, msg.sender, msg.created); 562 }); 563 } catch (_) {} 564} 565 566function subscribeToCurrentChat() { 567 if (!activeChatId || !pbClient) return; 568 569 pbClient.collection('chat_messages').subscribe('*', function (e) { 570 if (e.action === 'create' && e.record.chat === activeChatId) { 571 if (!renderedIds.has(e.record.id)) { 572 renderedIds.add(e.record.id); 573 renderMessageBubble(e.record.text, e.record.sender, e.record.created); 574 } 575 } 576 }); 577 578 pbClient.collection('chats').subscribe(activeChatId, function (e) { 579 if (e.action === 'update') { 580 currentChatCachedData = e.record; 581 const typingEl = document.getElementById('typingIndicator'); 582 if (typingEl) { 583 if (e.record.is_typing && e.record.status !== 'closed') { 584 typingEl.style.display = 'block'; 585 } else { 586 typingEl.style.display = 'none'; 587 } 588 } 589 590 if (e.record.status === 'closed') { 591 showChatEndedState(); 592 } 593 } 594 }); 595} 596 597function renderMessageBubble(text, sender, createdAt = null) { 598 const logs = document.getElementById('liveChatLogs'); 599 if (!logs) return; 600 const isVisitor = sender === 'visitor'; 601 const msgDate = createdAt ? new Date(createdAt) : new Date(); 602 const dateKey = msgDate.toDateString(); 603 604 if (clientLastDate !== dateKey) { 605 clientLastDate = dateKey; 606 const dateDiv = document.createElement('div'); 607 dateDiv.className = 'chat-msg-bubble'; 608 dateDiv.style.cssText = 'align-self: center; background: #e2ddd3; border: 1px solid #11120f; padding: 2px 8px; font-size: 10px; font-weight: 700; color: #6c6b64; margin: 4px 0; border-radius: 3px;'; 609 dateDiv.innerText = formatDateLabel(msgDate); 610 logs.appendChild(dateDiv); 611 } 612 613 const bubble = document.createElement('div'); 614 bubble.className = 'chat-msg-bubble'; 615 bubble.style.cssText = `
616 align-self: ${isVisitor ? 'flex-end' : 'flex-start'}; 617 background: ${isVisitor ? '#ff4444' : '#ffffff'}; 618 color: ${isVisitor ? '#ffffff' : '#11120f'}; 619 border: 1px solid #11120f; 620 padding: 8px 12px 6px; 621 font-size: 12px; 622 max-width: 85%; 623 box-shadow: 2px 2px 0px #11120f; 624 line-height: 1.6; 625 word-break: break-word; 626 display: flex; 627 flex-direction: column; 628 border-radius: 4px; 629 transform-origin: ${isVisitor ? 'bottom left' : 'bottom right'}; 630 `; 631 632 const timeFormatted = msgDate.toLocaleTimeString('ar-SA', { hour: '2-digit', minute: '2-digit' }); 633 bubble.innerHTML = ` 634 <div>${formatMessageText(text)}</div> 635 <span style="font-size: 9px; align-self: flex-end; margin-top: 3px; opacity: 0.8; font-family: monospace;">${timeFormatted}</span> 636 `; 637 638 logs.appendChild(bubble); 639 logs.scrollTop = logs.scrollHeight; 640} 641 642window.sendLiveClientMsg = async function(e) { 643 e.preventDefault(); 644 const input = document.getElementById('liveChatInput'); 645 const text = input.value.trim(); 646 if (!text) return; 647 648 if (!activeChatId) await createNewChatSession(); 649 if (!activeChatId || !pbClient) return; 650 651 input.value = ''; 652 pbClient.collection('chats').update(activeChatId, { is_visitor_typing: false }, { requestKey: null }).catch(() => {}); 653 654 try { 655 await pbClient.collection('chat_messages').create({ 656 chat: activeChatId, 657 sender: 'visitor', 658 text: text 659 }, { requestKey: null }); 660 } catch (err) { 661 console.error('ÙØ´Ù Ø¥Ø±Ø³Ø§Ù Ø§ÙØ±Ø³Ø§ÙØ©:', err); 662 } 663};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.