@adatechnology/conversations-ui 0.1.0-rc.26 → 0.1.0-rc.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/chunk-DXPSPUWF.js +110 -0
  2. package/dist/{chunk-TV4OQRGH.js → chunk-UZJBYD5O.js} +123 -45
  3. package/dist/{chunk-2AYDBWNE.js → chunk-WCBDXZ3X.js} +13 -3
  4. package/dist/flows/index.d.ts +2 -0
  5. package/dist/flows/index.js +61 -18
  6. package/dist/index.d.ts +37 -10
  7. package/dist/index.js +694 -297
  8. package/dist/preview/index.js +3 -3
  9. package/dist/styles.css +119 -6
  10. package/package.json +1 -1
  11. package/src/AudioPlayer.tsx +8 -0
  12. package/src/AudioRecorderButton.test.tsx +3 -3
  13. package/src/AudioRecorderButton.tsx +3 -3
  14. package/src/AudioTranscription.tsx +4 -1
  15. package/src/Avatar.tsx +1 -1
  16. package/src/ConversationContextPanel.tsx +2 -2
  17. package/src/ConversationDocumentsPanel.tsx +11 -6
  18. package/src/ConversationHeader.test.tsx +66 -0
  19. package/src/ConversationHeader.tsx +147 -47
  20. package/src/ConversationListItem.tsx +3 -2
  21. package/src/ConversationRow.tsx +31 -7
  22. package/src/DocumentsLibrary.tsx +9 -4
  23. package/src/EmojiPicker.tsx +2 -1
  24. package/src/InteractiveMessage.tsx +3 -0
  25. package/src/Lightbox.tsx +1 -1
  26. package/src/MediaRenderer.tsx +2 -0
  27. package/src/MessageBubble.test.tsx +41 -0
  28. package/src/MessageBubble.tsx +27 -8
  29. package/src/MessageComposer.tsx +11 -8
  30. package/src/RichMessageComposer.test.tsx +42 -12
  31. package/src/RichMessageComposer.tsx +216 -45
  32. package/src/SimpleEmojiPicker.tsx +5 -3
  33. package/src/StatusTicks.tsx +1 -1
  34. package/src/Toast.tsx +4 -0
  35. package/src/Tooltip.test.ts +42 -0
  36. package/src/Tooltip.tsx +164 -0
  37. package/src/WhatsAppMessageEditor.tsx +4 -4
  38. package/src/WindowExpiredNotice.tsx +12 -4
  39. package/src/composer.constant.ts +33 -0
  40. package/src/conversationWindow.ts +7 -5
  41. package/src/documents/DocumentsWorkspace.tsx +10 -6
  42. package/src/flows/FlowGroupHeader.tsx +2 -2
  43. package/src/flows/FlowMapNode.tsx +2 -1
  44. package/src/flows/FlowNodeCard.tsx +3 -3
  45. package/src/flows/FlowNodePanel.tsx +6 -5
  46. package/src/flows/FlowPalette.tsx +7 -1
  47. package/src/flows/FlowPortalNode.tsx +1 -1
  48. package/src/flows/FlowsWorkspace.tsx +13 -4
  49. package/src/flows/labels.ts +4 -0
  50. package/src/hooks/useContainerWidth.ts +35 -0
  51. package/src/hooks/useConversationRealtime.ts +10 -8
  52. package/src/icon.constant.ts +12 -0
  53. package/src/index.ts +1 -0
  54. package/src/lib/composer-formatting.test.ts +78 -0
  55. package/src/lib/composer-formatting.ts +145 -0
  56. package/src/lib/whatsapp-formatting.test.tsx +37 -0
  57. package/src/lib/whatsapp-formatting.tsx +28 -3
  58. package/src/listing/index.tsx +5 -1
  59. package/src/pagination.constant.ts +10 -0
  60. package/src/preview/ConversationSimulatorPanel.tsx +1 -1
  61. package/src/providers/ConversationsProvider.tsx +8 -6
  62. package/src/settings/MessagesWorkspace.tsx +3 -0
  63. package/src/settings/TopicsForm.tsx +2 -0
  64. package/src/settings/TranscriptionSettingsForm.test.tsx +1 -1
  65. package/src/settings/TranscriptionSettingsForm.tsx +1 -0
  66. package/src/settings/WelcomeFarewellForm.tsx +1 -0
  67. package/src/settings/WhatsAppCreateTemplateForm.tsx +1 -0
  68. package/src/settings/WhatsAppTemplateSettingsForm.tsx +5 -2
  69. package/src/settings/WhatsAppTemplatesSettings.tsx +2 -0
  70. package/src/styles.css +100 -4
  71. package/src/workspace/BulkTemplateModal.tsx +4 -2
  72. package/src/workspace/ConversationPane.tsx +49 -19
  73. package/src/workspace/ConversationsInboxList.tsx +17 -8
  74. package/src/workspace/ConversationsWorkspace.tsx +24 -16
  75. package/src/workspace/useConversationsInbox.ts +5 -2
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  parseWhatsAppFormatting,
3
3
  useIsDarkTheme
4
- } from "./chunk-2AYDBWNE.js";
4
+ } from "./chunk-WCBDXZ3X.js";
5
5
 
6
6
  // src/ConversationLocalesProvider.tsx
7
7
  import { createContext, useContext } from "react";
@@ -89,7 +89,7 @@ function Ticks({ double }) {
89
89
  }
90
90
  function StatusTicks({ status, title }) {
91
91
  const colorClass = STATUS_COLOR_CLASS[status] ?? STATUS_COLOR_CLASS.sent;
92
- return /* @__PURE__ */ jsx2("span", { className: `cursor-help leading-none flex items-center ${colorClass}`, title, children: status === "failed" ? /* @__PURE__ */ jsx2(AlertTriangle, { size: 11 }) : /* @__PURE__ */ jsx2(Ticks, { double: status !== "sent" }) });
92
+ return /* @__PURE__ */ jsx2("span", { className: `cursor-help leading-none flex items-center ${colorClass}`, "data-cv-tooltip": title, children: status === "failed" ? /* @__PURE__ */ jsx2(AlertTriangle, { size: 11 }) : /* @__PURE__ */ jsx2(Ticks, { double: status !== "sent" }) });
93
93
  }
94
94
 
95
95
  // src/AudioPlayer.tsx
