@myrjfa/ui 1.0.9 → 1.0.10
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/chat/ChatMain.d.ts +8 -8
- package/dist/chat/ChatMain.d.ts.map +1 -1
- package/dist/chat/ChatMain.js +272 -239
- package/dist/chat/ChatSidebar.js +7 -8
- package/dist/chat/ConversationList.d.ts.map +1 -1
- package/dist/chat/ConversationList.js +20 -18
- package/dist/chat/ExpensePanel.d.ts +4 -1
- package/dist/chat/ExpensePanel.d.ts.map +1 -1
- package/dist/chat/ExpensePanel.js +215 -195
- package/dist/chat/MessageBubble.d.ts +7 -4
- package/dist/chat/MessageBubble.d.ts.map +1 -1
- package/dist/chat/MessageBubble.js +185 -98
- package/dist/chat/MessageInput.d.ts +8 -1
- package/dist/chat/MessageInput.d.ts.map +1 -1
- package/dist/chat/MessageInput.js +182 -165
- package/dist/chat/MilestonePanel.d.ts +4 -1
- package/dist/chat/MilestonePanel.d.ts.map +1 -1
- package/dist/chat/MilestonePanel.js +150 -150
- package/dist/chat/components/ParticipantSelector.d.ts +11 -0
- package/dist/chat/components/ParticipantSelector.d.ts.map +1 -0
- package/dist/chat/components/ParticipantSelector.js +17 -0
- package/dist/chat/components/SplitInputs.d.ts +11 -0
- package/dist/chat/components/SplitInputs.d.ts.map +1 -0
- package/dist/chat/components/SplitInputs.js +83 -0
- package/dist/chat/components/SplitTypeSelector.d.ts +8 -0
- package/dist/chat/components/SplitTypeSelector.d.ts.map +1 -0
- package/dist/chat/components/SplitTypeSelector.js +13 -0
- package/dist/chat/dialogs/AddMemberDialog.js +30 -90
- package/dist/chat/dialogs/CameraDialog.js +38 -86
- package/dist/chat/dialogs/CreatePollDialog.d.ts +8 -0
- package/dist/chat/dialogs/CreatePollDialog.d.ts.map +1 -0
- package/dist/chat/dialogs/CreatePollDialog.js +38 -0
- package/dist/chat/dialogs/ExpenseAnalyticsDialog.d.ts +19 -0
- package/dist/chat/dialogs/ExpenseAnalyticsDialog.d.ts.map +1 -0
- package/dist/chat/dialogs/ExpenseAnalyticsDialog.js +52 -0
- package/dist/chat/dialogs/GroupInfoDialog.d.ts.map +1 -1
- package/dist/chat/dialogs/GroupInfoDialog.js +55 -92
- package/dist/chat/dialogs/GroupInfoViewDialog.js +5 -6
- package/dist/chat/dialogs/NewChatDialog.d.ts.map +1 -1
- package/dist/chat/dialogs/NewChatDialog.js +49 -113
- package/dist/chat/dialogs/NewGroupDialog.d.ts.map +1 -1
- package/dist/chat/dialogs/NewGroupDialog.js +49 -92
- package/dist/chat/dialogs/ParticipantsDialog.d.ts.map +1 -1
- package/dist/chat/dialogs/ParticipantsDialog.js +68 -118
- package/dist/chat/dialogs/UserProfileDialog.d.ts +10 -0
- package/dist/chat/dialogs/UserProfileDialog.d.ts.map +1 -0
- package/dist/chat/dialogs/UserProfileDialog.js +100 -0
- package/dist/dashboard/action-buttons.js +4 -5
- package/dist/dashboard/app-sidebar.js +46 -128
- package/dist/dashboard/card-header.js +1 -2
- package/dist/dashboard/dashboard-components.js +3 -5
- package/dist/dashboard/data-table.d.ts +18 -18
- package/dist/dashboard/data-table.js +54 -66
- package/dist/dashboard/nav-main.js +4 -5
- package/dist/dashboard/nav-secondary.js +2 -25
- package/dist/dashboard/stats-card.js +1 -2
- package/dist/dashboard/table.js +8 -54
- package/dist/header/mobile-nav-item.js +1 -2
- package/dist/header/mobile-navbar.js +8 -9
- package/dist/header/nav-item.js +1 -2
- package/dist/index.d.ts +57 -56
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +57 -57
- package/dist/main/footer.js +5 -9
- package/dist/main/navbar.js +54 -137
- package/dist/main/paginationBar.js +12 -14
- package/dist/main/portfolio/portfolioItem.js +1 -2
- package/dist/main/portfolio/portfolioPopup.js +16 -17
- package/dist/main/review-page.d.ts.map +1 -1
- package/dist/main/review-page.js +131 -198
- package/dist/misc/accordion.js +8 -34
- package/dist/misc/alert-dialog.js +22 -55
- package/dist/misc/alert.js +7 -32
- package/dist/misc/alertdialogclaude.js +50 -125
- package/dist/misc/avatar.js +6 -31
- package/dist/misc/badge.d.ts +8 -8
- package/dist/misc/badge.js +4 -27
- package/dist/misc/button.d.ts +9 -9
- package/dist/misc/button.js +4 -27
- package/dist/misc/card.js +12 -40
- package/dist/misc/carousel.js +37 -61
- package/dist/misc/checkbox.js +2 -25
- package/dist/misc/command.js +18 -49
- package/dist/misc/datepicker.js +66 -76
- package/dist/misc/dialog.js +20 -52
- package/dist/misc/dropdown-menu.js +30 -67
- package/dist/misc/faq.js +2 -3
- package/dist/misc/file-preview.js +43 -97
- package/dist/misc/filter.d.ts.map +1 -1
- package/dist/misc/filter.js +18 -21
- package/dist/misc/filterBar.js +136 -160
- package/dist/misc/form.js +39 -60
- package/dist/misc/image-cropper.d.ts +10 -0
- package/dist/misc/image-cropper.d.ts.map +1 -0
- package/dist/misc/image-cropper.js +44 -0
- package/dist/misc/input.js +2 -25
- package/dist/misc/label.js +2 -25
- package/dist/misc/line-carousel.js +3 -13
- package/dist/misc/multiImageUploader.d.ts +8 -8
- package/dist/misc/multiImageUploader.js +10 -11
- package/dist/misc/newsLetter.js +18 -78
- package/dist/misc/notification.d.ts +1 -2
- package/dist/misc/notification.d.ts.map +1 -1
- package/dist/misc/notification.js +89 -125
- package/dist/misc/pagination.js +16 -45
- package/dist/misc/password-eye.js +2 -3
- package/dist/misc/popover.d.ts.map +1 -1
- package/dist/misc/popover.js +8 -34
- package/dist/misc/progress.js +2 -25
- package/dist/misc/scroll-area.js +5 -29
- package/dist/misc/searchBar.js +27 -31
- package/dist/misc/searchBox.js +18 -22
- package/dist/misc/select.d.ts.map +1 -1
- package/dist/misc/select.js +22 -54
- package/dist/misc/separator.js +2 -25
- package/dist/misc/share.js +48 -104
- package/dist/misc/sheet.js +21 -53
- package/dist/misc/sidebar.d.ts +69 -69
- package/dist/misc/sidebar.js +112 -153
- package/dist/misc/skeleton.d.ts +2 -2
- package/dist/misc/skeleton.js +6 -29
- package/dist/misc/slider.js +3 -26
- package/dist/misc/starRating.js +5 -15
- package/dist/misc/stateCard.js +7 -8
- package/dist/misc/switch.d.ts +5 -0
- package/dist/misc/switch.d.ts.map +1 -0
- package/dist/misc/switch.js +8 -0
- package/dist/misc/tabs.js +8 -34
- package/dist/misc/textarea.js +2 -25
- package/dist/misc/tile.js +31 -92
- package/dist/misc/tooltip.js +8 -34
- package/dist/profile/socialLink.d.ts +1 -1
- package/dist/profile/socialLink.d.ts.map +1 -1
- package/dist/profile/socialLink.js +53 -69
- package/dist/utils/permissions.d.ts +7 -0
- package/dist/utils/permissions.d.ts.map +1 -0
- package/dist/utils/permissions.js +38 -0
- package/package.json +6 -4
|
@@ -4,9 +4,10 @@ import { Check, CheckCheck, Reply, MoreHorizontal, Pencil as PencilIcon, Trash2,
|
|
|
4
4
|
import { Avatar, AvatarFallback, AvatarImage } from "../misc/avatar";
|
|
5
5
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../misc/dropdown-menu";
|
|
6
6
|
import { Button } from "../misc/button";
|
|
7
|
-
import { useEffect, useState, useRef } from "react";
|
|
7
|
+
import { useEffect, useState, useRef, useCallback, useMemo } from "react";
|
|
8
8
|
import EmojiPicker from 'emoji-picker-react';
|
|
9
|
-
|
|
9
|
+
import { chatApi } from "@myrjfa/state";
|
|
10
|
+
const QUICK_REACTIONS = [
|
|
10
11
|
{ emoji: "❤️", label: "love" },
|
|
11
12
|
{ emoji: "👍", label: "like" },
|
|
12
13
|
{ emoji: "👎", label: "dislike" },
|
|
@@ -14,21 +15,31 @@ var QUICK_REACTIONS = [
|
|
|
14
15
|
{ emoji: "😠", label: "angry" },
|
|
15
16
|
{ emoji: "😢", label: "sad" },
|
|
16
17
|
];
|
|
17
|
-
export function MessageBubble(
|
|
18
|
-
var _b, _c, _d, _e;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
18
|
+
export function MessageBubble({ message, participants, currentUserId, showSender, senderProfilePic, onEdit, onDelete, onReply, onReactionAdd, onReactionRemove, onScrollToReply, onVote }) {
|
|
19
|
+
var _a, _b, _c, _d, _e, _f;
|
|
20
|
+
const [showQuickReactions, setShowQuickReactions] = useState(false);
|
|
21
|
+
const [showReactionPicker, setShowReactionPicker] = useState(false);
|
|
22
|
+
const [showActionMenu, setShowActionMenu] = useState(false);
|
|
23
|
+
const [showMobileMenu, setShowMobileMenu] = useState(false);
|
|
24
|
+
const [poll, setPoll] = useState(null);
|
|
25
|
+
const reactionRef = useRef(null);
|
|
26
|
+
const actionRef = useRef(null);
|
|
27
|
+
const mobileMenuRef = useRef(null);
|
|
28
|
+
const mobilePopupRef = useRef(null);
|
|
29
|
+
const messageBubbleRef = useRef(null);
|
|
30
|
+
const isOwn = message.senderId == currentUserId;
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const pollId = message.content.referenceId;
|
|
33
|
+
if (message.type === "poll" && pollId) {
|
|
34
|
+
const fetchPoll = async () => {
|
|
35
|
+
setPoll(await chatApi.getPollById(pollId));
|
|
36
|
+
};
|
|
37
|
+
fetchPoll();
|
|
38
|
+
}
|
|
39
|
+
}, [message]);
|
|
29
40
|
// Close reaction picker when clicking outside
|
|
30
|
-
useEffect(
|
|
31
|
-
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
const handleClickOutside = (event) => {
|
|
32
43
|
if (reactionRef.current && !reactionRef.current.contains(event.target)) {
|
|
33
44
|
setShowQuickReactions(false);
|
|
34
45
|
setShowReactionPicker(false);
|
|
@@ -36,99 +47,99 @@ export function MessageBubble(_a) {
|
|
|
36
47
|
};
|
|
37
48
|
if (showQuickReactions || showReactionPicker) {
|
|
38
49
|
document.addEventListener("mousedown", handleClickOutside);
|
|
39
|
-
return
|
|
50
|
+
return () => {
|
|
40
51
|
document.removeEventListener("mousedown", handleClickOutside);
|
|
41
52
|
};
|
|
42
53
|
}
|
|
43
54
|
}, [showQuickReactions, showReactionPicker]);
|
|
44
55
|
// Close mobile menu when clicking outside
|
|
45
|
-
useEffect(
|
|
46
|
-
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
const handleClickOutside = (event) => {
|
|
47
58
|
if (mobileMenuRef.current && !mobileMenuRef.current.contains(event.target)) {
|
|
48
59
|
setShowMobileMenu(false);
|
|
49
60
|
}
|
|
50
61
|
};
|
|
51
62
|
if (showMobileMenu) {
|
|
52
63
|
document.addEventListener("mousedown", handleClickOutside);
|
|
53
|
-
return
|
|
64
|
+
return () => {
|
|
54
65
|
document.removeEventListener("mousedown", handleClickOutside);
|
|
55
66
|
};
|
|
56
67
|
}
|
|
57
68
|
}, [showMobileMenu]);
|
|
58
69
|
// Position popup to stay within viewport and over message
|
|
59
|
-
useEffect(
|
|
70
|
+
useEffect(() => {
|
|
60
71
|
if (showMobileMenu && mobilePopupRef.current && messageBubbleRef.current) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
const popup = mobilePopupRef.current;
|
|
73
|
+
const messageBubble = messageBubbleRef.current;
|
|
74
|
+
const bubbleRect = messageBubble.getBoundingClientRect();
|
|
75
|
+
const popupRect = popup.getBoundingClientRect();
|
|
76
|
+
const viewportHeight = window.innerHeight;
|
|
77
|
+
const viewportWidth = window.innerWidth;
|
|
78
|
+
const padding = 8;
|
|
68
79
|
// Reset styles
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
80
|
+
popup.style.top = "auto";
|
|
81
|
+
popup.style.bottom = "auto";
|
|
82
|
+
popup.style.left = "auto";
|
|
83
|
+
popup.style.right = "auto";
|
|
84
|
+
popup.style.transform = "none";
|
|
74
85
|
// Position horizontally - prefer the side with more space
|
|
75
86
|
if (isOwn) {
|
|
76
87
|
// Message is on right, try to position popup on left of message
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (spaceOnLeft > popupWidth +
|
|
88
|
+
const spaceOnLeft = bubbleRect.left;
|
|
89
|
+
const popupWidth = 256; // w-64 = 16rem = 256px
|
|
90
|
+
if (spaceOnLeft > popupWidth + padding) {
|
|
80
91
|
// Enough space on left
|
|
81
|
-
|
|
82
|
-
|
|
92
|
+
popup.style.right = "100%";
|
|
93
|
+
popup.style.marginRight = "8px";
|
|
83
94
|
}
|
|
84
95
|
else {
|
|
85
96
|
// Not enough space, position on right but keep within viewport
|
|
86
|
-
|
|
87
|
-
|
|
97
|
+
popup.style.left = "100%";
|
|
98
|
+
popup.style.marginLeft = "8px";
|
|
88
99
|
// If it goes out of right edge, adjust
|
|
89
|
-
|
|
90
|
-
if (rect.right > viewportWidth -
|
|
91
|
-
|
|
92
|
-
|
|
100
|
+
const rect = popup.getBoundingClientRect();
|
|
101
|
+
if (rect.right > viewportWidth - padding) {
|
|
102
|
+
popup.style.left = "auto";
|
|
103
|
+
popup.style.right = "0";
|
|
93
104
|
}
|
|
94
105
|
}
|
|
95
106
|
}
|
|
96
107
|
else {
|
|
97
108
|
// Message is on left, try to position popup on right of message
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (spaceOnRight > popupWidth +
|
|
109
|
+
const spaceOnRight = viewportWidth - bubbleRect.right;
|
|
110
|
+
const popupWidth = 256; // w-64 = 16rem = 256px
|
|
111
|
+
if (spaceOnRight > popupWidth + padding) {
|
|
101
112
|
// Enough space on right
|
|
102
|
-
|
|
103
|
-
|
|
113
|
+
popup.style.left = "100%";
|
|
114
|
+
popup.style.marginLeft = "8px";
|
|
104
115
|
}
|
|
105
116
|
else {
|
|
106
117
|
// Not enough space, position on left but keep within viewport
|
|
107
|
-
|
|
108
|
-
|
|
118
|
+
popup.style.right = "100%";
|
|
119
|
+
popup.style.marginRight = "8px";
|
|
109
120
|
// If it goes out of left edge, adjust
|
|
110
|
-
|
|
111
|
-
if (rect.left <
|
|
112
|
-
|
|
113
|
-
|
|
121
|
+
const rect = popup.getBoundingClientRect();
|
|
122
|
+
if (rect.left < padding) {
|
|
123
|
+
popup.style.right = "auto";
|
|
124
|
+
popup.style.left = "0";
|
|
114
125
|
}
|
|
115
126
|
}
|
|
116
127
|
}
|
|
117
128
|
// Position vertically - center on message
|
|
118
|
-
|
|
119
|
-
|
|
129
|
+
popup.style.top = "50%";
|
|
130
|
+
popup.style.transform = "translateY(-50%)";
|
|
120
131
|
// Make sure it doesn't go out of bottom
|
|
121
|
-
setTimeout(
|
|
122
|
-
|
|
123
|
-
if (rect.bottom >
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
132
|
+
setTimeout(() => {
|
|
133
|
+
const rect = popup.getBoundingClientRect();
|
|
134
|
+
if (rect.bottom > viewportHeight - padding) {
|
|
135
|
+
popup.style.top = "auto";
|
|
136
|
+
popup.style.bottom = "0";
|
|
137
|
+
popup.style.transform = "translateY(0)";
|
|
127
138
|
}
|
|
128
|
-
else if (rect.top <
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
139
|
+
else if (rect.top < padding) {
|
|
140
|
+
popup.style.top = "0";
|
|
141
|
+
popup.style.bottom = "auto";
|
|
142
|
+
popup.style.transform = "translateY(0)";
|
|
132
143
|
}
|
|
133
144
|
}, 0);
|
|
134
145
|
}
|
|
@@ -136,61 +147,137 @@ export function MessageBubble(_a) {
|
|
|
136
147
|
if (message.type === "system" || message.type === "milestone" || message.type === "expense") {
|
|
137
148
|
return (_jsx("div", { className: "flex justify-center my-3", children: _jsx("div", { className: "bg-gray-100 dark:bg-gray-800 text-gray-500 dark:text-gray-400 text-xs px-4 py-1.5 rounded-full max-w-xs text-center shadow-sm", children: message.content.text }) }));
|
|
138
149
|
}
|
|
139
|
-
|
|
150
|
+
const time = new Date(message.createdAt).toLocaleTimeString([], {
|
|
140
151
|
hour: "2-digit",
|
|
141
152
|
minute: "2-digit",
|
|
142
153
|
});
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
154
|
+
const createMentionRegex = useCallback((participants) => {
|
|
155
|
+
const userIds = participants.map(p => p.userId);
|
|
156
|
+
// Escape special regex characters in usernames to ensure they are matched literally
|
|
157
|
+
const escapedUserIds = userIds.map(id => id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
|
158
|
+
// Sort by length descending to match longer names first, to prevent partial matches
|
|
159
|
+
escapedUserIds.sort((a, b) => b.length - a.length);
|
|
160
|
+
if (escapedUserIds.length === 0) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
// Match '@' followed by an exact username, then a non-word character or end of string.
|
|
164
|
+
// The `(?=\\s|$|[,.!?;:])` part ensures the mention is followed by a space, end of string, or common punctuation.
|
|
165
|
+
// This regex is designed to be as specific as possible to avoid false positives while allowing spaces/symbols.
|
|
166
|
+
const pattern = `@(${escapedUserIds.join('|')})(?=\\s|$|[,.!?;:])`;
|
|
167
|
+
return new RegExp(pattern, 'gi');
|
|
168
|
+
}, []);
|
|
169
|
+
const mentionRegex = useMemo(() => createMentionRegex(participants), [participants, createMentionRegex]);
|
|
170
|
+
const renderTextWithLinks = useCallback((text) => {
|
|
171
|
+
const elements = [];
|
|
172
|
+
let lastIndex = 0;
|
|
173
|
+
// Store all matches with their type and original value
|
|
174
|
+
const allMatches = [];
|
|
175
|
+
// Define regexes inside useCallback to avoid re-creation on every render if they don't depend on props
|
|
176
|
+
const linkRegex = /(https?:\/\/[^\s]+)/gi; // Added 'i' flag for case-insensitivity if needed, though URLs are usually case-sensitive for hostnames
|
|
177
|
+
const emailRegex = /([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+)/gi; // Added 'i' flag
|
|
178
|
+
const phoneRegex = /(\+?1?\d{9,15})/g; // Phone numbers are typically case-insensitive
|
|
179
|
+
// Find links
|
|
180
|
+
for (const match of text.matchAll(linkRegex)) {
|
|
181
|
+
if (match.index !== undefined) {
|
|
182
|
+
allMatches.push({ index: match.index, length: match[0].length, type: "link", value: match[0] });
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// Find emails
|
|
186
|
+
for (const match of text.matchAll(emailRegex)) {
|
|
187
|
+
if (match.index !== undefined) {
|
|
188
|
+
allMatches.push({ index: match.index, length: match[0].length, type: "email", value: match[0] });
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// Find phones
|
|
192
|
+
for (const match of text.matchAll(phoneRegex)) {
|
|
193
|
+
if (match.index !== undefined) {
|
|
194
|
+
allMatches.push({ index: match.index, length: match[0].length, type: "phone", value: match[0] });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// Find mentions
|
|
198
|
+
if (mentionRegex) {
|
|
199
|
+
for (const match of text.matchAll(mentionRegex)) {
|
|
200
|
+
if (match.index !== undefined) {
|
|
201
|
+
allMatches.push({ index: match.index, length: match[0].length, type: "mention", value: match[0] });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// Sort all matches by their starting index
|
|
206
|
+
allMatches.sort((a, b) => a.index - b.index);
|
|
207
|
+
for (const match of allMatches) {
|
|
208
|
+
// Add preceding text
|
|
209
|
+
if (match.index > lastIndex) {
|
|
210
|
+
elements.push(text.substring(lastIndex, match.index));
|
|
151
211
|
}
|
|
152
|
-
|
|
153
|
-
|
|
212
|
+
// Add the matched element
|
|
213
|
+
if (match.type === "link") {
|
|
214
|
+
elements.push(_jsx("a", { href: match.value, target: "_blank", rel: "noopener noreferrer", className: "underline hover:opacity-80 break-all text-indigo-500", children: match.value }, `link-${match.index}`));
|
|
154
215
|
}
|
|
155
|
-
if (
|
|
156
|
-
|
|
216
|
+
else if (match.type === "email") {
|
|
217
|
+
elements.push(_jsx("a", { href: `mailto:${match.value}`, className: "underline hover:opacity-80 break-all text-indigo-500", children: match.value }, `email-${match.index}`));
|
|
157
218
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
219
|
+
else if (match.type === "phone") {
|
|
220
|
+
elements.push(_jsx("a", { href: `tel:${match.value}`, className: "underline hover:opacity-80 break-all text-indigo-200", children: match.value }, `phone-${match.index}`));
|
|
221
|
+
}
|
|
222
|
+
else if (match.type === "mention") {
|
|
223
|
+
// The regex already ensures it's a valid participant userId
|
|
224
|
+
elements.push(_jsx("a", { href: `/chat?u=${match.value.substring(1)}`, className: "underline hover:opacity-80 break-all text-indigo-200", children: match.value }, `mention-${match.index}`));
|
|
225
|
+
}
|
|
226
|
+
lastIndex = match.index + match.length;
|
|
227
|
+
}
|
|
228
|
+
// Add any remaining text
|
|
229
|
+
if (lastIndex < text.length) {
|
|
230
|
+
elements.push(text.substring(lastIndex));
|
|
231
|
+
}
|
|
232
|
+
return elements;
|
|
233
|
+
}, [mentionRegex]);
|
|
234
|
+
return (_jsxs("div", { id: `message-${message._id}`, className: `flex mb-2 ${isOwn ? "justify-end" : "justify-start"}`, children: [!isOwn && (_jsxs(Avatar, { className: "w-8 h-8 mr-2 mt-1 flex-shrink-0", children: [_jsx(AvatarImage, { src: senderProfilePic }), _jsx(AvatarFallback, { children: message.senderId.charAt(0).toUpperCase() })] })), _jsxs("div", { className: `flex group items-end gap-1 ${isOwn ? "flex-row-reverse" : "flex-row"}`, children: [_jsxs("div", { className: `max-w-xs sm:max-w-md md:max-w-lg flex flex-col ${isOwn ? "items-end" : "items-start"}`, ref: messageBubbleRef, children: [showSender && !isOwn && (_jsx("span", { className: "text-xs font-medium text-teal-600 dark:text-teal-400 ml-1 mb-0.5", children: message.senderId })), message.replyTo && (_jsxs(Button, { onClick: () => onScrollToReply === null || onScrollToReply === void 0 ? void 0 : onScrollToReply(message.replyTo._id), className: `text-xs mb-2 px-3 py-2 rounded-lg border-l-2 max-w-full text-left hover:opacity-80 transition-opacity ${isOwn
|
|
162
235
|
? "bg-opacity-50 bg-teal-600/40 border-teal-300 text-teal-100"
|
|
163
|
-
: "bg-gray-200 dark:bg-gray-700 border-gray-400 dark:border-gray-600 text-gray-600 dark:text-gray-300"
|
|
236
|
+
: "bg-gray-200 dark:bg-gray-700 border-gray-400 dark:border-gray-600 text-gray-600 dark:text-gray-300"}`, children: [_jsxs("div", { className: "font-semibold text-xs truncate", children: ["\u21B3 ", (_a = message.replyTo) === null || _a === void 0 ? void 0 : _a.senderId] }), _jsx("div", { className: "truncate text-gray-500 dark:text-gray-400 text-xs", children: ((_d = (_c = (_b = message.replyTo) === null || _b === void 0 ? void 0 : _b.content) === null || _c === void 0 ? void 0 : _c.text) === null || _d === void 0 ? void 0 : _d.substring(0, 50)) || "Media/Attachment" })] })), _jsxs("div", { className: `px-4 py-2.5 rounded-2xl relative shadow-sm backdrop-blur-md
|
|
237
|
+
${isOwn
|
|
164
238
|
? "bg-gradient-to-r from-teal-500/70 to-teal-600/70 text-white rounded-br-md border border-teal-400/30"
|
|
165
|
-
: "bg-white dark:bg-gray-800 text-gray-900 dark:text-white rounded-bl-md border border-gray-100 dark:border-gray-700"
|
|
239
|
+
: "bg-white dark:bg-gray-800 text-gray-900 dark:text-white rounded-bl-md border border-gray-100 dark:border-gray-700"}
|
|
240
|
+
${message.isDeleted ? "opacity-60 italic" : ""}`, children: [message.content.media && message.content.media.length > 0 && (_jsx("div", { className: "mb-2 space-y-1", children: message.content.media.map((m, i) => (_jsx("div", { children: m.type === "image" ? (_jsx("img", { src: m.url, alt: m.name, className: "rounded-lg max-w-full max-h-64 object-cover" })) : m.type === "audio" ?
|
|
241
|
+
(_jsx("audio", { src: m.url, controls: true, children: _jsxs("a", { href: m.url, className: "text-sm underline", target: "_blank", rel: "noopener noreferrer", children: ["\uD83D\uDCCE ", m.name] }) }))
|
|
242
|
+
: (_jsxs("a", { href: m.url, className: "text-sm underline", target: "_blank", rel: "noopener noreferrer", children: ["\uD83D\uDCCE ", m.name] })) }, i))) })), _jsx("p", { className: "text-[15px] leading-relaxed whitespace-pre-wrap break-words", children: renderTextWithLinks((_e = message.content.text) !== null && _e !== void 0 ? _e : "") }), poll && (_jsxs("div", { className: "mt-3 space-y-2 min-w-[200px]", children: [poll.options.map((opt, i) => {
|
|
243
|
+
const votes = poll.votes || [];
|
|
244
|
+
const optionVotes = votes.filter((v) => v.optionIndex === i);
|
|
245
|
+
const hasVoted = optionVotes.some((v) => v.userId === currentUserId);
|
|
246
|
+
const percentage = votes.length > 0 ? (optionVotes.length / votes.length) * 100 : 0;
|
|
247
|
+
return (_jsxs("div", { className: `relative overflow-hidden rounded-lg border p-2 cursor-pointer transition-all active:scale-[0.98] ${hasVoted
|
|
248
|
+
? "border-indigo-500 bg-indigo-50/50 dark:bg-indigo-900/30"
|
|
249
|
+
: "border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600 bg-indigo-50/50 dark:bg-indigo-900/30"}
|
|
250
|
+
}`, onClick: () => onVote === null || onVote === void 0 ? void 0 : onVote(poll._id, i), children: [_jsx("div", { className: "absolute inset-0 bg-indigo-400/10 pointer-events-none transition-all duration-500", style: { width: `${percentage}%` } }), _jsxs("div", { className: "relative flex justify-between items-center text-sm", children: [_jsx("span", { className: `${hasVoted ? "font-semibold text-indigo-700 dark:text-indigo-300" : "text-gray-700 dark:text-gray-200"}`, children: opt.text }), _jsx("span", { className: "text-xs text-gray-500 dark:text-gray-400", children: optionVotes.length > 0 && `${optionVotes.length} (${Math.round(percentage)}%)` })] })] }, i));
|
|
251
|
+
}), _jsxs("div", { className: "text-[10px] text-gray-400 dark:text-gray-500 mt-1", children: [((_f = poll.votes) === null || _f === void 0 ? void 0 : _f.length) || 0, " votes \u2022 ", poll.allowMultiple ? "Multiple choice" : "Single choice"] })] })), _jsxs("div", { className: `flex items-center justify-end gap-1 mt-1 ${isOwn ? "text-teal-200" : "text-gray-400"}`, children: [message.isEdited && (_jsx(PencilIcon, { className: "w-3 h-3" })), _jsx("span", { className: "text-[10px]", children: time }), isOwn && (message.readBy.length > 1 ? (_jsx(CheckCheck, { className: "w-4 h-4 text-sky-300" })) : (_jsx(Check, { className: "w-4 h-4" })))] })] }), message.reactions && message.reactions.length > 0 && (_jsx("div", { className: `flex gap-1 mt-2 ${isOwn ? "justify-end" : "justify-start"} px-2`, children: Object.entries(message.reactions.reduce((acc, r) => {
|
|
166
252
|
acc[r.emoji] = (acc[r.emoji] || 0) + 1;
|
|
167
253
|
return acc;
|
|
168
|
-
}, {})).map(
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
254
|
+
}, {})).map(([emoji, count]) => (_jsxs("span", { className: "bg-gray-100 dark:bg-gray-700 rounded-full px-2 py-0.5 text-xs shadow-sm hover:bg-gray-200 dark:hover:bg-gray-600 cursor-pointer transition-colors", onClick: () => {
|
|
255
|
+
if (message.reactions.find(r => r.emoji == emoji && r.userId == currentUserId)) {
|
|
256
|
+
onReactionRemove === null || onReactionRemove === void 0 ? void 0 : onReactionRemove(message._id);
|
|
257
|
+
}
|
|
258
|
+
}, children: [emoji, " ", count > 1 && count] }, emoji))) }))] }), !message.isDeleted && (_jsxs(_Fragment, { children: [_jsxs("div", { className: `hidden md:flex gap-1 items-center flex-shrink-0 opacity-0 group-hover:opacity-100 transition-opacity my-auto`, children: [_jsxs("div", { className: "relative", ref: reactionRef, children: [_jsx("button", { onClick: () => {
|
|
172
259
|
setShowQuickReactions(!showQuickReactions);
|
|
173
260
|
setShowReactionPicker(false);
|
|
174
|
-
}, className: "flex items-center justify-center w-8 h-8 rounded-full bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors shadow-md", title: "Add reaction", children: _jsx(Smile, { className: "w-4 h-4" }) }), (showQuickReactions || showReactionPicker) && (_jsx("div", { className:
|
|
261
|
+
}, className: "flex items-center justify-center w-8 h-8 rounded-full bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors shadow-md", title: "Add reaction", children: _jsx(Smile, { className: "w-4 h-4" }) }), (showQuickReactions || showReactionPicker) && (_jsx("div", { className: `absolute z-50 bottom-10 ${isOwn ? "right-0" : "left-0"} whitespace-nowrap`, children: _jsxs("div", { className: "bg-white dark:bg-gray-800 rounded-xl shadow-xl border border-gray-200 dark:border-gray-700 p-2", children: [showQuickReactions && (_jsxs("div", { className: "flex gap-1 flex-wrap w-max", children: [QUICK_REACTIONS.map((reaction) => (_jsx("button", { onClick: () => {
|
|
175
262
|
onReactionAdd === null || onReactionAdd === void 0 ? void 0 : onReactionAdd(message._id, reaction.emoji);
|
|
176
263
|
setShowQuickReactions(false);
|
|
177
|
-
}, className: "text-xl hover:scale-125 transition-transform hover:bg-gray-100 dark:hover:bg-gray-700 p-2 rounded-lg", title: reaction.label, children: reaction.emoji }, reaction.label))
|
|
264
|
+
}, className: "text-xl hover:scale-125 transition-transform hover:bg-gray-100 dark:hover:bg-gray-700 p-2 rounded-lg", title: reaction.label, children: reaction.emoji }, reaction.label))), _jsx("button", { onClick: () => {
|
|
178
265
|
setShowQuickReactions(false);
|
|
179
266
|
setShowReactionPicker(true);
|
|
180
|
-
}, className: "text-xl hover:scale-125 transition-transform hover:bg-gray-100 dark:hover:bg-gray-700 p-2 rounded-lg", children: _jsx(PlusCircleIcon, { className: "w-5 h-5 text-gray-600 dark:text-gray-300" }) })] })), showReactionPicker && (_jsx("div", { className: "mt-2 border-t border-gray-200 dark:border-gray-700 pt-2", children: _jsx(EmojiPicker, { onEmojiClick:
|
|
267
|
+
}, className: "text-xl hover:scale-125 transition-transform hover:bg-gray-100 dark:hover:bg-gray-700 p-2 rounded-lg", children: _jsx(PlusCircleIcon, { className: "w-5 h-5 text-gray-600 dark:text-gray-300" }) })] })), showReactionPicker && (_jsx("div", { className: "mt-2 border-t border-gray-200 dark:border-gray-700 pt-2", children: _jsx(EmojiPicker, { onEmojiClick: (emojiObject) => {
|
|
181
268
|
onReactionAdd === null || onReactionAdd === void 0 ? void 0 : onReactionAdd(message._id, emojiObject.emoji);
|
|
182
269
|
setShowReactionPicker(false);
|
|
183
270
|
setShowQuickReactions(false);
|
|
184
|
-
} }) }))] }) }))] }), _jsx("div", { className: "relative", ref: actionRef, children: _jsxs(DropdownMenu, { open: showActionMenu, onOpenChange: setShowActionMenu, children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx("button", { className: "flex items-center justify-center w-8 h-8 rounded-full bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors shadow-md", children: _jsx(MoreHorizontal, { className: "w-4 h-4" }) }) }), _jsxs(DropdownMenuContent, { align: isOwn ? "end" : "start", className: "w-48", children: [_jsxs(DropdownMenuItem, { onClick:
|
|
271
|
+
} }) }))] }) }))] }), _jsx("div", { className: "relative", ref: actionRef, children: _jsxs(DropdownMenu, { open: showActionMenu, onOpenChange: setShowActionMenu, children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx("button", { className: "flex items-center justify-center w-8 h-8 rounded-full bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors shadow-md", children: _jsx(MoreHorizontal, { className: "w-4 h-4" }) }) }), _jsxs(DropdownMenuContent, { align: isOwn ? "end" : "start", className: "w-48", children: [_jsxs(DropdownMenuItem, { onClick: () => onReply === null || onReply === void 0 ? void 0 : onReply(message), children: [_jsx(Reply, { className: "w-4 h-4 mr-2" }), "Reply"] }), isOwn && _jsxs(DropdownMenuItem, { onClick: () => onEdit === null || onEdit === void 0 ? void 0 : onEdit(message), children: [_jsx(PencilIcon, { className: "w-4 h-4 mr-2" }), "Edit"] }), isOwn && _jsxs(DropdownMenuItem, { className: "text-red-500", onClick: () => onDelete === null || onDelete === void 0 ? void 0 : onDelete(message._id), children: [_jsx(Trash2, { className: "w-4 h-4 mr-2" }), "Unsend"] })] })] }) })] }), _jsxs("div", { className: "md:hidden relative my-auto", ref: mobileMenuRef, children: [_jsx("button", { onClick: () => setShowMobileMenu(!showMobileMenu), className: "flex items-center justify-center w-8 h-8 rounded-full bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors shadow-md flex-shrink-0", children: _jsx(MoreHorizontal, { className: "w-4 h-4" }) }), showMobileMenu && (_jsx("div", { className: "fixed inset-0 z-40", onClick: () => setShowMobileMenu(false) })), showMobileMenu && (_jsx("div", { ref: mobilePopupRef, className: "absolute z-50 bg-white dark:bg-gray-800 rounded-xl shadow-2xl border border-gray-200 dark:border-gray-700 overflow-hidden w-64 max-h-96 overflow-y-auto", children: _jsxs("div", { className: "flex flex-col", children: [_jsx("div", { className: "p-2 border-b border-gray-200 dark:border-gray-700", children: _jsx("div", { className: "flex gap-1 flex-wrap justify-center", children: QUICK_REACTIONS.map((reaction) => (_jsx("button", { onClick: () => {
|
|
185
272
|
onReactionAdd === null || onReactionAdd === void 0 ? void 0 : onReactionAdd(message._id, reaction.emoji);
|
|
186
273
|
setShowMobileMenu(false);
|
|
187
|
-
}, className: "text-lg hover:scale-125 transition-transform p-1 hover:bg-gray-100 dark:hover:bg-gray-700 rounded", title: reaction.label, children: reaction.emoji }, reaction.label))
|
|
274
|
+
}, className: "text-lg hover:scale-125 transition-transform p-1 hover:bg-gray-100 dark:hover:bg-gray-700 rounded", title: reaction.label, children: reaction.emoji }, reaction.label))) }) }), _jsxs("button", { onClick: () => {
|
|
188
275
|
onReply === null || onReply === void 0 ? void 0 : onReply(message);
|
|
189
276
|
setShowMobileMenu(false);
|
|
190
|
-
}, className: "px-4 py-2.5 text-sm text-left hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center gap-2 border-b border-gray-200 dark:border-gray-700", children: [_jsx(Reply, { className: "w-4 h-4" }), "Reply"] }), isOwn && (_jsxs(_Fragment, { children: [_jsxs("button", { onClick:
|
|
191
|
-
onEdit === null || onEdit === void 0 ? void 0 : onEdit(message
|
|
277
|
+
}, className: "px-4 py-2.5 text-sm text-left hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center gap-2 border-b border-gray-200 dark:border-gray-700", children: [_jsx(Reply, { className: "w-4 h-4" }), "Reply"] }), isOwn && (_jsxs(_Fragment, { children: [_jsxs("button", { onClick: () => {
|
|
278
|
+
onEdit === null || onEdit === void 0 ? void 0 : onEdit(message);
|
|
192
279
|
setShowMobileMenu(false);
|
|
193
|
-
}, className: "px-4 py-2.5 text-sm text-left hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center gap-2 border-b border-gray-200 dark:border-gray-700", children: [_jsx(PencilIcon, { className: "w-4 h-4" }), "Edit"] }), _jsxs("button", { onClick:
|
|
280
|
+
}, className: "px-4 py-2.5 text-sm text-left hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center gap-2 border-b border-gray-200 dark:border-gray-700", children: [_jsx(PencilIcon, { className: "w-4 h-4" }), "Edit"] }), _jsxs("button", { onClick: () => {
|
|
194
281
|
onDelete === null || onDelete === void 0 ? void 0 : onDelete(message._id);
|
|
195
282
|
setShowMobileMenu(false);
|
|
196
283
|
}, className: "px-4 py-2.5 text-sm text-left text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20 flex items-center gap-2", children: [_jsx(Trash2, { className: "w-4 h-4" }), "Unsend"] })] }))] }) }))] })] }))] })] }));
|
|
@@ -1,14 +1,21 @@
|
|
|
1
|
+
import { Message } from "@myrjfa/state";
|
|
1
2
|
interface MessageInputProps {
|
|
2
3
|
onSend: (text: string, files?: File[]) => void;
|
|
3
4
|
onTypingStart?: () => void;
|
|
4
5
|
onTypingStop?: () => void;
|
|
5
6
|
disabled?: boolean;
|
|
6
7
|
replyTo?: any;
|
|
8
|
+
editMessage?: Message | null;
|
|
9
|
+
onEdit?: (messageid: string, text: string) => void;
|
|
10
|
+
onCancelEdit?: () => void;
|
|
7
11
|
onCancelReply?: () => void;
|
|
8
12
|
onAddMilestone?: () => void;
|
|
9
13
|
onAddExpense?: () => void;
|
|
14
|
+
onCreatePoll?: (question: string, options: string[], allowMultiple: boolean) => void;
|
|
15
|
+
participants?: any[];
|
|
10
16
|
activePanel?: "chat" | "milestones" | "expenses";
|
|
11
17
|
}
|
|
12
|
-
export declare function MessageInput({ onSend, onTypingStart, onTypingStop, disabled, replyTo, onCancelReply, onAddMilestone, onAddExpense,
|
|
18
|
+
export declare function MessageInput({ onSend, onTypingStart, onTypingStop, disabled, replyTo, editMessage, onEdit, onCancelEdit, onCancelReply, onAddMilestone, onAddExpense, onCreatePoll, participants, // Added
|
|
19
|
+
activePanel }: MessageInputProps): import("react/jsx-runtime").JSX.Element;
|
|
13
20
|
export {};
|
|
14
21
|
//# sourceMappingURL=MessageInput.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MessageInput.d.ts","sourceRoot":"","sources":["../../src/chat/MessageInput.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"MessageInput.d.ts","sourceRoot":"","sources":["../../src/chat/MessageInput.tsx"],"names":[],"mappings":"AAcA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAExC,UAAU,iBAAiB;IACvB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IAC/C,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,GAAG,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7B,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,aAAa,EAAE,OAAO,KAAK,IAAI,CAAC;IACrF,YAAY,CAAC,EAAE,GAAG,EAAE,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,GAAG,YAAY,GAAG,UAAU,CAAC;CACpD;AAED,wBAAgB,YAAY,CAAC,EACzB,MAAM,EACN,aAAa,EACb,YAAY,EACZ,QAAQ,EACR,OAAO,EACP,WAAW,EACX,MAAM,EACN,YAAY,EACZ,aAAa,EACb,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,YAAiB,EAAE,QAAQ;AAC3B,WAAoB,EACvB,EAAE,iBAAiB,2CAobnB"}
|