victor34593993 commited on
Commit
42c144d
·
verified ·
1 Parent(s): 30481d2

widget: switch-channel back button fix

Browse files
Files changed (2) hide show
  1. app/static/widget.js +27 -3
  2. tests/test_widget_assets.py +10 -0
app/static/widget.js CHANGED
@@ -59,6 +59,7 @@
59
  listening: "Escuchando\u2026",
60
  micDenied: "Permiso de micr\u00f3fono denegado",
61
  homeTitle: "\u00bfC\u00f3mo prefieres escribirnos?",
 
62
  homeChat: "Chatear aqu\u00ed", homeWa: "Seguir por WhatsApp",
63
  starters: ["\u00bfQu\u00e9 ofrec\u00e9is?", "Quiero m\u00e1s informaci\u00f3n", "Hablar con una persona"]
64
  },
@@ -92,6 +93,7 @@
92
  listening: "Listening\u2026",
93
  micDenied: "Microphone permission denied",
94
  homeTitle: "How would you like to talk to us?",
 
95
  homeChat: "Chat here", homeWa: "Continue on WhatsApp",
96
  starters: ["What do you offer?", "I want more information", "Talk to a person"]
97
  },
@@ -125,6 +127,7 @@
125
  listening: "\u00c9coute\u2026",
126
  micDenied: "Acc\u00e8s au microphone refus\u00e9",
127
  homeTitle: "Comment pr\u00e9f\u00e9rez-vous nous \u00e9crire ?",
 
128
  homeChat: "Discuter ici", homeWa: "Continuer sur WhatsApp",
129
  starters: ["Que proposez-vous ?", "Je souhaite plus d'informations", "Parler \u00e0 une personne"]
130
  },
@@ -158,6 +161,7 @@
158
  listening: "H\u00f6re zu\u2026",
159
  micDenied: "Mikrofonzugriff verweigert",
160
  homeTitle: "Wie m\u00f6chten Sie uns schreiben?",
 
161
  homeChat: "Hier chatten", homeWa: "Weiter auf WhatsApp",
162
  starters: ["Was bieten Sie an?", "Ich m\u00f6chte mehr Informationen", "Mit einer Person sprechen"]
163
  },
@@ -191,6 +195,7 @@
191
  listening: "In ascolto\u2026",
192
  micDenied: "Accesso al microfono negato",
193
  homeTitle: "Come preferisci scriverci?",
 
194
  homeChat: "Chatta qui", homeWa: "Continua su WhatsApp",
195
  starters: ["Cosa offrite?", "Vorrei maggiori informazioni", "Parlare con una persona"]
196
  },
@@ -224,6 +229,7 @@
224
  listening: "A ouvir\u2026",
225
  micDenied: "Permiss\u00e3o de microfone negada",
226
  homeTitle: "Como prefere falar connosco?",
 
227
  homeChat: "Conversar aqui", homeWa: "Continuar no WhatsApp",
228
  starters: ["O que oferecem?", "Quero mais informa\u00e7\u00f5es", "Falar com uma pessoa"]
229
  }
@@ -263,6 +269,7 @@
263
 
264
  // Paperclip (attach a PDF/photo to the conversation).
265
  var ICON_CLIP = '<svg viewBox="0 0 24 24"><path d="M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5c0-1.38 1.12-2.5 2.5-2.5s2.5 1.12 2.5 2.5v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5c0 1.38 1.12 2.5 2.5 2.5s2.5-1.12 2.5-2.5V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z"/></svg>';
 
266
  var ICON_MIC = '<svg viewBox="0 0 24 24"><path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5.3-3c0 3-2.54 5.1-5.3 5.1S6.7 14 6.7 11H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c3.28-.49 6-3.31 6-6.72h-1.7z"/></svg>';
267
 
268
  // Voice input: browser-native speech recognition (free, no external service).
@@ -347,6 +354,8 @@
347
  "#ssb-head .ssb-x{margin-left:auto;background:transparent;border:0;color:#fff;font-size:24px;cursor:pointer;opacity:.85;line-height:1}" +
348
  "#ssb-mute{margin-left:auto;width:32px;height:32px;background:transparent;border:0;cursor:pointer;opacity:.85;display:flex;align-items:center;justify-content:center;padding:0;flex:none}" +
