1/*! Zippykind LLC - Proprietary and confidential. Copyright (c) Zippykind LLC. All rights reserved. 2 * zk-signature: sha256=b7f154ea4175ad7ba5e4828dc352b8f73297aecb45c59529aa5d5ab5eda738ff file=zk-chat.js by=Zippykind LLC 3 */ 4/* fe-749 */ 5;(function(){var s="Zippykind LLC|zk-signature|sha256=b7f154ea4175ad7ba5e4828dc352b8f73297aecb45c59529aa5d5ab5eda738ff|file=zk-chat.js";return s;})(); 6(function () { 7 "use strict"; 8 9 var ZK_CHAT = { 10 // fe-750 11 stub: false, 12 startUrl: "/chatApi/start", 13 messageUrl: "/chatApi/message", 14 feedbackUrl: "/chatApi/feedback", 15 // fe-751 16 qualifyUrl: "/chatApi/qualify", 17 // fe-752 18 notHelpfulUrl: "/chatApi/notHelpful", 19 maxChars: 500, 20 }; 21 window.ZK_CHAT = ZK_CHAT; 22 23 var root = document.querySelector("[data-zk-chat]"); 24 if (!root) return; 25 26 var launcher = root.querySelector("[data-zk-chat-open]"); 27 var panel = root.querySelector(".zk-chat-panel"); 28 var closeBtn = root.querySelector("[data-zk-chat-close]"); 29 var thread = root.querySelector("[data-zk-chat-thread]"); 30 var typing = root.querySelector("[data-zk-chat-typing]"); 31 var form = root.querySelector("[data-zk-chat-form]"); 32 var input = root.querySelector("[data-zk-chat-input]"); 33 var sendBtn = root.querySelector("[data-zk-chat-send]"); 34 var counter = root.querySelector("[data-zk-chat-count]"); 35 36 // fe-753 37 var survey = root.querySelector("[data-zk-chat-survey]"); 38 var surveyAsk = root.querySelector("[data-zk-survey-ask]"); 39 var surveyDone = root.querySelector("[data-zk-survey-done]"); 40 var surveySkip = root.querySelector("[data-zk-survey-skip]"); 41 var surveySend = root.querySelector("[data-zk-survey-send]"); 42 var surveyNote = root.querySelector("[data-zk-survey-note]"); 43 var surveyOpen = root.querySelector("[data-zk-survey-open]"); 44 var ratings = root.querySelectorAll("[data-zk-survey-rating]"); 45 46 // fe-754 47 var industryCard = root.querySelector('[data-zk-qual-card="industry"]'); 48 var profile = root.querySelector("[data-zk-chat-profile]"); 49 var profileAsk = root.querySelector("[data-zk-qual-ask]"); 50 var profileDone = root.querySelector("[data-zk-qual-done]"); 51 var profileSkip = root.querySelector("[data-zk-qual-skip]"); 52 var qualCompany = root.querySelector("[data-zk-qual-company]"); 53 var qualEmail = root.querySelector("[data-zk-qual-email]"); 54 var qualNote = root.querySelector("[data-zk-qual-note]"); 55 56 var sessionToken = null; 57 // fe-1573 58 var startError = null; 59 var userTurns = 0; // fe-755 60 var surveyShown = false; // fe-756 61 var started = false; 62 var stubMessageId = 0; // fe-757 63 var busy = false; 64 65 // fe-758 66 var industryQuestion = ""; // fe-759 67 var industryPending = false; // fe-760 68 69 // fe-761 70 var heldQuestion = ""; // fe-762 71 var bridgeMessage = ""; // fe-763 72 73 // fe-764 74 function pageSlug() { 75 return (window.ZK_PAGE && window.ZK_PAGE.slug) ? String(window.ZK_PAGE.slug) : ""; 76 } 77 78 // fe-765 79 80 // fe-1650 81 82 // fe-1654 83 var ZK_MD_SAFE_HREF = /^(https?:\/\/|mailto:|\/(?![\/\\]))/i; 84 var ZK_MD_INLINE = /\[([^\]\n]+)\]\(([^)\s]+)\)|\*\*([^\s*](?:[^*\n]*[^\s*])?)\*\*|\*([^\s*](?:[^*\n]*[^\s*])?)\*|(https?:\/\/[^\s<]*[^\s<.,;:!?)\]'"])/g; 85 86 function mdLink(href, label) { 87 var a = document.createElement("a"); 88 a.href = href; 89 a.textContent = label; 90 if (/^https?:\/\//i.test(href)) { a.target = "_blank"; a.rel = "noopener noreferrer"; } 91 return a; 92 } 93 94 function mdInline(parent, text) { 95 var re = new RegExp(ZK_MD_INLINE.source, "g"); 96 var last = 0, m; 97 while ((m = re.exec(text)) !== null) { 98 if (m.index > last) parent.appendChild(document.createTextNode(text.slice(last, m.index))); 99 if (m[1] !== undefined) { 100 parent.appendChild(ZK_MD_SAFE_HREF.test(m[2]) ? mdLink(m[2], m[1]) : document.createTextNode(m[0])); 101 } else if (m[3] !== undefined) { 102 // fe-1653 103 var s = document.createElement("strong"); mdInline(s, m[3]); parent.appendChild(s); 104 } else if (m[4] !== undefined) { 105 var e = document.createElement("em"); mdInline(e, m[4]); parent.appendChild(e); 106 } else { 107 parent.appendChild(mdLink(m[5], m[5])); 108 } 109 last = re.lastIndex; 110 } 111 if (last < text.length) parent.appendChild(document.createTextNode(text.slice(last))); 112 } 113 114 function renderMarkdown(el, text) { 115 var lines = String(text).replace(/\r\n?/g, "\n").split("\n"); 116 var list = null, listType = "", para = null; 117 for (var i = 0; i < lines.length; i++) { 118 var line = lines[i]; 119 var ul = /^\s*[-*â¢]\s+(.*)$/.exec(line); 120 var ol = /^\s*\d+[.)]\s+(.*)$/.exec(line); 121 if (ul || ol) { 122 para = null; 123 var type = ul ? "ul" : "ol"; 124 if (!list || listType !== type) { 125 list = document.createElement(type); listType = type; el.appendChild(list); 126 } 127 var li = document.createElement("li"); 128 mdInline(li, (ul || ol)[1]); 129 list.appendChild(li); 130 continue; 131 } 132 list = null; listType = ""; 133 if (/^\s*$/.test(line)) { para = null; continue; } 134 if (para) { para.appendChild(document.createElement("br")); } 135 else { para = document.createElement("p"); el.appendChild(para); } 136 var h = /^\s*#{1,6}\s+(.*)$/.exec(line); 137 if (h) { var st = document.createElement("strong"); mdInline(st, h[1]); para.appendChild(st); } 138 else { mdInline(para, line); } 139 } 140 } 141 142 // fe-766 143 function bubble(role, text, messageId) { 144 var row = document.createElement("div"); 145 row.className = "zk-chat-msg zk-chat-msg--" + role; 146 var b = document.createElement("div"); 147 b.className = "zk-chat-bubble"; 148 // fe-1651 149 b.zkSource = text == null ? "" : String(text); 150 if (role === "bot") { 151 b.className += " zk-chat-bubble--md"; 152 renderMarkdown(b, b.zkSource); 153 } else { 154 // fe-767 155 b.textContent = b.zkSource; 156 linkify(b); 157 } 158 row.appendChild(b); 159 if (role === "bot" && messageId) { 160 row.appendChild(notHelpfulControl(messageId)); 161 } 162 thread.appendChild(row); 163 thread.scrollTop = thread.scrollHeight; 164 // fe-768
165 if (typeof zkChatSave === "function") zkChatSave(); 166 return row; 167 } 168 169 // fe-769 170 function notHelpfulControl(messageId) { 171 var wrap = document.createElement("div"); 172 wrap.className = "zk-chat-nothelpful"; 173 var link = document.createElement("button"); 174 link.type = "button"; 175 link.className = "zk-chat-nothelpful-btn"; 176 link.textContent = "Not helpful?"; 177 link.addEventListener("click", function () { 178 if (link.disabled) return; 179 link.disabled = true; 180 link.textContent = "Thanks - flagged for review"; 181 postVerified(ZK_CHAT.notHelpfulUrl, { 182 session_token: sessionToken, 183 message_id: String(messageId), 184 }).catch(function () {}); 185 }); 186 wrap.appendChild(link); 187 return wrap; 188 } 189 190 function linkify(el) { 191 var re = /(https?:\/\/[^\s]+)/g; 192 if (!re.test(el.textContent)) return; 193 var parts = el.textContent.split(re); 194 el.textContent = ""; 195 parts.forEach(function (p) { 196 if (/^https?:\/\//.test(p)) { 197 var a = document.createElement("a"); 198 a.href = p; 199 a.target = "_blank"; 200 a.rel = "noopener"; 201 a.textContent = p; 202 el.appendChild(a); 203 } else if (p) { 204 el.appendChild(document.createTextNode(p)); 205 } 206 }); 207 } 208 209 function setTyping(on) { 210 if (typing) typing.hidden = !on; 211 if (on) thread.scrollTop = thread.scrollHeight; 212 } 213 214 function lockInput(msg) { 215 input.disabled = true; 216 sendBtn.disabled = true; 217 input.placeholder = msg || "Session ended â reload to start again."; 218 } 219 220 // fe-770 221 222 function post(url, fields) { 223 if (ZK_CHAT.stub) return stubbed(url, fields); 224 var body = new URLSearchParams(); 225 Object.keys(fields).forEach(function (k) { body.append(k, fields[k]); }); 226 return fetch(url, { 227 method: "POST", 228 headers: { "Content-Type": "application/x-www-form-urlencoded" }, 229 body: body.toString(), 230 }).then(function (r) { return r.json(); }); 231 } 232 233 // fe-1574 234 function handle(res, onOk) { 235 if (res && res.code === 1) return onOk(res.details); 236 var msg = (res && res.msg) || "Something went wrong â please try again."; 237 var reason = (res && res.details && res.details.reason) || ""; 238 bubble("bot", msg); 239 if (reason === "session_gone") { lockInput(); return; } 240 if (reason) return; 241 if (/expired|session/i.test(msg)) lockInput(); 242 } 243 244 // fe-771 245 246 var STUB_REPLIES = [ 247 ["flash", 248 "Flash Routes let you draw a shape on the dispatch map around the day's stops and build a " + 249 "route from every ticket inside it in one click, optionally auto-dispatched to a driver. It " + 250 "is part of the Routing Suite add-on, $0.04 per delivery on every plan."], 251 ["zone|territor|service area", 252 "Map zones are areas you draw on the map that drive auto-dispatch and delivery fees - a " + 253 "delivery landing in a zone goes to that zone's driver and gets that zone's fee. They are " + 254 "part of the Routing Suite add-on, $0.04 per delivery on every plan."], 255 ["\\beta\\b|where is|where's|arriv|on the way|on its way", 256 "Predictive ETA notifications text or email your customer automatically when the driver " + 257 "starts the route, at five stops away and at one stop away, so they hear from you before " + 258 "they have to ask. They are part of the Routing Suite add-on, $0.04 per delivery. The live " + 259 "tracking link itself is on every plan."], 260 ["messag|\\btext|group", 261 "The Message Center puts the driver, dispatcher and customer in one conversation inside the " + 262 "delivery, so nothing lives in a personal text thread. It is free on every plan; texting from " + 263 "your own dedicated branded number is $20/month plus carrier costs."], 264 ["price|cost|plan|pricing|how much", 265 "Two plans: Flex is $0.23 per delivery with no base fee, and Growth is $899/month with " + 266 "4,000 deliveries bundled ($0.20 after that). Growth wins above about 3,900 deliveries a " + 267 "month. There's a full breakdown on the pricing page."], 268 ["route|routing|optimi", 269 "Route optimization comes with the Routing Suite add-on, $0.04 per delivery on every plan, with " + 270 "up to 200 stops sequenced per run, and drivers can re-optimize from the app as the day " + 271 "changes. It is included in the 14 day trial."], 272 ["driver app|app|ios|android", 273 "Drivers get a free iOS and Android app: their route, turn-by-turn navigation, QR scanning, " +
274 "photo and signature capture, and on/off duty status."], 275 ["proof|signature|photo|pod", 276 "Every drop can capture a photo, a signature, and a GPS-stamped time. If a hand-off " + 277 "isn't possible, the customer can sign from the tracking link on their own phone."], 278 ["pharmacy|hipaa|medical|phi", 279 "Pharmacies are our biggest vertical â signature capture, cold-chain notes, and " + 280 "delivery records built to support HIPAA-compliant delivery. Worth booking a demo so we " + 281 "can walk through your workflow."], 282 ["demo|talk|human|call|sales", 283 "Happy to set that up â use the Book a Demo button in the menu, or the contact page " + 284 "if you'd rather send a message."], 285 ]; 286 287 function stubReply(text) { 288 var t = String(text).toLowerCase(); 289 for (var i = 0; i < STUB_REPLIES.length; i++) { 290 if (new RegExp(STUB_REPLIES[i][0]).test(t)) return STUB_REPLIES[i][1]; 291 } 292 return "Good question â in the live version I'd answer that from the Zippykind " + 293 "knowledgebase. For now, the features and pricing pages cover most of it, or book " + 294 "a demo and we'll walk you through it."; 295 } 296 297 function stubbed(url, fields) { 298 var delay = 450 + Math.random() * 500; 299 return new Promise(function (resolve) { 300 setTimeout(function () { 301 if (url === ZK_CHAT.startUrl) { 302 // fe-772 303 var stubSlug = pageSlug(); 304 // fe-773 305 if (stubSlug === "index" || stubSlug === "quick-start") { stubSlug = ""; } 306 resolve({ code: 1, details: { 307 session_token: "stub-session", 308 greeting: stubSlug === "" 309 ? "What kind of delivery business are you running?" 310 : "Is there anything specific on the " + stubSlug + 311 " page that you're looking for?", 312 industry_question: stubSlug === "" 313 ? "" : "What kind of delivery business are you running?", 314 max_chars: ZK_CHAT.maxChars, 315 }}); 316 } else { 317 // fe-774 318 stubMessageId += 1; 319 resolve({ code: 1, details: { reply: stubReply(fields.message), message_id: "stub-" + stubMessageId } }); 320 } 321 }, delay); 322 }); 323 } 324 325 // fe-775 326 327 // fe-776 328 function turnstileSitekey() { 329 return (window.ZK_CHAT_TURNSTILE && window.ZK_CHAT_TURNSTILE.sitekey) || ""; 330 } 331 332 // fe-777 333 function zkChatIsTrusted() { 334 return document.cookie.indexOf(" " + "zk_chat_trusted=") !== -1 335 || document.cookie.indexOf("zk_chat_trusted=") === 0; 336 } 337 338 // fe-1570 339 function getTurnstileToken(ignoreTrust) { 340 var sitekey = turnstileSitekey(); 341 var mount = root.querySelector("[data-zk-chat-turnstile]"); 342 if (!sitekey || !mount) return Promise.resolve(""); 343 // fe-778 344 if (!ignoreTrust && zkChatIsTrusted()) return Promise.resolve(""); 345 if (!window.ZKTurnstile || typeof window.ZKTurnstile.token !== "function") { 346 return Promise.reject(new Error("turnstile-unavailable")); 347 } 348 if (!mount.getAttribute("data-sitekey")) mount.setAttribute("data-sitekey", sitekey); 349 return window.ZKTurnstile.token(mount); 350 } 351 352 // fe-1655 353 var TS_TOKEN_MAX_AGE_MS = 240000; 354 var tsToken = "", tsTokenAt = 0, tsPending = null, chatVerified = false; 355 356 function freshToken(force) { 357 if (!force && tsToken && ((new Date()).getTime() - tsTokenAt) < TS_TOKEN_MAX_AGE_MS) { 358 return Promise.resolve(tsToken); 359 } 360 if (tsPending) return tsPending; 361 tsPending = getTurnstileToken(!!force).then(function (t) { 362 tsPending = null; tsToken = t || ""; tsTokenAt = (new Date()).getTime(); 363 return tsToken; 364 }, function (err) { 365 tsPending = null; throw err; 366 }); 367 return tsPending; 368 } 369 370 function prefetchToken() { 371 if (chatVerified || !turnstileSitekey()) return; 372 freshToken(false)["catch"](function () {}); 373 } 374 375 // fe-1656 376 // fe-1663 377 var verifyInFlight = null; 378 379 function postVerified(url, fields, retried) { 380 if (!chatVerified && verifyInFlight && !retried) { 381 var again = function () { return postVerified(url, fields, retried); }; 382 return verifyInFlight.then(again, again); 383 } 384 var need = !chatVerified; 385 var got = need ? freshToken(!!retried) : Promise.resolve(""); 386 var p = got.then(function (t) { 387 var body = {}; 388 for (var k in fields) { if (Object.prototype.hasOwnProperty.call(fields, k)) body[k] = fields[k]; } 389 if (need && t) body["cf-turnstile-response"] = t; 390 if (need) { tsToken = ""; tsTokenAt = 0; } 391 return post(url, body); 392 }).then(function (res) { 393 var reason = res && res.details && res.details.reason; 394 if (reason === "turnstile_required" && !retried) return postVerified(url, fields, true); 395 if (res && res.code === 1) chatVerified = true; 396 return res; 397 }); 398 if (need && !retried) { 399 verifyInFlight = p.then(function (r) { verifyInFlight = null; return r; }, 400 function (e) { verifyInFlight = null; throw e; }); 401 return verifyInFlight; 402 } 403 return p; 404 } 405 406 function postMessage(fields) { return postVerified(ZK_CHAT.messageUrl, fields); } 407 408 function turnstileFailText(err) { 409 if (err && (err.message === "turnstile-unavailable" || err.message === "turnstile-failed") 410 && window.ZKTurnstile && typeof window.ZKTurnstile.message === "function") { 411 return window.ZKTurnstile.message(err) + " The contact page will also get you a person."; 412 } 413 return ""; 414 } 415 416 // fe-781 417 var ZK_CHAT_STORE = "zkChatSession"; 418 419 function zkChatSave() { 420 try { 421 if (!sessionToken) return; 422 var turns = [];
423 var rows = thread.querySelectorAll(".zk-chat-msg"); 424 for (var i = 0; i < rows.length; i++) { 425 var b = rows[i].querySelector(".zk-chat-bubble"); 426 if (!b) continue; 427 turns.push({ 428 role: rows[i].className.indexOf("--user") !== -1 ? "user" : "bot", 429 text: typeof b.zkSource === "string" ? b.zkSource : (b.textContent || "") 430 }); 431 } 432 // fe-782 433 sessionStorage.setItem(ZK_CHAT_STORE, JSON.stringify({ 434 t: sessionToken, u: userTurns, turns: turns.slice(-40), at: (new Date()).getTime(), 435 iq: industryQuestion, bm: bridgeMessage, ia: !!qualAnswers.industry 436 })); 437 } catch (e) { /* fe-783 */ } 438 } 439 440 function zkChatRestore() { 441 try { 442 var raw = sessionStorage.getItem(ZK_CHAT_STORE); 443 if (!raw) return false; 444 var d = JSON.parse(raw); 445 if (!d || !d.t || !d.turns || !d.turns.length) return false; 446 // fe-784 447 if (d.at && ((new Date()).getTime() - d.at) > 3600000) { 448 sessionStorage.removeItem(ZK_CHAT_STORE); 449 return false; 450 } 451 sessionToken = d.t; 452 userTurns = d.u || 0; 453 for (var i = 0; i < d.turns.length; i++) bubble(d.turns[i].role, d.turns[i].text); 454 // fe-785 455 industryQuestion = d.iq || ""; 456 bridgeMessage = d.bm || ""; 457 industryPending = !d.ia; 458 if (d.ia) qualAnswers.industry = qualAnswers.industry || true; 459 started = true; 460 return true; 461 } catch (e) { return false; } 462 } 463 464 function start() { 465 // fe-1657 466 prefetchToken(); 467 // fe-786 468 if (started) return; 469 if (zkChatRestore()) return; 470 started = true; 471 setTyping(true); 472 // fe-787 473 post(ZK_CHAT.startUrl, { page: pageSlug() }).then(function (res) { 474 setTyping(false); 475 handle(res, function (d) { 476 sessionToken = d.session_token; 477 startError = null; 478 if (d.max_chars) { 479 ZK_CHAT.maxChars = d.max_chars; 480 input.setAttribute("maxlength", d.max_chars); 481 var total = root.querySelector(".zk-chat-count"); 482 if (total) total.lastChild.textContent = "/" + d.max_chars; 483 } 484 bubble("bot", d.greeting); 485 // fe-788 486 industryQuestion = d.industry_question || ""; 487 bridgeMessage = d.bridge_message || ""; 488 // fe-789 489 industryPending = true; 490 if (industryQuestion === "") { 491 // fe-790 492 industryPending = false; 493 showIndustryChips(); 494 } 495 }); 496 }).catch(function (err) { 497 setTyping(false); 498 // fe-1571 499 started = false; 500 // fe-1575 501 startError = (err && err.message) || "start-failed"; 502 // fe-1572 503 if (err && (err.message === "turnstile-unavailable" || err.message === "turnstile-failed") 504 && window.ZKTurnstile && typeof window.ZKTurnstile.message === "function") { 505 bubble("bot", window.ZKTurnstile.message(err) + " The contact page will also get you a person."); 506 return; 507 } 508 bubble("bot", "I can't reach the assistant right now â the contact page will get " + 509 "you a person."); 510 }); 511 } 512 513 function send(text) { 514 if (busy || !text.trim()) return; 515 516 // fe-1576 517 if (!sessionToken) { 518 userTurns++; 519 bubble("user", text); 520 input.value = ""; 521 autosize(); 522 count(); 523 var why = "I could not start a chat session, so I have not sent that."; 524 if ((startError === "turnstile-unavailable" || startError === "turnstile-failed") 525 && window.ZKTurnstile && typeof window.ZKTurnstile.message === "function") { 526 why = window.ZKTurnstile.message({ message: startError }); 527 } 528 bubble("bot", why + " The contact page will get you a person."); 529 return; 530 } 531 532 // fe-791 533 if (industryPending && heldQuestion === "") { 534 userTurns++; 535 bubble("user", text); 536 input.value = ""; 537 autosize(); 538 count(); 539 heldQuestion = text; 540 industryPending = false; 541 // fe-792 542 if (bridgeMessage) bubble("bot", bridgeMessage); 543 setTyping(true); 544 // fe-793 545 setTimeout(function () { 546 setTyping(false); 547 if (industryQuestion) bubble("bot", industryQuestion); 548 showIndustryChips(); 549 }, 2000); 550 return; 551 } 552 553 busy = true; 554 userTurns++; 555 bubble("user", text); 556 input.value = ""; 557 autosize(); 558 count(); 559 setTyping(true); 560 // fe-794 561 input.disabled = true; 562 sendBtn.disabled = true; 563 // fe-795 564 postMessage({ 565 session_token: sessionToken, 566 message: text, 567 page: pageSlug(), 568 }) 569 .then(function (res) { 570 setTyping(false); 571 busy = false;
572 // fe-796 573 input.disabled = false; 574 sendBtn.disabled = false; 575 handle(res, function (d) { 576 // fe-797 577 bubble("bot", d.reply, d.message_id); 578 // fe-798 579 askIndustry(); 580 // fe-799 581 if (userTurns >= 2) showProfile(); 582 }); 583 }) 584 .catch(function (err) { 585 setTyping(false); 586 busy = false; 587 // fe-800 588 input.disabled = false; 589 sendBtn.disabled = false; 590 bubble("bot", turnstileFailText(err) || "That didn't go through â try again in a moment."); 591 // fe-801 592 askIndustry(); 593 }); 594 } 595 596 /* fe-802 */ 597 function askIndustry() { 598 if (!industryPending) return; 599 industryPending = false; 600 if (industryQuestion !== "") bubble("bot", industryQuestion); 601 showIndustryChips(); 602 } 603 604 // fe-803 605 606 function open() { 607 // fe-804 608 cancelPeek(); 609 root.classList.remove("is-peeked"); 610 resetSurvey(); 611 panel.hidden = false; 612 launcher.setAttribute("aria-expanded", "true"); 613 root.classList.add("is-open"); 614 start(); 615 setTimeout(function () { input.focus(); }, 120); 616 } 617 618 // fe-805 619 function close(opts) { 620 cancelPeek(); 621 var silent = !!(opts && opts.silent === true); 622 // fe-806 623 var heldFocus = panel.contains(document.activeElement); 624 resetSurvey(); 625 panel.hidden = true; 626 launcher.setAttribute("aria-expanded", "false"); 627 root.classList.remove("is-open"); 628 if (!silent || heldFocus) { 629 // fe-807
630 launcher.focus({ preventScroll: true }); 631 } 632 } 633 634 /* fe-808 */ 635 636 function showSurvey() { 637 if (!survey) { close(); return; } 638 surveyShown = true; 639 root.classList.add("is-surveying"); 640 survey.hidden = false; 641 if (surveyAsk) surveyAsk.hidden = false; 642 if (surveyDone) surveyDone.hidden = true; 643 var first = survey.querySelector("[data-zk-survey-rating]"); 644 if (first) setTimeout(function () { first.focus(); }, 60); 645 } 646 647 function resetSurvey() { 648 if (!survey) return; 649 root.classList.remove("is-surveying"); 650 survey.hidden = true; 651 survey.reset(); 652 if (surveySend) surveySend.disabled = true; 653 } 654 655 function chosenRating() { 656 for (var i = 0; i < ratings.length; i++) { 657 if (ratings[i].checked) return Number(ratings[i].value); 658 } 659 return 0; 660 } 661 662 // fe-809 663 function requestClose() { 664 if (survey && !surveyShown && userTurns > 0 && !root.classList.contains("is-surveying")) { 665 showSurvey(); 666 return; 667 } 668 close(); 669 } 670 671 for (var r = 0; r < ratings.length; r++) { 672 ratings[r].addEventListener("change", function () { 673 if (surveySend) surveySend.disabled = chosenRating() === 0; 674 }); 675 } 676 677 if (surveySkip) surveySkip.addEventListener("click", close); 678 679 if (surveyOpen) { 680 surveyOpen.addEventListener("click", function (e) { 681 e.preventDefault(); 682 showSurvey(); 683 }); 684 } 685 686 if (survey) { 687 survey.addEventListener("submit", function (e) { 688 e.preventDefault(); 689 var payload = { 690 session_token: sessionToken, 691 rating: chosenRating(), 692 comment: surveyNote ? surveyNote.value.slice(0, 300) : "", 693 }; 694 if (ZK_CHAT.stub) { 695 // fe-810 696 console.log("[zk-chat] survey (stub, not posted):", payload); 697 } else { 698 postVerified(ZK_CHAT.feedbackUrl, payload).catch(function () {}); 699 } 700 if (surveyAsk) surveyAsk.hidden = true; 701 if (surveyDone) surveyDone.hidden = false; 702 setTimeout(close, 1400); 703 }); 704 } 705 706 /* fe-811 */ 707 var NO_PROFILE_KEY = "zk-chat-no-profile"; 708 var profileShown = false; 709 var qualAnswers = {}; 710 711 function postQual(fields) { 712 // fe-812 713 if (!sessionToken) return; 714 var payload = { session_token: sessionToken }; 715 Object.keys(fields).forEach(function (k) { payload[k] = fields[k]; }); 716 if (ZK_CHAT.stub) { 717 // fe-813 718 console.log("[zk-chat] qualify (stub, not posted):", payload); 719 return; 720 } 721 // fe-814 722 postVerified(ZK_CHAT.qualifyUrl, payload)["catch"](function () {}); 723 } 724 725 // fe-815 726 function bindChips(scope) { 727 if (!scope) return; 728 scope.addEventListener("click", function (e) { 729 var btn = e.target && e.target.closest ? e.target.closest("[data-zk-qual]") : null; 730 if (!btn || !scope.contains(btn)) return; 731 e.preventDefault(); 732 var field = btn.getAttribute("data-zk-qual"); 733 var value = btn.getAttribute("data-zk-val"); 734 if (!field || !value) return; 735 736 // fe-816 737 if (qualAnswers[field] === value) return; 738 qualAnswers[field] = value; 739 740 // fe-817 741 var group = btn.parentNode ? btn.parentNode.querySelectorAll("[data-zk-qual]") : []; 742 for (var i = 0; i < group.length; i++) { 743 if (group[i].getAttribute("data-zk-qual") !== field) continue; 744 group[i].classList.toggle("is-chosen", group[i] === btn); 745 group[i].setAttribute("aria-pressed", group[i] === btn ? "true" : "false"); 746 } 747 748 var one = {}; 749 one[field] = value; 750 postQual(one); 751 752 // fe-818 753 if (field === "industry") answerHeld(value); 754 }); 755 } 756 757 // fe-819 758 function answerHeld(industryValue) { 759 if (!heldQuestion) return; 760 var text = heldQuestion; 761 heldQuestion = ""; 762 busy = true; 763 setTyping(true); 764 input.disabled = true; 765 sendBtn.disabled = true; 766 // fe-1658 767 postMessage({ 768 session_token: sessionToken, 769 message: text, 770 page: pageSlug(), 771 industry: industryValue || "" 772 }).then(function (res) { 773 setTyping(false); 774 busy = false; 775 input.disabled = false; 776 sendBtn.disabled = false; 777 handle(res, function (d) { 778 bubble("bot", d.reply, d.message_id); 779 if (userTurns >= 2) showProfile(); 780 }); 781 }).catch(function (err) { 782 setTyping(false); 783 busy = false;
784 input.disabled = false; 785 sendBtn.disabled = false; 786 // fe-820 787 bubble("bot", turnstileFailText(err) || "That didn't go through â try asking again in a moment."); 788 }); 789 } 790 791 // fe-821 792 function showIndustryChips() { 793 if (!industryCard || industryCard.parentNode === thread) return; 794 thread.appendChild(industryCard); 795 industryCard.hidden = false; 796 thread.scrollTop = thread.scrollHeight; 797 } 798 799 function showProfile() { 800 if (!profile || profileShown) return; 801 if (stored(NO_PROFILE_KEY)) return; 802 profileShown = true; 803 thread.appendChild(profile); 804 profile.hidden = false; 805 thread.scrollTop = thread.scrollHeight; 806 } 807 808 bindChips(root); 809 810 if (profileSkip) { 811 profileSkip.addEventListener("click", function () { 812 store(NO_PROFILE_KEY, "1"); 813 if (profile) profile.hidden = true; 814 }); 815 } 816 817 if (profile) { 818 profile.addEventListener("submit", function (e) { 819 e.preventDefault(); 820 var company = qualCompany ? qualCompany.value.trim().slice(0, 120) : ""; 821 var email = qualEmail ? qualEmail.value.trim().slice(0, 120) : ""; 822 823 // fe-822 824 if (email !== "" && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { 825 if (qualNote) qualNote.hidden = false; 826 if (qualEmail) qualEmail.classList.add("is-bad"); 827 return; 828 } 829 if (qualNote) qualNote.hidden = true; 830 if (qualEmail) qualEmail.classList.remove("is-bad"); 831 832 var fields = {}; 833 if (company !== "") fields.company = company; 834 if (email !== "") fields.email = email; 835 if (Object.keys(fields).length) postQual(fields); 836 837 // fe-823 838 store(NO_PROFILE_KEY, "1"); 839 if (profileAsk) profileAsk.hidden = true; 840 if (profileDone) profileDone.hidden = false; 841 thread.scrollTop = thread.scrollHeight; 842 }); 843 } 844 845 function autosize() { 846 input.style.height = "auto"; 847 input.style.height = Math.min(input.scrollHeight, 96) + "px"; 848 } 849 850 function count() { 851 if (counter) counter.textContent = String(input.value.length); 852 } 853 854 launcher.addEventListener("click", function () { 855 root.classList.contains("is-open") ? requestClose() : open(); 856 }); 857 if (closeBtn) closeBtn.addEventListener("click", requestClose); 858 859 input.addEventListener("input", function () { autosize(); count(); }); 860 861 // fe-824 862 input.addEventListener("keydown", function (e) { 863 if (e.key === "Enter" && !e.shiftKey) { 864 e.preventDefault(); 865 send(input.value); 866 } 867 }); 868 869 form.addEventListener("submit", function (e) { 870 e.preventDefault(); 871 send(input.value); 872 }); 873 874 document.addEventListener("keydown", function (e) { 875 if (e.key === "Escape" && root.classList.contains("is-open")) close(); 876 }); 877 878 /* fe-825 */ 879 var NEVER_KEY = "zk-chat-no-autoopen"; 880 var optout = root.querySelector("[data-zk-chat-optout]"); 881 var never = root.querySelector("[data-zk-chat-never]"); 882 883 // fe-826 884 function functionalOptedOut() { 885 return !!(window.ZKConsent && window.ZKConsent.optedOut("functional")); 886 } 887 function stored(key) { 888 // fe-827 889 if (functionalOptedOut()) return null; 890 try { return window.localStorage.getItem(key); } catch (e) { return null; } 891 } 892 function store(key, val) { 893 if (functionalOptedOut()) return; 894 try { window.localStorage.setItem(key, val); } catch (e) {} 895 } 896 897 if (never) { 898 never.addEventListener("click", function () { 899 store(NEVER_KEY, "1"); 900 if (optout) optout.hidden = true; 901 close(); 902 }); 903 } 904 905 var touchedByUser = false; 906 launcher.addEventListener("click", function () { touchedByUser = true; }); 907 if (closeBtn) closeBtn.addEventListener("click", function () { touchedByUser = true; }); 908 909 var reduced = window.matchMedia && 910 window.matchMedia("(prefers-reduced-motion: reduce)").matches; 911 912 // fe-828 913 var noAutoopen = !!(window.ZK_CHAT_CFG && window.ZK_CHAT_CFG.noAutoopen); 914 915 /* fe-829 */ 916 var PEEK_MS = 4000; 917 var peekTimer = null; 918 var peekWatching = false; 919 920 function keepOpen() { cancelPeek(); } 921 922 function watchPeek(on) { 923 if (on === peekWatching) return; 924 peekWatching = on; 925 var fn = on ? "addEventListener" : "removeEventListener"; 926 panel[fn]("pointerdown", keepOpen);
927 panel[fn]("keydown", keepOpen); 928 panel[fn]("focusin", keepOpen); 929 } 930 931 // fe-830 932 function cancelPeek() { 933 if (peekTimer) { clearTimeout(peekTimer); peekTimer = null; } 934 watchPeek(false); 935 } 936 937 function peek() { 938 open(); 939 setTimeout(function () { 940 if (root.classList.contains("is-open") && peekTimer) watchPeek(true); 941 }, 200); 942 peekTimer = setTimeout(function () { 943 peekTimer = null; 944 watchPeek(false); 945 if (!root.classList.contains("is-open")) return; 946 close({ silent: true }); 947 root.classList.add("is-peeked"); 948 }, PEEK_MS); 949 } 950 951 if (!noAutoopen && !stored(NEVER_KEY) && !reduced && window.innerWidth > 767) { 952 var onScroll = function () { 953 if (window.scrollY < window.innerHeight) return; 954 window.removeEventListener("scroll", onScroll); 955 if (touchedByUser || root.classList.contains("is-open")) return; 956 if (optout) optout.hidden = false; 957 peek(); 958 }; 959 window.addEventListener("scroll", onScroll, { passive: true }); 960 } 961})();
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.