@pocketping/widget 1.1.0 → 1.2.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 {
@@ -372,6 +378,486 @@ function styles(primaryColor, theme) {
372
378
  .pp-footer a:hover {
373
379
  text-decoration: underline;
374
380
  }
381
+
382
+ /* Attachment Styles */
383
+ .pp-file-input {
384
+ /* Use offscreen positioning instead of display:none for better browser compatibility */
385
+ position: absolute;
386
+ width: 1px;
387
+ height: 1px;
388
+ padding: 0;
389
+ margin: -1px;
390
+ overflow: hidden;
391
+ clip: rect(0, 0, 0, 0);
392
+ white-space: nowrap;
393
+ border: 0;
394
+ }
395
+
396
+ .pp-attach-btn {
397
+ width: 40px;
398
+ height: 40px;
399
+ border-radius: 50%;
400
+ background: transparent;
401
+ color: ${colors.textSecondary};
402
+ border: 1px solid ${colors.border};
403
+ cursor: pointer;
404
+ display: flex;
405
+ align-items: center;
406
+ justify-content: center;
407
+ transition: color 0.2s, border-color 0.2s;
408
+ flex-shrink: 0;
409
+ }
410
+
411
+ .pp-attach-btn:hover:not(:disabled) {
412
+ color: ${primaryColor};
413
+ border-color: ${primaryColor};
414
+ }
415
+
416
+ .pp-attach-btn:disabled {
417
+ opacity: 0.5;
418
+ cursor: not-allowed;
419
+ }
420
+
421
+ .pp-attach-btn svg {
422
+ width: 18px;
423
+ height: 18px;
424
+ }
425
+
426
+ .pp-attachments-preview {
427
+ display: flex;
428
+ gap: 8px;
429
+ padding: 8px 12px;
430
+ border-top: 1px solid ${colors.border};
431
+ overflow-x: auto;
432
+ background: ${colors.bgSecondary};
433
+ }
434
+
435
+ .pp-attachment-preview {
436
+ position: relative;
437
+ width: 60px;
438
+ height: 60px;
439
+ border-radius: 8px;
440
+ overflow: hidden;
441
+ flex-shrink: 0;
442
+ background: ${colors.bg};
443
+ border: 1px solid ${colors.border};
444
+ }
445
+
446
+ .pp-preview-img {
447
+ width: 100%;
448
+ height: 100%;
449
+ object-fit: cover;
450
+ }
451
+
452
+ .pp-preview-file {
453
+ width: 100%;
454
+ height: 100%;
455
+ display: flex;
456
+ align-items: center;
457
+ justify-content: center;
458
+ color: ${colors.textSecondary};
459
+ }
460
+
461
+ .pp-preview-file svg {
462
+ width: 24px;
463
+ height: 24px;
464
+ }
465
+
466
+ .pp-remove-attachment {
467
+ position: absolute;
468
+ top: 2px;
469
+ right: 2px;
470
+ width: 18px;
471
+ height: 18px;
472
+ border-radius: 50%;
473
+ background: rgba(0, 0, 0, 0.6);
474
+ color: white;
475
+ border: none;
476
+ cursor: pointer;
477
+ display: flex;
478
+ align-items: center;
479
+ justify-content: center;
480
+ padding: 0;
481
+ }
482
+
483
+ .pp-remove-attachment svg {
484
+ width: 10px;
485
+ height: 10px;
486
+ }
487
+
488
+ .pp-upload-progress {
489
+ position: absolute;
490
+ bottom: 0;
491
+ left: 0;
492
+ height: 3px;
493
+ background: ${primaryColor};
494
+ transition: width 0.1s;
495
+ }
496
+
497
+ .pp-upload-error {
498
+ position: absolute;
499
+ top: 50%;
500
+ left: 50%;
501
+ transform: translate(-50%, -50%);
502
+ width: 24px;
503
+ height: 24px;
504
+ border-radius: 50%;
505
+ background: #ef4444;
506
+ color: white;
507
+ font-weight: bold;
508
+ display: flex;
509
+ align-items: center;
510
+ justify-content: center;
511
+ font-size: 14px;
512
+ }
513
+
514
+ .pp-attachment-uploading {
515
+ opacity: 0.7;
516
+ }
517
+
518
+ .pp-attachment-error {
519
+ border-color: #ef4444;
520
+ }
521
+
522
+ /* Message Attachments */
523
+ .pp-message-attachments {
524
+ display: flex;
525
+ flex-direction: column;
526
+ gap: 8px;
527
+ margin-top: 4px;
528
+ }
529
+
530
+ .pp-attachment {
531
+ display: block;
532
+ text-decoration: none;
533
+ color: inherit;
534
+ border-radius: 8px;
535
+ overflow: hidden;
536
+ }
537
+
538
+ .pp-attachment-image img {
539
+ max-width: 200px;
540
+ max-height: 200px;
541
+ border-radius: 8px;
542
+ display: block;
543
+ }
544
+
545
+ .pp-attachment-audio {
546
+ display: flex;
547
+ flex-direction: column;
548
+ gap: 4px;
549
+ }
550
+
551
+ .pp-attachment-audio audio {
552
+ width: 200px;
553
+ height: 36px;
554
+ }
555
+
556
+ .pp-attachment-audio .pp-attachment-name {
557
+ font-size: 11px;
558
+ opacity: 0.7;
559
+ white-space: nowrap;
560
+ overflow: hidden;
561
+ text-overflow: ellipsis;
562
+ max-width: 200px;
563
+ }
564
+
565
+ .pp-attachment-video video {
566
+ max-width: 200px;
567
+ max-height: 200px;
568
+ border-radius: 8px;
569
+ display: block;
570
+ }
571
+
572
+ .pp-attachment-file {
573
+ display: flex;
574
+ align-items: center;
575
+ gap: 8px;
576
+ padding: 8px 12px;
577
+ background: ${isDark ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.05)"};
578
+ border-radius: 8px;
579
+ transition: background 0.2s;
580
+ }
581
+
582
+ .pp-attachment-file:hover {
583
+ background: ${isDark ? "rgba(255,255,255,0.15)" : "rgba(0,0,0,0.08)"};
584
+ }
585
+
586
+ .pp-attachment-file svg {
587
+ width: 24px;
588
+ height: 24px;
589
+ flex-shrink: 0;
590
+ }
591
+
592
+ .pp-attachment-info {
593
+ display: flex;
594
+ flex-direction: column;
595
+ min-width: 0;
596
+ }
597
+
598
+ .pp-attachment-name {
599
+ font-size: 13px;
600
+ font-weight: 500;
601
+ white-space: nowrap;
602
+ overflow: hidden;
603
+ text-overflow: ellipsis;
604
+ }
605
+
606
+ .pp-attachment-size {
607
+ font-size: 11px;
608
+ opacity: 0.7;
609
+ }
610
+
611
+ /* Drag & Drop */
612
+ .pp-dragging {
613
+ position: relative;
614
+ }
615
+
616
+ .pp-drop-overlay {
617
+ position: absolute;
618
+ inset: 0;
619
+ background: ${isDark ? "rgba(0,0,0,0.9)" : "rgba(255,255,255,0.95)"};
620
+ display: flex;
621
+ flex-direction: column;
622
+ align-items: center;
623
+ justify-content: center;
624
+ gap: 12px;
625
+ z-index: 100;
626
+ border: 3px dashed ${primaryColor};
627
+ border-radius: 16px;
628
+ margin: 4px;
629
+ pointer-events: none;
630
+ }
631
+
632
+ .pp-drop-icon svg {
633
+ width: 48px;
634
+ height: 48px;
635
+ color: ${primaryColor};
636
+ }
637
+
638
+ .pp-drop-text {
639
+ font-size: 16px;
640
+ font-weight: 500;
641
+ color: ${colors.text};
642
+ }
643
+
644
+ /* Message Context Menu */
645
+ .pp-message-menu {
646
+ position: fixed;
647
+ background: ${colors.bg};
648
+ border: 1px solid ${colors.border};
649
+ border-radius: 8px;
650
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
651
+ padding: 4px;
652
+ z-index: 200;
653
+ min-width: 120px;
654
+ }
655
+
656
+ .pp-message-menu button {
657
+ display: flex;
658
+ align-items: center;
659
+ gap: 8px;
660
+ width: 100%;
661
+ padding: 8px 12px;
662
+ border: none;
663
+ background: transparent;
664
+ color: ${colors.text};
665
+ font-size: 13px;
666
+ cursor: pointer;
667
+ border-radius: 4px;
668
+ text-align: left;
669
+ }
670
+
671
+ .pp-message-menu button:hover {
672
+ background: ${isDark ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.05)"};
673
+ }
674
+
675
+ .pp-message-menu button svg {
676
+ width: 16px;
677
+ height: 16px;
678
+ }
679
+
680
+ .pp-menu-delete {
681
+ color: #ef4444 !important;
682
+ }
683
+
684
+ /* Edit Modal */
685
+ .pp-edit-modal {
686
+ position: absolute;
687
+ bottom: 80px;
688
+ left: 12px;
689
+ right: 12px;
690
+ background: ${colors.bg};
691
+ border: 1px solid ${colors.border};
692
+ border-radius: 12px;
693
+ box-shadow: 0 4px 20px rgba(0,0,0,0.15);
694
+ z-index: 150;
695
+ overflow: hidden;
696
+ }
697
+
698
+ .pp-edit-header {
699
+ display: flex;
700
+ justify-content: space-between;
701
+ align-items: center;
702
+ padding: 12px 16px;
703
+ border-bottom: 1px solid ${colors.border};
704
+ font-weight: 500;
705
+ }
706
+
707
+ .pp-edit-header button {
708
+ background: transparent;
709
+ border: none;
710
+ color: ${colors.textSecondary};
711
+ cursor: pointer;
712
+ padding: 4px;
713
+ }
714
+
715
+ .pp-edit-header button svg {
716
+ width: 18px;
717
+ height: 18px;
718
+ }
719
+
720
+ .pp-edit-input {
721
+ width: 100%;
722
+ padding: 12px 16px;
723
+ border: none;
724
+ background: transparent;
725
+ color: ${colors.text};
726
+ font-size: 14px;
727
+ resize: none;
728
+ min-height: 80px;
729
+ outline: none;
730
+ }
731
+
732
+ .pp-edit-actions {
733
+ display: flex;
734
+ justify-content: flex-end;
735
+ gap: 8px;
736
+ padding: 12px 16px;
737
+ border-top: 1px solid ${colors.border};
738
+ }
739
+
740
+ .pp-edit-cancel {
741
+ padding: 8px 16px;
742
+ border: 1px solid ${colors.border};
743
+ border-radius: 6px;
744
+ background: transparent;
745
+ color: ${colors.text};
746
+ font-size: 13px;
747
+ cursor: pointer;
748
+ }
749
+
750
+ .pp-edit-save {
751
+ padding: 8px 16px;
752
+ border: none;
753
+ border-radius: 6px;
754
+ background: ${primaryColor};
755
+ color: white;
756
+ font-size: 13px;
757
+ cursor: pointer;
758
+ }
759
+
760
+ .pp-edit-save:disabled {
761
+ opacity: 0.5;
762
+ cursor: not-allowed;
763
+ }
764
+
765
+ /* Reply Preview */
766
+ .pp-reply-preview {
767
+ display: flex;
768
+ align-items: center;
769
+ gap: 8px;
770
+ padding: 8px 12px;
771
+ background: ${isDark ? "rgba(255,255,255,0.05)" : "rgba(0,0,0,0.03)"};
772
+ border-top: 1px solid ${colors.border};
773
+ border-left: 3px solid ${primaryColor};
774
+ }
775
+
776
+ .pp-reply-preview-content {
777
+ flex: 1;
778
+ min-width: 0;
779
+ }
780
+
781
+ .pp-reply-label {
782
+ display: block;
783
+ font-size: 11px;
784
+ color: ${primaryColor};
785
+ font-weight: 500;
786
+ margin-bottom: 2px;
787
+ }
788
+
789
+ .pp-reply-text {
790
+ display: block;
791
+ font-size: 12px;
792
+ color: ${colors.textSecondary};
793
+ white-space: nowrap;
794
+ overflow: hidden;
795
+ text-overflow: ellipsis;
796
+ }
797
+
798
+ .pp-reply-cancel {
799
+ background: transparent;
800
+ border: none;
801
+ color: ${colors.textSecondary};
802
+ cursor: pointer;
803
+ padding: 4px;
804
+ flex-shrink: 0;
805
+ }
806
+
807
+ .pp-reply-cancel svg {
808
+ width: 16px;
809
+ height: 16px;
810
+ }
811
+
812
+ /* Reply Quote in Message */
813
+ .pp-reply-quote {
814
+ background: ${isDark ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.05)"};
815
+ border-left: 2px solid ${primaryColor};
816
+ padding: 4px 8px;
817
+ margin-bottom: 6px;
818
+ border-radius: 0 4px 4px 0;
819
+ font-size: 12px;
820
+ }
821
+
822
+ .pp-reply-sender {
823
+ display: block;
824
+ font-weight: 500;
825
+ color: ${primaryColor};
826
+ margin-bottom: 2px;
827
+ }
828
+
829
+ .pp-reply-content {
830
+ display: block;
831
+ color: ${colors.textSecondary};
832
+ white-space: nowrap;
833
+ overflow: hidden;
834
+ text-overflow: ellipsis;
835
+ }
836
+
837
+ /* Deleted Message */
838
+ .pp-message-deleted {
839
+ opacity: 0.6;
840
+ }
841
+
842
+ .pp-deleted-content {
843
+ font-style: italic;
844
+ color: ${colors.textSecondary};
845
+ display: flex;
846
+ align-items: center;
847
+ gap: 4px;
848
+ }
849
+
850
+ .pp-deleted-icon {
851
+ font-size: 12px;
852
+ }
853
+
854
+ /* Edited Badge */
855
+ .pp-edited-badge {
856
+ font-size: 10px;
857
+ color: ${colors.textSecondary};
858
+ margin-left: 4px;
859
+ font-style: italic;
860
+ }
375
861
  `;
376
862
  }
377
863
 
@@ -385,9 +871,18 @@ function ChatWidget({ client: client2, config: initialConfig }) {
385
871
  const [operatorOnline, setOperatorOnline] = useState(false);
386
872
  const [isConnected, setIsConnected] = useState(false);
387
873
  const [unreadCount, setUnreadCount] = useState(0);
874
+ const [pendingAttachments, setPendingAttachments] = useState([]);
875
+ const [isUploading, setIsUploading] = useState(false);
876
+ const [replyingTo, setReplyingTo] = useState(null);
877
+ const [editingMessage, setEditingMessage] = useState(null);
878
+ const [editContent, setEditContent] = useState("");
879
+ const [messageMenu, setMessageMenu] = useState(null);
880
+ const [isDragging, setIsDragging] = useState(false);
388
881
  const [config, setConfig] = useState(initialConfig);
389
882
  const messagesEndRef = useRef(null);
390
883
  const inputRef = useRef(null);
884
+ const fileInputRef = useRef(null);
885
+ const messagesContainerRef = useRef(null);
391
886
  useEffect(() => {
392
887
  const unsubOpen = client2.on("openChange", setIsOpen);
393
888
  const unsubMessage = client2.on("message", () => {
@@ -484,11 +979,17 @@ function ChatWidget({ client: client2, config: initialConfig }) {
484
979
  if (!shouldShow) return null;
485
980
  const handleSubmit = async (e) => {
486
981
  e.preventDefault();
487
- if (!inputValue.trim()) return;
982
+ const hasContent = inputValue.trim().length > 0;
983
+ const readyAttachments = pendingAttachments.filter((a) => a.status === "ready" && a.attachment);
984
+ if (!hasContent && readyAttachments.length === 0) return;
488
985
  const content = inputValue;
986
+ const attachmentIds = readyAttachments.map((a) => a.attachment.id);
987
+ const replyToId = replyingTo?.id;
489
988
  setInputValue("");
989
+ setPendingAttachments([]);
990
+ setReplyingTo(null);
490
991
  try {
491
- await client2.sendMessage(content);
992
+ await client2.sendMessage(content, attachmentIds, replyToId);
492
993
  } catch (err) {
493
994
  console.error("[PocketPing] Failed to send message:", err);
494
995
  }
@@ -498,6 +999,190 @@ function ChatWidget({ client: client2, config: initialConfig }) {
498
999
  setInputValue(target.value);
499
1000
  client2.sendTyping(true);
500
1001
  };
1002
+ const handleFileSelect = async (e) => {
1003
+ const target = e.target;
1004
+ const files = target.files;
1005
+ if (!files || files.length === 0) return;
1006
+ const newPending = [];
1007
+ for (let i = 0; i < files.length; i++) {
1008
+ const file = files[i];
1009
+ const id = `pending-${Date.now()}-${i}`;
1010
+ let preview;
1011
+ if (file.type.startsWith("image/")) {
1012
+ preview = URL.createObjectURL(file);
1013
+ }
1014
+ newPending.push({
1015
+ id,
1016
+ file,
1017
+ preview,
1018
+ progress: 0,
1019
+ status: "pending"
1020
+ });
1021
+ }
1022
+ setPendingAttachments((prev) => [...prev, ...newPending]);
1023
+ target.value = "";
1024
+ setIsUploading(true);
1025
+ for (const pending of newPending) {
1026
+ try {
1027
+ setPendingAttachments(
1028
+ (prev) => prev.map((a) => a.id === pending.id ? { ...a, status: "uploading" } : a)
1029
+ );
1030
+ const attachment = await client2.uploadFile(pending.file, (progress) => {
1031
+ setPendingAttachments(
1032
+ (prev) => prev.map((a) => a.id === pending.id ? { ...a, progress } : a)
1033
+ );
1034
+ });
1035
+ setPendingAttachments(
1036
+ (prev) => prev.map(
1037
+ (a) => a.id === pending.id ? { ...a, status: "ready", progress: 100, attachment } : a
1038
+ )
1039
+ );
1040
+ } catch (err) {
1041
+ console.error("[PocketPing] Failed to upload file:", err);
1042
+ setPendingAttachments(
1043
+ (prev) => prev.map(
1044
+ (a) => a.id === pending.id ? { ...a, status: "error", error: "Upload failed" } : a
1045
+ )
1046
+ );
1047
+ }
1048
+ }
1049
+ setIsUploading(false);
1050
+ };
1051
+ const handleRemoveAttachment = (id) => {
1052
+ setPendingAttachments((prev) => {
1053
+ const removed = prev.find((a) => a.id === id);
1054
+ if (removed?.preview) {
1055
+ URL.revokeObjectURL(removed.preview);
1056
+ }
1057
+ return prev.filter((a) => a.id !== id);
1058
+ });
1059
+ };
1060
+ const handleReply = (message) => {
1061
+ setReplyingTo(message);
1062
+ setMessageMenu(null);
1063
+ inputRef.current?.focus();
1064
+ };
1065
+ const handleCancelReply = () => {
1066
+ setReplyingTo(null);
1067
+ };
1068
+ const handleStartEdit = (message) => {
1069
+ if (message.sender !== "visitor") return;
1070
+ setEditingMessage(message);
1071
+ setEditContent(message.content);
1072
+ setMessageMenu(null);
1073
+ };
1074
+ const handleCancelEdit = () => {
1075
+ setEditingMessage(null);
1076
+ setEditContent("");
1077
+ };
1078
+ const handleSaveEdit = async () => {
1079
+ if (!editingMessage || !editContent.trim()) return;
1080
+ try {
1081
+ await client2.editMessage(editingMessage.id, editContent.trim());
1082
+ setEditingMessage(null);
1083
+ setEditContent("");
1084
+ } catch (err) {
1085
+ console.error("[PocketPing] Failed to edit message:", err);
1086
+ }
1087
+ };
1088
+ const handleDelete = async (message) => {
1089
+ if (message.sender !== "visitor") return;
1090
+ setMessageMenu(null);
1091
+ if (confirm("Delete this message?")) {
1092
+ try {
1093
+ await client2.deleteMessage(message.id);
1094
+ } catch (err) {
1095
+ console.error("[PocketPing] Failed to delete message:", err);
1096
+ }
1097
+ }
1098
+ };
1099
+ const handleMessageContextMenu = (e, message) => {
1100
+ e.preventDefault();
1101
+ const mouseEvent = e;
1102
+ setMessageMenu({
1103
+ message,
1104
+ x: mouseEvent.clientX,
1105
+ y: mouseEvent.clientY
1106
+ });
1107
+ };
1108
+ useEffect(() => {
1109
+ if (!messageMenu) return;
1110
+ const handleClickOutside = () => setMessageMenu(null);
1111
+ document.addEventListener("click", handleClickOutside);
1112
+ return () => document.removeEventListener("click", handleClickOutside);
1113
+ }, [messageMenu]);
1114
+ const dragCounterRef = useRef(0);
1115
+ const handleDragEnter = (e) => {
1116
+ e.preventDefault();
1117
+ e.stopPropagation();
1118
+ dragCounterRef.current++;
1119
+ if (dragCounterRef.current === 1) {
1120
+ setIsDragging(true);
1121
+ }
1122
+ };
1123
+ const handleDragOver = (e) => {
1124
+ e.preventDefault();
1125
+ e.stopPropagation();
1126
+ };
1127
+ const handleDragLeave = (e) => {
1128
+ e.preventDefault();
1129
+ e.stopPropagation();
1130
+ dragCounterRef.current--;
1131
+ if (dragCounterRef.current === 0) {
1132
+ setIsDragging(false);
1133
+ }
1134
+ };
1135
+ const handleDrop = async (e) => {
1136
+ e.preventDefault();
1137
+ e.stopPropagation();
1138
+ dragCounterRef.current = 0;
1139
+ setIsDragging(false);
1140
+ const files = e.dataTransfer?.files;
1141
+ if (!files || files.length === 0) return;
1142
+ const newPending = [];
1143
+ for (let i = 0; i < files.length; i++) {
1144
+ const file = files[i];
1145
+ const id = `pending-${Date.now()}-${i}`;
1146
+ let preview;
1147
+ if (file.type.startsWith("image/")) {
1148
+ preview = URL.createObjectURL(file);
1149
+ }
1150
+ newPending.push({
1151
+ id,
1152
+ file,
1153
+ preview,
1154
+ progress: 0,
1155
+ status: "pending"
1156
+ });
1157
+ }
1158
+ setPendingAttachments((prev) => [...prev, ...newPending]);
1159
+ setIsUploading(true);
1160
+ for (const pending of newPending) {
1161
+ try {
1162
+ setPendingAttachments(
1163
+ (prev) => prev.map((a) => a.id === pending.id ? { ...a, status: "uploading" } : a)
1164
+ );
1165
+ const attachment = await client2.uploadFile(pending.file, (progress) => {
1166
+ setPendingAttachments(
1167
+ (prev) => prev.map((a) => a.id === pending.id ? { ...a, progress } : a)
1168
+ );
1169
+ });
1170
+ setPendingAttachments(
1171
+ (prev) => prev.map(
1172
+ (a) => a.id === pending.id ? { ...a, status: "ready", progress: 100, attachment } : a
1173
+ )
1174
+ );
1175
+ } catch (err) {
1176
+ console.error("[PocketPing] Failed to upload dropped file:", err);
1177
+ setPendingAttachments(
1178
+ (prev) => prev.map(
1179
+ (a) => a.id === pending.id ? { ...a, status: "error", error: "Upload failed" } : a
1180
+ )
1181
+ );
1182
+ }
1183
+ }
1184
+ setIsUploading(false);
1185
+ };
501
1186
  const position = config.position ?? "bottom-right";
502
1187
  const theme = getTheme(config.theme ?? "auto");
503
1188
  const primaryColor = config.primaryColor ?? "#6366f1";
@@ -516,84 +1201,211 @@ function ChatWidget({ client: client2, config: initialConfig }) {
516
1201
  ]
517
1202
  }
518
1203
  ),
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 }) })
1204
+ isOpen && /* @__PURE__ */ jsxs(
1205
+ "div",
1206
+ {
1207
+ class: `pp-window pp-${position} pp-theme-${theme} ${isDragging ? "pp-dragging" : ""}`,
1208
+ onDragEnter: handleDragEnter,
1209
+ onDragOver: handleDragOver,
1210
+ onDragLeave: handleDragLeave,
1211
+ onDrop: handleDrop,
1212
+ children: [
1213
+ isDragging && /* @__PURE__ */ jsxs("div", { class: "pp-drop-overlay", children: [
1214
+ /* @__PURE__ */ jsx("div", { class: "pp-drop-icon", children: /* @__PURE__ */ jsx(AttachIcon, {}) }),
1215
+ /* @__PURE__ */ jsx("div", { class: "pp-drop-text", children: "Drop files to upload" })
1216
+ ] }),
1217
+ /* @__PURE__ */ jsxs("div", { class: "pp-header", children: [
1218
+ /* @__PURE__ */ jsxs("div", { class: "pp-header-info", children: [
1219
+ config.operatorAvatar && /* @__PURE__ */ jsx("img", { src: config.operatorAvatar, alt: "", class: "pp-avatar" }),
1220
+ /* @__PURE__ */ jsxs("div", { children: [
1221
+ /* @__PURE__ */ jsx("div", { class: "pp-header-title", children: config.operatorName ?? "Support" }),
1222
+ /* @__PURE__ */ jsx("div", { class: "pp-header-status", children: operatorOnline ? /* @__PURE__ */ jsxs(Fragment2, { children: [
1223
+ /* @__PURE__ */ jsx("span", { class: "pp-status-dot pp-online" }),
1224
+ " Online"
1225
+ ] }) : /* @__PURE__ */ jsxs(Fragment2, { children: [
1226
+ /* @__PURE__ */ jsx("span", { class: "pp-status-dot" }),
1227
+ " Away"
1228
+ ] }) })
556
1229
  ] })
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
- ] })
1230
+ ] }),
1231
+ /* @__PURE__ */ jsx(
1232
+ "button",
1233
+ {
1234
+ class: "pp-close-btn",
1235
+ onClick: () => client2.setOpen(false),
1236
+ "aria-label": "Close chat",
1237
+ children: /* @__PURE__ */ jsx(CloseIcon, {})
1238
+ }
1239
+ )
1240
+ ] }),
1241
+ /* @__PURE__ */ jsxs("div", { class: "pp-messages", ref: messagesContainerRef, children: [
1242
+ config.welcomeMessage && messages.length === 0 && /* @__PURE__ */ jsx("div", { class: "pp-welcome", children: config.welcomeMessage }),
1243
+ messages.map((msg) => {
1244
+ const isDeleted = !!msg.deletedAt;
1245
+ const isEdited = !!msg.editedAt;
1246
+ const replyToMsg = msg.replyTo ? messages.find((m) => m.id === msg.replyTo) : null;
1247
+ return /* @__PURE__ */ jsxs(
1248
+ "div",
1249
+ {
1250
+ class: `pp-message pp-message-${msg.sender} ${isDeleted ? "pp-message-deleted" : ""}`,
1251
+ onContextMenu: (e) => handleMessageContextMenu(e, msg),
1252
+ children: [
1253
+ replyToMsg && /* @__PURE__ */ jsxs("div", { class: "pp-reply-quote", children: [
1254
+ /* @__PURE__ */ jsx("span", { class: "pp-reply-sender", children: replyToMsg.sender === "visitor" ? "You" : "Support" }),
1255
+ /* @__PURE__ */ jsxs("span", { class: "pp-reply-content", children: [
1256
+ replyToMsg.deletedAt ? "Message deleted" : replyToMsg.content.slice(0, 50),
1257
+ replyToMsg.content.length > 50 ? "..." : ""
1258
+ ] })
1259
+ ] }),
1260
+ isDeleted ? /* @__PURE__ */ jsxs("div", { class: "pp-message-content pp-deleted-content", children: [
1261
+ /* @__PURE__ */ jsx("span", { class: "pp-deleted-icon", children: "\u{1F5D1}\uFE0F" }),
1262
+ " Message deleted"
1263
+ ] }) : /* @__PURE__ */ jsxs(Fragment2, { children: [
1264
+ msg.content && /* @__PURE__ */ jsx("div", { class: "pp-message-content", children: msg.content }),
1265
+ 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)) })
1266
+ ] }),
1267
+ /* @__PURE__ */ jsxs("div", { class: "pp-message-time", children: [
1268
+ formatTime(msg.timestamp),
1269
+ isEdited && !isDeleted && /* @__PURE__ */ jsx("span", { class: "pp-edited-badge", children: "edited" }),
1270
+ msg.sender === "ai" && /* @__PURE__ */ jsx("span", { class: "pp-ai-badge", children: "AI" }),
1271
+ msg.sender === "visitor" && !isDeleted && /* @__PURE__ */ jsx("span", { class: `pp-status pp-status-${msg.status ?? "sent"}`, children: /* @__PURE__ */ jsx(StatusIcon, { status: msg.status }) })
1272
+ ] })
1273
+ ]
1274
+ },
1275
+ msg.id
1276
+ );
1277
+ }),
1278
+ isTyping && /* @__PURE__ */ jsxs("div", { class: "pp-message pp-message-operator pp-typing", children: [
1279
+ /* @__PURE__ */ jsx("span", {}),
1280
+ /* @__PURE__ */ jsx("span", {}),
1281
+ /* @__PURE__ */ jsx("span", {})
1282
+ ] }),
1283
+ /* @__PURE__ */ jsx("div", { ref: messagesEndRef })
1284
+ ] }),
1285
+ messageMenu && /* @__PURE__ */ jsxs(
1286
+ "div",
1287
+ {
1288
+ class: "pp-message-menu",
1289
+ style: { top: `${messageMenu.y}px`, left: `${messageMenu.x}px` },
1290
+ children: [
1291
+ /* @__PURE__ */ jsxs("button", { onClick: () => handleReply(messageMenu.message), children: [
1292
+ /* @__PURE__ */ jsx(ReplyIcon, {}),
1293
+ " Reply"
1294
+ ] }),
1295
+ messageMenu.message.sender === "visitor" && !messageMenu.message.deletedAt && /* @__PURE__ */ jsxs(Fragment2, { children: [
1296
+ /* @__PURE__ */ jsxs("button", { onClick: () => handleStartEdit(messageMenu.message), children: [
1297
+ /* @__PURE__ */ jsx(EditIcon, {}),
1298
+ " Edit"
1299
+ ] }),
1300
+ /* @__PURE__ */ jsxs("button", { class: "pp-menu-delete", onClick: () => handleDelete(messageMenu.message), children: [
1301
+ /* @__PURE__ */ jsx(DeleteIcon, {}),
1302
+ " Delete"
1303
+ ] })
1304
+ ] })
1305
+ ]
1306
+ }
1307
+ ),
1308
+ editingMessage && /* @__PURE__ */ jsxs("div", { class: "pp-edit-modal", children: [
1309
+ /* @__PURE__ */ jsxs("div", { class: "pp-edit-header", children: [
1310
+ /* @__PURE__ */ jsx("span", { children: "Edit message" }),
1311
+ /* @__PURE__ */ jsx("button", { onClick: handleCancelEdit, children: /* @__PURE__ */ jsx(CloseIcon, {}) })
1312
+ ] }),
1313
+ /* @__PURE__ */ jsx(
1314
+ "textarea",
1315
+ {
1316
+ class: "pp-edit-input",
1317
+ value: editContent,
1318
+ onInput: (e) => setEditContent(e.target.value),
1319
+ autoFocus: true
1320
+ }
1321
+ ),
1322
+ /* @__PURE__ */ jsxs("div", { class: "pp-edit-actions", children: [
1323
+ /* @__PURE__ */ jsx("button", { class: "pp-edit-cancel", onClick: handleCancelEdit, children: "Cancel" }),
1324
+ /* @__PURE__ */ jsx("button", { class: "pp-edit-save", onClick: handleSaveEdit, disabled: !editContent.trim(), children: "Save" })
1325
+ ] })
1326
+ ] }),
1327
+ replyingTo && /* @__PURE__ */ jsxs("div", { class: "pp-reply-preview", children: [
1328
+ /* @__PURE__ */ jsxs("div", { class: "pp-reply-preview-content", children: [
1329
+ /* @__PURE__ */ jsx("span", { class: "pp-reply-label", children: "Replying to" }),
1330
+ /* @__PURE__ */ jsxs("span", { class: "pp-reply-text", children: [
1331
+ replyingTo.content.slice(0, 50),
1332
+ replyingTo.content.length > 50 ? "..." : ""
1333
+ ] })
1334
+ ] }),
1335
+ /* @__PURE__ */ jsx("button", { class: "pp-reply-cancel", onClick: handleCancelReply, children: /* @__PURE__ */ jsx(CloseIcon, {}) })
1336
+ ] }),
1337
+ 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: [
1338
+ 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 }) }),
1339
+ /* @__PURE__ */ jsx(
1340
+ "button",
1341
+ {
1342
+ class: "pp-remove-attachment",
1343
+ onClick: () => handleRemoveAttachment(pending.id),
1344
+ "aria-label": "Remove attachment",
1345
+ type: "button",
1346
+ children: /* @__PURE__ */ jsx(CloseIcon, {})
1347
+ }
1348
+ ),
1349
+ pending.status === "uploading" && /* @__PURE__ */ jsx("div", { class: "pp-upload-progress", style: { width: `${pending.progress}%` } }),
1350
+ pending.status === "error" && /* @__PURE__ */ jsx("div", { class: "pp-upload-error", title: pending.error, children: "!" })
1351
+ ] }, pending.id)) }),
1352
+ /* @__PURE__ */ jsxs("form", { class: "pp-input-form", onSubmit: handleSubmit, children: [
1353
+ /* @__PURE__ */ jsx(
1354
+ "input",
1355
+ {
1356
+ ref: (el) => {
1357
+ fileInputRef.current = el;
1358
+ if (el) {
1359
+ el.onchange = handleFileSelect;
1360
+ }
1361
+ },
1362
+ type: "file",
1363
+ class: "pp-file-input",
1364
+ accept: "image/*,audio/*,video/*,.pdf,.doc,.docx,.xls,.xlsx,.txt",
1365
+ multiple: true
1366
+ }
1367
+ ),
1368
+ /* @__PURE__ */ jsx(
1369
+ "button",
1370
+ {
1371
+ type: "button",
1372
+ class: "pp-attach-btn",
1373
+ onClick: () => fileInputRef.current?.click(),
1374
+ disabled: !isConnected || isUploading,
1375
+ "aria-label": "Attach file",
1376
+ children: /* @__PURE__ */ jsx(AttachIcon, {})
1377
+ }
1378
+ ),
1379
+ /* @__PURE__ */ jsx(
1380
+ "input",
1381
+ {
1382
+ ref: inputRef,
1383
+ type: "text",
1384
+ class: "pp-input",
1385
+ placeholder: config.placeholder ?? "Type a message...",
1386
+ value: inputValue,
1387
+ onInput: handleInputChange,
1388
+ disabled: !isConnected
1389
+ }
1390
+ ),
1391
+ /* @__PURE__ */ jsx(
1392
+ "button",
1393
+ {
1394
+ type: "submit",
1395
+ class: "pp-send-btn",
1396
+ disabled: !inputValue.trim() && pendingAttachments.filter((a) => a.status === "ready").length === 0 || !isConnected || isUploading,
1397
+ "aria-label": "Send message",
1398
+ children: /* @__PURE__ */ jsx(SendIcon, {})
1399
+ }
1400
+ )
1401
+ ] }),
1402
+ /* @__PURE__ */ jsxs("div", { class: "pp-footer", children: [
1403
+ "Powered by ",
1404
+ /* @__PURE__ */ jsx("a", { href: "https://pocketping.io", target: "_blank", rel: "noopener", children: "PocketPing" })
1405
+ ] })
1406
+ ]
1407
+ }
1408
+ )
597
1409
  ] });
598
1410
  }
599
1411
  function checkPageVisibility(config) {
@@ -649,6 +1461,86 @@ function StatusIcon({ status }) {
649
1461
  }
650
1462
  return null;
651
1463
  }
1464
+ function AttachIcon() {
1465
+ 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" }) });
1466
+ }
1467
+ function ReplyIcon() {
1468
+ return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", children: [
1469
+ /* @__PURE__ */ jsx("polyline", { points: "9 17 4 12 9 7" }),
1470
+ /* @__PURE__ */ jsx("path", { d: "M20 18v-2a4 4 0 0 0-4-4H4" })
1471
+ ] });
1472
+ }
1473
+ function EditIcon() {
1474
+ return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", children: /* @__PURE__ */ jsx("path", { d: "M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z" }) });
1475
+ }
1476
+ function DeleteIcon() {
1477
+ return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", children: [
1478
+ /* @__PURE__ */ jsx("polyline", { points: "3 6 5 6 21 6" }),
1479
+ /* @__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" })
1480
+ ] });
1481
+ }
1482
+ function FileIcon({ mimeType }) {
1483
+ if (mimeType === "application/pdf") {
1484
+ return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", children: [
1485
+ /* @__PURE__ */ jsx("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
1486
+ /* @__PURE__ */ jsx("polyline", { points: "14 2 14 8 20 8" }),
1487
+ /* @__PURE__ */ jsx("path", { d: "M9 15h6" }),
1488
+ /* @__PURE__ */ jsx("path", { d: "M9 11h6" })
1489
+ ] });
1490
+ }
1491
+ if (mimeType.startsWith("audio/")) {
1492
+ return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", children: [
1493
+ /* @__PURE__ */ jsx("path", { d: "M9 18V5l12-2v13" }),
1494
+ /* @__PURE__ */ jsx("circle", { cx: "6", cy: "18", r: "3" }),
1495
+ /* @__PURE__ */ jsx("circle", { cx: "18", cy: "16", r: "3" })
1496
+ ] });
1497
+ }
1498
+ if (mimeType.startsWith("video/")) {
1499
+ return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", children: [
1500
+ /* @__PURE__ */ jsx("rect", { x: "2", y: "2", width: "20", height: "20", rx: "2.18", ry: "2.18" }),
1501
+ /* @__PURE__ */ jsx("line", { x1: "7", y1: "2", x2: "7", y2: "22" }),
1502
+ /* @__PURE__ */ jsx("line", { x1: "17", y1: "2", x2: "17", y2: "22" }),
1503
+ /* @__PURE__ */ jsx("line", { x1: "2", y1: "12", x2: "22", y2: "12" }),
1504
+ /* @__PURE__ */ jsx("line", { x1: "2", y1: "7", x2: "7", y2: "7" }),
1505
+ /* @__PURE__ */ jsx("line", { x1: "2", y1: "17", x2: "7", y2: "17" }),
1506
+ /* @__PURE__ */ jsx("line", { x1: "17", y1: "17", x2: "22", y2: "17" }),
1507
+ /* @__PURE__ */ jsx("line", { x1: "17", y1: "7", x2: "22", y2: "7" })
1508
+ ] });
1509
+ }
1510
+ return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", children: [
1511
+ /* @__PURE__ */ jsx("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
1512
+ /* @__PURE__ */ jsx("polyline", { points: "14 2 14 8 20 8" })
1513
+ ] });
1514
+ }
1515
+ function AttachmentDisplay({ attachment }) {
1516
+ const isImage = attachment.mimeType.startsWith("image/");
1517
+ const isAudio = attachment.mimeType.startsWith("audio/");
1518
+ const isVideo = attachment.mimeType.startsWith("video/");
1519
+ const formatSize = (bytes) => {
1520
+ if (bytes < 1024) return `${bytes} B`;
1521
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
1522
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1523
+ };
1524
+ if (isImage) {
1525
+ 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 }) });
1526
+ }
1527
+ if (isAudio) {
1528
+ return /* @__PURE__ */ jsxs("div", { class: "pp-attachment pp-attachment-audio", children: [
1529
+ /* @__PURE__ */ jsx("audio", { controls: true, preload: "metadata", children: /* @__PURE__ */ jsx("source", { src: attachment.url, type: attachment.mimeType }) }),
1530
+ /* @__PURE__ */ jsx("span", { class: "pp-attachment-name", children: attachment.filename })
1531
+ ] });
1532
+ }
1533
+ if (isVideo) {
1534
+ 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 }) }) });
1535
+ }
1536
+ return /* @__PURE__ */ jsxs("a", { href: attachment.url, target: "_blank", rel: "noopener", class: "pp-attachment pp-attachment-file", children: [
1537
+ /* @__PURE__ */ jsx(FileIcon, { mimeType: attachment.mimeType }),
1538
+ /* @__PURE__ */ jsxs("div", { class: "pp-attachment-info", children: [
1539
+ /* @__PURE__ */ jsx("span", { class: "pp-attachment-name", children: attachment.filename }),
1540
+ /* @__PURE__ */ jsx("span", { class: "pp-attachment-size", children: formatSize(attachment.size) })
1541
+ ] })
1542
+ ] });
1543
+ }
652
1544
 
653
1545
  // src/version.ts
654
1546
  var VERSION = "0.3.6";
@@ -764,7 +1656,7 @@ var PocketPingClient = class {
764
1656
  this.cleanupTrackedElements();
765
1657
  this.disableInspectorMode();
766
1658
  }
767
- async sendMessage(content) {
1659
+ async sendMessage(content, attachmentIds, replyTo) {
768
1660
  if (!this.session) {
769
1661
  throw new Error("Not connected");
770
1662
  }
@@ -775,7 +1667,8 @@ var PocketPingClient = class {
775
1667
  content,
776
1668
  sender: "visitor",
777
1669
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
778
- status: "sending"
1670
+ status: "sending",
1671
+ replyTo
779
1672
  };
780
1673
  this.session.messages.push(tempMessage);
781
1674
  this.emit("message", tempMessage);
@@ -785,7 +1678,9 @@ var PocketPingClient = class {
785
1678
  body: JSON.stringify({
786
1679
  sessionId: this.session.sessionId,
787
1680
  content,
788
- sender: "visitor"
1681
+ sender: "visitor",
1682
+ attachmentIds: attachmentIds || [],
1683
+ replyTo
789
1684
  })
790
1685
  });
791
1686
  const messageIndex = this.session.messages.findIndex((m) => m.id === tempId);
@@ -793,6 +1688,9 @@ var PocketPingClient = class {
793
1688
  this.session.messages[messageIndex].id = response.messageId;
794
1689
  this.session.messages[messageIndex].timestamp = response.timestamp;
795
1690
  this.session.messages[messageIndex].status = "sent";
1691
+ if (response.attachments && response.attachments.length > 0) {
1692
+ this.session.messages[messageIndex].attachments = response.attachments;
1693
+ }
796
1694
  this.emit("message", this.session.messages[messageIndex]);
797
1695
  }
798
1696
  const message = this.session.messages[messageIndex] || {
@@ -801,7 +1699,8 @@ var PocketPingClient = class {
801
1699
  content,
802
1700
  sender: "visitor",
803
1701
  timestamp: response.timestamp,
804
- status: "sent"
1702
+ status: "sent",
1703
+ attachments: response.attachments
805
1704
  };
806
1705
  this.config.onMessage?.(message);
807
1706
  return message;
@@ -814,6 +1713,110 @@ var PocketPingClient = class {
814
1713
  throw error;
815
1714
  }
816
1715
  }
1716
+ /**
1717
+ * Upload a file attachment
1718
+ * Returns the attachment data after successful upload
1719
+ * @param file - File object to upload
1720
+ * @param onProgress - Optional callback for upload progress (0-100)
1721
+ * @example
1722
+ * const attachment = await PocketPing.uploadFile(file, (progress) => {
1723
+ * console.log(`Upload ${progress}% complete`)
1724
+ * })
1725
+ * await PocketPing.sendMessage('Check this file', [attachment.id])
1726
+ */
1727
+ async uploadFile(file, onProgress) {
1728
+ if (!this.session) {
1729
+ throw new Error("Not connected");
1730
+ }
1731
+ this.emit("uploadStart", { filename: file.name, size: file.size });
1732
+ try {
1733
+ const initResponse = await this.fetch("/upload", {
1734
+ method: "POST",
1735
+ body: JSON.stringify({
1736
+ sessionId: this.session.sessionId,
1737
+ filename: file.name,
1738
+ mimeType: file.type || "application/octet-stream",
1739
+ size: file.size
1740
+ })
1741
+ });
1742
+ onProgress?.(10);
1743
+ this.emit("uploadProgress", { filename: file.name, progress: 10 });
1744
+ await this.uploadToPresignedUrl(initResponse.uploadUrl, file, (progress) => {
1745
+ const mappedProgress = 10 + progress * 0.8;
1746
+ onProgress?.(mappedProgress);
1747
+ this.emit("uploadProgress", { filename: file.name, progress: mappedProgress });
1748
+ });
1749
+ const completeResponse = await this.fetch("/upload/complete", {
1750
+ method: "POST",
1751
+ body: JSON.stringify({
1752
+ sessionId: this.session.sessionId,
1753
+ attachmentId: initResponse.attachmentId
1754
+ })
1755
+ });
1756
+ onProgress?.(100);
1757
+ this.emit("uploadComplete", completeResponse);
1758
+ return {
1759
+ id: completeResponse.id,
1760
+ filename: completeResponse.filename,
1761
+ mimeType: completeResponse.mimeType,
1762
+ size: completeResponse.size,
1763
+ url: completeResponse.url,
1764
+ thumbnailUrl: completeResponse.thumbnailUrl,
1765
+ status: completeResponse.status
1766
+ };
1767
+ } catch (error) {
1768
+ this.emit("uploadError", { filename: file.name, error });
1769
+ throw error;
1770
+ }
1771
+ }
1772
+ /**
1773
+ * Upload multiple files at once
1774
+ * @param files - Array of File objects to upload
1775
+ * @param onProgress - Optional callback for overall progress (0-100)
1776
+ * @returns Array of uploaded attachments
1777
+ */
1778
+ async uploadFiles(files, onProgress) {
1779
+ const attachments = [];
1780
+ const totalFiles = files.length;
1781
+ for (let i = 0; i < totalFiles; i++) {
1782
+ const file = files[i];
1783
+ const baseProgress = i / totalFiles * 100;
1784
+ const fileProgress = 100 / totalFiles;
1785
+ const attachment = await this.uploadFile(file, (progress) => {
1786
+ const totalProgress = baseProgress + progress / 100 * fileProgress;
1787
+ onProgress?.(totalProgress);
1788
+ });
1789
+ attachments.push(attachment);
1790
+ }
1791
+ return attachments;
1792
+ }
1793
+ /**
1794
+ * Upload file to presigned URL with progress tracking
1795
+ */
1796
+ uploadToPresignedUrl(url, file, onProgress) {
1797
+ return new Promise((resolve, reject) => {
1798
+ const xhr = new XMLHttpRequest();
1799
+ xhr.upload.addEventListener("progress", (event) => {
1800
+ if (event.lengthComputable) {
1801
+ const progress = event.loaded / event.total * 100;
1802
+ onProgress?.(progress);
1803
+ }
1804
+ });
1805
+ xhr.addEventListener("load", () => {
1806
+ if (xhr.status >= 200 && xhr.status < 300) {
1807
+ resolve();
1808
+ } else {
1809
+ reject(new Error(`Upload failed with status ${xhr.status}`));
1810
+ }
1811
+ });
1812
+ xhr.addEventListener("error", () => {
1813
+ reject(new Error("Upload failed"));
1814
+ });
1815
+ xhr.open("PUT", url);
1816
+ xhr.setRequestHeader("Content-Type", file.type || "application/octet-stream");
1817
+ xhr.send(file);
1818
+ });
1819
+ }
817
1820
  async fetchMessages(after) {
818
1821
  if (!this.session) {
819
1822
  throw new Error("Not connected");
@@ -867,6 +1870,54 @@ var PocketPingClient = class {
867
1870
  console.error("[PocketPing] Failed to send read status:", err);
868
1871
  }
869
1872
  }
1873
+ /**
1874
+ * Edit a message (visitor can only edit their own messages)
1875
+ * @param messageId - ID of the message to edit
1876
+ * @param content - New content for the message
1877
+ */
1878
+ async editMessage(messageId, content) {
1879
+ if (!this.session) {
1880
+ throw new Error("Not connected");
1881
+ }
1882
+ const response = await this.fetch(`/message/${messageId}`, {
1883
+ method: "PATCH",
1884
+ body: JSON.stringify({
1885
+ sessionId: this.session.sessionId,
1886
+ content
1887
+ })
1888
+ });
1889
+ const messageIndex = this.session.messages.findIndex((m) => m.id === messageId);
1890
+ if (messageIndex >= 0) {
1891
+ this.session.messages[messageIndex].content = response.message.content;
1892
+ this.session.messages[messageIndex].editedAt = response.message.editedAt;
1893
+ this.emit("messageEdited", this.session.messages[messageIndex]);
1894
+ }
1895
+ return this.session.messages[messageIndex];
1896
+ }
1897
+ /**
1898
+ * Delete a message (soft delete - visitor can only delete their own messages)
1899
+ * @param messageId - ID of the message to delete
1900
+ */
1901
+ async deleteMessage(messageId) {
1902
+ if (!this.session) {
1903
+ throw new Error("Not connected");
1904
+ }
1905
+ const response = await this.fetch(`/message/${messageId}`, {
1906
+ method: "DELETE",
1907
+ body: JSON.stringify({
1908
+ sessionId: this.session.sessionId
1909
+ })
1910
+ });
1911
+ if (response.deleted) {
1912
+ const messageIndex = this.session.messages.findIndex((m) => m.id === messageId);
1913
+ if (messageIndex >= 0) {
1914
+ this.session.messages[messageIndex].deletedAt = (/* @__PURE__ */ new Date()).toISOString();
1915
+ this.session.messages[messageIndex].content = "";
1916
+ this.emit("messageDeleted", this.session.messages[messageIndex]);
1917
+ }
1918
+ }
1919
+ return response.deleted;
1920
+ }
870
1921
  async getPresence() {
871
1922
  return this.fetch("/presence", { method: "GET" });
872
1923
  }
@@ -1770,11 +2821,23 @@ function close() {
1770
2821
  function toggle() {
1771
2822
  client?.toggleOpen();
1772
2823
  }
1773
- function sendMessage(content) {
2824
+ function sendMessage(content, attachmentIds) {
2825
+ if (!client) {
2826
+ throw new Error("[PocketPing] Not initialized");
2827
+ }
2828
+ return client.sendMessage(content, attachmentIds);
2829
+ }
2830
+ async function uploadFile(file, onProgress) {
2831
+ if (!client) {
2832
+ throw new Error("[PocketPing] Not initialized");
2833
+ }
2834
+ return client.uploadFile(file, onProgress);
2835
+ }
2836
+ async function uploadFiles(files, onProgress) {
1774
2837
  if (!client) {
1775
2838
  throw new Error("[PocketPing] Not initialized");
1776
2839
  }
1777
- return client.sendMessage(content);
2840
+ return client.uploadFiles(files, onProgress);
1778
2841
  }
1779
2842
  function trigger(eventName, data, options) {
1780
2843
  if (!client) {
@@ -1843,7 +2906,7 @@ if (typeof document !== "undefined") {
1843
2906
  }
1844
2907
  }
1845
2908
  }
1846
- var index_default = { init, destroy, open, close, toggle, sendMessage, trigger, onEvent, offEvent, on, identify, reset, getIdentity, setupTrackedElements, getTrackedElements };
2909
+ var index_default = { init, destroy, open, close, toggle, sendMessage, uploadFile, uploadFiles, trigger, onEvent, offEvent, on, identify, reset, getIdentity, setupTrackedElements, getTrackedElements };
1847
2910
  export {
1848
2911
  close,
1849
2912
  index_default as default,
@@ -1860,5 +2923,7 @@ export {
1860
2923
  sendMessage,
1861
2924
  setupTrackedElements,
1862
2925
  toggle,
1863
- trigger
2926
+ trigger,
2927
+ uploadFile,
2928
+ uploadFiles
1864
2929
  };