349
  "#ssb-mute:hover{opacity:1}#ssb-mute svg{width:20px;height:20px;fill:#fff}" +
 
 
350
  "#ssb-head .ssb-x{margin-left:6px}" +
351
  "#ssb-launch .ssb-badge{position:absolute;top:-4px;right:-4px;min-width:20px;height:20px;box-sizing:border-box;padding:0 5px;border-radius:10px;background:#e11d48;border:2px solid #fff;color:#fff;font-family:system-ui,-apple-system,Segoe UI,sans-serif;font-size:11px;font-weight:700;line-height:16px;text-align:center;display:none}" +
352
  "#ssb-msgs{flex:1;overflow-y:auto;padding:16px 14px;background:#f4f5f7;display:flex;flex-direction:column;gap:10px}" +
@@ -460,6 +469,8 @@
460
  '<div id="ssb-head"><div class="ssb-av"></div><div>' +
461
  '<div class="ssb-name">' + esc(name) + '</div>' +
462
  '<div class="ssb-status"><i></i>' + T.status + '</div></div>' +
 
 
463
  '<button id="ssb-mute" type="button"></button>' +
464
  '<button class="ssb-x" aria-label="' + T.close + '">&times;</button></div>' +
465
  homeHtml +
@@ -484,22 +495,33 @@
484
  var greeted = false;
485
  var homeEl = panel.querySelector("#ssb-home");
486
  var chatarea = panel.querySelector("#ssb-chatarea");
 
487
 
488
  // "both" mode: visitor picks a channel on open. Chat -> reveal the normal
489
  // chat area (and greet, same as chat-only mode). WhatsApp -> open wa.me;
490
- // the panel stays open behind it so they can still switch to chat.
 
 
 
491
  function chooseChat() {
492
  if (homeEl) homeEl.style.display = "none";
493
  if (chatarea) chatarea.style.display = "flex";
 
494
  if (!greeted) { botMsg(cfg.welcome); renderStarters(); greeted = true; }
495
  input.focus();
496
  }
497
  function chooseWa() {
498
  window.open("https://wa.me/" + waDigits, "_blank", "noopener");
499
  }
 
 
 
 
 
500
  if (homeEl) {
501
  panel.querySelector("#ssb-home-chat").addEventListener("click", chooseChat);
502
  panel.querySelector("#ssb-home-wa").addEventListener("click", chooseWa);
 
503
  }
504
 
505
  // ---- Visitor uploads (Phase 3): paperclip -> chip -> ids on next send ----
@@ -801,8 +823,10 @@
801
  if (panel.classList.contains("open")) {
802
  setBadge(0); stopFlash();
803
  // With a home screen, wait for the visitor to pick a channel before
804
- // greeting/focusing the input (avoids a wasted bot turn pre-choice).
805
- if (!homeEl) {
 
 
806
  if (!greeted) { botMsg(cfg.welcome); renderStarters(); greeted = true; }
807
  input.focus();
808
  }
 
59
  listening: "Escuchando\u2026",
60
  micDenied: "Permiso de micr\u00f3fono denegado",
61
  homeTitle: "\u00bfC\u00f3mo prefieres escribirnos?",
62
+ switchChannel: "Cambiar canal",
63
  homeChat: "Chatear aqu\u00ed", homeWa: "Seguir por WhatsApp",
64
  starters: ["\u00bfQu\u00e9 ofrec\u00e9is?", "Quiero m\u00e1s informaci\u00f3n", "Hablar con una persona"]
65
  },
 
93
  listening: "Listening\u2026",
94
  micDenied: "Microphone permission denied",
95
  homeTitle: "How would you like to talk to us?",
96
+ switchChannel: "Switch channel",
97
  homeChat: "Chat here", homeWa: "Continue on WhatsApp",
98
  starters: ["What do you offer?", "I want more information", "Talk to a person"]
99
  },
 
127
  listening: "\u00c9coute\u2026",
128
  micDenied: "Acc\u00e8s au microphone refus\u00e9",
129
  homeTitle: "Comment pr\u00e9f\u00e9rez-vous nous \u00e9crire ?",
