@pocketping/widget 1.1.0 → 1.3.0

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.
package/dist/index.js CHANGED
@@ -190,6 +190,12 @@ function styles(primaryColor, theme) {
190
190
  border-radius: 4px;
191
191
  opacity: 0.8;
192
192
  transition: opacity 0.2s;
193
+ flex-shrink: 0;
194
+ width: 28px;
195
+ height: 28px;
196
+ display: flex;
197
+ align-items: center;
198
+ justify-content: center;
193
199
  }
194
200
 
195
201
  .pp-close-btn:hover {
@@ -197,8 +203,8 @@ function styles(primaryColor, theme) {
197
203
  }
198
204
 
199
205
  .pp-close-btn svg {
200
- width: 20px;
201
- height: 20px;
206
+ width: 16px;
207
+ height: 16px;
202
208
  }
203
209
 
204
210
  .pp-messages {
@@ -222,6 +228,94 @@ function styles(primaryColor, theme) {
222
228
  padding: 10px 14px;
223
229
  border-radius: 16px;
224
230
  word-wrap: break-word;
231
+ position: relative;
232
+ user-select: text;
233
+ -webkit-user-select: text;
234
+ }
235
+
236
+ /* Hover actions container - positioned above message (Slack style) */
237
+ .pp-message-actions {
238
+ position: absolute;
239
+ top: -28px;
240
+ display: flex;
241
+ gap: 2px;
242
+ background: ${colors.bg};
243
+ border: 1px solid ${colors.border};
244
+ border-radius: 6px;
245
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
246
+ padding: 2px;
247
+ opacity: 0;
248
+ animation: pp-actions-fade-in 0.12s ease forwards;
249
+ z-index: 10;
250
+ /* Reset color inheritance from message */
251
+ color: ${colors.textSecondary};
252
+ }
253
+
254
+ @keyframes pp-actions-fade-in {
255
+ from { opacity: 0; transform: translateY(4px); }
256
+ to { opacity: 1; transform: translateY(0); }
257
+ }
258
+
259
+ /* Visitor messages: actions aligned right */
260
+ .pp-actions-left {
261
+ right: 0;
262
+ }
263
+
264
+ /* Operator messages: actions aligned left */
265
+ .pp-actions-right {
266
+ left: 0;
267
+ }
268
+
269
+ .pp-message-actions .pp-action-btn {
270
+ width: 24px;
271
+ height: 24px;
272
+ border: none;
273
+ background: transparent;
274
+ border-radius: 4px;
275
+ cursor: pointer;
276
+ display: flex;
277
+ align-items: center;
278
+ justify-content: center;
279
+ color: ${colors.textSecondary} !important;
280
+ transition: background 0.1s, color 0.1s;
281
+ }
282
+
283
+ .pp-message-actions .pp-action-btn:hover {
284
+ background: ${colors.bgSecondary};
285
+ color: ${colors.text} !important;
286
+ }
287
+
288
+ .pp-message-actions .pp-action-btn svg {
289
+ width: 14px;
290
+ height: 14px;
291
+ stroke: ${colors.textSecondary};
292
+ }
293
+
294
+ .pp-message-actions .pp-action-btn:hover svg {
295
+ stroke: ${colors.text};
296
+ }
297
+
298
+ .pp-message-actions .pp-action-delete:hover {
299
+ background: #fef2f2;
300
+ }
301
+
302
+ .pp-message-actions .pp-action-delete:hover svg {
303
+ stroke: #ef4444;
304
+ }
305
+
306
+ .pp-theme-dark .pp-message-actions .pp-action-delete:hover {
307
+ background: #7f1d1d;
308
+ }
309
+
310
+ .pp-theme-dark .pp-message-actions .pp-action-delete:hover svg {
311
+ stroke: #fca5a5;
312
+ }
313
+
314
+ /* Hide hover actions on mobile */
315
+ @media (hover: none) and (pointer: coarse) {
316
+ .pp-message-actions {
317
+ display: none;
318
+ }
225
319
  }
226
320
 
227
321
  .pp-message-visitor {
@@ -305,72 +399,565 @@ function styles(primaryColor, theme) {
305
399
  40% { transform: scale(1); }
306
400
  }
307
401
 
308
- .pp-input-form {
309
- display: flex;
310
- padding: 12px;
311
- gap: 8px;
312
- border-top: 1px solid ${colors.border};
402
+ .pp-input-form {
403
+ display: flex;
404
+ padding: 12px;
405
+ gap: 8px;
406
+ border-top: 1px solid ${colors.border};
407
+ }
408
+
409
+ .pp-input {
410
+ flex: 1;
411
+ padding: 10px 14px;
412
+ border: 1px solid ${colors.border};
413
+ border-radius: 20px;
414
+ background: ${colors.bg};
415
+ color: ${colors.text};
416
+ font-size: 14px;
417
+ outline: none;
418
+ transition: border-color 0.2s;
419
+ }
420
+
421
+ .pp-input:focus {
422
+ border-color: ${primaryColor};
423
+ }
424
+
425
+ .pp-input::placeholder {
426
+ color: ${colors.textSecondary};
427
+ }
428
+
429
+ .pp-send-btn {
430
+ width: 40px;
431
+ height: 40px;
432
+ border-radius: 50%;
433
+ background: ${primaryColor};
434
+ color: white;
435
+ border: none;
436
+ cursor: pointer;
437
+ display: flex;
438
+ align-items: center;
439
+ justify-content: center;
440
+ transition: opacity 0.2s;
441
+ }
442
+
443
+ .pp-send-btn:disabled {
444
+ opacity: 0.5;
445
+ cursor: not-allowed;
446
+ }
447
+
448
+ .pp-send-btn svg {
449
+ width: 18px;
450
+ height: 18px;
451
+ }
452
+
453
+ .pp-footer {
454
+ text-align: center;
455
+ padding: 8px;
456
+ font-size: 11px;
457
+ color: ${colors.textSecondary};
458
+ border-top: 1px solid ${colors.border};
459
+ }
460
+
461
+ .pp-footer a {
462
+ color: ${primaryColor};
463
+ text-decoration: none;
464
+ }
465
+
466
+ .pp-footer a:hover {
467
+ text-decoration: underline;
468
+ }
469
+
470
+ /* Attachment Styles */
471
+ .pp-file-input {
472
+ /* Use offscreen positioning instead of display:none for better browser compatibility */
473
+ position: absolute;
474
+ width: 1px;
475
+ height: 1px;
476
+ padding: 0;
477
+ margin: -1px;
478
+ overflow: hidden;
479
+ clip: rect(0, 0, 0, 0);
480
+ white-space: nowrap;
481
+ border: 0;
482
+ }
483
+
484
+ .pp-attach-btn {
485
+ width: 40px;
486
+ height: 40px;
487
+ border-radius: 50%;
488
+ background: transparent;
489
+ color: ${colors.textSecondary};
490
+ border: 1px solid ${colors.border};
491
+ cursor: pointer;
492
+ display: flex;
493
+ align-items: center;
494
+ justify-content: center;
495
+ transition: color 0.2s, border-color 0.2s;
496
+ flex-shrink: 0;
497
+ }
498
+
499
+ .pp-attach-btn:hover:not(:disabled) {
500
+ color: ${primaryColor};
501
+ border-color: ${primaryColor};
502
+ }
503
+
504
+ .pp-attach-btn:disabled {
505
+ opacity: 0.5;
506
+ cursor: not-allowed;
507
+ }
508
+
509
+ .pp-attach-btn svg {
510
+ width: 18px;
511
+ height: 18px;
512
+ }
513
+
514
+ .pp-attachments-preview {
515
+ display: flex;
516
+ gap: 8px;
517
+ padding: 8px 12px;
518
+ border-top: 1px solid ${colors.border};
519
+ overflow-x: auto;
520
+ background: ${colors.bgSecondary};
521
+ }
522
+
523
+ .pp-attachment-preview {
524
+ position: relative;
525
+ width: 60px;
526
+ height: 60px;
527
+ border-radius: 8px;
528
+ overflow: hidden;
529
+ flex-shrink: 0;
530
+ background: ${colors.bg};
531
+ border: 1px solid ${colors.border};
532
+ }
533
+
534
+ .pp-preview-img {
535
+ width: 100%;
536
+ height: 100%;
537
+ object-fit: cover;
538
+ }
539
+
540
+ .pp-preview-file {
541
+ width: 100%;
542
+ height: 100%;
543
+ display: flex;
544
+ align-items: center;
545
+ justify-content: center;
546
+ color: ${colors.textSecondary};
547
+ }
548
+
549
+ .pp-preview-file svg {
550
+ width: 24px;
551
+ height: 24px;
552
+ }
553
+
554
+ .pp-remove-attachment {
555
+ position: absolute;
556
+ top: 2px;
557
+ right: 2px;
558
+ width: 18px;
559
+ height: 18px;
560
+ border-radius: 50%;
561
+ background: rgba(0, 0, 0, 0.6);
562
+ color: white;
563
+ border: none;
564
+ cursor: pointer;
565
+ display: flex;
566
+ align-items: center;
567
+ justify-content: center;
568
+ padding: 0;
569
+ }
570
+
571
+ .pp-remove-attachment svg {
572
+ width: 10px;
573
+ height: 10px;
574
+ }
575
+
576
+ .pp-upload-progress {
577
+ position: absolute;
578
+ bottom: 0;
579
+ left: 0;
580
+ height: 3px;
581
+ background: ${primaryColor};
582
+ transition: width 0.1s;
583
+ }
584
+
585
+ .pp-upload-error {
586
+ position: absolute;
587
+ top: 50%;
588
+ left: 50%;
589
+ transform: translate(-50%, -50%);
590
+ width: 24px;
591
+ height: 24px;
592
+ border-radius: 50%;
593
+ background: #ef4444;
594
+ color: white;
595
+ font-weight: bold;
596
+ display: flex;
597
+ align-items: center;
598
+ justify-content: center;
599
+ font-size: 14px;
600
+ }
601
+
602
+ .pp-attachment-uploading {
603
+ opacity: 0.7;
604
+ }
605
+
606
+ .pp-attachment-error {
607
+ border-color: #ef4444;
608
+ }
609
+
610
+ /* Message Attachments */
611
+ .pp-message-attachments {
612
+ display: flex;
613
+ flex-direction: column;
614
+ gap: 8px;
615
+ margin-top: 4px;
616
+ }
617
+
618
+ .pp-attachment {
619
+ display: block;
620
+ text-decoration: none;
621
+ color: inherit;
622
+ border-radius: 8px;
623
+ overflow: hidden;
624
+ }
625
+
626
+ .pp-attachment-image img {
627
+ max-width: 200px;
628
+ max-height: 200px;
629
+ border-radius: 8px;
630
+ display: block;
631
+ }
632
+
633
+ .pp-attachment-audio {
634
+ display: flex;
635
+ flex-direction: column;
636
+ gap: 4px;
637
+ }
638
+
639
+ .pp-attachment-audio audio {
640
+ width: 200px;
641
+ height: 36px;
642
+ }
643
+
644
+ .pp-attachment-audio .pp-attachment-name {
645
+ font-size: 11px;
646
+ opacity: 0.7;
647
+ white-space: nowrap;
648
+ overflow: hidden;
649
+ text-overflow: ellipsis;
650
+ max-width: 200px;
651
+ }
652
+
653
+ .pp-attachment-video video {
654
+ max-width: 200px;
655
+ max-height: 200px;
656
+ border-radius: 8px;
657
+ display: block;
658
+ }
659
+
660
+ .pp-attachment-file {
661
+ display: flex;
662
+ align-items: center;
663
+ gap: 8px;
664
+ padding: 8px 12px;
665
+ background: ${isDark ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.05)"};
666
+ border-radius: 8px;
667
+ transition: background 0.2s;
668
+ }
669
+
670
+ .pp-attachment-file:hover {
671
+ background: ${isDark ? "rgba(255,255,255,0.15)" : "rgba(0,0,0,0.08)"};
672
+ }
673
+
674
+ .pp-attachment-file svg {
675
+ width: 24px;
676
+ height: 24px;
677
+ flex-shrink: 0;
678
+ }
679
+
680
+ .pp-attachment-info {
681
+ display: flex;
682
+ flex-direction: column;
683
+ min-width: 0;
684
+ }
685
+
686
+ .pp-attachment-name {
687
+ font-size: 13px;
688
+ font-weight: 500;
689
+ white-space: nowrap;
690
+ overflow: hidden;
691
+ text-overflow: ellipsis;
692
+ }
693
+
694
+ .pp-attachment-size {
695
+ font-size: 11px;
696
+ opacity: 0.7;
697
+ }
698
+
699
+ /* Drag & Drop */
700
+ .pp-dragging {
701
+ position: relative;
702
+ }
703
+
704
+ .pp-drop-overlay {
705
+ position: absolute;
706
+ inset: 0;
707
+ background: ${isDark ? "rgba(0,0,0,0.9)" : "rgba(255,255,255,0.95)"};
708
+ display: flex;
709
+ flex-direction: column;
710
+ align-items: center;
711
+ justify-content: center;
712
+ gap: 12px;
713
+ z-index: 100;
714
+ border: 3px dashed ${primaryColor};
715
+ border-radius: 16px;
716
+ margin: 4px;
717
+ pointer-events: none;
718
+ }
719
+
720
+ .pp-drop-icon svg {
721
+ width: 48px;
722
+ height: 48px;
723
+ color: ${primaryColor};
724
+ }
725
+
726
+ .pp-drop-text {
727
+ font-size: 16px;
728
+ font-weight: 500;
729
+ color: ${colors.text};
730
+ }
731
+
732
+ /* Message Context Menu */
733
+ .pp-message-menu {
734
+ position: fixed;
735
+ background: ${colors.bg};
736
+ border: 1px solid ${colors.border};
737
+ border-radius: 8px;
738
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
739
+ padding: 4px;
740
+ z-index: 200;
741
+ min-width: 120px;
742
+ }
743
+
744
+ .pp-message-menu button {
745
+ display: flex;
746
+ align-items: center;
747
+ gap: 8px;
748
+ width: 100%;
749
+ padding: 8px 12px;
750
+ border: none;
751
+ background: transparent;
752
+ color: ${colors.text};
753
+ font-size: 13px;
754
+ cursor: pointer;
755
+ border-radius: 4px;
756
+ text-align: left;
757
+ }
758
+
759
+ .pp-message-menu button:hover {
760
+ background: ${isDark ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.05)"};
761
+ }
762
+
763
+ .pp-message-menu button svg {
764
+ width: 16px;
765
+ height: 16px;
766
+ }
767
+
768
+ .pp-menu-delete {
769
+ color: #ef4444 !important;
770
+ }
771
+
772
+ /* Edit Modal */
773
+ .pp-edit-modal {
774
+ position: absolute;
775
+ bottom: 80px;
776
+ left: 12px;
777
+ right: 12px;
778
+ background: ${colors.bg};
779
+ border: 1px solid ${colors.border};
780
+ border-radius: 12px;
781
+ box-shadow: 0 4px 20px rgba(0,0,0,0.15);
782
+ z-index: 150;
783
+ overflow: hidden;
784
+ }
785
+
786
+ .pp-edit-header {
787
+ display: flex;
788
+ justify-content: space-between;
789
+ align-items: center;
790
+ padding: 12px 16px;
791
+ border-bottom: 1px solid ${colors.border};
792
+ font-weight: 500;
793
+ }
794
+
795
+ .pp-edit-header button {
796
+ background: transparent;
797
+ border: none;
798
+ color: ${colors.textSecondary};
799
+ cursor: pointer;
800
+ padding: 4px;
801
+ }
802
+
803
+ .pp-edit-header button svg {
804
+ width: 18px;
805
+ height: 18px;
806
+ }
807
+
808
+ .pp-edit-input {
809
+ width: 100%;
810
+ padding: 12px 16px;
811
+ border: none;
812
+ background: transparent;
813
+ color: ${colors.text};
814
+ font-size: 14px;
815
+ resize: none;
816
+ min-height: 80px;
817
+ outline: none;
818
+ }
819
+
820
+ .pp-edit-actions {
821
+ display: flex;
822
+ justify-content: flex-end;
823
+ gap: 8px;
824
+ padding: 12px 16px;
825
+ border-top: 1px solid ${colors.border};
826
+ }
827
+
828
+ .pp-edit-cancel {
829
+ padding: 8px 16px;
830
+ border: 1px solid ${colors.border};
831
+ border-radius: 6px;
832
+ background: transparent;
833
+ color: ${colors.text};
834
+ font-size: 13px;
835
+ cursor: pointer;
836
+ }
837
+
838
+ .pp-edit-save {
839
+ padding: 8px 16px;
840
+ border: none;
841
+ border-radius: 6px;
842
+ background: ${primaryColor};
843
+ color: white;
844
+ font-size: 13px;
845
+ cursor: pointer;
846
+ }
847
+
848
+ .pp-edit-save:disabled {
849
+ opacity: 0.5;
850
+ cursor: not-allowed;
851
+ }
852
+
853
+ /* Reply Preview */
854
+ .pp-reply-preview {
855
+ display: flex;
856
+ align-items: center;
857
+ gap: 8px;
858
+ padding: 8px 12px;
859
+ background: ${isDark ? "rgba(255,255,255,0.05)" : "rgba(0,0,0,0.03)"};
860
+ border-top: 1px solid ${colors.border};
861
+ border-left: 3px solid ${primaryColor};
862
+ }
863
+
864
+ .pp-reply-preview-content {
865
+ flex: 1;
866
+ min-width: 0;
867
+ }
868
+
869
+ .pp-reply-label {
870
+ display: block;
871
+ font-size: 11px;
872
+ color: ${primaryColor};
873
+ font-weight: 500;
874
+ margin-bottom: 2px;
875
+ }
876
+
877
+ .pp-reply-text {
878
+ display: block;
879
+ font-size: 12px;
880
+ color: ${colors.textSecondary};
881
+ white-space: nowrap;
882
+ overflow: hidden;
883
+ text-overflow: ellipsis;
884
+ }
885
+
886
+ .pp-reply-cancel {
887
+ background: transparent;
888
+ border: none;
889
+ color: ${colors.textSecondary};
890
+ cursor: pointer;
891
+ padding: 4px;
892
+ flex-shrink: 0;
893
+ }
894
+
895
+ .pp-reply-cancel svg {
896
+ width: 16px;
897
+ height: 16px;
313
898
  }
314
899
 
315
- .pp-input {
316
- flex: 1;
317
- padding: 10px 14px;
318
- border: 1px solid ${colors.border};
319
- border-radius: 20px;
320
- background: ${colors.bg};
321
- color: ${colors.text};
322
- font-size: 14px;
323
- outline: none;
324
- transition: border-color 0.2s;
900
+ /* Reply Quote in Message */
901
+ .pp-reply-quote {
902
+ background: ${isDark ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.05)"};
903
+ border-left: 2px solid ${primaryColor};
904
+ padding: 4px 8px;
905
+ margin-bottom: 6px;
906
+ border-radius: 0 4px 4px 0;
907
+ font-size: 12px;
908
+ position: relative;
909
+ z-index: 1;
325
910
  }
326
911
 
327
- .pp-input:focus {
328
- border-color: ${primaryColor};
912
+ .pp-reply-sender {
913
+ display: block;
914
+ font-weight: 500;
915
+ color: ${primaryColor};
916
+ margin-bottom: 2px;
329
917
  }
330
918
 
331
- .pp-input::placeholder {
919
+ .pp-reply-content {
920
+ display: block;
332
921
  color: ${colors.textSecondary};
922
+ white-space: nowrap;
923
+ overflow: hidden;
924
+ text-overflow: ellipsis;
333
925
  }
334
926
 
335
- .pp-send-btn {
336
- width: 40px;
337
- height: 40px;
338
- border-radius: 50%;
339
- background: ${primaryColor};
340
- color: white;
341
- border: none;
342
- cursor: pointer;
343
- display: flex;
344
- align-items: center;
345
- justify-content: center;
346
- transition: opacity 0.2s;
927
+ /* Reply quote in visitor message bubble needs higher contrast */
928
+ .pp-message-visitor .pp-reply-quote {
929
+ background: rgba(255, 255, 255, 0.18);
930
+ border-left-color: rgba(255, 255, 255, 0.7);
347
931
  }
348
932
 
349
- .pp-send-btn:disabled {
350
- opacity: 0.5;
351
- cursor: not-allowed;
933
+ .pp-message-visitor .pp-reply-sender,
934
+ .pp-message-visitor .pp-reply-content {
935
+ color: rgba(255, 255, 255, 0.9);
352
936
  }
353
937
 
354
- .pp-send-btn svg {
355
- width: 18px;
356
- height: 18px;
938
+ /* Deleted Message */
939
+ .pp-message-deleted {
940
+ opacity: 0.6;
357
941
  }
358
942
 
359
- .pp-footer {
360
- text-align: center;
361
- padding: 8px;
362
- font-size: 11px;
943
+ .pp-deleted-content {
944
+ font-style: italic;
363
945
  color: ${colors.textSecondary};
364
- border-top: 1px solid ${colors.border};
946
+ display: flex;
947
+ align-items: center;
948
+ gap: 4px;
365
949
  }
366
950
 
367
- .pp-footer a {
368
- color: ${primaryColor};
369
- text-decoration: none;
951
+ .pp-deleted-icon {
952
+ font-size: 12px;
370
953
  }
371
954
 
372
- .pp-footer a:hover {
373
- text-decoration: underline;
955
+ /* Edited Badge */
956
+ .pp-edited-badge {
957
+ font-size: 10px;
958
+ color: ${colors.textSecondary};
959
+ margin-left: 4px;
960
+ font-style: italic;
374
961
  }
375
962
  `;
376
963
  }
@@ -385,9 +972,20 @@ function ChatWidget({ client: client2, config: initialConfig }) {
385
972
  const [operatorOnline, setOperatorOnline] = useState(false);
386
973
  const [isConnected, setIsConnected] = useState(false);
387
974
  const [unreadCount, setUnreadCount] = useState(0);
975
+ const [pendingAttachments, setPendingAttachments] = useState([]);
976
+ const [isUploading, setIsUploading] = useState(false);
977
+ const [replyingTo, setReplyingTo] = useState(null);
978
+ const [editingMessage, setEditingMessage] = useState(null);
979
+ const [editContent, setEditContent] = useState("");
980
+ const [messageMenu, setMessageMenu] = useState(null);
981
+ const [isDragging, setIsDragging] = useState(false);
982
+ const [hoveredMessageId, setHoveredMessageId] = useState(null);
983
+ const [longPressTimer, setLongPressTimer] = useState(null);
388
984
  const [config, setConfig] = useState(initialConfig);
389
985
  const messagesEndRef = useRef(null);
390
986
  const inputRef = useRef(null);
987
+ const fileInputRef = useRef(null);
988
+ const messagesContainerRef = useRef(null);
391
989
  useEffect(() => {
392
990
  const unsubOpen = client2.on("openChange", setIsOpen);
393
991
  const unsubMessage = client2.on("message", () => {
@@ -484,11 +1082,17 @@ function ChatWidget({ client: client2, config: initialConfig }) {
484
1082
  if (!shouldShow) return null;
485
1083
  const handleSubmit = async (e) => {
486
1084
  e.preventDefault();
487
- if (!inputValue.trim()) return;
1085
+ const hasContent = inputValue.trim().length > 0;
1086
+ const readyAttachments = pendingAttachments.filter((a) => a.status === "ready" && a.attachment);
1087
+ if (!hasContent && readyAttachments.length === 0) return;
488
1088
  const content = inputValue;
1089
+ const attachmentIds = readyAttachments.map((a) => a.attachment.id);
1090
+ const replyToId = replyingTo?.id;
489
1091
  setInputValue("");
1092
+ setPendingAttachments([]);
1093
+ setReplyingTo(null);
490
1094
  try {
491
- await client2.sendMessage(content);
1095
+ await client2.sendMessage(content, attachmentIds, replyToId);
492
1096
  } catch (err) {
493
1097
  console.error("[PocketPing] Failed to send message:", err);
494
1098
  }
@@ -498,9 +1102,213 @@ function ChatWidget({ client: client2, config: initialConfig }) {
498
1102
  setInputValue(target.value);
499
1103
  client2.sendTyping(true);
500
1104
  };
1105
+ const handleFileSelect = async (e) => {
1106
+ const target = e.target;
1107
+ const files = target.files;
1108
+ if (!files || files.length === 0) return;
1109
+ const newPending = [];
1110
+ for (let i = 0; i < files.length; i++) {
1111
+ const file = files[i];
1112
+ const id = `pending-${Date.now()}-${i}`;
1113
+ let preview;
1114
+ if (file.type.startsWith("image/")) {
1115
+ preview = URL.createObjectURL(file);
1116
+ }
1117
+ newPending.push({
1118
+ id,
1119
+ file,
1120
+ preview,
1121
+ progress: 0,
1122
+ status: "pending"
1123
+ });
1124
+ }
1125
+ setPendingAttachments((prev) => [...prev, ...newPending]);
1126
+ target.value = "";
1127
+ setIsUploading(true);
1128
+ for (const pending of newPending) {
1129
+ try {
1130
+ setPendingAttachments(
1131
+ (prev) => prev.map((a) => a.id === pending.id ? { ...a, status: "uploading" } : a)
1132
+ );
1133
+ const attachment = await client2.uploadFile(pending.file, (progress) => {
1134
+ setPendingAttachments(
1135
+ (prev) => prev.map((a) => a.id === pending.id ? { ...a, progress } : a)
1136
+ );
1137
+ });
1138
+ setPendingAttachments(
1139
+ (prev) => prev.map(
1140
+ (a) => a.id === pending.id ? { ...a, status: "ready", progress: 100, attachment } : a
1141
+ )
1142
+ );
1143
+ } catch (err) {
1144
+ console.error("[PocketPing] Failed to upload file:", err);
1145
+ setPendingAttachments(
1146
+ (prev) => prev.map(
1147
+ (a) => a.id === pending.id ? { ...a, status: "error", error: "Upload failed" } : a
1148
+ )
1149
+ );
1150
+ }
1151
+ }
1152
+ setIsUploading(false);
1153
+ };
1154
+ const handleRemoveAttachment = (id) => {
1155
+ setPendingAttachments((prev) => {
1156
+ const removed = prev.find((a) => a.id === id);
1157
+ if (removed?.preview) {
1158
+ URL.revokeObjectURL(removed.preview);
1159
+ }
1160
+ return prev.filter((a) => a.id !== id);
1161
+ });
1162
+ };
1163
+ const handleReply = (message) => {
1164
+ setReplyingTo(message);
1165
+ setMessageMenu(null);
1166
+ inputRef.current?.focus();
1167
+ };
1168
+ const handleCancelReply = () => {
1169
+ setReplyingTo(null);
1170
+ };
1171
+ const handleStartEdit = (message) => {
1172
+ if (message.sender !== "visitor") return;
1173
+ setEditingMessage(message);
1174
+ setEditContent(message.content);
1175
+ setMessageMenu(null);
1176
+ };
1177
+ const handleCancelEdit = () => {
1178
+ setEditingMessage(null);
1179
+ setEditContent("");
1180
+ };
1181
+ const handleSaveEdit = async () => {
1182
+ if (!editingMessage || !editContent.trim()) return;
1183
+ try {
1184
+ await client2.editMessage(editingMessage.id, editContent.trim());
1185
+ setEditingMessage(null);
1186
+ setEditContent("");
1187
+ } catch (err) {
1188
+ console.error("[PocketPing] Failed to edit message:", err);
1189
+ }
1190
+ };
1191
+ const handleDelete = async (message) => {
1192
+ if (message.sender !== "visitor") return;
1193
+ setMessageMenu(null);
1194
+ if (confirm("Delete this message?")) {
1195
+ try {
1196
+ await client2.deleteMessage(message.id);
1197
+ } catch (err) {
1198
+ console.error("[PocketPing] Failed to delete message:", err);
1199
+ }
1200
+ }
1201
+ };
1202
+ const handleMessageContextMenu = (e, message) => {
1203
+ e.preventDefault();
1204
+ const mouseEvent = e;
1205
+ setMessageMenu({
1206
+ message,
1207
+ x: mouseEvent.clientX,
1208
+ y: mouseEvent.clientY
1209
+ });
1210
+ };
1211
+ const handleTouchStart = (message) => {
1212
+ const timer = setTimeout(() => {
1213
+ if (navigator.vibrate) navigator.vibrate(50);
1214
+ setMessageMenu({
1215
+ message,
1216
+ x: window.innerWidth / 2 - 60,
1217
+ // Center horizontally
1218
+ y: window.innerHeight / 2 - 50
1219
+ // Center vertically
1220
+ });
1221
+ }, 500);
1222
+ setLongPressTimer(timer);
1223
+ };
1224
+ const handleTouchEnd = () => {
1225
+ if (longPressTimer) {
1226
+ clearTimeout(longPressTimer);
1227
+ setLongPressTimer(null);
1228
+ }
1229
+ };
1230
+ useEffect(() => {
1231
+ if (!messageMenu) return;
1232
+ const handleClickOutside = () => setMessageMenu(null);
1233
+ document.addEventListener("click", handleClickOutside);
1234
+ return () => document.removeEventListener("click", handleClickOutside);
1235
+ }, [messageMenu]);
1236
+ const dragCounterRef = useRef(0);
1237
+ const handleDragEnter = (e) => {
1238
+ e.preventDefault();
1239
+ e.stopPropagation();
1240
+ dragCounterRef.current++;
1241
+ if (dragCounterRef.current === 1) {
1242
+ setIsDragging(true);
1243
+ }
1244
+ };
1245
+ const handleDragOver = (e) => {
1246
+ e.preventDefault();
1247
+ e.stopPropagation();
1248
+ };
1249
+ const handleDragLeave = (e) => {
1250
+ e.preventDefault();
1251
+ e.stopPropagation();
1252
+ dragCounterRef.current--;
1253
+ if (dragCounterRef.current === 0) {
1254
+ setIsDragging(false);
1255
+ }
1256
+ };
1257
+ const handleDrop = async (e) => {
1258
+ e.preventDefault();
1259
+ e.stopPropagation();
1260
+ dragCounterRef.current = 0;
1261
+ setIsDragging(false);
1262
+ const files = e.dataTransfer?.files;
1263
+ if (!files || files.length === 0) return;
1264
+ const newPending = [];
1265
+ for (let i = 0; i < files.length; i++) {
1266
+ const file = files[i];
1267
+ const id = `pending-${Date.now()}-${i}`;
1268
+ let preview;
1269
+ if (file.type.startsWith("image/")) {
1270
+ preview = URL.createObjectURL(file);
1271
+ }
1272
+ newPending.push({
1273
+ id,
1274
+ file,
1275
+ preview,
1276
+ progress: 0,
1277
+ status: "pending"
1278
+ });
1279
+ }
1280
+ setPendingAttachments((prev) => [...prev, ...newPending]);
1281
+ setIsUploading(true);
1282
+ for (const pending of newPending) {
1283
+ try {
1284
+ setPendingAttachments(
1285
+ (prev) => prev.map((a) => a.id === pending.id ? { ...a, status: "uploading" } : a)
1286
+ );
1287
+ const attachment = await client2.uploadFile(pending.file, (progress) => {
1288
+ setPendingAttachments(
1289
+ (prev) => prev.map((a) => a.id === pending.id ? { ...a, progress } : a)
1290
+ );
1291
+ });
1292
+ setPendingAttachments(
1293
+ (prev) => prev.map(
1294
+ (a) => a.id === pending.id ? { ...a, status: "ready", progress: 100, attachment } : a
1295
+ )
1296
+ );
1297
+ } catch (err) {
1298
+ console.error("[PocketPing] Failed to upload dropped file:", err);
1299
+ setPendingAttachments(
1300
+ (prev) => prev.map(
1301
+ (a) => a.id === pending.id ? { ...a, status: "error", error: "Upload failed" } : a
1302
+ )
1303
+ );
1304
+ }
1305
+ }
1306
+ setIsUploading(false);
1307
+ };
501
1308
  const position = config.position ?? "bottom-right";
502
1309
  const theme = getTheme(config.theme ?? "auto");
503
1310
  const primaryColor = config.primaryColor ?? "#6366f1";
1311
+ const actionIconColor = theme === "dark" ? "#9ca3af" : "#6b7280";
504
1312
  return /* @__PURE__ */ jsxs(Fragment, { children: [
505
1313
  /* @__PURE__ */ jsx("style", { children: styles(primaryColor, theme) }),
506
1314
  /* @__PURE__ */ jsxs(
@@ -516,84 +1324,263 @@ function ChatWidget({ client: client2, config: initialConfig }) {
516
1324
  ]
517
1325
  }
518
1326
  ),
519
- isOpen && /* @__PURE__ */ jsxs("div", { class: `pp-window pp-${position} pp-theme-${theme}`, children: [
520
- /* @__PURE__ */ jsxs("div", { class: "pp-header", children: [
521
- /* @__PURE__ */ jsxs("div", { class: "pp-header-info", children: [
522
- config.operatorAvatar && /* @__PURE__ */ jsx("img", { src: config.operatorAvatar, alt: "", class: "pp-avatar" }),
523
- /* @__PURE__ */ jsxs("div", { children: [
524
- /* @__PURE__ */ jsx("div", { class: "pp-header-title", children: config.operatorName ?? "Support" }),
525
- /* @__PURE__ */ jsx("div", { class: "pp-header-status", children: operatorOnline ? /* @__PURE__ */ jsxs(Fragment2, { children: [
526
- /* @__PURE__ */ jsx("span", { class: "pp-status-dot pp-online" }),
527
- " Online"
528
- ] }) : /* @__PURE__ */ jsxs(Fragment2, { children: [
529
- /* @__PURE__ */ jsx("span", { class: "pp-status-dot" }),
530
- " Away"
531
- ] }) })
532
- ] })
533
- ] }),
534
- /* @__PURE__ */ jsx(
535
- "button",
536
- {
537
- class: "pp-close-btn",
538
- onClick: () => client2.setOpen(false),
539
- "aria-label": "Close chat",
540
- children: /* @__PURE__ */ jsx(CloseIcon, {})
541
- }
542
- )
543
- ] }),
544
- /* @__PURE__ */ jsxs("div", { class: "pp-messages", children: [
545
- config.welcomeMessage && messages.length === 0 && /* @__PURE__ */ jsx("div", { class: "pp-welcome", children: config.welcomeMessage }),
546
- messages.map((msg) => /* @__PURE__ */ jsxs(
547
- "div",
548
- {
549
- class: `pp-message pp-message-${msg.sender}`,
550
- children: [
551
- /* @__PURE__ */ jsx("div", { class: "pp-message-content", children: msg.content }),
552
- /* @__PURE__ */ jsxs("div", { class: "pp-message-time", children: [
553
- formatTime(msg.timestamp),
554
- msg.sender === "ai" && /* @__PURE__ */ jsx("span", { class: "pp-ai-badge", children: "AI" }),
555
- msg.sender === "visitor" && /* @__PURE__ */ jsx("span", { class: `pp-status pp-status-${msg.status ?? "sent"}`, children: /* @__PURE__ */ jsx(StatusIcon, { status: msg.status }) })
1327
+ isOpen && /* @__PURE__ */ jsxs(
1328
+ "div",
1329
+ {
1330
+ class: `pp-window pp-${position} pp-theme-${theme} ${isDragging ? "pp-dragging" : ""}`,
1331
+ onDragEnter: handleDragEnter,
1332
+ onDragOver: handleDragOver,
1333
+ onDragLeave: handleDragLeave,
1334
+ onDrop: handleDrop,
1335
+ children: [
1336
+ isDragging && /* @__PURE__ */ jsxs("div", { class: "pp-drop-overlay", children: [
1337
+ /* @__PURE__ */ jsx("div", { class: "pp-drop-icon", children: /* @__PURE__ */ jsx(AttachIcon, {}) }),
1338
+ /* @__PURE__ */ jsx("div", { class: "pp-drop-text", children: "Drop files to upload" })
1339
+ ] }),
1340
+ /* @__PURE__ */ jsxs("div", { class: "pp-header", children: [
1341
+ /* @__PURE__ */ jsxs("div", { class: "pp-header-info", children: [
1342
+ config.operatorAvatar && /* @__PURE__ */ jsx("img", { src: config.operatorAvatar, alt: "", class: "pp-avatar" }),
1343
+ /* @__PURE__ */ jsxs("div", { children: [
1344
+ /* @__PURE__ */ jsx("div", { class: "pp-header-title", children: config.operatorName ?? "Support" }),
1345
+ /* @__PURE__ */ jsx("div", { class: "pp-header-status", children: operatorOnline ? /* @__PURE__ */ jsxs(Fragment2, { children: [
1346
+ /* @__PURE__ */ jsx("span", { class: "pp-status-dot pp-online" }),
1347
+ " Online"
1348
+ ] }) : /* @__PURE__ */ jsxs(Fragment2, { children: [
1349
+ /* @__PURE__ */ jsx("span", { class: "pp-status-dot" }),
1350
+ " Away"
1351
+ ] }) })
556
1352
  ] })
557
- ]
558
- },
559
- msg.id
560
- )),
561
- isTyping && /* @__PURE__ */ jsxs("div", { class: "pp-message pp-message-operator pp-typing", children: [
562
- /* @__PURE__ */ jsx("span", {}),
563
- /* @__PURE__ */ jsx("span", {}),
564
- /* @__PURE__ */ jsx("span", {})
565
- ] }),
566
- /* @__PURE__ */ jsx("div", { ref: messagesEndRef })
567
- ] }),
568
- /* @__PURE__ */ jsxs("form", { class: "pp-input-form", onSubmit: handleSubmit, children: [
569
- /* @__PURE__ */ jsx(
570
- "input",
571
- {
572
- ref: inputRef,
573
- type: "text",
574
- class: "pp-input",
575
- placeholder: config.placeholder ?? "Type a message...",
576
- value: inputValue,
577
- onInput: handleInputChange,
578
- disabled: !isConnected
579
- }
580
- ),
581
- /* @__PURE__ */ jsx(
582
- "button",
583
- {
584
- type: "submit",
585
- class: "pp-send-btn",
586
- disabled: !inputValue.trim() || !isConnected,
587
- "aria-label": "Send message",
588
- children: /* @__PURE__ */ jsx(SendIcon, {})
589
- }
590
- )
591
- ] }),
592
- /* @__PURE__ */ jsxs("div", { class: "pp-footer", children: [
593
- "Powered by ",
594
- /* @__PURE__ */ jsx("a", { href: "https://pocketping.io", target: "_blank", rel: "noopener", children: "PocketPing" })
595
- ] })
596
- ] })
1353
+ ] }),
1354
+ /* @__PURE__ */ jsx(
1355
+ "button",
1356
+ {
1357
+ class: "pp-close-btn",
1358
+ onClick: () => client2.setOpen(false),
1359
+ "aria-label": "Close chat",
1360
+ children: /* @__PURE__ */ jsx(CloseIcon, {})
1361
+ }
1362
+ )
1363
+ ] }),
1364
+ /* @__PURE__ */ jsxs("div", { class: "pp-messages", ref: messagesContainerRef, children: [
1365
+ config.welcomeMessage && messages.length === 0 && /* @__PURE__ */ jsx("div", { class: "pp-welcome", children: config.welcomeMessage }),
1366
+ messages.map((msg) => {
1367
+ const isDeleted = !!msg.deletedAt;
1368
+ const isEdited = !!msg.editedAt;
1369
+ let replyData = null;
1370
+ if (msg.replyTo) {
1371
+ if (typeof msg.replyTo === "object") {
1372
+ replyData = msg.replyTo;
1373
+ } else {
1374
+ const replyToMsg = messages.find((m) => m.id === msg.replyTo);
1375
+ if (replyToMsg) {
1376
+ replyData = {
1377
+ sender: replyToMsg.sender,
1378
+ content: replyToMsg.content,
1379
+ deleted: !!replyToMsg.deletedAt
1380
+ };
1381
+ }
1382
+ }
1383
+ }
1384
+ const isHovered = hoveredMessageId === msg.id;
1385
+ const showActions = isHovered && !isDeleted;
1386
+ return /* @__PURE__ */ jsxs(
1387
+ "div",
1388
+ {
1389
+ class: `pp-message pp-message-${msg.sender} ${isDeleted ? "pp-message-deleted" : ""}`,
1390
+ onContextMenu: (e) => handleMessageContextMenu(e, msg),
1391
+ onMouseEnter: () => setHoveredMessageId(msg.id),
1392
+ onMouseLeave: () => setHoveredMessageId(null),
1393
+ onTouchStart: () => handleTouchStart(msg),
1394
+ onTouchEnd: handleTouchEnd,
1395
+ onTouchCancel: handleTouchEnd,
1396
+ children: [
1397
+ showActions && /* @__PURE__ */ jsxs("div", { class: `pp-message-actions ${msg.sender === "visitor" ? "pp-actions-left" : "pp-actions-right"}`, children: [
1398
+ /* @__PURE__ */ jsx(
1399
+ "button",
1400
+ {
1401
+ class: "pp-action-btn",
1402
+ onClick: () => handleReply(msg),
1403
+ title: "Reply",
1404
+ children: /* @__PURE__ */ jsx(ReplyIcon, { color: actionIconColor })
1405
+ }
1406
+ ),
1407
+ msg.sender === "visitor" && /* @__PURE__ */ jsxs(Fragment2, { children: [
1408
+ /* @__PURE__ */ jsx(
1409
+ "button",
1410
+ {
1411
+ class: "pp-action-btn",
1412
+ onClick: () => handleStartEdit(msg),
1413
+ title: "Edit",
1414
+ children: /* @__PURE__ */ jsx(EditIcon, { color: actionIconColor })
1415
+ }
1416
+ ),
1417
+ /* @__PURE__ */ jsx(
1418
+ "button",
1419
+ {
1420
+ class: "pp-action-btn pp-action-delete",
1421
+ onClick: () => handleDelete(msg),
1422
+ title: "Delete",
1423
+ children: /* @__PURE__ */ jsx(DeleteIcon, { color: actionIconColor })
1424
+ }
1425
+ )
1426
+ ] })
1427
+ ] }),
1428
+ replyData && replyData.content && /* @__PURE__ */ jsxs("div", { class: "pp-reply-quote", children: [
1429
+ /* @__PURE__ */ jsx("span", { class: "pp-reply-sender", children: replyData.sender === "visitor" ? "You" : "Support" }),
1430
+ /* @__PURE__ */ jsxs("span", { class: "pp-reply-content", children: [
1431
+ replyData.deleted ? "Message deleted" : (replyData.content || "").slice(0, 50),
1432
+ (replyData.content || "").length > 50 ? "..." : ""
1433
+ ] })
1434
+ ] }),
1435
+ isDeleted ? /* @__PURE__ */ jsxs("div", { class: "pp-message-content pp-deleted-content", children: [
1436
+ /* @__PURE__ */ jsx("span", { class: "pp-deleted-icon", children: "\u{1F5D1}\uFE0F" }),
1437
+ " Message deleted"
1438
+ ] }) : /* @__PURE__ */ jsxs(Fragment2, { children: [
1439
+ msg.content && /* @__PURE__ */ jsx("div", { class: "pp-message-content", children: msg.content }),
1440
+ msg.attachments && msg.attachments.length > 0 && /* @__PURE__ */ jsx("div", { class: "pp-message-attachments", children: msg.attachments.map((att) => /* @__PURE__ */ jsx(AttachmentDisplay, { attachment: att }, att.id)) })
1441
+ ] }),
1442
+ /* @__PURE__ */ jsxs("div", { class: "pp-message-time", children: [
1443
+ formatTime(msg.timestamp),
1444
+ isEdited && !isDeleted && /* @__PURE__ */ jsx("span", { class: "pp-edited-badge", children: "edited" }),
1445
+ msg.sender === "ai" && /* @__PURE__ */ jsx("span", { class: "pp-ai-badge", children: "AI" }),
1446
+ msg.sender === "visitor" && !isDeleted && /* @__PURE__ */ jsx("span", { class: `pp-status pp-status-${msg.status ?? "sent"}`, children: /* @__PURE__ */ jsx(StatusIcon, { status: msg.status }) })
1447
+ ] })
1448
+ ]
1449
+ },
1450
+ msg.id
1451
+ );
1452
+ }),
1453
+ isTyping && /* @__PURE__ */ jsxs("div", { class: "pp-message pp-message-operator pp-typing", children: [
1454
+ /* @__PURE__ */ jsx("span", {}),
1455
+ /* @__PURE__ */ jsx("span", {}),
1456
+ /* @__PURE__ */ jsx("span", {})
1457
+ ] }),
1458
+ /* @__PURE__ */ jsx("div", { ref: messagesEndRef })
1459
+ ] }),
1460
+ messageMenu && /* @__PURE__ */ jsxs(
1461
+ "div",
1462
+ {
1463
+ class: "pp-message-menu",
1464
+ style: { top: `${messageMenu.y}px`, left: `${messageMenu.x}px` },
1465
+ children: [
1466
+ /* @__PURE__ */ jsxs("button", { onClick: () => handleReply(messageMenu.message), children: [
1467
+ /* @__PURE__ */ jsx(ReplyIcon, { color: actionIconColor }),
1468
+ " Reply"
1469
+ ] }),
1470
+ messageMenu.message.sender === "visitor" && !messageMenu.message.deletedAt && /* @__PURE__ */ jsxs(Fragment2, { children: [
1471
+ /* @__PURE__ */ jsxs("button", { onClick: () => handleStartEdit(messageMenu.message), children: [
1472
+ /* @__PURE__ */ jsx(EditIcon, { color: actionIconColor }),
1473
+ " Edit"
1474
+ ] }),
1475
+ /* @__PURE__ */ jsxs("button", { class: "pp-menu-delete", onClick: () => handleDelete(messageMenu.message), children: [
1476
+ /* @__PURE__ */ jsx(DeleteIcon, { color: "#ef4444" }),
1477
+ " Delete"
1478
+ ] })
1479
+ ] })
1480
+ ]
1481
+ }
1482
+ ),
1483
+ editingMessage && /* @__PURE__ */ jsxs("div", { class: "pp-edit-modal", children: [
1484
+ /* @__PURE__ */ jsxs("div", { class: "pp-edit-header", children: [
1485
+ /* @__PURE__ */ jsx("span", { children: "Edit message" }),
1486
+ /* @__PURE__ */ jsx("button", { onClick: handleCancelEdit, children: /* @__PURE__ */ jsx(CloseIcon, {}) })
1487
+ ] }),
1488
+ /* @__PURE__ */ jsx(
1489
+ "textarea",
1490
+ {
1491
+ class: "pp-edit-input",
1492
+ value: editContent,
1493
+ onInput: (e) => setEditContent(e.target.value),
1494
+ autoFocus: true
1495
+ }
1496
+ ),
1497
+ /* @__PURE__ */ jsxs("div", { class: "pp-edit-actions", children: [
1498
+ /* @__PURE__ */ jsx("button", { class: "pp-edit-cancel", onClick: handleCancelEdit, children: "Cancel" }),
1499
+ /* @__PURE__ */ jsx("button", { class: "pp-edit-save", onClick: handleSaveEdit, disabled: !editContent.trim(), children: "Save" })
1500
+ ] })
1501
+ ] }),
1502
+ replyingTo && /* @__PURE__ */ jsxs("div", { class: "pp-reply-preview", children: [
1503
+ /* @__PURE__ */ jsxs("div", { class: "pp-reply-preview-content", children: [
1504
+ /* @__PURE__ */ jsx("span", { class: "pp-reply-label", children: "Replying to" }),
1505
+ /* @__PURE__ */ jsxs("span", { class: "pp-reply-text", children: [
1506
+ replyingTo.content.slice(0, 50),
1507
+ replyingTo.content.length > 50 ? "..." : ""
1508
+ ] })
1509
+ ] }),
1510
+ /* @__PURE__ */ jsx("button", { class: "pp-reply-cancel", onClick: handleCancelReply, children: /* @__PURE__ */ jsx(CloseIcon, {}) })
1511
+ ] }),
1512
+ pendingAttachments.length > 0 && /* @__PURE__ */ jsx("div", { class: "pp-attachments-preview", children: pendingAttachments.map((pending) => /* @__PURE__ */ jsxs("div", { class: `pp-attachment-preview pp-attachment-${pending.status}`, children: [
1513
+ pending.preview ? /* @__PURE__ */ jsx("img", { src: pending.preview, alt: pending.file.name, class: "pp-preview-img" }) : /* @__PURE__ */ jsx("div", { class: "pp-preview-file", children: /* @__PURE__ */ jsx(FileIcon, { mimeType: pending.file.type }) }),
1514
+ /* @__PURE__ */ jsx(
1515
+ "button",
1516
+ {
1517
+ class: "pp-remove-attachment",
1518
+ onClick: () => handleRemoveAttachment(pending.id),
1519
+ "aria-label": "Remove attachment",
1520
+ type: "button",
1521
+ children: /* @__PURE__ */ jsx(CloseIcon, {})
1522
+ }
1523
+ ),
1524
+ pending.status === "uploading" && /* @__PURE__ */ jsx("div", { class: "pp-upload-progress", style: { width: `${pending.progress}%` } }),
1525
+ pending.status === "error" && /* @__PURE__ */ jsx("div", { class: "pp-upload-error", title: pending.error, children: "!" })
1526
+ ] }, pending.id)) }),
1527
+ /* @__PURE__ */ jsxs("form", { class: "pp-input-form", onSubmit: handleSubmit, children: [
1528
+ /* @__PURE__ */ jsx(
1529
+ "input",
1530
+ {
1531
+ ref: (el) => {
1532
+ fileInputRef.current = el;
1533
+ if (el) {
1534
+ el.onchange = handleFileSelect;
1535
+ }
1536
+ },
1537
+ type: "file",
1538
+ class: "pp-file-input",
1539
+ accept: "image/*,audio/*,video/*,.pdf,.doc,.docx,.xls,.xlsx,.txt",
1540
+ multiple: true
1541
+ }
1542
+ ),
1543
+ /* @__PURE__ */ jsx(
1544
+ "button",
1545
+ {
1546
+ type: "button",
1547
+ class: "pp-attach-btn",
1548
+ onClick: () => fileInputRef.current?.click(),
1549
+ disabled: !isConnected || isUploading,
1550
+ "aria-label": "Attach file",
1551
+ children: /* @__PURE__ */ jsx(AttachIcon, {})
1552
+ }
1553
+ ),
1554
+ /* @__PURE__ */ jsx(
1555
+ "input",
1556
+ {
1557
+ ref: inputRef,
1558
+ type: "text",
1559
+ class: "pp-input",
1560
+ placeholder: config.placeholder ?? "Type a message...",
1561
+ value: inputValue,
1562
+ onInput: handleInputChange,
1563
+ disabled: !isConnected
1564
+ }
1565
+ ),
1566
+ /* @__PURE__ */ jsx(
1567
+ "button",
1568
+ {
1569
+ type: "submit",
1570
+ class: "pp-send-btn",
1571
+ disabled: !inputValue.trim() && pendingAttachments.filter((a) => a.status === "ready").length === 0 || !isConnected || isUploading,
1572
+ "aria-label": "Send message",
1573
+ children: /* @__PURE__ */ jsx(SendIcon, {})
1574
+ }
1575
+ )
1576
+ ] }),
1577
+ /* @__PURE__ */ jsxs("div", { class: "pp-footer", children: [
1578
+ "Powered by ",
1579
+ /* @__PURE__ */ jsx("a", { href: "https://pocketping.io", target: "_blank", rel: "noopener", children: "PocketPing" })
1580
+ ] })
1581
+ ]
1582
+ }
1583
+ )
597
1584
  ] });
598
1585
  }
599
1586
  function checkPageVisibility(config) {
@@ -649,6 +1636,89 @@ function StatusIcon({ status }) {
649
1636
  }
650
1637
  return null;
651
1638
  }
1639
+ function AttachIcon() {
1640
+ return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", children: /* @__PURE__ */ jsx("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" }) });
1641
+ }
1642
+ function ReplyIcon({ color, size = 16 }) {
1643
+ const strokeColor = color || "currentColor";
1644
+ return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", "stroke-width": "2", style: { stroke: strokeColor, width: `${size}px`, minWidth: `${size}px`, height: `${size}px`, display: "block", flexShrink: 0 }, children: [
1645
+ /* @__PURE__ */ jsx("polyline", { points: "9 17 4 12 9 7" }),
1646
+ /* @__PURE__ */ jsx("path", { d: "M20 18v-2a4 4 0 0 0-4-4H4" })
1647
+ ] });
1648
+ }
1649
+ function EditIcon({ color, size = 16 }) {
1650
+ const strokeColor = color || "currentColor";
1651
+ return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", "stroke-width": "2", style: { stroke: strokeColor, width: `${size}px`, minWidth: `${size}px`, height: `${size}px`, display: "block", flexShrink: 0 }, children: /* @__PURE__ */ jsx("path", { d: "M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z" }) });
1652
+ }
1653
+ function DeleteIcon({ color, size = 16 }) {
1654
+ const strokeColor = color || "currentColor";
1655
+ return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", "stroke-width": "2", style: { stroke: strokeColor, width: `${size}px`, minWidth: `${size}px`, height: `${size}px`, display: "block", flexShrink: 0 }, children: [
1656
+ /* @__PURE__ */ jsx("polyline", { points: "3 6 5 6 21 6" }),
1657
+ /* @__PURE__ */ jsx("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" })
1658
+ ] });
1659
+ }
1660
+ function FileIcon({ mimeType }) {
1661
+ if (mimeType === "application/pdf") {
1662
+ return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", children: [
1663
+ /* @__PURE__ */ jsx("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
1664
+ /* @__PURE__ */ jsx("polyline", { points: "14 2 14 8 20 8" }),
1665
+ /* @__PURE__ */ jsx("path", { d: "M9 15h6" }),
1666
+ /* @__PURE__ */ jsx("path", { d: "M9 11h6" })
1667
+ ] });
1668
+ }
1669
+ if (mimeType.startsWith("audio/")) {
1670
+ return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", children: [
1671
+ /* @__PURE__ */ jsx("path", { d: "M9 18V5l12-2v13" }),
1672
+ /* @__PURE__ */ jsx("circle", { cx: "6", cy: "18", r: "3" }),
1673
+ /* @__PURE__ */ jsx("circle", { cx: "18", cy: "16", r: "3" })
1674
+ ] });
1675
+ }
1676
+ if (mimeType.startsWith("video/")) {
1677
+ return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", children: [
1678
+ /* @__PURE__ */ jsx("rect", { x: "2", y: "2", width: "20", height: "20", rx: "2.18", ry: "2.18" }),
1679
+ /* @__PURE__ */ jsx("line", { x1: "7", y1: "2", x2: "7", y2: "22" }),
1680
+ /* @__PURE__ */ jsx("line", { x1: "17", y1: "2", x2: "17", y2: "22" }),
1681
+ /* @__PURE__ */ jsx("line", { x1: "2", y1: "12", x2: "22", y2: "12" }),
1682
+ /* @__PURE__ */ jsx("line", { x1: "2", y1: "7", x2: "7", y2: "7" }),
1683
+ /* @__PURE__ */ jsx("line", { x1: "2", y1: "17", x2: "7", y2: "17" }),
1684
+ /* @__PURE__ */ jsx("line", { x1: "17", y1: "17", x2: "22", y2: "17" }),
1685
+ /* @__PURE__ */ jsx("line", { x1: "17", y1: "7", x2: "22", y2: "7" })
1686
+ ] });
1687
+ }
1688
+ return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", children: [
1689
+ /* @__PURE__ */ jsx("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
1690
+ /* @__PURE__ */ jsx("polyline", { points: "14 2 14 8 20 8" })
1691
+ ] });
1692
+ }
1693
+ function AttachmentDisplay({ attachment }) {
1694
+ const isImage = attachment.mimeType.startsWith("image/");
1695
+ const isAudio = attachment.mimeType.startsWith("audio/");
1696
+ const isVideo = attachment.mimeType.startsWith("video/");
1697
+ const formatSize = (bytes) => {
1698
+ if (bytes < 1024) return `${bytes} B`;
1699
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
1700
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1701
+ };
1702
+ if (isImage) {
1703
+ return /* @__PURE__ */ jsx("a", { href: attachment.url, target: "_blank", rel: "noopener", class: "pp-attachment pp-attachment-image", children: /* @__PURE__ */ jsx("img", { src: attachment.thumbnailUrl || attachment.url, alt: attachment.filename }) });
1704
+ }
1705
+ if (isAudio) {
1706
+ return /* @__PURE__ */ jsxs("div", { class: "pp-attachment pp-attachment-audio", children: [
1707
+ /* @__PURE__ */ jsx("audio", { controls: true, preload: "metadata", children: /* @__PURE__ */ jsx("source", { src: attachment.url, type: attachment.mimeType }) }),
1708
+ /* @__PURE__ */ jsx("span", { class: "pp-attachment-name", children: attachment.filename })
1709
+ ] });
1710
+ }
1711
+ if (isVideo) {
1712
+ return /* @__PURE__ */ jsx("div", { class: "pp-attachment pp-attachment-video", children: /* @__PURE__ */ jsx("video", { controls: true, preload: "metadata", children: /* @__PURE__ */ jsx("source", { src: attachment.url, type: attachment.mimeType }) }) });
1713
+ }
1714
+ return /* @__PURE__ */ jsxs("a", { href: attachment.url, target: "_blank", rel: "noopener", class: "pp-attachment pp-attachment-file", children: [
1715
+ /* @__PURE__ */ jsx(FileIcon, { mimeType: attachment.mimeType }),
1716
+ /* @__PURE__ */ jsxs("div", { class: "pp-attachment-info", children: [
1717
+ /* @__PURE__ */ jsx("span", { class: "pp-attachment-name", children: attachment.filename }),
1718
+ /* @__PURE__ */ jsx("span", { class: "pp-attachment-size", children: formatSize(attachment.size) })
1719
+ ] })
1720
+ ] });
1721
+ }
652
1722
 
653
1723
  // src/version.ts
654
1724
  var VERSION = "0.3.6";
@@ -764,7 +1834,7 @@ var PocketPingClient = class {
764
1834
  this.cleanupTrackedElements();
765
1835
  this.disableInspectorMode();
766
1836
  }
767
- async sendMessage(content) {
1837
+ async sendMessage(content, attachmentIds, replyTo) {
768
1838
  if (!this.session) {
769
1839
  throw new Error("Not connected");
770
1840
  }
@@ -775,7 +1845,8 @@ var PocketPingClient = class {
775
1845
  content,
776
1846
  sender: "visitor",
777
1847
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
778
- status: "sending"
1848
+ status: "sending",
1849
+ replyTo
779
1850
  };
780
1851
  this.session.messages.push(tempMessage);
781
1852
  this.emit("message", tempMessage);
@@ -785,7 +1856,9 @@ var PocketPingClient = class {
785
1856
  body: JSON.stringify({
786
1857
  sessionId: this.session.sessionId,
787
1858
  content,
788
- sender: "visitor"
1859
+ sender: "visitor",
1860
+ attachmentIds: attachmentIds || [],
1861
+ replyTo
789
1862
  })
790
1863
  });
791
1864
  const messageIndex = this.session.messages.findIndex((m) => m.id === tempId);
@@ -793,6 +1866,9 @@ var PocketPingClient = class {
793
1866
  this.session.messages[messageIndex].id = response.messageId;
794
1867
  this.session.messages[messageIndex].timestamp = response.timestamp;
795
1868
  this.session.messages[messageIndex].status = "sent";
1869
+ if (response.attachments && response.attachments.length > 0) {
1870
+ this.session.messages[messageIndex].attachments = response.attachments;
1871
+ }
796
1872
  this.emit("message", this.session.messages[messageIndex]);
797
1873
  }
798
1874
  const message = this.session.messages[messageIndex] || {
@@ -801,7 +1877,8 @@ var PocketPingClient = class {
801
1877
  content,
802
1878
  sender: "visitor",
803
1879
  timestamp: response.timestamp,
804
- status: "sent"
1880
+ status: "sent",
1881
+ attachments: response.attachments
805
1882
  };
806
1883
  this.config.onMessage?.(message);
807
1884
  return message;
@@ -814,6 +1891,110 @@ var PocketPingClient = class {
814
1891
  throw error;
815
1892
  }
816
1893
  }
1894
+ /**
1895
+ * Upload a file attachment
1896
+ * Returns the attachment data after successful upload
1897
+ * @param file - File object to upload
1898
+ * @param onProgress - Optional callback for upload progress (0-100)
1899
+ * @example
1900
+ * const attachment = await PocketPing.uploadFile(file, (progress) => {
1901
+ * console.log(`Upload ${progress}% complete`)
1902
+ * })
1903
+ * await PocketPing.sendMessage('Check this file', [attachment.id])
1904
+ */
1905
+ async uploadFile(file, onProgress) {
1906
+ if (!this.session) {
1907
+ throw new Error("Not connected");
1908
+ }
1909
+ this.emit("uploadStart", { filename: file.name, size: file.size });
1910
+ try {
1911
+ const initResponse = await this.fetch("/upload", {
1912
+ method: "POST",
1913
+ body: JSON.stringify({
1914
+ sessionId: this.session.sessionId,
1915
+ filename: file.name,
1916
+ mimeType: file.type || "application/octet-stream",
1917
+ size: file.size
1918
+ })
1919
+ });
1920
+ onProgress?.(10);
1921
+ this.emit("uploadProgress", { filename: file.name, progress: 10 });
1922
+ await this.uploadToPresignedUrl(initResponse.uploadUrl, file, (progress) => {
1923
+ const mappedProgress = 10 + progress * 0.8;
1924
+ onProgress?.(mappedProgress);
1925
+ this.emit("uploadProgress", { filename: file.name, progress: mappedProgress });
1926
+ });
1927
+ const completeResponse = await this.fetch("/upload/complete", {
1928
+ method: "POST",
1929
+ body: JSON.stringify({
1930
+ sessionId: this.session.sessionId,
1931
+ attachmentId: initResponse.attachmentId
1932
+ })
1933
+ });
1934
+ onProgress?.(100);
1935
+ this.emit("uploadComplete", completeResponse);
1936
+ return {
1937
+ id: completeResponse.id,
1938
+ filename: completeResponse.filename,
1939
+ mimeType: completeResponse.mimeType,
1940
+ size: completeResponse.size,
1941
+ url: completeResponse.url,
1942
+ thumbnailUrl: completeResponse.thumbnailUrl,
1943
+ status: completeResponse.status
1944
+ };
1945
+ } catch (error) {
1946
+ this.emit("uploadError", { filename: file.name, error });
1947
+ throw error;
1948
+ }
1949
+ }
1950
+ /**
1951
+ * Upload multiple files at once
1952
+ * @param files - Array of File objects to upload
1953
+ * @param onProgress - Optional callback for overall progress (0-100)
1954
+ * @returns Array of uploaded attachments
1955
+ */
1956
+ async uploadFiles(files, onProgress) {
1957
+ const attachments = [];
1958
+ const totalFiles = files.length;
1959
+ for (let i = 0; i < totalFiles; i++) {
1960
+ const file = files[i];
1961
+ const baseProgress = i / totalFiles * 100;
1962
+ const fileProgress = 100 / totalFiles;
1963
+ const attachment = await this.uploadFile(file, (progress) => {
1964
+ const totalProgress = baseProgress + progress / 100 * fileProgress;
1965
+ onProgress?.(totalProgress);
1966
+ });
1967
+ attachments.push(attachment);
1968
+ }
1969
+ return attachments;
1970
+ }
1971
+ /**
1972
+ * Upload file to presigned URL with progress tracking
1973
+ */
1974
+ uploadToPresignedUrl(url, file, onProgress) {
1975
+ return new Promise((resolve, reject) => {
1976
+ const xhr = new XMLHttpRequest();
1977
+ xhr.upload.addEventListener("progress", (event) => {
1978
+ if (event.lengthComputable) {
1979
+ const progress = event.loaded / event.total * 100;
1980
+ onProgress?.(progress);
1981
+ }
1982
+ });
1983
+ xhr.addEventListener("load", () => {
1984
+ if (xhr.status >= 200 && xhr.status < 300) {
1985
+ resolve();
1986
+ } else {
1987
+ reject(new Error(`Upload failed with status ${xhr.status}`));
1988
+ }
1989
+ });
1990
+ xhr.addEventListener("error", () => {
1991
+ reject(new Error("Upload failed"));
1992
+ });
1993
+ xhr.open("PUT", url);
1994
+ xhr.setRequestHeader("Content-Type", file.type || "application/octet-stream");
1995
+ xhr.send(file);
1996
+ });
1997
+ }
817
1998
  async fetchMessages(after) {
818
1999
  if (!this.session) {
819
2000
  throw new Error("Not connected");
@@ -867,6 +2048,54 @@ var PocketPingClient = class {
867
2048
  console.error("[PocketPing] Failed to send read status:", err);
868
2049
  }
869
2050
  }
2051
+ /**
2052
+ * Edit a message (visitor can only edit their own messages)
2053
+ * @param messageId - ID of the message to edit
2054
+ * @param content - New content for the message
2055
+ */
2056
+ async editMessage(messageId, content) {
2057
+ if (!this.session) {
2058
+ throw new Error("Not connected");
2059
+ }
2060
+ const response = await this.fetch(`/message/${messageId}`, {
2061
+ method: "PATCH",
2062
+ body: JSON.stringify({
2063
+ sessionId: this.session.sessionId,
2064
+ content
2065
+ })
2066
+ });
2067
+ const messageIndex = this.session.messages.findIndex((m) => m.id === messageId);
2068
+ if (messageIndex >= 0) {
2069
+ this.session.messages[messageIndex].content = response.message.content;
2070
+ this.session.messages[messageIndex].editedAt = response.message.editedAt;
2071
+ this.emit("messageEdited", this.session.messages[messageIndex]);
2072
+ }
2073
+ return this.session.messages[messageIndex];
2074
+ }
2075
+ /**
2076
+ * Delete a message (soft delete - visitor can only delete their own messages)
2077
+ * @param messageId - ID of the message to delete
2078
+ */
2079
+ async deleteMessage(messageId) {
2080
+ if (!this.session) {
2081
+ throw new Error("Not connected");
2082
+ }
2083
+ const response = await this.fetch(`/message/${messageId}`, {
2084
+ method: "DELETE",
2085
+ body: JSON.stringify({
2086
+ sessionId: this.session.sessionId
2087
+ })
2088
+ });
2089
+ if (response.deleted) {
2090
+ const messageIndex = this.session.messages.findIndex((m) => m.id === messageId);
2091
+ if (messageIndex >= 0) {
2092
+ this.session.messages[messageIndex].deletedAt = (/* @__PURE__ */ new Date()).toISOString();
2093
+ this.session.messages[messageIndex].content = "";
2094
+ this.emit("messageDeleted", this.session.messages[messageIndex]);
2095
+ }
2096
+ }
2097
+ return response.deleted;
2098
+ }
870
2099
  async getPresence() {
871
2100
  return this.fetch("/presence", { method: "GET" });
872
2101
  }
@@ -1421,7 +2650,14 @@ var PocketPingClient = class {
1421
2650
  }
1422
2651
  connectSSE() {
1423
2652
  if (!this.session) return;
1424
- const sseUrl = this.config.endpoint.replace(/\/$/, "") + `/stream?sessionId=${this.session.sessionId}`;
2653
+ const params = new URLSearchParams({
2654
+ sessionId: this.session.sessionId
2655
+ });
2656
+ const lastEventTimestamp = this.getLastEventTimestamp();
2657
+ if (lastEventTimestamp) {
2658
+ params.set("after", lastEventTimestamp);
2659
+ }
2660
+ const sseUrl = this.config.endpoint.replace(/\/$/, "") + `/stream?${params.toString()}`;
1425
2661
  try {
1426
2662
  this.sse = new EventSource(sseUrl);
1427
2663
  const connectionTimeout = setTimeout(() => {
@@ -1476,6 +2712,19 @@ var PocketPingClient = class {
1476
2712
  handleRealtimeEvent(event) {
1477
2713
  this.handleWebSocketEvent(event);
1478
2714
  }
2715
+ getLastEventTimestamp() {
2716
+ if (!this.session) return null;
2717
+ let latest = null;
2718
+ for (const msg of this.session.messages) {
2719
+ const candidates = [msg.timestamp, msg.editedAt, msg.deletedAt, msg.deliveredAt, msg.readAt].filter(Boolean).map((value) => new Date(value)).filter((date) => !isNaN(date.getTime()));
2720
+ for (const date of candidates) {
2721
+ if (!latest || date > latest) {
2722
+ latest = date;
2723
+ }
2724
+ }
2725
+ }
2726
+ return latest ? latest.toISOString() : null;
2727
+ }
1479
2728
  handleWebSocketEvent(event) {
1480
2729
  switch (event.type) {
1481
2730
  case "message":
@@ -1498,12 +2747,41 @@ var PocketPingClient = class {
1498
2747
  }
1499
2748
  if (existingIndex >= 0) {
1500
2749
  const existing = this.session.messages[existingIndex];
2750
+ let updated = false;
1501
2751
  if (message.status && message.status !== existing.status) {
1502
2752
  existing.status = message.status;
1503
- if (message.deliveredAt) existing.deliveredAt = message.deliveredAt;
1504
- if (message.readAt) existing.readAt = message.readAt;
2753
+ updated = true;
2754
+ if (message.deliveredAt) {
2755
+ existing.deliveredAt = message.deliveredAt;
2756
+ }
2757
+ if (message.readAt) {
2758
+ existing.readAt = message.readAt;
2759
+ }
1505
2760
  this.emit("read", { messageIds: [message.id], status: message.status });
1506
2761
  }
2762
+ if (message.content !== void 0 && message.content !== existing.content) {
2763
+ existing.content = message.content;
2764
+ updated = true;
2765
+ }
2766
+ if (message.editedAt !== void 0 && message.editedAt !== existing.editedAt) {
2767
+ existing.editedAt = message.editedAt;
2768
+ updated = true;
2769
+ }
2770
+ if (message.deletedAt !== void 0 && message.deletedAt !== existing.deletedAt) {
2771
+ existing.deletedAt = message.deletedAt;
2772
+ updated = true;
2773
+ }
2774
+ if (message.replyTo !== void 0) {
2775
+ existing.replyTo = message.replyTo;
2776
+ updated = true;
2777
+ }
2778
+ if (message.attachments !== void 0) {
2779
+ existing.attachments = message.attachments;
2780
+ updated = true;
2781
+ }
2782
+ if (updated) {
2783
+ this.emit("message", existing);
2784
+ }
1507
2785
  } else {
1508
2786
  this.session.messages.push(message);
1509
2787
  this.emit("message", message);
@@ -1542,6 +2820,29 @@ var PocketPingClient = class {
1542
2820
  }
1543
2821
  this.emit("read", readData);
1544
2822
  break;
2823
+ case "message_edited":
2824
+ if (this.session) {
2825
+ const editData = event.data;
2826
+ const msgIndex = this.session.messages.findIndex((m) => m.id === editData.messageId);
2827
+ if (msgIndex >= 0) {
2828
+ const existing = this.session.messages[msgIndex];
2829
+ existing.content = editData.content;
2830
+ existing.editedAt = editData.editedAt ?? (/* @__PURE__ */ new Date()).toISOString();
2831
+ this.emit("message", existing);
2832
+ }
2833
+ }
2834
+ break;
2835
+ case "message_deleted":
2836
+ if (this.session) {
2837
+ const deleteData = event.data;
2838
+ const msgIndex = this.session.messages.findIndex((m) => m.id === deleteData.messageId);
2839
+ if (msgIndex >= 0) {
2840
+ const existing = this.session.messages[msgIndex];
2841
+ existing.deletedAt = deleteData.deletedAt ?? (/* @__PURE__ */ new Date()).toISOString();
2842
+ this.emit("message", existing);
2843
+ }
2844
+ }
2845
+ break;
1545
2846
  case "event":
1546
2847
  const customEvent = event.data;
1547
2848
  this.emitCustomEvent(customEvent);
@@ -1601,11 +2902,45 @@ var PocketPingClient = class {
1601
2902
  const poll = async () => {
1602
2903
  if (!this.session) return;
1603
2904
  try {
1604
- const lastMessageId = this.session.messages[this.session.messages.length - 1]?.id;
1605
- const newMessages = await this.fetchMessages(lastMessageId);
2905
+ const lastEventTimestamp = this.getLastEventTimestamp();
2906
+ const newMessages = await this.fetchMessages(lastEventTimestamp ?? void 0);
1606
2907
  this.pollingFailures = 0;
1607
2908
  for (const message of newMessages) {
1608
- if (!this.session.messages.find((m) => m.id === message.id)) {
2909
+ const existingIndex = this.session.messages.findIndex((m) => m.id === message.id);
2910
+ if (existingIndex >= 0) {
2911
+ const existing = this.session.messages[existingIndex];
2912
+ let updated = false;
2913
+ if (message.status && message.status !== existing.status) {
2914
+ existing.status = message.status;
2915
+ updated = true;
2916
+ if (message.deliveredAt) existing.deliveredAt = message.deliveredAt;
2917
+ if (message.readAt) existing.readAt = message.readAt;
2918
+ this.emit("read", { messageIds: [message.id], status: message.status });
2919
+ }
2920
+ if (message.content !== void 0 && message.content !== existing.content) {
2921
+ existing.content = message.content;
2922
+ updated = true;
2923
+ }
2924
+ if (message.editedAt !== void 0 && message.editedAt !== existing.editedAt) {
2925
+ existing.editedAt = message.editedAt;
2926
+ updated = true;
2927
+ }
2928
+ if (message.deletedAt !== void 0 && message.deletedAt !== existing.deletedAt) {
2929
+ existing.deletedAt = message.deletedAt;
2930
+ updated = true;
2931
+ }
2932
+ if (message.replyTo !== void 0) {
2933
+ existing.replyTo = message.replyTo;
2934
+ updated = true;
2935
+ }
2936
+ if (message.attachments !== void 0) {
2937
+ existing.attachments = message.attachments;
2938
+ updated = true;
2939
+ }
2940
+ if (updated) {
2941
+ this.emit("message", existing);
2942
+ }
2943
+ } else {
1609
2944
  this.session.messages.push(message);
1610
2945
  this.emit("message", message);
1611
2946
  this.config.onMessage?.(message);
@@ -1770,11 +3105,23 @@ function close() {
1770
3105
  function toggle() {
1771
3106
  client?.toggleOpen();
1772
3107
  }
1773
- function sendMessage(content) {
3108
+ function sendMessage(content, attachmentIds) {
3109
+ if (!client) {
3110
+ throw new Error("[PocketPing] Not initialized");
3111
+ }
3112
+ return client.sendMessage(content, attachmentIds);
3113
+ }
3114
+ async function uploadFile(file, onProgress) {
3115
+ if (!client) {
3116
+ throw new Error("[PocketPing] Not initialized");
3117
+ }
3118
+ return client.uploadFile(file, onProgress);
3119
+ }
3120
+ async function uploadFiles(files, onProgress) {
1774
3121
  if (!client) {
1775
3122
  throw new Error("[PocketPing] Not initialized");
1776
3123
  }
1777
- return client.sendMessage(content);
3124
+ return client.uploadFiles(files, onProgress);
1778
3125
  }
1779
3126
  function trigger(eventName, data, options) {
1780
3127
  if (!client) {
@@ -1843,7 +3190,7 @@ if (typeof document !== "undefined") {
1843
3190
  }
1844
3191
  }
1845
3192
  }
1846
- var index_default = { init, destroy, open, close, toggle, sendMessage, trigger, onEvent, offEvent, on, identify, reset, getIdentity, setupTrackedElements, getTrackedElements };
3193
+ var index_default = { init, destroy, open, close, toggle, sendMessage, uploadFile, uploadFiles, trigger, onEvent, offEvent, on, identify, reset, getIdentity, setupTrackedElements, getTrackedElements };
1847
3194
  export {
1848
3195
  close,
1849
3196
  index_default as default,
@@ -1860,5 +3207,7 @@ export {
1860
3207
  sendMessage,
1861
3208
  setupTrackedElements,
1862
3209
  toggle,
1863
- trigger
3210
+ trigger,
3211
+ uploadFile,
3212
+ uploadFiles
1864
3213
  };