@@ -100,6 +100,10 @@ var BARS = Array.from({ length: 30 }, (_, i) => {
100
100
  const heights = [3, 5, 8, 12, 7, 10, 14, 9, 6, 11, 15, 8, 4, 13, 7, 10, 6, 12, 9, 5, 14, 8, 11, 6, 13, 7, 10, 5, 9, 4];
101
101
  return heights[i % heights.length];
102
102
  });
103
+ var AUDIO_PLAYER_LABELS = {
104
+ play: "Reproduzir",
105
+ pause: "Pausar"
106
+ };
103
107
  function fmt(sec) {
104
108
  if (!isFinite(sec)) return "0:00";
105
109
  const m = Math.floor(sec / 60);
@@ -161,6 +165,8 @@ function AudioPlayer({ src, isMine = false }) {
161
165
  /* @__PURE__ */ jsx3(
162
166
  "button",
163
167
  {
168
+ "data-cv-tooltip": playing ? AUDIO_PLAYER_LABELS.pause : AUDIO_PLAYER_LABELS.play,
169
+ "aria-label": playing ? AUDIO_PLAYER_LABELS.pause : AUDIO_PLAYER_LABELS.play,
164
170
  onClick: toggle,
165
171
  className: `flex-shrink-0 w-9 h-9 rounded-full flex items-center justify-center transition-colors ${playBtn}`,
166
172
  children: playing ? /* @__PURE__ */ jsx3(Pause, { size: 16 }) : /* @__PURE__ */ jsx3(Play, { size: 16, className: "translate-x-0.5" })
@@ -257,7 +263,7 @@ function AudioTranscription({ transcription, onTranscribe, isMine = false }) {
257
263
  "button",
258
264
  {
259
265
  onClick: handleCopy,
260
- title: locales.copy,
266
+ "data-cv-tooltip": locales.copy,
261
267
  "aria-label": hasCopied ? locales.copied : locales.copy,
262
268
  className: "ml-auto flex flex-shrink-0 items-center gap-1 rounded px-1.5 py-0.5 text-[11px] text-gray-500 transition-colors hover:bg-black/5 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/10 dark:hover:text-gray-200",
263
269
  children: hasCopied ? /* @__PURE__ */ jsxs3(Fragment, { children: [
@@ -289,6 +295,8 @@ function AudioTranscription({ transcription, onTranscribe, isMine = false }) {
289
295
  isLong && /* @__PURE__ */ jsx4(
290
296
  "button",
291
297
  {
298
+ "data-cv-tooltip": isExpanded ? locales.showLess : locales.showMore,
299
+ "aria-label": isExpanded ? locales.showLess : locales.showMore,
292
300
  onClick: () => setIsExpanded((current) => !current),
293
301
  className: "mt-1 text-[11px] font-medium text-gray-500 underline decoration-dotted hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200",
294
302
  children: isExpanded ? locales.showLess : locales.showMore
@@ -298,6 +306,8 @@ function AudioTranscription({ transcription, onTranscribe, isMine = false }) {
298
306
  onTranscribe && /* @__PURE__ */ jsxs3(
299
307
  "button",
300
308
  {
309
+ "data-cv-tooltip": isTranscribing ? locales.transcribing : locales.retry,
310
+ "aria-label": isTranscribing ? locales.transcribing : locales.retry,
301
311
  onClick: handleTranscribe,
302
312
  disabled: isTranscribing,
303
313
  className: "mt-1 flex items-center gap-1 text-[11px] text-gray-400 opacity-0 transition-opacity hover:text-gray-600 focus:opacity-100 group-hover:opacity-100 disabled:opacity-50 dark:text-gray-500 dark:hover:text-gray-300",
@@ -325,6 +335,8 @@ function TranscribeButton({
325
335
  return /* @__PURE__ */ jsxs3(
326
336
  "button",
327
337
  {
338
+ "data-cv-tooltip": label,
339
+ "aria-label": label,
328
340
  onClick,
329
341
  disabled: isBusy || isDisabled,
330
342
  className: "flex items-center gap-1.5 rounded px-1.5 py-0.5 text-[11px] text-gray-500 transition-colors hover:bg-black/5 hover:text-gray-700 disabled:opacity-60 dark:text-gray-400 dark:hover:bg-white/10 dark:hover:text-gray-200",
@@ -492,6 +504,8 @@ function MediaRenderer({
492
504
  return /* @__PURE__ */ jsxs4(
493
505
  "button",
494
506
  {
507
+ "data-cv-tooltip": label,
508
+ "aria-label": label,
495
509
  onClick: lazy.load,
496
510
  disabled: lazy.loading,
497
511
  className: "flex min-w-[180px] items-center gap-2 rounded-lg bg-black/5 px-2 py-1.5 text-left transition-colors hover:bg-black/10 disabled:opacity-60 dark:bg-white/10 dark:hover:bg-white/15",
@@ -584,6 +598,7 @@ function MediaRenderer({
584
598
  ] }) }) : canLazyLoad ? /* @__PURE__ */ jsx6(
585
599
  "button",
586
600
  {
601
+ "data-cv-tooltip": bubble.downloadFile,
587
602
  onClick: lazy.load,
588
603
  disabled: lazy.loading,
589
604
  className: "w-8 h-8 flex items-center justify-center rounded-full bg-gray-200 hover:bg-gray-300 flex-shrink-0 transition-colors disabled:opacity-50",
@@ -615,7 +630,7 @@ function Lightbox({ imageUrl, caption, onClose, labels }) {
615
630
  return /* @__PURE__ */ jsx7("div", { className: "fixed inset-0 z-50 bg-black/85 flex items-center justify-center p-4", onClick: onClose, children: /* @__PURE__ */ jsxs5("div", { className: "max-w-[90vw] max-h-[90vh] flex flex-col items-center", onClick: (e) => e.stopPropagation(), children: [
616
631
  /* @__PURE__ */ jsx7("img", { src: imageUrl, alt: caption ?? imageAltLabel, className: "max-w-full max-h-[80vh] object-contain rounded-lg" }),
617
632
  caption && /* @__PURE__ */ jsx7("p", { className: "text-white text-sm mt-3 text-center", children: caption }),
618
- /* @__PURE__ */ jsx7("button", { onClick: onClose, className: "mt-4 px-4 py-2 bg-white/20 text-white rounded-lg hover:bg-white/30 transition-colors", children: closeLabel })
633
+ /* @__PURE__ */ jsx7("button", { "data-cv-tooltip": closeLabel, "aria-label": closeLabel, onClick: onClose, className: "mt-4 px-4 py-2 bg-white/20 text-white rounded-lg hover:bg-white/30 transition-colors", children: closeLabel })
619
634
  ] }) });
620
635
  }
621
636
 
@@ -655,6 +670,8 @@ function InteractiveMessage({ payload, onSelect, labels, className }) {
655
670
  buttons.length > 0 ? /* @__PURE__ */ jsx8("div", { className: "mt-1 flex flex-col gap-1 border-t border-gray-200 pt-1 dark:border-gray-700", children: buttons.map((button) => /* @__PURE__ */ jsx8(
656
671
  "button",
657
672
  {
673
+ "data-cv-tooltip": button.title,
674
+ "aria-label": button.title,
658
675
  type: "button",
659
676
  disabled: !isInteractable,
660
677
  onClick: () => onSelect?.({ kind: "button", option: button }),
@@ -667,6 +684,8 @@ function InteractiveMessage({ payload, onSelect, labels, className }) {
667
684
  /* @__PURE__ */ jsxs6(
668
685
  "button",
669
686
  {
687
+ "data-cv-tooltip": payload.action?.button ?? openListLabel,
688
+ "aria-label": payload.action?.button ?? openListLabel,
670
689
  type: "button",
671
690
  onClick: () => setIsListOpen((open) => !open),
672
691
  "aria-expanded": isListOpen,
@@ -682,6 +701,8 @@ function InteractiveMessage({ payload, onSelect, labels, className }) {
682
701
  (section.rows ?? []).map((row) => /* @__PURE__ */ jsxs6(
683
702
  "button",
684
703
  {
704
+ "data-cv-tooltip": row.title,
705
+ "aria-label": row.title,
685
706
  type: "button",
686
707
  disabled: !isInteractable,
687
708
  onClick: () => onSelect?.({ kind: "list", option: row }),
@@ -711,7 +732,7 @@ function createMediaUrlResolver(api) {
711
732
  }
712
733
 
713
734
  // src/providers/ConversationsProvider.tsx
714
- import { createContext as createContext2, useContext as useContext2 } from "react";
735
+ import { createContext as createContext2, useContext as useContext2, useMemo } from "react";
715
736
  import { jsx as jsx9 } from "react/jsx-runtime";
716
737
  var ConversationsContext = createContext2(null);
717
738
  function ConversationsProvider({
@@ -719,14 +740,15 @@ function ConversationsProvider({
719
740
  sse,
720
741
  children
721
742
  }) {
722
- return /* @__PURE__ */ jsx9(ConversationsContext.Provider, { value: { api, sse }, children });
743
+ const value = useMemo(() => ({ api, sse }), [api, sse]);
744
+ return /* @__PURE__ */ jsx9(ConversationsContext.Provider, { value, children });
723
745
  }
724
746
  function useConversations() {
725
747
  return useContext2(ConversationsContext);
726
748
  }
727
749
 
728
750
  // src/MessageBubble.tsx
729
- import { useMemo, useState as useState5 } from "react";
751
+ import { useMemo as useMemo2, useState as useState5 } from "react";
730
752
  import { Check as Check2 } from "lucide-react";
731
753
  import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
732
754
  var BUBBLE_COLOR = {
@@ -751,11 +773,11 @@ function MessageBubble({
751
773
  const { bubble, selection } = useConversationLocales();
752
774
  const [lightboxSrc, setLightboxSrc] = useState5(null);
753
775
  const context = useConversations();
754
- const resolveMediaUrl = useMemo(
776
+ const resolveMediaUrl = useMemo2(
755
777
  () => onResolveMediaUrl ?? (context?.api ? createMediaUrlResolver(context.api) : void 0),
756
778
  [onResolveMediaUrl, context?.api]
757
779
  );
758
- const requestTranscription = useMemo(() => {
780
+ const requestTranscription = useMemo2(() => {
759
781
  const transcribe = onTranscribeAudio ?? context?.api?.transcribeAudio?.bind(context.api);
760
782
  if (!transcribe) return void 0;
761
783
  return () => transcribe(message.id);
@@ -765,6 +787,10 @@ function MessageBubble({
765
787
  const isMedia = MEDIA_TYPES.has(message.type);
766
788
  const isTemplate = message.type === "template";
767
789
  const isInteractive = message.type === "interactive";
790
+ const mediaCaption = isMedia ? [message.caption, message.content].find((text) => {
791
+ const trimmed = text?.trim();
792
+ return trimmed && trimmed !== message.filename;
793
+ }) : void 0;
768
794
  const displayName = message.sender === "agent" && senderName ? senderName : bubble[message.sender] ?? message.sender;
769
795
  const tooltipText = message.status === "read" && message.readAt ? `${bubble.readAt}${formatDateTime(message.readAt)}` : message.status === "failed" ? bubble.windowExpired : void 0;
770
796
  const tailCornerClass = isFirstInGroup ? isMine ? "rounded-tr-md" : "rounded-tl-md" : "";
@@ -775,7 +801,8 @@ function MessageBubble({
775
801
  e.stopPropagation();
776
802
  onToggleSelect?.();
777
803
  },
778
- title: selection.select,
804
+ "data-cv-tooltip": selection.select,
805
+ "aria-label": selection.select,
779
806
  className: `
780
807
  flex-shrink-0 self-end mb-1 w-5 h-5 rounded-full border-2 flex items-center justify-center transition-all
781
808
  ${isSelected ? "bg-teal-600 border-teal-600" : "bg-white/80 dark:bg-black/40 border-black/20 dark:border-white/30"}
@@ -813,22 +840,25 @@ function MessageBubble({
813
840
  "div",
814
841
  {
815
842
  className: "mb-1 inline-flex items-center gap-1 rounded-full bg-amber-100 px-2 py-0.5 text-xs font-medium text-amber-800 dark:bg-amber-950/60 dark:text-amber-300",
816
- title: message.moderation.terms.length > 0 ? message.moderation.terms.join(", ") : void 0,
843
+ "data-cv-tooltip": message.moderation.terms.length > 0 ? message.moderation.terms.join(", ") : void 0,
817
844
  children: [
818
845
  /* @__PURE__ */ jsx10("span", { "aria-hidden": true, children: "\u26A0\uFE0F" }),
819
846
  /* @__PURE__ */ jsx10("span", { children: bubble.moderationFlagged })
820
847
  ]
821
848
  }
822
849
  ),
823
- isMedia ? /* @__PURE__ */ jsx10(
824
- MediaRenderer,
825
- {
826
- message,
827
- onLightbox: setLightboxSrc,
828
- onResolveUrl: resolveMediaUrl,
829
- ...requestTranscription ? { onTranscribeAudio: requestTranscription } : {}
830
- }
831
- ) : isInteractive && message.payload ? (
850
+ isMedia ? /* @__PURE__ */ jsxs7(Fragment2, { children: [
851
+ /* @__PURE__ */ jsx10(
852
+ MediaRenderer,
853
+ {
854
+ message,
855
+ onLightbox: setLightboxSrc,
856
+ onResolveUrl: resolveMediaUrl,
857
+ ...requestTranscription ? { onTranscribeAudio: requestTranscription } : {}
858
+ }
859
+ ),
860
+ mediaCaption ? /* @__PURE__ */ jsx10("div", { className: "mt-1 text-sm text-gray-900 dark:text-gray-100 whitespace-pre-wrap break-words leading-[19px]", children: parseWhatsAppFormatting(mediaCaption) }) : null
861
+ ] }) : isInteractive && message.payload ? (
832
862
  // O texto da mensagem interativa mora dentro do payload (`body.text`), e `content` guarda
833
863
  // só uma cópia achatada para busca — renderizar `content` aqui duplicaria o corpo.
834
864
  /* @__PURE__ */ jsx10(InteractiveMessage, { payload: message.payload, onSelect: onInteractiveSelect })
@@ -1046,7 +1076,7 @@ function searchEmojis(query) {
1046
1076
  }
1047
1077
 
1048
1078
  // src/EmojiPicker.tsx
1049
- import { useState as useState6, useCallback as useCallback2, useMemo as useMemo2 } from "react";
1079
+ import { useState as useState6, useCallback as useCallback2, useMemo as useMemo3 } from "react";
1050
1080
  import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
1051
1081
  var DEFAULT_EMOJI_PICKER_LABELS = {
1052
1082
  search: "Buscar emoji",
@@ -1064,7 +1094,7 @@ var EmojiPicker = ({ onSelect, labels, className = "" }) => {
1064
1094
  [onSelect]
1065
1095
  );
1066
1096
  const isSearching = query.trim().length > 0;
1067
- const visibleEntries = useMemo2(
1097
+ const visibleEntries = useMemo3(
1068
1098
  () => isSearching ? searchEmojis(query) : EMOJI_CATEGORIES[activeCategory].entries,
1069
1099
  [isSearching, query, activeCategory]
1070
1100
  );
@@ -1083,6 +1113,8 @@ var EmojiPicker = ({ onSelect, labels, className = "" }) => {
1083
1113
  isSearching ? null : /* @__PURE__ */ jsx12("div", { className: "flex border-b border-gray-200 overflow-x-auto", children: EMOJI_CATEGORIES.map((category, index) => /* @__PURE__ */ jsxs8(
1084
1114
  "button",
1085
1115
  {
1116
+ "data-cv-tooltip": category.name,
1117
+ "aria-label": category.name,
1086
1118
  onClick: () => setActiveCategory(index),
1087
1119
  className: `px-3 py-2 text-xs font-medium whitespace-nowrap border-b-2 transition-colors ${activeCategory === index ? "border-blue-500 text-blue-600" : "border-transparent text-gray-500 hover:text-gray-700"}`,
1088
1120
  children: [
@@ -1099,7 +1131,7 @@ var EmojiPicker = ({ onSelect, labels, className = "" }) => {
1099
1131
  onClick: () => handleSelect(entry.emoji),
1100
1132
  className: "w-8 h-8 flex items-center justify-center text-lg hover:bg-gray-100 rounded transition-colors cursor-pointer",
1101
1133
  "aria-label": entry.emoji,
1102
- title: entry.keywords[0],
1134
+ "data-cv-tooltip": entry.keywords[0],
1103
1135
  children: entry.emoji
1104
1136
  },
1105
1137
  entry.emoji
@@ -1221,7 +1253,7 @@ function AudioRecorderButton({
1221
1253
  {
1222
1254
  type: "button",
1223
1255
  onClick: discard,
1224
- title: labelOf("discard"),
1256
+ "data-cv-tooltip": labelOf("discard"),
1225
1257
  "aria-label": labelOf("discard"),
1226
1258
  className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full text-gray-500 transition-colors hover:bg-gray-100 hover:text-red-500 dark:hover:bg-gray-700",
1227
1259
  children: /* @__PURE__ */ jsxs9("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: [
@@ -1235,7 +1267,7 @@ function AudioRecorderButton({
1235
1267
  {
1236
1268
  type: "button",
1237
1269
  onClick: confirm,
1238
- title: labelOf("send"),
1270
+ "data-cv-tooltip": labelOf("send"),
1239
1271
  "aria-label": labelOf("send"),
1240
1272
  className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-emerald-500 text-white transition-colors hover:bg-emerald-600",
1241
1273
  children: /* @__PURE__ */ jsx13("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx13("path", { d: "M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" }) })
@@ -1250,7 +1282,7 @@ function AudioRecorderButton({
1250
1282
  type: "button",
1251
1283
  disabled: disabled || pending !== void 0,
1252
1284
  onClick: () => isRecording ? stop() : void start(),
1253
- title: toggleLabel,
1285
+ "data-cv-tooltip": toggleLabel,
1254
1286
  "aria-label": toggleLabel,
1255
1287
  "aria-pressed": isRecording,
1256
1288
  className: `flex h-10 w-10 items-center justify-center rounded-full transition-colors disabled:opacity-50 ${isRecording ? "animate-pulse bg-red-500 text-white ring-4 ring-red-500/30 hover:bg-red-600" : "text-gray-500 hover:bg-gray-200 dark:hover:bg-gray-700"}`,
@@ -1267,6 +1299,17 @@ function AudioRecorderButton({
1267
1299
 
1268
1300
  // src/MessageComposer.tsx
1269
1301
  import { useState as useState8, useRef as useRef3, useCallback as useCallback4 } from "react";
1302
+
1303
+ // src/composer.constant.ts
1304
+ var COMPOSER_BAR_CLASS = "cv-composer-bar";
1305
+ var COMPOSER_TOOL_BUTTON_CLASS = "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-full transition-colors hover:bg-teal-50 hover:text-teal-600 dark:hover:bg-teal-900/30 dark:hover:text-teal-400";
1306
+ var COMPOSER_TOOL_BUTTON_ACTIVE_CLASS = "bg-teal-50 text-teal-600 dark:bg-teal-900/40 dark:text-teal-400";
1307
+ var COMPOSER_TOOL_BUTTON_IDLE_CLASS = "text-gray-400 dark:text-gray-500";
1308
+ var COMPOSER_MONOSPACE_CLASS = "bg-black/5 dark:bg-white/10 rounded px-0.5 font-mono text-sm";
1309
+ var COMPOSER_COMPACT_WIDTH = 480;
1310
+ var QUICK_REPLY_PILL_CLASS = "whitespace-nowrap rounded-full border border-gray-200 bg-white px-3 py-1.5 text-xs text-gray-600 shadow-sm transition-colors hover:border-teal-200 hover:bg-teal-50 hover:text-teal-700 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400 dark:hover:border-teal-800 dark:hover:bg-teal-950/40 dark:hover:text-teal-400";
1311
+
1312
+ // src/MessageComposer.tsx
1270
1313
  import { Fragment as Fragment3, jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
1271
1314
  function applyQuickReplyVariables(template, variables = {}) {
1272
1315
  return template.replace(/\{\{\s*([\w.]+)\s*\}\}/g, (_, chave) => variables[chave] ?? "");
@@ -1277,7 +1320,8 @@ function resolveQuickReply(quickReply, variables = {}) {
1277
1320
  var DEFAULT_MESSAGE_COMPOSER_LABELS = {
1278
1321
  emoji: "Emoji",
1279
1322
  attach: "Anexar",
1280
- send: "Enviar"
1323
+ send: "Enviar",
1324
+ removeAttachment: "Remover anexo"
1281
1325
  };
1282
1326
  var DEFAULT_ACCEPTED_FILE_TYPES = [
1283
1327
  "image/jpeg,image/png,image/webp",
@@ -1309,6 +1353,7 @@ var MessageComposer = ({
1309
1353
  const emojiLabel = labels?.emoji ?? DEFAULT_MESSAGE_COMPOSER_LABELS.emoji;
1310
1354
  const attachLabel = labels?.attach ?? DEFAULT_MESSAGE_COMPOSER_LABELS.attach;
1311
1355
  const sendLabel = labels?.send ?? DEFAULT_MESSAGE_COMPOSER_LABELS.send;
1356
+ const removeAttachmentLabel = labels?.removeAttachment ?? DEFAULT_MESSAGE_COMPOSER_LABELS.removeAttachment;
1312
1357
  const [internalText, setInternalText] = useState8("");
1313
1358
  const [showEmoji, setShowEmoji] = useState8(false);
1314
1359
  const [attachments, setAttachments] = useState8([]);
@@ -1406,7 +1451,7 @@ var MessageComposer = ({
1406
1451
  /* A barra é a superfície (cinza, largura cheia, sem raio) e o campo dentro é que arredonda —
1407
1452
  ordem do WhatsApp. Invertido, o pill arredondado ia até a borda da tela e os cantos
1408
1453
  descobriam o fundo branco da página, que lia como defeito. */
1409
- /* @__PURE__ */ jsxs10("div", { className: cn("bg-[#f0f2f5] px-2 py-2", className), children: [
1454
+ /* @__PURE__ */ jsxs10("div", { className: cn(COMPOSER_BAR_CLASS, className), children: [
1410
1455
  quickReplies && quickReplies.length > 0 && /* @__PURE__ */ jsx14(
1411
1456
  "div",
1412
1457
  {
@@ -1417,15 +1462,14 @@ var MessageComposer = ({
1417
1462
  children: quickReplies.map((quickReply) => /* @__PURE__ */ jsx14(
1418
1463
  "button",
1419
1464
  {
1465
+ "data-cv-tooltip": quickReply.label,
1466
+ "aria-label": quickReply.label,
1420
1467
  type: "button",
1421
1468
  onClick: () => {
1422
1469
  setText(resolveQuickReply(quickReply, quickReplyVariables));
1423
1470
  textareaRef.current?.focus();
1424
1471
  },
1425
- className: cn(
1426
- "whitespace-nowrap rounded-full border border-gray-200 bg-white px-2 py-1 text-xs text-gray-600 transition-colors hover:border-teal-200 hover:bg-teal-50 hover:text-teal-700 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400",
1427
- classNames?.quickReply
1428
- ),
1472
+ className: cn(QUICK_REPLY_PILL_CLASS, classNames?.quickReply),
1429
1473
  children: quickReply.label
1430
1474
  },
1431
1475
  quickReply.key
@@ -1437,11 +1481,11 @@ var MessageComposer = ({
1437
1481
  /* @__PURE__ */ jsx14("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
1438
1482
  /* @__PURE__ */ jsx14("polyline", { points: "14 2 14 8 20 8" })
1439
1483
  ] }) }),
1440
- /* @__PURE__ */ jsx14("button", { onClick: () => removeAttachment(i), className: "absolute -top-2 -right-2 w-5 h-5 bg-gray-600 text-white rounded-full flex items-center justify-center hover:bg-gray-800 text-xs", children: "\u2715" })
1484
+ /* @__PURE__ */ jsx14("button", { "data-cv-tooltip": removeAttachmentLabel, "aria-label": removeAttachmentLabel, onClick: () => removeAttachment(i), className: "absolute -top-2 -right-2 w-5 h-5 bg-gray-600 text-white rounded-full flex items-center justify-center hover:bg-gray-800 text-xs", children: "\u2715" })
1441
1485
  ] }, i)) }),
1442
1486
  /* @__PURE__ */ jsxs10("div", { className: cn("flex items-end gap-1.5 rounded-xl bg-white px-3 py-2", classNames?.field), children: [
1443
1487
  showEmojiButton && /* @__PURE__ */ jsxs10("div", { className: "relative flex-shrink-0", children: [
1444
- /* @__PURE__ */ jsx14("button", { onClick: () => setShowEmoji((v) => !v), className: "w-9 h-9 flex items-center justify-center rounded-full text-gray-500 hover:bg-gray-200 transition-colors", "aria-label": emojiLabel, children: /* @__PURE__ */ jsxs10("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
1488
+ /* @__PURE__ */ jsx14("button", { "data-cv-tooltip": emojiLabel, onClick: () => setShowEmoji((v) => !v), className: "w-9 h-9 flex items-center justify-center rounded-full text-gray-500 hover:bg-gray-200 transition-colors", "aria-label": emojiLabel, children: /* @__PURE__ */ jsxs10("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
1445
1489
  /* @__PURE__ */ jsx14("circle", { cx: "12", cy: "12", r: "10" }),
1446
1490
  /* @__PURE__ */ jsx14("path", { d: "M8 14s1.5 2 4 2 4-2 4-2" }),
1447
1491
  /* @__PURE__ */ jsx14("circle", { cx: "9", cy: "9", r: "0.5", fill: "currentColor" }),
@@ -1468,11 +1512,12 @@ var MessageComposer = ({
1468
1512
  ),
1469
1513
  showAttachButton && /* @__PURE__ */ jsxs10(Fragment3, { children: [
1470
1514
  /* @__PURE__ */ jsx14("input", { ref: fileInputRef, type: "file", multiple: true, accept: acceptedFileTypes, onChange: handleFileChange, className: "hidden" }),
1471
- /* @__PURE__ */ jsx14("button", { onClick: () => fileInputRef.current?.click(), className: "w-9 h-9 flex items-center justify-center rounded-full text-gray-500 hover:bg-gray-200 flex-shrink-0 transition-colors", "aria-label": attachLabel, children: /* @__PURE__ */ jsx14("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx14("path", { d: "M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" }) }) })
1515
+ /* @__PURE__ */ jsx14("button", { "data-cv-tooltip": attachLabel, onClick: () => fileInputRef.current?.click(), className: "w-9 h-9 flex items-center justify-center rounded-full text-gray-500 hover:bg-gray-200 flex-shrink-0 transition-colors", "aria-label": attachLabel, children: /* @__PURE__ */ jsx14("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx14("path", { d: "M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" }) }) })
1472
1516
  ] }),
1473
1517
  !canSend && effectiveIdleAction ? /* @__PURE__ */ jsx14("div", { className: "flex-shrink-0", children: effectiveIdleAction }) : /* @__PURE__ */ jsx14(
1474
1518
  "button",
1475
1519
  {
1520
+ "data-cv-tooltip": sendLabel,
1476
1521
  onClick: sendMessage,
1477
1522
  disabled: !canSend || disabled,
1478
1523
  className: `w-10 h-10 flex items-center justify-center rounded-full flex-shrink-0 transition-all ${canSend && !disabled ? "bg-[#00a884] text-white hover:bg-[#06cf9c] shadow-sm" : "bg-gray-200 text-gray-400 cursor-not-allowed"}`,
@@ -1613,6 +1658,16 @@ function useConversationDocuments(conversationId, params) {
1613
1658
  // src/ConversationDocumentsPanel.tsx
1614
1659
  import { useState as useState10 } from "react";
1615
1660
  import { ArrowUpDown, Bot, Download, Eye, Users } from "lucide-react";
1661
+
1662
+ // src/pagination.constant.ts
1663
+ var PAGINATION_LABELS = {
1664
+ first: "Primeira p\xE1gina",
1665
+ previous: "P\xE1gina anterior",
1666
+ next: "Pr\xF3xima p\xE1gina",
1667
+ last: "\xDAltima p\xE1gina"
1668
+ };
1669
+
1670
+ // src/ConversationDocumentsPanel.tsx
1616
1671
  import { jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
1617
1672
  var DOCUMENT_SOURCE_FILTER = {
1618
1673
  ALL: "all",
@@ -1740,6 +1795,8 @@ function ConversationDocumentsPanel({
1740
1795
  /* @__PURE__ */ jsxs11(
1741
1796
  "button",
1742
1797
  {
1798
+ "data-cv-tooltip": sortDirection === "desc" ? labels.sortMostRecent : labels.sortOldest,
1799
+ "aria-label": sortDirection === "desc" ? labels.sortMostRecent : labels.sortOldest,
1743
1800
  type: "button",
1744
1801
  onClick: () => applyFilter(() => setSortDirection(sortDirection === "desc" ? "asc" : "desc")),
1745
1802
  className: cn("cv-header-action inline-flex items-center gap-1", classNames?.sortButton),
@@ -1752,6 +1809,8 @@ function ConversationDocumentsPanel({
1752
1809
  hasFilters ? /* @__PURE__ */ jsx16(
1753
1810
  "button",
1754
1811
  {
1812
+ "data-cv-tooltip": labels.clearFilters,
1813
+ "aria-label": labels.clearFilters,
1755
1814
  type: "button",
1756
1815
  onClick: () => applyFilter(() => {
1757
1816
  setSearch("");
@@ -1779,7 +1838,7 @@ function ConversationDocumentsPanel({
1779
1838
  ),
1780
1839
  labels.selectAll
1781
1840
  ] }),
1782
- selectedIds.length > 0 ? /* @__PURE__ */ jsx16("button", { type: "button", onClick: () => void handleDownloadSelected(), className: "cv-header-action", children: labels.downloadSelected(selectedIds.length) }) : null,
1841
+ selectedIds.length > 0 ? /* @__PURE__ */ jsx16("button", { "data-cv-tooltip": labels.downloadSelected(selectedIds.length), "aria-label": labels.downloadSelected(selectedIds.length), type: "button", onClick: () => void handleDownloadSelected(), className: "cv-header-action", children: labels.downloadSelected(selectedIds.length) }) : null,
1783
1842
  archiveError ? /* @__PURE__ */ jsx16("span", { role: "alert", className: "text-red-600 dark:text-red-400", children: labels.archiveFailed }) : null
1784
1843
  ] }) : null,
1785
1844
  /* @__PURE__ */ jsx16("ul", { className: cn("space-y-2", classNames?.list), children: documents.map((document2) => {
@@ -1824,7 +1883,7 @@ function ConversationDocumentsPanel({
1824
1883
  "div",
1825
1884
  {
1826
1885
  className: cn("truncate text-sm font-medium", classNames?.filename),
1827
- title: document2.filename,
1886
+ "data-cv-tooltip": document2.filename,
1828
1887
  children: document2.filename
1829
1888
  }
1830
1889
  ),
@@ -1841,7 +1900,7 @@ function ConversationDocumentsPanel({
1841
1900
  {
1842
1901
  type: "button",
1843
1902
  onClick: () => void handleOpen(document2.id, "inline"),
1844
- title: labels.view,
1903
+ "data-cv-tooltip": labels.view,
1845
1904
  "aria-label": `${labels.view}: ${document2.filename}`,
1846
1905
  className: cn("cv-header-icon", classNames?.viewButton),
1847
1906
  children: /* @__PURE__ */ jsx16(Eye, { size: 14 })
@@ -1852,7 +1911,7 @@ function ConversationDocumentsPanel({
1852
1911
  {
1853
1912
  type: "button",
1854
1913
  onClick: () => void handleOpen(document2.id, "attachment"),
1855
- title: labels.download,
1914
+ "data-cv-tooltip": labels.download,
1856
1915
  "aria-label": `${labels.download}: ${document2.filename}`,
1857
1916
  className: cn("cv-header-icon", classNames?.downloadButton),
1858
1917
  children: /* @__PURE__ */ jsx16(Download, { size: 14 })
@@ -1880,7 +1939,8 @@ function ConversationDocumentsPanel({
1880
1939
  type: "button",
1881
1940
  onClick: () => setPage(page - 1),
1882
1941
  disabled: page <= 1,
1883
- "aria-label": labels.sortOldest,
1942
+ "aria-label": PAGINATION_LABELS.previous,
1943
+ "data-cv-tooltip": PAGINATION_LABELS.previous,
1884
1944
  className: "cv-header-icon disabled:opacity-40",
1885
1945
  children: "\u2039"
1886
1946
  }
@@ -1892,7 +1952,8 @@ function ConversationDocumentsPanel({
1892
1952
  type: "button",
1893
1953
  onClick: () => setPage(page + 1),
1894
1954
  disabled: page >= lastPage,
1895
- "aria-label": labels.sortMostRecent,
1955
+ "aria-label": PAGINATION_LABELS.next,
1956
+ "data-cv-tooltip": PAGINATION_LABELS.next,
1896
1957
  className: "cv-header-icon disabled:opacity-40",
1897
1958
  children: "\u203A"
1898
1959
  }
@@ -2013,6 +2074,8 @@ function DocumentsLibrary({
2013
2074
  /* @__PURE__ */ jsxs12(
2014
2075
  "button",
2015
2076
  {
2077
+ "data-cv-tooltip": sortDirection === "desc" ? labels.sortMostRecent : labels.sortOldest,
2078
+ "aria-label": sortDirection === "desc" ? labels.sortMostRecent : labels.sortOldest,
2016
2079
  type: "button",
2017
2080
  onClick: () => applyFilter(() => setSortDirection(sortDirection === "desc" ? "asc" : "desc")),
2018
2081
  className: cn("cv-header-action inline-flex items-center gap-1", classNames?.sortButton),
@@ -2025,6 +2088,8 @@ function DocumentsLibrary({
2025
2088
  hasFilters ? /* @__PURE__ */ jsx17(
2026
2089
  "button",
2027
2090
  {
2091
+ "data-cv-tooltip": labels.clearFilters,
2092
+ "aria-label": labels.clearFilters,
2028
2093
  type: "button",
2029
2094
  onClick: () => applyFilter(() => {
2030
2095
  setSearch("");
@@ -2053,7 +2118,7 @@ function DocumentsLibrary({
2053
2118
  /* @__PURE__ */ jsxs12("div", { className: "flex min-w-0 flex-1 items-center gap-3", children: [
2054
2119
  /* @__PURE__ */ jsx17(FileIcon, { filename: document2.filename, mimeType: document2.mimeType }),
2055
2120
  /* @__PURE__ */ jsxs12("div", { className: "min-w-0 flex-1", children: [
2056
- /* @__PURE__ */ jsx17("div", { className: cn("truncate text-sm font-medium", classNames?.filename), title: document2.filename, children: document2.filename }),
2121
+ /* @__PURE__ */ jsx17("div", { className: cn("truncate text-sm font-medium", classNames?.filename), "data-cv-tooltip": document2.filename, children: document2.filename }),
2057
2122
  /* @__PURE__ */ jsxs12("div", { className: cn("flex flex-wrap items-center gap-x-2 text-xs text-gray-500", classNames?.meta), children: [
2058
2123
  /* @__PURE__ */ jsxs12("span", { className: "inline-flex items-center gap-1", children: [
2059
2124
  /* @__PURE__ */ jsx17(SourceIcon, { size: 11 }),
@@ -2071,7 +2136,8 @@ function DocumentsLibrary({
2071
2136
  {
2072
2137
  type: "button",
2073
2138
  onClick: () => onOpenConversation(document2.conversationId),
2074
- title: labels.openConversation,
2139
+ "data-cv-tooltip": labels.openConversation,
2140
+ "aria-label": labels.openConversation,
2075
2141
  className: cn("cv-header-action inline-flex shrink-0 items-center gap-1", classNames?.conversationLink),
2076
2142
  children: [
2077
2143
  /* @__PURE__ */ jsx17(MessageSquare, { size: 12 }),
@@ -2085,7 +2151,7 @@ function DocumentsLibrary({
2085
2151
  {
2086
2152
  type: "button",
2087
2153
  onClick: () => void handleOpen(document2.id, "inline"),
2088
- title: labels.view,
2154
+ "data-cv-tooltip": labels.view,
2089
2155
  "aria-label": `${labels.view}: ${document2.filename}`,
2090
2156
  className: "cv-header-icon",
2091
2157
  children: /* @__PURE__ */ jsx17(Eye2, { size: 14 })
@@ -2096,7 +2162,7 @@ function DocumentsLibrary({
2096
2162
  {
2097
2163
  type: "button",
2098
2164
  onClick: () => void handleOpen(document2.id, "attachment"),
2099
- title: labels.download,
2165
+ "data-cv-tooltip": labels.download,
2100
2166
  "aria-label": `${labels.download}: ${document2.filename}`,
2101
2167
  className: "cv-header-icon",
2102
2168
  children: /* @__PURE__ */ jsx17(Download2, { size: 14 })
@@ -2114,6 +2180,8 @@ function DocumentsLibrary({
2114
2180
  /* @__PURE__ */ jsx17(
2115
2181
  "button",
2116
2182
  {
2183
+ "data-cv-tooltip": PAGINATION_LABELS.previous,
2184
+ "aria-label": PAGINATION_LABELS.previous,
2117
2185
  type: "button",
2118
2186
  onClick: () => setPage(page - 1),
2119
2187
  disabled: page <= 1,
@@ -2125,6 +2193,8 @@ function DocumentsLibrary({
2125
2193
  /* @__PURE__ */ jsx17(
2126
2194
  "button",
2127
2195
  {
2196
+ "data-cv-tooltip": PAGINATION_LABELS.next,
2197
+ "aria-label": PAGINATION_LABELS.next,
2128
2198
  type: "button",
2129
2199
  onClick: () => setPage(page + 1),
2130
2200
  disabled: page >= lastPage,
@@ -2166,6 +2236,13 @@ export {
2166
2236
  DEFAULT_AUDIO_RECORDER_BUTTON_LABELS,
2167
2237
  DEFAULT_MAX_RECORDING_MILLISECONDS,
2168
2238
  AudioRecorderButton,
2239
+ COMPOSER_BAR_CLASS,
2240
+ COMPOSER_TOOL_BUTTON_CLASS,
2241
+ COMPOSER_TOOL_BUTTON_ACTIVE_CLASS,
2242
+ COMPOSER_TOOL_BUTTON_IDLE_CLASS,
2243
+ COMPOSER_MONOSPACE_CLASS,
2244
+ COMPOSER_COMPACT_WIDTH,
2245
+ QUICK_REPLY_PILL_CLASS,
2169
2246
  applyQuickReplyVariables,
2170
2247
  resolveQuickReply,
2171
2248
  DEFAULT_MESSAGE_COMPOSER_LABELS,
@@ -2179,6 +2256,7 @@ export {
2179
2256
  conversationsOf,
2180
2257
  totalOf,
2181
2258
  useConversationDocuments,
2259
+ PAGINATION_LABELS,
2182
2260
  DOCUMENT_SOURCE_FILTER,
2183
2261
  DEFAULT_CONVERSATION_DOCUMENTS_LABELS,
2184
2262
  ConversationDocumentsPanel,
@@ -69,9 +69,12 @@ function parseInlineTokens(text) {
69
69
  if (remaining) result.push({ type: "text", content: remaining });
70
70
  break;
71
71
  }
72
- if (nextSpecial > 0) {
73
- result.push({ type: "text", content: remaining.slice(0, nextSpecial) });
72
+ if (nextSpecial === 0) {
73
+ result.push({ type: "text", content: remaining.slice(0, 1) });
74
+ remaining = remaining.slice(1);
75
+ continue;
74
76
  }
77
+ result.push({ type: "text", content: remaining.slice(0, nextSpecial) });
75
78
  remaining = remaining.slice(nextSpecial);
76
79
  }
77
80
  return result;
@@ -103,6 +106,9 @@ function unescapeHtml(text) {
103
106
  }
104
107
  var CODE_TOKEN_MARK = String.fromCharCode(57344);
105
108
  var CODE_TOKEN_REGEX = new RegExp(`${CODE_TOKEN_MARK}(\\d+)${CODE_TOKEN_MARK}`, "g");
109
+ var ZERO_WIDTH_SPACE = String.fromCharCode(8203);
110
+ var EMPTY_CODE_REGEX = new RegExp(`<code[^>]*>[${ZERO_WIDTH_SPACE}\\s]*</code>`, "gi");
111
+ var ZERO_WIDTH_SPACE_REGEX = new RegExp(ZERO_WIDTH_SPACE, "g");
106
112
  function waToHTML(text) {
107
113
  if (!text) return "";
108
114
  const codeTokens = [];
@@ -117,7 +123,7 @@ function waToHTML(text) {
117
123
  let html = escapeHtml(working);
118
124
  html = html.replace(/\*([^*\n]+)\*/g, "<strong>$1</strong>");
119
125
  html = html.replace(/_([^_\n]+)_/g, "<em>$1</em>");
120
- html = html.replace(/~([^~\n]+)~/g, "<del>$1</del>");
126
+ html = html.replace(/~([^~\n]+)~/g, "<s>$1</s>");
121
127
  html = html.replace(/\n/g, "<br>");
122
128
  html = html.replace(CODE_TOKEN_REGEX, (_match, indexStr) => {
123
129
  const token = codeTokens[Number(indexStr)];
@@ -132,6 +138,7 @@ function waToHTML(text) {
132
138
  function htmlToWA(html) {
133
139
  if (!html) return "";
134
140
  let text = html;
141
+ text = text.replace(EMPTY_CODE_REGEX, "");
135
142
  text = text.replace(/<code data-wa="block"[^>]*>([\s\S]*?)<\/code>/gi, (_match, inner) => `\`\`\`${unescapeHtml(inner.replace(/<br\s*\/?>/gi, "\n"))}\`\`\``);
136
143
  text = text.replace(/<code data-wa="inline"[^>]*>([\s\S]*?)<\/code>/gi, (_match, inner) => `\`${unescapeHtml(inner)}\``);
137
144
  text = text.replace(/<br\s*\/?>/gi, "\n").replace(/<div>/gi, "\n").replace(/<\/div>/gi, "").replace(/<\/p>/gi, "\n").replace(/<p[^>]*>/gi, "");
@@ -141,9 +148,11 @@ function htmlToWA(html) {
141
148
  text = text.replace(/<i>(.*?)<\/i>/gi, "_$1_");
142
149
  text = text.replace(/<del>(.*?)<\/del>/gi, "~$1~");
143
150
  text = text.replace(/<s>(.*?)<\/s>/gi, "~$1~");
151
+ text = text.replace(/<strike>(.*?)<\/strike>/gi, "~$1~");
144
152
  text = text.replace(/<code[^>]*>(.*?)<\/code>/gi, "`$1`");
145
153
  text = text.replace(/<[^>]+>/g, "");
146
154
  text = unescapeHtml(text);
155
+ text = text.replace(ZERO_WIDTH_SPACE_REGEX, "");
147
156
  return text.trim();
148
157
  }
149
158
  function waToHTMLInline(text) {
@@ -205,6 +214,7 @@ function useDarkMode() {
205
214
 
206
215
  export {
207
216
  parseWhatsAppFormatting,
217
+ ZERO_WIDTH_SPACE,
208
218
  waToHTML,
209
219
  htmlToWA,
210
220
  waToHTMLInline,
@@ -85,6 +85,8 @@ interface FlowEditorLabels {
85
85
  question: string;
86
86
  options: string;
87
87
  addOption: string;
88
+ removeOption: string;
89
+ close: string;
88
90
  optionId: string;
89
91
  optionLabel: string;
90
92
  next: string;