130
+ switchChannel: "Changer de canal",
131
  homeChat: "Discuter ici", homeWa: "Continuer sur WhatsApp",
132
  starters: ["Que proposez-vous ?", "Je souhaite plus d'informations", "Parler \u00e0 une personne"]
133
  },
 
161
  listening: "H\u00f6re zu\u2026",
162
  micDenied: "Mikrofonzugriff verweigert",
163
  homeTitle: "Wie m\u00f6chten Sie uns schreiben?",
164
+ switchChannel: "Kanal wechseln",
165
  homeChat: "Hier chatten", homeWa: "Weiter auf WhatsApp",
166
  starters: ["Was bieten Sie an?", "Ich m\u00f6chte mehr Informationen", "Mit einer Person sprechen"]
167
  },
 
195
  listening: "In ascolto\u2026",
196
  micDenied: "Accesso al microfono negato",
197
  homeTitle: "Come preferisci scriverci?",
198
+ switchChannel: "Cambia canale",
199
  homeChat: "Chatta qui", homeWa: "Continua su WhatsApp",
200
  starters: ["Cosa offrite?", "Vorrei maggiori informazioni", "Parlare con una persona"]
201
  },
 
229
  listening: "A ouvir\u2026",
230
  micDenied: "Permiss\u00e3o de microfone negada",
231
  homeTitle: "Como prefere falar connosco?",
232
+ switchChannel: "Mudar de canal",
233
  homeChat: "Conversar aqui", homeWa: "Continuar no WhatsApp",
234
  starters: ["O que oferecem?", "Quero mais informa\u00e7\u00f5es", "Falar com uma pessoa"]
235
  }
 
269
 
270
  // Paperclip (attach a PDF/photo to the conversation).
271
  var ICON_CLIP = '<svg viewBox="0 0 24 24"><path d="M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5c0-1.38 1.12-2.5 2.5-2.5s2.5 1.12 2.5 2.5v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5c0 1.38 1.12 2.5 2.5 2.5s2.5-1.12 2.5-2.5V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z"/></svg>';
272
+ var ICON_BACK = '<svg viewBox="0 0 24 24"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>';
273
  var ICON_MIC = '<svg viewBox="0 0 24 24"><path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5.3-3c0 3-2.54 5.1-5.3 5.1S6.7 14 6.7 11H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c3.28-.49 6-3.31 6-6.72h-1.7z"/></svg>';
274
 
275
  // Voice input: browser-native speech recognition (free, no external service).
 
354
  "#ssb-head .ssb-x{margin-left:auto;background:transparent;border:0;color:#fff;font-size:24px;cursor:pointer;opacity:.85;line-height:1}" +
355
  "#ssb-mute{margin-left:auto;width:32px;height:32px;background:transparent;border:0;cursor:pointer;opacity:.85;display:flex;align-items:center;justify-content:center;padding:0;flex:none}" +
356
  "#ssb-mute:hover{opacity:1}#ssb-mute svg{width:20px;height:20px;fill:#fff}" +
357
+ "#ssb-switch{margin-left:auto;width:32px;height:32px;background:transparent;border:0;cursor:pointer;opacity:.85;align-items:center;justify-content:center;padding:0;flex:none}" +
358
+ "#ssb-switch:hover{opacity:1}#ssb-switch svg{width:19px;height:19px;fill:#fff}" +
359
  "#ssb-head .ssb-x{margin-left:6px}" +
360
  "#ssb-launch .ssb-badge{position:absolute;top:-4px;right:-4px;min-width:20px;height:20px;box-sizing:border-box;padding:0 5px;border-radius:10px;background:#e11d48;border:2px solid #fff;color:#fff;font-family:system-ui,-apple-system,Segoe UI,sans-serif;font-size:11px;font-weight:700;line-height:16px;text-align:center;display:none}" +
361
  "#ssb-msgs{flex:1;overflow-y:auto;padding:16px 14px;background:#f4f5f7;display:flex;flex-direction:column;gap:10px}" +
 
469
  '<div id="ssb-head"><div class="ssb-av"></div><div>' +
470
  '<div class="ssb-name">' + esc(name) + '</div>' +
471
  '<div class="ssb-status"><i></i>' + T.status + '</div></div>' +
