react-mention-input 1.1.1 → 1.1.3
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/README.md +6 -3
- package/dist/MentionInput.d.ts +5 -1
- package/dist/MentionInput.js +40 -41
- package/dist/ShowMessageCard.css +6 -0
- package/dist/ShowMessageCard.d.ts +2 -2
- package/dist/ShowMessageCard.js +37 -20
- package/package.json +1 -1
- package/src/MentionInput.tsx +60 -56
- package/src/ShowMessageCard.css +6 -0
- package/src/ShowMessageCard.tsx +47 -30
package/README.md
CHANGED
|
@@ -37,7 +37,8 @@ A customizable input component with @mention functionality.
|
|
|
37
37
|
| `suggestionListClassName` | `string` | Custom class name for the suggestion list. |
|
|
38
38
|
| `suggestionItemClassName` | `string` | Custom class name for each suggestion item. |
|
|
39
39
|
| `sendButtonIcon` | `ReactNode` | Custom icon for the send button (MUI icon or image path). |
|
|
40
|
-
| `onSendMessage` | `(
|
|
40
|
+
| `onSendMessage` | `(obj: {messageText: string, messageHTML: string}) => void` | Callback function triggered on sending a message, providing both plain text and HTML. |
|
|
41
|
+
| `suggestionPosition` | `'top' | 'bottom' | 'left' | 'right'` | Position of the suggestion dropdown relative to the input. Default is `bottom`. |
|
|
41
42
|
|
|
42
43
|
#### Example Usage
|
|
43
44
|
|
|
@@ -51,8 +52,9 @@ const users = [
|
|
|
51
52
|
];
|
|
52
53
|
|
|
53
54
|
function App() {
|
|
54
|
-
const handleSendMessage = (
|
|
55
|
-
console.log('Message
|
|
55
|
+
const handleSendMessage = ({messageText, messageHTML}) => {
|
|
56
|
+
console.log('Message Text:', messageText);
|
|
57
|
+
console.log('Message HTML:', messageHTML);
|
|
56
58
|
};
|
|
57
59
|
|
|
58
60
|
return (
|
|
@@ -61,6 +63,7 @@ function App() {
|
|
|
61
63
|
placeholder="Type @ to mention someone..."
|
|
62
64
|
sendButtonIcon={<SendIcon />} // Optional MUI icon or image path
|
|
63
65
|
onSendMessage={handleSendMessage}
|
|
66
|
+
suggestionPosition="top" // Customize suggestion position (top, bottom, left, right)
|
|
64
67
|
/>
|
|
65
68
|
);
|
|
66
69
|
}
|
package/dist/MentionInput.d.ts
CHANGED
|
@@ -14,7 +14,11 @@ interface MentionInputProps {
|
|
|
14
14
|
suggestionListClassName?: string;
|
|
15
15
|
suggestionItemClassName?: string;
|
|
16
16
|
sendButtonIcon?: ReactNode;
|
|
17
|
-
onSendMessage?: (
|
|
17
|
+
onSendMessage?: (obj: {
|
|
18
|
+
messageText: string;
|
|
19
|
+
messageHTML: string;
|
|
20
|
+
}) => void;
|
|
21
|
+
suggestionPosition?: 'top' | 'bottom' | 'left' | 'right';
|
|
18
22
|
}
|
|
19
23
|
declare const MentionInput: React.FC<MentionInputProps>;
|
|
20
24
|
export default MentionInput;
|
package/dist/MentionInput.js
CHANGED
|
@@ -2,17 +2,15 @@ import React, { useState, useRef } from "react";
|
|
|
2
2
|
import ReactDOM from "react-dom";
|
|
3
3
|
import "./MentionInput.css";
|
|
4
4
|
var MentionInput = function (_a) {
|
|
5
|
-
var users = _a.users, _b = _a.placeholder, placeholder = _b === void 0 ? "Type a message..." : _b, containerClassName = _a.containerClassName, inputContainerClassName = _a.inputContainerClassName, inputClassName = _a.inputClassName, sendBtnClassName = _a.sendBtnClassName, suggestionListClassName = _a.suggestionListClassName, suggestionItemClassName = _a.suggestionItemClassName, sendButtonIcon = _a.sendButtonIcon, onSendMessage = _a.onSendMessage;
|
|
6
|
-
var
|
|
7
|
-
var
|
|
8
|
-
var
|
|
9
|
-
var _f = useState('bottom'), tooltipPosition = _f[0], setTooltipPosition = _f[1];
|
|
5
|
+
var users = _a.users, _b = _a.placeholder, placeholder = _b === void 0 ? "Type a message..." : _b, containerClassName = _a.containerClassName, inputContainerClassName = _a.inputContainerClassName, inputClassName = _a.inputClassName, sendBtnClassName = _a.sendBtnClassName, suggestionListClassName = _a.suggestionListClassName, suggestionItemClassName = _a.suggestionItemClassName, sendButtonIcon = _a.sendButtonIcon, onSendMessage = _a.onSendMessage, _c = _a.suggestionPosition, suggestionPosition = _c === void 0 ? 'bottom' : _c;
|
|
6
|
+
var _d = useState(""), inputValue = _d[0], setInputValue = _d[1]; // Plain text
|
|
7
|
+
var _e = useState([]), suggestions = _e[0], setSuggestions = _e[1];
|
|
8
|
+
var _f = useState(false), showSuggestions = _f[0], setShowSuggestions = _f[1];
|
|
10
9
|
var inputRef = useRef(null);
|
|
11
10
|
var suggestionListRef = useRef(null);
|
|
12
11
|
var caretOffsetRef = useRef(0);
|
|
13
12
|
var highlightMentions = function (text) {
|
|
14
|
-
return text.replace(/@([^\s]+(?: [^\s]+)?)(?=\s|$)/g,
|
|
15
|
-
'<span class="mention-highlight">@$1</span>');
|
|
13
|
+
return text.replace(/@([^\s]+(?: [^\s]+)?)(?=\s|$)/g, '<span class="mention-highlight">@$1</span>');
|
|
16
14
|
};
|
|
17
15
|
var restoreCaretPosition = function (node, caretOffset) {
|
|
18
16
|
var range = document.createRange();
|
|
@@ -56,12 +54,11 @@ var MentionInput = function (_a) {
|
|
|
56
54
|
var preCaretRange = range.cloneRange();
|
|
57
55
|
preCaretRange.selectNodeContents(inputRef.current);
|
|
58
56
|
preCaretRange.setEnd(range.startContainer, range.startOffset);
|
|
59
|
-
newCaretOffset = preCaretRange.toString().length;
|
|
57
|
+
newCaretOffset = preCaretRange.toString().length;
|
|
60
58
|
}
|
|
61
|
-
caretOffsetRef.current = newCaretOffset;
|
|
59
|
+
caretOffsetRef.current = newCaretOffset;
|
|
62
60
|
var plainText = inputRef.current.innerText;
|
|
63
61
|
setInputValue(plainText);
|
|
64
|
-
// Handle mentions and suggestions
|
|
65
62
|
var mentionMatch = plainText.slice(0, newCaretOffset).match(/@(\S*)$/);
|
|
66
63
|
if (mentionMatch) {
|
|
67
64
|
var query_1 = mentionMatch[1].toLowerCase();
|
|
@@ -79,43 +76,16 @@ var MentionInput = function (_a) {
|
|
|
79
76
|
}
|
|
80
77
|
restoreCaretPosition(inputRef.current, newCaretOffset);
|
|
81
78
|
};
|
|
82
|
-
var handleSuggestionClick = function (user) {
|
|
83
|
-
if (!inputRef.current)
|
|
84
|
-
return;
|
|
85
|
-
var plainText = inputValue; // Current input value
|
|
86
|
-
var caretOffset = caretOffsetRef.current; // Use ref value for caret offset
|
|
87
|
-
var mentionMatch = plainText.slice(0, caretOffset).match(/@(\S*)$/);
|
|
88
|
-
if (!mentionMatch)
|
|
89
|
-
return;
|
|
90
|
-
var mentionIndex = plainText.slice(0, caretOffset).lastIndexOf("@");
|
|
91
|
-
var newValue = plainText.substring(0, mentionIndex + 1) + user.name + plainText.substring(caretOffset);
|
|
92
|
-
setInputValue(newValue);
|
|
93
|
-
inputRef.current.innerText = newValue;
|
|
94
|
-
var htmlWithHighlights = highlightMentions(newValue);
|
|
95
|
-
inputRef.current.innerHTML = htmlWithHighlights;
|
|
96
|
-
setShowSuggestions(false);
|
|
97
|
-
var mentionEnd = mentionIndex + user.name.length + 1;
|
|
98
|
-
restoreCaretPosition(inputRef.current, mentionEnd);
|
|
99
|
-
};
|
|
100
|
-
var handleSendMessage = function () {
|
|
101
|
-
if (inputValue.trim() && onSendMessage) {
|
|
102
|
-
onSendMessage(inputValue.trim());
|
|
103
|
-
setInputValue("");
|
|
104
|
-
setShowSuggestions(false);
|
|
105
|
-
if (inputRef.current)
|
|
106
|
-
inputRef.current.innerText = "";
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
79
|
var renderSuggestions = function () {
|
|
110
80
|
if (!showSuggestions || !inputRef.current)
|
|
111
81
|
return null;
|
|
112
82
|
var inputRect = inputRef.current.getBoundingClientRect();
|
|
113
83
|
var styles = {
|
|
114
84
|
position: 'absolute',
|
|
115
|
-
zIndex: 1000
|
|
85
|
+
zIndex: 1000,
|
|
116
86
|
};
|
|
117
|
-
//
|
|
118
|
-
switch (
|
|
87
|
+
// Use suggestionPosition prop to adjust tooltip position
|
|
88
|
+
switch (suggestionPosition) {
|
|
119
89
|
case 'top':
|
|
120
90
|
styles.left = "".concat(inputRect.left, "px");
|
|
121
91
|
styles.top = "".concat(inputRect.top - 150, "px");
|
|
@@ -135,10 +105,39 @@ var MentionInput = function (_a) {
|
|
|
135
105
|
default:
|
|
136
106
|
break;
|
|
137
107
|
}
|
|
138
|
-
console.log("document.body", document.body);
|
|
139
108
|
return ReactDOM.createPortal(React.createElement("ul", { className: "suggestion-list ".concat(suggestionListClassName || ''), ref: suggestionListRef, style: styles }, suggestions.map(function (user) { return (React.createElement("li", { key: user.id, onClick: function () { return handleSuggestionClick(user); }, className: "suggestion-item ".concat(suggestionItemClassName || ''), role: "option", tabIndex: 0, "aria-selected": "false" }, user.name)); })), window.document.body // Render in portal
|
|
140
109
|
);
|
|
141
110
|
};
|
|
111
|
+
var handleSuggestionClick = function (user) {
|
|
112
|
+
if (!inputRef.current)
|
|
113
|
+
return;
|
|
114
|
+
var plainText = inputValue;
|
|
115
|
+
var caretOffset = caretOffsetRef.current;
|
|
116
|
+
var mentionMatch = plainText.slice(0, caretOffset).match(/@(\S*)$/);
|
|
117
|
+
if (!mentionMatch)
|
|
118
|
+
return;
|
|
119
|
+
var mentionIndex = plainText.slice(0, caretOffset).lastIndexOf("@");
|
|
120
|
+
var newValue = plainText.substring(0, mentionIndex + 1) + user.name + plainText.substring(caretOffset);
|
|
121
|
+
setInputValue(newValue);
|
|
122
|
+
inputRef.current.innerText = newValue;
|
|
123
|
+
var htmlWithHighlights = highlightMentions(newValue);
|
|
124
|
+
inputRef.current.innerHTML = htmlWithHighlights;
|
|
125
|
+
setShowSuggestions(false);
|
|
126
|
+
var mentionEnd = mentionIndex + user.name.length + 1;
|
|
127
|
+
restoreCaretPosition(inputRef.current, mentionEnd);
|
|
128
|
+
};
|
|
129
|
+
var handleSendMessage = function () {
|
|
130
|
+
if (inputRef.current) {
|
|
131
|
+
var messageText = inputRef.current.innerText.trim(); // Plain text
|
|
132
|
+
var messageHTML = inputRef.current.innerHTML.trim(); // HTML with <span> highlighting
|
|
133
|
+
if (messageText && onSendMessage) {
|
|
134
|
+
onSendMessage({ messageText: messageText, messageHTML: messageHTML }); // Pass both plain text and HTML
|
|
135
|
+
setInputValue(""); // Clear state
|
|
136
|
+
setShowSuggestions(false); // Hide suggestions
|
|
137
|
+
inputRef.current.innerText = ""; // Clear input field
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
};
|
|
142
141
|
return (React.createElement("div", { className: "mention-container ".concat(containerClassName || "") },
|
|
143
142
|
React.createElement("div", { className: "mention-input-container ".concat(inputContainerClassName || "") },
|
|
144
143
|
React.createElement("div", { ref: inputRef, contentEditable: true, suppressContentEditableWarning: true, className: "mention-input ".concat(inputClassName || ""), onInput: handleInputChange }),
|
package/dist/ShowMessageCard.css
CHANGED
package/dist/ShowMessageCard.js
CHANGED
|
@@ -1,32 +1,49 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
var __assign = (this && this.__assign) || function () {
|
|
2
|
+
__assign = Object.assign || function(t) {
|
|
3
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
4
|
+
s = arguments[i];
|
|
5
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
6
|
+
t[p] = s[p];
|
|
7
|
+
}
|
|
8
|
+
return t;
|
|
9
|
+
};
|
|
10
|
+
return __assign.apply(this, arguments);
|
|
11
|
+
};
|
|
12
|
+
import React, { useState } from "react";
|
|
13
|
+
import "./ShowMessageCard.css";
|
|
3
14
|
export var ShowMessageCard = function (_a) {
|
|
4
|
-
var data = _a.data, _b = _a.nameKey, nameKey = _b === void 0 ?
|
|
5
|
-
|
|
15
|
+
var data = _a.data, _b = _a.nameKey, nameKey = _b === void 0 ? "name" : _b, _c = _a.dateKey, dateKey = _c === void 0 ? "date" : _c, _d = _a.commentKey, commentKey = _d === void 0 ? "comment" : _d, _e = _a.imgSrcKey, imgSrcKey = _e === void 0 ? "imgSrc" : _e, containerClassName = _a.containerClassName, containerStyle = _a.containerStyle, cardClassName = _a.cardClassName, cardStyle = _a.cardStyle, headerClassName = _a.headerClassName, headerStyle = _a.headerStyle, imgClassName = _a.imgClassName, imgStyle = _a.imgStyle, infoClassName = _a.infoClassName, infoStyle = _a.infoStyle, nameClassName = _a.nameClassName, nameStyle = _a.nameStyle, dateClassName = _a.dateClassName, dateStyle = _a.dateStyle, bodyClassName = _a.bodyClassName, bodyStyle = _a.bodyStyle, commentClassName = _a.commentClassName, commentStyle = _a.commentStyle, renderItem = _a.renderItem;
|
|
16
|
+
// State to manage initials for images that fail to load
|
|
17
|
+
var _f = useState({}), initialsState = _f[0], setInitialsState = _f[1];
|
|
18
|
+
// Handle image load failure
|
|
19
|
+
var handleImageError = function (id) {
|
|
20
|
+
setInitialsState(function (prevState) {
|
|
21
|
+
var _a;
|
|
22
|
+
return (__assign(__assign({}, prevState), (_a = {}, _a[id] = true, _a)));
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
// Helper function to generate initials from the name
|
|
6
26
|
var getInitials = function (name) {
|
|
7
|
-
var nameParts = name.split(
|
|
27
|
+
var nameParts = name.split(" ");
|
|
8
28
|
var initials = nameParts
|
|
9
|
-
.map(function (part) { var _a; return ((_a = part[0]) === null || _a === void 0 ? void 0 : _a.toUpperCase()) ||
|
|
29
|
+
.map(function (part) { var _a; return ((_a = part[0]) === null || _a === void 0 ? void 0 : _a.toUpperCase()) || ""; }) // Take the first letter of each part
|
|
10
30
|
.slice(0, 2) // Limit to 2 letters
|
|
11
|
-
.join(
|
|
31
|
+
.join("");
|
|
12
32
|
return initials;
|
|
13
33
|
};
|
|
14
|
-
return (React.createElement("div", { className: "message-card-container ".concat(containerClassName ||
|
|
34
|
+
return (React.createElement("div", { className: "message-card-container ".concat(containerClassName || ""), style: containerStyle }, data.map(function (item, index) {
|
|
15
35
|
if (renderItem !== undefined) {
|
|
16
36
|
// Use custom render function if provided
|
|
17
37
|
return (React.createElement(React.Fragment, { key: item.id || index }, renderItem(item)));
|
|
18
38
|
}
|
|
19
|
-
var
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
React.createElement("p", { className: "message-card-date ".concat(dateClassName || ''), style: dateStyle }, item[dateKey]))),
|
|
29
|
-
React.createElement("div", { className: "message-card-body ".concat(bodyClassName || ''), style: bodyStyle },
|
|
30
|
-
React.createElement("p", { className: "message-card-comment ".concat(commentClassName || ''), style: commentStyle }, item[commentKey]))));
|
|
39
|
+
var showInitials = initialsState[item.id || index] || !item[imgSrcKey]; // Decide whether to show initials
|
|
40
|
+
return (React.createElement("div", { key: item.id || index, className: "message-card ".concat(cardClassName || ""), style: cardStyle },
|
|
41
|
+
React.createElement("div", { className: "message-card-header ".concat(headerClassName || ""), style: headerStyle },
|
|
42
|
+
showInitials ? (React.createElement("div", { className: "message-card-initials ".concat(imgClassName || ""), style: imgStyle }, getInitials(item[nameKey]))) : (React.createElement("img", { src: item[imgSrcKey], alt: item[nameKey], className: "message-card-img ".concat(imgClassName || ""), style: imgStyle, onError: function () { return handleImageError(item.id || index); } })),
|
|
43
|
+
React.createElement("div", { className: "message-card-info ".concat(infoClassName || ""), style: infoStyle },
|
|
44
|
+
React.createElement("h3", { className: "message-card-name ".concat(nameClassName || ""), style: nameStyle }, item[nameKey]),
|
|
45
|
+
React.createElement("p", { className: "message-card-date ".concat(dateClassName || ""), style: dateStyle }, item[dateKey]))),
|
|
46
|
+
React.createElement("div", { className: "message-card-body ".concat(bodyClassName || ""), style: bodyStyle },
|
|
47
|
+
React.createElement("p", { className: "message-card-comment ".concat(commentClassName || ""), style: commentStyle, dangerouslySetInnerHTML: { __html: item[commentKey] } }))));
|
|
31
48
|
})));
|
|
32
49
|
};
|
package/package.json
CHANGED
package/src/MentionInput.tsx
CHANGED
|
@@ -16,8 +16,9 @@ interface MentionInputProps {
|
|
|
16
16
|
sendBtnClassName?: string;
|
|
17
17
|
suggestionListClassName?: string;
|
|
18
18
|
suggestionItemClassName?: string;
|
|
19
|
-
sendButtonIcon?: ReactNode; //
|
|
20
|
-
onSendMessage?: (
|
|
19
|
+
sendButtonIcon?: ReactNode; // Button icon (MUI icon or image path)
|
|
20
|
+
onSendMessage?: (obj:{messageText: string, messageHTML: string}) => void;
|
|
21
|
+
suggestionPosition?: 'top' | 'bottom' | 'left' | 'right'; // New prop for tooltip position
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
const MentionInput: React.FC<MentionInputProps> = ({
|
|
@@ -31,22 +32,19 @@ const MentionInput: React.FC<MentionInputProps> = ({
|
|
|
31
32
|
suggestionItemClassName,
|
|
32
33
|
sendButtonIcon,
|
|
33
34
|
onSendMessage,
|
|
35
|
+
suggestionPosition = 'bottom', // Default position is bottom
|
|
34
36
|
}) => {
|
|
35
37
|
const [inputValue, setInputValue] = useState<string>(""); // Plain text
|
|
36
38
|
const [suggestions, setSuggestions] = useState<User[]>([]);
|
|
37
39
|
const [showSuggestions, setShowSuggestions] = useState<boolean>(false);
|
|
38
|
-
const [tooltipPosition, setTooltipPosition] = useState<'top' | 'bottom' | 'left' | 'right'>(
|
|
39
|
-
'bottom'
|
|
40
|
-
);
|
|
41
40
|
|
|
42
41
|
const inputRef = useRef<HTMLDivElement>(null);
|
|
43
42
|
const suggestionListRef = useRef<HTMLUListElement>(null);
|
|
44
43
|
const caretOffsetRef = useRef<number>(0);
|
|
45
|
-
|
|
46
44
|
|
|
47
45
|
const highlightMentions = (text: string): string => {
|
|
48
46
|
return text.replace(
|
|
49
|
-
/@([^\s]+(?: [^\s]+)?)(?=\s|$)/g,
|
|
47
|
+
/@([^\s]+(?: [^\s]+)?)(?=\s|$)/g,
|
|
50
48
|
'<span class="mention-highlight">@$1</span>'
|
|
51
49
|
);
|
|
52
50
|
};
|
|
@@ -84,76 +82,41 @@ const MentionInput: React.FC<MentionInputProps> = ({
|
|
|
84
82
|
|
|
85
83
|
const handleInputChange = () => {
|
|
86
84
|
if (!inputRef.current) return;
|
|
87
|
-
|
|
85
|
+
|
|
88
86
|
const selection = window.getSelection();
|
|
89
87
|
const range = selection?.getRangeAt(0);
|
|
90
|
-
|
|
88
|
+
|
|
91
89
|
let newCaretOffset = 0;
|
|
92
90
|
if (range && inputRef.current.contains(range.startContainer)) {
|
|
93
91
|
const preCaretRange = range.cloneRange();
|
|
94
92
|
preCaretRange.selectNodeContents(inputRef.current);
|
|
95
93
|
preCaretRange.setEnd(range.startContainer, range.startOffset);
|
|
96
|
-
newCaretOffset = preCaretRange.toString().length;
|
|
94
|
+
newCaretOffset = preCaretRange.toString().length;
|
|
97
95
|
}
|
|
98
|
-
|
|
99
|
-
caretOffsetRef.current = newCaretOffset;
|
|
100
|
-
|
|
96
|
+
|
|
97
|
+
caretOffsetRef.current = newCaretOffset;
|
|
98
|
+
|
|
101
99
|
const plainText = inputRef.current.innerText;
|
|
102
100
|
setInputValue(plainText);
|
|
103
|
-
|
|
104
|
-
// Handle mentions and suggestions
|
|
101
|
+
|
|
105
102
|
const mentionMatch = plainText.slice(0, newCaretOffset).match(/@(\S*)$/);
|
|
106
103
|
if (mentionMatch) {
|
|
107
104
|
const query = mentionMatch[1].toLowerCase();
|
|
108
105
|
const filteredUsers = query === "" ? users : users.filter((user) => user.name.toLowerCase().startsWith(query));
|
|
109
|
-
|
|
106
|
+
|
|
110
107
|
setSuggestions(filteredUsers);
|
|
111
108
|
setShowSuggestions(filteredUsers.length > 0);
|
|
112
109
|
} else {
|
|
113
110
|
setShowSuggestions(false);
|
|
114
111
|
}
|
|
115
|
-
|
|
112
|
+
|
|
116
113
|
const previousHTML = inputRef.current.innerHTML;
|
|
117
114
|
const htmlWithHighlights = highlightMentions(plainText);
|
|
118
115
|
if (previousHTML !== htmlWithHighlights) {
|
|
119
116
|
inputRef.current.innerHTML = htmlWithHighlights;
|
|
120
117
|
}
|
|
121
|
-
|
|
122
|
-
restoreCaretPosition(inputRef.current, newCaretOffset);
|
|
123
|
-
};
|
|
124
118
|
|
|
125
|
-
|
|
126
|
-
if (!inputRef.current) return;
|
|
127
|
-
|
|
128
|
-
const plainText = inputValue; // Current input value
|
|
129
|
-
const caretOffset = caretOffsetRef.current; // Use ref value for caret offset
|
|
130
|
-
const mentionMatch = plainText.slice(0, caretOffset).match(/@(\S*)$/);
|
|
131
|
-
|
|
132
|
-
if (!mentionMatch) return;
|
|
133
|
-
|
|
134
|
-
const mentionIndex = plainText.slice(0, caretOffset).lastIndexOf("@");
|
|
135
|
-
|
|
136
|
-
const newValue =
|
|
137
|
-
plainText.substring(0, mentionIndex + 1) + user.name + plainText.substring(caretOffset);
|
|
138
|
-
|
|
139
|
-
setInputValue(newValue);
|
|
140
|
-
inputRef.current.innerText = newValue;
|
|
141
|
-
|
|
142
|
-
const htmlWithHighlights = highlightMentions(newValue);
|
|
143
|
-
inputRef.current.innerHTML = htmlWithHighlights;
|
|
144
|
-
|
|
145
|
-
setShowSuggestions(false);
|
|
146
|
-
|
|
147
|
-
const mentionEnd = mentionIndex + user.name.length + 1;
|
|
148
|
-
restoreCaretPosition(inputRef.current, mentionEnd);
|
|
149
|
-
};
|
|
150
|
-
const handleSendMessage = () => {
|
|
151
|
-
if (inputValue.trim() && onSendMessage) {
|
|
152
|
-
onSendMessage(inputValue.trim());
|
|
153
|
-
setInputValue("");
|
|
154
|
-
setShowSuggestions(false);
|
|
155
|
-
if (inputRef.current) inputRef.current.innerText = "";
|
|
156
|
-
}
|
|
119
|
+
restoreCaretPosition(inputRef.current, newCaretOffset);
|
|
157
120
|
};
|
|
158
121
|
|
|
159
122
|
const renderSuggestions = () => {
|
|
@@ -162,11 +125,11 @@ const MentionInput: React.FC<MentionInputProps> = ({
|
|
|
162
125
|
const inputRect = inputRef.current.getBoundingClientRect();
|
|
163
126
|
const styles: React.CSSProperties = {
|
|
164
127
|
position: 'absolute',
|
|
165
|
-
zIndex: 1000
|
|
128
|
+
zIndex: 1000,
|
|
166
129
|
};
|
|
167
130
|
|
|
168
|
-
//
|
|
169
|
-
switch (
|
|
131
|
+
// Use suggestionPosition prop to adjust tooltip position
|
|
132
|
+
switch (suggestionPosition) {
|
|
170
133
|
case 'top':
|
|
171
134
|
styles.left = `${inputRect.left}px`;
|
|
172
135
|
styles.top = `${inputRect.top - 150}px`;
|
|
@@ -187,7 +150,6 @@ const MentionInput: React.FC<MentionInputProps> = ({
|
|
|
187
150
|
break;
|
|
188
151
|
}
|
|
189
152
|
|
|
190
|
-
console.log("document.body",document.body);
|
|
191
153
|
return ReactDOM.createPortal(
|
|
192
154
|
<ul
|
|
193
155
|
className={`suggestion-list ${suggestionListClassName || ''}`}
|
|
@@ -211,6 +173,48 @@ const MentionInput: React.FC<MentionInputProps> = ({
|
|
|
211
173
|
);
|
|
212
174
|
};
|
|
213
175
|
|
|
176
|
+
const handleSuggestionClick = (user: User) => {
|
|
177
|
+
if (!inputRef.current) return;
|
|
178
|
+
|
|
179
|
+
const plainText = inputValue;
|
|
180
|
+
const caretOffset = caretOffsetRef.current;
|
|
181
|
+
const mentionMatch = plainText.slice(0, caretOffset).match(/@(\S*)$/);
|
|
182
|
+
|
|
183
|
+
if (!mentionMatch) return;
|
|
184
|
+
|
|
185
|
+
const mentionIndex = plainText.slice(0, caretOffset).lastIndexOf("@");
|
|
186
|
+
|
|
187
|
+
const newValue =
|
|
188
|
+
plainText.substring(0, mentionIndex + 1) + user.name + plainText.substring(caretOffset);
|
|
189
|
+
|
|
190
|
+
setInputValue(newValue);
|
|
191
|
+
inputRef.current.innerText = newValue;
|
|
192
|
+
|
|
193
|
+
const htmlWithHighlights = highlightMentions(newValue);
|
|
194
|
+
inputRef.current.innerHTML = htmlWithHighlights;
|
|
195
|
+
|
|
196
|
+
setShowSuggestions(false);
|
|
197
|
+
|
|
198
|
+
const mentionEnd = mentionIndex + user.name.length + 1;
|
|
199
|
+
restoreCaretPosition(inputRef.current, mentionEnd);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const handleSendMessage = () => {
|
|
203
|
+
if (inputRef.current) {
|
|
204
|
+
const messageText = inputRef.current.innerText.trim(); // Plain text
|
|
205
|
+
const messageHTML = inputRef.current.innerHTML.trim(); // HTML with <span> highlighting
|
|
206
|
+
|
|
207
|
+
if (messageText && onSendMessage) {
|
|
208
|
+
onSendMessage({messageText, messageHTML}); // Pass both plain text and HTML
|
|
209
|
+
setInputValue(""); // Clear state
|
|
210
|
+
setShowSuggestions(false); // Hide suggestions
|
|
211
|
+
inputRef.current.innerText = ""; // Clear input field
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
|
|
214
218
|
return (
|
|
215
219
|
<div className={`mention-container ${containerClassName || ""}`}>
|
|
216
220
|
<div className={`mention-input-container ${inputContainerClassName || ""}`}>
|
package/src/ShowMessageCard.css
CHANGED
package/src/ShowMessageCard.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React, { CSSProperties, useState, ReactNode } from
|
|
2
|
-
import
|
|
1
|
+
import React, { CSSProperties, useState, ReactNode } from "react";
|
|
2
|
+
import "./ShowMessageCard.css";
|
|
3
3
|
|
|
4
4
|
interface MessageCardProps {
|
|
5
5
|
[key: string]: any; // Support dynamic keys
|
|
@@ -34,10 +34,10 @@ interface ShowMessageCardProps {
|
|
|
34
34
|
|
|
35
35
|
export const ShowMessageCard: React.FC<ShowMessageCardProps> = ({
|
|
36
36
|
data,
|
|
37
|
-
nameKey =
|
|
38
|
-
dateKey =
|
|
39
|
-
commentKey =
|
|
40
|
-
imgSrcKey =
|
|
37
|
+
nameKey = "name",
|
|
38
|
+
dateKey = "date",
|
|
39
|
+
commentKey = "comment",
|
|
40
|
+
imgSrcKey = "imgSrc",
|
|
41
41
|
containerClassName,
|
|
42
42
|
containerStyle,
|
|
43
43
|
cardClassName,
|
|
@@ -56,19 +56,36 @@ export const ShowMessageCard: React.FC<ShowMessageCardProps> = ({
|
|
|
56
56
|
bodyStyle,
|
|
57
57
|
commentClassName,
|
|
58
58
|
commentStyle,
|
|
59
|
-
renderItem // Custom render function
|
|
59
|
+
renderItem, // Custom render function
|
|
60
60
|
}) => {
|
|
61
|
+
// State to manage initials for images that fail to load
|
|
62
|
+
const [initialsState, setInitialsState] = useState<{ [key: string]: boolean }>(
|
|
63
|
+
{}
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
// Handle image load failure
|
|
67
|
+
const handleImageError = (id: string | number) => {
|
|
68
|
+
setInitialsState((prevState) => ({
|
|
69
|
+
...prevState,
|
|
70
|
+
[id]: true, // Show initials for the specific card
|
|
71
|
+
}));
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// Helper function to generate initials from the name
|
|
61
75
|
const getInitials = (name: string) => {
|
|
62
|
-
const nameParts = name.split(
|
|
76
|
+
const nameParts = name.split(" ");
|
|
63
77
|
const initials = nameParts
|
|
64
|
-
.map((part) => part[0]?.toUpperCase() ||
|
|
78
|
+
.map((part) => part[0]?.toUpperCase() || "") // Take the first letter of each part
|
|
65
79
|
.slice(0, 2) // Limit to 2 letters
|
|
66
|
-
.join(
|
|
80
|
+
.join("");
|
|
67
81
|
return initials;
|
|
68
82
|
};
|
|
69
83
|
|
|
70
84
|
return (
|
|
71
|
-
<div
|
|
85
|
+
<div
|
|
86
|
+
className={`message-card-container ${containerClassName || ""}`}
|
|
87
|
+
style={containerStyle}
|
|
88
|
+
>
|
|
72
89
|
{data.map((item, index) => {
|
|
73
90
|
if (renderItem !== undefined) {
|
|
74
91
|
// Use custom render function if provided
|
|
@@ -79,25 +96,21 @@ export const ShowMessageCard: React.FC<ShowMessageCardProps> = ({
|
|
|
79
96
|
);
|
|
80
97
|
}
|
|
81
98
|
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
const handleImageError = () => {
|
|
85
|
-
setShowInitials(true);
|
|
86
|
-
};
|
|
99
|
+
const showInitials = initialsState[item.id || index] || !item[imgSrcKey]; // Decide whether to show initials
|
|
87
100
|
|
|
88
101
|
return (
|
|
89
102
|
<div
|
|
90
103
|
key={item.id || index}
|
|
91
|
-
className={`message-card ${cardClassName ||
|
|
104
|
+
className={`message-card ${cardClassName || ""}`}
|
|
92
105
|
style={cardStyle}
|
|
93
106
|
>
|
|
94
107
|
<div
|
|
95
|
-
className={`message-card-header ${headerClassName ||
|
|
108
|
+
className={`message-card-header ${headerClassName || ""}`}
|
|
96
109
|
style={headerStyle}
|
|
97
110
|
>
|
|
98
|
-
{showInitials
|
|
111
|
+
{showInitials ? (
|
|
99
112
|
<div
|
|
100
|
-
className={`message-card-initials ${imgClassName ||
|
|
113
|
+
className={`message-card-initials ${imgClassName || ""}`}
|
|
101
114
|
style={imgStyle}
|
|
102
115
|
>
|
|
103
116
|
{getInitials(item[nameKey])}
|
|
@@ -106,23 +119,23 @@ export const ShowMessageCard: React.FC<ShowMessageCardProps> = ({
|
|
|
106
119
|
<img
|
|
107
120
|
src={item[imgSrcKey]}
|
|
108
121
|
alt={item[nameKey]}
|
|
109
|
-
className={`message-card-img ${imgClassName ||
|
|
122
|
+
className={`message-card-img ${imgClassName || ""}`}
|
|
110
123
|
style={imgStyle}
|
|
111
|
-
onError={handleImageError}
|
|
124
|
+
onError={() => handleImageError(item.id || index)} // Pass card id or index
|
|
112
125
|
/>
|
|
113
126
|
)}
|
|
114
127
|
<div
|
|
115
|
-
className={`message-card-info ${infoClassName ||
|
|
128
|
+
className={`message-card-info ${infoClassName || ""}`}
|
|
116
129
|
style={infoStyle}
|
|
117
130
|
>
|
|
118
131
|
<h3
|
|
119
|
-
className={`message-card-name ${nameClassName ||
|
|
132
|
+
className={`message-card-name ${nameClassName || ""}`}
|
|
120
133
|
style={nameStyle}
|
|
121
134
|
>
|
|
122
135
|
{item[nameKey]}
|
|
123
136
|
</h3>
|
|
124
137
|
<p
|
|
125
|
-
className={`message-card-date ${dateClassName ||
|
|
138
|
+
className={`message-card-date ${dateClassName || ""}`}
|
|
126
139
|
style={dateStyle}
|
|
127
140
|
>
|
|
128
141
|
{item[dateKey]}
|
|
@@ -130,15 +143,20 @@ export const ShowMessageCard: React.FC<ShowMessageCardProps> = ({
|
|
|
130
143
|
</div>
|
|
131
144
|
</div>
|
|
132
145
|
<div
|
|
133
|
-
className={`message-card-body ${bodyClassName ||
|
|
146
|
+
className={`message-card-body ${bodyClassName || ""}`}
|
|
134
147
|
style={bodyStyle}
|
|
135
148
|
>
|
|
136
|
-
<p
|
|
137
|
-
className={`message-card-comment ${commentClassName ||
|
|
149
|
+
{/* <p
|
|
150
|
+
className={`message-card-comment ${commentClassName || ""}`}
|
|
138
151
|
style={commentStyle}
|
|
139
152
|
>
|
|
140
153
|
{item[commentKey]}
|
|
141
|
-
</p>
|
|
154
|
+
</p> */}
|
|
155
|
+
<p
|
|
156
|
+
className={`message-card-comment ${commentClassName || ""}`}
|
|
157
|
+
style={commentStyle}
|
|
158
|
+
dangerouslySetInnerHTML={{ __html: item[commentKey] }}
|
|
159
|
+
></p>
|
|
142
160
|
</div>
|
|
143
161
|
</div>
|
|
144
162
|
);
|
|
@@ -147,4 +165,3 @@ export const ShowMessageCard: React.FC<ShowMessageCardProps> = ({
|
|
|
147
165
|
);
|
|
148
166
|
};
|
|
149
167
|
|
|
150
|
-
|