react-mention-input 1.1.14 → 1.1.16
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/demo.tsx +98 -0
- package/dist/MentionInput.css +8 -0
- package/dist/MentionInput.d.ts +2 -0
- package/dist/MentionInput.js +2 -2
- package/dist/ShowMessageCard.css +52 -0
- package/dist/ShowMessageCard.d.ts +2 -0
- package/dist/ShowMessageCard.js +2 -2
- package/dist/demo.d.ts +3 -0
- package/dist/demo.js +80 -0
- package/dist/main.d.ts +1 -0
- package/dist/main.js +5 -0
- package/dist/src/MentionInput.d.ts +38 -0
- package/dist/src/MentionInput.js +376 -0
- package/dist/src/ShowMessageCard.d.ts +41 -0
- package/dist/src/ShowMessageCard.js +71 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +2 -0
- package/dist/vite.config.d.ts +2 -0
- package/dist/vite.config.js +9 -0
- package/index.html +12 -0
- package/main.tsx +9 -0
- package/package.json +9 -5
- package/src/MentionInput.css +8 -0
- package/src/MentionInput.tsx +31 -5
- package/src/ShowMessageCard.css +52 -0
- package/src/ShowMessageCard.tsx +86 -30
- package/vite.config.ts +10 -0
package/src/MentionInput.tsx
CHANGED
|
@@ -16,6 +16,8 @@ interface MentionInputProps {
|
|
|
16
16
|
sendBtnClassName?: string;
|
|
17
17
|
suggestionListClassName?: string;
|
|
18
18
|
suggestionItemClassName?: string;
|
|
19
|
+
attachedImageContainerClassName?: string;
|
|
20
|
+
attachedImageContainerStyle?: React.CSSProperties;
|
|
19
21
|
imgClassName?: string; // New prop for image CSS class
|
|
20
22
|
imgStyle?: React.CSSProperties; // New prop for inline image styles
|
|
21
23
|
sendButtonIcon?: ReactNode; // Button icon (MUI icon or image path)
|
|
@@ -25,6 +27,7 @@ interface MentionInputProps {
|
|
|
25
27
|
messageHTML: string;
|
|
26
28
|
userSelectListWithIds: { id: number; name: string }[];
|
|
27
29
|
userSelectListName: string[];
|
|
30
|
+
tags: string[];
|
|
28
31
|
images?: File[];
|
|
29
32
|
imageUrl?: string | null;
|
|
30
33
|
}) => void;
|
|
@@ -43,6 +46,8 @@ const MentionInput: React.FC<MentionInputProps> = ({
|
|
|
43
46
|
suggestionItemClassName,
|
|
44
47
|
imgClassName,
|
|
45
48
|
imgStyle,
|
|
49
|
+
attachedImageContainerClassName,
|
|
50
|
+
attachedImageContainerStyle,
|
|
46
51
|
sendButtonIcon,
|
|
47
52
|
attachmentButtonIcon,
|
|
48
53
|
onSendMessage,
|
|
@@ -64,17 +69,29 @@ const MentionInput: React.FC<MentionInputProps> = ({
|
|
|
64
69
|
const caretOffsetRef = useRef<number>(0);
|
|
65
70
|
const userSelectListRef = useRef<string[]>([]); // Only unique names
|
|
66
71
|
const userSelectListWithIdsRef = useRef<{ id: number; name: string }[]>([]); // Unique IDs with names
|
|
72
|
+
const tagsListRef = useRef<string[]>([]); // Store hashtags
|
|
67
73
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
68
74
|
|
|
69
75
|
const highlightMentionsAndLinks = (text: string): string => {
|
|
70
76
|
// Regular expression for detecting links
|
|
71
77
|
const linkRegex = /(https?:\/\/[^\s]+)/g;
|
|
78
|
+
|
|
79
|
+
// Regular expression for detecting hashtags
|
|
80
|
+
const hashtagRegex = /#[\w]+/g;
|
|
72
81
|
|
|
73
82
|
// Highlight links
|
|
74
83
|
let highlightedText = text.replace(
|
|
75
84
|
linkRegex,
|
|
76
85
|
'<a href="$1" target="_blank" rel="noopener noreferrer" class="link-highlight">$1</a>'
|
|
77
86
|
);
|
|
87
|
+
|
|
88
|
+
// Highlight hashtags
|
|
89
|
+
highlightedText = highlightedText.replace(
|
|
90
|
+
hashtagRegex,
|
|
91
|
+
(match) => {
|
|
92
|
+
return `<span class="hashtag-highlight">${match}</span>`;
|
|
93
|
+
}
|
|
94
|
+
);
|
|
78
95
|
|
|
79
96
|
// Highlight mentions manually based on `userSelectListRef`
|
|
80
97
|
userSelectListRef?.current.forEach((userName) => {
|
|
@@ -156,8 +173,17 @@ const MentionInput: React.FC<MentionInputProps> = ({
|
|
|
156
173
|
setShowSuggestions(false);
|
|
157
174
|
}
|
|
158
175
|
|
|
159
|
-
//
|
|
160
|
-
|
|
176
|
+
// Extract and store hashtags
|
|
177
|
+
const hashtagMatches = plainText.match(/#[\w]+/g);
|
|
178
|
+
if (hashtagMatches) {
|
|
179
|
+
const uniqueTags = Array.from(new Set(hashtagMatches));
|
|
180
|
+
tagsListRef.current = uniqueTags;
|
|
181
|
+
} else {
|
|
182
|
+
tagsListRef.current = [];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Only apply highlighting if we have mentions, hashtags, or links to highlight
|
|
186
|
+
if (userSelectListRef.current.length > 0 || plainText.match(/(https?:\/\/[^\s]+)/g) || plainText.match(/#[\w]+/g)) {
|
|
161
187
|
const currentHTML = inputRef.current.innerHTML;
|
|
162
188
|
const htmlWithHighlights = highlightMentionsAndLinks(plainText);
|
|
163
189
|
|
|
@@ -369,6 +395,7 @@ const MentionInput: React.FC<MentionInputProps> = ({
|
|
|
369
395
|
messageHTML,
|
|
370
396
|
userSelectListWithIds: userSelectListWithIdsRef.current,
|
|
371
397
|
userSelectListName: userSelectListRef.current,
|
|
398
|
+
tags: tagsListRef.current,
|
|
372
399
|
images: selectedImage ? [selectedImage] : [],
|
|
373
400
|
imageUrl: imageUrl
|
|
374
401
|
});
|
|
@@ -379,6 +406,7 @@ const MentionInput: React.FC<MentionInputProps> = ({
|
|
|
379
406
|
setImageUrl(null);
|
|
380
407
|
userSelectListRef.current = [];
|
|
381
408
|
userSelectListWithIdsRef.current = [];
|
|
409
|
+
tagsListRef.current = [];
|
|
382
410
|
}
|
|
383
411
|
}
|
|
384
412
|
};
|
|
@@ -390,12 +418,10 @@ const MentionInput: React.FC<MentionInputProps> = ({
|
|
|
390
418
|
}
|
|
391
419
|
};
|
|
392
420
|
|
|
393
|
-
console.log(inputValue, inputRef.current?.innerText.trim(), "inputValue====")
|
|
394
|
-
|
|
395
421
|
return (
|
|
396
422
|
<div className={`mention-container ${containerClassName || ""}`}>
|
|
397
423
|
{imageUrl && selectedImage && (
|
|
398
|
-
<div className=
|
|
424
|
+
<div className={`image-preview-card ${attachedImageContainerClassName || ""}`} style={attachedImageContainerStyle}>
|
|
399
425
|
<img
|
|
400
426
|
src={imageUrl}
|
|
401
427
|
alt="Preview"
|
package/src/ShowMessageCard.css
CHANGED
|
@@ -21,9 +21,25 @@
|
|
|
21
21
|
.message-card-header {
|
|
22
22
|
display: flex;
|
|
23
23
|
align-items: center;
|
|
24
|
+
justify-content: space-between;
|
|
24
25
|
margin-bottom: 8px; /* Space between header and body */
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
.message-card-header-left {
|
|
29
|
+
display: flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.message-card-item-name {
|
|
34
|
+
font-size: 14px;
|
|
35
|
+
color: #666;
|
|
36
|
+
font-weight: 500;
|
|
37
|
+
background-color: #f5f5f5;
|
|
38
|
+
padding: 4px 8px;
|
|
39
|
+
border-radius: 6px;
|
|
40
|
+
border: 1px solid #e0e0e0;
|
|
41
|
+
}
|
|
42
|
+
|
|
27
43
|
.message-card-img,
|
|
28
44
|
.message-card-initials {
|
|
29
45
|
width: 48px;
|
|
@@ -72,4 +88,40 @@
|
|
|
72
88
|
color: #007bff;
|
|
73
89
|
padding: 2px 4px;
|
|
74
90
|
border-radius: 4px;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.hashtag-highlight {
|
|
94
|
+
background-color: rgba(255, 165, 0, 0.15);
|
|
95
|
+
color: #FF8C00;
|
|
96
|
+
padding: 2px 4px;
|
|
97
|
+
border-radius: 4px;
|
|
98
|
+
font-weight: 500;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* Tag chips styling */
|
|
102
|
+
.message-card-tags {
|
|
103
|
+
display: flex;
|
|
104
|
+
flex-wrap: wrap;
|
|
105
|
+
gap: 8px;
|
|
106
|
+
margin-top: 12px;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.tag-chip {
|
|
110
|
+
padding: 6px 12px;
|
|
111
|
+
border-radius: 16px;
|
|
112
|
+
font-size: 12px;
|
|
113
|
+
font-weight: 500;
|
|
114
|
+
border: none;
|
|
115
|
+
display: inline-block;
|
|
116
|
+
white-space: nowrap;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.hashtag-chip {
|
|
120
|
+
background-color: #D4A574;
|
|
121
|
+
color: #FFFFFF;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.mention-chip {
|
|
125
|
+
background-color: #2684FF;
|
|
126
|
+
color: #FFFFFF;
|
|
75
127
|
}
|
package/src/ShowMessageCard.tsx
CHANGED
|
@@ -12,6 +12,7 @@ interface ShowMessageCardProps {
|
|
|
12
12
|
commentKey?: string; // Custom key for comment
|
|
13
13
|
imgSrcKey?: string; // Custom key for image source
|
|
14
14
|
imageUrlKey?: string; // Custom key for attached image URL
|
|
15
|
+
itemNameKey?: string; // Custom key for item identifier (top-right)
|
|
15
16
|
containerClassName?: string; // Class for the outermost container
|
|
16
17
|
containerStyle?: CSSProperties; // Style for the outermost container
|
|
17
18
|
cardClassName?: string; // Class for the card
|
|
@@ -32,6 +33,10 @@ interface ShowMessageCardProps {
|
|
|
32
33
|
commentStyle?: CSSProperties; // Style for the comment text
|
|
33
34
|
attachedImageClassName?: string; // Class for the attached image
|
|
34
35
|
attachedImageStyle?: CSSProperties; // Style for the attached image
|
|
36
|
+
attachedImageContainerClassName?: string; // Class for the attached image container
|
|
37
|
+
attachedImageContainerStyle?: CSSProperties; // Style for the attached image container
|
|
38
|
+
itemNameClassName?: string; // Class for the item name (top-right)
|
|
39
|
+
itemNameStyle?: CSSProperties; // Style for the item name (top-right)
|
|
35
40
|
renderItem?: (element: MessageCardProps) => ReactNode; // Custom render function
|
|
36
41
|
}
|
|
37
42
|
|
|
@@ -42,6 +47,7 @@ export const ShowMessageCard: React.FC<ShowMessageCardProps> = ({
|
|
|
42
47
|
commentKey = "comment",
|
|
43
48
|
imgSrcKey = "imgSrc",
|
|
44
49
|
imageUrlKey = "imageUrl", // Default key for attached image
|
|
50
|
+
itemNameKey = "name", // Default key for item identifier
|
|
45
51
|
containerClassName,
|
|
46
52
|
containerStyle,
|
|
47
53
|
cardClassName,
|
|
@@ -62,6 +68,10 @@ export const ShowMessageCard: React.FC<ShowMessageCardProps> = ({
|
|
|
62
68
|
commentStyle,
|
|
63
69
|
attachedImageClassName,
|
|
64
70
|
attachedImageStyle,
|
|
71
|
+
attachedImageContainerClassName,
|
|
72
|
+
attachedImageContainerStyle,
|
|
73
|
+
itemNameClassName,
|
|
74
|
+
itemNameStyle,
|
|
65
75
|
renderItem, // Custom render function
|
|
66
76
|
}) => {
|
|
67
77
|
// State to manage initials for images that fail to load
|
|
@@ -87,6 +97,18 @@ export const ShowMessageCard: React.FC<ShowMessageCardProps> = ({
|
|
|
87
97
|
return initials;
|
|
88
98
|
};
|
|
89
99
|
|
|
100
|
+
// Helper function to extract hashtags and mentions from text
|
|
101
|
+
const extractTagsAndMentions = (text: string) => {
|
|
102
|
+
const plainText = text.replace(/<[^>]*>/g, ''); // Remove HTML tags to get plain text
|
|
103
|
+
const hashtags = plainText.match(/#[\w]+/g) || [];
|
|
104
|
+
const mentions = plainText.match(/@[\w\s-]+/g) || [];
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
hashtags: Array.from(new Set(hashtags)), // Remove duplicates
|
|
108
|
+
mentions: Array.from(new Set(mentions.map(mention => mention.trim()))) // Remove duplicates and trim
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
|
|
90
112
|
return (
|
|
91
113
|
<div
|
|
92
114
|
className={`message-card-container ${containerClassName || ""}`}
|
|
@@ -103,6 +125,9 @@ export const ShowMessageCard: React.FC<ShowMessageCardProps> = ({
|
|
|
103
125
|
}
|
|
104
126
|
|
|
105
127
|
const showInitials = initialsState[item.id || index] || !item[imgSrcKey]; // Decide whether to show initials
|
|
128
|
+
|
|
129
|
+
// Extract tags and mentions from the comment
|
|
130
|
+
const { hashtags, mentions } = extractTagsAndMentions(item[commentKey] || '');
|
|
106
131
|
|
|
107
132
|
return (
|
|
108
133
|
<div
|
|
@@ -114,39 +139,51 @@ export const ShowMessageCard: React.FC<ShowMessageCardProps> = ({
|
|
|
114
139
|
className={`message-card-header ${headerClassName || ""}`}
|
|
115
140
|
style={headerStyle}
|
|
116
141
|
>
|
|
117
|
-
|
|
142
|
+
<div className="message-card-header-left">
|
|
143
|
+
{showInitials ? (
|
|
144
|
+
<div
|
|
145
|
+
className={`message-card-initials ${imgClassName || ""}`}
|
|
146
|
+
style={imgStyle}
|
|
147
|
+
>
|
|
148
|
+
{getInitials(item[nameKey])}
|
|
149
|
+
</div>
|
|
150
|
+
) : (
|
|
151
|
+
<img
|
|
152
|
+
src={item[imgSrcKey]}
|
|
153
|
+
alt={item[nameKey]}
|
|
154
|
+
className={`message-card-img ${imgClassName || ""}`}
|
|
155
|
+
style={imgStyle}
|
|
156
|
+
onError={() => handleImageError(item.id || index)} // Pass card id or index
|
|
157
|
+
/>
|
|
158
|
+
)}
|
|
118
159
|
<div
|
|
119
|
-
className={`message-card-
|
|
120
|
-
style={
|
|
160
|
+
className={`message-card-info ${infoClassName || ""}`}
|
|
161
|
+
style={infoStyle}
|
|
121
162
|
>
|
|
122
|
-
|
|
163
|
+
<h3
|
|
164
|
+
className={`message-card-name ${nameClassName || ""}`}
|
|
165
|
+
style={nameStyle}
|
|
166
|
+
>
|
|
167
|
+
{item[nameKey]}
|
|
168
|
+
</h3>
|
|
169
|
+
<p
|
|
170
|
+
className={`message-card-date ${dateClassName || ""}`}
|
|
171
|
+
style={dateStyle}
|
|
172
|
+
>
|
|
173
|
+
{item[dateKey]}
|
|
174
|
+
</p>
|
|
123
175
|
</div>
|
|
124
|
-
) : (
|
|
125
|
-
<img
|
|
126
|
-
src={item[imgSrcKey]}
|
|
127
|
-
alt={item[nameKey]}
|
|
128
|
-
className={`message-card-img ${imgClassName || ""}`}
|
|
129
|
-
style={imgStyle}
|
|
130
|
-
onError={() => handleImageError(item.id || index)} // Pass card id or index
|
|
131
|
-
/>
|
|
132
|
-
)}
|
|
133
|
-
<div
|
|
134
|
-
className={`message-card-info ${infoClassName || ""}`}
|
|
135
|
-
style={infoStyle}
|
|
136
|
-
>
|
|
137
|
-
<h3
|
|
138
|
-
className={`message-card-name ${nameClassName || ""}`}
|
|
139
|
-
style={nameStyle}
|
|
140
|
-
>
|
|
141
|
-
{item[nameKey]}
|
|
142
|
-
</h3>
|
|
143
|
-
<p
|
|
144
|
-
className={`message-card-date ${dateClassName || ""}`}
|
|
145
|
-
style={dateStyle}
|
|
146
|
-
>
|
|
147
|
-
{item[dateKey]}
|
|
148
|
-
</p>
|
|
149
176
|
</div>
|
|
177
|
+
|
|
178
|
+
{/* Item identifier in top-right corner */}
|
|
179
|
+
{item[itemNameKey] && (
|
|
180
|
+
<div
|
|
181
|
+
className={`message-card-item-name ${itemNameClassName || ""}`}
|
|
182
|
+
style={itemNameStyle}
|
|
183
|
+
>
|
|
184
|
+
{item[itemNameKey]}
|
|
185
|
+
</div>
|
|
186
|
+
)}
|
|
150
187
|
</div>
|
|
151
188
|
<div
|
|
152
189
|
className={`message-card-body ${bodyClassName || ""}`}
|
|
@@ -160,7 +197,10 @@ export const ShowMessageCard: React.FC<ShowMessageCardProps> = ({
|
|
|
160
197
|
|
|
161
198
|
{/* Display attached image if available */}
|
|
162
199
|
{item?.[imageUrlKey] && (
|
|
163
|
-
<div
|
|
200
|
+
<div
|
|
201
|
+
className={`message-card-attached-image-container ${attachedImageContainerClassName || ""}`}
|
|
202
|
+
style={attachedImageContainerStyle}
|
|
203
|
+
>
|
|
164
204
|
<img
|
|
165
205
|
src={item[imageUrlKey]}
|
|
166
206
|
alt="Attached"
|
|
@@ -169,6 +209,22 @@ export const ShowMessageCard: React.FC<ShowMessageCardProps> = ({
|
|
|
169
209
|
/>
|
|
170
210
|
</div>
|
|
171
211
|
)}
|
|
212
|
+
|
|
213
|
+
{/* Display hashtags and mentions as chips */}
|
|
214
|
+
{(hashtags.length > 0 || mentions.length > 0) && (
|
|
215
|
+
<div className="message-card-tags">
|
|
216
|
+
{hashtags.map((tag, tagIndex) => (
|
|
217
|
+
<span key={`hashtag-${tagIndex}`} className="tag-chip hashtag-chip">
|
|
218
|
+
{tag}
|
|
219
|
+
</span>
|
|
220
|
+
))}
|
|
221
|
+
{mentions.map((mention, mentionIndex) => (
|
|
222
|
+
<span key={`mention-${mentionIndex}`} className="tag-chip mention-chip">
|
|
223
|
+
{mention}
|
|
224
|
+
</span>
|
|
225
|
+
))}
|
|
226
|
+
</div>
|
|
227
|
+
)}
|
|
172
228
|
</div>
|
|
173
229
|
</div>
|
|
174
230
|
);
|