472
+ (showHome ? '<button id="ssb-switch" type="button" aria-label="' + T.switchChannel +
473
+ '" title="' + T.switchChannel + '" style="display:none">' + ICON_BACK + '</button>' : '') +
474
  '<button id="ssb-mute" type="button"></button>' +
475
  '<button class="ssb-x" aria-label="' + T.close + '">&times;</button></div>' +
476
  homeHtml +
 
495
  var greeted = false;
496
  var homeEl = panel.querySelector("#ssb-home");
497
  var chatarea = panel.querySelector("#ssb-chatarea");
498
+ var switchBtn = panel.querySelector("#ssb-switch");
499
 
500
  // "both" mode: visitor picks a channel on open. Chat -> reveal the normal
501
  // chat area (and greet, same as chat-only mode). WhatsApp -> open wa.me;
502
+ // the panel stays open behind it so they can still switch to chat. A small
503
+ // back button in the header (once chat is showing) lets them return to the
504
+ // picker at any time -- reopening the panel later must NOT strand them on
505
+ // whichever channel they picked first with no way back.
506
  function chooseChat() {
507
  if (homeEl) homeEl.style.display = "none";
508
  if (chatarea) chatarea.style.display = "flex";
509
+ if (switchBtn) switchBtn.style.display = "flex";
510
  if (!greeted) { botMsg(cfg.welcome); renderStarters(); greeted = true; }
511
  input.focus();
512
  }
513
  function chooseWa() {
514
  window.open("https://wa.me/" + waDigits, "_blank", "noopener");
515
  }
516
+ function backToHome() {
517
+ if (chatarea) chatarea.style.display = "none";
518
+ if (switchBtn) switchBtn.style.display = "none";
519
+ if (homeEl) homeEl.style.display = "flex";
520
+ }
521
  if (homeEl) {
522
  panel.querySelector("#ssb-home-chat").addEventListener("click", chooseChat);
523
  panel.querySelector("#ssb-home-wa").addEventListener("click", chooseWa);
524
+ if (switchBtn) switchBtn.addEventListener("click", backToHome);
525
  }
526
 
527
  // ---- Visitor uploads (Phase 3): paperclip -> chip -> ids on next send ----
 
823
  if (panel.classList.contains("open")) {
824
  setBadge(0); stopFlash();
825
  // With a home screen, wait for the visitor to pick a channel before
826
+ // greeting/focusing the input (avoids a wasted bot turn pre-choice). Once
827
+ // they've already chosen chat (chatarea visible), reopening later still
828
+ // focuses the input like the chat-only mode does.
829
+ if (!homeEl || (chatarea && chatarea.style.display !== "none")) {
830
  if (!greeted) { botMsg(cfg.welcome); renderStarters(); greeted = true; }
831
  input.focus();
832
  }
tests/test_widget_assets.py CHANGED
@@ -35,6 +35,16 @@ def test_widget_both_mode_shows_a_single_bubble_with_channel_picker():
35
  assert js.count(key + ":") == 6, f"{key} must be defined in all 6 languages"
36
 
37
 
 
 
 
 
 
 
 
 
 
 
38
  def test_block_is_app_embed_loading_widget_in_proxy_mode():
39
  liquid = BLOCK.read_text(encoding="utf-8")
40
  assert "{% schema %}" in liquid
 
35
  assert js.count(key + ":") == 6, f"{key} must be defined in all 6 languages"
36
 
37
 
38
+ def test_widget_both_mode_can_switch_back_to_the_channel_picker():
39
+ """Regression (reported live): once a visitor picked a channel, closing and
40
+ reopening the panel left them stuck with no way back to the picker. A header
41
+ button must let them return to it at any point, even after a re-open."""
42
+ js = WIDGET.read_text(encoding="utf-8")
43
+ assert 'id="ssb-switch"' in js
44
+ assert "backToHome" in js
45
+ assert js.count("switchChannel:") == 6, "switchChannel label must be defined in all 6 languages"
46
+
47
+
48
  def test_block_is_app_embed_loading_widget_in_proxy_mode():
49
  liquid = BLOCK.read_text(encoding="utf-8")
50
  assert "{% schema %}" in liquid